diff --git a/.gitignore b/.gitignore index ee771c87..8cacf425 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ -vendor/ .phpunit.result.cache .php-cs-fixer.cache diff --git a/vendor/apereo/phpcas/CAS.php b/vendor/apereo/phpcas/CAS.php new file mode 100644 index 00000000..6ddcf07b --- /dev/null +++ b/vendor/apereo/phpcas/CAS.php @@ -0,0 +1,32 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +require_once __DIR__.'/source/CAS.php'; + +trigger_error('Including CAS.php is deprecated. Install phpCAS using composer instead.', E_USER_DEPRECATED); diff --git a/vendor/apereo/phpcas/LICENSE b/vendor/apereo/phpcas/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/vendor/apereo/phpcas/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/apereo/phpcas/NOTICE b/vendor/apereo/phpcas/NOTICE new file mode 100644 index 00000000..70d9ffcd --- /dev/null +++ b/vendor/apereo/phpcas/NOTICE @@ -0,0 +1,81 @@ +Copyright 2007-2011, JA-SIG, Inc. +This project includes software developed by Jasig. +http://www.jasig.org/ + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this software except in compliance with the License. +You may obtain a copy of the License at: + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +=========================================================================== + +Copyright © 2003-2007, The ESUP-Portail consortium + +Requirements for sources originally licensed under the New BSD License: + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +- Neither the name of JA-SIG, Inc. nor the names of its contributors may be +used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +=========================================================================== + +Copyright (c) 2009, Regents of the University of Nebraska +All rights reserved. + +Requirements for CAS_Autloader originally licensed under the New BSD License: + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of the University of Nebraska nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/apereo/phpcas/README.md b/vendor/apereo/phpcas/README.md new file mode 100644 index 00000000..d4812891 --- /dev/null +++ b/vendor/apereo/phpcas/README.md @@ -0,0 +1,35 @@ +phpCAS +======= + +phpCAS is an authentication library that allows PHP applications to easily authenticate +users via a Central Authentication Service (CAS) server. + +Please see the wiki website for more information: + +https://apereo.github.io/phpCAS/ + +Api documentation can be found here: + +https://apereo.github.io/phpCAS/api/ + + +[![Test](https://github.com/apereo/phpCAS/actions/workflows/test.yml/badge.svg)](https://github.com/apereo/phpCAS/actions/workflows/test.yml) + +LICENSE +------- + +Copyright 2007-2020, Apereo Foundation. +This project includes software developed by Apereo Foundation. +http://www.apereo.org/ + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this software except in compliance with the License. +You may obtain a copy of the License at: + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/vendor/apereo/phpcas/composer.json b/vendor/apereo/phpcas/composer.json new file mode 100644 index 00000000..bf8a17c4 --- /dev/null +++ b/vendor/apereo/phpcas/composer.json @@ -0,0 +1,55 @@ +{ + "name" : "apereo/phpcas", + "description" : "Provides a simple API for authenticating users against a CAS server", + "keywords" : [ + "cas", + "jasig", + "apereo" + ], + "homepage" : "https://wiki.jasig.org/display/CASC/phpCAS", + "type" : "library", + "license" : "Apache-2.0", + "authors" : [{ + "name" : "Joachim Fritschi", + "homepage" : "https://github.com/jfritschi", + "email" : "jfritschi@freenet.de" + }, { + "name" : "Adam Franco", + "homepage" : "https://github.com/adamfranco" + }, { + "name" : "Henry Pan", + "homepage" : "https://github.com/phy25" + } + ], + "require" : { + "php" : ">=7.1.0", + "ext-curl" : "*", + "ext-dom" : "*", + "psr/log" : "^1.0 || ^2.0 || ^3.0" + }, + "require-dev" : { + "monolog/monolog" : "^1.0.0 || ^2.0.0", + "phpunit/phpunit" : ">=7.5", + "phpstan/phpstan" : "^1.5" + }, + "autoload" : { + "files": ["source/CAS.php"], + "classmap" : [ + "source/" + ] + }, + "autoload-dev" : { + "psr-4" : { + "PhpCas\\" : "test/CAS/" + } + }, + "scripts" : { + "test" : "phpunit", + "phpstan" : "phpstan" + }, + "extra" : { + "branch-alias" : { + "dev-master" : "1.3.x-dev" + } + } +} diff --git a/vendor/apereo/phpcas/source/CAS.php b/vendor/apereo/phpcas/source/CAS.php new file mode 100644 index 00000000..71c04755 --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS.php @@ -0,0 +1,2083 @@ + + * @author Olivier Berger + * @author Brett Bieber + * @author Joachim Fritschi + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * @ingroup public + */ + +use Psr\Log\LoggerInterface; + +// +// hack by Vangelis Haniotakis to handle the absence of $_SERVER['REQUEST_URI'] +// in IIS +// +if (!isset($_SERVER['REQUEST_URI']) && isset($_SERVER['SCRIPT_NAME']) && isset($_SERVER['QUERY_STRING'])) { + $_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'] . '?' . $_SERVER['QUERY_STRING']; +} + + +// ######################################################################## +// CONSTANTS +// ######################################################################## + +// ------------------------------------------------------------------------ +// CAS VERSIONS +// ------------------------------------------------------------------------ + +/** + * phpCAS version. accessible for the user by phpCAS::getVersion(). + */ +define('PHPCAS_VERSION', '1.6.1'); + +/** + * @addtogroup public + * @{ + */ + +/** + * phpCAS supported protocols. accessible for the user by phpCAS::getSupportedProtocols(). + */ + +/** + * CAS version 1.0 + */ +define("CAS_VERSION_1_0", '1.0'); +/*! + * CAS version 2.0 +*/ +define("CAS_VERSION_2_0", '2.0'); +/** + * CAS version 3.0 + */ +define("CAS_VERSION_3_0", '3.0'); + +// ------------------------------------------------------------------------ +// SAML defines +// ------------------------------------------------------------------------ + +/** + * SAML protocol + */ +define("SAML_VERSION_1_1", 'S1'); + +/** + * XML header for SAML POST + */ +define("SAML_XML_HEADER", ''); + +/** + * SOAP envelope for SAML POST + */ +define("SAML_SOAP_ENV", ''); + +/** + * SOAP body for SAML POST + */ +define("SAML_SOAP_BODY", ''); + +/** + * SAMLP request + */ +define("SAMLP_REQUEST", ''); +define("SAMLP_REQUEST_CLOSE", ''); + +/** + * SAMLP artifact tag (for the ticket) + */ +define("SAML_ASSERTION_ARTIFACT", ''); + +/** + * SAMLP close + */ +define("SAML_ASSERTION_ARTIFACT_CLOSE", ''); + +/** + * SOAP body close + */ +define("SAML_SOAP_BODY_CLOSE", ''); + +/** + * SOAP envelope close + */ +define("SAML_SOAP_ENV_CLOSE", ''); + +/** + * SAML Attributes + */ +define("SAML_ATTRIBUTES", 'SAMLATTRIBS'); + +/** @} */ +/** + * @addtogroup publicPGTStorage + * @{ + */ +// ------------------------------------------------------------------------ +// FILE PGT STORAGE +// ------------------------------------------------------------------------ +/** + * Default path used when storing PGT's to file + */ +define("CAS_PGT_STORAGE_FILE_DEFAULT_PATH", session_save_path()); +/** @} */ +// ------------------------------------------------------------------------ +// SERVICE ACCESS ERRORS +// ------------------------------------------------------------------------ +/** + * @addtogroup publicServices + * @{ + */ + +/** + * phpCAS::service() error code on success + */ +define("PHPCAS_SERVICE_OK", 0); +/** + * phpCAS::service() error code when the PT could not retrieve because + * the CAS server did not respond. + */ +define("PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE", 1); +/** + * phpCAS::service() error code when the PT could not retrieve because + * the response of the CAS server was ill-formed. + */ +define("PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE", 2); +/** + * phpCAS::service() error code when the PT could not retrieve because + * the CAS server did not want to. + */ +define("PHPCAS_SERVICE_PT_FAILURE", 3); +/** + * phpCAS::service() error code when the service was not available. + */ +define("PHPCAS_SERVICE_NOT_AVAILABLE", 4); + +// ------------------------------------------------------------------------ +// SERVICE TYPES +// ------------------------------------------------------------------------ +/** + * phpCAS::getProxiedService() type for HTTP GET + */ +define("PHPCAS_PROXIED_SERVICE_HTTP_GET", 'CAS_ProxiedService_Http_Get'); +/** + * phpCAS::getProxiedService() type for HTTP POST + */ +define("PHPCAS_PROXIED_SERVICE_HTTP_POST", 'CAS_ProxiedService_Http_Post'); +/** + * phpCAS::getProxiedService() type for IMAP + */ +define("PHPCAS_PROXIED_SERVICE_IMAP", 'CAS_ProxiedService_Imap'); + + +/** @} */ +// ------------------------------------------------------------------------ +// LANGUAGES +// ------------------------------------------------------------------------ +/** + * @addtogroup publicLang + * @{ + */ + +define("PHPCAS_LANG_ENGLISH", 'CAS_Languages_English'); +define("PHPCAS_LANG_FRENCH", 'CAS_Languages_French'); +define("PHPCAS_LANG_GREEK", 'CAS_Languages_Greek'); +define("PHPCAS_LANG_GERMAN", 'CAS_Languages_German'); +define("PHPCAS_LANG_JAPANESE", 'CAS_Languages_Japanese'); +define("PHPCAS_LANG_SPANISH", 'CAS_Languages_Spanish'); +define("PHPCAS_LANG_CATALAN", 'CAS_Languages_Catalan'); +define("PHPCAS_LANG_CHINESE_SIMPLIFIED", 'CAS_Languages_ChineseSimplified'); +define("PHPCAS_LANG_GALEGO", 'CAS_Languages_Galego'); +define("PHPCAS_LANG_PORTUGUESE", 'CAS_Languages_Portuguese'); + +/** @} */ + +/** + * @addtogroup internalLang + * @{ + */ + +/** + * phpCAS default language (when phpCAS::setLang() is not used) + */ +define("PHPCAS_LANG_DEFAULT", PHPCAS_LANG_ENGLISH); + +/** @} */ +// ------------------------------------------------------------------------ +// DEBUG +// ------------------------------------------------------------------------ +/** + * @addtogroup publicDebug + * @{ + */ + +/** + * The default directory for the debug file under Unix. + * @return string directory for the debug file + */ +function gettmpdir() { +if (!empty($_ENV['TMP'])) { return realpath($_ENV['TMP']); } +if (!empty($_ENV['TMPDIR'])) { return realpath( $_ENV['TMPDIR']); } +if (!empty($_ENV['TEMP'])) { return realpath( $_ENV['TEMP']); } +return "/tmp"; +} +define('DEFAULT_DEBUG_DIR', gettmpdir()."/"); + +/** @} */ + +// include the class autoloader +require_once __DIR__ . '/CAS/Autoload.php'; + +/** + * The phpCAS class is a simple container for the phpCAS library. It provides CAS + * authentication for web applications written in PHP. + * + * @ingroup public + * @class phpCAS + * @category Authentication + * @package PhpCAS + * @author Pascal Aubry + * @author Olivier Berger + * @author Brett Bieber + * @author Joachim Fritschi + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +class phpCAS +{ + + /** + * This variable is used by the interface class phpCAS. + * + * @var CAS_Client + * @hideinitializer + */ + private static $_PHPCAS_CLIENT; + + /** + * @var array + * This variable is used to store where the initializer is called from + * (to print a comprehensive error in case of multiple calls). + * + * @hideinitializer + */ + private static $_PHPCAS_INIT_CALL; + + /** + * @var array + * This variable is used to store phpCAS debug mode. + * + * @hideinitializer + */ + private static $_PHPCAS_DEBUG; + + /** + * This variable is used to enable verbose mode + * This pevents debug info to be show to the user. Since it's a security + * feature the default is false + * + * @hideinitializer + */ + private static $_PHPCAS_VERBOSE = false; + + + // ######################################################################## + // INITIALIZATION + // ######################################################################## + + /** + * @addtogroup publicInit + * @{ + */ + + /** + * phpCAS client initializer. + * + * @param string $server_version the version of the CAS server + * @param string $server_hostname the hostname of the CAS server + * @param int $server_port the port the CAS server is running on + * @param string $server_uri the URI the CAS server is responding on + * @param string|string[]|CAS_ServiceBaseUrl_Interface + * $service_base_url the base URL (protocol, host and the + * optional port) of the CAS client; pass + * in an array to use auto discovery with + * an allowlist; pass in + * CAS_ServiceBaseUrl_Interface for custom + * behavior. Added in 1.6.0. Similar to + * serverName config in other CAS clients. + * @param bool $changeSessionID Allow phpCAS to change the session_id + * (Single Sign Out/handleLogoutRequests + * is based on that change) + * @param \SessionHandlerInterface $sessionHandler the session handler + * + * @return void a newly created CAS_Client object + * @note Only one of the phpCAS::client() and phpCAS::proxy functions should be + * called, only once, and before all other methods (except phpCAS::getVersion() + * and phpCAS::setDebug()). + */ + public static function client($server_version, $server_hostname, + $server_port, $server_uri, $service_base_url, + $changeSessionID = true, \SessionHandlerInterface $sessionHandler = null + ) { + phpCAS :: traceBegin(); + if (is_object(self::$_PHPCAS_CLIENT)) { + phpCAS :: error(self::$_PHPCAS_INIT_CALL['method'] . '() has already been called (at ' . self::$_PHPCAS_INIT_CALL['file'] . ':' . self::$_PHPCAS_INIT_CALL['line'] . ')'); + } + + // store where the initializer is called from + $dbg = debug_backtrace(); + self::$_PHPCAS_INIT_CALL = array ( + 'done' => true, + 'file' => $dbg[0]['file'], + 'line' => $dbg[0]['line'], + 'method' => __CLASS__ . '::' . __FUNCTION__ + ); + + // initialize the object $_PHPCAS_CLIENT + try { + self::$_PHPCAS_CLIENT = new CAS_Client( + $server_version, false, $server_hostname, $server_port, $server_uri, $service_base_url, + $changeSessionID, $sessionHandler + ); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + phpCAS :: traceEnd(); + } + + /** + * phpCAS proxy initializer. + * + * @param string $server_version the version of the CAS server + * @param string $server_hostname the hostname of the CAS server + * @param string $server_port the port the CAS server is running on + * @param string $server_uri the URI the CAS server is responding on + * @param string|string[]|CAS_ServiceBaseUrl_Interface + * $service_base_url the base URL (protocol, host and the + * optional port) of the CAS client; pass + * in an array to use auto discovery with + * an allowlist; pass in + * CAS_ServiceBaseUrl_Interface for custom + * behavior. Added in 1.6.0. Similar to + * serverName config in other CAS clients. + * @param bool $changeSessionID Allow phpCAS to change the session_id + * (Single Sign Out/handleLogoutRequests + * is based on that change) + * @param \SessionHandlerInterface $sessionHandler the session handler + * + * @return void a newly created CAS_Client object + * @note Only one of the phpCAS::client() and phpCAS::proxy functions should be + * called, only once, and before all other methods (except phpCAS::getVersion() + * and phpCAS::setDebug()). + */ + public static function proxy($server_version, $server_hostname, + $server_port, $server_uri, $service_base_url, + $changeSessionID = true, \SessionHandlerInterface $sessionHandler = null + ) { + phpCAS :: traceBegin(); + if (is_object(self::$_PHPCAS_CLIENT)) { + phpCAS :: error(self::$_PHPCAS_INIT_CALL['method'] . '() has already been called (at ' . self::$_PHPCAS_INIT_CALL['file'] . ':' . self::$_PHPCAS_INIT_CALL['line'] . ')'); + } + + // store where the initialzer is called from + $dbg = debug_backtrace(); + self::$_PHPCAS_INIT_CALL = array ( + 'done' => true, + 'file' => $dbg[0]['file'], + 'line' => $dbg[0]['line'], + 'method' => __CLASS__ . '::' . __FUNCTION__ + ); + + // initialize the object $_PHPCAS_CLIENT + try { + self::$_PHPCAS_CLIENT = new CAS_Client( + $server_version, true, $server_hostname, $server_port, $server_uri, $service_base_url, + $changeSessionID, $sessionHandler + ); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + phpCAS :: traceEnd(); + } + + /** + * Answer whether or not the client or proxy has been initialized + * + * @return bool + */ + public static function isInitialized () + { + return (is_object(self::$_PHPCAS_CLIENT)); + } + + /** @} */ + // ######################################################################## + // DEBUGGING + // ######################################################################## + + /** + * @addtogroup publicDebug + * @{ + */ + + /** + * Set/unset PSR-3 logger + * + * @param LoggerInterface $logger the PSR-3 logger used for logging, or + * null to stop logging. + * + * @return void + */ + public static function setLogger($logger = null) + { + if (empty(self::$_PHPCAS_DEBUG['unique_id'])) { + self::$_PHPCAS_DEBUG['unique_id'] = substr(strtoupper(md5(uniqid(''))), 0, 4); + } + self::$_PHPCAS_DEBUG['logger'] = $logger; + self::$_PHPCAS_DEBUG['indent'] = 0; + phpCAS :: trace('START ('.date("Y-m-d H:i:s").') phpCAS-' . PHPCAS_VERSION . ' ******************'); + } + + /** + * Set/unset debug mode + * + * @param string $filename the name of the file used for logging, or false + * to stop debugging. + * + * @return void + * + * @deprecated + */ + public static function setDebug($filename = '') + { + trigger_error('phpCAS::setDebug() is deprecated in favor of phpCAS::setLogger().', E_USER_DEPRECATED); + + if ($filename != false && gettype($filename) != 'string') { + phpCAS :: error('type mismatched for parameter $dbg (should be false or the name of the log file)'); + } + if ($filename === false) { + self::$_PHPCAS_DEBUG['filename'] = false; + + } else { + if (empty ($filename)) { + if (preg_match('/^Win.*/', getenv('OS'))) { + if (isset ($_ENV['TMP'])) { + $debugDir = $_ENV['TMP'] . '/'; + } else { + $debugDir = ''; + } + } else { + $debugDir = DEFAULT_DEBUG_DIR; + } + $filename = $debugDir . 'phpCAS.log'; + } + + if (empty (self::$_PHPCAS_DEBUG['unique_id'])) { + self::$_PHPCAS_DEBUG['unique_id'] = substr(strtoupper(md5(uniqid(''))), 0, 4); + } + + self::$_PHPCAS_DEBUG['filename'] = $filename; + self::$_PHPCAS_DEBUG['indent'] = 0; + + phpCAS :: trace('START ('.date("Y-m-d H:i:s").') phpCAS-' . PHPCAS_VERSION . ' ******************'); + } + } + + /** + * Enable verbose errors messages in the website output + * This is a security relevant since internal status info may leak an may + * help an attacker. Default is therefore false + * + * @param bool $verbose enable verbose output + * + * @return void + */ + public static function setVerbose($verbose) + { + if ($verbose === true) { + self::$_PHPCAS_VERBOSE = true; + } else { + self::$_PHPCAS_VERBOSE = false; + } + } + + + /** + * Show is verbose mode is on + * + * @return bool verbose + */ + public static function getVerbose() + { + return self::$_PHPCAS_VERBOSE; + } + + /** + * Logs a string in debug mode. + * + * @param string $str the string to write + * + * @return void + * @private + */ + public static function log($str) + { + $indent_str = "."; + + + if (isset(self::$_PHPCAS_DEBUG['logger']) || !empty(self::$_PHPCAS_DEBUG['filename'])) { + for ($i = 0; $i < self::$_PHPCAS_DEBUG['indent']; $i++) { + + $indent_str .= '| '; + } + // allow for multiline output with proper identing. Usefull for + // dumping cas answers etc. + $str2 = str_replace("\n", "\n" . self::$_PHPCAS_DEBUG['unique_id'] . ' ' . $indent_str, $str); + $str3 = self::$_PHPCAS_DEBUG['unique_id'] . ' ' . $indent_str . $str2; + if (isset(self::$_PHPCAS_DEBUG['logger'])) { + self::$_PHPCAS_DEBUG['logger']->info($str3); + } + if (!empty(self::$_PHPCAS_DEBUG['filename'])) { + // Check if file exists and modifiy file permissions to be only + // readable by the webserver + if (!file_exists(self::$_PHPCAS_DEBUG['filename'])) { + touch(self::$_PHPCAS_DEBUG['filename']); + // Chmod will fail on windows + @chmod(self::$_PHPCAS_DEBUG['filename'], 0600); + } + error_log($str3 . "\n", 3, self::$_PHPCAS_DEBUG['filename']); + } + } + + } + + /** + * This method is used by interface methods to print an error and where the + * function was originally called from. + * + * @param string $msg the message to print + * + * @return void + * @private + */ + public static function error($msg) + { + phpCAS :: traceBegin(); + $dbg = debug_backtrace(); + $function = '?'; + $file = '?'; + $line = '?'; + if (is_array($dbg)) { + for ($i = 1; $i < sizeof($dbg); $i++) { + if (is_array($dbg[$i]) && isset($dbg[$i]['class']) ) { + if ($dbg[$i]['class'] == __CLASS__) { + $function = $dbg[$i]['function']; + $file = $dbg[$i]['file']; + $line = $dbg[$i]['line']; + } + } + } + } + if (self::$_PHPCAS_VERBOSE) { + echo "
\nphpCAS error: " . __CLASS__ . "::" . $function . '(): ' . htmlentities($msg) . " in " . $file . " on line " . $line . "
\n"; + } + phpCAS :: trace($msg . ' in ' . $file . 'on line ' . $line ); + phpCAS :: traceEnd(); + + throw new CAS_GracefullTerminationException(__CLASS__ . "::" . $function . '(): ' . $msg); + } + + /** + * This method is used to log something in debug mode. + * + * @param string $str string to log + * + * @return void + */ + public static function trace($str) + { + $dbg = debug_backtrace(); + phpCAS :: log($str . ' [' . basename($dbg[0]['file']) . ':' . $dbg[0]['line'] . ']'); + } + + /** + * This method is used to indicate the start of the execution of a function + * in debug mode. + * + * @return void + */ + public static function traceBegin() + { + $dbg = debug_backtrace(); + $str = '=> '; + if (!empty ($dbg[1]['class'])) { + $str .= $dbg[1]['class'] . '::'; + } + $str .= $dbg[1]['function'] . '('; + if (is_array($dbg[1]['args'])) { + foreach ($dbg[1]['args'] as $index => $arg) { + if ($index != 0) { + $str .= ', '; + } + if (is_object($arg)) { + $str .= get_class($arg); + } else { + $str .= str_replace(array("\r\n", "\n", "\r"), "", var_export($arg, true)); + } + } + } + if (isset($dbg[1]['file'])) { + $file = basename($dbg[1]['file']); + } else { + $file = 'unknown_file'; + } + if (isset($dbg[1]['line'])) { + $line = $dbg[1]['line']; + } else { + $line = 'unknown_line'; + } + $str .= ') [' . $file . ':' . $line . ']'; + phpCAS :: log($str); + if (!isset(self::$_PHPCAS_DEBUG['indent'])) { + self::$_PHPCAS_DEBUG['indent'] = 0; + } else { + self::$_PHPCAS_DEBUG['indent']++; + } + } + + /** + * This method is used to indicate the end of the execution of a function in + * debug mode. + * + * @param mixed $res the result of the function + * + * @return void + */ + public static function traceEnd($res = '') + { + if (empty(self::$_PHPCAS_DEBUG['indent'])) { + self::$_PHPCAS_DEBUG['indent'] = 0; + } else { + self::$_PHPCAS_DEBUG['indent']--; + } + $str = ''; + if (is_object($res)) { + $str .= '<= ' . get_class($res); + } else { + $str .= '<= ' . str_replace(array("\r\n", "\n", "\r"), "", var_export($res, true)); + } + + phpCAS :: log($str); + } + + /** + * This method is used to indicate the end of the execution of the program + * + * @return void + */ + public static function traceExit() + { + phpCAS :: log('exit()'); + while (self::$_PHPCAS_DEBUG['indent'] > 0) { + phpCAS :: log('-'); + self::$_PHPCAS_DEBUG['indent']--; + } + } + + /** @} */ + // ######################################################################## + // INTERNATIONALIZATION + // ######################################################################## + /** + * @addtogroup publicLang + * @{ + */ + + /** + * This method is used to set the language used by phpCAS. + * + * @param string $lang string representing the language. + * + * @return void + * + * @sa PHPCAS_LANG_FRENCH, PHPCAS_LANG_ENGLISH + * @note Can be called only once. + */ + public static function setLang($lang) + { + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setLang($lang); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + } + + /** @} */ + // ######################################################################## + // VERSION + // ######################################################################## + /** + * @addtogroup public + * @{ + */ + + /** + * This method returns the phpCAS version. + * + * @return string the phpCAS version. + */ + public static function getVersion() + { + return PHPCAS_VERSION; + } + + /** + * This method returns supported protocols. + * + * @return array an array of all supported protocols. Use internal protocol name as array key. + */ + public static function getSupportedProtocols() + { + $supportedProtocols = array(); + $supportedProtocols[CAS_VERSION_1_0] = 'CAS 1.0'; + $supportedProtocols[CAS_VERSION_2_0] = 'CAS 2.0'; + $supportedProtocols[CAS_VERSION_3_0] = 'CAS 3.0'; + $supportedProtocols[SAML_VERSION_1_1] = 'SAML 1.1'; + + return $supportedProtocols; + } + + /** @} */ + // ######################################################################## + // HTML OUTPUT + // ######################################################################## + /** + * @addtogroup publicOutput + * @{ + */ + + /** + * This method sets the HTML header used for all outputs. + * + * @param string $header the HTML header. + * + * @return void + */ + public static function setHTMLHeader($header) + { + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setHTMLHeader($header); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + } + + /** + * This method sets the HTML footer used for all outputs. + * + * @param string $footer the HTML footer. + * + * @return void + */ + public static function setHTMLFooter($footer) + { + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setHTMLFooter($footer); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + } + + /** @} */ + // ######################################################################## + // PGT STORAGE + // ######################################################################## + /** + * @addtogroup publicPGTStorage + * @{ + */ + + /** + * This method can be used to set a custom PGT storage object. + * + * @param CAS_PGTStorage_AbstractStorage $storage a PGT storage object that inherits from the + * CAS_PGTStorage_AbstractStorage class + * + * @return void + */ + public static function setPGTStorage($storage) + { + phpCAS :: traceBegin(); + phpCAS::_validateProxyExists(); + + try { + self::$_PHPCAS_CLIENT->setPGTStorage($storage); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + phpCAS :: traceEnd(); + } + + /** + * This method is used to tell phpCAS to store the response of the + * CAS server to PGT requests in a database. + * + * @param string $dsn_or_pdo a dsn string to use for creating a PDO + * object or a PDO object + * @param string $username the username to use when connecting to the + * database + * @param string $password the password to use when connecting to the + * database + * @param string $table the table to use for storing and retrieving + * PGT's + * @param string $driver_options any driver options to use when connecting + * to the database + * + * @return void + */ + public static function setPGTStorageDb($dsn_or_pdo, $username='', + $password='', $table='', $driver_options=null + ) { + phpCAS :: traceBegin(); + phpCAS::_validateProxyExists(); + + try { + self::$_PHPCAS_CLIENT->setPGTStorageDb($dsn_or_pdo, $username, $password, $table, $driver_options); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + phpCAS :: traceEnd(); + } + + /** + * This method is used to tell phpCAS to store the response of the + * CAS server to PGT requests onto the filesystem. + * + * @param string $path the path where the PGT's should be stored + * + * @return void + */ + public static function setPGTStorageFile($path = '') + { + phpCAS :: traceBegin(); + phpCAS::_validateProxyExists(); + + try { + self::$_PHPCAS_CLIENT->setPGTStorageFile($path); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + phpCAS :: traceEnd(); + } + /** @} */ + // ######################################################################## + // ACCESS TO EXTERNAL SERVICES + // ######################################################################## + /** + * @addtogroup publicServices + * @{ + */ + + /** + * Answer a proxy-authenticated service handler. + * + * @param string $type The service type. One of + * PHPCAS_PROXIED_SERVICE_HTTP_GET; PHPCAS_PROXIED_SERVICE_HTTP_POST; + * PHPCAS_PROXIED_SERVICE_IMAP + * + * @return CAS_ProxiedService + * @throws InvalidArgumentException If the service type is unknown. + */ + public static function getProxiedService ($type) + { + phpCAS :: traceBegin(); + phpCAS::_validateProxyExists(); + + try { + $res = self::$_PHPCAS_CLIENT->getProxiedService($type); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS :: traceEnd(); + return $res; + } + + /** + * Initialize a proxied-service handler with the proxy-ticket it should use. + * + * @param CAS_ProxiedService $proxiedService Proxied Service Handler + * + * @return void + * @throws CAS_ProxyTicketException If there is a proxy-ticket failure. + * The code of the Exception will be one of: + * PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE + * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE + * PHPCAS_SERVICE_PT_FAILURE + */ + public static function initializeProxiedService (CAS_ProxiedService $proxiedService) + { + phpCAS::_validateProxyExists(); + + try { + self::$_PHPCAS_CLIENT->initializeProxiedService($proxiedService); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + } + + /** + * This method is used to access an HTTP[S] service. + * + * @param string $url the service to access. + * @param int &$err_code an error code Possible values are + * PHPCAS_SERVICE_OK (on success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, + * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE, PHPCAS_SERVICE_PT_FAILURE, + * PHPCAS_SERVICE_NOT_AVAILABLE. + * @param string &$output the output of the service (also used to give an + * error message on failure). + * + * @return bool true on success, false otherwise (in this later case, + * $err_code gives the reason why it failed and $output contains an error + * message). + */ + public static function serviceWeb($url, & $err_code, & $output) + { + phpCAS :: traceBegin(); + phpCAS::_validateProxyExists(); + + try { + $res = self::$_PHPCAS_CLIENT->serviceWeb($url, $err_code, $output); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS :: traceEnd($res); + return $res; + } + + /** + * This method is used to access an IMAP/POP3/NNTP service. + * + * @param string $url a string giving the URL of the service, + * including the mailing box for IMAP URLs, as accepted by imap_open(). + * @param string $service a string giving for CAS retrieve Proxy ticket + * @param string $flags options given to imap_open(). + * @param int &$err_code an error code Possible values are + * PHPCAS_SERVICE_OK (on success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, + * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE, PHPCAS_SERVICE_PT_FAILURE, + * PHPCAS_SERVICE_NOT_AVAILABLE. + * @param string &$err_msg an error message on failure + * @param string &$pt the Proxy Ticket (PT) retrieved from the CAS + * server to access the URL on success, false on error). + * + * @return object|false IMAP stream on success, false otherwise (in this later + * case, $err_code gives the reason why it failed and $err_msg contains an + * error message). + */ + public static function serviceMail($url, $service, $flags, & $err_code, & $err_msg, & $pt) + { + phpCAS :: traceBegin(); + phpCAS::_validateProxyExists(); + + try { + $res = self::$_PHPCAS_CLIENT->serviceMail($url, $service, $flags, $err_code, $err_msg, $pt); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS :: traceEnd($res); + return $res; + } + + /** @} */ + // ######################################################################## + // AUTHENTICATION + // ######################################################################## + /** + * @addtogroup publicAuth + * @{ + */ + + /** + * Set the times authentication will be cached before really accessing the + * CAS server in gateway mode: + * - -1: check only once, and then never again (until you pree login) + * - 0: always check + * - n: check every "n" time + * + * @param int $n an integer. + * + * @return void + */ + public static function setCacheTimesForAuthRecheck($n) + { + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setCacheTimesForAuthRecheck($n); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + } + + + /** + * Set a callback function to be run when receiving CAS attributes + * + * The callback function will be passed an $success_elements + * payload of the response (\DOMElement) as its first parameter. + * + * @param string $function Callback function + * @param array $additionalArgs optional array of arguments + * + * @return void + */ + public static function setCasAttributeParserCallback($function, array $additionalArgs = array()) + { + phpCAS::_validateClientExists(); + + self::$_PHPCAS_CLIENT->setCasAttributeParserCallback($function, $additionalArgs); + } + + /** + * Set a callback function to be run when a user authenticates. + * + * The callback function will be passed a $logoutTicket as its first + * parameter, followed by any $additionalArgs you pass. The $logoutTicket + * parameter is an opaque string that can be used to map the session-id to + * logout request in order to support single-signout in applications that + * manage their own sessions (rather than letting phpCAS start the session). + * + * phpCAS::forceAuthentication() will always exit and forward client unless + * they are already authenticated. To perform an action at the moment the user + * logs in (such as registering an account, performing logging, etc), register + * a callback function here. + * + * @param callable $function Callback function + * @param array $additionalArgs optional array of arguments + * + * @return void + */ + public static function setPostAuthenticateCallback ($function, array $additionalArgs = array()) + { + phpCAS::_validateClientExists(); + + self::$_PHPCAS_CLIENT->setPostAuthenticateCallback($function, $additionalArgs); + } + + /** + * Set a callback function to be run when a single-signout request is + * received. The callback function will be passed a $logoutTicket as its + * first parameter, followed by any $additionalArgs you pass. The + * $logoutTicket parameter is an opaque string that can be used to map a + * session-id to the logout request in order to support single-signout in + * applications that manage their own sessions (rather than letting phpCAS + * start and destroy the session). + * + * @param callable $function Callback function + * @param array $additionalArgs optional array of arguments + * + * @return void + */ + public static function setSingleSignoutCallback ($function, array $additionalArgs = array()) + { + phpCAS::_validateClientExists(); + + self::$_PHPCAS_CLIENT->setSingleSignoutCallback($function, $additionalArgs); + } + + /** + * This method is called to check if the user is already authenticated + * locally or has a global cas session. A already existing cas session is + * determined by a cas gateway call.(cas login call without any interactive + * prompt) + * + * @return bool true when the user is authenticated, false when a previous + * gateway login failed or the function will not return if the user is + * redirected to the cas server for a gateway login attempt + */ + public static function checkAuthentication() + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + $auth = self::$_PHPCAS_CLIENT->checkAuthentication(); + + // store where the authentication has been checked and the result + self::$_PHPCAS_CLIENT->markAuthenticationCall($auth); + + phpCAS :: traceEnd($auth); + return $auth; + } + + /** + * This method is called to force authentication if the user was not already + * authenticated. If the user is not authenticated, halt by redirecting to + * the CAS server. + * + * @return bool Authentication + */ + public static function forceAuthentication() + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + $auth = self::$_PHPCAS_CLIENT->forceAuthentication(); + + // store where the authentication has been checked and the result + self::$_PHPCAS_CLIENT->markAuthenticationCall($auth); + + /* if (!$auth) { + phpCAS :: trace('user is not authenticated, redirecting to the CAS server'); + self::$_PHPCAS_CLIENT->forceAuthentication(); + } else { + phpCAS :: trace('no need to authenticate (user `' . phpCAS :: getUser() . '\' is already authenticated)'); + }*/ + + phpCAS :: traceEnd(); + return $auth; + } + + /** + * This method is called to renew the authentication. + * + * @return void + **/ + public static function renewAuthentication() + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + $auth = self::$_PHPCAS_CLIENT->renewAuthentication(); + + // store where the authentication has been checked and the result + self::$_PHPCAS_CLIENT->markAuthenticationCall($auth); + + //self::$_PHPCAS_CLIENT->renewAuthentication(); + phpCAS :: traceEnd(); + } + + /** + * This method is called to check if the user is authenticated (previously or by + * tickets given in the URL). + * + * @return bool true when the user is authenticated. + */ + public static function isAuthenticated() + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + // call the isAuthenticated method of the $_PHPCAS_CLIENT object + $auth = self::$_PHPCAS_CLIENT->isAuthenticated(); + + // store where the authentication has been checked and the result + self::$_PHPCAS_CLIENT->markAuthenticationCall($auth); + + phpCAS :: traceEnd($auth); + return $auth; + } + + /** + * Checks whether authenticated based on $_SESSION. Useful to avoid + * server calls. + * + * @return bool true if authenticated, false otherwise. + * @since 0.4.22 by Brendan Arnold + */ + public static function isSessionAuthenticated() + { + phpCAS::_validateClientExists(); + + return (self::$_PHPCAS_CLIENT->isSessionAuthenticated()); + } + + /** + * This method returns the CAS user's login name. + * + * @return string the login name of the authenticated user + * @warning should only be called after phpCAS::forceAuthentication() + * or phpCAS::checkAuthentication(). + * */ + public static function getUser() + { + phpCAS::_validateClientExists(); + + try { + return self::$_PHPCAS_CLIENT->getUser(); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + } + + /** + * Answer attributes about the authenticated user. + * + * @warning should only be called after phpCAS::forceAuthentication() + * or phpCAS::checkAuthentication(). + * + * @return array + */ + public static function getAttributes() + { + phpCAS::_validateClientExists(); + + try { + return self::$_PHPCAS_CLIENT->getAttributes(); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + } + + /** + * Answer true if there are attributes for the authenticated user. + * + * @warning should only be called after phpCAS::forceAuthentication() + * or phpCAS::checkAuthentication(). + * + * @return bool + */ + public static function hasAttributes() + { + phpCAS::_validateClientExists(); + + try { + return self::$_PHPCAS_CLIENT->hasAttributes(); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + } + + /** + * Answer true if an attribute exists for the authenticated user. + * + * @param string $key attribute name + * + * @return bool + * @warning should only be called after phpCAS::forceAuthentication() + * or phpCAS::checkAuthentication(). + */ + public static function hasAttribute($key) + { + phpCAS::_validateClientExists(); + + try { + return self::$_PHPCAS_CLIENT->hasAttribute($key); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + } + + /** + * Answer an attribute for the authenticated user. + * + * @param string $key attribute name + * + * @return mixed string for a single value or an array if multiple values exist. + * @warning should only be called after phpCAS::forceAuthentication() + * or phpCAS::checkAuthentication(). + */ + public static function getAttribute($key) + { + phpCAS::_validateClientExists(); + + try { + return self::$_PHPCAS_CLIENT->getAttribute($key); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + } + + /** + * Handle logout requests. + * + * @param bool $check_client additional safety check + * @param array $allowed_clients array of allowed clients + * + * @return void + */ + public static function handleLogoutRequests($check_client = true, $allowed_clients = array()) + { + phpCAS::_validateClientExists(); + + return (self::$_PHPCAS_CLIENT->handleLogoutRequests($check_client, $allowed_clients)); + } + + /** + * This method returns the URL to be used to login. + * + * @return string the login URL + */ + public static function getServerLoginURL() + { + phpCAS::_validateClientExists(); + + return self::$_PHPCAS_CLIENT->getServerLoginURL(); + } + + /** + * Set the login URL of the CAS server. + * + * @param string $url the login URL + * + * @return void + * @since 0.4.21 by Wyman Chan + */ + public static function setServerLoginURL($url = '') + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setServerLoginURL($url); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS :: traceEnd(); + } + + /** + * Set the serviceValidate URL of the CAS server. + * Used for all CAS versions of URL validations. + * Examples: + * CAS 1.0 http://www.exemple.com/validate + * CAS 2.0 http://www.exemple.com/validateURL + * CAS 3.0 http://www.exemple.com/p3/serviceValidate + * + * @param string $url the serviceValidate URL + * + * @return void + */ + public static function setServerServiceValidateURL($url = '') + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setServerServiceValidateURL($url); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS :: traceEnd(); + } + + /** + * Set the proxyValidate URL of the CAS server. + * Used for all CAS versions of proxy URL validations + * Examples: + * CAS 1.0 http://www.exemple.com/ + * CAS 2.0 http://www.exemple.com/proxyValidate + * CAS 3.0 http://www.exemple.com/p3/proxyValidate + * + * @param string $url the proxyValidate URL + * + * @return void + */ + public static function setServerProxyValidateURL($url = '') + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setServerProxyValidateURL($url); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS :: traceEnd(); + } + + /** + * Set the samlValidate URL of the CAS server. + * + * @param string $url the samlValidate URL + * + * @return void + */ + public static function setServerSamlValidateURL($url = '') + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setServerSamlValidateURL($url); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS :: traceEnd(); + } + + /** + * This method returns the URL to be used to logout. + * + * @return string the URL to use to log out + */ + public static function getServerLogoutURL() + { + phpCAS::_validateClientExists(); + + return self::$_PHPCAS_CLIENT->getServerLogoutURL(); + } + + /** + * Set the logout URL of the CAS server. + * + * @param string $url the logout URL + * + * @return void + * @since 0.4.21 by Wyman Chan + */ + public static function setServerLogoutURL($url = '') + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setServerLogoutURL($url); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS :: traceEnd(); + } + + /** + * This method is used to logout from CAS. + * + * @param string $params an array that contains the optional url and + * service parameters that will be passed to the CAS server + * + * @return void + */ + public static function logout($params = "") + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + $parsedParams = array (); + if ($params != "") { + if (is_string($params)) { + phpCAS :: error('method `phpCAS::logout($url)\' is now deprecated, use `phpCAS::logoutWithUrl($url)\' instead'); + } + if (!is_array($params)) { + phpCAS :: error('type mismatched for parameter $params (should be `array\')'); + } + foreach ($params as $key => $value) { + if ($key != "service" && $key != "url") { + phpCAS :: error('only `url\' and `service\' parameters are allowed for method `phpCAS::logout($params)\''); + } + $parsedParams[$key] = $value; + } + } + self::$_PHPCAS_CLIENT->logout($parsedParams); + // never reached + phpCAS :: traceEnd(); + } + + /** + * This method is used to logout from CAS. Halts by redirecting to the CAS + * server. + * + * @param string $service a URL that will be transmitted to the CAS server + * + * @return void + */ + public static function logoutWithRedirectService($service) + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + if (!is_string($service)) { + phpCAS :: error('type mismatched for parameter $service (should be `string\')'); + } + self::$_PHPCAS_CLIENT->logout(array ( "service" => $service )); + // never reached + phpCAS :: traceEnd(); + } + + /** + * This method is used to logout from CAS. Halts by redirecting to the CAS + * server. + * + * @param string $url a URL that will be transmitted to the CAS server + * + * @return void + * @deprecated The url parameter has been removed from the CAS server as of + * version 3.3.5.1 + */ + public static function logoutWithUrl($url) + { + trigger_error('Function deprecated for cas servers >= 3.3.5.1', E_USER_DEPRECATED); + phpCAS :: traceBegin(); + if (!is_object(self::$_PHPCAS_CLIENT)) { + phpCAS :: error('this method should only be called after ' . __CLASS__ . '::client() or' . __CLASS__ . '::proxy()'); + } + if (!is_string($url)) { + phpCAS :: error('type mismatched for parameter $url (should be `string\')'); + } + self::$_PHPCAS_CLIENT->logout(array ( "url" => $url )); + // never reached + phpCAS :: traceEnd(); + } + + /** + * This method is used to logout from CAS. Halts by redirecting to the CAS + * server. + * + * @param string $service a URL that will be transmitted to the CAS server + * @param string $url a URL that will be transmitted to the CAS server + * + * @return void + * + * @deprecated The url parameter has been removed from the CAS server as of + * version 3.3.5.1 + */ + public static function logoutWithRedirectServiceAndUrl($service, $url) + { + trigger_error('Function deprecated for cas servers >= 3.3.5.1', E_USER_DEPRECATED); + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + if (!is_string($service)) { + phpCAS :: error('type mismatched for parameter $service (should be `string\')'); + } + if (!is_string($url)) { + phpCAS :: error('type mismatched for parameter $url (should be `string\')'); + } + self::$_PHPCAS_CLIENT->logout( + array ( + "service" => $service, + "url" => $url + ) + ); + // never reached + phpCAS :: traceEnd(); + } + + /** + * Set the fixed URL that will be used by the CAS server to transmit the + * PGT. When this method is not called, a phpCAS script uses its own URL + * for the callback. + * + * @param string $url the URL + * + * @return void + */ + public static function setFixedCallbackURL($url = '') + { + phpCAS :: traceBegin(); + phpCAS::_validateProxyExists(); + + try { + self::$_PHPCAS_CLIENT->setCallbackURL($url); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS :: traceEnd(); + } + + /** + * Set the fixed URL that will be set as the CAS service parameter. When this + * method is not called, a phpCAS script uses its own URL. + * + * @param string $url the URL + * + * @return void + */ + public static function setFixedServiceURL($url) + { + phpCAS :: traceBegin(); + phpCAS::_validateProxyExists(); + + try { + self::$_PHPCAS_CLIENT->setURL($url); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS :: traceEnd(); + } + + /** + * Get the URL that is set as the CAS service parameter. + * + * @return string Service Url + */ + public static function getServiceURL() + { + phpCAS::_validateProxyExists(); + return (self::$_PHPCAS_CLIENT->getURL()); + } + + /** + * Retrieve a Proxy Ticket from the CAS server. + * + * @param string $target_service Url string of service to proxy + * @param int &$err_code error code + * @param string &$err_msg error message + * + * @return string Proxy Ticket + */ + public static function retrievePT($target_service, & $err_code, & $err_msg) + { + phpCAS::_validateProxyExists(); + + try { + return (self::$_PHPCAS_CLIENT->retrievePT($target_service, $err_code, $err_msg)); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + } + + /** + * Set the certificate of the CAS server CA and if the CN should be properly + * verified. + * + * @param string $cert CA certificate file name + * @param bool $validate_cn Validate CN in certificate (default true) + * + * @return void + */ + public static function setCasServerCACert($cert, $validate_cn = true) + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setCasServerCACert($cert, $validate_cn); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS :: traceEnd(); + } + + /** + * Set no SSL validation for the CAS server. + * + * @return void + */ + public static function setNoCasServerValidation() + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + phpCAS :: trace('You have configured no validation of the legitimacy of the cas server. This is not recommended for production use.'); + self::$_PHPCAS_CLIENT->setNoCasServerValidation(); + phpCAS :: traceEnd(); + } + + + /** + * Disable the removal of a CAS-Ticket from the URL when authenticating + * DISABLING POSES A SECURITY RISK: + * We normally remove the ticket by an additional redirect as a security + * precaution to prevent a ticket in the HTTP_REFERRER or be carried over in + * the URL parameter + * + * @return void + */ + public static function setNoClearTicketsFromUrl() + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + self::$_PHPCAS_CLIENT->setNoClearTicketsFromUrl(); + phpCAS :: traceEnd(); + } + + /** @} */ + + /** + * Change CURL options. + * CURL is used to connect through HTTPS to CAS server + * + * @param string $key the option key + * @param string $value the value to set + * + * @return void + */ + public static function setExtraCurlOption($key, $value) + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + self::$_PHPCAS_CLIENT->setExtraCurlOption($key, $value); + phpCAS :: traceEnd(); + } + + /** + * Set a salt/seed for the session-id hash to make it harder to guess. + * + * When $changeSessionID = true phpCAS will create a session-id that is derived + * from the service ticket. Doing so allows phpCAS to look-up and destroy the + * proper session on single-log-out requests. While the service tickets + * provided by the CAS server may include enough data to generate a strong + * hash, clients may provide an additional salt to ensure that session ids + * are not guessable if the session tickets do not have enough entropy. + * + * @param string $salt The salt to combine with the session ticket. + * + * @return void + */ + public static function setSessionIdSalt($salt) { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + self::$_PHPCAS_CLIENT->setSessionIdSalt($salt); + phpCAS :: traceEnd(); + } + + /** + * If you want your service to be proxied you have to enable it (default + * disabled) and define an accepable list of proxies that are allowed to + * proxy your service. + * + * Add each allowed proxy definition object. For the normal CAS_ProxyChain + * class, the constructor takes an array of proxies to match. The list is in + * reverse just as seen from the service. Proxies have to be defined in reverse + * from the service to the user. If a user hits service A and gets proxied via + * B to service C the list of acceptable on C would be array(B,A). The definition + * of an individual proxy can be either a string or a regexp (preg_match is used) + * that will be matched against the proxy list supplied by the cas server + * when validating the proxy tickets. The strings are compared starting from + * the beginning and must fully match with the proxies in the list. + * Example: + * phpCAS::allowProxyChain(new CAS_ProxyChain(array( + * 'https://app.example.com/' + * ))); + * phpCAS::allowProxyChain(new CAS_ProxyChain(array( + * '/^https:\/\/app[0-9]\.example\.com\/rest\//', + * 'http://client.example.com/' + * ))); + * + * For quick testing or in certain production screnarios you might want to + * allow allow any other valid service to proxy your service. To do so, add + * the "Any" chain: + * phpCAS::allowProxyChain(new CAS_ProxyChain_Any); + * THIS SETTING IS HOWEVER NOT RECOMMENDED FOR PRODUCTION AND HAS SECURITY + * IMPLICATIONS: YOU ARE ALLOWING ANY SERVICE TO ACT ON BEHALF OF A USER + * ON THIS SERVICE. + * + * @param CAS_ProxyChain_Interface $proxy_chain A proxy-chain that will be + * matched against the proxies requesting access + * + * @return void + */ + public static function allowProxyChain(CAS_ProxyChain_Interface $proxy_chain) + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + if (self::$_PHPCAS_CLIENT->getServerVersion() !== CAS_VERSION_2_0 + && self::$_PHPCAS_CLIENT->getServerVersion() !== CAS_VERSION_3_0 + ) { + phpCAS :: error('this method can only be used with the cas 2.0/3.0 protocols'); + } + self::$_PHPCAS_CLIENT->getAllowedProxyChains()->allowProxyChain($proxy_chain); + phpCAS :: traceEnd(); + } + + /** + * Answer an array of proxies that are sitting in front of this application. + * This method will only return a non-empty array if we have received and + * validated a Proxy Ticket. + * + * @return array + * @access public + * @since 6/25/09 + */ + public static function getProxies () + { + phpCAS::_validateProxyExists(); + + return(self::$_PHPCAS_CLIENT->getProxies()); + } + + // ######################################################################## + // PGTIOU/PGTID and logoutRequest rebroadcasting + // ######################################################################## + + /** + * Add a pgtIou/pgtId and logoutRequest rebroadcast node. + * + * @param string $rebroadcastNodeUrl The rebroadcast node URL. Can be + * hostname or IP. + * + * @return void + */ + public static function addRebroadcastNode($rebroadcastNodeUrl) + { + phpCAS::traceBegin(); + phpCAS::log('rebroadcastNodeUrl:'.$rebroadcastNodeUrl); + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->addRebroadcastNode($rebroadcastNodeUrl); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS::traceEnd(); + } + + /** + * This method is used to add header parameters when rebroadcasting + * pgtIou/pgtId or logoutRequest. + * + * @param String $header Header to send when rebroadcasting. + * + * @return void + */ + public static function addRebroadcastHeader($header) + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->addRebroadcastHeader($header); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS :: traceEnd(); + } + + /** + * Checks if a client already exists + * + * @throws CAS_OutOfSequenceBeforeClientException + * + * @return void + */ + private static function _validateClientExists() + { + if (!is_object(self::$_PHPCAS_CLIENT)) { + throw new CAS_OutOfSequenceBeforeClientException(); + } + } + + /** + * Checks of a proxy client aready exists + * + * @throws CAS_OutOfSequenceBeforeProxyException + * + * @return void + */ + private static function _validateProxyExists() + { + if (!is_object(self::$_PHPCAS_CLIENT)) { + throw new CAS_OutOfSequenceBeforeProxyException(); + } + } + + /** + * @return CAS_Client + */ + public static function getCasClient() + { + return self::$_PHPCAS_CLIENT; + } + + /** + * For testing purposes, use this method to set the client to a test double + * + * @return void + */ + public static function setCasClient(\CAS_Client $client) + { + self::$_PHPCAS_CLIENT = $client; + } +} +// ######################################################################## +// DOCUMENTATION +// ######################################################################## + +// ######################################################################## +// MAIN PAGE + +/** + * @mainpage + * + * The following pages only show the source documentation. + * + */ + +// ######################################################################## +// MODULES DEFINITION + +/** @defgroup public User interface */ + +/** @defgroup publicInit Initialization + * @ingroup public */ + +/** @defgroup publicAuth Authentication + * @ingroup public */ + +/** @defgroup publicServices Access to external services + * @ingroup public */ + +/** @defgroup publicConfig Configuration + * @ingroup public */ + +/** @defgroup publicLang Internationalization + * @ingroup publicConfig */ + +/** @defgroup publicOutput HTML output + * @ingroup publicConfig */ + +/** @defgroup publicPGTStorage PGT storage + * @ingroup publicConfig */ + +/** @defgroup publicDebug Debugging + * @ingroup public */ + +/** @defgroup internal Implementation */ + +/** @defgroup internalAuthentication Authentication + * @ingroup internal */ + +/** @defgroup internalBasic CAS Basic client features (CAS 1.0, Service Tickets) + * @ingroup internal */ + +/** @defgroup internalProxy CAS Proxy features (CAS 2.0, Proxy Granting Tickets) + * @ingroup internal */ + +/** @defgroup internalSAML CAS SAML features (SAML 1.1) + * @ingroup internal */ + +/** @defgroup internalPGTStorage PGT storage + * @ingroup internalProxy */ + +/** @defgroup internalPGTStorageDb PGT storage in a database + * @ingroup internalPGTStorage */ + +/** @defgroup internalPGTStorageFile PGT storage on the filesystem + * @ingroup internalPGTStorage */ + +/** @defgroup internalCallback Callback from the CAS server + * @ingroup internalProxy */ + +/** @defgroup internalProxyServices Proxy other services + * @ingroup internalProxy */ + +/** @defgroup internalService CAS client features (CAS 2.0, Proxied service) + * @ingroup internal */ + +/** @defgroup internalConfig Configuration + * @ingroup internal */ + +/** @defgroup internalBehave Internal behaviour of phpCAS + * @ingroup internalConfig */ + +/** @defgroup internalOutput HTML output + * @ingroup internalConfig */ + +/** @defgroup internalLang Internationalization + * @ingroup internalConfig + * + * To add a new language: + * - 1. define a new constant PHPCAS_LANG_XXXXXX in CAS/CAS.php + * - 2. copy any file from CAS/languages to CAS/languages/XXXXXX.php + * - 3. Make the translations + */ + +/** @defgroup internalDebug Debugging + * @ingroup internal */ + +/** @defgroup internalMisc Miscellaneous + * @ingroup internal */ + +// ######################################################################## +// EXAMPLES + +/** + * @example example_simple.php + */ +/** + * @example example_service.php + */ +/** + * @example example_service_that_proxies.php + */ +/** + * @example example_service_POST.php + */ +/** + * @example example_proxy_serviceWeb.php + */ +/** + * @example example_proxy_serviceWeb_chaining.php + */ +/** + * @example example_proxy_POST.php + */ +/** + * @example example_proxy_GET.php + */ +/** + * @example example_lang.php + */ +/** + * @example example_html.php + */ +/** + * @example example_pgt_storage_file.php + */ +/** + * @example example_pgt_storage_db.php + */ +/** + * @example example_gateway.php + */ +/** + * @example example_logout.php + */ +/** + * @example example_rebroadcast.php + */ +/** + * @example example_custom_urls.php + */ +/** + * @example example_advanced_saml11.php + */ diff --git a/vendor/apereo/phpcas/source/CAS/AuthenticationException.php b/vendor/apereo/phpcas/source/CAS/AuthenticationException.php new file mode 100644 index 00000000..803c8890 --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/AuthenticationException.php @@ -0,0 +1,115 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This interface defines methods that allow proxy-authenticated service handlers + * to interact with phpCAS. + * + * Proxy service handlers must implement this interface as well as call + * phpCAS::initializeProxiedService($this) at some point in their implementation. + * + * While not required, proxy-authenticated service handlers are encouraged to + * implement the CAS_ProxiedService_Testable interface to facilitate unit testing. + * + * @class CAS_AuthenticationException + * @category Authentication + * @package PhpCAS + * @author Joachim Fritschi + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +class CAS_AuthenticationException +extends RuntimeException +implements CAS_Exception +{ + + /** + * This method is used to print the HTML output when the user was not + * authenticated. + * + * @param CAS_Client $client phpcas client + * @param string $failure the failure that occured + * @param string $cas_url the URL the CAS server was asked for + * @param bool $no_response the response from the CAS server (other + * parameters are ignored if TRUE) + * @param bool $bad_response bad response from the CAS server ($err_code + * and $err_msg ignored if TRUE) + * @param string $cas_response the response of the CAS server + * @param int $err_code the error code given by the CAS server + * @param string $err_msg the error message given by the CAS server + */ + public function __construct($client,$failure,$cas_url,$no_response, + $bad_response=false,$cas_response='',$err_code=-1,$err_msg='' + ) { + $messages = array(); + phpCAS::traceBegin(); + $lang = $client->getLangObj(); + $client->printHTMLHeader($lang->getAuthenticationFailed()); + + if (phpCAS::getVerbose()) { + printf( + $lang->getYouWereNotAuthenticated(), + htmlentities($client->getURL()), + $_SERVER['SERVER_ADMIN'] ?? '' + ); + } + + phpCAS::trace($messages[] = 'CAS URL: '.$cas_url); + phpCAS::trace($messages[] = 'Authentication failure: '.$failure); + if ( $no_response ) { + phpCAS::trace($messages[] = 'Reason: no response from the CAS server'); + } else { + if ( $bad_response ) { + phpCAS::trace($messages[] = 'Reason: bad response from the CAS server'); + } else { + switch ($client->getServerVersion()) { + case CAS_VERSION_1_0: + phpCAS::trace($messages[] = 'Reason: CAS error'); + break; + case CAS_VERSION_2_0: + case CAS_VERSION_3_0: + if ( $err_code === -1 ) { + phpCAS::trace($messages[] = 'Reason: no CAS error'); + } else { + phpCAS::trace($messages[] = 'Reason: ['.$err_code.'] CAS error: '.$err_msg); + } + break; + } + } + phpCAS::trace($messages[] = 'CAS response: '.$cas_response); + } + $client->printHTMLFooter(); + phpCAS::traceExit(); + + parent::__construct(implode("\n", $messages)); + } + +} +?> diff --git a/vendor/apereo/phpcas/source/CAS/Autoload.php b/vendor/apereo/phpcas/source/CAS/Autoload.php new file mode 100644 index 00000000..29395d59 --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/Autoload.php @@ -0,0 +1,95 @@ + + * @copyright 2008 Regents of the University of Nebraska + * @license http://www1.unl.edu/wdn/wiki/Software_License BSD License + * @link http://code.google.com/p/simplecas/ + **/ + +/** + * Autoload a class + * + * @param string $class Classname to load + * + * @return bool + */ +function CAS_autoload($class) +{ + // Static to hold the Include Path to CAS + static $include_path; + // Check only for CAS classes + if (substr($class, 0, 4) !== 'CAS_' && substr($class, 0, 7) !== 'PhpCas\\') { + return false; + } + + // Setup the include path if it's not already set from a previous call + if (empty($include_path)) { + $include_path = array(dirname(__DIR__)); + } + + // Declare local variable to store the expected full path to the file + foreach ($include_path as $path) { + $class_path = str_replace('_', DIRECTORY_SEPARATOR, $class); + // PhpCas namespace mapping + if (substr($class_path, 0, 7) === 'PhpCas\\') { + $class_path = 'CAS' . DIRECTORY_SEPARATOR . substr($class_path, 7); + } + + $file_path = $path . DIRECTORY_SEPARATOR . $class_path . '.php'; + $fp = @fopen($file_path, 'r', true); + if ($fp) { + fclose($fp); + include $file_path; + if (!class_exists($class, false) && !interface_exists($class, false)) { + die( + new Exception( + 'Class ' . $class . ' was not present in ' . + $file_path . + ' [CAS_autoload]' + ) + ); + } + return true; + } + } + + $e = new Exception( + 'Class ' . $class . ' could not be loaded from ' . + $file_path . ', file does not exist (Path="' + . implode(':', $include_path) .'") [CAS_autoload]' + ); + $trace = $e->getTrace(); + if (isset($trace[2]) && isset($trace[2]['function']) + && in_array($trace[2]['function'], array('class_exists', 'interface_exists', 'trait_exists')) + ) { + return false; + } + if (isset($trace[1]) && isset($trace[1]['function']) + && in_array($trace[1]['function'], array('class_exists', 'interface_exists', 'trait_exists')) + ) { + return false; + } + die ((string) $e); +} + +// Set up autoload if not already configured by composer. +if (!class_exists('CAS_Client')) +{ + trigger_error('phpCAS autoloader is deprecated. Install phpCAS using composer instead.', E_USER_DEPRECATED); + spl_autoload_register('CAS_autoload'); + if (function_exists('__autoload') + && !in_array('__autoload', spl_autoload_functions()) + ) { + // __autoload() was being used, but now would be ignored, add + // it to the autoload stack + spl_autoload_register('__autoload'); + } +} diff --git a/vendor/apereo/phpcas/source/CAS/Client.php b/vendor/apereo/phpcas/source/CAS/Client.php new file mode 100644 index 00000000..8ca9711f --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/Client.php @@ -0,0 +1,4387 @@ + + * @author Olivier Berger + * @author Brett Bieber + * @author Joachim Fritschi + * @author Adam Franco + * @author Tobias Schiebeck + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * The CAS_Client class is a client interface that provides CAS authentication + * to PHP applications. + * + * @class CAS_Client + * @category Authentication + * @package PhpCAS + * @author Pascal Aubry + * @author Olivier Berger + * @author Brett Bieber + * @author Joachim Fritschi + * @author Adam Franco + * @author Tobias Schiebeck + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * + */ + +class CAS_Client +{ + + // ######################################################################## + // HTML OUTPUT + // ######################################################################## + /** + * @addtogroup internalOutput + * @{ + */ + + /** + * This method filters a string by replacing special tokens by appropriate values + * and prints it. The corresponding tokens are taken into account: + * - __CAS_VERSION__ + * - __PHPCAS_VERSION__ + * - __SERVER_BASE_URL__ + * + * Used by CAS_Client::PrintHTMLHeader() and CAS_Client::printHTMLFooter(). + * + * @param string $str the string to filter and output + * + * @return void + */ + private function _htmlFilterOutput($str) + { + $str = str_replace('__CAS_VERSION__', $this->getServerVersion(), $str); + $str = str_replace('__PHPCAS_VERSION__', phpCAS::getVersion(), $str); + $str = str_replace('__SERVER_BASE_URL__', $this->_getServerBaseURL(), $str); + echo $str; + } + + /** + * A string used to print the header of HTML pages. Written by + * CAS_Client::setHTMLHeader(), read by CAS_Client::printHTMLHeader(). + * + * @hideinitializer + * @see CAS_Client::setHTMLHeader, CAS_Client::printHTMLHeader() + */ + private $_output_header = ''; + + /** + * This method prints the header of the HTML output (after filtering). If + * CAS_Client::setHTMLHeader() was not used, a default header is output. + * + * @param string $title the title of the page + * + * @return void + * @see _htmlFilterOutput() + */ + public function printHTMLHeader($title) + { + if (!phpCAS::getVerbose()) { + return; + } + + $this->_htmlFilterOutput( + str_replace( + '__TITLE__', $title, + (empty($this->_output_header) + ? '__TITLE__

__TITLE__

' + : $this->_output_header) + ) + ); + } + + /** + * A string used to print the footer of HTML pages. Written by + * CAS_Client::setHTMLFooter(), read by printHTMLFooter(). + * + * @hideinitializer + * @see CAS_Client::setHTMLFooter, CAS_Client::printHTMLFooter() + */ + private $_output_footer = ''; + + /** + * This method prints the footer of the HTML output (after filtering). If + * CAS_Client::setHTMLFooter() was not used, a default footer is output. + * + * @return void + * @see _htmlFilterOutput() + */ + public function printHTMLFooter() + { + if (!phpCAS::getVerbose()) { + return; + } + + $lang = $this->getLangObj(); + $message = empty($this->_output_footer) + ? '
phpCAS __PHPCAS_VERSION__ ' . $lang->getUsingServer() . + ' __SERVER_BASE_URL__ (CAS __CAS_VERSION__)
' + : $this->_output_footer; + + $this->_htmlFilterOutput($message); + } + + /** + * This method set the HTML header used for all outputs. + * + * @param string $header the HTML header. + * + * @return void + */ + public function setHTMLHeader($header) + { + // Argument Validation + if (gettype($header) != 'string') + throw new CAS_TypeMismatchException($header, '$header', 'string'); + + $this->_output_header = $header; + } + + /** + * This method set the HTML footer used for all outputs. + * + * @param string $footer the HTML footer. + * + * @return void + */ + public function setHTMLFooter($footer) + { + // Argument Validation + if (gettype($footer) != 'string') + throw new CAS_TypeMismatchException($footer, '$footer', 'string'); + + $this->_output_footer = $footer; + } + + /** + * Simple wrapper for printf function, that respects + * phpCAS verbosity setting. + * + * @param string $format + * @param string|int|float ...$values + * + * @see printf() + */ + private function printf(string $format, ...$values): void + { + if (phpCAS::getVerbose()) { + printf($format, ...$values); + } + } + + /** @} */ + + + // ######################################################################## + // INTERNATIONALIZATION + // ######################################################################## + /** + * @addtogroup internalLang + * @{ + */ + /** + * A string corresponding to the language used by phpCAS. Written by + * CAS_Client::setLang(), read by CAS_Client::getLang(). + + * @note debugging information is always in english (debug purposes only). + */ + private $_lang = PHPCAS_LANG_DEFAULT; + + /** + * This method is used to set the language used by phpCAS. + * + * @param string $lang representing the language. + * + * @return void + */ + public function setLang($lang) + { + // Argument Validation + if (gettype($lang) != 'string') + throw new CAS_TypeMismatchException($lang, '$lang', 'string'); + + phpCAS::traceBegin(); + $obj = new $lang(); + if (!($obj instanceof CAS_Languages_LanguageInterface)) { + throw new CAS_InvalidArgumentException( + '$className must implement the CAS_Languages_LanguageInterface' + ); + } + $this->_lang = $lang; + phpCAS::traceEnd(); + } + /** + * Create the language + * + * @return CAS_Languages_LanguageInterface object implementing the class + */ + public function getLangObj() + { + $classname = $this->_lang; + return new $classname(); + } + + /** @} */ + // ######################################################################## + // CAS SERVER CONFIG + // ######################################################################## + /** + * @addtogroup internalConfig + * @{ + */ + + /** + * a record to store information about the CAS server. + * - $_server['version']: the version of the CAS server + * - $_server['hostname']: the hostname of the CAS server + * - $_server['port']: the port the CAS server is running on + * - $_server['uri']: the base URI the CAS server is responding on + * - $_server['base_url']: the base URL of the CAS server + * - $_server['login_url']: the login URL of the CAS server + * - $_server['service_validate_url']: the service validating URL of the + * CAS server + * - $_server['proxy_url']: the proxy URL of the CAS server + * - $_server['proxy_validate_url']: the proxy validating URL of the CAS server + * - $_server['logout_url']: the logout URL of the CAS server + * + * $_server['version'], $_server['hostname'], $_server['port'] and + * $_server['uri'] are written by CAS_Client::CAS_Client(), read by + * CAS_Client::getServerVersion(), CAS_Client::_getServerHostname(), + * CAS_Client::_getServerPort() and CAS_Client::_getServerURI(). + * + * The other fields are written and read by CAS_Client::_getServerBaseURL(), + * CAS_Client::getServerLoginURL(), CAS_Client::getServerServiceValidateURL(), + * CAS_Client::getServerProxyValidateURL() and CAS_Client::getServerLogoutURL(). + * + * @hideinitializer + */ + private $_server = array( + 'version' => '', + 'hostname' => 'none', + 'port' => -1, + 'uri' => 'none'); + + /** + * This method is used to retrieve the version of the CAS server. + * + * @return string the version of the CAS server. + */ + public function getServerVersion() + { + return $this->_server['version']; + } + + /** + * This method is used to retrieve the hostname of the CAS server. + * + * @return string the hostname of the CAS server. + */ + private function _getServerHostname() + { + return $this->_server['hostname']; + } + + /** + * This method is used to retrieve the port of the CAS server. + * + * @return int the port of the CAS server. + */ + private function _getServerPort() + { + return $this->_server['port']; + } + + /** + * This method is used to retrieve the URI of the CAS server. + * + * @return string a URI. + */ + private function _getServerURI() + { + return $this->_server['uri']; + } + + /** + * This method is used to retrieve the base URL of the CAS server. + * + * @return string a URL. + */ + private function _getServerBaseURL() + { + // the URL is build only when needed + if ( empty($this->_server['base_url']) ) { + $this->_server['base_url'] = 'https://' . $this->_getServerHostname(); + if ($this->_getServerPort()!=443) { + $this->_server['base_url'] .= ':' + .$this->_getServerPort(); + } + $this->_server['base_url'] .= $this->_getServerURI(); + } + return $this->_server['base_url']; + } + + /** + * This method is used to retrieve the login URL of the CAS server. + * + * @param bool $gateway true to check authentication, false to force it + * @param bool $renew true to force the authentication with the CAS server + * + * @return string a URL. + * @note It is recommended that CAS implementations ignore the "gateway" + * parameter if "renew" is set + */ + public function getServerLoginURL($gateway=false,$renew=false) + { + phpCAS::traceBegin(); + // the URL is build only when needed + if ( empty($this->_server['login_url']) ) { + $this->_server['login_url'] = $this->_buildQueryUrl($this->_getServerBaseURL().'login','service='.urlencode($this->getURL())); + } + $url = $this->_server['login_url']; + if ($renew) { + // It is recommended that when the "renew" parameter is set, its + // value be "true" + $url = $this->_buildQueryUrl($url, 'renew=true'); + } elseif ($gateway) { + // It is recommended that when the "gateway" parameter is set, its + // value be "true" + $url = $this->_buildQueryUrl($url, 'gateway=true'); + } + phpCAS::traceEnd($url); + return $url; + } + + /** + * This method sets the login URL of the CAS server. + * + * @param string $url the login URL + * + * @return string login url + */ + public function setServerLoginURL($url) + { + // Argument Validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + + return $this->_server['login_url'] = $url; + } + + + /** + * This method sets the serviceValidate URL of the CAS server. + * + * @param string $url the serviceValidate URL + * + * @return string serviceValidate URL + */ + public function setServerServiceValidateURL($url) + { + // Argument Validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + + return $this->_server['service_validate_url'] = $url; + } + + + /** + * This method sets the proxyValidate URL of the CAS server. + * + * @param string $url the proxyValidate URL + * + * @return string proxyValidate URL + */ + public function setServerProxyValidateURL($url) + { + // Argument Validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + + return $this->_server['proxy_validate_url'] = $url; + } + + + /** + * This method sets the samlValidate URL of the CAS server. + * + * @param string $url the samlValidate URL + * + * @return string samlValidate URL + */ + public function setServerSamlValidateURL($url) + { + // Argument Validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + + return $this->_server['saml_validate_url'] = $url; + } + + + /** + * This method is used to retrieve the service validating URL of the CAS server. + * + * @return string serviceValidate URL. + */ + public function getServerServiceValidateURL() + { + phpCAS::traceBegin(); + // the URL is build only when needed + if ( empty($this->_server['service_validate_url']) ) { + switch ($this->getServerVersion()) { + case CAS_VERSION_1_0: + $this->_server['service_validate_url'] = $this->_getServerBaseURL() + .'validate'; + break; + case CAS_VERSION_2_0: + $this->_server['service_validate_url'] = $this->_getServerBaseURL() + .'serviceValidate'; + break; + case CAS_VERSION_3_0: + $this->_server['service_validate_url'] = $this->_getServerBaseURL() + .'p3/serviceValidate'; + break; + } + } + $url = $this->_buildQueryUrl( + $this->_server['service_validate_url'], + 'service='.urlencode($this->getURL()) + ); + phpCAS::traceEnd($url); + return $url; + } + /** + * This method is used to retrieve the SAML validating URL of the CAS server. + * + * @return string samlValidate URL. + */ + public function getServerSamlValidateURL() + { + phpCAS::traceBegin(); + // the URL is build only when needed + if ( empty($this->_server['saml_validate_url']) ) { + switch ($this->getServerVersion()) { + case SAML_VERSION_1_1: + $this->_server['saml_validate_url'] = $this->_getServerBaseURL().'samlValidate'; + break; + } + } + + $url = $this->_buildQueryUrl( + $this->_server['saml_validate_url'], + 'TARGET='.urlencode($this->getURL()) + ); + phpCAS::traceEnd($url); + return $url; + } + + /** + * This method is used to retrieve the proxy validating URL of the CAS server. + * + * @return string proxyValidate URL. + */ + public function getServerProxyValidateURL() + { + phpCAS::traceBegin(); + // the URL is build only when needed + if ( empty($this->_server['proxy_validate_url']) ) { + switch ($this->getServerVersion()) { + case CAS_VERSION_1_0: + $this->_server['proxy_validate_url'] = ''; + break; + case CAS_VERSION_2_0: + $this->_server['proxy_validate_url'] = $this->_getServerBaseURL().'proxyValidate'; + break; + case CAS_VERSION_3_0: + $this->_server['proxy_validate_url'] = $this->_getServerBaseURL().'p3/proxyValidate'; + break; + } + } + $url = $this->_buildQueryUrl( + $this->_server['proxy_validate_url'], + 'service='.urlencode($this->getURL()) + ); + phpCAS::traceEnd($url); + return $url; + } + + + /** + * This method is used to retrieve the proxy URL of the CAS server. + * + * @return string proxy URL. + */ + public function getServerProxyURL() + { + // the URL is build only when needed + if ( empty($this->_server['proxy_url']) ) { + switch ($this->getServerVersion()) { + case CAS_VERSION_1_0: + $this->_server['proxy_url'] = ''; + break; + case CAS_VERSION_2_0: + case CAS_VERSION_3_0: + $this->_server['proxy_url'] = $this->_getServerBaseURL().'proxy'; + break; + } + } + return $this->_server['proxy_url']; + } + + /** + * This method is used to retrieve the logout URL of the CAS server. + * + * @return string logout URL. + */ + public function getServerLogoutURL() + { + // the URL is build only when needed + if ( empty($this->_server['logout_url']) ) { + $this->_server['logout_url'] = $this->_getServerBaseURL().'logout'; + } + return $this->_server['logout_url']; + } + + /** + * This method sets the logout URL of the CAS server. + * + * @param string $url the logout URL + * + * @return string logout url + */ + public function setServerLogoutURL($url) + { + // Argument Validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + + return $this->_server['logout_url'] = $url; + } + + /** + * An array to store extra curl options. + */ + private $_curl_options = array(); + + /** + * This method is used to set additional user curl options. + * + * @param string $key name of the curl option + * @param string $value value of the curl option + * + * @return void + */ + public function setExtraCurlOption($key, $value) + { + $this->_curl_options[$key] = $value; + } + + /** @} */ + + // ######################################################################## + // Change the internal behaviour of phpcas + // ######################################################################## + + /** + * @addtogroup internalBehave + * @{ + */ + + /** + * The class to instantiate for making web requests in readUrl(). + * The class specified must implement the CAS_Request_RequestInterface. + * By default CAS_Request_CurlRequest is used, but this may be overridden to + * supply alternate request mechanisms for testing. + */ + private $_requestImplementation = 'CAS_Request_CurlRequest'; + + /** + * Override the default implementation used to make web requests in readUrl(). + * This class must implement the CAS_Request_RequestInterface. + * + * @param string $className name of the RequestImplementation class + * + * @return void + */ + public function setRequestImplementation ($className) + { + $obj = new $className; + if (!($obj instanceof CAS_Request_RequestInterface)) { + throw new CAS_InvalidArgumentException( + '$className must implement the CAS_Request_RequestInterface' + ); + } + $this->_requestImplementation = $className; + } + + /** + * @var boolean $_clearTicketsFromUrl; If true, phpCAS will clear session + * tickets from the URL after a successful authentication. + */ + private $_clearTicketsFromUrl = true; + + /** + * Configure the client to not send redirect headers and call exit() on + * authentication success. The normal redirect is used to remove the service + * ticket from the client's URL, but for running unit tests we need to + * continue without exiting. + * + * Needed for testing authentication + * + * @return void + */ + public function setNoClearTicketsFromUrl () + { + $this->_clearTicketsFromUrl = false; + } + + /** + * @var callback $_attributeParserCallbackFunction; + */ + private $_casAttributeParserCallbackFunction = null; + + /** + * @var array $_attributeParserCallbackArgs; + */ + private $_casAttributeParserCallbackArgs = array(); + + /** + * Set a callback function to be run when parsing CAS attributes + * + * The callback function will be passed a XMLNode as its first parameter, + * followed by any $additionalArgs you pass. + * + * @param string $function callback function to call + * @param array $additionalArgs optional array of arguments + * + * @return void + */ + public function setCasAttributeParserCallback($function, array $additionalArgs = array()) + { + $this->_casAttributeParserCallbackFunction = $function; + $this->_casAttributeParserCallbackArgs = $additionalArgs; + } + + /** @var callable $_postAuthenticateCallbackFunction; + */ + private $_postAuthenticateCallbackFunction = null; + + /** + * @var array $_postAuthenticateCallbackArgs; + */ + private $_postAuthenticateCallbackArgs = array(); + + /** + * Set a callback function to be run when a user authenticates. + * + * The callback function will be passed a $logoutTicket as its first parameter, + * followed by any $additionalArgs you pass. The $logoutTicket parameter is an + * opaque string that can be used to map a session-id to the logout request + * in order to support single-signout in applications that manage their own + * sessions (rather than letting phpCAS start the session). + * + * phpCAS::forceAuthentication() will always exit and forward client unless + * they are already authenticated. To perform an action at the moment the user + * logs in (such as registering an account, performing logging, etc), register + * a callback function here. + * + * @param callable $function callback function to call + * @param array $additionalArgs optional array of arguments + * + * @return void + */ + public function setPostAuthenticateCallback ($function, array $additionalArgs = array()) + { + $this->_postAuthenticateCallbackFunction = $function; + $this->_postAuthenticateCallbackArgs = $additionalArgs; + } + + /** + * @var callable $_signoutCallbackFunction; + */ + private $_signoutCallbackFunction = null; + + /** + * @var array $_signoutCallbackArgs; + */ + private $_signoutCallbackArgs = array(); + + /** + * Set a callback function to be run when a single-signout request is received. + * + * The callback function will be passed a $logoutTicket as its first parameter, + * followed by any $additionalArgs you pass. The $logoutTicket parameter is an + * opaque string that can be used to map a session-id to the logout request in + * order to support single-signout in applications that manage their own sessions + * (rather than letting phpCAS start and destroy the session). + * + * @param callable $function callback function to call + * @param array $additionalArgs optional array of arguments + * + * @return void + */ + public function setSingleSignoutCallback ($function, array $additionalArgs = array()) + { + $this->_signoutCallbackFunction = $function; + $this->_signoutCallbackArgs = $additionalArgs; + } + + // ######################################################################## + // Methods for supplying code-flow feedback to integrators. + // ######################################################################## + + /** + * Ensure that this is actually a proxy object or fail with an exception + * + * @throws CAS_OutOfSequenceBeforeProxyException + * + * @return void + */ + public function ensureIsProxy() + { + if (!$this->isProxy()) { + throw new CAS_OutOfSequenceBeforeProxyException(); + } + } + + /** + * Mark the caller of authentication. This will help client integraters determine + * problems with their code flow if they call a function such as getUser() before + * authentication has occurred. + * + * @param bool $auth True if authentication was successful, false otherwise. + * + * @return null + */ + public function markAuthenticationCall ($auth) + { + // store where the authentication has been checked and the result + $dbg = debug_backtrace(); + $this->_authentication_caller = array ( + 'file' => $dbg[1]['file'], + 'line' => $dbg[1]['line'], + 'method' => $dbg[1]['class'] . '::' . $dbg[1]['function'], + 'result' => (boolean)$auth + ); + } + private $_authentication_caller; + + /** + * Answer true if authentication has been checked. + * + * @return bool + */ + public function wasAuthenticationCalled () + { + return !empty($this->_authentication_caller); + } + + /** + * Ensure that authentication was checked. Terminate with exception if no + * authentication was performed + * + * @throws CAS_OutOfSequenceBeforeAuthenticationCallException + * + * @return void + */ + private function _ensureAuthenticationCalled() + { + if (!$this->wasAuthenticationCalled()) { + throw new CAS_OutOfSequenceBeforeAuthenticationCallException(); + } + } + + /** + * Answer the result of the authentication call. + * + * Throws a CAS_OutOfSequenceException if wasAuthenticationCalled() is false + * and markAuthenticationCall() didn't happen. + * + * @return bool + */ + public function wasAuthenticationCallSuccessful () + { + $this->_ensureAuthenticationCalled(); + return $this->_authentication_caller['result']; + } + + + /** + * Ensure that authentication was checked. Terminate with exception if no + * authentication was performed + * + * @throws CAS_OutOfSequenceException + * + * @return void + */ + public function ensureAuthenticationCallSuccessful() + { + $this->_ensureAuthenticationCalled(); + if (!$this->_authentication_caller['result']) { + throw new CAS_OutOfSequenceException( + 'authentication was checked (by ' + . $this->getAuthenticationCallerMethod() + . '() at ' . $this->getAuthenticationCallerFile() + . ':' . $this->getAuthenticationCallerLine() + . ') but the method returned false' + ); + } + } + + /** + * Answer information about the authentication caller. + * + * Throws a CAS_OutOfSequenceException if wasAuthenticationCalled() is false + * and markAuthenticationCall() didn't happen. + * + * @return string the file that called authentication + */ + public function getAuthenticationCallerFile () + { + $this->_ensureAuthenticationCalled(); + return $this->_authentication_caller['file']; + } + + /** + * Answer information about the authentication caller. + * + * Throws a CAS_OutOfSequenceException if wasAuthenticationCalled() is false + * and markAuthenticationCall() didn't happen. + * + * @return int the line that called authentication + */ + public function getAuthenticationCallerLine () + { + $this->_ensureAuthenticationCalled(); + return $this->_authentication_caller['line']; + } + + /** + * Answer information about the authentication caller. + * + * Throws a CAS_OutOfSequenceException if wasAuthenticationCalled() is false + * and markAuthenticationCall() didn't happen. + * + * @return string the method that called authentication + */ + public function getAuthenticationCallerMethod () + { + $this->_ensureAuthenticationCalled(); + return $this->_authentication_caller['method']; + } + + /** @} */ + + // ######################################################################## + // CONSTRUCTOR + // ######################################################################## + /** + * @addtogroup internalConfig + * @{ + */ + + /** + * CAS_Client constructor. + * + * @param string $server_version the version of the CAS server + * @param bool $proxy true if the CAS client is a CAS proxy + * @param string $server_hostname the hostname of the CAS server + * @param int $server_port the port the CAS server is running on + * @param string $server_uri the URI the CAS server is responding on + * @param bool $changeSessionID Allow phpCAS to change the session_id + * (Single Sign Out/handleLogoutRequests + * is based on that change) + * @param string|string[]|CAS_ServiceBaseUrl_Interface + * $service_base_url the base URL (protocol, host and the + * optional port) of the CAS client; pass + * in an array to use auto discovery with + * an allowlist; pass in + * CAS_ServiceBaseUrl_Interface for custom + * behavior. Added in 1.6.0. Similar to + * serverName config in other CAS clients. + * @param \SessionHandlerInterface $sessionHandler the session handler + * + * @return self a newly created CAS_Client object + */ + public function __construct( + $server_version, + $proxy, + $server_hostname, + $server_port, + $server_uri, + $service_base_url, + $changeSessionID = true, + \SessionHandlerInterface $sessionHandler = null + ) { + // Argument validation + if (gettype($server_version) != 'string') + throw new CAS_TypeMismatchException($server_version, '$server_version', 'string'); + if (gettype($proxy) != 'boolean') + throw new CAS_TypeMismatchException($proxy, '$proxy', 'boolean'); + if (gettype($server_hostname) != 'string') + throw new CAS_TypeMismatchException($server_hostname, '$server_hostname', 'string'); + if (gettype($server_port) != 'integer') + throw new CAS_TypeMismatchException($server_port, '$server_port', 'integer'); + if (gettype($server_uri) != 'string') + throw new CAS_TypeMismatchException($server_uri, '$server_uri', 'string'); + if (gettype($changeSessionID) != 'boolean') + throw new CAS_TypeMismatchException($changeSessionID, '$changeSessionID', 'boolean'); + + $this->_setServiceBaseUrl($service_base_url); + + if (empty($sessionHandler)) { + $sessionHandler = new CAS_Session_PhpSession; + } + + phpCAS::traceBegin(); + // true : allow to change the session_id(), false session_id won't be + // changed and logout won't be handled because of that + $this->_setChangeSessionID($changeSessionID); + + $this->setSessionHandler($sessionHandler); + + if (!$this->_isLogoutRequest()) { + if (session_id() === "") { + // skip Session Handling for logout requests and if don't want it + session_start(); + phpCAS :: trace("Starting a new session " . session_id()); + } + } + + // Only for debug purposes + if ($this->isSessionAuthenticated()){ + phpCAS :: trace("Session is authenticated as: " . $this->getSessionValue('user')); + } else { + phpCAS :: trace("Session is not authenticated"); + } + // are we in proxy mode ? + $this->_proxy = $proxy; + + // Make cookie handling available. + if ($this->isProxy()) { + if (!$this->hasSessionValue('service_cookies')) { + $this->setSessionValue('service_cookies', array()); + } + // TODO remove explicit call to $_SESSION + $this->_serviceCookieJar = new CAS_CookieJar( + $_SESSION[static::PHPCAS_SESSION_PREFIX]['service_cookies'] + ); + } + + // check version + $supportedProtocols = phpCAS::getSupportedProtocols(); + if (isset($supportedProtocols[$server_version]) === false) { + phpCAS::error( + 'this version of CAS (`'.$server_version + .'\') is not supported by phpCAS '.phpCAS::getVersion() + ); + } + + if ($server_version === CAS_VERSION_1_0 && $this->isProxy()) { + phpCAS::error( + 'CAS proxies are not supported in CAS '.$server_version + ); + } + + $this->_server['version'] = $server_version; + + // check hostname + if ( empty($server_hostname) + || !preg_match('/[\.\d\-a-z]*/', $server_hostname) + ) { + phpCAS::error('bad CAS server hostname (`'.$server_hostname.'\')'); + } + $this->_server['hostname'] = $server_hostname; + + // check port + if ( $server_port == 0 + || !is_int($server_port) + ) { + phpCAS::error('bad CAS server port (`'.$server_hostname.'\')'); + } + $this->_server['port'] = $server_port; + + // check URI + if ( !preg_match('/[\.\d\-_a-z\/]*/', $server_uri) ) { + phpCAS::error('bad CAS server URI (`'.$server_uri.'\')'); + } + // add leading and trailing `/' and remove doubles + if(strstr($server_uri, '?') === false) $server_uri .= '/'; + $server_uri = preg_replace('/\/\//', '/', '/'.$server_uri); + $this->_server['uri'] = $server_uri; + + // set to callback mode if PgtIou and PgtId CGI GET parameters are provided + if ( $this->isProxy() ) { + if(!empty($_GET['pgtIou'])&&!empty($_GET['pgtId'])) { + $this->_setCallbackMode(true); + $this->_setCallbackModeUsingPost(false); + } elseif (!empty($_POST['pgtIou'])&&!empty($_POST['pgtId'])) { + $this->_setCallbackMode(true); + $this->_setCallbackModeUsingPost(true); + } else { + $this->_setCallbackMode(false); + $this->_setCallbackModeUsingPost(false); + } + + + } + + if ( $this->_isCallbackMode() ) { + //callback mode: check that phpCAS is secured + if ( !$this->getServiceBaseUrl()->isHttps() ) { + phpCAS::error( + 'CAS proxies must be secured to use phpCAS; PGT\'s will not be received from the CAS server' + ); + } + } else { + //normal mode: get ticket and remove it from CGI parameters for + // developers + $ticket = (isset($_GET['ticket']) ? $_GET['ticket'] : ''); + if (preg_match('/^[SP]T-/', $ticket) ) { + phpCAS::trace('Ticket \''.$ticket.'\' found'); + $this->setTicket($ticket); + unset($_GET['ticket']); + } else if ( !empty($ticket) ) { + //ill-formed ticket, halt + phpCAS::error( + 'ill-formed ticket found in the URL (ticket=`' + .htmlentities($ticket).'\')' + ); + } + + } + phpCAS::traceEnd(); + } + + /** @} */ + + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + // XX XX + // XX Session Handling XX + // XX XX + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + + /** + * @addtogroup internalConfig + * @{ + */ + + /** The session prefix for phpCAS values */ + const PHPCAS_SESSION_PREFIX = 'phpCAS'; + + /** + * @var bool A variable to whether phpcas will use its own session handling. Default = true + * @hideinitializer + */ + private $_change_session_id = true; + + /** + * @var SessionHandlerInterface + */ + private $_sessionHandler; + + /** + * Set a parameter whether to allow phpCAS to change session_id + * + * @param bool $allowed allow phpCAS to change session_id + * + * @return void + */ + private function _setChangeSessionID($allowed) + { + $this->_change_session_id = $allowed; + } + + /** + * Get whether phpCAS is allowed to change session_id + * + * @return bool + */ + public function getChangeSessionID() + { + return $this->_change_session_id; + } + + /** + * Set the session handler. + * + * @param \SessionHandlerInterface $sessionHandler + * + * @return bool + */ + public function setSessionHandler(\SessionHandlerInterface $sessionHandler) + { + $this->_sessionHandler = $sessionHandler; + if (session_status() !== PHP_SESSION_ACTIVE) { + return session_set_save_handler($this->_sessionHandler, true); + } + return true; + } + + /** + * Get a session value using the given key. + * + * @param string $key + * @param mixed $default default value if the key is not set + * + * @return mixed + */ + protected function getSessionValue($key, $default = null) + { + $this->validateSession($key); + + if (isset($_SESSION[static::PHPCAS_SESSION_PREFIX][$key])) { + return $_SESSION[static::PHPCAS_SESSION_PREFIX][$key]; + } + + return $default; + } + + /** + * Determine whether a session value is set or not. + * + * To check if a session value is empty or not please use + * !!(getSessionValue($key)). + * + * @param string $key + * + * @return bool + */ + protected function hasSessionValue($key) + { + $this->validateSession($key); + + return isset($_SESSION[static::PHPCAS_SESSION_PREFIX][$key]); + } + + /** + * Set a session value using the given key and value. + * + * @param string $key + * @param mixed $value + * + * @return string + */ + protected function setSessionValue($key, $value) + { + $this->validateSession($key); + + $this->ensureSessionArray(); + $_SESSION[static::PHPCAS_SESSION_PREFIX][$key] = $value; + } + + /** + * Ensure that the session array is initialized before writing to it. + */ + protected function ensureSessionArray() { + // init phpCAS session array + if (!isset($_SESSION[static::PHPCAS_SESSION_PREFIX]) + || !is_array($_SESSION[static::PHPCAS_SESSION_PREFIX])) { + $_SESSION[static::PHPCAS_SESSION_PREFIX] = array(); + } + } + + /** + * Remove a session value with the given key. + * + * @param string $key + */ + protected function removeSessionValue($key) + { + $this->validateSession($key); + + if (isset($_SESSION[static::PHPCAS_SESSION_PREFIX][$key])) { + unset($_SESSION[static::PHPCAS_SESSION_PREFIX][$key]); + return true; + } + + return false; + } + + /** + * Remove all phpCAS session values. + */ + protected function clearSessionValues() + { + unset($_SESSION[static::PHPCAS_SESSION_PREFIX]); + } + + /** + * Ensure $key is a string for session utils input + * + * @param string $key + * + * @return bool + */ + protected function validateSession($key) + { + if (!is_string($key)) { + throw new InvalidArgumentException('Session key must be a string.'); + } + + return true; + } + + /** + * Renaming the session + * + * @param string $ticket name of the ticket + * + * @return void + */ + protected function _renameSession($ticket) + { + phpCAS::traceBegin(); + if ($this->getChangeSessionID()) { + if (!empty($this->_user)) { + $old_session = $_SESSION; + phpCAS :: trace("Killing session: ". session_id()); + session_destroy(); + // set up a new session, of name based on the ticket + $session_id = $this->_sessionIdForTicket($ticket); + phpCAS :: trace("Starting session: ". $session_id); + session_id($session_id); + session_start(); + phpCAS :: trace("Restoring old session vars"); + $_SESSION = $old_session; + } else { + phpCAS :: trace ( + 'Session should only be renamed after successfull authentication' + ); + } + } else { + phpCAS :: trace( + "Skipping session rename since phpCAS is not handling the session." + ); + } + phpCAS::traceEnd(); + } + + /** @} */ + + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + // XX XX + // XX AUTHENTICATION XX + // XX XX + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + + /** + * @addtogroup internalAuthentication + * @{ + */ + + /** + * The Authenticated user. Written by CAS_Client::_setUser(), read by + * CAS_Client::getUser(). + * + * @hideinitializer + */ + private $_user = ''; + + /** + * This method sets the CAS user's login name. + * + * @param string $user the login name of the authenticated user. + * + * @return void + */ + private function _setUser($user) + { + $this->_user = $user; + } + + /** + * This method returns the CAS user's login name. + * + * @return string the login name of the authenticated user + * + * @warning should be called only after CAS_Client::forceAuthentication() or + * CAS_Client::isAuthenticated(), otherwise halt with an error. + */ + public function getUser() + { + // Sequence validation + $this->ensureAuthenticationCallSuccessful(); + + return $this->_getUser(); + } + + /** + * This method returns the CAS user's login name. + * + * @return string the login name of the authenticated user + * + * @warning should be called only after CAS_Client::forceAuthentication() or + * CAS_Client::isAuthenticated(), otherwise halt with an error. + */ + private function _getUser() + { + // This is likely a duplicate check that could be removed.... + if ( empty($this->_user) ) { + phpCAS::error( + 'this method should be used only after '.__CLASS__ + .'::forceAuthentication() or '.__CLASS__.'::isAuthenticated()' + ); + } + return $this->_user; + } + + /** + * The Authenticated users attributes. Written by + * CAS_Client::setAttributes(), read by CAS_Client::getAttributes(). + * @attention client applications should use phpCAS::getAttributes(). + * + * @hideinitializer + */ + private $_attributes = array(); + + /** + * Set an array of attributes + * + * @param array $attributes a key value array of attributes + * + * @return void + */ + public function setAttributes($attributes) + { + $this->_attributes = $attributes; + } + + /** + * Get an key values arry of attributes + * + * @return array of attributes + */ + public function getAttributes() + { + // Sequence validation + $this->ensureAuthenticationCallSuccessful(); + // This is likely a duplicate check that could be removed.... + if ( empty($this->_user) ) { + // if no user is set, there shouldn't be any attributes also... + phpCAS::error( + 'this method should be used only after '.__CLASS__ + .'::forceAuthentication() or '.__CLASS__.'::isAuthenticated()' + ); + } + return $this->_attributes; + } + + /** + * Check whether attributes are available + * + * @return bool attributes available + */ + public function hasAttributes() + { + // Sequence validation + $this->ensureAuthenticationCallSuccessful(); + + return !empty($this->_attributes); + } + /** + * Check whether a specific attribute with a name is available + * + * @param string $key name of attribute + * + * @return bool is attribute available + */ + public function hasAttribute($key) + { + // Sequence validation + $this->ensureAuthenticationCallSuccessful(); + + return $this->_hasAttribute($key); + } + + /** + * Check whether a specific attribute with a name is available + * + * @param string $key name of attribute + * + * @return bool is attribute available + */ + private function _hasAttribute($key) + { + return (is_array($this->_attributes) + && array_key_exists($key, $this->_attributes)); + } + + /** + * Get a specific attribute by name + * + * @param string $key name of attribute + * + * @return string attribute values + */ + public function getAttribute($key) + { + // Sequence validation + $this->ensureAuthenticationCallSuccessful(); + + if ($this->_hasAttribute($key)) { + return $this->_attributes[$key]; + } + } + + /** + * This method is called to renew the authentication of the user + * If the user is authenticated, renew the connection + * If not, redirect to CAS + * + * @return bool true when the user is authenticated; otherwise halt. + */ + public function renewAuthentication() + { + phpCAS::traceBegin(); + // Either way, the user is authenticated by CAS + $this->removeSessionValue('auth_checked'); + if ( $this->isAuthenticated(true) ) { + phpCAS::trace('user already authenticated'); + $res = true; + } else { + $this->redirectToCas(false, true); + // never reached + $res = false; + } + phpCAS::traceEnd(); + return $res; + } + + /** + * This method is called to be sure that the user is authenticated. When not + * authenticated, halt by redirecting to the CAS server; otherwise return true. + * + * @return bool true when the user is authenticated; otherwise halt. + */ + public function forceAuthentication() + { + phpCAS::traceBegin(); + + if ( $this->isAuthenticated() ) { + // the user is authenticated, nothing to be done. + phpCAS::trace('no need to authenticate'); + $res = true; + } else { + // the user is not authenticated, redirect to the CAS server + $this->removeSessionValue('auth_checked'); + $this->redirectToCas(false/* no gateway */); + // never reached + $res = false; + } + phpCAS::traceEnd($res); + return $res; + } + + /** + * An integer that gives the number of times authentication will be cached + * before rechecked. + * + * @hideinitializer + */ + private $_cache_times_for_auth_recheck = 0; + + /** + * Set the number of times authentication will be cached before rechecked. + * + * @param int $n number of times to wait for a recheck + * + * @return void + */ + public function setCacheTimesForAuthRecheck($n) + { + if (gettype($n) != 'integer') + throw new CAS_TypeMismatchException($n, '$n', 'string'); + + $this->_cache_times_for_auth_recheck = $n; + } + + /** + * This method is called to check whether the user is authenticated or not. + * + * @return bool true when the user is authenticated, false when a previous + * gateway login failed or the function will not return if the user is + * redirected to the cas server for a gateway login attempt + */ + public function checkAuthentication() + { + phpCAS::traceBegin(); + $res = false; // default + if ( $this->isAuthenticated() ) { + phpCAS::trace('user is authenticated'); + /* The 'auth_checked' variable is removed just in case it's set. */ + $this->removeSessionValue('auth_checked'); + $res = true; + } else if ($this->getSessionValue('auth_checked')) { + // the previous request has redirected the client to the CAS server + // with gateway=true + $this->removeSessionValue('auth_checked'); + } else { + // avoid a check against CAS on every request + // we need to write this back to session later + $unauth_count = $this->getSessionValue('unauth_count', -2); + + if (($unauth_count != -2 + && $this->_cache_times_for_auth_recheck == -1) + || ($unauth_count >= 0 + && $unauth_count < $this->_cache_times_for_auth_recheck) + ) { + if ($this->_cache_times_for_auth_recheck != -1) { + $unauth_count++; + phpCAS::trace( + 'user is not authenticated (cached for ' + .$unauth_count.' times of ' + .$this->_cache_times_for_auth_recheck.')' + ); + } else { + phpCAS::trace( + 'user is not authenticated (cached for until login pressed)' + ); + } + $this->setSessionValue('unauth_count', $unauth_count); + } else { + $this->setSessionValue('unauth_count', 0); + $this->setSessionValue('auth_checked', true); + phpCAS::trace('user is not authenticated (cache reset)'); + $this->redirectToCas(true/* gateway */); + // never reached + } + } + phpCAS::traceEnd($res); + return $res; + } + + /** + * This method is called to check if the user is authenticated (previously or by + * tickets given in the URL). + * + * @param bool $renew true to force the authentication with the CAS server + * + * @return bool true when the user is authenticated. Also may redirect to the + * same URL without the ticket. + */ + public function isAuthenticated($renew=false) + { + phpCAS::traceBegin(); + $res = false; + + if ( $this->_wasPreviouslyAuthenticated() ) { + if ($this->hasTicket()) { + // User has a additional ticket but was already authenticated + phpCAS::trace( + 'ticket was present and will be discarded, use renewAuthenticate()' + ); + if ($this->_clearTicketsFromUrl) { + phpCAS::trace("Prepare redirect to : ".$this->getURL()); + session_write_close(); + header('Location: '.$this->getURL()); + flush(); + phpCAS::traceExit(); + throw new CAS_GracefullTerminationException(); + } else { + phpCAS::trace( + 'Already authenticated, but skipping ticket clearing since setNoClearTicketsFromUrl() was used.' + ); + $res = true; + } + } else { + // the user has already (previously during the session) been + // authenticated, nothing to be done. + phpCAS::trace( + 'user was already authenticated, no need to look for tickets' + ); + $res = true; + } + + // Mark the auth-check as complete to allow post-authentication + // callbacks to make use of phpCAS::getUser() and similar methods + $this->markAuthenticationCall($res); + } else { + if ($this->hasTicket()) { + $validate_url = ''; + $text_response = ''; + $tree_response = ''; + + switch ($this->getServerVersion()) { + case CAS_VERSION_1_0: + // if a Service Ticket was given, validate it + phpCAS::trace( + 'CAS 1.0 ticket `'.$this->getTicket().'\' is present' + ); + $this->validateCAS10( + $validate_url, $text_response, $tree_response, $renew + ); // if it fails, it halts + phpCAS::trace( + 'CAS 1.0 ticket `'.$this->getTicket().'\' was validated' + ); + $this->setSessionValue('user', $this->_getUser()); + $res = true; + $logoutTicket = $this->getTicket(); + break; + case CAS_VERSION_2_0: + case CAS_VERSION_3_0: + // if a Proxy Ticket was given, validate it + phpCAS::trace( + 'CAS '.$this->getServerVersion().' ticket `'.$this->getTicket().'\' is present' + ); + $this->validateCAS20( + $validate_url, $text_response, $tree_response, $renew + ); // note: if it fails, it halts + phpCAS::trace( + 'CAS '.$this->getServerVersion().' ticket `'.$this->getTicket().'\' was validated' + ); + if ( $this->isProxy() ) { + $this->_validatePGT( + $validate_url, $text_response, $tree_response + ); // idem + phpCAS::trace('PGT `'.$this->_getPGT().'\' was validated'); + $this->setSessionValue('pgt', $this->_getPGT()); + } + $this->setSessionValue('user', $this->_getUser()); + if (!empty($this->_attributes)) { + $this->setSessionValue('attributes', $this->_attributes); + } + $proxies = $this->getProxies(); + if (!empty($proxies)) { + $this->setSessionValue('proxies', $this->getProxies()); + } + $res = true; + $logoutTicket = $this->getTicket(); + break; + case SAML_VERSION_1_1: + // if we have a SAML ticket, validate it. + phpCAS::trace( + 'SAML 1.1 ticket `'.$this->getTicket().'\' is present' + ); + $this->validateSA( + $validate_url, $text_response, $tree_response, $renew + ); // if it fails, it halts + phpCAS::trace( + 'SAML 1.1 ticket `'.$this->getTicket().'\' was validated' + ); + $this->setSessionValue('user', $this->_getUser()); + $this->setSessionValue('attributes', $this->_attributes); + $res = true; + $logoutTicket = $this->getTicket(); + break; + default: + phpCAS::trace('Protocol error'); + break; + } + } else { + // no ticket given, not authenticated + phpCAS::trace('no ticket found'); + } + + // Mark the auth-check as complete to allow post-authentication + // callbacks to make use of phpCAS::getUser() and similar methods + $this->markAuthenticationCall($res); + + if ($res) { + // call the post-authenticate callback if registered. + if ($this->_postAuthenticateCallbackFunction) { + $args = $this->_postAuthenticateCallbackArgs; + array_unshift($args, $logoutTicket); + call_user_func_array( + $this->_postAuthenticateCallbackFunction, $args + ); + } + + // if called with a ticket parameter, we need to redirect to the + // app without the ticket so that CAS-ification is transparent + // to the browser (for later POSTS) most of the checks and + // errors should have been made now, so we're safe for redirect + // without masking error messages. remove the ticket as a + // security precaution to prevent a ticket in the HTTP_REFERRER + if ($this->_clearTicketsFromUrl) { + phpCAS::trace("Prepare redirect to : ".$this->getURL()); + session_write_close(); + header('Location: '.$this->getURL()); + flush(); + phpCAS::traceExit(); + throw new CAS_GracefullTerminationException(); + } + } + } + phpCAS::traceEnd($res); + return $res; + } + + /** + * This method tells if the current session is authenticated. + * + * @return bool true if authenticated based soley on $_SESSION variable + */ + public function isSessionAuthenticated () + { + return !!$this->getSessionValue('user'); + } + + /** + * This method tells if the user has already been (previously) authenticated + * by looking into the session variables. + * + * @note This function switches to callback mode when needed. + * + * @return bool true when the user has already been authenticated; false otherwise. + */ + private function _wasPreviouslyAuthenticated() + { + phpCAS::traceBegin(); + + if ( $this->_isCallbackMode() ) { + // Rebroadcast the pgtIou and pgtId to all nodes + if ($this->_rebroadcast&&!isset($_POST['rebroadcast'])) { + $this->_rebroadcast(self::PGTIOU); + } + $this->_callback(); + } + + $auth = false; + + if ( $this->isProxy() ) { + // CAS proxy: username and PGT must be present + if ( $this->isSessionAuthenticated() + && $this->getSessionValue('pgt') + ) { + // authentication already done + $this->_setUser($this->getSessionValue('user')); + if ($this->hasSessionValue('attributes')) { + $this->setAttributes($this->getSessionValue('attributes')); + } + $this->_setPGT($this->getSessionValue('pgt')); + phpCAS::trace( + 'user = `'.$this->getSessionValue('user').'\', PGT = `' + .$this->getSessionValue('pgt').'\'' + ); + + // Include the list of proxies + if ($this->hasSessionValue('proxies')) { + $this->_setProxies($this->getSessionValue('proxies')); + phpCAS::trace( + 'proxies = "' + .implode('", "', $this->getSessionValue('proxies')).'"' + ); + } + + $auth = true; + } elseif ( $this->isSessionAuthenticated() + && !$this->getSessionValue('pgt') + ) { + // these two variables should be empty or not empty at the same time + phpCAS::trace( + 'username found (`'.$this->getSessionValue('user') + .'\') but PGT is empty' + ); + // unset all tickets to enforce authentication + $this->clearSessionValues(); + $this->setTicket(''); + } elseif ( !$this->isSessionAuthenticated() + && $this->getSessionValue('pgt') + ) { + // these two variables should be empty or not empty at the same time + phpCAS::trace( + 'PGT found (`'.$this->getSessionValue('pgt') + .'\') but username is empty' + ); + // unset all tickets to enforce authentication + $this->clearSessionValues(); + $this->setTicket(''); + } else { + phpCAS::trace('neither user nor PGT found'); + } + } else { + // `simple' CAS client (not a proxy): username must be present + if ( $this->isSessionAuthenticated() ) { + // authentication already done + $this->_setUser($this->getSessionValue('user')); + if ($this->hasSessionValue('attributes')) { + $this->setAttributes($this->getSessionValue('attributes')); + } + phpCAS::trace('user = `'.$this->getSessionValue('user').'\''); + + // Include the list of proxies + if ($this->hasSessionValue('proxies')) { + $this->_setProxies($this->getSessionValue('proxies')); + phpCAS::trace( + 'proxies = "' + .implode('", "', $this->getSessionValue('proxies')).'"' + ); + } + + $auth = true; + } else { + phpCAS::trace('no user found'); + } + } + + phpCAS::traceEnd($auth); + return $auth; + } + + /** + * This method is used to redirect the client to the CAS server. + * It is used by CAS_Client::forceAuthentication() and + * CAS_Client::checkAuthentication(). + * + * @param bool $gateway true to check authentication, false to force it + * @param bool $renew true to force the authentication with the CAS server + * + * @return void + */ + public function redirectToCas($gateway=false,$renew=false) + { + phpCAS::traceBegin(); + $cas_url = $this->getServerLoginURL($gateway, $renew); + session_write_close(); + if (php_sapi_name() === 'cli') { + @header('Location: '.$cas_url); + } else { + header('Location: '.$cas_url); + } + phpCAS::trace("Redirect to : ".$cas_url); + $lang = $this->getLangObj(); + $this->printHTMLHeader($lang->getAuthenticationWanted()); + $this->printf('

'. $lang->getShouldHaveBeenRedirected(). '

', $cas_url); + $this->printHTMLFooter(); + phpCAS::traceExit(); + throw new CAS_GracefullTerminationException(); + } + + + /** + * This method is used to logout from CAS. + * + * @param array $params an array that contains the optional url and service + * parameters that will be passed to the CAS server + * + * @return void + */ + public function logout($params) + { + phpCAS::traceBegin(); + $cas_url = $this->getServerLogoutURL(); + $paramSeparator = '?'; + if (isset($params['url'])) { + $cas_url = $cas_url . $paramSeparator . "url=" + . urlencode($params['url']); + $paramSeparator = '&'; + } + if (isset($params['service'])) { + $cas_url = $cas_url . $paramSeparator . "service=" + . urlencode($params['service']); + } + header('Location: '.$cas_url); + phpCAS::trace("Prepare redirect to : ".$cas_url); + + phpCAS::trace("Destroying session : ".session_id()); + session_unset(); + session_destroy(); + if (session_status() === PHP_SESSION_NONE) { + phpCAS::trace("Session terminated"); + } else { + phpCAS::error("Session was not terminated"); + phpCAS::trace("Session was not terminated"); + } + $lang = $this->getLangObj(); + $this->printHTMLHeader($lang->getLogout()); + $this->printf('

'.$lang->getShouldHaveBeenRedirected(). '

', $cas_url); + $this->printHTMLFooter(); + phpCAS::traceExit(); + throw new CAS_GracefullTerminationException(); + } + + /** + * Check of the current request is a logout request + * + * @return bool is logout request. + */ + private function _isLogoutRequest() + { + return !empty($_POST['logoutRequest']); + } + + /** + * This method handles logout requests. + * + * @param bool $check_client true to check the client bofore handling + * the request, false not to perform any access control. True by default. + * @param array $allowed_clients an array of host names allowed to send + * logout requests. + * + * @return void + */ + public function handleLogoutRequests($check_client=true, $allowed_clients=array()) + { + phpCAS::traceBegin(); + if (!$this->_isLogoutRequest()) { + phpCAS::trace("Not a logout request"); + phpCAS::traceEnd(); + return; + } + if (!$this->getChangeSessionID() + && is_null($this->_signoutCallbackFunction) + ) { + phpCAS::trace( + "phpCAS can't handle logout requests if it is not allowed to change session_id." + ); + } + phpCAS::trace("Logout requested"); + $decoded_logout_rq = urldecode($_POST['logoutRequest']); + phpCAS::trace("SAML REQUEST: ".$decoded_logout_rq); + $allowed = false; + if ($check_client) { + if ($allowed_clients === array()) { + $allowed_clients = array( $this->_getServerHostname() ); + } + $client_ip = $_SERVER['REMOTE_ADDR']; + $client = gethostbyaddr($client_ip); + phpCAS::trace("Client: ".$client."/".$client_ip); + foreach ($allowed_clients as $allowed_client) { + if (($client == $allowed_client) + || ($client_ip == $allowed_client) + ) { + phpCAS::trace( + "Allowed client '".$allowed_client + ."' matches, logout request is allowed" + ); + $allowed = true; + break; + } else { + phpCAS::trace( + "Allowed client '".$allowed_client."' does not match" + ); + } + } + } else { + phpCAS::trace("No access control set"); + $allowed = true; + } + // If Logout command is permitted proceed with the logout + if ($allowed) { + phpCAS::trace("Logout command allowed"); + // Rebroadcast the logout request + if ($this->_rebroadcast && !isset($_POST['rebroadcast'])) { + $this->_rebroadcast(self::LOGOUT); + } + // Extract the ticket from the SAML Request + preg_match( + "|(.*)|", + $decoded_logout_rq, $tick, PREG_OFFSET_CAPTURE, 3 + ); + $wrappedSamlSessionIndex = preg_replace( + '||', '', $tick[0][0] + ); + $ticket2logout = preg_replace( + '||', '', $wrappedSamlSessionIndex + ); + phpCAS::trace("Ticket to logout: ".$ticket2logout); + + // call the post-authenticate callback if registered. + if ($this->_signoutCallbackFunction) { + $args = $this->_signoutCallbackArgs; + array_unshift($args, $ticket2logout); + call_user_func_array($this->_signoutCallbackFunction, $args); + } + + // If phpCAS is managing the session_id, destroy session thanks to + // session_id. + if ($this->getChangeSessionID()) { + $session_id = $this->_sessionIdForTicket($ticket2logout); + phpCAS::trace("Session id: ".$session_id); + + // destroy a possible application session created before phpcas + if (session_id() !== "") { + session_unset(); + session_destroy(); + } + // fix session ID + session_id($session_id); + $_COOKIE[session_name()]=$session_id; + $_GET[session_name()]=$session_id; + + // Overwrite session + session_start(); + session_unset(); + session_destroy(); + phpCAS::trace("Session ". $session_id . " destroyed"); + } + } else { + phpCAS::error("Unauthorized logout request from client '".$client."'"); + phpCAS::trace("Unauthorized logout request from client '".$client."'"); + } + flush(); + phpCAS::traceExit(); + throw new CAS_GracefullTerminationException(); + + } + + /** @} */ + + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + // XX XX + // XX BASIC CLIENT FEATURES (CAS 1.0) XX + // XX XX + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + + // ######################################################################## + // ST + // ######################################################################## + /** + * @addtogroup internalBasic + * @{ + */ + + /** + * The Ticket provided in the URL of the request if present + * (empty otherwise). Written by CAS_Client::CAS_Client(), read by + * CAS_Client::getTicket() and CAS_Client::_hasPGT(). + * + * @hideinitializer + */ + private $_ticket = ''; + + /** + * This method returns the Service Ticket provided in the URL of the request. + * + * @return string service ticket. + */ + public function getTicket() + { + return $this->_ticket; + } + + /** + * This method stores the Service Ticket. + * + * @param string $st The Service Ticket. + * + * @return void + */ + public function setTicket($st) + { + $this->_ticket = $st; + } + + /** + * This method tells if a Service Ticket was stored. + * + * @return bool if a Service Ticket has been stored. + */ + public function hasTicket() + { + return !empty($this->_ticket); + } + + /** @} */ + + // ######################################################################## + // ST VALIDATION + // ######################################################################## + /** + * @addtogroup internalBasic + * @{ + */ + + /** + * @var string the certificate of the CAS server CA. + * + * @hideinitializer + */ + private $_cas_server_ca_cert = null; + + + /** + + * validate CN of the CAS server certificate + + * + + * @hideinitializer + + */ + + private $_cas_server_cn_validate = true; + + /** + * Set to true not to validate the CAS server. + * + * @hideinitializer + */ + private $_no_cas_server_validation = false; + + + /** + * Set the CA certificate of the CAS server. + * + * @param string $cert the PEM certificate file name of the CA that emited + * the cert of the server + * @param bool $validate_cn valiate CN of the CAS server certificate + * + * @return void + */ + public function setCasServerCACert($cert, $validate_cn) + { + // Argument validation + if (gettype($cert) != 'string') { + throw new CAS_TypeMismatchException($cert, '$cert', 'string'); + } + if (gettype($validate_cn) != 'boolean') { + throw new CAS_TypeMismatchException($validate_cn, '$validate_cn', 'boolean'); + } + if (!file_exists($cert)) { + throw new CAS_InvalidArgumentException("Certificate file does not exist " . $this->_requestImplementation); + } + $this->_cas_server_ca_cert = $cert; + $this->_cas_server_cn_validate = $validate_cn; + } + + /** + * Set no SSL validation for the CAS server. + * + * @return void + */ + public function setNoCasServerValidation() + { + $this->_no_cas_server_validation = true; + } + + /** + * This method is used to validate a CAS 1,0 ticket; halt on failure, and + * sets $validate_url, $text_reponse and $tree_response on success. + * + * @param string &$validate_url reference to the the URL of the request to + * the CAS server. + * @param string &$text_response reference to the response of the CAS + * server, as is (XML text). + * @param string &$tree_response reference to the response of the CAS + * server, as a DOM XML tree. + * @param bool $renew true to force the authentication with the CAS server + * + * @return bool true when successfull and issue a CAS_AuthenticationException + * and false on an error + * @throws CAS_AuthenticationException + */ + public function validateCAS10(&$validate_url,&$text_response,&$tree_response,$renew=false) + { + phpCAS::traceBegin(); + // build the URL to validate the ticket + $validate_url = $this->getServerServiceValidateURL() + .'&ticket='.urlencode($this->getTicket()); + + if ( $renew ) { + // pass the renew + $validate_url .= '&renew=true'; + } + + $headers = ''; + $err_msg = ''; + // open and read the URL + if ( !$this->_readURL($validate_url, $headers, $text_response, $err_msg) ) { + phpCAS::trace( + 'could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')' + ); + throw new CAS_AuthenticationException( + $this, 'CAS 1.0 ticket not validated', $validate_url, + true/*$no_response*/ + ); + } + + if (preg_match('/^no\n/', $text_response)) { + phpCAS::trace('Ticket has not been validated'); + throw new CAS_AuthenticationException( + $this, 'ST not validated', $validate_url, false/*$no_response*/, + false/*$bad_response*/, $text_response + ); + } else if (!preg_match('/^yes\n/', $text_response)) { + phpCAS::trace('ill-formed response'); + throw new CAS_AuthenticationException( + $this, 'Ticket not validated', $validate_url, + false/*$no_response*/, true/*$bad_response*/, $text_response + ); + } + // ticket has been validated, extract the user name + $arr = preg_split('/\n/', $text_response); + $this->_setUser(trim($arr[1])); + + $this->_renameSession($this->getTicket()); + + // at this step, ticket has been validated and $this->_user has been set, + phpCAS::traceEnd(true); + return true; + } + + /** @} */ + + + // ######################################################################## + // SAML VALIDATION + // ######################################################################## + /** + * @addtogroup internalSAML + * @{ + */ + + /** + * This method is used to validate a SAML TICKET; halt on failure, and sets + * $validate_url, $text_reponse and $tree_response on success. These + * parameters are used later by CAS_Client::_validatePGT() for CAS proxies. + * + * @param string &$validate_url reference to the the URL of the request to + * the CAS server. + * @param string &$text_response reference to the response of the CAS + * server, as is (XML text). + * @param string &$tree_response reference to the response of the CAS + * server, as a DOM XML tree. + * @param bool $renew true to force the authentication with the CAS server + * + * @return bool true when successfull and issue a CAS_AuthenticationException + * and false on an error + * + * @throws CAS_AuthenticationException + */ + public function validateSA(&$validate_url,&$text_response,&$tree_response,$renew=false) + { + phpCAS::traceBegin(); + $result = false; + // build the URL to validate the ticket + $validate_url = $this->getServerSamlValidateURL(); + + if ( $renew ) { + // pass the renew + $validate_url .= '&renew=true'; + } + + $headers = ''; + $err_msg = ''; + // open and read the URL + if ( !$this->_readURL($validate_url, $headers, $text_response, $err_msg) ) { + phpCAS::trace( + 'could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')' + ); + throw new CAS_AuthenticationException( + $this, 'SA not validated', $validate_url, true/*$no_response*/ + ); + } + + phpCAS::trace('server version: '.$this->getServerVersion()); + + // analyze the result depending on the version + switch ($this->getServerVersion()) { + case SAML_VERSION_1_1: + // create new DOMDocument Object + $dom = new DOMDocument(); + // Fix possible whitspace problems + $dom->preserveWhiteSpace = false; + // read the response of the CAS server into a DOM object + if (!($dom->loadXML($text_response))) { + phpCAS::trace('dom->loadXML() failed'); + throw new CAS_AuthenticationException( + $this, 'SA not validated', $validate_url, + false/*$no_response*/, true/*$bad_response*/, + $text_response + ); + } + // read the root node of the XML tree + if (!($tree_response = $dom->documentElement)) { + phpCAS::trace('documentElement() failed'); + throw new CAS_AuthenticationException( + $this, 'SA not validated', $validate_url, + false/*$no_response*/, true/*$bad_response*/, + $text_response + ); + } else if ( $tree_response->localName != 'Envelope' ) { + // insure that tag name is 'Envelope' + phpCAS::trace( + 'bad XML root node (should be `Envelope\' instead of `' + .$tree_response->localName.'\'' + ); + throw new CAS_AuthenticationException( + $this, 'SA not validated', $validate_url, + false/*$no_response*/, true/*$bad_response*/, + $text_response + ); + } else if ($tree_response->getElementsByTagName("NameIdentifier")->length != 0) { + // check for the NameIdentifier tag in the SAML response + $success_elements = $tree_response->getElementsByTagName("NameIdentifier"); + phpCAS::trace('NameIdentifier found'); + $user = trim($success_elements->item(0)->nodeValue); + phpCAS::trace('user = `'.$user.'`'); + $this->_setUser($user); + $this->_setSessionAttributes($text_response); + $result = true; + } else { + phpCAS::trace('no tag found in SAML payload'); + throw new CAS_AuthenticationException( + $this, 'SA not validated', $validate_url, + false/*$no_response*/, true/*$bad_response*/, + $text_response + ); + } + } + if ($result) { + $this->_renameSession($this->getTicket()); + } + // at this step, ST has been validated and $this->_user has been set, + phpCAS::traceEnd($result); + return $result; + } + + /** + * This method will parse the DOM and pull out the attributes from the SAML + * payload and put them into an array, then put the array into the session. + * + * @param string $text_response the SAML payload. + * + * @return bool true when successfull and false if no attributes a found + */ + private function _setSessionAttributes($text_response) + { + phpCAS::traceBegin(); + + $result = false; + + $attr_array = array(); + + // create new DOMDocument Object + $dom = new DOMDocument(); + // Fix possible whitspace problems + $dom->preserveWhiteSpace = false; + if (($dom->loadXML($text_response))) { + $xPath = new DOMXPath($dom); + $xPath->registerNamespace('samlp', 'urn:oasis:names:tc:SAML:1.0:protocol'); + $xPath->registerNamespace('saml', 'urn:oasis:names:tc:SAML:1.0:assertion'); + $nodelist = $xPath->query("//saml:Attribute"); + + if ($nodelist) { + foreach ($nodelist as $node) { + $xres = $xPath->query("saml:AttributeValue", $node); + $name = $node->getAttribute("AttributeName"); + $value_array = array(); + foreach ($xres as $node2) { + $value_array[] = $node2->nodeValue; + } + $attr_array[$name] = $value_array; + } + // UGent addition... + foreach ($attr_array as $attr_key => $attr_value) { + if (count($attr_value) > 1) { + $this->_attributes[$attr_key] = $attr_value; + phpCAS::trace("* " . $attr_key . "=" . print_r($attr_value, true)); + } else { + $this->_attributes[$attr_key] = $attr_value[0]; + phpCAS::trace("* " . $attr_key . "=" . $attr_value[0]); + } + } + $result = true; + } else { + phpCAS::trace("SAML Attributes are empty"); + $result = false; + } + } + phpCAS::traceEnd($result); + return $result; + } + + /** @} */ + + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + // XX XX + // XX PROXY FEATURES (CAS 2.0) XX + // XX XX + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + + // ######################################################################## + // PROXYING + // ######################################################################## + /** + * @addtogroup internalProxy + * @{ + */ + + /** + * @var bool is the client a proxy + * A boolean telling if the client is a CAS proxy or not. Written by + * CAS_Client::CAS_Client(), read by CAS_Client::isProxy(). + */ + private $_proxy; + + /** + * @var CAS_CookieJar Handler for managing service cookies. + */ + private $_serviceCookieJar; + + /** + * Tells if a CAS client is a CAS proxy or not + * + * @return bool true when the CAS client is a CAS proxy, false otherwise + */ + public function isProxy() + { + return $this->_proxy; + } + + + /** @} */ + // ######################################################################## + // PGT + // ######################################################################## + /** + * @addtogroup internalProxy + * @{ + */ + + /** + * the Proxy Grnting Ticket given by the CAS server (empty otherwise). + * Written by CAS_Client::_setPGT(), read by CAS_Client::_getPGT() and + * CAS_Client::_hasPGT(). + * + * @hideinitializer + */ + private $_pgt = ''; + + /** + * This method returns the Proxy Granting Ticket given by the CAS server. + * + * @return string the Proxy Granting Ticket. + */ + private function _getPGT() + { + return $this->_pgt; + } + + /** + * This method stores the Proxy Granting Ticket. + * + * @param string $pgt The Proxy Granting Ticket. + * + * @return void + */ + private function _setPGT($pgt) + { + $this->_pgt = $pgt; + } + + /** + * This method tells if a Proxy Granting Ticket was stored. + * + * @return bool true if a Proxy Granting Ticket has been stored. + */ + private function _hasPGT() + { + return !empty($this->_pgt); + } + + /** @} */ + + // ######################################################################## + // CALLBACK MODE + // ######################################################################## + /** + * @addtogroup internalCallback + * @{ + */ + /** + * each PHP script using phpCAS in proxy mode is its own callback to get the + * PGT back from the CAS server. callback_mode is detected by the constructor + * thanks to the GET parameters. + */ + + /** + * @var bool a boolean to know if the CAS client is running in callback mode. Written by + * CAS_Client::setCallBackMode(), read by CAS_Client::_isCallbackMode(). + * + * @hideinitializer + */ + private $_callback_mode = false; + + /** + * This method sets/unsets callback mode. + * + * @param bool $callback_mode true to set callback mode, false otherwise. + * + * @return void + */ + private function _setCallbackMode($callback_mode) + { + $this->_callback_mode = $callback_mode; + } + + /** + * This method returns true when the CAS client is running in callback mode, + * false otherwise. + * + * @return bool A boolean. + */ + private function _isCallbackMode() + { + return $this->_callback_mode; + } + + /** + * @var bool a boolean to know if the CAS client is using POST parameters when in callback mode. + * Written by CAS_Client::_setCallbackModeUsingPost(), read by CAS_Client::_isCallbackModeUsingPost(). + * + * @hideinitializer + */ + private $_callback_mode_using_post = false; + + /** + * This method sets/unsets usage of POST parameters in callback mode (default/false is GET parameters) + * + * @param bool $callback_mode_using_post true to use POST, false to use GET (default). + * + * @return void + */ + private function _setCallbackModeUsingPost($callback_mode_using_post) + { + $this->_callback_mode_using_post = $callback_mode_using_post; + } + + /** + * This method returns true when the callback mode is using POST, false otherwise. + * + * @return bool A boolean. + */ + private function _isCallbackModeUsingPost() + { + return $this->_callback_mode_using_post; + } + + /** + * the URL that should be used for the PGT callback (in fact the URL of the + * current request without any CGI parameter). Written and read by + * CAS_Client::_getCallbackURL(). + * + * @hideinitializer + */ + private $_callback_url = ''; + + /** + * This method returns the URL that should be used for the PGT callback (in + * fact the URL of the current request without any CGI parameter, except if + * phpCAS::setFixedCallbackURL() was used). + * + * @return string The callback URL + */ + private function _getCallbackURL() + { + // the URL is built when needed only + if ( empty($this->_callback_url) ) { + // remove the ticket if present in the URL + $final_uri = $this->getServiceBaseUrl()->get(); + $request_uri = $_SERVER['REQUEST_URI']; + $request_uri = preg_replace('/\?.*$/', '', $request_uri); + $final_uri .= $request_uri; + $this->_callback_url = $final_uri; + } + return $this->_callback_url; + } + + /** + * This method sets the callback url. + * + * @param string $url url to set callback + * + * @return string the callback url + */ + public function setCallbackURL($url) + { + // Sequence validation + $this->ensureIsProxy(); + // Argument Validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + + return $this->_callback_url = $url; + } + + /** + * This method is called by CAS_Client::CAS_Client() when running in callback + * mode. It stores the PGT and its PGT Iou, prints its output and halts. + * + * @return void + */ + private function _callback() + { + phpCAS::traceBegin(); + if ($this->_isCallbackModeUsingPost()) { + $pgtId = $_POST['pgtId']; + $pgtIou = $_POST['pgtIou']; + } else { + $pgtId = $_GET['pgtId']; + $pgtIou = $_GET['pgtIou']; + } + if (preg_match('/^PGTIOU-[\.\-\w]+$/', $pgtIou)) { + if (preg_match('/^[PT]GT-[\.\-\w]+$/', $pgtId)) { + phpCAS::trace('Storing PGT `'.$pgtId.'\' (id=`'.$pgtIou.'\')'); + $this->_storePGT($pgtId, $pgtIou); + if ($this->isXmlResponse()) { + echo '' . "\r\n"; + echo ''; + phpCAS::traceExit("XML response sent"); + } else { + $this->printHTMLHeader('phpCAS callback'); + echo '

Storing PGT `'.$pgtId.'\' (id=`'.$pgtIou.'\').

'; + $this->printHTMLFooter(); + phpCAS::traceExit("HTML response sent"); + } + phpCAS::traceExit("Successfull Callback"); + } else { + phpCAS::error('PGT format invalid' . $pgtId); + phpCAS::traceExit('PGT format invalid' . $pgtId); + } + } else { + phpCAS::error('PGTiou format invalid' . $pgtIou); + phpCAS::traceExit('PGTiou format invalid' . $pgtIou); + } + + // Flush the buffer to prevent from sending anything other then a 200 + // Success Status back to the CAS Server. The Exception would normally + // report as a 500 error. + flush(); + throw new CAS_GracefullTerminationException(); + } + + /** + * Check if application/xml or text/xml is pressent in HTTP_ACCEPT header values + * when return value is complex and contains attached q parameters. + * Example: HTTP_ACCEPT = text/html,application/xhtml+xml,application/xml;q=0.9 + * @return bool + */ + private function isXmlResponse() + { + if (!array_key_exists('HTTP_ACCEPT', $_SERVER)) { + return false; + } + if (strpos($_SERVER['HTTP_ACCEPT'], 'application/xml') === false && strpos($_SERVER['HTTP_ACCEPT'], 'text/xml') === false) { + return false; + } + + return true; + } + + /** @} */ + + // ######################################################################## + // PGT STORAGE + // ######################################################################## + /** + * @addtogroup internalPGTStorage + * @{ + */ + + /** + * @var CAS_PGTStorage_AbstractStorage + * an instance of a class inheriting of PGTStorage, used to deal with PGT + * storage. Created by CAS_Client::setPGTStorageFile(), used + * by CAS_Client::setPGTStorageFile() and CAS_Client::_initPGTStorage(). + * + * @hideinitializer + */ + private $_pgt_storage = null; + + /** + * This method is used to initialize the storage of PGT's. + * Halts on error. + * + * @return void + */ + private function _initPGTStorage() + { + // if no SetPGTStorageXxx() has been used, default to file + if ( !is_object($this->_pgt_storage) ) { + $this->setPGTStorageFile(); + } + + // initializes the storage + $this->_pgt_storage->init(); + } + + /** + * This method stores a PGT. Halts on error. + * + * @param string $pgt the PGT to store + * @param string $pgt_iou its corresponding Iou + * + * @return void + */ + private function _storePGT($pgt,$pgt_iou) + { + // ensure that storage is initialized + $this->_initPGTStorage(); + // writes the PGT + $this->_pgt_storage->write($pgt, $pgt_iou); + } + + /** + * This method reads a PGT from its Iou and deletes the corresponding + * storage entry. + * + * @param string $pgt_iou the PGT Iou + * + * @return string mul The PGT corresponding to the Iou, false when not found. + */ + private function _loadPGT($pgt_iou) + { + // ensure that storage is initialized + $this->_initPGTStorage(); + // read the PGT + return $this->_pgt_storage->read($pgt_iou); + } + + /** + * This method can be used to set a custom PGT storage object. + * + * @param CAS_PGTStorage_AbstractStorage $storage a PGT storage object that + * inherits from the CAS_PGTStorage_AbstractStorage class + * + * @return void + */ + public function setPGTStorage($storage) + { + // Sequence validation + $this->ensureIsProxy(); + + // check that the storage has not already been set + if ( is_object($this->_pgt_storage) ) { + phpCAS::error('PGT storage already defined'); + } + + // check to make sure a valid storage object was specified + if ( !($storage instanceof CAS_PGTStorage_AbstractStorage) ) + throw new CAS_TypeMismatchException($storage, '$storage', 'CAS_PGTStorage_AbstractStorage object'); + + // store the PGTStorage object + $this->_pgt_storage = $storage; + } + + /** + * This method is used to tell phpCAS to store the response of the + * CAS server to PGT requests in a database. + * + * @param string|PDO $dsn_or_pdo a dsn string to use for creating a PDO + * object or a PDO object + * @param string $username the username to use when connecting to the + * database + * @param string $password the password to use when connecting to the + * database + * @param string $table the table to use for storing and retrieving + * PGTs + * @param string $driver_options any driver options to use when connecting + * to the database + * + * @return void + */ + public function setPGTStorageDb( + $dsn_or_pdo, $username='', $password='', $table='', $driver_options=null + ) { + // Sequence validation + $this->ensureIsProxy(); + + // Argument validation + if (!(is_object($dsn_or_pdo) && $dsn_or_pdo instanceof PDO) && !is_string($dsn_or_pdo)) + throw new CAS_TypeMismatchException($dsn_or_pdo, '$dsn_or_pdo', 'string or PDO object'); + if (gettype($username) != 'string') + throw new CAS_TypeMismatchException($username, '$username', 'string'); + if (gettype($password) != 'string') + throw new CAS_TypeMismatchException($password, '$password', 'string'); + if (gettype($table) != 'string') + throw new CAS_TypeMismatchException($table, '$password', 'string'); + + // create the storage object + $this->setPGTStorage( + new CAS_PGTStorage_Db( + $this, $dsn_or_pdo, $username, $password, $table, $driver_options + ) + ); + } + + /** + * This method is used to tell phpCAS to store the response of the + * CAS server to PGT requests onto the filesystem. + * + * @param string $path the path where the PGT's should be stored + * + * @return void + */ + public function setPGTStorageFile($path='') + { + // Sequence validation + $this->ensureIsProxy(); + + // Argument validation + if (gettype($path) != 'string') + throw new CAS_TypeMismatchException($path, '$path', 'string'); + + // create the storage object + $this->setPGTStorage(new CAS_PGTStorage_File($this, $path)); + } + + + // ######################################################################## + // PGT VALIDATION + // ######################################################################## + /** + * This method is used to validate a PGT; halt on failure. + * + * @param string &$validate_url the URL of the request to the CAS server. + * @param string $text_response the response of the CAS server, as is + * (XML text); result of + * CAS_Client::validateCAS10() or + * CAS_Client::validateCAS20(). + * @param DOMElement $tree_response the response of the CAS server, as a DOM XML + * tree; result of CAS_Client::validateCAS10() or CAS_Client::validateCAS20(). + * + * @return bool true when successfull and issue a CAS_AuthenticationException + * and false on an error + * + * @throws CAS_AuthenticationException + */ + private function _validatePGT(&$validate_url,$text_response,$tree_response) + { + phpCAS::traceBegin(); + if ( $tree_response->getElementsByTagName("proxyGrantingTicket")->length == 0) { + phpCAS::trace(' not found'); + // authentication succeded, but no PGT Iou was transmitted + throw new CAS_AuthenticationException( + $this, 'Ticket validated but no PGT Iou transmitted', + $validate_url, false/*$no_response*/, false/*$bad_response*/, + $text_response + ); + } else { + // PGT Iou transmitted, extract it + $pgt_iou = trim( + $tree_response->getElementsByTagName("proxyGrantingTicket")->item(0)->nodeValue + ); + if (preg_match('/^PGTIOU-[\.\-\w]+$/', $pgt_iou)) { + $pgt = $this->_loadPGT($pgt_iou); + if ( $pgt == false ) { + phpCAS::trace('could not load PGT'); + throw new CAS_AuthenticationException( + $this, + 'PGT Iou was transmitted but PGT could not be retrieved', + $validate_url, false/*$no_response*/, + false/*$bad_response*/, $text_response + ); + } + $this->_setPGT($pgt); + } else { + phpCAS::trace('PGTiou format error'); + throw new CAS_AuthenticationException( + $this, 'PGT Iou was transmitted but has wrong format', + $validate_url, false/*$no_response*/, false/*$bad_response*/, + $text_response + ); + } + } + phpCAS::traceEnd(true); + return true; + } + + // ######################################################################## + // PGT VALIDATION + // ######################################################################## + + /** + * This method is used to retrieve PT's from the CAS server thanks to a PGT. + * + * @param string $target_service the service to ask for with the PT. + * @param int &$err_code an error code (PHPCAS_SERVICE_OK on success). + * @param string &$err_msg an error message (empty on success). + * + * @return string|false a Proxy Ticket, or false on error. + */ + public function retrievePT($target_service,&$err_code,&$err_msg) + { + // Argument validation + if (gettype($target_service) != 'string') + throw new CAS_TypeMismatchException($target_service, '$target_service', 'string'); + + phpCAS::traceBegin(); + + // by default, $err_msg is set empty and $pt to true. On error, $pt is + // set to false and $err_msg to an error message. At the end, if $pt is false + // and $error_msg is still empty, it is set to 'invalid response' (the most + // commonly encountered error). + $err_msg = ''; + + // build the URL to retrieve the PT + $cas_url = $this->getServerProxyURL().'?targetService=' + .urlencode($target_service).'&pgt='.$this->_getPGT(); + + $headers = ''; + $cas_response = ''; + // open and read the URL + if ( !$this->_readURL($cas_url, $headers, $cas_response, $err_msg) ) { + phpCAS::trace( + 'could not open URL \''.$cas_url.'\' to validate ('.$err_msg.')' + ); + $err_code = PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE; + $err_msg = 'could not retrieve PT (no response from the CAS server)'; + phpCAS::traceEnd(false); + return false; + } + + $bad_response = false; + + // create new DOMDocument object + $dom = new DOMDocument(); + // Fix possible whitspace problems + $dom->preserveWhiteSpace = false; + // read the response of the CAS server into a DOM object + if ( !($dom->loadXML($cas_response))) { + phpCAS::trace('dom->loadXML() failed'); + // read failed + $bad_response = true; + } + + if ( !$bad_response ) { + // read the root node of the XML tree + if ( !($root = $dom->documentElement) ) { + phpCAS::trace('documentElement failed'); + // read failed + $bad_response = true; + } + } + + if ( !$bad_response ) { + // insure that tag name is 'serviceResponse' + if ( $root->localName != 'serviceResponse' ) { + phpCAS::trace('localName failed'); + // bad root node + $bad_response = true; + } + } + + if ( !$bad_response ) { + // look for a proxySuccess tag + if ( $root->getElementsByTagName("proxySuccess")->length != 0) { + $proxy_success_list = $root->getElementsByTagName("proxySuccess"); + + // authentication succeded, look for a proxyTicket tag + if ( $proxy_success_list->item(0)->getElementsByTagName("proxyTicket")->length != 0) { + $err_code = PHPCAS_SERVICE_OK; + $err_msg = ''; + $pt = trim( + $proxy_success_list->item(0)->getElementsByTagName("proxyTicket")->item(0)->nodeValue + ); + phpCAS::trace('original PT: '.trim($pt)); + phpCAS::traceEnd($pt); + return $pt; + } else { + phpCAS::trace(' was found, but not '); + } + } else if ($root->getElementsByTagName("proxyFailure")->length != 0) { + // look for a proxyFailure tag + $proxy_failure_list = $root->getElementsByTagName("proxyFailure"); + + // authentication failed, extract the error + $err_code = PHPCAS_SERVICE_PT_FAILURE; + $err_msg = 'PT retrieving failed (code=`' + .$proxy_failure_list->item(0)->getAttribute('code') + .'\', message=`' + .trim($proxy_failure_list->item(0)->nodeValue) + .'\')'; + phpCAS::traceEnd(false); + return false; + } else { + phpCAS::trace('neither nor found'); + } + } + + // at this step, we are sure that the response of the CAS server was + // illformed + $err_code = PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE; + $err_msg = 'Invalid response from the CAS server (response=`' + .$cas_response.'\')'; + + phpCAS::traceEnd(false); + return false; + } + + /** @} */ + + // ######################################################################## + // READ CAS SERVER ANSWERS + // ######################################################################## + + /** + * @addtogroup internalMisc + * @{ + */ + + /** + * This method is used to acces a remote URL. + * + * @param string $url the URL to access. + * @param string &$headers an array containing the HTTP header lines of the + * response (an empty array on failure). + * @param string &$body the body of the response, as a string (empty on + * failure). + * @param string &$err_msg an error message, filled on failure. + * + * @return bool true on success, false otherwise (in this later case, $err_msg + * contains an error message). + */ + private function _readURL($url, &$headers, &$body, &$err_msg) + { + phpCAS::traceBegin(); + $className = $this->_requestImplementation; + $request = new $className(); + + if (count($this->_curl_options)) { + $request->setCurlOptions($this->_curl_options); + } + + $request->setUrl($url); + + if (empty($this->_cas_server_ca_cert) && !$this->_no_cas_server_validation) { + phpCAS::error( + 'one of the methods phpCAS::setCasServerCACert() or phpCAS::setNoCasServerValidation() must be called.' + ); + } + if ($this->_cas_server_ca_cert != '') { + $request->setSslCaCert( + $this->_cas_server_ca_cert, $this->_cas_server_cn_validate + ); + } + + // add extra stuff if SAML + if ($this->getServerVersion() == SAML_VERSION_1_1) { + $request->addHeader("soapaction: http://www.oasis-open.org/committees/security"); + $request->addHeader("cache-control: no-cache"); + $request->addHeader("pragma: no-cache"); + $request->addHeader("accept: text/xml"); + $request->addHeader("connection: keep-alive"); + $request->addHeader("content-type: text/xml"); + $request->makePost(); + $request->setPostBody($this->_buildSAMLPayload()); + } + + if ($request->send()) { + $headers = $request->getResponseHeaders(); + $body = $request->getResponseBody(); + $err_msg = ''; + phpCAS::traceEnd(true); + return true; + } else { + $headers = ''; + $body = ''; + $err_msg = $request->getErrorMessage(); + phpCAS::traceEnd(false); + return false; + } + } + + /** + * This method is used to build the SAML POST body sent to /samlValidate URL. + * + * @return string the SOAP-encased SAMLP artifact (the ticket). + */ + private function _buildSAMLPayload() + { + phpCAS::traceBegin(); + + //get the ticket + $sa = urlencode($this->getTicket()); + + $body = SAML_SOAP_ENV.SAML_SOAP_BODY.SAMLP_REQUEST + .SAML_ASSERTION_ARTIFACT.$sa.SAML_ASSERTION_ARTIFACT_CLOSE + .SAMLP_REQUEST_CLOSE.SAML_SOAP_BODY_CLOSE.SAML_SOAP_ENV_CLOSE; + + phpCAS::traceEnd($body); + return ($body); + } + + /** @} **/ + + // ######################################################################## + // ACCESS TO EXTERNAL SERVICES + // ######################################################################## + + /** + * @addtogroup internalProxyServices + * @{ + */ + + + /** + * Answer a proxy-authenticated service handler. + * + * @param string $type The service type. One of: + * PHPCAS_PROXIED_SERVICE_HTTP_GET, PHPCAS_PROXIED_SERVICE_HTTP_POST, + * PHPCAS_PROXIED_SERVICE_IMAP + * + * @return CAS_ProxiedService + * @throws InvalidArgumentException If the service type is unknown. + */ + public function getProxiedService ($type) + { + // Sequence validation + $this->ensureIsProxy(); + $this->ensureAuthenticationCallSuccessful(); + + // Argument validation + if (gettype($type) != 'string') + throw new CAS_TypeMismatchException($type, '$type', 'string'); + + switch ($type) { + case PHPCAS_PROXIED_SERVICE_HTTP_GET: + case PHPCAS_PROXIED_SERVICE_HTTP_POST: + $requestClass = $this->_requestImplementation; + $request = new $requestClass(); + if (count($this->_curl_options)) { + $request->setCurlOptions($this->_curl_options); + } + $proxiedService = new $type($request, $this->_serviceCookieJar); + if ($proxiedService instanceof CAS_ProxiedService_Testable) { + $proxiedService->setCasClient($this); + } + return $proxiedService; + case PHPCAS_PROXIED_SERVICE_IMAP; + $proxiedService = new CAS_ProxiedService_Imap($this->_getUser()); + if ($proxiedService instanceof CAS_ProxiedService_Testable) { + $proxiedService->setCasClient($this); + } + return $proxiedService; + default: + throw new CAS_InvalidArgumentException( + "Unknown proxied-service type, $type." + ); + } + } + + /** + * Initialize a proxied-service handler with the proxy-ticket it should use. + * + * @param CAS_ProxiedService $proxiedService service handler + * + * @return void + * + * @throws CAS_ProxyTicketException If there is a proxy-ticket failure. + * The code of the Exception will be one of: + * PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE + * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE + * PHPCAS_SERVICE_PT_FAILURE + * @throws CAS_ProxiedService_Exception If there is a failure getting the + * url from the proxied service. + */ + public function initializeProxiedService (CAS_ProxiedService $proxiedService) + { + // Sequence validation + $this->ensureIsProxy(); + $this->ensureAuthenticationCallSuccessful(); + + $url = $proxiedService->getServiceUrl(); + if (!is_string($url)) { + throw new CAS_ProxiedService_Exception( + "Proxied Service ".get_class($proxiedService) + ."->getServiceUrl() should have returned a string, returned a " + .gettype($url)." instead." + ); + } + $pt = $this->retrievePT($url, $err_code, $err_msg); + if (!$pt) { + throw new CAS_ProxyTicketException($err_msg, $err_code); + } + $proxiedService->setProxyTicket($pt); + } + + /** + * This method is used to access an HTTP[S] service. + * + * @param string $url the service to access. + * @param int &$err_code an error code Possible values are + * PHPCAS_SERVICE_OK (on success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, + * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE, PHPCAS_SERVICE_PT_FAILURE, + * PHPCAS_SERVICE_NOT_AVAILABLE. + * @param string &$output the output of the service (also used to give an error + * message on failure). + * + * @return bool true on success, false otherwise (in this later case, $err_code + * gives the reason why it failed and $output contains an error message). + */ + public function serviceWeb($url,&$err_code,&$output) + { + // Sequence validation + $this->ensureIsProxy(); + $this->ensureAuthenticationCallSuccessful(); + + // Argument validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + + try { + $service = $this->getProxiedService(PHPCAS_PROXIED_SERVICE_HTTP_GET); + $service->setUrl($url); + $service->send(); + $output = $service->getResponseBody(); + $err_code = PHPCAS_SERVICE_OK; + return true; + } catch (CAS_ProxyTicketException $e) { + $err_code = $e->getCode(); + $output = $e->getMessage(); + return false; + } catch (CAS_ProxiedService_Exception $e) { + $lang = $this->getLangObj(); + $output = sprintf( + $lang->getServiceUnavailable(), $url, $e->getMessage() + ); + $err_code = PHPCAS_SERVICE_NOT_AVAILABLE; + return false; + } + } + + /** + * This method is used to access an IMAP/POP3/NNTP service. + * + * @param string $url a string giving the URL of the service, including + * the mailing box for IMAP URLs, as accepted by imap_open(). + * @param string $serviceUrl a string giving for CAS retrieve Proxy ticket + * @param string $flags options given to imap_open(). + * @param int &$err_code an error code Possible values are + * PHPCAS_SERVICE_OK (on success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, + * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE, PHPCAS_SERVICE_PT_FAILURE, + * PHPCAS_SERVICE_NOT_AVAILABLE. + * @param string &$err_msg an error message on failure + * @param string &$pt the Proxy Ticket (PT) retrieved from the CAS + * server to access the URL on success, false on error). + * + * @return object|false an IMAP stream on success, false otherwise (in this later + * case, $err_code gives the reason why it failed and $err_msg contains an + * error message). + */ + public function serviceMail($url,$serviceUrl,$flags,&$err_code,&$err_msg,&$pt) + { + // Sequence validation + $this->ensureIsProxy(); + $this->ensureAuthenticationCallSuccessful(); + + // Argument validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + if (gettype($serviceUrl) != 'string') + throw new CAS_TypeMismatchException($serviceUrl, '$serviceUrl', 'string'); + if (gettype($flags) != 'integer') + throw new CAS_TypeMismatchException($flags, '$flags', 'string'); + + try { + $service = $this->getProxiedService(PHPCAS_PROXIED_SERVICE_IMAP); + $service->setServiceUrl($serviceUrl); + $service->setMailbox($url); + $service->setOptions($flags); + + $stream = $service->open(); + $err_code = PHPCAS_SERVICE_OK; + $pt = $service->getImapProxyTicket(); + return $stream; + } catch (CAS_ProxyTicketException $e) { + $err_msg = $e->getMessage(); + $err_code = $e->getCode(); + $pt = false; + return false; + } catch (CAS_ProxiedService_Exception $e) { + $lang = $this->getLangObj(); + $err_msg = sprintf( + $lang->getServiceUnavailable(), + $url, + $e->getMessage() + ); + $err_code = PHPCAS_SERVICE_NOT_AVAILABLE; + $pt = false; + return false; + } + } + + /** @} **/ + + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + // XX XX + // XX PROXIED CLIENT FEATURES (CAS 2.0) XX + // XX XX + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + + // ######################################################################## + // PT + // ######################################################################## + /** + * @addtogroup internalService + * @{ + */ + + /** + * This array will store a list of proxies in front of this application. This + * property will only be populated if this script is being proxied rather than + * accessed directly. + * + * It is set in CAS_Client::validateCAS20() and can be read by + * CAS_Client::getProxies() + * + * @access private + */ + private $_proxies = array(); + + /** + * Answer an array of proxies that are sitting in front of this application. + * + * This method will only return a non-empty array if we have received and + * validated a Proxy Ticket. + * + * @return array + * @access public + */ + public function getProxies() + { + return $this->_proxies; + } + + /** + * Set the Proxy array, probably from persistant storage. + * + * @param array $proxies An array of proxies + * + * @return void + * @access private + */ + private function _setProxies($proxies) + { + $this->_proxies = $proxies; + if (!empty($proxies)) { + // For proxy-authenticated requests people are not viewing the URL + // directly since the client is another application making a + // web-service call. + // Because of this, stripping the ticket from the URL is unnecessary + // and causes another web-service request to be performed. Additionally, + // if session handling on either the client or the server malfunctions + // then the subsequent request will not complete successfully. + $this->setNoClearTicketsFromUrl(); + } + } + + /** + * A container of patterns to be allowed as proxies in front of the cas client. + * + * @var CAS_ProxyChain_AllowedList + */ + private $_allowed_proxy_chains; + + /** + * Answer the CAS_ProxyChain_AllowedList object for this client. + * + * @return CAS_ProxyChain_AllowedList + */ + public function getAllowedProxyChains () + { + if (empty($this->_allowed_proxy_chains)) { + $this->_allowed_proxy_chains = new CAS_ProxyChain_AllowedList(); + } + return $this->_allowed_proxy_chains; + } + + /** @} */ + // ######################################################################## + // PT VALIDATION + // ######################################################################## + /** + * @addtogroup internalProxied + * @{ + */ + + /** + * This method is used to validate a cas 2.0 ST or PT; halt on failure + * Used for all CAS 2.0 validations + * + * @param string &$validate_url the url of the reponse + * @param string &$text_response the text of the repsones + * @param DOMElement &$tree_response the domxml tree of the respones + * @param bool $renew true to force the authentication with the CAS server + * + * @return bool true when successfull and issue a CAS_AuthenticationException + * and false on an error + * + * @throws CAS_AuthenticationException + */ + public function validateCAS20(&$validate_url,&$text_response,&$tree_response, $renew=false) + { + phpCAS::traceBegin(); + phpCAS::trace($text_response); + // build the URL to validate the ticket + if ($this->getAllowedProxyChains()->isProxyingAllowed()) { + $validate_url = $this->getServerProxyValidateURL().'&ticket=' + .urlencode($this->getTicket()); + } else { + $validate_url = $this->getServerServiceValidateURL().'&ticket=' + .urlencode($this->getTicket()); + } + + if ( $this->isProxy() ) { + // pass the callback url for CAS proxies + $validate_url .= '&pgtUrl='.urlencode($this->_getCallbackURL()); + } + + if ( $renew ) { + // pass the renew + $validate_url .= '&renew=true'; + } + + // open and read the URL + if ( !$this->_readURL($validate_url, $headers, $text_response, $err_msg) ) { + phpCAS::trace( + 'could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')' + ); + throw new CAS_AuthenticationException( + $this, 'Ticket not validated', $validate_url, + true/*$no_response*/ + ); + } + + // create new DOMDocument object + $dom = new DOMDocument(); + // Fix possible whitspace problems + $dom->preserveWhiteSpace = false; + // CAS servers should only return data in utf-8 + $dom->encoding = "utf-8"; + // read the response of the CAS server into a DOMDocument object + if ( !($dom->loadXML($text_response))) { + // read failed + throw new CAS_AuthenticationException( + $this, 'Ticket not validated', $validate_url, + false/*$no_response*/, true/*$bad_response*/, $text_response + ); + } else if ( !($tree_response = $dom->documentElement) ) { + // read the root node of the XML tree + // read failed + throw new CAS_AuthenticationException( + $this, 'Ticket not validated', $validate_url, + false/*$no_response*/, true/*$bad_response*/, $text_response + ); + } else if ($tree_response->localName != 'serviceResponse') { + // insure that tag name is 'serviceResponse' + // bad root node + throw new CAS_AuthenticationException( + $this, 'Ticket not validated', $validate_url, + false/*$no_response*/, true/*$bad_response*/, $text_response + ); + } else if ( $tree_response->getElementsByTagName("authenticationFailure")->length != 0) { + // authentication failed, extract the error code and message and throw exception + $auth_fail_list = $tree_response + ->getElementsByTagName("authenticationFailure"); + throw new CAS_AuthenticationException( + $this, 'Ticket not validated', $validate_url, + false/*$no_response*/, false/*$bad_response*/, + $text_response, + $auth_fail_list->item(0)->getAttribute('code')/*$err_code*/, + trim($auth_fail_list->item(0)->nodeValue)/*$err_msg*/ + ); + } else if ($tree_response->getElementsByTagName("authenticationSuccess")->length != 0) { + // authentication succeded, extract the user name + $success_elements = $tree_response + ->getElementsByTagName("authenticationSuccess"); + if ( $success_elements->item(0)->getElementsByTagName("user")->length == 0) { + // no user specified => error + throw new CAS_AuthenticationException( + $this, 'Ticket not validated', $validate_url, + false/*$no_response*/, true/*$bad_response*/, $text_response + ); + } else { + $this->_setUser( + trim( + $success_elements->item(0)->getElementsByTagName("user")->item(0)->nodeValue + ) + ); + $this->_readExtraAttributesCas20($success_elements); + // Store the proxies we are sitting behind for authorization checking + $proxyList = array(); + if ( sizeof($arr = $success_elements->item(0)->getElementsByTagName("proxy")) > 0) { + foreach ($arr as $proxyElem) { + phpCAS::trace("Found Proxy: ".$proxyElem->nodeValue); + $proxyList[] = trim($proxyElem->nodeValue); + } + $this->_setProxies($proxyList); + phpCAS::trace("Storing Proxy List"); + } + // Check if the proxies in front of us are allowed + if (!$this->getAllowedProxyChains()->isProxyListAllowed($proxyList)) { + throw new CAS_AuthenticationException( + $this, 'Proxy not allowed', $validate_url, + false/*$no_response*/, true/*$bad_response*/, + $text_response + ); + } else { + $result = true; + } + } + } else { + throw new CAS_AuthenticationException( + $this, 'Ticket not validated', $validate_url, + false/*$no_response*/, true/*$bad_response*/, + $text_response + ); + } + + $this->_renameSession($this->getTicket()); + + // at this step, Ticket has been validated and $this->_user has been set, + + phpCAS::traceEnd($result); + return $result; + } + + /** + * This method recursively parses the attribute XML. + * It also collapses name-value pairs into a single + * array entry. It parses all common formats of + * attributes and well formed XML files. + * + * @param string $root the DOM root element to be parsed + * @param string $namespace namespace of the elements + * + * @return an array of the parsed XML elements + * + * Formats tested: + * + * "Jasig Style" Attributes: + * + * + * + * jsmith + * + * RubyCAS + * Smith + * John + * CN=Staff,OU=Groups,DC=example,DC=edu + * CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu + * + * PGTIOU-84678-8a9d2sfa23casd + * + * + * + * "Jasig Style" Attributes (longer version): + * + * + * + * jsmith + * + * + * surname + * Smith + * + * + * givenName + * John + * + * + * memberOf + * ['CN=Staff,OU=Groups,DC=example,DC=edu', 'CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu'] + * + * + * PGTIOU-84678-8a9d2sfa23casd + * + * + * + * "RubyCAS Style" attributes + * + * + * + * jsmith + * + * RubyCAS + * Smith + * John + * CN=Staff,OU=Groups,DC=example,DC=edu + * CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu + * + * PGTIOU-84678-8a9d2sfa23casd + * + * + * + * "Name-Value" attributes. + * + * Attribute format from these mailing list thread: + * http://jasig.275507.n4.nabble.com/CAS-attributes-and-how-they-appear-in-the-CAS-response-td264272.html + * Note: This is a less widely used format, but in use by at least two institutions. + * + * + * + * jsmith + * + * + * + * + * + * + * + * PGTIOU-84678-8a9d2sfa23casd + * + * + * + * result: + * + * Array ( + * [surname] => Smith + * [givenName] => John + * [memberOf] => Array ( + * [0] => CN=Staff, OU=Groups, DC=example, DC=edu + * [1] => CN=Spanish Department, OU=Departments, OU=Groups, DC=example, DC=edu + * ) + * ) + */ + private function _xml_to_array($root, $namespace = "cas") + { + $result = array(); + if ($root->hasAttributes()) { + $attrs = $root->attributes; + $pair = array(); + foreach ($attrs as $attr) { + if ($attr->name === "name") { + $pair['name'] = $attr->value; + } elseif ($attr->name === "value") { + $pair['value'] = $attr->value; + } else { + $result[$attr->name] = $attr->value; + } + if (array_key_exists('name', $pair) && array_key_exists('value', $pair)) { + $result[$pair['name']] = $pair['value']; + } + } + } + if ($root->hasChildNodes()) { + $children = $root->childNodes; + if ($children->length == 1) { + $child = $children->item(0); + if ($child->nodeType == XML_TEXT_NODE) { + $result['_value'] = $child->nodeValue; + return (count($result) == 1) ? $result['_value'] : $result; + } + } + $groups = array(); + foreach ($children as $child) { + $child_nodeName = str_ireplace($namespace . ":", "", $child->nodeName); + if (in_array($child_nodeName, array("user", "proxies", "proxyGrantingTicket"))) { + continue; + } + if (!isset($result[$child_nodeName])) { + $res = $this->_xml_to_array($child, $namespace); + if (!empty($res)) { + $result[$child_nodeName] = $this->_xml_to_array($child, $namespace); + } + } else { + if (!isset($groups[$child_nodeName])) { + $result[$child_nodeName] = array($result[$child_nodeName]); + $groups[$child_nodeName] = 1; + } + $result[$child_nodeName][] = $this->_xml_to_array($child, $namespace); + } + } + } + return $result; + } + + /** + * This method parses a "JSON-like array" of strings + * into an array of strings + * + * @param string $json_value the json-like string: + * e.g.: + * ['CN=Staff,OU=Groups,DC=example,DC=edu', 'CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu'] + * + * @return array of strings Description + * e.g.: + * Array ( + * [0] => CN=Staff,OU=Groups,DC=example,DC=edu + * [1] => CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu + * ) + */ + private function _parse_json_like_array_value($json_value) + { + $parts = explode(",", trim($json_value, "[]")); + $out = array(); + $quote = ''; + foreach ($parts as $part) { + $part = trim($part); + if ($quote === '') { + $value = ""; + if ($this->_startsWith($part, '\'')) { + $quote = '\''; + } elseif ($this->_startsWith($part, '"')) { + $quote = '"'; + } else { + $out[] = $part; + } + $part = ltrim($part, $quote); + } + if ($quote !== '') { + $value .= $part; + if ($this->_endsWith($part, $quote)) { + $out[] = rtrim($value, $quote); + $quote = ''; + } else { + $value .= ", "; + }; + } + } + return $out; + } + + /** + * This method recursively removes unneccessary hirarchy levels in array-trees. + * into an array of strings + * + * @param array $arr the array to flatten + * e.g.: + * Array ( + * [attributes] => Array ( + * [attribute] => Array ( + * [0] => Array ( + * [name] => surname + * [value] => Smith + * ) + * [1] => Array ( + * [name] => givenName + * [value] => John + * ) + * [2] => Array ( + * [name] => memberOf + * [value] => ['CN=Staff,OU=Groups,DC=example,DC=edu', 'CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu'] + * ) + * ) + * ) + * ) + * + * @return array the flattened array + * e.g.: + * Array ( + * [attribute] => Array ( + * [surname] => Smith + * [givenName] => John + * [memberOf] => Array ( + * [0] => CN=Staff, OU=Groups, DC=example, DC=edu + * [1] => CN=Spanish Department, OU=Departments, OU=Groups, DC=example, DC=edu + * ) + * ) + * ) + */ + private function _flatten_array($arr) + { + if (!is_array($arr)) { + if ($this->_startsWith($arr, '[') && $this->_endsWith($arr, ']')) { + return $this->_parse_json_like_array_value($arr); + } else { + return $arr; + } + } + $out = array(); + foreach ($arr as $key => $val) { + if (!is_array($val)) { + $out[$key] = $val; + } else { + switch (count($val)) { + case 1 : { + $key = key($val); + if (array_key_exists($key, $out)) { + $value = $out[$key]; + if (!is_array($value)) { + $out[$key] = array(); + $out[$key][] = $value; + } + $out[$key][] = $this->_flatten_array($val[$key]); + } else { + $out[$key] = $this->_flatten_array($val[$key]); + }; + break; + }; + case 2 : { + if (array_key_exists("name", $val) && array_key_exists("value", $val)) { + $key = $val['name']; + if (array_key_exists($key, $out)) { + $value = $out[$key]; + if (!is_array($value)) { + $out[$key] = array(); + $out[$key][] = $value; + } + $out[$key][] = $this->_flatten_array($val['value']); + } else { + $out[$key] = $this->_flatten_array($val['value']); + }; + } else { + $out[$key] = $this->_flatten_array($val); + } + break; + }; + default: { + $out[$key] = $this->_flatten_array($val); + } + } + } + } + return $out; + } + + /** + * This method will parse the DOM and pull out the attributes from the XML + * payload and put them into an array, then put the array into the session. + * + * @param DOMNodeList $success_elements payload of the response + * + * @return bool true when successfull, halt otherwise by calling + * CAS_Client::_authError(). + */ + private function _readExtraAttributesCas20($success_elements) + { + phpCAS::traceBegin(); + + $extra_attributes = array(); + if ($this->_casAttributeParserCallbackFunction !== null + && is_callable($this->_casAttributeParserCallbackFunction) + ) { + array_unshift($this->_casAttributeParserCallbackArgs, $success_elements->item(0)); + phpCAS :: trace("Calling attritubeParser callback"); + $extra_attributes = call_user_func_array( + $this->_casAttributeParserCallbackFunction, + $this->_casAttributeParserCallbackArgs + ); + } else { + phpCAS :: trace("Parse extra attributes: "); + $attributes = $this->_xml_to_array($success_elements->item(0)); + phpCAS :: trace(print_r($attributes,true). "\nFLATTEN Array: "); + $extra_attributes = $this->_flatten_array($attributes); + phpCAS :: trace(print_r($extra_attributes, true)."\nFILTER : "); + if (array_key_exists("attribute", $extra_attributes)) { + $extra_attributes = $extra_attributes["attribute"]; + } elseif (array_key_exists("attributes", $extra_attributes)) { + $extra_attributes = $extra_attributes["attributes"]; + }; + phpCAS :: trace(print_r($extra_attributes, true)."return"); + } + $this->setAttributes($extra_attributes); + phpCAS::traceEnd(); + return true; + } + + /** + * Add an attribute value to an array of attributes. + * + * @param array &$attributeArray reference to array + * @param string $name name of attribute + * @param string $value value of attribute + * + * @return void + */ + private function _addAttributeToArray(array &$attributeArray, $name, $value) + { + // If multiple attributes exist, add as an array value + if (isset($attributeArray[$name])) { + // Initialize the array with the existing value + if (!is_array($attributeArray[$name])) { + $existingValue = $attributeArray[$name]; + $attributeArray[$name] = array($existingValue); + } + + $attributeArray[$name][] = trim($value); + } else { + $attributeArray[$name] = trim($value); + } + } + + /** @} */ + + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + // XX XX + // XX MISC XX + // XX XX + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + + /** + * @addtogroup internalMisc + * @{ + */ + + // ######################################################################## + // URL + // ######################################################################## + /** + * the URL of the current request (without any ticket CGI parameter). Written + * and read by CAS_Client::getURL(). + * + * @hideinitializer + */ + private $_url = ''; + + + /** + * This method sets the URL of the current request + * + * @param string $url url to set for service + * + * @return void + */ + public function setURL($url) + { + // Argument Validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + + $this->_url = $url; + } + + /** + * This method returns the URL of the current request (without any ticket + * CGI parameter). + * + * @return string The URL + */ + public function getURL() + { + phpCAS::traceBegin(); + // the URL is built when needed only + if ( empty($this->_url) ) { + // remove the ticket if present in the URL + $final_uri = $this->getServiceBaseUrl()->get(); + $request_uri = explode('?', $_SERVER['REQUEST_URI'], 2); + $final_uri .= $request_uri[0]; + + if (isset($request_uri[1]) && $request_uri[1]) { + $query_string= $this->_removeParameterFromQueryString('ticket', $request_uri[1]); + + // If the query string still has anything left, + // append it to the final URI + if ($query_string !== '') { + $final_uri .= "?$query_string"; + } + } + + phpCAS::trace("Final URI: $final_uri"); + $this->setURL($final_uri); + } + phpCAS::traceEnd($this->_url); + return $this->_url; + } + + /** + * This method sets the base URL of the CAS server. + * + * @param string $url the base URL + * + * @return string base url + */ + public function setBaseURL($url) + { + // Argument Validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + + return $this->_server['base_url'] = $url; + } + + /** + * The ServiceBaseUrl object that provides base URL during service URL + * discovery process. + * + * @var CAS_ServiceBaseUrl_Interface + * + * @hideinitializer + */ + private $_serviceBaseUrl = null; + + /** + * Answer the CAS_ServiceBaseUrl_Interface object for this client. + * + * @return CAS_ServiceBaseUrl_Interface + */ + public function getServiceBaseUrl() + { + if (empty($this->_serviceBaseUrl)) { + phpCAS::error("ServiceBaseUrl object is not initialized"); + } + return $this->_serviceBaseUrl; + } + + /** + * This method sets the service base URL used during service URL discovery process. + * + * This is required since phpCAS 1.6.0 to protect the integrity of the authentication. + * + * @since phpCAS 1.6.0 + * + * @param $name can be any of the following: + * - A base URL string. The service URL discovery will always use this (protocol, + * hostname and optional port number) without using any external host names. + * - An array of base URL strings. The service URL discovery will check against + * this list before using the auto discovered base URL. If there is no match, + * the first base URL in the array will be used as the default. This option is + * helpful if your PHP website is accessible through multiple domains without a + * canonical name, or through both HTTP and HTTPS. + * - A class that implements CAS_ServiceBaseUrl_Interface. If you need to customize + * the base URL discovery behavior, you can pass in a class that implements the + * interface. + * + * @return void + */ + private function _setServiceBaseUrl($name) + { + if (is_array($name)) { + $this->_serviceBaseUrl = new CAS_ServiceBaseUrl_AllowedListDiscovery($name); + } else if (is_string($name)) { + $this->_serviceBaseUrl = new CAS_ServiceBaseUrl_Static($name); + } else if ($name instanceof CAS_ServiceBaseUrl_Interface) { + $this->_serviceBaseUrl = $name; + } else { + throw new CAS_TypeMismatchException($name, '$name', 'array, string, or CAS_ServiceBaseUrl_Interface object'); + } + } + + /** + * Removes a parameter from a query string + * + * @param string $parameterName name of parameter + * @param string $queryString query string + * + * @return string new query string + * + * @link http://stackoverflow.com/questions/1842681/regular-expression-to-remove-one-parameter-from-query-string + */ + private function _removeParameterFromQueryString($parameterName, $queryString) + { + $parameterName = preg_quote($parameterName); + return preg_replace( + "/&$parameterName(=[^&]*)?|^$parameterName(=[^&]*)?&?/", + '', $queryString + ); + } + + /** + * This method is used to append query parameters to an url. Since the url + * might already contain parameter it has to be detected and to build a proper + * URL + * + * @param string $url base url to add the query params to + * @param string $query params in query form with & separated + * + * @return string url with query params + */ + private function _buildQueryUrl($url, $query) + { + $url .= (strstr($url, '?') === false) ? '?' : '&'; + $url .= $query; + return $url; + } + + /** + * This method tests if a string starts with a given character. + * + * @param string $text text to test + * @param string $char character to test for + * + * @return bool true if the $text starts with $char + */ + private function _startsWith($text, $char) + { + return (strpos($text, $char) === 0); + } + + /** + * This method tests if a string ends with a given character + * + * @param string $text text to test + * @param string $char character to test for + * + * @return bool true if the $text ends with $char + */ + private function _endsWith($text, $char) + { + return (strpos(strrev($text), $char) === 0); + } + + /** + * Answer a valid session-id given a CAS ticket. + * + * The output must be deterministic to allow single-log-out when presented with + * the ticket to log-out. + * + * + * @param string $ticket name of the ticket + * + * @return string + */ + private function _sessionIdForTicket($ticket) + { + // Hash the ticket to ensure that the value meets the PHP 7.1 requirement + // that session-ids have a length between 22 and 256 characters. + return hash('sha256', $this->_sessionIdSalt . $ticket); + } + + /** + * Set a salt/seed for the session-id hash to make it harder to guess. + * + * @var string $_sessionIdSalt + */ + private $_sessionIdSalt = ''; + + /** + * Set a salt/seed for the session-id hash to make it harder to guess. + * + * @param string $salt + * + * @return void + */ + public function setSessionIdSalt($salt) { + $this->_sessionIdSalt = (string)$salt; + } + + // ######################################################################## + // AUTHENTICATION ERROR HANDLING + // ######################################################################## + /** + * This method is used to print the HTML output when the user was not + * authenticated. + * + * @param string $failure the failure that occured + * @param string $cas_url the URL the CAS server was asked for + * @param bool $no_response the response from the CAS server (other + * parameters are ignored if true) + * @param bool $bad_response bad response from the CAS server ($err_code + * and $err_msg ignored if true) + * @param string $cas_response the response of the CAS server + * @param int $err_code the error code given by the CAS server + * @param string $err_msg the error message given by the CAS server + * + * @return void + */ + private function _authError( + $failure, + $cas_url, + $no_response=false, + $bad_response=false, + $cas_response='', + $err_code=-1, + $err_msg='' + ) { + phpCAS::traceBegin(); + $lang = $this->getLangObj(); + $this->printHTMLHeader($lang->getAuthenticationFailed()); + $this->printf( + $lang->getYouWereNotAuthenticated(), htmlentities($this->getURL()), + isset($_SERVER['SERVER_ADMIN']) ? $_SERVER['SERVER_ADMIN']:'' + ); + phpCAS::trace('CAS URL: '.$cas_url); + phpCAS::trace('Authentication failure: '.$failure); + if ( $no_response ) { + phpCAS::trace('Reason: no response from the CAS server'); + } else { + if ( $bad_response ) { + phpCAS::trace('Reason: bad response from the CAS server'); + } else { + switch ($this->getServerVersion()) { + case CAS_VERSION_1_0: + phpCAS::trace('Reason: CAS error'); + break; + case CAS_VERSION_2_0: + case CAS_VERSION_3_0: + if ( $err_code === -1 ) { + phpCAS::trace('Reason: no CAS error'); + } else { + phpCAS::trace( + 'Reason: ['.$err_code.'] CAS error: '.$err_msg + ); + } + break; + } + } + phpCAS::trace('CAS response: '.$cas_response); + } + $this->printHTMLFooter(); + phpCAS::traceExit(); + throw new CAS_GracefullTerminationException(); + } + + // ######################################################################## + // PGTIOU/PGTID and logoutRequest rebroadcasting + // ######################################################################## + + /** + * Boolean of whether to rebroadcast pgtIou/pgtId and logoutRequest, and + * array of the nodes. + */ + private $_rebroadcast = false; + private $_rebroadcast_nodes = array(); + + /** + * Constants used for determining rebroadcast node type. + */ + const HOSTNAME = 0; + const IP = 1; + + /** + * Determine the node type from the URL. + * + * @param String $nodeURL The node URL. + * + * @return int hostname + * + */ + private function _getNodeType($nodeURL) + { + phpCAS::traceBegin(); + if (preg_match("/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/", $nodeURL)) { + phpCAS::traceEnd(self::IP); + return self::IP; + } else { + phpCAS::traceEnd(self::HOSTNAME); + return self::HOSTNAME; + } + } + + /** + * Store the rebroadcast node for pgtIou/pgtId and logout requests. + * + * @param string $rebroadcastNodeUrl The rebroadcast node URL. + * + * @return void + */ + public function addRebroadcastNode($rebroadcastNodeUrl) + { + // Argument validation + if ( !(bool)preg_match("/^(http|https):\/\/([A-Z0-9][A-Z0-9_-]*(?:\.[A-Z0-9][A-Z0-9_-]*)+):?(\d+)?\/?/i", $rebroadcastNodeUrl)) + throw new CAS_TypeMismatchException($rebroadcastNodeUrl, '$rebroadcastNodeUrl', 'url'); + + // Store the rebroadcast node and set flag + $this->_rebroadcast = true; + $this->_rebroadcast_nodes[] = $rebroadcastNodeUrl; + } + + /** + * An array to store extra rebroadcast curl options. + */ + private $_rebroadcast_headers = array(); + + /** + * This method is used to add header parameters when rebroadcasting + * pgtIou/pgtId or logoutRequest. + * + * @param string $header Header to send when rebroadcasting. + * + * @return void + */ + public function addRebroadcastHeader($header) + { + if (gettype($header) != 'string') + throw new CAS_TypeMismatchException($header, '$header', 'string'); + + $this->_rebroadcast_headers[] = $header; + } + + /** + * Constants used for determining rebroadcast type (logout or pgtIou/pgtId). + */ + const LOGOUT = 0; + const PGTIOU = 1; + + /** + * This method rebroadcasts logout/pgtIou requests. Can be LOGOUT,PGTIOU + * + * @param int $type type of rebroadcasting. + * + * @return void + */ + private function _rebroadcast($type) + { + phpCAS::traceBegin(); + + $rebroadcast_curl_options = array( + CURLOPT_FAILONERROR => 1, + CURLOPT_FOLLOWLOCATION => 1, + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_CONNECTTIMEOUT => 1, + CURLOPT_TIMEOUT => 4); + + // Try to determine the IP address of the server + if (!empty($_SERVER['SERVER_ADDR'])) { + $ip = $_SERVER['SERVER_ADDR']; + } else if (!empty($_SERVER['LOCAL_ADDR'])) { + // IIS 7 + $ip = $_SERVER['LOCAL_ADDR']; + } + // Try to determine the DNS name of the server + if (!empty($ip)) { + $dns = gethostbyaddr($ip); + } + $multiClassName = 'CAS_Request_CurlMultiRequest'; + $multiRequest = new $multiClassName(); + + for ($i = 0; $i < sizeof($this->_rebroadcast_nodes); $i++) { + if ((($this->_getNodeType($this->_rebroadcast_nodes[$i]) == self::HOSTNAME) && !empty($dns) && (stripos($this->_rebroadcast_nodes[$i], $dns) === false)) + || (($this->_getNodeType($this->_rebroadcast_nodes[$i]) == self::IP) && !empty($ip) && (stripos($this->_rebroadcast_nodes[$i], $ip) === false)) + ) { + phpCAS::trace( + 'Rebroadcast target URL: '.$this->_rebroadcast_nodes[$i] + .$_SERVER['REQUEST_URI'] + ); + $className = $this->_requestImplementation; + $request = new $className(); + + $url = $this->_rebroadcast_nodes[$i].$_SERVER['REQUEST_URI']; + $request->setUrl($url); + + if (count($this->_rebroadcast_headers)) { + $request->addHeaders($this->_rebroadcast_headers); + } + + $request->makePost(); + if ($type == self::LOGOUT) { + // Logout request + $request->setPostBody( + 'rebroadcast=false&logoutRequest='.$_POST['logoutRequest'] + ); + } else if ($type == self::PGTIOU) { + // pgtIou/pgtId rebroadcast + $request->setPostBody('rebroadcast=false'); + } + + $request->setCurlOptions($rebroadcast_curl_options); + + $multiRequest->addRequest($request); + } else { + phpCAS::trace( + 'Rebroadcast not sent to self: ' + .$this->_rebroadcast_nodes[$i].' == '.(!empty($ip)?$ip:'') + .'/'.(!empty($dns)?$dns:'') + ); + } + } + // We need at least 1 request + if ($multiRequest->getNumRequests() > 0) { + $multiRequest->send(); + } + phpCAS::traceEnd(); + } + + /** @} */ +} diff --git a/vendor/apereo/phpcas/source/CAS/CookieJar.php b/vendor/apereo/phpcas/source/CAS/CookieJar.php new file mode 100644 index 00000000..b2439373 --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/CookieJar.php @@ -0,0 +1,385 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This class provides access to service cookies and handles parsing of response + * headers to pull out cookie values. + * + * @class CAS_CookieJar + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_CookieJar +{ + + private $_cookies; + + /** + * Create a new cookie jar by passing it a reference to an array in which it + * should store cookies. + * + * @param array &$storageArray Array to store cookies + * + * @return void + */ + public function __construct (array &$storageArray) + { + $this->_cookies =& $storageArray; + } + + /** + * Store cookies for a web service request. + * Cookie storage is based on RFC 2965: http://www.ietf.org/rfc/rfc2965.txt + * + * @param string $request_url The URL that generated the response headers. + * @param array $response_headers An array of the HTTP response header strings. + * + * @return void + * + * @access private + */ + public function storeCookies ($request_url, $response_headers) + { + $urlParts = parse_url($request_url); + $defaultDomain = $urlParts['host']; + + $cookies = $this->parseCookieHeaders($response_headers, $defaultDomain); + + foreach ($cookies as $cookie) { + // Enforce the same-origin policy by verifying that the cookie + // would match the url that is setting it + if (!$this->cookieMatchesTarget($cookie, $urlParts)) { + continue; + } + + // store the cookie + $this->storeCookie($cookie); + + phpCAS::trace($cookie['name'].' -> '.$cookie['value']); + } + } + + /** + * Retrieve cookies applicable for a web service request. + * Cookie applicability is based on RFC 2965: http://www.ietf.org/rfc/rfc2965.txt + * + * @param string $request_url The url that the cookies will be for. + * + * @return array An array containing cookies. E.g. array('name' => 'val'); + * + * @access private + */ + public function getCookies ($request_url) + { + if (!count($this->_cookies)) { + return array(); + } + + // If our request URL can't be parsed, no cookies apply. + $target = parse_url($request_url); + if ($target === false) { + return array(); + } + + $this->expireCookies(); + + $matching_cookies = array(); + foreach ($this->_cookies as $key => $cookie) { + if ($this->cookieMatchesTarget($cookie, $target)) { + $matching_cookies[$cookie['name']] = $cookie['value']; + } + } + return $matching_cookies; + } + + + /** + * Parse Cookies without PECL + * From the comments in http://php.net/manual/en/function.http-parse-cookie.php + * + * @param array $header array of header lines. + * @param string $defaultDomain The domain to use if none is specified in + * the cookie. + * + * @return array of cookies + */ + protected function parseCookieHeaders( $header, $defaultDomain ) + { + phpCAS::traceBegin(); + $cookies = array(); + foreach ( $header as $line ) { + if ( preg_match('/^Set-Cookie2?: /i', $line)) { + $cookies[] = $this->parseCookieHeader($line, $defaultDomain); + } + } + + phpCAS::traceEnd($cookies); + return $cookies; + } + + /** + * Parse a single cookie header line. + * + * Based on RFC2965 http://www.ietf.org/rfc/rfc2965.txt + * + * @param string $line The header line. + * @param string $defaultDomain The domain to use if none is specified in + * the cookie. + * + * @return array + */ + protected function parseCookieHeader ($line, $defaultDomain) + { + if (!$defaultDomain) { + throw new CAS_InvalidArgumentException( + '$defaultDomain was not provided.' + ); + } + + // Set our default values + $cookie = array( + 'domain' => $defaultDomain, + 'path' => '/', + 'secure' => false, + ); + + $line = preg_replace('/^Set-Cookie2?: /i', '', trim($line)); + + // trim any trailing semicolons. + $line = trim($line, ';'); + + phpCAS::trace("Cookie Line: $line"); + + // This implementation makes the assumption that semicolons will not + // be present in quoted attribute values. While attribute values that + // contain semicolons are allowed by RFC2965, they are hopefully rare + // enough to ignore for our purposes. Most browsers make the same + // assumption. + $attributeStrings = explode(';', $line); + + foreach ( $attributeStrings as $attributeString ) { + // split on the first equals sign and use the rest as value + $attributeParts = explode('=', $attributeString, 2); + + $attributeName = trim($attributeParts[0]); + $attributeNameLC = strtolower($attributeName); + + if (isset($attributeParts[1])) { + $attributeValue = trim($attributeParts[1]); + // Values may be quoted strings. + if (strpos($attributeValue, '"') === 0) { + $attributeValue = trim($attributeValue, '"'); + // unescape any escaped quotes: + $attributeValue = str_replace('\"', '"', $attributeValue); + } + } else { + $attributeValue = null; + } + + switch ($attributeNameLC) { + case 'expires': + $cookie['expires'] = strtotime($attributeValue); + break; + case 'max-age': + $cookie['max-age'] = (int)$attributeValue; + // Set an expiry time based on the max-age + if ($cookie['max-age']) { + $cookie['expires'] = time() + $cookie['max-age']; + } else { + // If max-age is zero, then the cookie should be removed + // imediately so set an expiry before now. + $cookie['expires'] = time() - 1; + } + break; + case 'secure': + $cookie['secure'] = true; + break; + case 'domain': + case 'path': + case 'port': + case 'version': + case 'comment': + case 'commenturl': + case 'discard': + case 'httponly': + case 'samesite': + $cookie[$attributeNameLC] = $attributeValue; + break; + default: + $cookie['name'] = $attributeName; + $cookie['value'] = $attributeValue; + } + } + + return $cookie; + } + + /** + * Add, update, or remove a cookie. + * + * @param array $cookie A cookie array as created by parseCookieHeaders() + * + * @return void + * + * @access protected + */ + protected function storeCookie ($cookie) + { + // Discard any old versions of this cookie. + $this->discardCookie($cookie); + $this->_cookies[] = $cookie; + + } + + /** + * Discard an existing cookie + * + * @param array $cookie An cookie + * + * @return void + * + * @access protected + */ + protected function discardCookie ($cookie) + { + if (!isset($cookie['domain']) + || !isset($cookie['path']) + || !isset($cookie['path']) + ) { + throw new CAS_InvalidArgumentException('Invalid Cookie array passed.'); + } + + foreach ($this->_cookies as $key => $old_cookie) { + if ( $cookie['domain'] == $old_cookie['domain'] + && $cookie['path'] == $old_cookie['path'] + && $cookie['name'] == $old_cookie['name'] + ) { + unset($this->_cookies[$key]); + } + } + } + + /** + * Go through our stored cookies and remove any that are expired. + * + * @return void + * + * @access protected + */ + protected function expireCookies () + { + foreach ($this->_cookies as $key => $cookie) { + if (isset($cookie['expires']) && $cookie['expires'] < time()) { + unset($this->_cookies[$key]); + } + } + } + + /** + * Answer true if cookie is applicable to a target. + * + * @param array $cookie An array of cookie attributes. + * @param array|false $target An array of URL attributes as generated by parse_url(). + * + * @return bool + * + * @access private + */ + protected function cookieMatchesTarget ($cookie, $target) + { + if (!is_array($target)) { + throw new CAS_InvalidArgumentException( + '$target must be an array of URL attributes as generated by parse_url().' + ); + } + if (!isset($target['host'])) { + throw new CAS_InvalidArgumentException( + '$target must be an array of URL attributes as generated by parse_url().' + ); + } + + // Verify that the scheme matches + if ($cookie['secure'] && $target['scheme'] != 'https') { + return false; + } + + // Verify that the host matches + // Match domain and mulit-host cookies + if (strpos($cookie['domain'], '.') === 0) { + // .host.domain.edu cookies are valid for host.domain.edu + if (substr($cookie['domain'], 1) == $target['host']) { + // continue with other checks + } else { + // non-exact host-name matches. + // check that the target host a.b.c.edu is within .b.c.edu + $pos = strripos($target['host'], $cookie['domain']); + if (!$pos) { + return false; + } + // verify that the cookie domain is the last part of the host. + if ($pos + strlen($cookie['domain']) != strlen($target['host'])) { + return false; + } + // verify that the host name does not contain interior dots as per + // RFC 2965 section 3.3.2 Rejecting Cookies + // http://www.ietf.org/rfc/rfc2965.txt + $hostname = substr($target['host'], 0, $pos); + if (strpos($hostname, '.') !== false) { + return false; + } + } + } else { + // If the cookie host doesn't begin with '.', + // the host must case-insensitive match exactly + if (strcasecmp($target['host'], $cookie['domain']) !== 0) { + return false; + } + } + + // Verify that the port matches + if (isset($cookie['ports']) + && !in_array($target['port'], $cookie['ports']) + ) { + return false; + } + + // Verify that the path matches + if (strpos($target['path'], $cookie['path']) !== 0) { + return false; + } + + return true; + } + +} + +?> diff --git a/vendor/apereo/phpcas/source/CAS/Exception.php b/vendor/apereo/phpcas/source/CAS/Exception.php new file mode 100644 index 00000000..2ff7cd65 --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/Exception.php @@ -0,0 +1,59 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * A root exception interface for all exceptions in phpCAS. + * + * All exceptions thrown in phpCAS should implement this interface to allow them + * to be caught as a category by clients. Each phpCAS exception should extend + * an appropriate SPL exception class that best fits its type. + * + * For example, an InvalidArgumentException in phpCAS should be defined as + * + * class CAS_InvalidArgumentException + * extends InvalidArgumentException + * implements CAS_Exception + * { } + * + * This definition allows the CAS_InvalidArgumentException to be caught as either + * an InvalidArgumentException or as a CAS_Exception. + * + * @class CAS_Exception + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * + */ +interface CAS_Exception +{ + +} +?> diff --git a/vendor/apereo/phpcas/source/CAS/GracefullTerminationException.php b/vendor/apereo/phpcas/source/CAS/GracefullTerminationException.php new file mode 100644 index 00000000..29aa638c --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/GracefullTerminationException.php @@ -0,0 +1,86 @@ + + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * An exception for terminatinating execution or to throw for unit testing + * + * @class CAS_GracefullTerminationException.php + * @category Authentication + * @package PhpCAS + * @author Joachim Fritschi + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +class CAS_GracefullTerminationException +extends RuntimeException +implements CAS_Exception +{ + + /** + * Test if exceptions should be thrown or if we should just exit. + * In production usage we want to just exit cleanly when prompting the user + * for a redirect without filling the error logs with uncaught exceptions. + * In unit testing scenarios we cannot exit or we won't be able to continue + * with our tests. + * + * @param string $message Message Text + * @param int $code Error code + * + * @return self + */ + public function __construct ($message = 'Terminate Gracefully', $code = 0) + { + // Exit cleanly to avoid filling up the logs with uncaught exceptions. + if (self::$_exitWhenThrown) { + exit; + } else { + // Throw exceptions to allow unit testing to continue; + parent::__construct($message, $code); + } + } + + private static $_exitWhenThrown = true; + /** + * Force phpcas to thow Exceptions instead of calling exit() + * Needed for unit testing. Generally shouldn't be used in production due to + * an increase in Apache error logging if CAS_GracefulTerminiationExceptions + * are not caught and handled. + * + * @return void + */ + public static function throwInsteadOfExiting() + { + self::$_exitWhenThrown = false; + } + +} +?> diff --git a/vendor/apereo/phpcas/source/CAS/InvalidArgumentException.php b/vendor/apereo/phpcas/source/CAS/InvalidArgumentException.php new file mode 100644 index 00000000..99be2ac3 --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/InvalidArgumentException.php @@ -0,0 +1,46 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * Exception that denotes invalid arguments were passed. + * + * @class CAS_InvalidArgumentException + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_InvalidArgumentException +extends InvalidArgumentException +implements CAS_Exception +{ + +} +?> diff --git a/vendor/apereo/phpcas/source/CAS/Languages/Catalan.php b/vendor/apereo/phpcas/source/CAS/Languages/Catalan.php new file mode 100644 index 00000000..1ead905f --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/Languages/Catalan.php @@ -0,0 +1,114 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * Catalan language class + * + * @class CAS_Languages_Catalan + * @category Authentication + * @package PhpCAS + * @author Iván-Benjamín García Torà + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * + * @sa @link internalLang Internationalization @endlink + * @ingroup internalLang + */ +class CAS_Languages_Catalan implements CAS_Languages_LanguageInterface +{ + /** + * Get the using server string + * + * @return string using server + */ + public function getUsingServer() + { + return 'usant servidor'; + } + + /** + * Get authentication wanted string + * + * @return string authentication wanted + */ + public function getAuthenticationWanted() + { + return 'Autentificació CAS necessària!'; + } + + /** + * Get logout string + * + * @return string logout + */ + public function getLogout() + { + return 'Sortida de CAS necessària!'; + } + + /** + * Get the should have been redirected string + * + * @return string should habe been redirected + */ + public function getShouldHaveBeenRedirected() + { + return 'Ja hauria d\ haver estat redireccionat al servidor CAS. Feu click aquí per a continuar.'; + } + + /** + * Get authentication failed string + * + * @return string authentication failed + */ + public function getAuthenticationFailed() + { + return 'Autentificació CAS fallida!'; + } + + /** + * Get the your were not authenticated string + * + * @return string not authenticated + */ + public function getYouWereNotAuthenticated() + { + return '

No estàs autentificat.

Pots tornar a intentar-ho fent click aquí.

Si el problema persisteix hauría de contactar amb l\'administrador d\'aquest llocc.

'; + } + + /** + * Get the service unavailable string + * + * @return string service unavailable + */ + public function getServiceUnavailable() + { + return 'El servei `%s\' no està disponible (%s).'; + } +} diff --git a/vendor/apereo/phpcas/source/CAS/Languages/ChineseSimplified.php b/vendor/apereo/phpcas/source/CAS/Languages/ChineseSimplified.php new file mode 100644 index 00000000..5e33cb65 --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/Languages/ChineseSimplified.php @@ -0,0 +1,114 @@ +, Phy25 + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * Chinese Simplified language class + * + * @class CAS_Languages_ChineseSimplified + * @category Authentication + * @package PhpCAS + * @author Pascal Aubry , Phy25 + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * + * @sa @link internalLang Internationalization @endlink + * @ingroup internalLang + */ +class CAS_Languages_ChineseSimplified implements CAS_Languages_LanguageInterface +{ + /** + * Get the using server string + * + * @return string using server + */ + public function getUsingServer() + { + return '连接的服务器'; + } + + /** + * Get authentication wanted string + * + * @return string authentication wanted + */ + public function getAuthenticationWanted() + { + return '请进行 CAS 认证!'; + } + + /** + * Get logout string + * + * @return string logout + */ + public function getLogout() + { + return '请进行 CAS 登出!'; + } + + /** + * Get the should have been redirected string + * + * @return string should habe been redirected + */ + public function getShouldHaveBeenRedirected() + { + return '你正被重定向到 CAS 服务器。点击这里继续。'; + } + + /** + * Get authentication failed string + * + * @return string authentication failed + */ + public function getAuthenticationFailed() + { + return 'CAS 认证失败!'; + } + + /** + * Get the your were not authenticated string + * + * @return string not authenticated + */ + public function getYouWereNotAuthenticated() + { + return '

你没有成功登录。

你可以点击这里重新登录

如果问题依然存在,请联系本站管理员

'; + } + + /** + * Get the service unavailable string + * + * @return string service unavailable + */ + public function getServiceUnavailable() + { + return '服务器 %s 不可用(%s)。'; + } +} diff --git a/vendor/apereo/phpcas/source/CAS/Languages/English.php b/vendor/apereo/phpcas/source/CAS/Languages/English.php new file mode 100644 index 00000000..cb13bde9 --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/Languages/English.php @@ -0,0 +1,114 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * English language class + * + * @class CAS_Languages_English + * @category Authentication + * @package PhpCAS + * @author Pascal Aubry + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * + * @sa @link internalLang Internationalization @endlink + * @ingroup internalLang + */ +class CAS_Languages_English implements CAS_Languages_LanguageInterface +{ + /** + * Get the using server string + * + * @return string using server + */ + public function getUsingServer() + { + return 'using server'; + } + + /** + * Get authentication wanted string + * + * @return string authentication wanted + */ + public function getAuthenticationWanted() + { + return 'CAS Authentication wanted!'; + } + + /** + * Get logout string + * + * @return string logout + */ + public function getLogout() + { + return 'CAS logout wanted!'; + } + + /** + * Get the should have been redirected string + * + * @return string should habe been redirected + */ + public function getShouldHaveBeenRedirected() + { + return 'You should already have been redirected to the CAS server. Click here to continue.'; + } + + /** + * Get authentication failed string + * + * @return string authentication failed + */ + public function getAuthenticationFailed() + { + return 'CAS Authentication failed!'; + } + + /** + * Get the your were not authenticated string + * + * @return string not authenticated + */ + public function getYouWereNotAuthenticated() + { + return '

You were not authenticated.

You may submit your request again by clicking here.

If the problem persists, you may contact the administrator of this site.

'; + } + + /** + * Get the service unavailable string + * + * @return string service unavailable + */ + public function getServiceUnavailable() + { + return 'The service `%s\' is not available (%s).'; + } +} diff --git a/vendor/apereo/phpcas/source/CAS/Languages/French.php b/vendor/apereo/phpcas/source/CAS/Languages/French.php new file mode 100644 index 00000000..14f65aba --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/Languages/French.php @@ -0,0 +1,116 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * French language class + * + * @class CAS_Languages_French + * @category Authentication + * @package PhpCAS + * @author Pascal Aubry + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * + * @sa @link internalLang Internationalization @endlink + * @ingroup internalLang + */ +class CAS_Languages_French implements CAS_Languages_LanguageInterface +{ + /** + * Get the using server string + * + * @return string using server + */ + public function getUsingServer() + { + return 'utilisant le serveur'; + } + + /** + * Get authentication wanted string + * + * @return string authentication wanted + */ + public function getAuthenticationWanted() + { + return 'Authentication CAS nécessaire !'; + } + + /** + * Get logout string + * + * @return string logout + */ + public function getLogout() + { + return 'Déconnexion demandée !'; + } + + /** + * Get the should have been redirected string + * + * @return string should habe been redirected + */ + public function getShouldHaveBeenRedirected() + { + return 'Vous auriez du etre redirigé(e) vers le serveur CAS. Cliquez ici pour continuer.'; + } + + /** + * Get authentication failed string + * + * @return string authentication failed + */ + public function getAuthenticationFailed() + { + return 'Authentification CAS infructueuse !'; + } + + /** + * Get the your were not authenticated string + * + * @return string not authenticated + */ + public function getYouWereNotAuthenticated() + { + return '

Vous n\'avez pas été authentifié(e).

Vous pouvez soumettre votre requete à nouveau en cliquant ici.

Si le problème persiste, vous pouvez contacter l\'administrateur de ce site.

'; + } + + /** + * Get the service unavailable string + * + * @return string service unavailable + */ + public function getServiceUnavailable() + { + return 'Le service `%s\' est indisponible (%s)'; + } +} + +?> diff --git a/vendor/apereo/phpcas/source/CAS/Languages/Galego.php b/vendor/apereo/phpcas/source/CAS/Languages/Galego.php new file mode 100644 index 00000000..d5bf4045 --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/Languages/Galego.php @@ -0,0 +1,117 @@ +aquí para continuar'; + } + + /** + * Get authentication failed string + * + * @return string authentication failed + */ + public function getAuthenticationFailed() + { + return 'Autenticación CAS errada!'; + } + + /** + * Get the your were not authenticated string + * + * @return string not authenticated + */ + public function getYouWereNotAuthenticated() + { + return ' +

Non estás autenticado

Podes volver tentalo facendo click aquí.

Se o problema persiste debería contactar con el administrador deste sitio.

'; + } + + /** + * Get the service unavailable string + * + * @return string service unavailable + */ + public function getServiceUnavailable() + { + return 'O servizo `%s\' non está dispoñible (%s).'; + } +} +?> diff --git a/vendor/apereo/phpcas/source/CAS/Languages/German.php b/vendor/apereo/phpcas/source/CAS/Languages/German.php new file mode 100644 index 00000000..b718b145 --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/Languages/German.php @@ -0,0 +1,116 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * German language class + * + * @class CAS_Languages_German + * @category Authentication + * @package PhpCAS + * @author Henrik Genssen + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * + * @sa @link internalLang Internationalization @endlink + * @ingroup internalLang + */ +class CAS_Languages_German implements CAS_Languages_LanguageInterface +{ + /** + * Get the using server string + * + * @return string using server + */ + public function getUsingServer() + { + return 'via Server'; + } + + /** + * Get authentication wanted string + * + * @return string authentication wanted + */ + public function getAuthenticationWanted() + { + return 'CAS Authentifizierung erforderlich!'; + } + + /** + * Get logout string + * + * @return string logout + */ + public function getLogout() + { + return 'CAS Abmeldung!'; + } + + /** + * Get the should have been redirected string + * + * @return string should habe been redirected + */ + public function getShouldHaveBeenRedirected() + { + return 'eigentlich häten Sie zum CAS Server weitergeleitet werden sollen. Drücken Sie hier um fortzufahren.'; + } + + /** + * Get authentication failed string + * + * @return string authentication failed + */ + public function getAuthenticationFailed() + { + return 'CAS Anmeldung fehlgeschlagen!'; + } + + /** + * Get the your were not authenticated string + * + * @return string not authenticated + */ + public function getYouWereNotAuthenticated() + { + return '

Sie wurden nicht angemeldet.

Um es erneut zu versuchen klicken Sie hier.

Wenn das Problem bestehen bleibt, kontaktieren Sie den Administrator dieser Seite.

'; + } + + /** + * Get the service unavailable string + * + * @return string service unavailable + */ + public function getServiceUnavailable() + { + return 'Der Dienst `%s\' ist nicht verfügbar (%s).'; + } +} + +?> diff --git a/vendor/apereo/phpcas/source/CAS/Languages/Greek.php b/vendor/apereo/phpcas/source/CAS/Languages/Greek.php new file mode 100644 index 00000000..1cfb107e --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/Languages/Greek.php @@ -0,0 +1,115 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * Greek language class + * + * @class CAS_Languages_Greek + * @category Authentication + * @package PhpCAS + * @author Vangelis Haniotakis + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * + * @sa @link internalLang Internationalization @endlink + * @ingroup internalLang + */ +class CAS_Languages_Greek implements CAS_Languages_LanguageInterface +{ + /** + * Get the using server string + * + * @return string using server + */ + public function getUsingServer() + { + return 'χρησιμοποιείται ο εξυπηρετητής'; + } + + /** + * Get authentication wanted string + * + * @return string authentication wanted + */ + public function getAuthenticationWanted() + { + return 'Απαιτείται η ταυτοποίηση CAS!'; + } + + /** + * Get logout string + * + * @return string logout + */ + public function getLogout() + { + return 'Απαιτείται η αποσύνδεση από CAS!'; + } + + /** + * Get the should have been redirected string + * + * @return string should habe been redirected + */ + public function getShouldHaveBeenRedirected() + { + return 'Θα έπρεπε να είχατε ανακατευθυνθεί στον εξυπηρετητή CAS. Κάντε κλίκ εδώ για να συνεχίσετε.'; + } + + /** + * Get authentication failed string + * + * @return string authentication failed + */ + public function getAuthenticationFailed() + { + return 'Η ταυτοποίηση CAS απέτυχε!'; + } + + /** + * Get the your were not authenticated string + * + * @return string not authenticated + */ + public function getYouWereNotAuthenticated() + { + return '

Δεν ταυτοποιηθήκατε.

Μπορείτε να ξαναπροσπαθήσετε, κάνοντας κλίκ εδώ.

Εαν το πρόβλημα επιμείνει, ελάτε σε επαφή με τον διαχειριστή.

'; + } + + /** + * Get the service unavailable string + * + * @return string service unavailable + */ + public function getServiceUnavailable() + { + return 'Η υπηρεσία `%s\' δεν είναι διαθέσιμη (%s).'; + } +} +?> diff --git a/vendor/apereo/phpcas/source/CAS/Languages/Japanese.php b/vendor/apereo/phpcas/source/CAS/Languages/Japanese.php new file mode 100644 index 00000000..56814845 --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/Languages/Japanese.php @@ -0,0 +1,113 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * Japanese language class. Now Encoding is UTF-8. + * + * @class CAS_Languages_Japanese + * @category Authentication + * @package PhpCAS + * @author fnorif + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * + **/ +class CAS_Languages_Japanese implements CAS_Languages_LanguageInterface +{ + /** + * Get the using server string + * + * @return string using server + */ + public function getUsingServer() + { + return 'サーバーを使っています。'; + } + + /** + * Get authentication wanted string + * + * @return string authentication wanted + */ + public function getAuthenticationWanted() + { + return 'CASによる認証を行います。'; + } + + /** + * Get logout string + * + * @return string logout + */ + public function getLogout() + { + return 'CASからログアウトします!'; + } + + /** + * Get the should have been redirected string + * + * @return string should habe been redirected + */ + public function getShouldHaveBeenRedirected() + { + return 'CASサーバに行く必要があります。自動的に転送されない場合は こちら をクリックして続行します。'; + } + + /** + * Get authentication failed string + * + * @return string authentication failed + */ + public function getAuthenticationFailed() + { + return 'CASによる認証に失敗しました。'; + } + + /** + * Get the your were not authenticated string + * + * @return string not authenticated + */ + public function getYouWereNotAuthenticated() + { + return '

認証できませんでした。

もう一度リクエストを送信する場合はこちらをクリック。

問題が解決しない場合は このサイトの管理者に問い合わせてください。

'; + } + + /** + * Get the service unavailable string + * + * @return string service unavailable + */ + public function getServiceUnavailable() + { + return 'サービス `%s\' は利用できません (%s)。'; + } +} +?> diff --git a/vendor/apereo/phpcas/source/CAS/Languages/LanguageInterface.php b/vendor/apereo/phpcas/source/CAS/Languages/LanguageInterface.php new file mode 100644 index 00000000..dfb0ac51 --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/Languages/LanguageInterface.php @@ -0,0 +1,96 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * Language Interface class for all internationalization files + * + * @class CAS_Languages_LanguageInterface + * @category Authentication + * @package PhpCAS + * @author Joachim Fritschi + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * + * @sa @link internalLang Internationalization @endlink + * @ingroup internalLang + */ + +interface CAS_Languages_LanguageInterface +{ + /** + * Get the using server string + * + * @return string using server + */ + public function getUsingServer(); + + /** + * Get authentication wanted string + * + * @return string authentication wanted + */ + public function getAuthenticationWanted(); + + /** + * Get logout string + * + * @return string logout + */ + public function getLogout(); + + /** + * Get the should have been redirected string + * + * @return string should habe been redirected + */ + public function getShouldHaveBeenRedirected(); + + /** + * Get authentication failed string + * + * @return string authentication failed + */ + public function getAuthenticationFailed(); + + /** + * Get the your were not authenticated string + * + * @return string not authenticated + */ + public function getYouWereNotAuthenticated(); + + /** + * Get the service unavailable string + * + * @return string service unavailable + */ + public function getServiceUnavailable(); + +} +?> diff --git a/vendor/apereo/phpcas/source/CAS/Languages/Portuguese.php b/vendor/apereo/phpcas/source/CAS/Languages/Portuguese.php new file mode 100644 index 00000000..a927cad6 --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/Languages/Portuguese.php @@ -0,0 +1,114 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://apereo.atlassian.net/wiki/spaces/CASC/pages/103252517/phpCAS + */ + +/** + * Portuguese language class + * + * @class CAS_Languages_Portuguese + * @category Authentication + * @package PhpCAS + * @author Sherwin Harris + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://apereo.atlassian.net/wiki/spaces/CASC/pages/103252517/phpCAS + * + * @sa @link internalLang Internationalization @endlink + * @ingroup internalLang + */ +class CAS_Languages_Portuguese implements CAS_Languages_LanguageInterface +{ + /** + * Get the using server string + * + * @return string using server + */ + public function getUsingServer() + { + return 'Usando o servidor'; + } + + /** + * Get authentication wanted string + * + * @return string authentication wanted + */ + public function getAuthenticationWanted() + { + return 'A autenticação do servidor CAS desejado!'; + } + + /** + * Get logout string + * + * @return string logout + */ + public function getLogout() + { + return 'Saida do servidor CAS desejado!'; + } + + /** + * Get the should have been redirected string + * + * @return string should have been redirected + */ + public function getShouldHaveBeenRedirected() + { + return 'Você já deve ter sido redirecionado para o servidor CAS. Clique aqui para continuar'; + } + + /** + * Get authentication failed string + * + * @return string authentication failed + */ + public function getAuthenticationFailed() + { + return 'A autenticação do servidor CAS falheu!'; + } + + /** + * Get the your were not authenticated string + * + * @return string not authenticated + */ + public function getYouWereNotAuthenticated() + { + return '

Você não foi autenticado.

Você pode enviar sua solicitação novamente clicando aqui.

Se o problema persistir, você pode entrar em contato com o administrador deste site.

'; + } + + /** + * Get the service unavailable string + * + * @return string service unavailable + */ + public function getServiceUnavailable() + { + return 'O serviço `%s\' não está disponível (%s).'; + } +} diff --git a/vendor/apereo/phpcas/source/CAS/Languages/Spanish.php b/vendor/apereo/phpcas/source/CAS/Languages/Spanish.php new file mode 100644 index 00000000..c6ea50e7 --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/Languages/Spanish.php @@ -0,0 +1,117 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * Spanish language class + * + * @class CAS_Languages_Spanish + * @category Authentication + * @package PhpCAS + * @author Iván-Benjamín García Torà + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * + + * @sa @link internalLang Internationalization @endlink + * @ingroup internalLang + */ +class CAS_Languages_Spanish implements CAS_Languages_LanguageInterface +{ + + /** + * Get the using server string + * + * @return string using server + */ + public function getUsingServer() + { + return 'usando servidor'; + } + + /** + * Get authentication wanted string + * + * @return string authentication wanted + */ + public function getAuthenticationWanted() + { + return '¡Autentificación CAS necesaria!'; + } + + /** + * Get logout string + * + * @return string logout + */ + public function getLogout() + { + return '¡Salida CAS necesaria!'; + } + + /** + * Get the should have been redirected string + * + * @return string should habe been redirected + */ + public function getShouldHaveBeenRedirected() + { + return 'Ya debería haber sido redireccionado al servidor CAS. Haga click aquí para continuar.'; + } + + /** + * Get authentication failed string + * + * @return string authentication failed + */ + public function getAuthenticationFailed() + { + return '¡Autentificación CAS fallida!'; + } + + /** + * Get the your were not authenticated string + * + * @return string not authenticated + */ + public function getYouWereNotAuthenticated() + { + return '

No estás autentificado.

Puedes volver a intentarlo haciendo click aquí.

Si el problema persiste debería contactar con el administrador de este sitio.

'; + } + + /** + * Get the service unavailable string + * + * @return string service unavailable + */ + public function getServiceUnavailable() + { + return 'El servicio `%s\' no está disponible (%s).'; + } +} +?> diff --git a/vendor/apereo/phpcas/source/CAS/OutOfSequenceBeforeAuthenticationCallException.php b/vendor/apereo/phpcas/source/CAS/OutOfSequenceBeforeAuthenticationCallException.php new file mode 100644 index 00000000..d4d7680d --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/OutOfSequenceBeforeAuthenticationCallException.php @@ -0,0 +1,56 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This class defines Exceptions that should be thrown when the sequence of + * operations is invalid. In this case it should be thrown when an + * authentication call has not yet happened. + * + * @class CAS_OutOfSequenceBeforeAuthenticationCallException + * @category Authentication + * @package PhpCAS + * @author Joachim Fritschi + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_OutOfSequenceBeforeAuthenticationCallException +extends CAS_OutOfSequenceException +implements CAS_Exception +{ + /** + * Return standard error meessage + * + * @return void + */ + public function __construct () + { + parent::__construct('An authentication call hasn\'t happened yet.'); + } +} diff --git a/vendor/apereo/phpcas/source/CAS/OutOfSequenceBeforeClientException.php b/vendor/apereo/phpcas/source/CAS/OutOfSequenceBeforeClientException.php new file mode 100644 index 00000000..6c2c39c5 --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/OutOfSequenceBeforeClientException.php @@ -0,0 +1,58 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This class defines Exceptions that should be thrown when the sequence of + * operations is invalid. In this case it should be thrown when the client() or + * proxy() call has not yet happened and no client or proxy object exists. + * + * @class CAS_OutOfSequenceBeforeClientException + * @category Authentication + * @package PhpCAS + * @author Joachim Fritschi + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_OutOfSequenceBeforeClientException +extends CAS_OutOfSequenceException +implements CAS_Exception +{ + /** + * Return standard error message + * + * @return void + */ + public function __construct () + { + parent::__construct( + 'this method cannot be called before phpCAS::client() or phpCAS::proxy()' + ); + } +} diff --git a/vendor/apereo/phpcas/source/CAS/OutOfSequenceBeforeProxyException.php b/vendor/apereo/phpcas/source/CAS/OutOfSequenceBeforeProxyException.php new file mode 100644 index 00000000..79915552 --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/OutOfSequenceBeforeProxyException.php @@ -0,0 +1,59 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This class defines Exceptions that should be thrown when the sequence of + * operations is invalid. In this case it should be thrown when the proxy() call + * has not yet happened and no proxy object exists. + * + * @class CAS_OutOfSequenceBeforeProxyException + * @category Authentication + * @package PhpCAS + * @author Joachim Fritschi + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_OutOfSequenceBeforeProxyException +extends CAS_OutOfSequenceException +implements CAS_Exception +{ + + /** + * Return standard error message + * + * @return void + */ + public function __construct () + { + parent::__construct( + 'this method cannot be called before phpCAS::proxy()' + ); + } +} diff --git a/vendor/apereo/phpcas/source/CAS/OutOfSequenceException.php b/vendor/apereo/phpcas/source/CAS/OutOfSequenceException.php new file mode 100644 index 00000000..d6f7d88f --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/OutOfSequenceException.php @@ -0,0 +1,49 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This class defines Exceptions that should be thrown when the sequence of + * operations is invalid. Examples are: + * - Requesting the response before executing a request. + * - Changing the URL of a request after executing the request. + * + * @class CAS_OutOfSequenceException + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_OutOfSequenceException +extends BadMethodCallException +implements CAS_Exception +{ + +} diff --git a/vendor/apereo/phpcas/source/CAS/PGTStorage/AbstractStorage.php b/vendor/apereo/phpcas/source/CAS/PGTStorage/AbstractStorage.php new file mode 100644 index 00000000..a93568d6 --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/PGTStorage/AbstractStorage.php @@ -0,0 +1,222 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * Basic class for PGT storage + * The CAS_PGTStorage_AbstractStorage class is a generic class for PGT storage. + * This class should not be instanciated itself but inherited by specific PGT + * storage classes. + * + * @class CAS_PGTStorage_AbstractStorage + * @category Authentication + * @package PhpCAS + * @author Pascal Aubry + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * + * @ingroup internalPGTStorage + */ + +abstract class CAS_PGTStorage_AbstractStorage +{ + /** + * @addtogroup internalPGTStorage + * @{ + */ + + // ######################################################################## + // CONSTRUCTOR + // ######################################################################## + + /** + * The constructor of the class, should be called only by inherited classes. + * + * @param CAS_Client $cas_parent the CAS _client instance that creates the + * current object. + * + * @return void + * + * @protected + */ + function __construct($cas_parent) + { + phpCAS::traceBegin(); + if ( !$cas_parent->isProxy() ) { + phpCAS::error( + 'defining PGT storage makes no sense when not using a CAS proxy' + ); + } + phpCAS::traceEnd(); + } + + // ######################################################################## + // DEBUGGING + // ######################################################################## + + /** + * This virtual method returns an informational string giving the type of storage + * used by the object (used for debugging purposes). + * + * @return string + * + * @public + */ + function getStorageType() + { + phpCAS::error(__CLASS__.'::'.__FUNCTION__.'() should never be called'); + } + + /** + * This virtual method returns an informational string giving informations on the + * parameters of the storage.(used for debugging purposes). + * + * @return string + * + * @public + */ + function getStorageInfo() + { + phpCAS::error(__CLASS__.'::'.__FUNCTION__.'() should never be called'); + } + + // ######################################################################## + // ERROR HANDLING + // ######################################################################## + + /** + * string used to store an error message. Written by + * PGTStorage::setErrorMessage(), read by PGTStorage::getErrorMessage(). + * + * @hideinitializer + * @deprecated not used. + */ + var $_error_message=false; + + /** + * This method sets en error message, which can be read later by + * PGTStorage::getErrorMessage(). + * + * @param string $error_message an error message + * + * @return void + * + * @deprecated not used. + */ + function setErrorMessage($error_message) + { + $this->_error_message = $error_message; + } + + /** + * This method returns an error message set by PGTStorage::setErrorMessage(). + * + * @return string an error message when set by PGTStorage::setErrorMessage(), FALSE + * otherwise. + * + * @deprecated not used. + */ + function getErrorMessage() + { + return $this->_error_message; + } + + // ######################################################################## + // INITIALIZATION + // ######################################################################## + + /** + * a boolean telling if the storage has already been initialized. Written by + * PGTStorage::init(), read by PGTStorage::isInitialized(). + * + * @hideinitializer + */ + var $_initialized = false; + + /** + * This method tells if the storage has already been intialized. + * + * @return bool + * + * @protected + */ + function isInitialized() + { + return $this->_initialized; + } + + /** + * This virtual method initializes the object. + * + * @return void + */ + function init() + { + $this->_initialized = true; + } + + // ######################################################################## + // PGT I/O + // ######################################################################## + + /** + * This virtual method stores a PGT and its corresponding PGT Iuo. + * + * @param string $pgt the PGT + * @param string $pgt_iou the PGT iou + * + * @return void + * + * @note Should never be called. + * + */ + function write($pgt,$pgt_iou) + { + phpCAS::error(__CLASS__.'::'.__FUNCTION__.'() should never be called'); + } + + /** + * This virtual method reads a PGT corresponding to a PGT Iou and deletes + * the corresponding storage entry. + * + * @param string $pgt_iou the PGT iou + * + * @return string + * + * @note Should never be called. + */ + function read($pgt_iou) + { + phpCAS::error(__CLASS__.'::'.__FUNCTION__.'() should never be called'); + } + + /** @} */ + +} + +?> diff --git a/vendor/apereo/phpcas/source/CAS/PGTStorage/Db.php b/vendor/apereo/phpcas/source/CAS/PGTStorage/Db.php new file mode 100644 index 00000000..2efe5a3e --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/PGTStorage/Db.php @@ -0,0 +1,440 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +define('CAS_PGT_STORAGE_DB_DEFAULT_TABLE', 'cas_pgts'); + +/** + * Basic class for PGT database storage + * The CAS_PGTStorage_Db class is a class for PGT database storage. + * + * @class CAS_PGTStorage_Db + * @category Authentication + * @package PhpCAS + * @author Daniel Frett + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * + * @ingroup internalPGTStorageDb + */ + +class CAS_PGTStorage_Db extends CAS_PGTStorage_AbstractStorage +{ + /** + * @addtogroup internalCAS_PGTStorageDb + * @{ + */ + + /** + * the PDO object to use for database interactions + */ + private $_pdo; + + /** + * This method returns the PDO object to use for database interactions. + * + * @return PDO object + */ + private function _getPdo() + { + return $this->_pdo; + } + + /** + * database connection options to use when creating a new PDO object + */ + private $_dsn; + private $_username; + private $_password; + private $_driver_options; + + /** + * @var string the table to use for storing/retrieving pgt's + */ + private $_table; + + /** + * This method returns the table to use when storing/retrieving PGT's + * + * @return string the name of the pgt storage table. + */ + private function _getTable() + { + return $this->_table; + } + + // ######################################################################## + // DEBUGGING + // ######################################################################## + + /** + * This method returns an informational string giving the type of storage + * used by the object (used for debugging purposes). + * + * @return string an informational string. + */ + public function getStorageType() + { + return "db"; + } + + /** + * This method returns an informational string giving informations on the + * parameters of the storage.(used for debugging purposes). + * + * @return string an informational string. + * @public + */ + public function getStorageInfo() + { + return 'table=`'.$this->_getTable().'\''; + } + + // ######################################################################## + // CONSTRUCTOR + // ######################################################################## + + /** + * The class constructor. + * + * @param CAS_Client $cas_parent the CAS_Client instance that creates + * the object. + * @param string $dsn_or_pdo a dsn string to use for creating a PDO + * object or a PDO object + * @param string $username the username to use when connecting to + * the database + * @param string $password the password to use when connecting to + * the database + * @param string $table the table to use for storing and + * retrieving PGT's + * @param string $driver_options any driver options to use when + * connecting to the database + */ + public function __construct( + $cas_parent, $dsn_or_pdo, $username='', $password='', $table='', + $driver_options=null + ) { + phpCAS::traceBegin(); + // call the ancestor's constructor + parent::__construct($cas_parent); + + // set default values + if ( empty($table) ) { + $table = CAS_PGT_STORAGE_DB_DEFAULT_TABLE; + } + if ( !is_array($driver_options) ) { + $driver_options = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION); + } + + // store the specified parameters + if ($dsn_or_pdo instanceof PDO) { + $this->_pdo = $dsn_or_pdo; + } else { + $this->_dsn = $dsn_or_pdo; + $this->_username = $username; + $this->_password = $password; + $this->_driver_options = $driver_options; + } + + // store the table name + $this->_table = $table; + + phpCAS::traceEnd(); + } + + // ######################################################################## + // INITIALIZATION + // ######################################################################## + + /** + * This method is used to initialize the storage. Halts on error. + * + * @return void + */ + public function init() + { + phpCAS::traceBegin(); + // if the storage has already been initialized, return immediatly + if ($this->isInitialized()) { + return; + } + + // initialize the base object + parent::init(); + + // create the PDO object if it doesn't exist already + if (!($this->_pdo instanceof PDO)) { + try { + $this->_pdo = new PDO( + $this->_dsn, $this->_username, $this->_password, + $this->_driver_options + ); + } + catch(PDOException $e) { + phpCAS::error('Database connection error: ' . $e->getMessage()); + } + } + + phpCAS::traceEnd(); + } + + // ######################################################################## + // PDO database interaction + // ######################################################################## + + /** + * attribute that stores the previous error mode for the PDO handle while + * processing a transaction + */ + private $_errMode; + + /** + * This method will enable the Exception error mode on the PDO object + * + * @return void + */ + private function _setErrorMode() + { + // get PDO object and enable exception error mode + $pdo = $this->_getPdo(); + $this->_errMode = $pdo->getAttribute(PDO::ATTR_ERRMODE); + $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + } + + /** + * this method will reset the error mode on the PDO object + * + * @return void + */ + private function _resetErrorMode() + { + // get PDO object and reset the error mode to what it was originally + $pdo = $this->_getPdo(); + $pdo->setAttribute(PDO::ATTR_ERRMODE, $this->_errMode); + } + + // ######################################################################## + // database queries + // ######################################################################## + // these queries are potentially unsafe because the person using this library + // can set the table to use, but there is no reliable way to escape SQL + // fieldnames in PDO yet + + /** + * This method returns the query used to create a pgt storage table + * + * @return string the create table SQL, no bind params in query + */ + protected function createTableSql() + { + return 'CREATE TABLE ' . $this->_getTable() + . ' (pgt_iou VARCHAR(255) NOT NULL PRIMARY KEY, pgt VARCHAR(255) NOT NULL)'; + } + + /** + * This method returns the query used to store a pgt + * + * @return string the store PGT SQL, :pgt and :pgt_iou are the bind params contained + * in the query + */ + protected function storePgtSql() + { + return 'INSERT INTO ' . $this->_getTable() + . ' (pgt_iou, pgt) VALUES (:pgt_iou, :pgt)'; + } + + /** + * This method returns the query used to retrieve a pgt. the first column + * of the first row should contain the pgt + * + * @return string the retrieve PGT SQL, :pgt_iou is the only bind param contained + * in the query + */ + protected function retrievePgtSql() + { + return 'SELECT pgt FROM ' . $this->_getTable() . ' WHERE pgt_iou = :pgt_iou'; + } + + /** + * This method returns the query used to delete a pgt. + * + * @return string the delete PGT SQL, :pgt_iou is the only bind param contained in + * the query + */ + protected function deletePgtSql() + { + return 'DELETE FROM ' . $this->_getTable() . ' WHERE pgt_iou = :pgt_iou'; + } + + // ######################################################################## + // PGT I/O + // ######################################################################## + + /** + * This method creates the database table used to store pgt's and pgtiou's + * + * @return void + */ + public function createTable() + { + phpCAS::traceBegin(); + + // initialize this PGTStorage object if it hasn't been initialized yet + if ( !$this->isInitialized() ) { + $this->init(); + } + + // initialize the PDO object for this method + $pdo = $this->_getPdo(); + $this->_setErrorMode(); + + try { + $pdo->beginTransaction(); + + $query = $pdo->query($this->createTableSQL()); + $query->closeCursor(); + + $pdo->commit(); + } + catch(PDOException $e) { + // attempt rolling back the transaction before throwing a phpCAS error + try { + $pdo->rollBack(); + } + catch(PDOException $e) { + } + phpCAS::error('error creating PGT storage table: ' . $e->getMessage()); + } + + // reset the PDO object + $this->_resetErrorMode(); + + phpCAS::traceEnd(); + } + + /** + * This method stores a PGT and its corresponding PGT Iou in the database. + * Echoes a warning on error. + * + * @param string $pgt the PGT + * @param string $pgt_iou the PGT iou + * + * @return void + */ + public function write($pgt, $pgt_iou) + { + phpCAS::traceBegin(); + + // initialize the PDO object for this method + $pdo = $this->_getPdo(); + $this->_setErrorMode(); + + try { + $pdo->beginTransaction(); + + $query = $pdo->prepare($this->storePgtSql()); + $query->bindValue(':pgt', $pgt, PDO::PARAM_STR); + $query->bindValue(':pgt_iou', $pgt_iou, PDO::PARAM_STR); + $query->execute(); + $query->closeCursor(); + + $pdo->commit(); + } + catch(PDOException $e) { + // attempt rolling back the transaction before throwing a phpCAS error + try { + $pdo->rollBack(); + } + catch(PDOException $e) { + } + phpCAS::error('error writing PGT to database: ' . $e->getMessage()); + } + + // reset the PDO object + $this->_resetErrorMode(); + + phpCAS::traceEnd(); + } + + /** + * This method reads a PGT corresponding to a PGT Iou and deletes the + * corresponding db entry. + * + * @param string $pgt_iou the PGT iou + * + * @return string|false the corresponding PGT, or FALSE on error + */ + public function read($pgt_iou) + { + phpCAS::traceBegin(); + $pgt = false; + + // initialize the PDO object for this method + $pdo = $this->_getPdo(); + $this->_setErrorMode(); + + try { + $pdo->beginTransaction(); + + // fetch the pgt for the specified pgt_iou + $query = $pdo->prepare($this->retrievePgtSql()); + $query->bindValue(':pgt_iou', $pgt_iou, PDO::PARAM_STR); + $query->execute(); + $pgt = $query->fetchColumn(0); + $query->closeCursor(); + + // delete the specified pgt_iou from the database + $query = $pdo->prepare($this->deletePgtSql()); + $query->bindValue(':pgt_iou', $pgt_iou, PDO::PARAM_STR); + $query->execute(); + $query->closeCursor(); + + $pdo->commit(); + } + catch(PDOException $e) { + // attempt rolling back the transaction before throwing a phpCAS error + try { + $pdo->rollBack(); + } + catch(PDOException $e) { + } + phpCAS::trace('error reading PGT from database: ' . $e->getMessage()); + } + + // reset the PDO object + $this->_resetErrorMode(); + + phpCAS::traceEnd(); + return $pgt; + } + + /** @} */ + +} + +?> diff --git a/vendor/apereo/phpcas/source/CAS/PGTStorage/File.php b/vendor/apereo/phpcas/source/CAS/PGTStorage/File.php new file mode 100644 index 00000000..fbacd3b7 --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/PGTStorage/File.php @@ -0,0 +1,261 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * The CAS_PGTStorage_File class is a class for PGT file storage. An instance of + * this class is returned by CAS_Client::SetPGTStorageFile(). + * + * @class CAS_PGTStorage_File + * @category Authentication + * @package PhpCAS + * @author Pascal Aubry + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * + * + * @ingroup internalPGTStorageFile + */ + +class CAS_PGTStorage_File extends CAS_PGTStorage_AbstractStorage +{ + /** + * @addtogroup internalPGTStorageFile + * @{ + */ + + /** + * a string telling where PGT's should be stored on the filesystem. Written by + * PGTStorageFile::PGTStorageFile(), read by getPath(). + * + * @private + */ + var $_path; + + /** + * This method returns the name of the directory where PGT's should be stored + * on the filesystem. + * + * @return string the name of a directory (with leading and trailing '/') + * + * @private + */ + function getPath() + { + return $this->_path; + } + + // ######################################################################## + // DEBUGGING + // ######################################################################## + + /** + * This method returns an informational string giving the type of storage + * used by the object (used for debugging purposes). + * + * @return string an informational string. + * @public + */ + function getStorageType() + { + return "file"; + } + + /** + * This method returns an informational string giving informations on the + * parameters of the storage.(used for debugging purposes). + * + * @return string an informational string. + * @public + */ + function getStorageInfo() + { + return 'path=`'.$this->getPath().'\''; + } + + // ######################################################################## + // CONSTRUCTOR + // ######################################################################## + + /** + * The class constructor, called by CAS_Client::SetPGTStorageFile(). + * + * @param CAS_Client $cas_parent the CAS_Client instance that creates the object. + * @param string $path the path where the PGT's should be stored + * + * @return void + * + * @public + */ + function __construct($cas_parent,$path) + { + phpCAS::traceBegin(); + // call the ancestor's constructor + parent::__construct($cas_parent); + + if (empty($path)) { + $path = CAS_PGT_STORAGE_FILE_DEFAULT_PATH; + } + // check that the path is an absolute path + if (getenv("OS")=="Windows_NT" || strtoupper(substr(PHP_OS,0,3)) == 'WIN') { + + if (!preg_match('`^[a-zA-Z]:`', $path)) { + phpCAS::error('an absolute path is needed for PGT storage to file'); + } + + } else { + + if ( $path[0] != '/' ) { + phpCAS::error('an absolute path is needed for PGT storage to file'); + } + + // store the path (with a leading and trailing '/') + $path = preg_replace('|[/]*$|', '/', $path); + $path = preg_replace('|^[/]*|', '/', $path); + } + + $this->_path = $path; + phpCAS::traceEnd(); + } + + // ######################################################################## + // INITIALIZATION + // ######################################################################## + + /** + * This method is used to initialize the storage. Halts on error. + * + * @return void + * @public + */ + function init() + { + phpCAS::traceBegin(); + // if the storage has already been initialized, return immediatly + if ($this->isInitialized()) { + return; + } + // call the ancestor's method (mark as initialized) + parent::init(); + phpCAS::traceEnd(); + } + + // ######################################################################## + // PGT I/O + // ######################################################################## + + /** + * This method returns the filename corresponding to a PGT Iou. + * + * @param string $pgt_iou the PGT iou. + * + * @return string a filename + * @private + */ + function getPGTIouFilename($pgt_iou) + { + phpCAS::traceBegin(); + $filename = $this->getPath()."phpcas-".hash("sha256", $pgt_iou); +// $filename = $this->getPath().$pgt_iou.'.plain'; + phpCAS::trace("Sha256 filename:" . $filename); + phpCAS::traceEnd(); + return $filename; + } + + /** + * This method stores a PGT and its corresponding PGT Iou into a file. Echoes a + * warning on error. + * + * @param string $pgt the PGT + * @param string $pgt_iou the PGT iou + * + * @return void + * + * @public + */ + function write($pgt,$pgt_iou) + { + phpCAS::traceBegin(); + $fname = $this->getPGTIouFilename($pgt_iou); + if (!file_exists($fname)) { + touch($fname); + // Chmod will fail on windows + @chmod($fname, 0600); + if ($f=fopen($fname, "w")) { + if (fputs($f, $pgt) === false) { + phpCAS::error('could not write PGT to `'.$fname.'\''); + } + phpCAS::trace('Successful write of PGT to `'.$fname.'\''); + fclose($f); + } else { + phpCAS::error('could not open `'.$fname.'\''); + } + } else { + phpCAS::error('File exists: `'.$fname.'\''); + } + phpCAS::traceEnd(); + } + + /** + * This method reads a PGT corresponding to a PGT Iou and deletes the + * corresponding file. + * + * @param string $pgt_iou the PGT iou + * + * @return string|false the corresponding PGT, or FALSE on error + * + * @public + */ + function read($pgt_iou) + { + phpCAS::traceBegin(); + $pgt = false; + $fname = $this->getPGTIouFilename($pgt_iou); + if (file_exists($fname)) { + if (!($f=fopen($fname, "r"))) { + phpCAS::error('could not open `'.$fname.'\''); + } else { + if (($pgt=fgets($f)) === false) { + phpCAS::error('could not read PGT from `'.$fname.'\''); + } + phpCAS::trace('Successful read of PGT to `'.$fname.'\''); + fclose($f); + } + // delete the PGT file + @unlink($fname); + } else { + phpCAS::error('No such file `'.$fname.'\''); + } + phpCAS::traceEnd($pgt); + return $pgt; + } + + /** @} */ + +} +?> diff --git a/vendor/apereo/phpcas/source/CAS/ProxiedService.php b/vendor/apereo/phpcas/source/CAS/ProxiedService.php new file mode 100644 index 00000000..2673ee95 --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/ProxiedService.php @@ -0,0 +1,72 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This interface defines methods that allow proxy-authenticated service handlers + * to interact with phpCAS. + * + * Proxy service handlers must implement this interface as well as call + * phpCAS::initializeProxiedService($this) at some point in their implementation. + * + * While not required, proxy-authenticated service handlers are encouraged to + * implement the CAS_ProxiedService_Testable interface to facilitate unit testing. + * + * @class CAS_ProxiedService + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +interface CAS_ProxiedService +{ + + /** + * Answer a service identifier (URL) for whom we should fetch a proxy ticket. + * + * @return string + * @throws Exception If no service url is available. + */ + public function getServiceUrl (); + + /** + * Register a proxy ticket with the ProxiedService that it can use when + * making requests. + * + * @param string $proxyTicket Proxy ticket string + * + * @return void + * @throws InvalidArgumentException If the $proxyTicket is invalid. + * @throws CAS_OutOfSequenceException If called after a proxy ticket has + * already been initialized/set. + */ + public function setProxyTicket ($proxyTicket); + +} +?> diff --git a/vendor/apereo/phpcas/source/CAS/ProxiedService/Abstract.php b/vendor/apereo/phpcas/source/CAS/ProxiedService/Abstract.php new file mode 100644 index 00000000..0801c723 --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/ProxiedService/Abstract.php @@ -0,0 +1,149 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This class implements common methods for ProxiedService implementations included + * with phpCAS. + * + * @class CAS_ProxiedService_Abstract + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +abstract class CAS_ProxiedService_Abstract +implements CAS_ProxiedService, CAS_ProxiedService_Testable +{ + + /** + * The proxy ticket that can be used when making service requests. + * @var string $_proxyTicket; + */ + private $_proxyTicket; + + /** + * Register a proxy ticket with the Proxy that it can use when making requests. + * + * @param string $proxyTicket proxy ticket + * + * @return void + * @throws InvalidArgumentException If the $proxyTicket is invalid. + * @throws CAS_OutOfSequenceException If called after a proxy ticket has + * already been initialized/set. + */ + public function setProxyTicket ($proxyTicket) + { + if (empty($proxyTicket)) { + throw new CAS_InvalidArgumentException( + 'Trying to initialize with an empty proxy ticket.' + ); + } + if (!empty($this->_proxyTicket)) { + throw new CAS_OutOfSequenceException( + 'Already initialized, cannot change the proxy ticket.' + ); + } + $this->_proxyTicket = $proxyTicket; + } + + /** + * Answer the proxy ticket to be used when making requests. + * + * @return string + * @throws CAS_OutOfSequenceException If called before a proxy ticket has + * already been initialized/set. + */ + protected function getProxyTicket () + { + if (empty($this->_proxyTicket)) { + throw new CAS_OutOfSequenceException( + 'No proxy ticket yet. Call $this->initializeProxyTicket() to aquire the proxy ticket.' + ); + } + + return $this->_proxyTicket; + } + + /** + * @var CAS_Client $_casClient; + */ + private $_casClient; + + /** + * Use a particular CAS_Client->initializeProxiedService() rather than the + * static phpCAS::initializeProxiedService(). + * + * This method should not be called in standard operation, but is needed for unit + * testing. + * + * @param CAS_Client $casClient cas client + * + * @return void + * @throws CAS_OutOfSequenceException If called after a proxy ticket has + * already been initialized/set. + */ + public function setCasClient (CAS_Client $casClient) + { + if (!empty($this->_proxyTicket)) { + throw new CAS_OutOfSequenceException( + 'Already initialized, cannot change the CAS_Client.' + ); + } + + $this->_casClient = $casClient; + } + + /** + * Fetch our proxy ticket. + * + * Descendent classes should call this method once their service URL is available + * to initialize their proxy ticket. + * + * @return void + * @throws CAS_OutOfSequenceException If called after a proxy ticket has + * already been initialized. + */ + protected function initializeProxyTicket() + { + if (!empty($this->_proxyTicket)) { + throw new CAS_OutOfSequenceException( + 'Already initialized, cannot initialize again.' + ); + } + // Allow usage of a particular CAS_Client for unit testing. + if (empty($this->_casClient)) { + phpCAS::initializeProxiedService($this); + } else { + $this->_casClient->initializeProxiedService($this); + } + } + +} +?> diff --git a/vendor/apereo/phpcas/source/CAS/ProxiedService/Exception.php b/vendor/apereo/phpcas/source/CAS/ProxiedService/Exception.php new file mode 100644 index 00000000..0f87413d --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/ProxiedService/Exception.php @@ -0,0 +1,46 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * An Exception for problems communicating with a proxied service. + * + * @class CAS_ProxiedService_Exception + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_ProxiedService_Exception +extends Exception +implements CAS_Exception +{ + +} +?> diff --git a/vendor/apereo/phpcas/source/CAS/ProxiedService/Http.php b/vendor/apereo/phpcas/source/CAS/ProxiedService/Http.php new file mode 100644 index 00000000..4240b061 --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/ProxiedService/Http.php @@ -0,0 +1,91 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This interface defines methods that clients should use for configuring, sending, + * and receiving proxied HTTP requests. + * + * @class CAS_ProxiedService_Http + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +interface CAS_ProxiedService_Http +{ + + /********************************************************* + * Configure the Request + *********************************************************/ + + /** + * Set the URL of the Request + * + * @param string $url Url to set + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function setUrl ($url); + + /********************************************************* + * 2. Send the Request + *********************************************************/ + + /** + * Perform the request. + * + * @return bool TRUE on success, FALSE on failure. + * @throws CAS_OutOfSequenceException If called multiple times. + */ + public function send (); + + /********************************************************* + * 3. Access the response + *********************************************************/ + + /** + * Answer the headers of the response. + * + * @return array An array of header strings. + * @throws CAS_OutOfSequenceException If called before the Request has been sent. + */ + public function getResponseHeaders (); + + /** + * Answer the body of response. + * + * @return string + * @throws CAS_OutOfSequenceException If called before the Request has been sent. + */ + public function getResponseBody (); + +} +?> diff --git a/vendor/apereo/phpcas/source/CAS/ProxiedService/Http/Abstract.php b/vendor/apereo/phpcas/source/CAS/ProxiedService/Http/Abstract.php new file mode 100644 index 00000000..8d55edd5 --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/ProxiedService/Http/Abstract.php @@ -0,0 +1,360 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This class implements common methods for ProxiedService implementations included + * with phpCAS. + * + * @class CAS_ProxiedService_Http_Abstract + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +abstract class CAS_ProxiedService_Http_Abstract extends +CAS_ProxiedService_Abstract implements CAS_ProxiedService_Http +{ + /** + * The HTTP request mechanism talking to the target service. + * + * @var CAS_Request_RequestInterface $requestHandler + */ + protected $requestHandler; + + /** + * The storage mechanism for cookies set by the target service. + * + * @var CAS_CookieJar $_cookieJar + */ + private $_cookieJar; + + /** + * Constructor. + * + * @param CAS_Request_RequestInterface $requestHandler request handler object + * @param CAS_CookieJar $cookieJar cookieJar object + * + * @return void + */ + public function __construct(CAS_Request_RequestInterface $requestHandler, + CAS_CookieJar $cookieJar + ) { + $this->requestHandler = $requestHandler; + $this->_cookieJar = $cookieJar; + } + + /** + * The target service url. + * @var string $_url; + */ + private $_url; + + /** + * Answer a service identifier (URL) for whom we should fetch a proxy ticket. + * + * @return string + * @throws Exception If no service url is available. + */ + public function getServiceUrl() + { + if (empty($this->_url)) { + throw new CAS_ProxiedService_Exception( + 'No URL set via ' . get_class($this) . '->setUrl($url).' + ); + } + + return $this->_url; + } + + /********************************************************* + * Configure the Request + *********************************************************/ + + /** + * Set the URL of the Request + * + * @param string $url url to set + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function setUrl($url) + { + if ($this->hasBeenSent()) { + throw new CAS_OutOfSequenceException( + 'Cannot set the URL, request already sent.' + ); + } + if (!is_string($url)) { + throw new CAS_InvalidArgumentException('$url must be a string.'); + } + + $this->_url = $url; + } + + /********************************************************* + * 2. Send the Request + *********************************************************/ + + /** + * Perform the request. + * + * @return void + * @throws CAS_OutOfSequenceException If called multiple times. + * @throws CAS_ProxyTicketException If there is a proxy-ticket failure. + * The code of the Exception will be one of: + * PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE + * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE + * PHPCAS_SERVICE_PT_FAILURE + * @throws CAS_ProxiedService_Exception If there is a failure sending the + * request to the target service. + */ + public function send() + { + if ($this->hasBeenSent()) { + throw new CAS_OutOfSequenceException( + 'Cannot send, request already sent.' + ); + } + + phpCAS::traceBegin(); + + // Get our proxy ticket and append it to our URL. + $this->initializeProxyTicket(); + $url = $this->getServiceUrl(); + if (strstr($url, '?') === false) { + $url = $url . '?ticket=' . $this->getProxyTicket(); + } else { + $url = $url . '&ticket=' . $this->getProxyTicket(); + } + + try { + $this->makeRequest($url); + } catch (Exception $e) { + phpCAS::traceEnd(); + throw $e; + } + } + + /** + * Indicator of the number of requests (including redirects performed. + * + * @var int $_numRequests; + */ + private $_numRequests = 0; + + /** + * The response headers. + * + * @var array $_responseHeaders; + */ + private $_responseHeaders = array(); + + /** + * The response status code. + * + * @var int $_responseStatusCode; + */ + private $_responseStatusCode = ''; + + /** + * The response headers. + * + * @var string $_responseBody; + */ + private $_responseBody = ''; + + /** + * Build and perform a request, following redirects + * + * @param string $url url for the request + * + * @return void + * @throws CAS_ProxyTicketException If there is a proxy-ticket failure. + * The code of the Exception will be one of: + * PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE + * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE + * PHPCAS_SERVICE_PT_FAILURE + * @throws CAS_ProxiedService_Exception If there is a failure sending the + * request to the target service. + */ + protected function makeRequest($url) + { + // Verify that we are not in a redirect loop + $this->_numRequests++; + if ($this->_numRequests > 4) { + $message = 'Exceeded the maximum number of redirects (3) in proxied service request.'; + phpCAS::trace($message); + throw new CAS_ProxiedService_Exception($message); + } + + // Create a new request. + $request = clone $this->requestHandler; + $request->setUrl($url); + + // Add any cookies to the request. + $request->addCookies($this->_cookieJar->getCookies($url)); + + // Add any other parts of the request needed by concrete classes + $this->populateRequest($request); + + // Perform the request. + phpCAS::trace('Performing proxied service request to \'' . $url . '\''); + if (!$request->send()) { + $message = 'Could not perform proxied service request to URL`' + . $url . '\'. ' . $request->getErrorMessage(); + phpCAS::trace($message); + throw new CAS_ProxiedService_Exception($message); + } + + // Store any cookies from the response; + $this->_cookieJar->storeCookies($url, $request->getResponseHeaders()); + + // Follow any redirects + if ($redirectUrl = $this->getRedirectUrl($request->getResponseHeaders()) + ) { + phpCAS::trace('Found redirect:' . $redirectUrl); + $this->makeRequest($redirectUrl); + } else { + + $this->_responseHeaders = $request->getResponseHeaders(); + $this->_responseBody = $request->getResponseBody(); + $this->_responseStatusCode = $request->getResponseStatusCode(); + } + } + + /** + * Add any other parts of the request needed by concrete classes + * + * @param CAS_Request_RequestInterface $request request interface object + * + * @return void + */ + abstract protected function populateRequest( + CAS_Request_RequestInterface $request + ); + + /** + * Answer a redirect URL if a redirect header is found, otherwise null. + * + * @param array $responseHeaders response header to extract a redirect from + * + * @return string|null + */ + protected function getRedirectUrl(array $responseHeaders) + { + // Check for the redirect after authentication + foreach ($responseHeaders as $header) { + if ( preg_match('/^(Location:|URI:)\s*([^\s]+.*)$/', $header, $matches) + ) { + return trim(array_pop($matches)); + } + } + return null; + } + + /********************************************************* + * 3. Access the response + *********************************************************/ + + /** + * Answer true if our request has been sent yet. + * + * @return bool + */ + protected function hasBeenSent() + { + return ($this->_numRequests > 0); + } + + /** + * Answer the headers of the response. + * + * @return array An array of header strings. + * @throws CAS_OutOfSequenceException If called before the Request has been sent. + */ + public function getResponseHeaders() + { + if (!$this->hasBeenSent()) { + throw new CAS_OutOfSequenceException( + 'Cannot access response, request not sent yet.' + ); + } + + return $this->_responseHeaders; + } + + /** + * Answer HTTP status code of the response + * + * @return int + * @throws CAS_OutOfSequenceException If called before the Request has been sent. + */ + public function getResponseStatusCode() + { + if (!$this->hasBeenSent()) { + throw new CAS_OutOfSequenceException( + 'Cannot access response, request not sent yet.' + ); + } + + return $this->_responseStatusCode; + } + + /** + * Answer the body of response. + * + * @return string + * @throws CAS_OutOfSequenceException If called before the Request has been sent. + */ + public function getResponseBody() + { + if (!$this->hasBeenSent()) { + throw new CAS_OutOfSequenceException( + 'Cannot access response, request not sent yet.' + ); + } + + return $this->_responseBody; + } + + /** + * Answer the cookies from the response. This may include cookies set during + * redirect responses. + * + * @return array An array containing cookies. E.g. array('name' => 'val'); + */ + public function getCookies() + { + return $this->_cookieJar->getCookies($this->getServiceUrl()); + } + +} +?> diff --git a/vendor/apereo/phpcas/source/CAS/ProxiedService/Http/Get.php b/vendor/apereo/phpcas/source/CAS/ProxiedService/Http/Get.php new file mode 100644 index 00000000..a459d55a --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/ProxiedService/Http/Get.php @@ -0,0 +1,85 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This class is used to make proxied service requests via the HTTP GET method. + * + * Usage Example: + * + * try { + * $service = phpCAS::getProxiedService(PHPCAS_PROXIED_SERVICE_HTTP_GET); + * $service->setUrl('http://www.example.com/path/'); + * $service->send(); + * if ($service->getResponseStatusCode() == 200) + * return $service->getResponseBody(); + * else + * // The service responded with an error code 404, 500, etc. + * throw new Exception('The service responded with an error.'); + * + * } catch (CAS_ProxyTicketException $e) { + * if ($e->getCode() == PHPCAS_SERVICE_PT_FAILURE) + * return "Your login has timed out. You need to log in again."; + * else + * // Other proxy ticket errors are from bad request format + * // (shouldn't happen) or CAS server failure (unlikely) + * // so lets just stop if we hit those. + * throw $e; + * } catch (CAS_ProxiedService_Exception $e) { + * // Something prevented the service request from being sent or received. + * // We didn't even get a valid error response (404, 500, etc), so this + * // might be caused by a network error or a DNS resolution failure. + * // We could handle it in some way, but for now we will just stop. + * throw $e; + * } + * + * @class CAS_ProxiedService_Http_Get + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_ProxiedService_Http_Get +extends CAS_ProxiedService_Http_Abstract +{ + + /** + * Add any other parts of the request needed by concrete classes + * + * @param CAS_Request_RequestInterface $request request interface + * + * @return void + */ + protected function populateRequest (CAS_Request_RequestInterface $request) + { + // do nothing, since the URL has already been sent and that is our + // only data. + } +} +?> diff --git a/vendor/apereo/phpcas/source/CAS/ProxiedService/Http/Post.php b/vendor/apereo/phpcas/source/CAS/ProxiedService/Http/Post.php new file mode 100644 index 00000000..344c4398 --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/ProxiedService/Http/Post.php @@ -0,0 +1,152 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This class is used to make proxied service requests via the HTTP POST method. + * + * Usage Example: + * + * try { + * $service = phpCAS::getProxiedService(PHPCAS_PROXIED_SERVICE_HTTP_POST); + * $service->setUrl('http://www.example.com/path/'); + * $service->setContentType('text/xml'); + * $service->setBody('example.search'); + * $service->send(); + * if ($service->getResponseStatusCode() == 200) + * return $service->getResponseBody(); + * else + * // The service responded with an error code 404, 500, etc. + * throw new Exception('The service responded with an error.'); + * + * } catch (CAS_ProxyTicketException $e) { + * if ($e->getCode() == PHPCAS_SERVICE_PT_FAILURE) + * return "Your login has timed out. You need to log in again."; + * else + * // Other proxy ticket errors are from bad request format + * // (shouldn't happen) or CAS server failure (unlikely) so lets just + * // stop if we hit those. + * throw $e; + * } catch (CAS_ProxiedService_Exception $e) { + * // Something prevented the service request from being sent or received. + * // We didn't even get a valid error response (404, 500, etc), so this + * // might be caused by a network error or a DNS resolution failure. + * // We could handle it in some way, but for now we will just stop. + * throw $e; + * } + * + * @class CAS_ProxiedService_Http_Post + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_ProxiedService_Http_Post +extends CAS_ProxiedService_Http_Abstract +{ + + /** + * The content-type of this request + * + * @var string $_contentType + */ + private $_contentType; + + /** + * The body of the this request + * + * @var string $_body + */ + private $_body; + + /** + * Set the content type of this POST request. + * + * @param string $contentType content type + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function setContentType ($contentType) + { + if ($this->hasBeenSent()) { + throw new CAS_OutOfSequenceException( + 'Cannot set the content type, request already sent.' + ); + } + + $this->_contentType = $contentType; + } + + /** + * Set the body of this POST request. + * + * @param string $body body to set + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function setBody ($body) + { + if ($this->hasBeenSent()) { + throw new CAS_OutOfSequenceException( + 'Cannot set the body, request already sent.' + ); + } + + $this->_body = $body; + } + + /** + * Add any other parts of the request needed by concrete classes + * + * @param CAS_Request_RequestInterface $request request interface class + * + * @return void + */ + protected function populateRequest (CAS_Request_RequestInterface $request) + { + if (empty($this->_contentType) && !empty($this->_body)) { + throw new CAS_ProxiedService_Exception( + "If you pass a POST body, you must specify a content type via " + .get_class($this).'->setContentType($contentType).' + ); + } + + $request->makePost(); + if (!empty($this->_body)) { + $request->addHeader('Content-Type: '.$this->_contentType); + $request->addHeader('Content-Length: '.strlen($this->_body)); + $request->setPostBody($this->_body); + } + } + + +} +?> diff --git a/vendor/apereo/phpcas/source/CAS/ProxiedService/Imap.php b/vendor/apereo/phpcas/source/CAS/ProxiedService/Imap.php new file mode 100644 index 00000000..c4b47401 --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/ProxiedService/Imap.php @@ -0,0 +1,281 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * Provides access to a proxy-authenticated IMAP stream + * + * @class CAS_ProxiedService_Imap + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_ProxiedService_Imap +extends CAS_ProxiedService_Abstract +{ + + /** + * The username to send via imap_open. + * + * @var string $_username; + */ + private $_username; + + /** + * Constructor. + * + * @param string $username Username + * + * @return void + */ + public function __construct ($username) + { + if (!is_string($username) || !strlen($username)) { + throw new CAS_InvalidArgumentException('Invalid username.'); + } + + $this->_username = $username; + } + + /** + * The target service url. + * @var string $_url; + */ + private $_url; + + /** + * Answer a service identifier (URL) for whom we should fetch a proxy ticket. + * + * @return string + * @throws Exception If no service url is available. + */ + public function getServiceUrl () + { + if (empty($this->_url)) { + throw new CAS_ProxiedService_Exception( + 'No URL set via '.get_class($this).'->getServiceUrl($url).' + ); + } + + return $this->_url; + } + + /********************************************************* + * Configure the Stream + *********************************************************/ + + /** + * Set the URL of the service to pass to CAS for proxy-ticket retrieval. + * + * @param string $url Url to set + * + * @return void + * @throws CAS_OutOfSequenceException If called after the stream has been opened. + */ + public function setServiceUrl ($url) + { + if ($this->hasBeenOpened()) { + throw new CAS_OutOfSequenceException( + 'Cannot set the URL, stream already opened.' + ); + } + if (!is_string($url) || !strlen($url)) { + throw new CAS_InvalidArgumentException('Invalid url.'); + } + + $this->_url = $url; + } + + /** + * The mailbox to open. See the $mailbox parameter of imap_open(). + * + * @var string $_mailbox + */ + private $_mailbox; + + /** + * Set the mailbox to open. See the $mailbox parameter of imap_open(). + * + * @param string $mailbox Mailbox to set + * + * @return void + * @throws CAS_OutOfSequenceException If called after the stream has been opened. + */ + public function setMailbox ($mailbox) + { + if ($this->hasBeenOpened()) { + throw new CAS_OutOfSequenceException( + 'Cannot set the mailbox, stream already opened.' + ); + } + if (!is_string($mailbox) || !strlen($mailbox)) { + throw new CAS_InvalidArgumentException('Invalid mailbox.'); + } + + $this->_mailbox = $mailbox; + } + + /** + * A bit mask of options to pass to imap_open() as the $options parameter. + * + * @var int $_options + */ + private $_options = null; + + /** + * Set the options for opening the stream. See the $options parameter of + * imap_open(). + * + * @param int $options Options for the stream + * + * @return void + * @throws CAS_OutOfSequenceException If called after the stream has been opened. + */ + public function setOptions ($options) + { + if ($this->hasBeenOpened()) { + throw new CAS_OutOfSequenceException( + 'Cannot set options, stream already opened.' + ); + } + if (!is_int($options)) { + throw new CAS_InvalidArgumentException('Invalid options.'); + } + + $this->_options = $options; + } + + /********************************************************* + * 2. Open the stream + *********************************************************/ + + /** + * Open the IMAP stream (similar to imap_open()). + * + * @return resource Returns an IMAP stream on success + * @throws CAS_OutOfSequenceException If called multiple times. + * @throws CAS_ProxyTicketException If there is a proxy-ticket failure. + * The code of the Exception will be one of: + * PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE + * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE + * PHPCAS_SERVICE_PT_FAILURE + * @throws CAS_ProxiedService_Exception If there is a failure sending the + * request to the target service. + */ + public function open () + { + if ($this->hasBeenOpened()) { + throw new CAS_OutOfSequenceException('Stream already opened.'); + } + if (empty($this->_mailbox)) { + throw new CAS_ProxiedService_Exception( + 'You must specify a mailbox via '.get_class($this) + .'->setMailbox($mailbox)' + ); + } + + phpCAS::traceBegin(); + + // Get our proxy ticket and append it to our URL. + $this->initializeProxyTicket(); + phpCAS::trace('opening IMAP mailbox `'.$this->_mailbox.'\'...'); + $this->_stream = @imap_open( + $this->_mailbox, $this->_username, $this->getProxyTicket(), + $this->_options + ); + if ($this->_stream) { + phpCAS::trace('ok'); + } else { + phpCAS::trace('could not open mailbox'); + // @todo add localization integration. + $message = 'IMAP Error: '.$this->_url.' '. var_export(imap_errors(), true); + phpCAS::trace($message); + throw new CAS_ProxiedService_Exception($message); + } + + phpCAS::traceEnd(); + return $this->_stream; + } + + /** + * Answer true if our request has been sent yet. + * + * @return bool + */ + protected function hasBeenOpened () + { + return !empty($this->_stream); + } + + /********************************************************* + * 3. Access the result + *********************************************************/ + /** + * The IMAP stream + * + * @var resource $_stream + */ + private $_stream; + + /** + * Answer the IMAP stream + * + * @return resource + * @throws CAS_OutOfSequenceException if stream is not opened yet + */ + public function getStream () + { + if (!$this->hasBeenOpened()) { + throw new CAS_OutOfSequenceException( + 'Cannot access stream, not opened yet.' + ); + } + return $this->_stream; + } + + /** + * CAS_Client::serviceMail() needs to return the proxy ticket for some reason, + * so this method provides access to it. + * + * @return string + * @throws CAS_OutOfSequenceException If called before the stream has been + * opened. + */ + public function getImapProxyTicket () + { + if (!$this->hasBeenOpened()) { + throw new CAS_OutOfSequenceException( + 'Cannot access errors, stream not opened yet.' + ); + } + return $this->getProxyTicket(); + } +} +?> diff --git a/vendor/apereo/phpcas/source/CAS/ProxiedService/Testable.php b/vendor/apereo/phpcas/source/CAS/ProxiedService/Testable.php new file mode 100644 index 00000000..3ce44fd1 --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/ProxiedService/Testable.php @@ -0,0 +1,75 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This interface defines methods that allow proxy-authenticated service handlers + * to be tested in unit tests. + * + * Classes implementing this interface SHOULD store the CAS_Client passed and + * initialize themselves with that client rather than via the static phpCAS + * method. For example: + * + * / ** + * * Fetch our proxy ticket. + * * / + * protected function initializeProxyTicket() { + * // Allow usage of a particular CAS_Client for unit testing. + * if (is_null($this->casClient)) + * phpCAS::initializeProxiedService($this); + * else + * $this->casClient->initializeProxiedService($this); + * } + * + * @class CAS_ProxiedService_Testabel + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +interface CAS_ProxiedService_Testable +{ + + /** + * Use a particular CAS_Client->initializeProxiedService() rather than the + * static phpCAS::initializeProxiedService(). + * + * This method should not be called in standard operation, but is needed for unit + * testing. + * + * @param CAS_Client $casClient Cas client object + * + * @return void + * @throws CAS_OutOfSequenceException If called after a proxy ticket has + * already been initialized/set. + */ + public function setCasClient (CAS_Client $casClient); + +} +?> diff --git a/vendor/apereo/phpcas/source/CAS/ProxyChain.php b/vendor/apereo/phpcas/source/CAS/ProxyChain.php new file mode 100644 index 00000000..e200724c --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/ProxyChain.php @@ -0,0 +1,127 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * A normal proxy-chain definition that lists each level of the chain as either + * a string or regular expression. + * + * @class CAS_ProxyChain + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +class CAS_ProxyChain +implements CAS_ProxyChain_Interface +{ + + protected $chain = array(); + + /** + * A chain is an array of strings or regexp strings that will be matched + * against. Regexp will be matched with preg_match and strings will be + * matched from the beginning. A string must fully match the beginning of + * an proxy url. So you can define a full domain as acceptable or go further + * down. + * Proxies have to be defined in reverse from the service to the user. If a + * user hits service A get proxied via B to service C the list of acceptable + * proxies on C would be array(B,A); + * + * @param array $chain A chain of proxies + */ + public function __construct(array $chain) + { + // Ensure that we have an indexed array + $this->chain = array_values($chain); + } + + /** + * Match a list of proxies. + * + * @param array $list The list of proxies in front of this service. + * + * @return bool + */ + public function matches(array $list) + { + $list = array_values($list); // Ensure that we have an indexed array + if ($this->isSizeValid($list)) { + $mismatch = false; + foreach ($this->chain as $i => $search) { + $proxy_url = $list[$i]; + if (preg_match('/^\/.*\/[ixASUXu]*$/s', $search)) { + if (preg_match($search, $proxy_url)) { + phpCAS::trace( + "Found regexp " . $search . " matching " . $proxy_url + ); + } else { + phpCAS::trace( + "No regexp match " . $search . " != " . $proxy_url + ); + $mismatch = true; + break; + } + } else { + if (strncasecmp($search, $proxy_url, strlen($search)) == 0) { + phpCAS::trace( + "Found string " . $search . " matching " . $proxy_url + ); + } else { + phpCAS::trace( + "No match " . $search . " != " . $proxy_url + ); + $mismatch = true; + break; + } + } + } + if (!$mismatch) { + phpCAS::trace("Proxy chain matches"); + return true; + } + } else { + phpCAS::trace("Proxy chain skipped: size mismatch"); + } + return false; + } + + /** + * Validate the size of the the list as compared to our chain. + * + * @param array $list List of proxies + * + * @return bool + */ + protected function isSizeValid (array $list) + { + return (sizeof($this->chain) == sizeof($list)); + } +} diff --git a/vendor/apereo/phpcas/source/CAS/ProxyChain/AllowedList.php b/vendor/apereo/phpcas/source/CAS/ProxyChain/AllowedList.php new file mode 100644 index 00000000..988ddbb3 --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/ProxyChain/AllowedList.php @@ -0,0 +1,119 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + + +/** + * ProxyChain is a container for storing chains of valid proxies that can + * be used to validate proxied requests to a service + * + * @class CAS_ProxyChain_AllowedList + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +class CAS_ProxyChain_AllowedList +{ + + private $_chains = array(); + + /** + * Check whether proxies are allowed by configuration + * + * @return bool + */ + public function isProxyingAllowed() + { + return (count($this->_chains) > 0); + } + + /** + * Add a chain of proxies to the list of possible chains + * + * @param CAS_ProxyChain_Interface $chain A chain of proxies + * + * @return void + */ + public function allowProxyChain(CAS_ProxyChain_Interface $chain) + { + $this->_chains[] = $chain; + } + + /** + * Check if the proxies found in the response match the allowed proxies + * + * @param array $proxies list of proxies to check + * + * @return bool whether the proxies match the allowed proxies + */ + public function isProxyListAllowed(array $proxies) + { + phpCAS::traceBegin(); + if (empty($proxies)) { + phpCAS::trace("No proxies were found in the response"); + phpCAS::traceEnd(true); + return true; + } elseif (!$this->isProxyingAllowed()) { + phpCAS::trace("Proxies are not allowed"); + phpCAS::traceEnd(false); + return false; + } else { + $res = $this->contains($proxies); + phpCAS::traceEnd($res); + return $res; + } + } + + /** + * Validate the proxies from the proxy ticket validation against the + * chains that were definded. + * + * @param array $list List of proxies from the proxy ticket validation. + * + * @return bool if any chain fully matches the supplied list + */ + public function contains(array $list) + { + phpCAS::traceBegin(); + $count = 0; + foreach ($this->_chains as $chain) { + phpCAS::trace("Checking chain ". $count++); + if ($chain->matches($list)) { + phpCAS::traceEnd(true); + return true; + } + } + phpCAS::trace("No proxy chain matches."); + phpCAS::traceEnd(false); + return false; + } +} +?> diff --git a/vendor/apereo/phpcas/source/CAS/ProxyChain/Any.php b/vendor/apereo/phpcas/source/CAS/ProxyChain/Any.php new file mode 100644 index 00000000..fe18c5fb --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/ProxyChain/Any.php @@ -0,0 +1,64 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * A proxy-chain definition that will match any list of proxies. + * + * Use this class for quick testing or in certain production screnarios you + * might want to allow allow any other valid service to proxy your service. + * + * THIS CLASS IS HOWEVER NOT RECOMMENDED FOR PRODUCTION AND HAS SECURITY + * IMPLICATIONS: YOU ARE ALLOWING ANY SERVICE TO ACT ON BEHALF OF A USER + * ON THIS SERVICE. + * + * @class CAS_ProxyChain_Any + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_ProxyChain_Any +implements CAS_ProxyChain_Interface +{ + + /** + * Match a list of proxies. + * + * @param array $list The list of proxies in front of this service. + * + * @return bool + */ + public function matches(array $list) + { + phpCAS::trace("Using CAS_ProxyChain_Any. No proxy validation is performed."); + return true; + } + +} diff --git a/vendor/apereo/phpcas/source/CAS/ProxyChain/Interface.php b/vendor/apereo/phpcas/source/CAS/ProxyChain/Interface.php new file mode 100644 index 00000000..b1d68817 --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/ProxyChain/Interface.php @@ -0,0 +1,53 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * An interface for classes that define a list of allowed proxies in front of + * the current application. + * + * @class CAS_ProxyChain_Interface + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +interface CAS_ProxyChain_Interface +{ + + /** + * Match a list of proxies. + * + * @param array $list The list of proxies in front of this service. + * + * @return bool + */ + public function matches(array $list); + +} diff --git a/vendor/apereo/phpcas/source/CAS/ProxyChain/Trusted.php b/vendor/apereo/phpcas/source/CAS/ProxyChain/Trusted.php new file mode 100644 index 00000000..e67d7085 --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/ProxyChain/Trusted.php @@ -0,0 +1,59 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * A proxy-chain definition that defines a chain up to a trusted proxy and + * delegates the resposibility of validating the rest of the chain to that + * trusted proxy. + * + * @class CAS_ProxyChain_Trusted + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_ProxyChain_Trusted +extends CAS_ProxyChain +implements CAS_ProxyChain_Interface +{ + + /** + * Validate the size of the the list as compared to our chain. + * + * @param array $list list of proxies + * + * @return bool + */ + protected function isSizeValid (array $list) + { + return (sizeof($this->chain) <= sizeof($list)); + } + +} diff --git a/vendor/apereo/phpcas/source/CAS/ProxyTicketException.php b/vendor/apereo/phpcas/source/CAS/ProxyTicketException.php new file mode 100644 index 00000000..2f825b42 --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/ProxyTicketException.php @@ -0,0 +1,71 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * + */ + +/** + * An Exception for errors related to fetching or validating proxy tickets. + * + * @class CAS_ProxyTicketException + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_ProxyTicketException +extends BadMethodCallException +implements CAS_Exception +{ + + /** + * Constructor + * + * @param string $message Message text + * @param int $code Error code + * + * @return void + */ + public function __construct ($message, $code = PHPCAS_SERVICE_PT_FAILURE) + { + // Warn if the code is not in our allowed list + $ptCodes = array( + PHPCAS_SERVICE_PT_FAILURE, + PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, + PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE, + ); + if (!in_array($code, $ptCodes)) { + trigger_error( + 'Invalid code '.$code + .' passed. Must be one of PHPCAS_SERVICE_PT_FAILURE, PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, or PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE.' + ); + } + + parent::__construct($message, $code); + } +} diff --git a/vendor/apereo/phpcas/source/CAS/Request/AbstractRequest.php b/vendor/apereo/phpcas/source/CAS/Request/AbstractRequest.php new file mode 100644 index 00000000..4f9013ee --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/Request/AbstractRequest.php @@ -0,0 +1,380 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * Provides support for performing web-requests via curl + * + * @class CAS_Request_AbstractRequest + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +abstract class CAS_Request_AbstractRequest +implements CAS_Request_RequestInterface +{ + + protected $url = null; + protected $cookies = array(); + protected $headers = array(); + protected $isPost = false; + protected $postBody = null; + protected $caCertPath = null; + protected $validateCN = true; + private $_sent = false; + private $_responseHeaders = array(); + private $_responseBody = null; + private $_errorMessage = ''; + + /********************************************************* + * Configure the Request + *********************************************************/ + + /** + * Set the URL of the Request + * + * @param string $url Url to set + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function setUrl ($url) + { + if ($this->_sent) { + throw new CAS_OutOfSequenceException( + 'Request has already been sent cannot '.__METHOD__ + ); + } + + $this->url = $url; + } + + /** + * Add a cookie to the request. + * + * @param string $name Name of entry + * @param string $value value of entry + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function addCookie ($name, $value) + { + if ($this->_sent) { + throw new CAS_OutOfSequenceException( + 'Request has already been sent cannot '.__METHOD__ + ); + } + + $this->cookies[$name] = $value; + } + + /** + * Add an array of cookies to the request. + * The cookie array is of the form + * array('cookie_name' => 'cookie_value', 'cookie_name2' => cookie_value2') + * + * @param array $cookies cookies to add + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function addCookies (array $cookies) + { + if ($this->_sent) { + throw new CAS_OutOfSequenceException( + 'Request has already been sent cannot '.__METHOD__ + ); + } + + $this->cookies = array_merge($this->cookies, $cookies); + } + + /** + * Add a header string to the request. + * + * @param string $header Header to add + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function addHeader ($header) + { + if ($this->_sent) { + throw new CAS_OutOfSequenceException( + 'Request has already been sent cannot '.__METHOD__ + ); + } + + $this->headers[] = $header; + } + + /** + * Add an array of header strings to the request. + * + * @param array $headers headers to add + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function addHeaders (array $headers) + { + if ($this->_sent) { + throw new CAS_OutOfSequenceException( + 'Request has already been sent cannot '.__METHOD__ + ); + } + + $this->headers = array_merge($this->headers, $headers); + } + + /** + * Make the request a POST request rather than the default GET request. + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function makePost () + { + if ($this->_sent) { + throw new CAS_OutOfSequenceException( + 'Request has already been sent cannot '.__METHOD__ + ); + } + + $this->isPost = true; + } + + /** + * Add a POST body to the request + * + * @param string $body body to add + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function setPostBody ($body) + { + if ($this->_sent) { + throw new CAS_OutOfSequenceException( + 'Request has already been sent cannot '.__METHOD__ + ); + } + if (!$this->isPost) { + throw new CAS_OutOfSequenceException( + 'Cannot add a POST body to a GET request, use makePost() first.' + ); + } + + $this->postBody = $body; + } + + /** + * Specify the path to an SSL CA certificate to validate the server with. + * + * @param string $caCertPath path to cert + * @param bool $validate_cn valdiate CN of certificate + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function setSslCaCert ($caCertPath,$validate_cn=true) + { + if ($this->_sent) { + throw new CAS_OutOfSequenceException( + 'Request has already been sent cannot '.__METHOD__ + ); + } + $this->caCertPath = $caCertPath; + $this->validateCN = $validate_cn; + } + + /********************************************************* + * 2. Send the Request + *********************************************************/ + + /** + * Perform the request. + * + * @return bool TRUE on success, FALSE on failure. + * @throws CAS_OutOfSequenceException If called multiple times. + */ + public function send () + { + if ($this->_sent) { + throw new CAS_OutOfSequenceException( + 'Request has already been sent cannot send again.' + ); + } + if (is_null($this->url) || !$this->url) { + throw new CAS_OutOfSequenceException( + 'A url must be specified via setUrl() before the request can be sent.' + ); + } + $this->_sent = true; + return $this->sendRequest(); + } + + /** + * Send the request and store the results. + * + * @return bool TRUE on success, FALSE on failure. + */ + abstract protected function sendRequest (); + + /** + * Store the response headers. + * + * @param array $headers headers to store + * + * @return void + */ + protected function storeResponseHeaders (array $headers) + { + $this->_responseHeaders = array_merge($this->_responseHeaders, $headers); + } + + /** + * Store a single response header to our array. + * + * @param string $header header to store + * + * @return void + */ + protected function storeResponseHeader ($header) + { + $this->_responseHeaders[] = $header; + } + + /** + * Store the response body. + * + * @param string $body body to store + * + * @return void + */ + protected function storeResponseBody ($body) + { + $this->_responseBody = $body; + } + + /** + * Add a string to our error message. + * + * @param string $message message to add + * + * @return void + */ + protected function storeErrorMessage ($message) + { + $this->_errorMessage .= $message; + } + + /********************************************************* + * 3. Access the response + *********************************************************/ + + /** + * Answer the headers of the response. + * + * @return array An array of header strings. + * @throws CAS_OutOfSequenceException If called before the Request has been sent. + */ + public function getResponseHeaders () + { + if (!$this->_sent) { + throw new CAS_OutOfSequenceException( + 'Request has not been sent yet. Cannot '.__METHOD__ + ); + } + return $this->_responseHeaders; + } + + /** + * Answer HTTP status code of the response + * + * @return int + * @throws CAS_OutOfSequenceException If called before the Request has been sent. + * @throws CAS_Request_Exception if the response did not contain a status code + */ + public function getResponseStatusCode () + { + if (!$this->_sent) { + throw new CAS_OutOfSequenceException( + 'Request has not been sent yet. Cannot '.__METHOD__ + ); + } + + if (!preg_match( + '/HTTP\/[0-9.]+\s+([0-9]+)\s*(.*)/', + $this->_responseHeaders[0], $matches + ) + ) { + throw new CAS_Request_Exception( + 'Bad response, no status code was found in the first line.' + ); + } + + return intval($matches[1]); + } + + /** + * Answer the body of response. + * + * @return string + * @throws CAS_OutOfSequenceException If called before the Request has been sent. + */ + public function getResponseBody () + { + if (!$this->_sent) { + throw new CAS_OutOfSequenceException( + 'Request has not been sent yet. Cannot '.__METHOD__ + ); + } + + return $this->_responseBody; + } + + /** + * Answer a message describing any errors if the request failed. + * + * @return string + * @throws CAS_OutOfSequenceException If called before the Request has been sent. + */ + public function getErrorMessage () + { + if (!$this->_sent) { + throw new CAS_OutOfSequenceException( + 'Request has not been sent yet. Cannot '.__METHOD__ + ); + } + return $this->_errorMessage; + } +} diff --git a/vendor/apereo/phpcas/source/CAS/Request/CurlMultiRequest.php b/vendor/apereo/phpcas/source/CAS/Request/CurlMultiRequest.php new file mode 100644 index 00000000..850f6f0e --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/Request/CurlMultiRequest.php @@ -0,0 +1,147 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This interface defines a class library for performing multiple web requests + * in batches. Implementations of this interface may perform requests serially + * or in parallel. + * + * @class CAS_Request_CurlMultiRequest + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_Request_CurlMultiRequest +implements CAS_Request_MultiRequestInterface +{ + private $_requests = array(); + private $_sent = false; + + /********************************************************* + * Add Requests + *********************************************************/ + + /** + * Add a new Request to this batch. + * Note, implementations will likely restrict requests to their own concrete + * class hierarchy. + * + * @param CAS_Request_RequestInterface $request reqest to add + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + * @throws CAS_InvalidArgumentException If passed a Request of the wrong + * implmentation. + */ + public function addRequest (CAS_Request_RequestInterface $request) + { + if ($this->_sent) { + throw new CAS_OutOfSequenceException( + 'Request has already been sent cannot '.__METHOD__ + ); + } + if (!$request instanceof CAS_Request_CurlRequest) { + throw new CAS_InvalidArgumentException( + 'As a CAS_Request_CurlMultiRequest, I can only work with CAS_Request_CurlRequest objects.' + ); + } + + $this->_requests[] = $request; + } + + /** + * Retrieve the number of requests added to this batch. + * + * @return int number of request elements + * @throws CAS_OutOfSequenceException if the request has already been sent + */ + public function getNumRequests() + { + if ($this->_sent) { + throw new CAS_OutOfSequenceException( + 'Request has already been sent cannot '.__METHOD__ + ); + } + return count($this->_requests); + } + + /********************************************************* + * 2. Send the Request + *********************************************************/ + + /** + * Perform the request. After sending, all requests will have their + * responses poulated. + * + * @return bool TRUE on success, FALSE on failure. + * @throws CAS_OutOfSequenceException If called multiple times. + */ + public function send () + { + if ($this->_sent) { + throw new CAS_OutOfSequenceException( + 'Request has already been sent cannot send again.' + ); + } + if (!count($this->_requests)) { + throw new CAS_OutOfSequenceException( + 'At least one request must be added via addRequest() before the multi-request can be sent.' + ); + } + + $this->_sent = true; + + // Initialize our handles and configure all requests. + $handles = array(); + $multiHandle = curl_multi_init(); + foreach ($this->_requests as $i => $request) { + $handle = $request->initAndConfigure(); + curl_setopt($handle, CURLOPT_RETURNTRANSFER, true); + $handles[$i] = $handle; + curl_multi_add_handle($multiHandle, $handle); + } + + // Execute the requests in parallel. + do { + curl_multi_exec($multiHandle, $running); + } while ($running > 0); + + // Populate all of the responses or errors back into the request objects. + foreach ($this->_requests as $i => $request) { + $buf = curl_multi_getcontent($handles[$i]); + $request->_storeResponseBody($buf); + curl_multi_remove_handle($multiHandle, $handles[$i]); + curl_close($handles[$i]); + } + + curl_multi_close($multiHandle); + } +} diff --git a/vendor/apereo/phpcas/source/CAS/Request/CurlRequest.php b/vendor/apereo/phpcas/source/CAS/Request/CurlRequest.php new file mode 100644 index 00000000..e30dd0d1 --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/Request/CurlRequest.php @@ -0,0 +1,198 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * Provides support for performing web-requests via curl + * + * @class CAS_Request_CurlRequest + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_Request_CurlRequest +extends CAS_Request_AbstractRequest +implements CAS_Request_RequestInterface +{ + + /** + * Set additional curl options + * + * @param array $options option to set + * + * @return void + */ + public function setCurlOptions (array $options) + { + $this->_curlOptions = $options; + } + private $_curlOptions = array(); + + /** + * Send the request and store the results. + * + * @return bool true on success, false on failure. + */ + protected function sendRequest () + { + phpCAS::traceBegin(); + + /********************************************************* + * initialize the CURL session + *********************************************************/ + $ch = $this->initAndConfigure(); + + /********************************************************* + * Perform the query + *********************************************************/ + $buf = curl_exec($ch); + if ( $buf === false ) { + phpCAS::trace('curl_exec() failed'); + $this->storeErrorMessage( + 'CURL error #'.curl_errno($ch).': '.curl_error($ch) + ); + $res = false; + } else { + $this->storeResponseBody($buf); + phpCAS::trace("Response Body: \n".$buf."\n"); + $res = true; + + } + // close the CURL session + curl_close($ch); + + phpCAS::traceEnd($res); + return $res; + } + + /** + * Internal method to initialize our cURL handle and configure the request. + * This method should NOT be used outside of the CurlRequest or the + * CurlMultiRequest. + * + * @return resource|false The cURL handle on success, false on failure + */ + public function initAndConfigure() + { + /********************************************************* + * initialize the CURL session + *********************************************************/ + $ch = curl_init($this->url); + + curl_setopt_array($ch, $this->_curlOptions); + + /********************************************************* + * Set SSL configuration + *********************************************************/ + if ($this->caCertPath) { + if ($this->validateCN) { + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); + } else { + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); + } + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1); + curl_setopt($ch, CURLOPT_CAINFO, $this->caCertPath); + phpCAS::trace('CURL: Set CURLOPT_CAINFO ' . $this->caCertPath); + } else { + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); + } + + /********************************************************* + * Configure curl to capture our output. + *********************************************************/ + // return the CURL output into a variable + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + + // get the HTTP header with a callback + curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, '_curlReadHeaders')); + + /********************************************************* + * Add cookie headers to our request. + *********************************************************/ + if (count($this->cookies)) { + $cookieStrings = array(); + foreach ($this->cookies as $name => $val) { + $cookieStrings[] = $name.'='.$val; + } + curl_setopt($ch, CURLOPT_COOKIE, implode(';', $cookieStrings)); + } + + /********************************************************* + * Add any additional headers + *********************************************************/ + if (count($this->headers)) { + curl_setopt($ch, CURLOPT_HTTPHEADER, $this->headers); + } + + /********************************************************* + * Flag and Body for POST requests + *********************************************************/ + if ($this->isPost) { + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, $this->postBody); + } + + /********************************************************* + * Set User Agent + *********************************************************/ + curl_setopt($ch, CURLOPT_USERAGENT, 'phpCAS/' . phpCAS::getVersion()); + + return $ch; + } + + /** + * Store the response body. + * This method should NOT be used outside of the CurlRequest or the + * CurlMultiRequest. + * + * @param string $body body to stor + * + * @return void + */ + public function _storeResponseBody ($body) + { + $this->storeResponseBody($body); + } + + /** + * Internal method for capturing the headers from a curl request. + * + * @param resource $ch handle of curl + * @param string $header header + * + * @return int + */ + public function _curlReadHeaders ($ch, $header) + { + $this->storeResponseHeader($header); + return strlen($header); + } +} diff --git a/vendor/apereo/phpcas/source/CAS/Request/Exception.php b/vendor/apereo/phpcas/source/CAS/Request/Exception.php new file mode 100644 index 00000000..dd5a2a55 --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/Request/Exception.php @@ -0,0 +1,45 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * An Exception for problems performing requests + * + * @class CAS_Request_Exception + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_Request_Exception +extends Exception +implements CAS_Exception +{ + +} diff --git a/vendor/apereo/phpcas/source/CAS/Request/MultiRequestInterface.php b/vendor/apereo/phpcas/source/CAS/Request/MultiRequestInterface.php new file mode 100644 index 00000000..41002c77 --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/Request/MultiRequestInterface.php @@ -0,0 +1,83 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This interface defines a class library for performing multiple web requests + * in batches. Implementations of this interface may perform requests serially + * or in parallel. + * + * @class CAS_Request_MultiRequestInterface + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +interface CAS_Request_MultiRequestInterface +{ + + /********************************************************* + * Add Requests + *********************************************************/ + + /** + * Add a new Request to this batch. + * Note, implementations will likely restrict requests to their own concrete + * class hierarchy. + * + * @param CAS_Request_RequestInterface $request request interface + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been + * sent. + * @throws CAS_InvalidArgumentException If passed a Request of the wrong + * implmentation. + */ + public function addRequest (CAS_Request_RequestInterface $request); + + /** + * Retrieve the number of requests added to this batch. + * + * @return int number of request elements + */ + public function getNumRequests (); + + /********************************************************* + * 2. Send the Request + *********************************************************/ + + /** + * Perform the request. After sending, all requests will have their + * responses poulated. + * + * @return bool TRUE on success, FALSE on failure. + * @throws CAS_OutOfSequenceException If called multiple times. + */ + public function send (); +} diff --git a/vendor/apereo/phpcas/source/CAS/Request/RequestInterface.php b/vendor/apereo/phpcas/source/CAS/Request/RequestInterface.php new file mode 100644 index 00000000..b8e8772e --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/Request/RequestInterface.php @@ -0,0 +1,179 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This interface defines a class library for performing web requests. + * + * @class CAS_Request_RequestInterface + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +interface CAS_Request_RequestInterface +{ + + /********************************************************* + * Configure the Request + *********************************************************/ + + /** + * Set the URL of the Request + * + * @param string $url url to set + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function setUrl ($url); + + /** + * Add a cookie to the request. + * + * @param string $name name of cookie + * @param string $value value of cookie + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function addCookie ($name, $value); + + /** + * Add an array of cookies to the request. + * The cookie array is of the form + * array('cookie_name' => 'cookie_value', 'cookie_name2' => cookie_value2') + * + * @param array $cookies cookies to add + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function addCookies (array $cookies); + + /** + * Add a header string to the request. + * + * @param string $header header to add + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function addHeader ($header); + + /** + * Add an array of header strings to the request. + * + * @param array $headers headers to add + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function addHeaders (array $headers); + + /** + * Make the request a POST request rather than the default GET request. + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function makePost (); + + /** + * Add a POST body to the request + * + * @param string $body body to add + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function setPostBody ($body); + + + /** + * Specify the path to an SSL CA certificate to validate the server with. + * + * @param string $caCertPath path to cert file + * @param boolean $validate_cn validate CN of SSL certificate + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function setSslCaCert ($caCertPath, $validate_cn = true); + + + + /********************************************************* + * 2. Send the Request + *********************************************************/ + + /** + * Perform the request. + * + * @return bool TRUE on success, FALSE on failure. + * @throws CAS_OutOfSequenceException If called multiple times. + */ + public function send (); + + /********************************************************* + * 3. Access the response + *********************************************************/ + + /** + * Answer the headers of the response. + * + * @return array An array of header strings. + * @throws CAS_OutOfSequenceException If called before the Request has been sent. + */ + public function getResponseHeaders (); + + /** + * Answer HTTP status code of the response + * + * @return int + * @throws CAS_OutOfSequenceException If called before the Request has been sent. + */ + public function getResponseStatusCode (); + + /** + * Answer the body of response. + * + * @return string + * @throws CAS_OutOfSequenceException If called before the Request has been sent. + */ + public function getResponseBody (); + + /** + * Answer a message describing any errors if the request failed. + * + * @return string + * @throws CAS_OutOfSequenceException If called before the Request has been sent. + */ + public function getErrorMessage (); +} diff --git a/vendor/apereo/phpcas/source/CAS/ServiceBaseUrl/AllowedListDiscovery.php b/vendor/apereo/phpcas/source/CAS/ServiceBaseUrl/AllowedListDiscovery.php new file mode 100644 index 00000000..39d269c0 --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/ServiceBaseUrl/AllowedListDiscovery.php @@ -0,0 +1,152 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + + +/** + * Class that gets the service base URL of the PHP server by HTTP header + * discovery and allowlist check. This is used to generate service URL + * and PGT callback URL. + * + * @class CAS_ServiceBaseUrl_AllowedListDiscovery + * @category Authentication + * @package PhpCAS + * @author Henry Pan + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +class CAS_ServiceBaseUrl_AllowedListDiscovery +extends CAS_ServiceBaseUrl_Base +{ + private $_list = array(); + + public function __construct($list) { + if (is_array($list)) { + if (count($list) === 0) { + throw new CAS_InvalidArgumentException('$list should not be empty'); + } + foreach ($list as $value) { + $this->allow($value); + } + } else { + throw new CAS_TypeMismatchException($list, '$list', 'array'); + } + } + + /** + * Add a base URL to the allowed list. + * + * @param $url protocol, host name and port to add to the allowed list + * + * @return void + */ + public function allow($url) + { + $this->_list[] = $this->removeStandardPort($url); + } + + /** + * Check if the server name is allowed by configuration. + * + * @param $name server name to check + * + * @return bool whether the allowed list contains the server name + */ + protected function isAllowed($name) + { + return in_array($name, $this->_list); + } + + /** + * Discover the server name through HTTP headers. + * + * We read: + * - HTTP header X-Forwarded-Host + * - HTTP header X-Forwarded-Server and X-Forwarded-Port + * - HTTP header Host and SERVER_PORT + * - PHP SERVER_NAME (which can change based on the HTTP server used) + * + * The standard port will be omitted (80 for HTTP, 443 for HTTPS). + * + * @return string the discovered, unsanitized server protocol, hostname and port + */ + protected function discover() + { + $isHttps = $this->isHttps(); + $protocol = $isHttps ? 'https' : 'http'; + $protocol .= '://'; + if (!empty($_SERVER['HTTP_X_FORWARDED_HOST'])) { + // explode the host list separated by comma and use the first host + $hosts = explode(',', $_SERVER['HTTP_X_FORWARDED_HOST']); + // see rfc7239#5.3 and rfc7230#2.7.1: port is in HTTP_X_FORWARDED_HOST if non default + return $protocol . $hosts[0]; + } else if (!empty($_SERVER['HTTP_X_FORWARDED_SERVER'])) { + $server_url = $_SERVER['HTTP_X_FORWARDED_SERVER']; + } else { + if (empty($_SERVER['SERVER_NAME'])) { + $server_url = $_SERVER['HTTP_HOST']; + } else { + $server_url = $_SERVER['SERVER_NAME']; + } + } + if (!strpos($server_url, ':')) { + if (empty($_SERVER['HTTP_X_FORWARDED_PORT'])) { + $server_port = $_SERVER['SERVER_PORT']; + } else { + $ports = explode(',', $_SERVER['HTTP_X_FORWARDED_PORT']); + $server_port = $ports[0]; + } + + $server_url .= ':'; + $server_url .= $server_port; + } + return $protocol . $server_url; + } + + /** + * Get PHP server base URL. + * + * @return string the server protocol, hostname and port + */ + public function get() + { + phpCAS::traceBegin(); + $result = $this->removeStandardPort($this->discover()); + phpCAS::trace("Discovered server base URL: " . $result); + if ($this->isAllowed($result)) { + phpCAS::trace("Server base URL is allowed"); + phpCAS::traceEnd(true); + } else { + $result = $this->_list[0]; + phpCAS::trace("Server base URL is not allowed, using default: " . $result); + phpCAS::traceEnd(false); + } + return $result; + } +} diff --git a/vendor/apereo/phpcas/source/CAS/ServiceBaseUrl/Base.php b/vendor/apereo/phpcas/source/CAS/ServiceBaseUrl/Base.php new file mode 100644 index 00000000..6b4d3f30 --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/ServiceBaseUrl/Base.php @@ -0,0 +1,98 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * Base class of CAS/ServiceBaseUrl that implements isHTTPS method. + * + * @class CAS_ServiceBaseUrl_Base + * @category Authentication + * @package PhpCAS + * @author Henry Pan + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +abstract class CAS_ServiceBaseUrl_Base +implements CAS_ServiceBaseUrl_Interface +{ + + /** + * Get PHP server name. + * + * @return string the server hostname and port of the server + */ + abstract public function get(); + + /** + * Check whether HTTPS is used. + * + * This is used to construct the protocol in the URL. + * + * @return bool true if HTTPS is used + */ + public function isHttps() { + if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) { + return ($_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https'); + } elseif (!empty($_SERVER['HTTP_X_FORWARDED_PROTOCOL'])) { + return ($_SERVER['HTTP_X_FORWARDED_PROTOCOL'] === 'https'); + } elseif ( isset($_SERVER['HTTPS']) + && !empty($_SERVER['HTTPS']) + && strcasecmp($_SERVER['HTTPS'], 'off') !== 0 + ) { + return true; + } + return false; + } + + /** + * Remove standard HTTP and HTTPS port for discovery and allowlist input. + * + * @param $url URL as https://domain:port without trailing slash + * @return standardized URL, or the original URL + * @throws CAS_InvalidArgumentException if the URL does not include the protocol + */ + protected function removeStandardPort($url) { + if (strpos($url, "://") === false) { + throw new CAS_InvalidArgumentException( + "Configured base URL should include the protocol string: " . $url); + } + + $url = rtrim($url, '/'); + + if (strpos($url, "https://") === 0 && substr_compare($url, ':443', -4) === 0) { + return substr($url, 0, -4); + } + + if (strpos($url, "http://") === 0 && substr_compare($url, ':80', -3) === 0) { + return substr($url, 0, -3); + } + + return $url; + } + +} diff --git a/vendor/apereo/phpcas/source/CAS/ServiceBaseUrl/Interface.php b/vendor/apereo/phpcas/source/CAS/ServiceBaseUrl/Interface.php new file mode 100644 index 00000000..77cb2bdc --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/ServiceBaseUrl/Interface.php @@ -0,0 +1,61 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * An interface for classes that gets the server name of the PHP server. + * This is used to generate service URL and PGT callback URL. + * + * @class CAS_ServiceBaseUrl_Interface + * @category Authentication + * @package PhpCAS + * @author Henry Pan + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +interface CAS_ServiceBaseUrl_Interface +{ + + /** + * Get PHP HTTP protocol and server name. + * + * @return string protocol, server hostname, and optionally port, + * without trailing slash (https://localhost:8443) + */ + public function get(); + + /** + * Check whether HTTPS is used. + * + * This is used to construct the protocol in the URL. + * + * @return bool true if HTTPS is used + */ + public function isHttps(); + +} diff --git a/vendor/apereo/phpcas/source/CAS/ServiceBaseUrl/Static.php b/vendor/apereo/phpcas/source/CAS/ServiceBaseUrl/Static.php new file mode 100644 index 00000000..577ecb97 --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/ServiceBaseUrl/Static.php @@ -0,0 +1,69 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + + +/** + * Class that gets the server name of the PHP server by statically set + * hostname and port. This is used to generate service URL and PGT + * callback URL. + * + * @class CAS_ServiceBaseUrl_Static + * @category Authentication + * @package PhpCAS + * @author Henry Pan + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +class CAS_ServiceBaseUrl_Static +extends CAS_ServiceBaseUrl_Base +{ + private $_name = null; + + public function __construct($name) { + if (is_string($name)) { + $this->_name = $this->removeStandardPort($name); + } else { + throw new CAS_TypeMismatchException($name, '$name', 'string'); + } + } + + /** + * Get the server name through static config. + * + * @return string the server hostname and port of the server configured + */ + public function get() + { + phpCAS::traceBegin(); + phpCAS::trace("Returning static server name: " . $this->_name); + phpCAS::traceEnd(true); + return $this->_name; + } +} \ No newline at end of file diff --git a/vendor/apereo/phpcas/source/CAS/Session/PhpSession.php b/vendor/apereo/phpcas/source/CAS/Session/PhpSession.php new file mode 100644 index 00000000..031cbbc7 --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/Session/PhpSession.php @@ -0,0 +1,45 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * Empty class used as a default implementation for phpCAS. + * + * Implements the standard PHP session handler without no alterations. + * + * @class CAS_Session_PhpSession + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_Session_PhpSession extends SessionHandler implements SessionHandlerInterface +{ +} diff --git a/vendor/apereo/phpcas/source/CAS/TypeMismatchException.php b/vendor/apereo/phpcas/source/CAS/TypeMismatchException.php new file mode 100644 index 00000000..72bdc87a --- /dev/null +++ b/vendor/apereo/phpcas/source/CAS/TypeMismatchException.php @@ -0,0 +1,70 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * Exception that denotes invalid arguments were passed. + * + * @class CAS_InvalidArgumentException + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_TypeMismatchException +extends CAS_InvalidArgumentException +{ + /** + * Constructor, provides a nice message. + * + * @param mixed $argument Argument + * @param string $argumentName Argument Name + * @param string $type Type + * @param string $message Error Message + * @param integer $code Code + * + * @return void + */ + public function __construct ( + $argument, $argumentName, $type, $message = '', $code = 0 + ) { + if (is_object($argument)) { + $foundType = get_class($argument).' object'; + } else { + $foundType = gettype($argument); + } + + parent::__construct( + 'type mismatched for parameter ' + . $argumentName . ' (should be \'' . $type .' \'), ' + . $foundType . ' given. ' . $message, $code + ); + } +} +?> diff --git a/vendor/autoload.php b/vendor/autoload.php new file mode 100644 index 00000000..d96b4f3a --- /dev/null +++ b/vendor/autoload.php @@ -0,0 +1,25 @@ +realpath = realpath($opened_path) ?: $opened_path; + $opened_path = $this->realpath; + $this->handle = fopen($this->realpath, $mode); + $this->position = 0; + + return (bool) $this->handle; + } + + public function stream_read($count) + { + $data = fread($this->handle, $count); + + if ($this->position === 0) { + $data = preg_replace('{^#!.*\r?\n}', '', $data); + } + + $this->position += strlen($data); + + return $data; + } + + public function stream_cast($castAs) + { + return $this->handle; + } + + public function stream_close() + { + fclose($this->handle); + } + + public function stream_lock($operation) + { + return $operation ? flock($this->handle, $operation) : true; + } + + public function stream_seek($offset, $whence) + { + if (0 === fseek($this->handle, $offset, $whence)) { + $this->position = ftell($this->handle); + return true; + } + + return false; + } + + public function stream_tell() + { + return $this->position; + } + + public function stream_eof() + { + return feof($this->handle); + } + + public function stream_stat() + { + return array(); + } + + public function stream_set_option($option, $arg1, $arg2) + { + return true; + } + + public function url_stat($path, $flags) + { + $path = substr($path, 17); + if (file_exists($path)) { + return stat($path); + } + + return false; + } + } + } + + if ( + (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . __DIR__ . '/..'.'/friendsofphp/php-cs-fixer/php-cs-fixer'); + } +} + +return include __DIR__ . '/..'.'/friendsofphp/php-cs-fixer/php-cs-fixer'; diff --git a/vendor/bin/php-parse b/vendor/bin/php-parse new file mode 100755 index 00000000..61566e60 --- /dev/null +++ b/vendor/bin/php-parse @@ -0,0 +1,119 @@ +#!/usr/bin/env php +realpath = realpath($opened_path) ?: $opened_path; + $opened_path = $this->realpath; + $this->handle = fopen($this->realpath, $mode); + $this->position = 0; + + return (bool) $this->handle; + } + + public function stream_read($count) + { + $data = fread($this->handle, $count); + + if ($this->position === 0) { + $data = preg_replace('{^#!.*\r?\n}', '', $data); + } + + $this->position += strlen($data); + + return $data; + } + + public function stream_cast($castAs) + { + return $this->handle; + } + + public function stream_close() + { + fclose($this->handle); + } + + public function stream_lock($operation) + { + return $operation ? flock($this->handle, $operation) : true; + } + + public function stream_seek($offset, $whence) + { + if (0 === fseek($this->handle, $offset, $whence)) { + $this->position = ftell($this->handle); + return true; + } + + return false; + } + + public function stream_tell() + { + return $this->position; + } + + public function stream_eof() + { + return feof($this->handle); + } + + public function stream_stat() + { + return array(); + } + + public function stream_set_option($option, $arg1, $arg2) + { + return true; + } + + public function url_stat($path, $flags) + { + $path = substr($path, 17); + if (file_exists($path)) { + return stat($path); + } + + return false; + } + } + } + + if ( + (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . __DIR__ . '/..'.'/nikic/php-parser/bin/php-parse'); + } +} + +return include __DIR__ . '/..'.'/nikic/php-parser/bin/php-parse'; diff --git a/vendor/bin/phpunit b/vendor/bin/phpunit new file mode 100755 index 00000000..b5b530a8 --- /dev/null +++ b/vendor/bin/phpunit @@ -0,0 +1,122 @@ +#!/usr/bin/env php +realpath = realpath($opened_path) ?: $opened_path; + $opened_path = 'phpvfscomposer://'.$this->realpath; + $this->handle = fopen($this->realpath, $mode); + $this->position = 0; + + return (bool) $this->handle; + } + + public function stream_read($count) + { + $data = fread($this->handle, $count); + + if ($this->position === 0) { + $data = preg_replace('{^#!.*\r?\n}', '', $data); + } + $data = str_replace('__DIR__', var_export(dirname($this->realpath), true), $data); + $data = str_replace('__FILE__', var_export($this->realpath, true), $data); + + $this->position += strlen($data); + + return $data; + } + + public function stream_cast($castAs) + { + return $this->handle; + } + + public function stream_close() + { + fclose($this->handle); + } + + public function stream_lock($operation) + { + return $operation ? flock($this->handle, $operation) : true; + } + + public function stream_seek($offset, $whence) + { + if (0 === fseek($this->handle, $offset, $whence)) { + $this->position = ftell($this->handle); + return true; + } + + return false; + } + + public function stream_tell() + { + return $this->position; + } + + public function stream_eof() + { + return feof($this->handle); + } + + public function stream_stat() + { + return array(); + } + + public function stream_set_option($option, $arg1, $arg2) + { + return true; + } + + public function url_stat($path, $flags) + { + $path = substr($path, 17); + if (file_exists($path)) { + return stat($path); + } + + return false; + } + } + } + + if ( + (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . __DIR__ . '/..'.'/phpunit/phpunit/phpunit'); + } +} + +return include __DIR__ . '/..'.'/phpunit/phpunit/phpunit'; diff --git a/vendor/composer/ClassLoader.php b/vendor/composer/ClassLoader.php new file mode 100644 index 00000000..7824d8f7 --- /dev/null +++ b/vendor/composer/ClassLoader.php @@ -0,0 +1,579 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + /** @var \Closure(string):void */ + private static $includeFile; + + /** @var string|null */ + private $vendorDir; + + // PSR-4 + /** + * @var array> + */ + private $prefixLengthsPsr4 = array(); + /** + * @var array> + */ + private $prefixDirsPsr4 = array(); + /** + * @var list + */ + private $fallbackDirsPsr4 = array(); + + // PSR-0 + /** + * List of PSR-0 prefixes + * + * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) + * + * @var array>> + */ + private $prefixesPsr0 = array(); + /** + * @var list + */ + private $fallbackDirsPsr0 = array(); + + /** @var bool */ + private $useIncludePath = false; + + /** + * @var array + */ + private $classMap = array(); + + /** @var bool */ + private $classMapAuthoritative = false; + + /** + * @var array + */ + private $missingClasses = array(); + + /** @var string|null */ + private $apcuPrefix; + + /** + * @var array + */ + private static $registeredLoaders = array(); + + /** + * @param string|null $vendorDir + */ + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + self::initializeIncludeClosure(); + } + + /** + * @return array> + */ + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + /** + * @return array> + */ + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + /** + * @return list + */ + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + /** + * @return list + */ + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + /** + * @return array Array of classname => path + */ + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + * + * @return void + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void + */ + public function add($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 base directories + * + * @return void + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + * + * @return void + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + * + * @return void + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + * + * @return void + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + * + * @return void + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } + } + + /** + * Unregisters this instance as an autoloader. + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return true|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + $includeFile = self::$includeFile; + $includeFile($file); + + return true; + } + + return null; + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + /** + * Returns the currently registered loaders keyed by their corresponding vendor directories. + * + * @return array + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + + /** + * @param string $class + * @param string $ext + * @return string|false + */ + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } + + /** + * @return void + */ + private static function initializeIncludeClosure() + { + if (self::$includeFile !== null) { + return; + } + + /** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + */ + self::$includeFile = \Closure::bind(static function($file) { + include $file; + }, null, null); + } +} diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php new file mode 100644 index 00000000..51e734a7 --- /dev/null +++ b/vendor/composer/InstalledVersions.php @@ -0,0 +1,359 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\ClassLoader; +use Composer\Semver\VersionParser; + +/** + * This class is copied in every Composer installed project and available to all + * + * See also https://getcomposer.org/doc/07-runtime.md#installed-versions + * + * To require its presence, you can require `composer-runtime-api ^2.0` + * + * @final + */ +class InstalledVersions +{ + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null + */ + private static $installed; + + /** + * @var bool|null + */ + private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array}> + */ + private static $installedByVendor = array(); + + /** + * Returns a list of all package names which are present, either by being installed, replaced or provided + * + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackages() + { + $packages = array(); + foreach (self::getInstalled() as $installed) { + $packages[] = array_keys($installed['versions']); + } + + if (1 === \count($packages)) { + return $packages[0]; + } + + return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); + } + + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @param bool $includeDevRequirements + * @return bool + */ + public static function isInstalled($packageName, $includeDevRequirements = true) + { + foreach (self::getInstalled() as $installed) { + if (isset($installed['versions'][$packageName])) { + return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; + } + } + + return false; + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints((string) $constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + $ranges = array(); + if (isset($installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = $installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['version'])) { + return null; + } + + return $installed['versions'][$packageName]['version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getPrettyVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return $installed['versions'][$packageName]['pretty_version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['reference'])) { + return null; + } + + return $installed['versions'][$packageName]['reference']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. + */ + public static function getInstallPath($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @return array + * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} + */ + public static function getRootPackage() + { + $installed = self::getInstalled(); + + return $installed[0]['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. + * @return array[] + * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} + */ + public static function getRawData() + { + @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = include __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + + return self::$installed; + } + + /** + * Returns the raw data of all installed.php which are currently loaded for custom implementations + * + * @return array[] + * @psalm-return list}> + */ + public static function getAllRawData() + { + return self::getInstalled(); + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data + */ + public static function reload($data) + { + self::$installed = $data; + self::$installedByVendor = array(); + } + + /** + * @return array[] + * @psalm-return list}> + */ + private static function getInstalled() + { + if (null === self::$canGetVendors) { + self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); + } + + $installed = array(); + + if (self::$canGetVendors) { + foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + if (isset(self::$installedByVendor[$vendorDir])) { + $installed[] = self::$installedByVendor[$vendorDir]; + } elseif (is_file($vendorDir.'/composer/installed.php')) { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require $vendorDir.'/composer/installed.php'; + $installed[] = self::$installedByVendor[$vendorDir] = $required; + if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { + self::$installed = $installed[count($installed) - 1]; + } + } + } + } + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require __DIR__ . '/installed.php'; + self::$installed = $required; + } else { + self::$installed = array(); + } + } + + if (self::$installed !== array()) { + $installed[] = self::$installed; + } + + return $installed; + } +} diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE new file mode 100644 index 00000000..f27399a0 --- /dev/null +++ b/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php new file mode 100644 index 00000000..dc0eb0c7 --- /dev/null +++ b/vendor/composer/autoload_classmap.php @@ -0,0 +1,699 @@ + $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'CAS_AuthenticationException' => $vendorDir . '/apereo/phpcas/source/CAS/AuthenticationException.php', + 'CAS_Client' => $vendorDir . '/apereo/phpcas/source/CAS/Client.php', + 'CAS_CookieJar' => $vendorDir . '/apereo/phpcas/source/CAS/CookieJar.php', + 'CAS_Exception' => $vendorDir . '/apereo/phpcas/source/CAS/Exception.php', + 'CAS_GracefullTerminationException' => $vendorDir . '/apereo/phpcas/source/CAS/GracefullTerminationException.php', + 'CAS_InvalidArgumentException' => $vendorDir . '/apereo/phpcas/source/CAS/InvalidArgumentException.php', + 'CAS_Languages_Catalan' => $vendorDir . '/apereo/phpcas/source/CAS/Languages/Catalan.php', + 'CAS_Languages_ChineseSimplified' => $vendorDir . '/apereo/phpcas/source/CAS/Languages/ChineseSimplified.php', + 'CAS_Languages_English' => $vendorDir . '/apereo/phpcas/source/CAS/Languages/English.php', + 'CAS_Languages_French' => $vendorDir . '/apereo/phpcas/source/CAS/Languages/French.php', + 'CAS_Languages_Galego' => $vendorDir . '/apereo/phpcas/source/CAS/Languages/Galego.php', + 'CAS_Languages_German' => $vendorDir . '/apereo/phpcas/source/CAS/Languages/German.php', + 'CAS_Languages_Greek' => $vendorDir . '/apereo/phpcas/source/CAS/Languages/Greek.php', + 'CAS_Languages_Japanese' => $vendorDir . '/apereo/phpcas/source/CAS/Languages/Japanese.php', + 'CAS_Languages_LanguageInterface' => $vendorDir . '/apereo/phpcas/source/CAS/Languages/LanguageInterface.php', + 'CAS_Languages_Portuguese' => $vendorDir . '/apereo/phpcas/source/CAS/Languages/Portuguese.php', + 'CAS_Languages_Spanish' => $vendorDir . '/apereo/phpcas/source/CAS/Languages/Spanish.php', + 'CAS_OutOfSequenceBeforeAuthenticationCallException' => $vendorDir . '/apereo/phpcas/source/CAS/OutOfSequenceBeforeAuthenticationCallException.php', + 'CAS_OutOfSequenceBeforeClientException' => $vendorDir . '/apereo/phpcas/source/CAS/OutOfSequenceBeforeClientException.php', + 'CAS_OutOfSequenceBeforeProxyException' => $vendorDir . '/apereo/phpcas/source/CAS/OutOfSequenceBeforeProxyException.php', + 'CAS_OutOfSequenceException' => $vendorDir . '/apereo/phpcas/source/CAS/OutOfSequenceException.php', + 'CAS_PGTStorage_AbstractStorage' => $vendorDir . '/apereo/phpcas/source/CAS/PGTStorage/AbstractStorage.php', + 'CAS_PGTStorage_Db' => $vendorDir . '/apereo/phpcas/source/CAS/PGTStorage/Db.php', + 'CAS_PGTStorage_File' => $vendorDir . '/apereo/phpcas/source/CAS/PGTStorage/File.php', + 'CAS_ProxiedService' => $vendorDir . '/apereo/phpcas/source/CAS/ProxiedService.php', + 'CAS_ProxiedService_Abstract' => $vendorDir . '/apereo/phpcas/source/CAS/ProxiedService/Abstract.php', + 'CAS_ProxiedService_Exception' => $vendorDir . '/apereo/phpcas/source/CAS/ProxiedService/Exception.php', + 'CAS_ProxiedService_Http' => $vendorDir . '/apereo/phpcas/source/CAS/ProxiedService/Http.php', + 'CAS_ProxiedService_Http_Abstract' => $vendorDir . '/apereo/phpcas/source/CAS/ProxiedService/Http/Abstract.php', + 'CAS_ProxiedService_Http_Get' => $vendorDir . '/apereo/phpcas/source/CAS/ProxiedService/Http/Get.php', + 'CAS_ProxiedService_Http_Post' => $vendorDir . '/apereo/phpcas/source/CAS/ProxiedService/Http/Post.php', + 'CAS_ProxiedService_Imap' => $vendorDir . '/apereo/phpcas/source/CAS/ProxiedService/Imap.php', + 'CAS_ProxiedService_Testable' => $vendorDir . '/apereo/phpcas/source/CAS/ProxiedService/Testable.php', + 'CAS_ProxyChain' => $vendorDir . '/apereo/phpcas/source/CAS/ProxyChain.php', + 'CAS_ProxyChain_AllowedList' => $vendorDir . '/apereo/phpcas/source/CAS/ProxyChain/AllowedList.php', + 'CAS_ProxyChain_Any' => $vendorDir . '/apereo/phpcas/source/CAS/ProxyChain/Any.php', + 'CAS_ProxyChain_Interface' => $vendorDir . '/apereo/phpcas/source/CAS/ProxyChain/Interface.php', + 'CAS_ProxyChain_Trusted' => $vendorDir . '/apereo/phpcas/source/CAS/ProxyChain/Trusted.php', + 'CAS_ProxyTicketException' => $vendorDir . '/apereo/phpcas/source/CAS/ProxyTicketException.php', + 'CAS_Request_AbstractRequest' => $vendorDir . '/apereo/phpcas/source/CAS/Request/AbstractRequest.php', + 'CAS_Request_CurlMultiRequest' => $vendorDir . '/apereo/phpcas/source/CAS/Request/CurlMultiRequest.php', + 'CAS_Request_CurlRequest' => $vendorDir . '/apereo/phpcas/source/CAS/Request/CurlRequest.php', + 'CAS_Request_Exception' => $vendorDir . '/apereo/phpcas/source/CAS/Request/Exception.php', + 'CAS_Request_MultiRequestInterface' => $vendorDir . '/apereo/phpcas/source/CAS/Request/MultiRequestInterface.php', + 'CAS_Request_RequestInterface' => $vendorDir . '/apereo/phpcas/source/CAS/Request/RequestInterface.php', + 'CAS_ServiceBaseUrl_AllowedListDiscovery' => $vendorDir . '/apereo/phpcas/source/CAS/ServiceBaseUrl/AllowedListDiscovery.php', + 'CAS_ServiceBaseUrl_Base' => $vendorDir . '/apereo/phpcas/source/CAS/ServiceBaseUrl/Base.php', + 'CAS_ServiceBaseUrl_Interface' => $vendorDir . '/apereo/phpcas/source/CAS/ServiceBaseUrl/Interface.php', + 'CAS_ServiceBaseUrl_Static' => $vendorDir . '/apereo/phpcas/source/CAS/ServiceBaseUrl/Static.php', + 'CAS_Session_PhpSession' => $vendorDir . '/apereo/phpcas/source/CAS/Session/PhpSession.php', + 'CAS_TypeMismatchException' => $vendorDir . '/apereo/phpcas/source/CAS/TypeMismatchException.php', + 'CURLStringFile' => $vendorDir . '/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php', + 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', + 'Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php', + 'PHPUnit\\Exception' => $vendorDir . '/phpunit/phpunit/src/Exception.php', + 'PHPUnit\\Framework\\ActualValueIsNotAnObjectException' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/ActualValueIsNotAnObjectException.php', + 'PHPUnit\\Framework\\Assert' => $vendorDir . '/phpunit/phpunit/src/Framework/Assert.php', + 'PHPUnit\\Framework\\AssertionFailedError' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/AssertionFailedError.php', + 'PHPUnit\\Framework\\CodeCoverageException' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/CodeCoverageException.php', + 'PHPUnit\\Framework\\ComparisonMethodDoesNotAcceptParameterTypeException' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotAcceptParameterTypeException.php', + 'PHPUnit\\Framework\\ComparisonMethodDoesNotDeclareBoolReturnTypeException' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotDeclareBoolReturnTypeException.php', + 'PHPUnit\\Framework\\ComparisonMethodDoesNotDeclareExactlyOneParameterException' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotDeclareExactlyOneParameterException.php', + 'PHPUnit\\Framework\\ComparisonMethodDoesNotDeclareParameterTypeException' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotDeclareParameterTypeException.php', + 'PHPUnit\\Framework\\ComparisonMethodDoesNotExistException' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotExistException.php', + 'PHPUnit\\Framework\\Constraint\\ArrayHasKey' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Traversable/ArrayHasKey.php', + 'PHPUnit\\Framework\\Constraint\\BinaryOperator' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Operator/BinaryOperator.php', + 'PHPUnit\\Framework\\Constraint\\Callback' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Callback.php', + 'PHPUnit\\Framework\\Constraint\\ClassHasAttribute' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Object/ClassHasAttribute.php', + 'PHPUnit\\Framework\\Constraint\\ClassHasStaticAttribute' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Object/ClassHasStaticAttribute.php', + 'PHPUnit\\Framework\\Constraint\\Constraint' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Constraint.php', + 'PHPUnit\\Framework\\Constraint\\Count' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Cardinality/Count.php', + 'PHPUnit\\Framework\\Constraint\\DirectoryExists' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Filesystem/DirectoryExists.php', + 'PHPUnit\\Framework\\Constraint\\Exception' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Exception/Exception.php', + 'PHPUnit\\Framework\\Constraint\\ExceptionCode' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Exception/ExceptionCode.php', + 'PHPUnit\\Framework\\Constraint\\ExceptionMessage' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Exception/ExceptionMessage.php', + 'PHPUnit\\Framework\\Constraint\\ExceptionMessageRegularExpression' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Exception/ExceptionMessageRegularExpression.php', + 'PHPUnit\\Framework\\Constraint\\FileExists' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Filesystem/FileExists.php', + 'PHPUnit\\Framework\\Constraint\\GreaterThan' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Cardinality/GreaterThan.php', + 'PHPUnit\\Framework\\Constraint\\IsAnything' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsAnything.php', + 'PHPUnit\\Framework\\Constraint\\IsEmpty' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Cardinality/IsEmpty.php', + 'PHPUnit\\Framework\\Constraint\\IsEqual' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqual.php', + 'PHPUnit\\Framework\\Constraint\\IsEqualCanonicalizing' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqualCanonicalizing.php', + 'PHPUnit\\Framework\\Constraint\\IsEqualIgnoringCase' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqualIgnoringCase.php', + 'PHPUnit\\Framework\\Constraint\\IsEqualWithDelta' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqualWithDelta.php', + 'PHPUnit\\Framework\\Constraint\\IsFalse' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Boolean/IsFalse.php', + 'PHPUnit\\Framework\\Constraint\\IsFinite' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Math/IsFinite.php', + 'PHPUnit\\Framework\\Constraint\\IsIdentical' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsIdentical.php', + 'PHPUnit\\Framework\\Constraint\\IsInfinite' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Math/IsInfinite.php', + 'PHPUnit\\Framework\\Constraint\\IsInstanceOf' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Type/IsInstanceOf.php', + 'PHPUnit\\Framework\\Constraint\\IsJson' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/String/IsJson.php', + 'PHPUnit\\Framework\\Constraint\\IsNan' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Math/IsNan.php', + 'PHPUnit\\Framework\\Constraint\\IsNull' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Type/IsNull.php', + 'PHPUnit\\Framework\\Constraint\\IsReadable' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Filesystem/IsReadable.php', + 'PHPUnit\\Framework\\Constraint\\IsTrue' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Boolean/IsTrue.php', + 'PHPUnit\\Framework\\Constraint\\IsType' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Type/IsType.php', + 'PHPUnit\\Framework\\Constraint\\IsWritable' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Filesystem/IsWritable.php', + 'PHPUnit\\Framework\\Constraint\\JsonMatches' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/JsonMatches.php', + 'PHPUnit\\Framework\\Constraint\\JsonMatchesErrorMessageProvider' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/JsonMatchesErrorMessageProvider.php', + 'PHPUnit\\Framework\\Constraint\\LessThan' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Cardinality/LessThan.php', + 'PHPUnit\\Framework\\Constraint\\LogicalAnd' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Operator/LogicalAnd.php', + 'PHPUnit\\Framework\\Constraint\\LogicalNot' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Operator/LogicalNot.php', + 'PHPUnit\\Framework\\Constraint\\LogicalOr' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Operator/LogicalOr.php', + 'PHPUnit\\Framework\\Constraint\\LogicalXor' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Operator/LogicalXor.php', + 'PHPUnit\\Framework\\Constraint\\ObjectEquals' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Object/ObjectEquals.php', + 'PHPUnit\\Framework\\Constraint\\ObjectHasAttribute' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Object/ObjectHasAttribute.php', + 'PHPUnit\\Framework\\Constraint\\ObjectHasProperty' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Object/ObjectHasProperty.php', + 'PHPUnit\\Framework\\Constraint\\Operator' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Operator/Operator.php', + 'PHPUnit\\Framework\\Constraint\\RegularExpression' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/String/RegularExpression.php', + 'PHPUnit\\Framework\\Constraint\\SameSize' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Cardinality/SameSize.php', + 'PHPUnit\\Framework\\Constraint\\StringContains' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/String/StringContains.php', + 'PHPUnit\\Framework\\Constraint\\StringEndsWith' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/String/StringEndsWith.php', + 'PHPUnit\\Framework\\Constraint\\StringMatchesFormatDescription' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/String/StringMatchesFormatDescription.php', + 'PHPUnit\\Framework\\Constraint\\StringStartsWith' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/String/StringStartsWith.php', + 'PHPUnit\\Framework\\Constraint\\TraversableContains' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Traversable/TraversableContains.php', + 'PHPUnit\\Framework\\Constraint\\TraversableContainsEqual' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Traversable/TraversableContainsEqual.php', + 'PHPUnit\\Framework\\Constraint\\TraversableContainsIdentical' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Traversable/TraversableContainsIdentical.php', + 'PHPUnit\\Framework\\Constraint\\TraversableContainsOnly' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Traversable/TraversableContainsOnly.php', + 'PHPUnit\\Framework\\Constraint\\UnaryOperator' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Operator/UnaryOperator.php', + 'PHPUnit\\Framework\\CoveredCodeNotExecutedException' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/CoveredCodeNotExecutedException.php', + 'PHPUnit\\Framework\\DataProviderTestSuite' => $vendorDir . '/phpunit/phpunit/src/Framework/DataProviderTestSuite.php', + 'PHPUnit\\Framework\\Error' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/Error.php', + 'PHPUnit\\Framework\\ErrorTestCase' => $vendorDir . '/phpunit/phpunit/src/Framework/ErrorTestCase.php', + 'PHPUnit\\Framework\\Error\\Deprecated' => $vendorDir . '/phpunit/phpunit/src/Framework/Error/Deprecated.php', + 'PHPUnit\\Framework\\Error\\Error' => $vendorDir . '/phpunit/phpunit/src/Framework/Error/Error.php', + 'PHPUnit\\Framework\\Error\\Notice' => $vendorDir . '/phpunit/phpunit/src/Framework/Error/Notice.php', + 'PHPUnit\\Framework\\Error\\Warning' => $vendorDir . '/phpunit/phpunit/src/Framework/Error/Warning.php', + 'PHPUnit\\Framework\\Exception' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/Exception.php', + 'PHPUnit\\Framework\\ExceptionWrapper' => $vendorDir . '/phpunit/phpunit/src/Framework/ExceptionWrapper.php', + 'PHPUnit\\Framework\\ExecutionOrderDependency' => $vendorDir . '/phpunit/phpunit/src/Framework/ExecutionOrderDependency.php', + 'PHPUnit\\Framework\\ExpectationFailedException' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/ExpectationFailedException.php', + 'PHPUnit\\Framework\\IncompleteTest' => $vendorDir . '/phpunit/phpunit/src/Framework/IncompleteTest.php', + 'PHPUnit\\Framework\\IncompleteTestCase' => $vendorDir . '/phpunit/phpunit/src/Framework/IncompleteTestCase.php', + 'PHPUnit\\Framework\\IncompleteTestError' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/IncompleteTestError.php', + 'PHPUnit\\Framework\\InvalidArgumentException' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/InvalidArgumentException.php', + 'PHPUnit\\Framework\\InvalidCoversTargetException' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/InvalidCoversTargetException.php', + 'PHPUnit\\Framework\\InvalidDataProviderException' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/InvalidDataProviderException.php', + 'PHPUnit\\Framework\\InvalidParameterGroupException' => $vendorDir . '/phpunit/phpunit/src/Framework/InvalidParameterGroupException.php', + 'PHPUnit\\Framework\\MissingCoversAnnotationException' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/MissingCoversAnnotationException.php', + 'PHPUnit\\Framework\\MockObject\\Api' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Api/Api.php', + 'PHPUnit\\Framework\\MockObject\\BadMethodCallException' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Exception/BadMethodCallException.php', + 'PHPUnit\\Framework\\MockObject\\Builder\\Identity' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Builder/Identity.php', + 'PHPUnit\\Framework\\MockObject\\Builder\\InvocationMocker' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Builder/InvocationMocker.php', + 'PHPUnit\\Framework\\MockObject\\Builder\\InvocationStubber' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Builder/InvocationStubber.php', + 'PHPUnit\\Framework\\MockObject\\Builder\\MethodNameMatch' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Builder/MethodNameMatch.php', + 'PHPUnit\\Framework\\MockObject\\Builder\\ParametersMatch' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Builder/ParametersMatch.php', + 'PHPUnit\\Framework\\MockObject\\Builder\\Stub' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Builder/Stub.php', + 'PHPUnit\\Framework\\MockObject\\CannotUseAddMethodsException' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Exception/CannotUseAddMethodsException.php', + 'PHPUnit\\Framework\\MockObject\\CannotUseOnlyMethodsException' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Exception/CannotUseOnlyMethodsException.php', + 'PHPUnit\\Framework\\MockObject\\ClassAlreadyExistsException' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Exception/ClassAlreadyExistsException.php', + 'PHPUnit\\Framework\\MockObject\\ClassIsFinalException' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Exception/ClassIsFinalException.php', + 'PHPUnit\\Framework\\MockObject\\ClassIsReadonlyException' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Exception/ClassIsReadonlyException.php', + 'PHPUnit\\Framework\\MockObject\\ConfigurableMethod' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/ConfigurableMethod.php', + 'PHPUnit\\Framework\\MockObject\\ConfigurableMethodsAlreadyInitializedException' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Exception/ConfigurableMethodsAlreadyInitializedException.php', + 'PHPUnit\\Framework\\MockObject\\DuplicateMethodException' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Exception/DuplicateMethodException.php', + 'PHPUnit\\Framework\\MockObject\\Exception' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Exception/Exception.php', + 'PHPUnit\\Framework\\MockObject\\Generator' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Generator.php', + 'PHPUnit\\Framework\\MockObject\\IncompatibleReturnValueException' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Exception/IncompatibleReturnValueException.php', + 'PHPUnit\\Framework\\MockObject\\InvalidMethodNameException' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Exception/InvalidMethodNameException.php', + 'PHPUnit\\Framework\\MockObject\\Invocation' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Invocation.php', + 'PHPUnit\\Framework\\MockObject\\InvocationHandler' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/InvocationHandler.php', + 'PHPUnit\\Framework\\MockObject\\MatchBuilderNotFoundException' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Exception/MatchBuilderNotFoundException.php', + 'PHPUnit\\Framework\\MockObject\\Matcher' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Matcher.php', + 'PHPUnit\\Framework\\MockObject\\MatcherAlreadyRegisteredException' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Exception/MatcherAlreadyRegisteredException.php', + 'PHPUnit\\Framework\\MockObject\\Method' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Api/Method.php', + 'PHPUnit\\Framework\\MockObject\\MethodCannotBeConfiguredException' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Exception/MethodCannotBeConfiguredException.php', + 'PHPUnit\\Framework\\MockObject\\MethodNameAlreadyConfiguredException' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Exception/MethodNameAlreadyConfiguredException.php', + 'PHPUnit\\Framework\\MockObject\\MethodNameConstraint' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/MethodNameConstraint.php', + 'PHPUnit\\Framework\\MockObject\\MethodNameNotConfiguredException' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Exception/MethodNameNotConfiguredException.php', + 'PHPUnit\\Framework\\MockObject\\MethodParametersAlreadyConfiguredException' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Exception/MethodParametersAlreadyConfiguredException.php', + 'PHPUnit\\Framework\\MockObject\\MockBuilder' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/MockBuilder.php', + 'PHPUnit\\Framework\\MockObject\\MockClass' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/MockClass.php', + 'PHPUnit\\Framework\\MockObject\\MockMethod' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/MockMethod.php', + 'PHPUnit\\Framework\\MockObject\\MockMethodSet' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/MockMethodSet.php', + 'PHPUnit\\Framework\\MockObject\\MockObject' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/MockObject.php', + 'PHPUnit\\Framework\\MockObject\\MockTrait' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/MockTrait.php', + 'PHPUnit\\Framework\\MockObject\\MockType' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/MockType.php', + 'PHPUnit\\Framework\\MockObject\\OriginalConstructorInvocationRequiredException' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Exception/OriginalConstructorInvocationRequiredException.php', + 'PHPUnit\\Framework\\MockObject\\ReflectionException' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Exception/ReflectionException.php', + 'PHPUnit\\Framework\\MockObject\\ReturnValueNotConfiguredException' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Exception/ReturnValueNotConfiguredException.php', + 'PHPUnit\\Framework\\MockObject\\Rule\\AnyInvokedCount' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Rule/AnyInvokedCount.php', + 'PHPUnit\\Framework\\MockObject\\Rule\\AnyParameters' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Rule/AnyParameters.php', + 'PHPUnit\\Framework\\MockObject\\Rule\\ConsecutiveParameters' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Rule/ConsecutiveParameters.php', + 'PHPUnit\\Framework\\MockObject\\Rule\\InvocationOrder' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Rule/InvocationOrder.php', + 'PHPUnit\\Framework\\MockObject\\Rule\\InvokedAtIndex' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtIndex.php', + 'PHPUnit\\Framework\\MockObject\\Rule\\InvokedAtLeastCount' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtLeastCount.php', + 'PHPUnit\\Framework\\MockObject\\Rule\\InvokedAtLeastOnce' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtLeastOnce.php', + 'PHPUnit\\Framework\\MockObject\\Rule\\InvokedAtMostCount' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtMostCount.php', + 'PHPUnit\\Framework\\MockObject\\Rule\\InvokedCount' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedCount.php', + 'PHPUnit\\Framework\\MockObject\\Rule\\MethodName' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Rule/MethodName.php', + 'PHPUnit\\Framework\\MockObject\\Rule\\Parameters' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Rule/Parameters.php', + 'PHPUnit\\Framework\\MockObject\\Rule\\ParametersRule' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Rule/ParametersRule.php', + 'PHPUnit\\Framework\\MockObject\\RuntimeException' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Exception/RuntimeException.php', + 'PHPUnit\\Framework\\MockObject\\SoapExtensionNotAvailableException' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Exception/SoapExtensionNotAvailableException.php', + 'PHPUnit\\Framework\\MockObject\\Stub' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Stub.php', + 'PHPUnit\\Framework\\MockObject\\Stub\\ConsecutiveCalls' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Stub/ConsecutiveCalls.php', + 'PHPUnit\\Framework\\MockObject\\Stub\\Exception' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Stub/Exception.php', + 'PHPUnit\\Framework\\MockObject\\Stub\\ReturnArgument' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnArgument.php', + 'PHPUnit\\Framework\\MockObject\\Stub\\ReturnCallback' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnCallback.php', + 'PHPUnit\\Framework\\MockObject\\Stub\\ReturnReference' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnReference.php', + 'PHPUnit\\Framework\\MockObject\\Stub\\ReturnSelf' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnSelf.php', + 'PHPUnit\\Framework\\MockObject\\Stub\\ReturnStub' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnStub.php', + 'PHPUnit\\Framework\\MockObject\\Stub\\ReturnValueMap' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnValueMap.php', + 'PHPUnit\\Framework\\MockObject\\Stub\\Stub' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Stub/Stub.php', + 'PHPUnit\\Framework\\MockObject\\UnknownClassException' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Exception/UnknownClassException.php', + 'PHPUnit\\Framework\\MockObject\\UnknownTraitException' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Exception/UnknownTraitException.php', + 'PHPUnit\\Framework\\MockObject\\UnknownTypeException' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Exception/UnknownTypeException.php', + 'PHPUnit\\Framework\\MockObject\\Verifiable' => $vendorDir . '/phpunit/phpunit/src/Framework/MockObject/Verifiable.php', + 'PHPUnit\\Framework\\NoChildTestSuiteException' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/NoChildTestSuiteException.php', + 'PHPUnit\\Framework\\OutputError' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/OutputError.php', + 'PHPUnit\\Framework\\PHPTAssertionFailedError' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/PHPTAssertionFailedError.php', + 'PHPUnit\\Framework\\Reorderable' => $vendorDir . '/phpunit/phpunit/src/Framework/Reorderable.php', + 'PHPUnit\\Framework\\RiskyTestError' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/RiskyTestError.php', + 'PHPUnit\\Framework\\SelfDescribing' => $vendorDir . '/phpunit/phpunit/src/Framework/SelfDescribing.php', + 'PHPUnit\\Framework\\SkippedTest' => $vendorDir . '/phpunit/phpunit/src/Framework/SkippedTest.php', + 'PHPUnit\\Framework\\SkippedTestCase' => $vendorDir . '/phpunit/phpunit/src/Framework/SkippedTestCase.php', + 'PHPUnit\\Framework\\SkippedTestError' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/SkippedTestError.php', + 'PHPUnit\\Framework\\SkippedTestSuiteError' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/SkippedTestSuiteError.php', + 'PHPUnit\\Framework\\SyntheticError' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/SyntheticError.php', + 'PHPUnit\\Framework\\SyntheticSkippedError' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/SyntheticSkippedError.php', + 'PHPUnit\\Framework\\Test' => $vendorDir . '/phpunit/phpunit/src/Framework/Test.php', + 'PHPUnit\\Framework\\TestBuilder' => $vendorDir . '/phpunit/phpunit/src/Framework/TestBuilder.php', + 'PHPUnit\\Framework\\TestCase' => $vendorDir . '/phpunit/phpunit/src/Framework/TestCase.php', + 'PHPUnit\\Framework\\TestFailure' => $vendorDir . '/phpunit/phpunit/src/Framework/TestFailure.php', + 'PHPUnit\\Framework\\TestListener' => $vendorDir . '/phpunit/phpunit/src/Framework/TestListener.php', + 'PHPUnit\\Framework\\TestListenerDefaultImplementation' => $vendorDir . '/phpunit/phpunit/src/Framework/TestListenerDefaultImplementation.php', + 'PHPUnit\\Framework\\TestResult' => $vendorDir . '/phpunit/phpunit/src/Framework/TestResult.php', + 'PHPUnit\\Framework\\TestSuite' => $vendorDir . '/phpunit/phpunit/src/Framework/TestSuite.php', + 'PHPUnit\\Framework\\TestSuiteIterator' => $vendorDir . '/phpunit/phpunit/src/Framework/TestSuiteIterator.php', + 'PHPUnit\\Framework\\UnintentionallyCoveredCodeError' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/UnintentionallyCoveredCodeError.php', + 'PHPUnit\\Framework\\Warning' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/Warning.php', + 'PHPUnit\\Framework\\WarningTestCase' => $vendorDir . '/phpunit/phpunit/src/Framework/WarningTestCase.php', + 'PHPUnit\\Runner\\AfterIncompleteTestHook' => $vendorDir . '/phpunit/phpunit/src/Runner/Hook/AfterIncompleteTestHook.php', + 'PHPUnit\\Runner\\AfterLastTestHook' => $vendorDir . '/phpunit/phpunit/src/Runner/Hook/AfterLastTestHook.php', + 'PHPUnit\\Runner\\AfterRiskyTestHook' => $vendorDir . '/phpunit/phpunit/src/Runner/Hook/AfterRiskyTestHook.php', + 'PHPUnit\\Runner\\AfterSkippedTestHook' => $vendorDir . '/phpunit/phpunit/src/Runner/Hook/AfterSkippedTestHook.php', + 'PHPUnit\\Runner\\AfterSuccessfulTestHook' => $vendorDir . '/phpunit/phpunit/src/Runner/Hook/AfterSuccessfulTestHook.php', + 'PHPUnit\\Runner\\AfterTestErrorHook' => $vendorDir . '/phpunit/phpunit/src/Runner/Hook/AfterTestErrorHook.php', + 'PHPUnit\\Runner\\AfterTestFailureHook' => $vendorDir . '/phpunit/phpunit/src/Runner/Hook/AfterTestFailureHook.php', + 'PHPUnit\\Runner\\AfterTestHook' => $vendorDir . '/phpunit/phpunit/src/Runner/Hook/AfterTestHook.php', + 'PHPUnit\\Runner\\AfterTestWarningHook' => $vendorDir . '/phpunit/phpunit/src/Runner/Hook/AfterTestWarningHook.php', + 'PHPUnit\\Runner\\BaseTestRunner' => $vendorDir . '/phpunit/phpunit/src/Runner/BaseTestRunner.php', + 'PHPUnit\\Runner\\BeforeFirstTestHook' => $vendorDir . '/phpunit/phpunit/src/Runner/Hook/BeforeFirstTestHook.php', + 'PHPUnit\\Runner\\BeforeTestHook' => $vendorDir . '/phpunit/phpunit/src/Runner/Hook/BeforeTestHook.php', + 'PHPUnit\\Runner\\DefaultTestResultCache' => $vendorDir . '/phpunit/phpunit/src/Runner/DefaultTestResultCache.php', + 'PHPUnit\\Runner\\Exception' => $vendorDir . '/phpunit/phpunit/src/Runner/Exception.php', + 'PHPUnit\\Runner\\Extension\\ExtensionHandler' => $vendorDir . '/phpunit/phpunit/src/Runner/Extension/ExtensionHandler.php', + 'PHPUnit\\Runner\\Extension\\PharLoader' => $vendorDir . '/phpunit/phpunit/src/Runner/Extension/PharLoader.php', + 'PHPUnit\\Runner\\Filter\\ExcludeGroupFilterIterator' => $vendorDir . '/phpunit/phpunit/src/Runner/Filter/ExcludeGroupFilterIterator.php', + 'PHPUnit\\Runner\\Filter\\Factory' => $vendorDir . '/phpunit/phpunit/src/Runner/Filter/Factory.php', + 'PHPUnit\\Runner\\Filter\\GroupFilterIterator' => $vendorDir . '/phpunit/phpunit/src/Runner/Filter/GroupFilterIterator.php', + 'PHPUnit\\Runner\\Filter\\IncludeGroupFilterIterator' => $vendorDir . '/phpunit/phpunit/src/Runner/Filter/IncludeGroupFilterIterator.php', + 'PHPUnit\\Runner\\Filter\\NameFilterIterator' => $vendorDir . '/phpunit/phpunit/src/Runner/Filter/NameFilterIterator.php', + 'PHPUnit\\Runner\\Hook' => $vendorDir . '/phpunit/phpunit/src/Runner/Hook/Hook.php', + 'PHPUnit\\Runner\\NullTestResultCache' => $vendorDir . '/phpunit/phpunit/src/Runner/NullTestResultCache.php', + 'PHPUnit\\Runner\\PhptTestCase' => $vendorDir . '/phpunit/phpunit/src/Runner/PhptTestCase.php', + 'PHPUnit\\Runner\\ResultCacheExtension' => $vendorDir . '/phpunit/phpunit/src/Runner/ResultCacheExtension.php', + 'PHPUnit\\Runner\\StandardTestSuiteLoader' => $vendorDir . '/phpunit/phpunit/src/Runner/StandardTestSuiteLoader.php', + 'PHPUnit\\Runner\\TestHook' => $vendorDir . '/phpunit/phpunit/src/Runner/Hook/TestHook.php', + 'PHPUnit\\Runner\\TestListenerAdapter' => $vendorDir . '/phpunit/phpunit/src/Runner/Hook/TestListenerAdapter.php', + 'PHPUnit\\Runner\\TestResultCache' => $vendorDir . '/phpunit/phpunit/src/Runner/TestResultCache.php', + 'PHPUnit\\Runner\\TestSuiteLoader' => $vendorDir . '/phpunit/phpunit/src/Runner/TestSuiteLoader.php', + 'PHPUnit\\Runner\\TestSuiteSorter' => $vendorDir . '/phpunit/phpunit/src/Runner/TestSuiteSorter.php', + 'PHPUnit\\Runner\\Version' => $vendorDir . '/phpunit/phpunit/src/Runner/Version.php', + 'PHPUnit\\TextUI\\CliArguments\\Builder' => $vendorDir . '/phpunit/phpunit/src/TextUI/CliArguments/Builder.php', + 'PHPUnit\\TextUI\\CliArguments\\Configuration' => $vendorDir . '/phpunit/phpunit/src/TextUI/CliArguments/Configuration.php', + 'PHPUnit\\TextUI\\CliArguments\\Exception' => $vendorDir . '/phpunit/phpunit/src/TextUI/CliArguments/Exception.php', + 'PHPUnit\\TextUI\\CliArguments\\Mapper' => $vendorDir . '/phpunit/phpunit/src/TextUI/CliArguments/Mapper.php', + 'PHPUnit\\TextUI\\Command' => $vendorDir . '/phpunit/phpunit/src/TextUI/Command.php', + 'PHPUnit\\TextUI\\DefaultResultPrinter' => $vendorDir . '/phpunit/phpunit/src/TextUI/DefaultResultPrinter.php', + 'PHPUnit\\TextUI\\Exception' => $vendorDir . '/phpunit/phpunit/src/TextUI/Exception/Exception.php', + 'PHPUnit\\TextUI\\Help' => $vendorDir . '/phpunit/phpunit/src/TextUI/Help.php', + 'PHPUnit\\TextUI\\ReflectionException' => $vendorDir . '/phpunit/phpunit/src/TextUI/Exception/ReflectionException.php', + 'PHPUnit\\TextUI\\ResultPrinter' => $vendorDir . '/phpunit/phpunit/src/TextUI/ResultPrinter.php', + 'PHPUnit\\TextUI\\RuntimeException' => $vendorDir . '/phpunit/phpunit/src/TextUI/Exception/RuntimeException.php', + 'PHPUnit\\TextUI\\TestDirectoryNotFoundException' => $vendorDir . '/phpunit/phpunit/src/TextUI/Exception/TestDirectoryNotFoundException.php', + 'PHPUnit\\TextUI\\TestFileNotFoundException' => $vendorDir . '/phpunit/phpunit/src/TextUI/Exception/TestFileNotFoundException.php', + 'PHPUnit\\TextUI\\TestRunner' => $vendorDir . '/phpunit/phpunit/src/TextUI/TestRunner.php', + 'PHPUnit\\TextUI\\TestSuiteMapper' => $vendorDir . '/phpunit/phpunit/src/TextUI/TestSuiteMapper.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\CodeCoverage\\CodeCoverage' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/CodeCoverage.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\CodeCoverage\\FilterMapper' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/FilterMapper.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\CodeCoverage\\Filter\\Directory' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Filter/Directory.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\CodeCoverage\\Filter\\DirectoryCollection' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Filter/DirectoryCollection.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\CodeCoverage\\Filter\\DirectoryCollectionIterator' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Filter/DirectoryCollectionIterator.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\CodeCoverage\\Report\\Clover' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Clover.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\CodeCoverage\\Report\\Cobertura' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Cobertura.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\CodeCoverage\\Report\\Crap4j' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Crap4j.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\CodeCoverage\\Report\\Html' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Html.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\CodeCoverage\\Report\\Php' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Php.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\CodeCoverage\\Report\\Text' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Text.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\CodeCoverage\\Report\\Xml' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Xml.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Configuration' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Configuration.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Constant' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/Constant.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\ConstantCollection' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/ConstantCollection.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\ConstantCollectionIterator' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/ConstantCollectionIterator.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\ConvertLogTypes' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/ConvertLogTypes.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\CoverageCloverToReport' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageCloverToReport.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\CoverageCrap4jToReport' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageCrap4jToReport.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\CoverageHtmlToReport' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageHtmlToReport.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\CoveragePhpToReport' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoveragePhpToReport.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\CoverageTextToReport' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageTextToReport.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\CoverageXmlToReport' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageXmlToReport.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Directory' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/Directory.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\DirectoryCollection' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/DirectoryCollection.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\DirectoryCollectionIterator' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/DirectoryCollectionIterator.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Exception' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Exception.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Extension' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/PHPUnit/Extension.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\ExtensionCollection' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/PHPUnit/ExtensionCollection.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\ExtensionCollectionIterator' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/PHPUnit/ExtensionCollectionIterator.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\File' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/File.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\FileCollection' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/FileCollection.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\FileCollectionIterator' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/FileCollectionIterator.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Generator' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Generator.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Group' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Group/Group.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\GroupCollection' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Group/GroupCollection.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\GroupCollectionIterator' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Group/GroupCollectionIterator.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Groups' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Group/Groups.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\IniSetting' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/IniSetting.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\IniSettingCollection' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/IniSettingCollection.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\IniSettingCollectionIterator' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/IniSettingCollectionIterator.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\IntroduceCoverageElement' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/IntroduceCoverageElement.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Loader' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Loader.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\LogToReportMigration' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/LogToReportMigration.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Logging\\Junit' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/Junit.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Logging\\Logging' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/Logging.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Logging\\TeamCity' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/TeamCity.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Logging\\TestDox\\Html' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/TestDox/Html.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Logging\\TestDox\\Text' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/TestDox/Text.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Logging\\TestDox\\Xml' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/TestDox/Xml.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Logging\\Text' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/Text.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Migration' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/Migration.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\MigrationBuilder' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/MigrationBuilder.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\MigrationBuilderException' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/MigrationBuilderException.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\MigrationException' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/MigrationException.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Migrator' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrator.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\MoveAttributesFromFilterWhitelistToCoverage' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/MoveAttributesFromFilterWhitelistToCoverage.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\MoveAttributesFromRootToCoverage' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/MoveAttributesFromRootToCoverage.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\MoveWhitelistExcludesToCoverage' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/MoveWhitelistExcludesToCoverage.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\MoveWhitelistIncludesToCoverage' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/MoveWhitelistIncludesToCoverage.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\PHPUnit' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/PHPUnit/PHPUnit.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Php' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/Php.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\PhpHandler' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/PhpHandler.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\RemoveCacheTokensAttribute' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/RemoveCacheTokensAttribute.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\RemoveEmptyFilter' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/RemoveEmptyFilter.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\RemoveLogTypes' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/RemoveLogTypes.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\TestDirectory' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestDirectory.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\TestDirectoryCollection' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestDirectoryCollection.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\TestDirectoryCollectionIterator' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestDirectoryCollectionIterator.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\TestFile' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestFile.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\TestFileCollection' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestFileCollection.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\TestFileCollectionIterator' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestFileCollectionIterator.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\TestSuite' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestSuite.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\TestSuiteCollection' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestSuiteCollection.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\TestSuiteCollectionIterator' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestSuiteCollectionIterator.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\UpdateSchemaLocationTo93' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/UpdateSchemaLocationTo93.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Variable' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/Variable.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\VariableCollection' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/VariableCollection.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\VariableCollectionIterator' => $vendorDir . '/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/VariableCollectionIterator.php', + 'PHPUnit\\Util\\Annotation\\DocBlock' => $vendorDir . '/phpunit/phpunit/src/Util/Annotation/DocBlock.php', + 'PHPUnit\\Util\\Annotation\\Registry' => $vendorDir . '/phpunit/phpunit/src/Util/Annotation/Registry.php', + 'PHPUnit\\Util\\Blacklist' => $vendorDir . '/phpunit/phpunit/src/Util/Blacklist.php', + 'PHPUnit\\Util\\Cloner' => $vendorDir . '/phpunit/phpunit/src/Util/Cloner.php', + 'PHPUnit\\Util\\Color' => $vendorDir . '/phpunit/phpunit/src/Util/Color.php', + 'PHPUnit\\Util\\ErrorHandler' => $vendorDir . '/phpunit/phpunit/src/Util/ErrorHandler.php', + 'PHPUnit\\Util\\Exception' => $vendorDir . '/phpunit/phpunit/src/Util/Exception.php', + 'PHPUnit\\Util\\ExcludeList' => $vendorDir . '/phpunit/phpunit/src/Util/ExcludeList.php', + 'PHPUnit\\Util\\FileLoader' => $vendorDir . '/phpunit/phpunit/src/Util/FileLoader.php', + 'PHPUnit\\Util\\Filesystem' => $vendorDir . '/phpunit/phpunit/src/Util/Filesystem.php', + 'PHPUnit\\Util\\Filter' => $vendorDir . '/phpunit/phpunit/src/Util/Filter.php', + 'PHPUnit\\Util\\GlobalState' => $vendorDir . '/phpunit/phpunit/src/Util/GlobalState.php', + 'PHPUnit\\Util\\InvalidDataSetException' => $vendorDir . '/phpunit/phpunit/src/Util/InvalidDataSetException.php', + 'PHPUnit\\Util\\Json' => $vendorDir . '/phpunit/phpunit/src/Util/Json.php', + 'PHPUnit\\Util\\Log\\JUnit' => $vendorDir . '/phpunit/phpunit/src/Util/Log/JUnit.php', + 'PHPUnit\\Util\\Log\\TeamCity' => $vendorDir . '/phpunit/phpunit/src/Util/Log/TeamCity.php', + 'PHPUnit\\Util\\PHP\\AbstractPhpProcess' => $vendorDir . '/phpunit/phpunit/src/Util/PHP/AbstractPhpProcess.php', + 'PHPUnit\\Util\\PHP\\DefaultPhpProcess' => $vendorDir . '/phpunit/phpunit/src/Util/PHP/DefaultPhpProcess.php', + 'PHPUnit\\Util\\PHP\\WindowsPhpProcess' => $vendorDir . '/phpunit/phpunit/src/Util/PHP/WindowsPhpProcess.php', + 'PHPUnit\\Util\\Printer' => $vendorDir . '/phpunit/phpunit/src/Util/Printer.php', + 'PHPUnit\\Util\\Reflection' => $vendorDir . '/phpunit/phpunit/src/Util/Reflection.php', + 'PHPUnit\\Util\\RegularExpression' => $vendorDir . '/phpunit/phpunit/src/Util/RegularExpression.php', + 'PHPUnit\\Util\\Test' => $vendorDir . '/phpunit/phpunit/src/Util/Test.php', + 'PHPUnit\\Util\\TestDox\\CliTestDoxPrinter' => $vendorDir . '/phpunit/phpunit/src/Util/TestDox/CliTestDoxPrinter.php', + 'PHPUnit\\Util\\TestDox\\HtmlResultPrinter' => $vendorDir . '/phpunit/phpunit/src/Util/TestDox/HtmlResultPrinter.php', + 'PHPUnit\\Util\\TestDox\\NamePrettifier' => $vendorDir . '/phpunit/phpunit/src/Util/TestDox/NamePrettifier.php', + 'PHPUnit\\Util\\TestDox\\ResultPrinter' => $vendorDir . '/phpunit/phpunit/src/Util/TestDox/ResultPrinter.php', + 'PHPUnit\\Util\\TestDox\\TestDoxPrinter' => $vendorDir . '/phpunit/phpunit/src/Util/TestDox/TestDoxPrinter.php', + 'PHPUnit\\Util\\TestDox\\TextResultPrinter' => $vendorDir . '/phpunit/phpunit/src/Util/TestDox/TextResultPrinter.php', + 'PHPUnit\\Util\\TestDox\\XmlResultPrinter' => $vendorDir . '/phpunit/phpunit/src/Util/TestDox/XmlResultPrinter.php', + 'PHPUnit\\Util\\TextTestListRenderer' => $vendorDir . '/phpunit/phpunit/src/Util/TextTestListRenderer.php', + 'PHPUnit\\Util\\Type' => $vendorDir . '/phpunit/phpunit/src/Util/Type.php', + 'PHPUnit\\Util\\VersionComparisonOperator' => $vendorDir . '/phpunit/phpunit/src/Util/VersionComparisonOperator.php', + 'PHPUnit\\Util\\XdebugFilterScriptGenerator' => $vendorDir . '/phpunit/phpunit/src/Util/XdebugFilterScriptGenerator.php', + 'PHPUnit\\Util\\Xml' => $vendorDir . '/phpunit/phpunit/src/Util/Xml.php', + 'PHPUnit\\Util\\XmlTestListRenderer' => $vendorDir . '/phpunit/phpunit/src/Util/XmlTestListRenderer.php', + 'PHPUnit\\Util\\Xml\\Exception' => $vendorDir . '/phpunit/phpunit/src/Util/Xml/Exception.php', + 'PHPUnit\\Util\\Xml\\FailedSchemaDetectionResult' => $vendorDir . '/phpunit/phpunit/src/Util/Xml/FailedSchemaDetectionResult.php', + 'PHPUnit\\Util\\Xml\\Loader' => $vendorDir . '/phpunit/phpunit/src/Util/Xml/Loader.php', + 'PHPUnit\\Util\\Xml\\SchemaDetectionResult' => $vendorDir . '/phpunit/phpunit/src/Util/Xml/SchemaDetectionResult.php', + 'PHPUnit\\Util\\Xml\\SchemaDetector' => $vendorDir . '/phpunit/phpunit/src/Util/Xml/SchemaDetector.php', + 'PHPUnit\\Util\\Xml\\SchemaFinder' => $vendorDir . '/phpunit/phpunit/src/Util/Xml/SchemaFinder.php', + 'PHPUnit\\Util\\Xml\\SnapshotNodeList' => $vendorDir . '/phpunit/phpunit/src/Util/Xml/SnapshotNodeList.php', + 'PHPUnit\\Util\\Xml\\SuccessfulSchemaDetectionResult' => $vendorDir . '/phpunit/phpunit/src/Util/Xml/SuccessfulSchemaDetectionResult.php', + 'PHPUnit\\Util\\Xml\\ValidationResult' => $vendorDir . '/phpunit/phpunit/src/Util/Xml/ValidationResult.php', + 'PHPUnit\\Util\\Xml\\Validator' => $vendorDir . '/phpunit/phpunit/src/Util/Xml/Validator.php', + 'PharIo\\Manifest\\Application' => $vendorDir . '/phar-io/manifest/src/values/Application.php', + 'PharIo\\Manifest\\ApplicationName' => $vendorDir . '/phar-io/manifest/src/values/ApplicationName.php', + 'PharIo\\Manifest\\Author' => $vendorDir . '/phar-io/manifest/src/values/Author.php', + 'PharIo\\Manifest\\AuthorCollection' => $vendorDir . '/phar-io/manifest/src/values/AuthorCollection.php', + 'PharIo\\Manifest\\AuthorCollectionIterator' => $vendorDir . '/phar-io/manifest/src/values/AuthorCollectionIterator.php', + 'PharIo\\Manifest\\AuthorElement' => $vendorDir . '/phar-io/manifest/src/xml/AuthorElement.php', + 'PharIo\\Manifest\\AuthorElementCollection' => $vendorDir . '/phar-io/manifest/src/xml/AuthorElementCollection.php', + 'PharIo\\Manifest\\BundledComponent' => $vendorDir . '/phar-io/manifest/src/values/BundledComponent.php', + 'PharIo\\Manifest\\BundledComponentCollection' => $vendorDir . '/phar-io/manifest/src/values/BundledComponentCollection.php', + 'PharIo\\Manifest\\BundledComponentCollectionIterator' => $vendorDir . '/phar-io/manifest/src/values/BundledComponentCollectionIterator.php', + 'PharIo\\Manifest\\BundlesElement' => $vendorDir . '/phar-io/manifest/src/xml/BundlesElement.php', + 'PharIo\\Manifest\\ComponentElement' => $vendorDir . '/phar-io/manifest/src/xml/ComponentElement.php', + 'PharIo\\Manifest\\ComponentElementCollection' => $vendorDir . '/phar-io/manifest/src/xml/ComponentElementCollection.php', + 'PharIo\\Manifest\\ContainsElement' => $vendorDir . '/phar-io/manifest/src/xml/ContainsElement.php', + 'PharIo\\Manifest\\CopyrightElement' => $vendorDir . '/phar-io/manifest/src/xml/CopyrightElement.php', + 'PharIo\\Manifest\\CopyrightInformation' => $vendorDir . '/phar-io/manifest/src/values/CopyrightInformation.php', + 'PharIo\\Manifest\\ElementCollection' => $vendorDir . '/phar-io/manifest/src/xml/ElementCollection.php', + 'PharIo\\Manifest\\ElementCollectionException' => $vendorDir . '/phar-io/manifest/src/exceptions/ElementCollectionException.php', + 'PharIo\\Manifest\\Email' => $vendorDir . '/phar-io/manifest/src/values/Email.php', + 'PharIo\\Manifest\\Exception' => $vendorDir . '/phar-io/manifest/src/exceptions/Exception.php', + 'PharIo\\Manifest\\ExtElement' => $vendorDir . '/phar-io/manifest/src/xml/ExtElement.php', + 'PharIo\\Manifest\\ExtElementCollection' => $vendorDir . '/phar-io/manifest/src/xml/ExtElementCollection.php', + 'PharIo\\Manifest\\Extension' => $vendorDir . '/phar-io/manifest/src/values/Extension.php', + 'PharIo\\Manifest\\ExtensionElement' => $vendorDir . '/phar-io/manifest/src/xml/ExtensionElement.php', + 'PharIo\\Manifest\\InvalidApplicationNameException' => $vendorDir . '/phar-io/manifest/src/exceptions/InvalidApplicationNameException.php', + 'PharIo\\Manifest\\InvalidEmailException' => $vendorDir . '/phar-io/manifest/src/exceptions/InvalidEmailException.php', + 'PharIo\\Manifest\\InvalidUrlException' => $vendorDir . '/phar-io/manifest/src/exceptions/InvalidUrlException.php', + 'PharIo\\Manifest\\Library' => $vendorDir . '/phar-io/manifest/src/values/Library.php', + 'PharIo\\Manifest\\License' => $vendorDir . '/phar-io/manifest/src/values/License.php', + 'PharIo\\Manifest\\LicenseElement' => $vendorDir . '/phar-io/manifest/src/xml/LicenseElement.php', + 'PharIo\\Manifest\\Manifest' => $vendorDir . '/phar-io/manifest/src/values/Manifest.php', + 'PharIo\\Manifest\\ManifestDocument' => $vendorDir . '/phar-io/manifest/src/xml/ManifestDocument.php', + 'PharIo\\Manifest\\ManifestDocumentException' => $vendorDir . '/phar-io/manifest/src/exceptions/ManifestDocumentException.php', + 'PharIo\\Manifest\\ManifestDocumentLoadingException' => $vendorDir . '/phar-io/manifest/src/exceptions/ManifestDocumentLoadingException.php', + 'PharIo\\Manifest\\ManifestDocumentMapper' => $vendorDir . '/phar-io/manifest/src/ManifestDocumentMapper.php', + 'PharIo\\Manifest\\ManifestDocumentMapperException' => $vendorDir . '/phar-io/manifest/src/exceptions/ManifestDocumentMapperException.php', + 'PharIo\\Manifest\\ManifestElement' => $vendorDir . '/phar-io/manifest/src/xml/ManifestElement.php', + 'PharIo\\Manifest\\ManifestElementException' => $vendorDir . '/phar-io/manifest/src/exceptions/ManifestElementException.php', + 'PharIo\\Manifest\\ManifestLoader' => $vendorDir . '/phar-io/manifest/src/ManifestLoader.php', + 'PharIo\\Manifest\\ManifestLoaderException' => $vendorDir . '/phar-io/manifest/src/exceptions/ManifestLoaderException.php', + 'PharIo\\Manifest\\ManifestSerializer' => $vendorDir . '/phar-io/manifest/src/ManifestSerializer.php', + 'PharIo\\Manifest\\NoEmailAddressException' => $vendorDir . '/phar-io/manifest/src/exceptions/NoEmailAddressException.php', + 'PharIo\\Manifest\\PhpElement' => $vendorDir . '/phar-io/manifest/src/xml/PhpElement.php', + 'PharIo\\Manifest\\PhpExtensionRequirement' => $vendorDir . '/phar-io/manifest/src/values/PhpExtensionRequirement.php', + 'PharIo\\Manifest\\PhpVersionRequirement' => $vendorDir . '/phar-io/manifest/src/values/PhpVersionRequirement.php', + 'PharIo\\Manifest\\Requirement' => $vendorDir . '/phar-io/manifest/src/values/Requirement.php', + 'PharIo\\Manifest\\RequirementCollection' => $vendorDir . '/phar-io/manifest/src/values/RequirementCollection.php', + 'PharIo\\Manifest\\RequirementCollectionIterator' => $vendorDir . '/phar-io/manifest/src/values/RequirementCollectionIterator.php', + 'PharIo\\Manifest\\RequiresElement' => $vendorDir . '/phar-io/manifest/src/xml/RequiresElement.php', + 'PharIo\\Manifest\\Type' => $vendorDir . '/phar-io/manifest/src/values/Type.php', + 'PharIo\\Manifest\\Url' => $vendorDir . '/phar-io/manifest/src/values/Url.php', + 'PharIo\\Version\\AbstractVersionConstraint' => $vendorDir . '/phar-io/version/src/constraints/AbstractVersionConstraint.php', + 'PharIo\\Version\\AndVersionConstraintGroup' => $vendorDir . '/phar-io/version/src/constraints/AndVersionConstraintGroup.php', + 'PharIo\\Version\\AnyVersionConstraint' => $vendorDir . '/phar-io/version/src/constraints/AnyVersionConstraint.php', + 'PharIo\\Version\\BuildMetaData' => $vendorDir . '/phar-io/version/src/BuildMetaData.php', + 'PharIo\\Version\\ExactVersionConstraint' => $vendorDir . '/phar-io/version/src/constraints/ExactVersionConstraint.php', + 'PharIo\\Version\\Exception' => $vendorDir . '/phar-io/version/src/exceptions/Exception.php', + 'PharIo\\Version\\GreaterThanOrEqualToVersionConstraint' => $vendorDir . '/phar-io/version/src/constraints/GreaterThanOrEqualToVersionConstraint.php', + 'PharIo\\Version\\InvalidPreReleaseSuffixException' => $vendorDir . '/phar-io/version/src/exceptions/InvalidPreReleaseSuffixException.php', + 'PharIo\\Version\\InvalidVersionException' => $vendorDir . '/phar-io/version/src/exceptions/InvalidVersionException.php', + 'PharIo\\Version\\NoBuildMetaDataException' => $vendorDir . '/phar-io/version/src/exceptions/NoBuildMetaDataException.php', + 'PharIo\\Version\\NoPreReleaseSuffixException' => $vendorDir . '/phar-io/version/src/exceptions/NoPreReleaseSuffixException.php', + 'PharIo\\Version\\OrVersionConstraintGroup' => $vendorDir . '/phar-io/version/src/constraints/OrVersionConstraintGroup.php', + 'PharIo\\Version\\PreReleaseSuffix' => $vendorDir . '/phar-io/version/src/PreReleaseSuffix.php', + 'PharIo\\Version\\SpecificMajorAndMinorVersionConstraint' => $vendorDir . '/phar-io/version/src/constraints/SpecificMajorAndMinorVersionConstraint.php', + 'PharIo\\Version\\SpecificMajorVersionConstraint' => $vendorDir . '/phar-io/version/src/constraints/SpecificMajorVersionConstraint.php', + 'PharIo\\Version\\UnsupportedVersionConstraintException' => $vendorDir . '/phar-io/version/src/exceptions/UnsupportedVersionConstraintException.php', + 'PharIo\\Version\\Version' => $vendorDir . '/phar-io/version/src/Version.php', + 'PharIo\\Version\\VersionConstraint' => $vendorDir . '/phar-io/version/src/constraints/VersionConstraint.php', + 'PharIo\\Version\\VersionConstraintParser' => $vendorDir . '/phar-io/version/src/VersionConstraintParser.php', + 'PharIo\\Version\\VersionConstraintValue' => $vendorDir . '/phar-io/version/src/VersionConstraintValue.php', + 'PharIo\\Version\\VersionNumber' => $vendorDir . '/phar-io/version/src/VersionNumber.php', + 'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', + 'ReturnTypeWillChange' => $vendorDir . '/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php', + 'SebastianBergmann\\CliParser\\AmbiguousOptionException' => $vendorDir . '/sebastian/cli-parser/src/exceptions/AmbiguousOptionException.php', + 'SebastianBergmann\\CliParser\\Exception' => $vendorDir . '/sebastian/cli-parser/src/exceptions/Exception.php', + 'SebastianBergmann\\CliParser\\OptionDoesNotAllowArgumentException' => $vendorDir . '/sebastian/cli-parser/src/exceptions/OptionDoesNotAllowArgumentException.php', + 'SebastianBergmann\\CliParser\\Parser' => $vendorDir . '/sebastian/cli-parser/src/Parser.php', + 'SebastianBergmann\\CliParser\\RequiredOptionArgumentMissingException' => $vendorDir . '/sebastian/cli-parser/src/exceptions/RequiredOptionArgumentMissingException.php', + 'SebastianBergmann\\CliParser\\UnknownOptionException' => $vendorDir . '/sebastian/cli-parser/src/exceptions/UnknownOptionException.php', + 'SebastianBergmann\\CodeCoverage\\BranchAndPathCoverageNotSupportedException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/BranchAndPathCoverageNotSupportedException.php', + 'SebastianBergmann\\CodeCoverage\\CodeCoverage' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage.php', + 'SebastianBergmann\\CodeCoverage\\DeadCodeDetectionNotSupportedException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/DeadCodeDetectionNotSupportedException.php', + 'SebastianBergmann\\CodeCoverage\\Driver\\Driver' => $vendorDir . '/phpunit/php-code-coverage/src/Driver/Driver.php', + 'SebastianBergmann\\CodeCoverage\\Driver\\PathExistsButIsNotDirectoryException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/PathExistsButIsNotDirectoryException.php', + 'SebastianBergmann\\CodeCoverage\\Driver\\PcovDriver' => $vendorDir . '/phpunit/php-code-coverage/src/Driver/PcovDriver.php', + 'SebastianBergmann\\CodeCoverage\\Driver\\PcovNotAvailableException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/PcovNotAvailableException.php', + 'SebastianBergmann\\CodeCoverage\\Driver\\PhpdbgDriver' => $vendorDir . '/phpunit/php-code-coverage/src/Driver/PhpdbgDriver.php', + 'SebastianBergmann\\CodeCoverage\\Driver\\PhpdbgNotAvailableException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/PhpdbgNotAvailableException.php', + 'SebastianBergmann\\CodeCoverage\\Driver\\Selector' => $vendorDir . '/phpunit/php-code-coverage/src/Driver/Selector.php', + 'SebastianBergmann\\CodeCoverage\\Driver\\WriteOperationFailedException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/WriteOperationFailedException.php', + 'SebastianBergmann\\CodeCoverage\\Driver\\WrongXdebugVersionException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/WrongXdebugVersionException.php', + 'SebastianBergmann\\CodeCoverage\\Driver\\Xdebug2Driver' => $vendorDir . '/phpunit/php-code-coverage/src/Driver/Xdebug2Driver.php', + 'SebastianBergmann\\CodeCoverage\\Driver\\Xdebug2NotEnabledException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/Xdebug2NotEnabledException.php', + 'SebastianBergmann\\CodeCoverage\\Driver\\Xdebug3Driver' => $vendorDir . '/phpunit/php-code-coverage/src/Driver/Xdebug3Driver.php', + 'SebastianBergmann\\CodeCoverage\\Driver\\Xdebug3NotEnabledException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/Xdebug3NotEnabledException.php', + 'SebastianBergmann\\CodeCoverage\\Driver\\XdebugNotAvailableException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/XdebugNotAvailableException.php', + 'SebastianBergmann\\CodeCoverage\\Exception' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/Exception.php', + 'SebastianBergmann\\CodeCoverage\\Filter' => $vendorDir . '/phpunit/php-code-coverage/src/Filter.php', + 'SebastianBergmann\\CodeCoverage\\InvalidArgumentException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/InvalidArgumentException.php', + 'SebastianBergmann\\CodeCoverage\\NoCodeCoverageDriverAvailableException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/NoCodeCoverageDriverAvailableException.php', + 'SebastianBergmann\\CodeCoverage\\NoCodeCoverageDriverWithPathCoverageSupportAvailableException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/NoCodeCoverageDriverWithPathCoverageSupportAvailableException.php', + 'SebastianBergmann\\CodeCoverage\\Node\\AbstractNode' => $vendorDir . '/phpunit/php-code-coverage/src/Node/AbstractNode.php', + 'SebastianBergmann\\CodeCoverage\\Node\\Builder' => $vendorDir . '/phpunit/php-code-coverage/src/Node/Builder.php', + 'SebastianBergmann\\CodeCoverage\\Node\\CrapIndex' => $vendorDir . '/phpunit/php-code-coverage/src/Node/CrapIndex.php', + 'SebastianBergmann\\CodeCoverage\\Node\\Directory' => $vendorDir . '/phpunit/php-code-coverage/src/Node/Directory.php', + 'SebastianBergmann\\CodeCoverage\\Node\\File' => $vendorDir . '/phpunit/php-code-coverage/src/Node/File.php', + 'SebastianBergmann\\CodeCoverage\\Node\\Iterator' => $vendorDir . '/phpunit/php-code-coverage/src/Node/Iterator.php', + 'SebastianBergmann\\CodeCoverage\\ParserException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/ParserException.php', + 'SebastianBergmann\\CodeCoverage\\ProcessedCodeCoverageData' => $vendorDir . '/phpunit/php-code-coverage/src/ProcessedCodeCoverageData.php', + 'SebastianBergmann\\CodeCoverage\\RawCodeCoverageData' => $vendorDir . '/phpunit/php-code-coverage/src/RawCodeCoverageData.php', + 'SebastianBergmann\\CodeCoverage\\ReflectionException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/ReflectionException.php', + 'SebastianBergmann\\CodeCoverage\\ReportAlreadyFinalizedException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/ReportAlreadyFinalizedException.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Clover' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Clover.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Cobertura' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Cobertura.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Crap4j' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Crap4j.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Html\\Dashboard' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Html/Renderer/Dashboard.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Html\\Directory' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Html/Renderer/Directory.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Html\\Facade' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Html/Facade.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Html\\File' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Html/Renderer/File.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Html\\Renderer' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Html/Renderer.php', + 'SebastianBergmann\\CodeCoverage\\Report\\PHP' => $vendorDir . '/phpunit/php-code-coverage/src/Report/PHP.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Text' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Text.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\BuildInformation' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/BuildInformation.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Coverage' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Coverage.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Directory' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Directory.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Facade' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Facade.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\File' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/File.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Method' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Method.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Node' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Node.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Project' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Project.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Report' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Report.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Source' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Source.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Tests' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Tests.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Totals' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Totals.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Unit' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Unit.php', + 'SebastianBergmann\\CodeCoverage\\StaticAnalysisCacheNotConfiguredException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/StaticAnalysisCacheNotConfiguredException.php', + 'SebastianBergmann\\CodeCoverage\\StaticAnalysis\\CacheWarmer' => $vendorDir . '/phpunit/php-code-coverage/src/StaticAnalysis/CacheWarmer.php', + 'SebastianBergmann\\CodeCoverage\\StaticAnalysis\\CachingFileAnalyser' => $vendorDir . '/phpunit/php-code-coverage/src/StaticAnalysis/CachingFileAnalyser.php', + 'SebastianBergmann\\CodeCoverage\\StaticAnalysis\\CodeUnitFindingVisitor' => $vendorDir . '/phpunit/php-code-coverage/src/StaticAnalysis/CodeUnitFindingVisitor.php', + 'SebastianBergmann\\CodeCoverage\\StaticAnalysis\\ExecutableLinesFindingVisitor' => $vendorDir . '/phpunit/php-code-coverage/src/StaticAnalysis/ExecutableLinesFindingVisitor.php', + 'SebastianBergmann\\CodeCoverage\\StaticAnalysis\\FileAnalyser' => $vendorDir . '/phpunit/php-code-coverage/src/StaticAnalysis/FileAnalyser.php', + 'SebastianBergmann\\CodeCoverage\\StaticAnalysis\\IgnoredLinesFindingVisitor' => $vendorDir . '/phpunit/php-code-coverage/src/StaticAnalysis/IgnoredLinesFindingVisitor.php', + 'SebastianBergmann\\CodeCoverage\\StaticAnalysis\\ParsingFileAnalyser' => $vendorDir . '/phpunit/php-code-coverage/src/StaticAnalysis/ParsingFileAnalyser.php', + 'SebastianBergmann\\CodeCoverage\\TestIdMissingException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/TestIdMissingException.php', + 'SebastianBergmann\\CodeCoverage\\UnintentionallyCoveredCodeException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/UnintentionallyCoveredCodeException.php', + 'SebastianBergmann\\CodeCoverage\\Util\\DirectoryCouldNotBeCreatedException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/DirectoryCouldNotBeCreatedException.php', + 'SebastianBergmann\\CodeCoverage\\Util\\Filesystem' => $vendorDir . '/phpunit/php-code-coverage/src/Util/Filesystem.php', + 'SebastianBergmann\\CodeCoverage\\Util\\Percentage' => $vendorDir . '/phpunit/php-code-coverage/src/Util/Percentage.php', + 'SebastianBergmann\\CodeCoverage\\Version' => $vendorDir . '/phpunit/php-code-coverage/src/Version.php', + 'SebastianBergmann\\CodeCoverage\\XmlException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/XmlException.php', + 'SebastianBergmann\\CodeUnitReverseLookup\\Wizard' => $vendorDir . '/sebastian/code-unit-reverse-lookup/src/Wizard.php', + 'SebastianBergmann\\CodeUnit\\ClassMethodUnit' => $vendorDir . '/sebastian/code-unit/src/ClassMethodUnit.php', + 'SebastianBergmann\\CodeUnit\\ClassUnit' => $vendorDir . '/sebastian/code-unit/src/ClassUnit.php', + 'SebastianBergmann\\CodeUnit\\CodeUnit' => $vendorDir . '/sebastian/code-unit/src/CodeUnit.php', + 'SebastianBergmann\\CodeUnit\\CodeUnitCollection' => $vendorDir . '/sebastian/code-unit/src/CodeUnitCollection.php', + 'SebastianBergmann\\CodeUnit\\CodeUnitCollectionIterator' => $vendorDir . '/sebastian/code-unit/src/CodeUnitCollectionIterator.php', + 'SebastianBergmann\\CodeUnit\\Exception' => $vendorDir . '/sebastian/code-unit/src/exceptions/Exception.php', + 'SebastianBergmann\\CodeUnit\\FunctionUnit' => $vendorDir . '/sebastian/code-unit/src/FunctionUnit.php', + 'SebastianBergmann\\CodeUnit\\InterfaceMethodUnit' => $vendorDir . '/sebastian/code-unit/src/InterfaceMethodUnit.php', + 'SebastianBergmann\\CodeUnit\\InterfaceUnit' => $vendorDir . '/sebastian/code-unit/src/InterfaceUnit.php', + 'SebastianBergmann\\CodeUnit\\InvalidCodeUnitException' => $vendorDir . '/sebastian/code-unit/src/exceptions/InvalidCodeUnitException.php', + 'SebastianBergmann\\CodeUnit\\Mapper' => $vendorDir . '/sebastian/code-unit/src/Mapper.php', + 'SebastianBergmann\\CodeUnit\\NoTraitException' => $vendorDir . '/sebastian/code-unit/src/exceptions/NoTraitException.php', + 'SebastianBergmann\\CodeUnit\\ReflectionException' => $vendorDir . '/sebastian/code-unit/src/exceptions/ReflectionException.php', + 'SebastianBergmann\\CodeUnit\\TraitMethodUnit' => $vendorDir . '/sebastian/code-unit/src/TraitMethodUnit.php', + 'SebastianBergmann\\CodeUnit\\TraitUnit' => $vendorDir . '/sebastian/code-unit/src/TraitUnit.php', + 'SebastianBergmann\\Comparator\\ArrayComparator' => $vendorDir . '/sebastian/comparator/src/ArrayComparator.php', + 'SebastianBergmann\\Comparator\\Comparator' => $vendorDir . '/sebastian/comparator/src/Comparator.php', + 'SebastianBergmann\\Comparator\\ComparisonFailure' => $vendorDir . '/sebastian/comparator/src/ComparisonFailure.php', + 'SebastianBergmann\\Comparator\\DOMNodeComparator' => $vendorDir . '/sebastian/comparator/src/DOMNodeComparator.php', + 'SebastianBergmann\\Comparator\\DateTimeComparator' => $vendorDir . '/sebastian/comparator/src/DateTimeComparator.php', + 'SebastianBergmann\\Comparator\\DoubleComparator' => $vendorDir . '/sebastian/comparator/src/DoubleComparator.php', + 'SebastianBergmann\\Comparator\\Exception' => $vendorDir . '/sebastian/comparator/src/exceptions/Exception.php', + 'SebastianBergmann\\Comparator\\ExceptionComparator' => $vendorDir . '/sebastian/comparator/src/ExceptionComparator.php', + 'SebastianBergmann\\Comparator\\Factory' => $vendorDir . '/sebastian/comparator/src/Factory.php', + 'SebastianBergmann\\Comparator\\MockObjectComparator' => $vendorDir . '/sebastian/comparator/src/MockObjectComparator.php', + 'SebastianBergmann\\Comparator\\NumericComparator' => $vendorDir . '/sebastian/comparator/src/NumericComparator.php', + 'SebastianBergmann\\Comparator\\ObjectComparator' => $vendorDir . '/sebastian/comparator/src/ObjectComparator.php', + 'SebastianBergmann\\Comparator\\ResourceComparator' => $vendorDir . '/sebastian/comparator/src/ResourceComparator.php', + 'SebastianBergmann\\Comparator\\RuntimeException' => $vendorDir . '/sebastian/comparator/src/exceptions/RuntimeException.php', + 'SebastianBergmann\\Comparator\\ScalarComparator' => $vendorDir . '/sebastian/comparator/src/ScalarComparator.php', + 'SebastianBergmann\\Comparator\\SplObjectStorageComparator' => $vendorDir . '/sebastian/comparator/src/SplObjectStorageComparator.php', + 'SebastianBergmann\\Comparator\\TypeComparator' => $vendorDir . '/sebastian/comparator/src/TypeComparator.php', + 'SebastianBergmann\\Complexity\\Calculator' => $vendorDir . '/sebastian/complexity/src/Calculator.php', + 'SebastianBergmann\\Complexity\\Complexity' => $vendorDir . '/sebastian/complexity/src/Complexity/Complexity.php', + 'SebastianBergmann\\Complexity\\ComplexityCalculatingVisitor' => $vendorDir . '/sebastian/complexity/src/Visitor/ComplexityCalculatingVisitor.php', + 'SebastianBergmann\\Complexity\\ComplexityCollection' => $vendorDir . '/sebastian/complexity/src/Complexity/ComplexityCollection.php', + 'SebastianBergmann\\Complexity\\ComplexityCollectionIterator' => $vendorDir . '/sebastian/complexity/src/Complexity/ComplexityCollectionIterator.php', + 'SebastianBergmann\\Complexity\\CyclomaticComplexityCalculatingVisitor' => $vendorDir . '/sebastian/complexity/src/Visitor/CyclomaticComplexityCalculatingVisitor.php', + 'SebastianBergmann\\Complexity\\Exception' => $vendorDir . '/sebastian/complexity/src/Exception/Exception.php', + 'SebastianBergmann\\Complexity\\RuntimeException' => $vendorDir . '/sebastian/complexity/src/Exception/RuntimeException.php', + 'SebastianBergmann\\Diff\\Chunk' => $vendorDir . '/sebastian/diff/src/Chunk.php', + 'SebastianBergmann\\Diff\\ConfigurationException' => $vendorDir . '/sebastian/diff/src/Exception/ConfigurationException.php', + 'SebastianBergmann\\Diff\\Diff' => $vendorDir . '/sebastian/diff/src/Diff.php', + 'SebastianBergmann\\Diff\\Differ' => $vendorDir . '/sebastian/diff/src/Differ.php', + 'SebastianBergmann\\Diff\\Exception' => $vendorDir . '/sebastian/diff/src/Exception/Exception.php', + 'SebastianBergmann\\Diff\\InvalidArgumentException' => $vendorDir . '/sebastian/diff/src/Exception/InvalidArgumentException.php', + 'SebastianBergmann\\Diff\\Line' => $vendorDir . '/sebastian/diff/src/Line.php', + 'SebastianBergmann\\Diff\\LongestCommonSubsequenceCalculator' => $vendorDir . '/sebastian/diff/src/LongestCommonSubsequenceCalculator.php', + 'SebastianBergmann\\Diff\\MemoryEfficientLongestCommonSubsequenceCalculator' => $vendorDir . '/sebastian/diff/src/MemoryEfficientLongestCommonSubsequenceCalculator.php', + 'SebastianBergmann\\Diff\\Output\\AbstractChunkOutputBuilder' => $vendorDir . '/sebastian/diff/src/Output/AbstractChunkOutputBuilder.php', + 'SebastianBergmann\\Diff\\Output\\DiffOnlyOutputBuilder' => $vendorDir . '/sebastian/diff/src/Output/DiffOnlyOutputBuilder.php', + 'SebastianBergmann\\Diff\\Output\\DiffOutputBuilderInterface' => $vendorDir . '/sebastian/diff/src/Output/DiffOutputBuilderInterface.php', + 'SebastianBergmann\\Diff\\Output\\StrictUnifiedDiffOutputBuilder' => $vendorDir . '/sebastian/diff/src/Output/StrictUnifiedDiffOutputBuilder.php', + 'SebastianBergmann\\Diff\\Output\\UnifiedDiffOutputBuilder' => $vendorDir . '/sebastian/diff/src/Output/UnifiedDiffOutputBuilder.php', + 'SebastianBergmann\\Diff\\Parser' => $vendorDir . '/sebastian/diff/src/Parser.php', + 'SebastianBergmann\\Diff\\TimeEfficientLongestCommonSubsequenceCalculator' => $vendorDir . '/sebastian/diff/src/TimeEfficientLongestCommonSubsequenceCalculator.php', + 'SebastianBergmann\\Environment\\Console' => $vendorDir . '/sebastian/environment/src/Console.php', + 'SebastianBergmann\\Environment\\OperatingSystem' => $vendorDir . '/sebastian/environment/src/OperatingSystem.php', + 'SebastianBergmann\\Environment\\Runtime' => $vendorDir . '/sebastian/environment/src/Runtime.php', + 'SebastianBergmann\\Exporter\\Exporter' => $vendorDir . '/sebastian/exporter/src/Exporter.php', + 'SebastianBergmann\\FileIterator\\Facade' => $vendorDir . '/phpunit/php-file-iterator/src/Facade.php', + 'SebastianBergmann\\FileIterator\\Factory' => $vendorDir . '/phpunit/php-file-iterator/src/Factory.php', + 'SebastianBergmann\\FileIterator\\Iterator' => $vendorDir . '/phpunit/php-file-iterator/src/Iterator.php', + 'SebastianBergmann\\GlobalState\\CodeExporter' => $vendorDir . '/sebastian/global-state/src/CodeExporter.php', + 'SebastianBergmann\\GlobalState\\Exception' => $vendorDir . '/sebastian/global-state/src/exceptions/Exception.php', + 'SebastianBergmann\\GlobalState\\ExcludeList' => $vendorDir . '/sebastian/global-state/src/ExcludeList.php', + 'SebastianBergmann\\GlobalState\\Restorer' => $vendorDir . '/sebastian/global-state/src/Restorer.php', + 'SebastianBergmann\\GlobalState\\RuntimeException' => $vendorDir . '/sebastian/global-state/src/exceptions/RuntimeException.php', + 'SebastianBergmann\\GlobalState\\Snapshot' => $vendorDir . '/sebastian/global-state/src/Snapshot.php', + 'SebastianBergmann\\Invoker\\Exception' => $vendorDir . '/phpunit/php-invoker/src/exceptions/Exception.php', + 'SebastianBergmann\\Invoker\\Invoker' => $vendorDir . '/phpunit/php-invoker/src/Invoker.php', + 'SebastianBergmann\\Invoker\\ProcessControlExtensionNotLoadedException' => $vendorDir . '/phpunit/php-invoker/src/exceptions/ProcessControlExtensionNotLoadedException.php', + 'SebastianBergmann\\Invoker\\TimeoutException' => $vendorDir . '/phpunit/php-invoker/src/exceptions/TimeoutException.php', + 'SebastianBergmann\\LinesOfCode\\Counter' => $vendorDir . '/sebastian/lines-of-code/src/Counter.php', + 'SebastianBergmann\\LinesOfCode\\Exception' => $vendorDir . '/sebastian/lines-of-code/src/Exception/Exception.php', + 'SebastianBergmann\\LinesOfCode\\IllogicalValuesException' => $vendorDir . '/sebastian/lines-of-code/src/Exception/IllogicalValuesException.php', + 'SebastianBergmann\\LinesOfCode\\LineCountingVisitor' => $vendorDir . '/sebastian/lines-of-code/src/LineCountingVisitor.php', + 'SebastianBergmann\\LinesOfCode\\LinesOfCode' => $vendorDir . '/sebastian/lines-of-code/src/LinesOfCode.php', + 'SebastianBergmann\\LinesOfCode\\NegativeValueException' => $vendorDir . '/sebastian/lines-of-code/src/Exception/NegativeValueException.php', + 'SebastianBergmann\\LinesOfCode\\RuntimeException' => $vendorDir . '/sebastian/lines-of-code/src/Exception/RuntimeException.php', + 'SebastianBergmann\\ObjectEnumerator\\Enumerator' => $vendorDir . '/sebastian/object-enumerator/src/Enumerator.php', + 'SebastianBergmann\\ObjectEnumerator\\Exception' => $vendorDir . '/sebastian/object-enumerator/src/Exception.php', + 'SebastianBergmann\\ObjectEnumerator\\InvalidArgumentException' => $vendorDir . '/sebastian/object-enumerator/src/InvalidArgumentException.php', + 'SebastianBergmann\\ObjectReflector\\Exception' => $vendorDir . '/sebastian/object-reflector/src/Exception.php', + 'SebastianBergmann\\ObjectReflector\\InvalidArgumentException' => $vendorDir . '/sebastian/object-reflector/src/InvalidArgumentException.php', + 'SebastianBergmann\\ObjectReflector\\ObjectReflector' => $vendorDir . '/sebastian/object-reflector/src/ObjectReflector.php', + 'SebastianBergmann\\RecursionContext\\Context' => $vendorDir . '/sebastian/recursion-context/src/Context.php', + 'SebastianBergmann\\RecursionContext\\Exception' => $vendorDir . '/sebastian/recursion-context/src/Exception.php', + 'SebastianBergmann\\RecursionContext\\InvalidArgumentException' => $vendorDir . '/sebastian/recursion-context/src/InvalidArgumentException.php', + 'SebastianBergmann\\ResourceOperations\\ResourceOperations' => $vendorDir . '/sebastian/resource-operations/src/ResourceOperations.php', + 'SebastianBergmann\\Template\\Exception' => $vendorDir . '/phpunit/php-text-template/src/exceptions/Exception.php', + 'SebastianBergmann\\Template\\InvalidArgumentException' => $vendorDir . '/phpunit/php-text-template/src/exceptions/InvalidArgumentException.php', + 'SebastianBergmann\\Template\\RuntimeException' => $vendorDir . '/phpunit/php-text-template/src/exceptions/RuntimeException.php', + 'SebastianBergmann\\Template\\Template' => $vendorDir . '/phpunit/php-text-template/src/Template.php', + 'SebastianBergmann\\Timer\\Duration' => $vendorDir . '/phpunit/php-timer/src/Duration.php', + 'SebastianBergmann\\Timer\\Exception' => $vendorDir . '/phpunit/php-timer/src/exceptions/Exception.php', + 'SebastianBergmann\\Timer\\NoActiveTimerException' => $vendorDir . '/phpunit/php-timer/src/exceptions/NoActiveTimerException.php', + 'SebastianBergmann\\Timer\\ResourceUsageFormatter' => $vendorDir . '/phpunit/php-timer/src/ResourceUsageFormatter.php', + 'SebastianBergmann\\Timer\\TimeSinceStartOfRequestNotAvailableException' => $vendorDir . '/phpunit/php-timer/src/exceptions/TimeSinceStartOfRequestNotAvailableException.php', + 'SebastianBergmann\\Timer\\Timer' => $vendorDir . '/phpunit/php-timer/src/Timer.php', + 'SebastianBergmann\\Type\\CallableType' => $vendorDir . '/sebastian/type/src/type/CallableType.php', + 'SebastianBergmann\\Type\\Exception' => $vendorDir . '/sebastian/type/src/exception/Exception.php', + 'SebastianBergmann\\Type\\FalseType' => $vendorDir . '/sebastian/type/src/type/FalseType.php', + 'SebastianBergmann\\Type\\GenericObjectType' => $vendorDir . '/sebastian/type/src/type/GenericObjectType.php', + 'SebastianBergmann\\Type\\IntersectionType' => $vendorDir . '/sebastian/type/src/type/IntersectionType.php', + 'SebastianBergmann\\Type\\IterableType' => $vendorDir . '/sebastian/type/src/type/IterableType.php', + 'SebastianBergmann\\Type\\MixedType' => $vendorDir . '/sebastian/type/src/type/MixedType.php', + 'SebastianBergmann\\Type\\NeverType' => $vendorDir . '/sebastian/type/src/type/NeverType.php', + 'SebastianBergmann\\Type\\NullType' => $vendorDir . '/sebastian/type/src/type/NullType.php', + 'SebastianBergmann\\Type\\ObjectType' => $vendorDir . '/sebastian/type/src/type/ObjectType.php', + 'SebastianBergmann\\Type\\Parameter' => $vendorDir . '/sebastian/type/src/Parameter.php', + 'SebastianBergmann\\Type\\ReflectionMapper' => $vendorDir . '/sebastian/type/src/ReflectionMapper.php', + 'SebastianBergmann\\Type\\RuntimeException' => $vendorDir . '/sebastian/type/src/exception/RuntimeException.php', + 'SebastianBergmann\\Type\\SimpleType' => $vendorDir . '/sebastian/type/src/type/SimpleType.php', + 'SebastianBergmann\\Type\\StaticType' => $vendorDir . '/sebastian/type/src/type/StaticType.php', + 'SebastianBergmann\\Type\\TrueType' => $vendorDir . '/sebastian/type/src/type/TrueType.php', + 'SebastianBergmann\\Type\\Type' => $vendorDir . '/sebastian/type/src/type/Type.php', + 'SebastianBergmann\\Type\\TypeName' => $vendorDir . '/sebastian/type/src/TypeName.php', + 'SebastianBergmann\\Type\\UnionType' => $vendorDir . '/sebastian/type/src/type/UnionType.php', + 'SebastianBergmann\\Type\\UnknownType' => $vendorDir . '/sebastian/type/src/type/UnknownType.php', + 'SebastianBergmann\\Type\\VoidType' => $vendorDir . '/sebastian/type/src/type/VoidType.php', + 'SebastianBergmann\\Version' => $vendorDir . '/sebastian/version/src/Version.php', + 'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'TheSeer\\Tokenizer\\Exception' => $vendorDir . '/theseer/tokenizer/src/Exception.php', + 'TheSeer\\Tokenizer\\NamespaceUri' => $vendorDir . '/theseer/tokenizer/src/NamespaceUri.php', + 'TheSeer\\Tokenizer\\NamespaceUriException' => $vendorDir . '/theseer/tokenizer/src/NamespaceUriException.php', + 'TheSeer\\Tokenizer\\Token' => $vendorDir . '/theseer/tokenizer/src/Token.php', + 'TheSeer\\Tokenizer\\TokenCollection' => $vendorDir . '/theseer/tokenizer/src/TokenCollection.php', + 'TheSeer\\Tokenizer\\TokenCollectionException' => $vendorDir . '/theseer/tokenizer/src/TokenCollectionException.php', + 'TheSeer\\Tokenizer\\Tokenizer' => $vendorDir . '/theseer/tokenizer/src/Tokenizer.php', + 'TheSeer\\Tokenizer\\XMLSerializer' => $vendorDir . '/theseer/tokenizer/src/XMLSerializer.php', + 'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', + 'phpCAS' => $vendorDir . '/apereo/phpcas/source/CAS.php', +); diff --git a/vendor/composer/autoload_files.php b/vendor/composer/autoload_files.php new file mode 100644 index 00000000..44b53f16 --- /dev/null +++ b/vendor/composer/autoload_files.php @@ -0,0 +1,22 @@ + $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', + '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', + '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php', + '8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php', + 'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php', + 'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php', + 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', + '23c18046f52bef3eea034657bafda50f' => $vendorDir . '/symfony/polyfill-php81/bootstrap.php', + '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php', + '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php', + '6124b4c8570aa390c21fafd04a26c69f' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php', + '344f11dc3484aaed5cbde58e23513be4' => $vendorDir . '/apereo/phpcas/source/CAS.php', + 'ec07570ca5a812141189b1fa81503674' => $vendorDir . '/phpunit/phpunit/src/Framework/Assert/Functions.php', +); diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php new file mode 100644 index 00000000..15a2ff3a --- /dev/null +++ b/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($vendorDir . '/symfony/polyfill-php81'), + 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'), + 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), + 'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'), + 'Symfony\\Polyfill\\Intl\\Grapheme\\' => array($vendorDir . '/symfony/polyfill-intl-grapheme'), + 'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'), + 'Symfony\\Contracts\\Service\\' => array($vendorDir . '/symfony/service-contracts'), + 'Symfony\\Contracts\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher-contracts'), + 'Symfony\\Component\\String\\' => array($vendorDir . '/symfony/string'), + 'Symfony\\Component\\Stopwatch\\' => array($vendorDir . '/symfony/stopwatch'), + 'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'), + 'Symfony\\Component\\OptionsResolver\\' => array($vendorDir . '/symfony/options-resolver'), + 'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'), + 'Symfony\\Component\\Filesystem\\' => array($vendorDir . '/symfony/filesystem'), + 'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'), + 'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'), + 'RobRichards\\XMLSecLibs\\' => array($vendorDir . '/robrichards/xmlseclibs/src'), + 'Redaxo\\PhpCsFixerConfig\\' => array($vendorDir . '/redaxo/php-cs-fixer-config/src'), + 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src'), + 'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'), + 'Psr\\EventDispatcher\\' => array($vendorDir . '/psr/event-dispatcher/src'), + 'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'), + 'PhpCsFixer\\' => array($vendorDir . '/friendsofphp/php-cs-fixer/src'), + 'PhpCsFixerCustomFixers\\' => array($vendorDir . '/kubawerlos/php-cs-fixer-custom-fixers/src'), + 'OneLogin\\' => array($vendorDir . '/onelogin/php-saml/src'), + 'League\\OAuth2\\Client\\' => array($vendorDir . '/league/oauth2-client/src'), + 'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'), + 'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'), + 'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'), + 'Doctrine\\Instantiator\\' => array($vendorDir . '/doctrine/instantiator/src/Doctrine/Instantiator'), + 'DeepCopy\\' => array($vendorDir . '/myclabs/deep-copy/src/DeepCopy'), + 'Composer\\XdebugHandler\\' => array($vendorDir . '/composer/xdebug-handler/src'), + 'Composer\\Semver\\' => array($vendorDir . '/composer/semver/src'), + 'Composer\\Pcre\\' => array($vendorDir . '/composer/pcre/src'), +); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php new file mode 100644 index 00000000..f3e5e0b4 --- /dev/null +++ b/vendor/composer/autoload_real.php @@ -0,0 +1,50 @@ +register(true); + + $filesToLoad = \Composer\Autoload\ComposerStaticInitafdf4419c977e377e00fce23bd5a0744::$files; + $requireFile = \Closure::bind(static function ($fileIdentifier, $file) { + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + + require $file; + } + }, null, null); + foreach ($filesToLoad as $fileIdentifier => $file) { + $requireFile($fileIdentifier, $file); + } + + return $loader; + } +} diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php new file mode 100644 index 00000000..a27bed0e --- /dev/null +++ b/vendor/composer/autoload_static.php @@ -0,0 +1,927 @@ + __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', + '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', + '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php', + '8825ede83f2f289127722d4e842cf7e8' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/bootstrap.php', + 'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php', + 'b6b991a57620e2fb6b2f66f03fe9ddc2' => __DIR__ . '/..' . '/symfony/string/Resources/functions.php', + 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', + '23c18046f52bef3eea034657bafda50f' => __DIR__ . '/..' . '/symfony/polyfill-php81/bootstrap.php', + '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php', + '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php', + '6124b4c8570aa390c21fafd04a26c69f' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php', + '344f11dc3484aaed5cbde58e23513be4' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS.php', + 'ec07570ca5a812141189b1fa81503674' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Assert/Functions.php', + ); + + public static $prefixLengthsPsr4 = array ( + 'S' => + array ( + 'Symfony\\Polyfill\\Php81\\' => 23, + 'Symfony\\Polyfill\\Php80\\' => 23, + 'Symfony\\Polyfill\\Mbstring\\' => 26, + 'Symfony\\Polyfill\\Intl\\Normalizer\\' => 33, + 'Symfony\\Polyfill\\Intl\\Grapheme\\' => 31, + 'Symfony\\Polyfill\\Ctype\\' => 23, + 'Symfony\\Contracts\\Service\\' => 26, + 'Symfony\\Contracts\\EventDispatcher\\' => 34, + 'Symfony\\Component\\String\\' => 25, + 'Symfony\\Component\\Stopwatch\\' => 28, + 'Symfony\\Component\\Process\\' => 26, + 'Symfony\\Component\\OptionsResolver\\' => 34, + 'Symfony\\Component\\Finder\\' => 25, + 'Symfony\\Component\\Filesystem\\' => 29, + 'Symfony\\Component\\EventDispatcher\\' => 34, + 'Symfony\\Component\\Console\\' => 26, + ), + 'R' => + array ( + 'RobRichards\\XMLSecLibs\\' => 23, + 'Redaxo\\PhpCsFixerConfig\\' => 24, + ), + 'P' => + array ( + 'Psr\\Http\\Message\\' => 17, + 'Psr\\Http\\Client\\' => 16, + 'Psr\\EventDispatcher\\' => 20, + 'PhpParser\\' => 10, + 'PhpCsFixer\\' => 11, + 'PhpCsFixerCustomFixers\\' => 23, + ), + 'O' => + array ( + 'OneLogin\\' => 9, + ), + 'L' => + array ( + 'League\\OAuth2\\Client\\' => 21, + ), + 'G' => + array ( + 'GuzzleHttp\\Psr7\\' => 16, + 'GuzzleHttp\\Promise\\' => 19, + 'GuzzleHttp\\' => 11, + ), + 'D' => + array ( + 'Doctrine\\Instantiator\\' => 22, + 'DeepCopy\\' => 9, + ), + 'C' => + array ( + 'Composer\\XdebugHandler\\' => 23, + 'Composer\\Semver\\' => 16, + 'Composer\\Pcre\\' => 14, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Symfony\\Polyfill\\Php81\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php81', + ), + 'Symfony\\Polyfill\\Php80\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php80', + ), + 'Symfony\\Polyfill\\Mbstring\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', + ), + 'Symfony\\Polyfill\\Intl\\Normalizer\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer', + ), + 'Symfony\\Polyfill\\Intl\\Grapheme\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme', + ), + 'Symfony\\Polyfill\\Ctype\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-ctype', + ), + 'Symfony\\Contracts\\Service\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/service-contracts', + ), + 'Symfony\\Contracts\\EventDispatcher\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/event-dispatcher-contracts', + ), + 'Symfony\\Component\\String\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/string', + ), + 'Symfony\\Component\\Stopwatch\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/stopwatch', + ), + 'Symfony\\Component\\Process\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/process', + ), + 'Symfony\\Component\\OptionsResolver\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/options-resolver', + ), + 'Symfony\\Component\\Finder\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/finder', + ), + 'Symfony\\Component\\Filesystem\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/filesystem', + ), + 'Symfony\\Component\\EventDispatcher\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/event-dispatcher', + ), + 'Symfony\\Component\\Console\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/console', + ), + 'RobRichards\\XMLSecLibs\\' => + array ( + 0 => __DIR__ . '/..' . '/robrichards/xmlseclibs/src', + ), + 'Redaxo\\PhpCsFixerConfig\\' => + array ( + 0 => __DIR__ . '/..' . '/redaxo/php-cs-fixer-config/src', + ), + 'Psr\\Http\\Message\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/http-factory/src', + ), + 'Psr\\Http\\Client\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/http-client/src', + ), + 'Psr\\EventDispatcher\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/event-dispatcher/src', + ), + 'PhpParser\\' => + array ( + 0 => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser', + ), + 'PhpCsFixer\\' => + array ( + 0 => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src', + ), + 'PhpCsFixerCustomFixers\\' => + array ( + 0 => __DIR__ . '/..' . '/kubawerlos/php-cs-fixer-custom-fixers/src', + ), + 'OneLogin\\' => + array ( + 0 => __DIR__ . '/..' . '/onelogin/php-saml/src', + ), + 'League\\OAuth2\\Client\\' => + array ( + 0 => __DIR__ . '/..' . '/league/oauth2-client/src', + ), + 'GuzzleHttp\\Psr7\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src', + ), + 'GuzzleHttp\\Promise\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/promises/src', + ), + 'GuzzleHttp\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/guzzle/src', + ), + 'Doctrine\\Instantiator\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/instantiator/src/Doctrine/Instantiator', + ), + 'DeepCopy\\' => + array ( + 0 => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy', + ), + 'Composer\\XdebugHandler\\' => + array ( + 0 => __DIR__ . '/..' . '/composer/xdebug-handler/src', + ), + 'Composer\\Semver\\' => + array ( + 0 => __DIR__ . '/..' . '/composer/semver/src', + ), + 'Composer\\Pcre\\' => + array ( + 0 => __DIR__ . '/..' . '/composer/pcre/src', + ), + ); + + public static $classMap = array ( + 'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'CAS_AuthenticationException' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/AuthenticationException.php', + 'CAS_Client' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Client.php', + 'CAS_CookieJar' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/CookieJar.php', + 'CAS_Exception' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Exception.php', + 'CAS_GracefullTerminationException' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/GracefullTerminationException.php', + 'CAS_InvalidArgumentException' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/InvalidArgumentException.php', + 'CAS_Languages_Catalan' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Languages/Catalan.php', + 'CAS_Languages_ChineseSimplified' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Languages/ChineseSimplified.php', + 'CAS_Languages_English' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Languages/English.php', + 'CAS_Languages_French' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Languages/French.php', + 'CAS_Languages_Galego' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Languages/Galego.php', + 'CAS_Languages_German' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Languages/German.php', + 'CAS_Languages_Greek' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Languages/Greek.php', + 'CAS_Languages_Japanese' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Languages/Japanese.php', + 'CAS_Languages_LanguageInterface' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Languages/LanguageInterface.php', + 'CAS_Languages_Portuguese' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Languages/Portuguese.php', + 'CAS_Languages_Spanish' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Languages/Spanish.php', + 'CAS_OutOfSequenceBeforeAuthenticationCallException' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/OutOfSequenceBeforeAuthenticationCallException.php', + 'CAS_OutOfSequenceBeforeClientException' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/OutOfSequenceBeforeClientException.php', + 'CAS_OutOfSequenceBeforeProxyException' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/OutOfSequenceBeforeProxyException.php', + 'CAS_OutOfSequenceException' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/OutOfSequenceException.php', + 'CAS_PGTStorage_AbstractStorage' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/PGTStorage/AbstractStorage.php', + 'CAS_PGTStorage_Db' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/PGTStorage/Db.php', + 'CAS_PGTStorage_File' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/PGTStorage/File.php', + 'CAS_ProxiedService' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/ProxiedService.php', + 'CAS_ProxiedService_Abstract' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/ProxiedService/Abstract.php', + 'CAS_ProxiedService_Exception' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/ProxiedService/Exception.php', + 'CAS_ProxiedService_Http' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/ProxiedService/Http.php', + 'CAS_ProxiedService_Http_Abstract' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/ProxiedService/Http/Abstract.php', + 'CAS_ProxiedService_Http_Get' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/ProxiedService/Http/Get.php', + 'CAS_ProxiedService_Http_Post' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/ProxiedService/Http/Post.php', + 'CAS_ProxiedService_Imap' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/ProxiedService/Imap.php', + 'CAS_ProxiedService_Testable' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/ProxiedService/Testable.php', + 'CAS_ProxyChain' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/ProxyChain.php', + 'CAS_ProxyChain_AllowedList' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/ProxyChain/AllowedList.php', + 'CAS_ProxyChain_Any' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/ProxyChain/Any.php', + 'CAS_ProxyChain_Interface' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/ProxyChain/Interface.php', + 'CAS_ProxyChain_Trusted' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/ProxyChain/Trusted.php', + 'CAS_ProxyTicketException' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/ProxyTicketException.php', + 'CAS_Request_AbstractRequest' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Request/AbstractRequest.php', + 'CAS_Request_CurlMultiRequest' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Request/CurlMultiRequest.php', + 'CAS_Request_CurlRequest' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Request/CurlRequest.php', + 'CAS_Request_Exception' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Request/Exception.php', + 'CAS_Request_MultiRequestInterface' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Request/MultiRequestInterface.php', + 'CAS_Request_RequestInterface' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Request/RequestInterface.php', + 'CAS_ServiceBaseUrl_AllowedListDiscovery' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/ServiceBaseUrl/AllowedListDiscovery.php', + 'CAS_ServiceBaseUrl_Base' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/ServiceBaseUrl/Base.php', + 'CAS_ServiceBaseUrl_Interface' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/ServiceBaseUrl/Interface.php', + 'CAS_ServiceBaseUrl_Static' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/ServiceBaseUrl/Static.php', + 'CAS_Session_PhpSession' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Session/PhpSession.php', + 'CAS_TypeMismatchException' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/TypeMismatchException.php', + 'CURLStringFile' => __DIR__ . '/..' . '/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php', + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + 'Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php', + 'PHPUnit\\Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/Exception.php', + 'PHPUnit\\Framework\\ActualValueIsNotAnObjectException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/ActualValueIsNotAnObjectException.php', + 'PHPUnit\\Framework\\Assert' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Assert.php', + 'PHPUnit\\Framework\\AssertionFailedError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/AssertionFailedError.php', + 'PHPUnit\\Framework\\CodeCoverageException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/CodeCoverageException.php', + 'PHPUnit\\Framework\\ComparisonMethodDoesNotAcceptParameterTypeException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotAcceptParameterTypeException.php', + 'PHPUnit\\Framework\\ComparisonMethodDoesNotDeclareBoolReturnTypeException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotDeclareBoolReturnTypeException.php', + 'PHPUnit\\Framework\\ComparisonMethodDoesNotDeclareExactlyOneParameterException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotDeclareExactlyOneParameterException.php', + 'PHPUnit\\Framework\\ComparisonMethodDoesNotDeclareParameterTypeException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotDeclareParameterTypeException.php', + 'PHPUnit\\Framework\\ComparisonMethodDoesNotExistException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotExistException.php', + 'PHPUnit\\Framework\\Constraint\\ArrayHasKey' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Traversable/ArrayHasKey.php', + 'PHPUnit\\Framework\\Constraint\\BinaryOperator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Operator/BinaryOperator.php', + 'PHPUnit\\Framework\\Constraint\\Callback' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Callback.php', + 'PHPUnit\\Framework\\Constraint\\ClassHasAttribute' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Object/ClassHasAttribute.php', + 'PHPUnit\\Framework\\Constraint\\ClassHasStaticAttribute' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Object/ClassHasStaticAttribute.php', + 'PHPUnit\\Framework\\Constraint\\Constraint' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Constraint.php', + 'PHPUnit\\Framework\\Constraint\\Count' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Cardinality/Count.php', + 'PHPUnit\\Framework\\Constraint\\DirectoryExists' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Filesystem/DirectoryExists.php', + 'PHPUnit\\Framework\\Constraint\\Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Exception/Exception.php', + 'PHPUnit\\Framework\\Constraint\\ExceptionCode' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Exception/ExceptionCode.php', + 'PHPUnit\\Framework\\Constraint\\ExceptionMessage' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Exception/ExceptionMessage.php', + 'PHPUnit\\Framework\\Constraint\\ExceptionMessageRegularExpression' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Exception/ExceptionMessageRegularExpression.php', + 'PHPUnit\\Framework\\Constraint\\FileExists' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Filesystem/FileExists.php', + 'PHPUnit\\Framework\\Constraint\\GreaterThan' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Cardinality/GreaterThan.php', + 'PHPUnit\\Framework\\Constraint\\IsAnything' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsAnything.php', + 'PHPUnit\\Framework\\Constraint\\IsEmpty' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Cardinality/IsEmpty.php', + 'PHPUnit\\Framework\\Constraint\\IsEqual' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqual.php', + 'PHPUnit\\Framework\\Constraint\\IsEqualCanonicalizing' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqualCanonicalizing.php', + 'PHPUnit\\Framework\\Constraint\\IsEqualIgnoringCase' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqualIgnoringCase.php', + 'PHPUnit\\Framework\\Constraint\\IsEqualWithDelta' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqualWithDelta.php', + 'PHPUnit\\Framework\\Constraint\\IsFalse' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Boolean/IsFalse.php', + 'PHPUnit\\Framework\\Constraint\\IsFinite' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Math/IsFinite.php', + 'PHPUnit\\Framework\\Constraint\\IsIdentical' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsIdentical.php', + 'PHPUnit\\Framework\\Constraint\\IsInfinite' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Math/IsInfinite.php', + 'PHPUnit\\Framework\\Constraint\\IsInstanceOf' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Type/IsInstanceOf.php', + 'PHPUnit\\Framework\\Constraint\\IsJson' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/String/IsJson.php', + 'PHPUnit\\Framework\\Constraint\\IsNan' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Math/IsNan.php', + 'PHPUnit\\Framework\\Constraint\\IsNull' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Type/IsNull.php', + 'PHPUnit\\Framework\\Constraint\\IsReadable' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Filesystem/IsReadable.php', + 'PHPUnit\\Framework\\Constraint\\IsTrue' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Boolean/IsTrue.php', + 'PHPUnit\\Framework\\Constraint\\IsType' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Type/IsType.php', + 'PHPUnit\\Framework\\Constraint\\IsWritable' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Filesystem/IsWritable.php', + 'PHPUnit\\Framework\\Constraint\\JsonMatches' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/JsonMatches.php', + 'PHPUnit\\Framework\\Constraint\\JsonMatchesErrorMessageProvider' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/JsonMatchesErrorMessageProvider.php', + 'PHPUnit\\Framework\\Constraint\\LessThan' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Cardinality/LessThan.php', + 'PHPUnit\\Framework\\Constraint\\LogicalAnd' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Operator/LogicalAnd.php', + 'PHPUnit\\Framework\\Constraint\\LogicalNot' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Operator/LogicalNot.php', + 'PHPUnit\\Framework\\Constraint\\LogicalOr' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Operator/LogicalOr.php', + 'PHPUnit\\Framework\\Constraint\\LogicalXor' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Operator/LogicalXor.php', + 'PHPUnit\\Framework\\Constraint\\ObjectEquals' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Object/ObjectEquals.php', + 'PHPUnit\\Framework\\Constraint\\ObjectHasAttribute' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Object/ObjectHasAttribute.php', + 'PHPUnit\\Framework\\Constraint\\ObjectHasProperty' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Object/ObjectHasProperty.php', + 'PHPUnit\\Framework\\Constraint\\Operator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Operator/Operator.php', + 'PHPUnit\\Framework\\Constraint\\RegularExpression' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/String/RegularExpression.php', + 'PHPUnit\\Framework\\Constraint\\SameSize' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Cardinality/SameSize.php', + 'PHPUnit\\Framework\\Constraint\\StringContains' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/String/StringContains.php', + 'PHPUnit\\Framework\\Constraint\\StringEndsWith' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/String/StringEndsWith.php', + 'PHPUnit\\Framework\\Constraint\\StringMatchesFormatDescription' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/String/StringMatchesFormatDescription.php', + 'PHPUnit\\Framework\\Constraint\\StringStartsWith' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/String/StringStartsWith.php', + 'PHPUnit\\Framework\\Constraint\\TraversableContains' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Traversable/TraversableContains.php', + 'PHPUnit\\Framework\\Constraint\\TraversableContainsEqual' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Traversable/TraversableContainsEqual.php', + 'PHPUnit\\Framework\\Constraint\\TraversableContainsIdentical' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Traversable/TraversableContainsIdentical.php', + 'PHPUnit\\Framework\\Constraint\\TraversableContainsOnly' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Traversable/TraversableContainsOnly.php', + 'PHPUnit\\Framework\\Constraint\\UnaryOperator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Operator/UnaryOperator.php', + 'PHPUnit\\Framework\\CoveredCodeNotExecutedException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/CoveredCodeNotExecutedException.php', + 'PHPUnit\\Framework\\DataProviderTestSuite' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/DataProviderTestSuite.php', + 'PHPUnit\\Framework\\Error' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/Error.php', + 'PHPUnit\\Framework\\ErrorTestCase' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/ErrorTestCase.php', + 'PHPUnit\\Framework\\Error\\Deprecated' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Error/Deprecated.php', + 'PHPUnit\\Framework\\Error\\Error' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Error/Error.php', + 'PHPUnit\\Framework\\Error\\Notice' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Error/Notice.php', + 'PHPUnit\\Framework\\Error\\Warning' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Error/Warning.php', + 'PHPUnit\\Framework\\Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/Exception.php', + 'PHPUnit\\Framework\\ExceptionWrapper' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/ExceptionWrapper.php', + 'PHPUnit\\Framework\\ExecutionOrderDependency' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/ExecutionOrderDependency.php', + 'PHPUnit\\Framework\\ExpectationFailedException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/ExpectationFailedException.php', + 'PHPUnit\\Framework\\IncompleteTest' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/IncompleteTest.php', + 'PHPUnit\\Framework\\IncompleteTestCase' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/IncompleteTestCase.php', + 'PHPUnit\\Framework\\IncompleteTestError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/IncompleteTestError.php', + 'PHPUnit\\Framework\\InvalidArgumentException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/InvalidArgumentException.php', + 'PHPUnit\\Framework\\InvalidCoversTargetException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/InvalidCoversTargetException.php', + 'PHPUnit\\Framework\\InvalidDataProviderException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/InvalidDataProviderException.php', + 'PHPUnit\\Framework\\InvalidParameterGroupException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/InvalidParameterGroupException.php', + 'PHPUnit\\Framework\\MissingCoversAnnotationException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/MissingCoversAnnotationException.php', + 'PHPUnit\\Framework\\MockObject\\Api' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Api/Api.php', + 'PHPUnit\\Framework\\MockObject\\BadMethodCallException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Exception/BadMethodCallException.php', + 'PHPUnit\\Framework\\MockObject\\Builder\\Identity' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Builder/Identity.php', + 'PHPUnit\\Framework\\MockObject\\Builder\\InvocationMocker' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Builder/InvocationMocker.php', + 'PHPUnit\\Framework\\MockObject\\Builder\\InvocationStubber' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Builder/InvocationStubber.php', + 'PHPUnit\\Framework\\MockObject\\Builder\\MethodNameMatch' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Builder/MethodNameMatch.php', + 'PHPUnit\\Framework\\MockObject\\Builder\\ParametersMatch' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Builder/ParametersMatch.php', + 'PHPUnit\\Framework\\MockObject\\Builder\\Stub' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Builder/Stub.php', + 'PHPUnit\\Framework\\MockObject\\CannotUseAddMethodsException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Exception/CannotUseAddMethodsException.php', + 'PHPUnit\\Framework\\MockObject\\CannotUseOnlyMethodsException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Exception/CannotUseOnlyMethodsException.php', + 'PHPUnit\\Framework\\MockObject\\ClassAlreadyExistsException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Exception/ClassAlreadyExistsException.php', + 'PHPUnit\\Framework\\MockObject\\ClassIsFinalException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Exception/ClassIsFinalException.php', + 'PHPUnit\\Framework\\MockObject\\ClassIsReadonlyException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Exception/ClassIsReadonlyException.php', + 'PHPUnit\\Framework\\MockObject\\ConfigurableMethod' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/ConfigurableMethod.php', + 'PHPUnit\\Framework\\MockObject\\ConfigurableMethodsAlreadyInitializedException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Exception/ConfigurableMethodsAlreadyInitializedException.php', + 'PHPUnit\\Framework\\MockObject\\DuplicateMethodException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Exception/DuplicateMethodException.php', + 'PHPUnit\\Framework\\MockObject\\Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Exception/Exception.php', + 'PHPUnit\\Framework\\MockObject\\Generator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Generator.php', + 'PHPUnit\\Framework\\MockObject\\IncompatibleReturnValueException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Exception/IncompatibleReturnValueException.php', + 'PHPUnit\\Framework\\MockObject\\InvalidMethodNameException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Exception/InvalidMethodNameException.php', + 'PHPUnit\\Framework\\MockObject\\Invocation' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Invocation.php', + 'PHPUnit\\Framework\\MockObject\\InvocationHandler' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/InvocationHandler.php', + 'PHPUnit\\Framework\\MockObject\\MatchBuilderNotFoundException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Exception/MatchBuilderNotFoundException.php', + 'PHPUnit\\Framework\\MockObject\\Matcher' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Matcher.php', + 'PHPUnit\\Framework\\MockObject\\MatcherAlreadyRegisteredException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Exception/MatcherAlreadyRegisteredException.php', + 'PHPUnit\\Framework\\MockObject\\Method' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Api/Method.php', + 'PHPUnit\\Framework\\MockObject\\MethodCannotBeConfiguredException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Exception/MethodCannotBeConfiguredException.php', + 'PHPUnit\\Framework\\MockObject\\MethodNameAlreadyConfiguredException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Exception/MethodNameAlreadyConfiguredException.php', + 'PHPUnit\\Framework\\MockObject\\MethodNameConstraint' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/MethodNameConstraint.php', + 'PHPUnit\\Framework\\MockObject\\MethodNameNotConfiguredException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Exception/MethodNameNotConfiguredException.php', + 'PHPUnit\\Framework\\MockObject\\MethodParametersAlreadyConfiguredException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Exception/MethodParametersAlreadyConfiguredException.php', + 'PHPUnit\\Framework\\MockObject\\MockBuilder' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/MockBuilder.php', + 'PHPUnit\\Framework\\MockObject\\MockClass' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/MockClass.php', + 'PHPUnit\\Framework\\MockObject\\MockMethod' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/MockMethod.php', + 'PHPUnit\\Framework\\MockObject\\MockMethodSet' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/MockMethodSet.php', + 'PHPUnit\\Framework\\MockObject\\MockObject' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/MockObject.php', + 'PHPUnit\\Framework\\MockObject\\MockTrait' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/MockTrait.php', + 'PHPUnit\\Framework\\MockObject\\MockType' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/MockType.php', + 'PHPUnit\\Framework\\MockObject\\OriginalConstructorInvocationRequiredException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Exception/OriginalConstructorInvocationRequiredException.php', + 'PHPUnit\\Framework\\MockObject\\ReflectionException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Exception/ReflectionException.php', + 'PHPUnit\\Framework\\MockObject\\ReturnValueNotConfiguredException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Exception/ReturnValueNotConfiguredException.php', + 'PHPUnit\\Framework\\MockObject\\Rule\\AnyInvokedCount' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Rule/AnyInvokedCount.php', + 'PHPUnit\\Framework\\MockObject\\Rule\\AnyParameters' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Rule/AnyParameters.php', + 'PHPUnit\\Framework\\MockObject\\Rule\\ConsecutiveParameters' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Rule/ConsecutiveParameters.php', + 'PHPUnit\\Framework\\MockObject\\Rule\\InvocationOrder' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Rule/InvocationOrder.php', + 'PHPUnit\\Framework\\MockObject\\Rule\\InvokedAtIndex' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtIndex.php', + 'PHPUnit\\Framework\\MockObject\\Rule\\InvokedAtLeastCount' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtLeastCount.php', + 'PHPUnit\\Framework\\MockObject\\Rule\\InvokedAtLeastOnce' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtLeastOnce.php', + 'PHPUnit\\Framework\\MockObject\\Rule\\InvokedAtMostCount' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtMostCount.php', + 'PHPUnit\\Framework\\MockObject\\Rule\\InvokedCount' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedCount.php', + 'PHPUnit\\Framework\\MockObject\\Rule\\MethodName' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Rule/MethodName.php', + 'PHPUnit\\Framework\\MockObject\\Rule\\Parameters' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Rule/Parameters.php', + 'PHPUnit\\Framework\\MockObject\\Rule\\ParametersRule' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Rule/ParametersRule.php', + 'PHPUnit\\Framework\\MockObject\\RuntimeException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Exception/RuntimeException.php', + 'PHPUnit\\Framework\\MockObject\\SoapExtensionNotAvailableException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Exception/SoapExtensionNotAvailableException.php', + 'PHPUnit\\Framework\\MockObject\\Stub' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Stub.php', + 'PHPUnit\\Framework\\MockObject\\Stub\\ConsecutiveCalls' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Stub/ConsecutiveCalls.php', + 'PHPUnit\\Framework\\MockObject\\Stub\\Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Stub/Exception.php', + 'PHPUnit\\Framework\\MockObject\\Stub\\ReturnArgument' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnArgument.php', + 'PHPUnit\\Framework\\MockObject\\Stub\\ReturnCallback' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnCallback.php', + 'PHPUnit\\Framework\\MockObject\\Stub\\ReturnReference' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnReference.php', + 'PHPUnit\\Framework\\MockObject\\Stub\\ReturnSelf' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnSelf.php', + 'PHPUnit\\Framework\\MockObject\\Stub\\ReturnStub' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnStub.php', + 'PHPUnit\\Framework\\MockObject\\Stub\\ReturnValueMap' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnValueMap.php', + 'PHPUnit\\Framework\\MockObject\\Stub\\Stub' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Stub/Stub.php', + 'PHPUnit\\Framework\\MockObject\\UnknownClassException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Exception/UnknownClassException.php', + 'PHPUnit\\Framework\\MockObject\\UnknownTraitException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Exception/UnknownTraitException.php', + 'PHPUnit\\Framework\\MockObject\\UnknownTypeException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Exception/UnknownTypeException.php', + 'PHPUnit\\Framework\\MockObject\\Verifiable' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MockObject/Verifiable.php', + 'PHPUnit\\Framework\\NoChildTestSuiteException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/NoChildTestSuiteException.php', + 'PHPUnit\\Framework\\OutputError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/OutputError.php', + 'PHPUnit\\Framework\\PHPTAssertionFailedError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/PHPTAssertionFailedError.php', + 'PHPUnit\\Framework\\Reorderable' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Reorderable.php', + 'PHPUnit\\Framework\\RiskyTestError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/RiskyTestError.php', + 'PHPUnit\\Framework\\SelfDescribing' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/SelfDescribing.php', + 'PHPUnit\\Framework\\SkippedTest' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/SkippedTest.php', + 'PHPUnit\\Framework\\SkippedTestCase' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/SkippedTestCase.php', + 'PHPUnit\\Framework\\SkippedTestError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/SkippedTestError.php', + 'PHPUnit\\Framework\\SkippedTestSuiteError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/SkippedTestSuiteError.php', + 'PHPUnit\\Framework\\SyntheticError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/SyntheticError.php', + 'PHPUnit\\Framework\\SyntheticSkippedError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/SyntheticSkippedError.php', + 'PHPUnit\\Framework\\Test' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Test.php', + 'PHPUnit\\Framework\\TestBuilder' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestBuilder.php', + 'PHPUnit\\Framework\\TestCase' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestCase.php', + 'PHPUnit\\Framework\\TestFailure' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestFailure.php', + 'PHPUnit\\Framework\\TestListener' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestListener.php', + 'PHPUnit\\Framework\\TestListenerDefaultImplementation' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestListenerDefaultImplementation.php', + 'PHPUnit\\Framework\\TestResult' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestResult.php', + 'PHPUnit\\Framework\\TestSuite' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestSuite.php', + 'PHPUnit\\Framework\\TestSuiteIterator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestSuiteIterator.php', + 'PHPUnit\\Framework\\UnintentionallyCoveredCodeError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/UnintentionallyCoveredCodeError.php', + 'PHPUnit\\Framework\\Warning' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/Warning.php', + 'PHPUnit\\Framework\\WarningTestCase' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/WarningTestCase.php', + 'PHPUnit\\Runner\\AfterIncompleteTestHook' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Hook/AfterIncompleteTestHook.php', + 'PHPUnit\\Runner\\AfterLastTestHook' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Hook/AfterLastTestHook.php', + 'PHPUnit\\Runner\\AfterRiskyTestHook' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Hook/AfterRiskyTestHook.php', + 'PHPUnit\\Runner\\AfterSkippedTestHook' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Hook/AfterSkippedTestHook.php', + 'PHPUnit\\Runner\\AfterSuccessfulTestHook' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Hook/AfterSuccessfulTestHook.php', + 'PHPUnit\\Runner\\AfterTestErrorHook' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Hook/AfterTestErrorHook.php', + 'PHPUnit\\Runner\\AfterTestFailureHook' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Hook/AfterTestFailureHook.php', + 'PHPUnit\\Runner\\AfterTestHook' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Hook/AfterTestHook.php', + 'PHPUnit\\Runner\\AfterTestWarningHook' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Hook/AfterTestWarningHook.php', + 'PHPUnit\\Runner\\BaseTestRunner' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/BaseTestRunner.php', + 'PHPUnit\\Runner\\BeforeFirstTestHook' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Hook/BeforeFirstTestHook.php', + 'PHPUnit\\Runner\\BeforeTestHook' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Hook/BeforeTestHook.php', + 'PHPUnit\\Runner\\DefaultTestResultCache' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/DefaultTestResultCache.php', + 'PHPUnit\\Runner\\Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Exception.php', + 'PHPUnit\\Runner\\Extension\\ExtensionHandler' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Extension/ExtensionHandler.php', + 'PHPUnit\\Runner\\Extension\\PharLoader' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Extension/PharLoader.php', + 'PHPUnit\\Runner\\Filter\\ExcludeGroupFilterIterator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Filter/ExcludeGroupFilterIterator.php', + 'PHPUnit\\Runner\\Filter\\Factory' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Filter/Factory.php', + 'PHPUnit\\Runner\\Filter\\GroupFilterIterator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Filter/GroupFilterIterator.php', + 'PHPUnit\\Runner\\Filter\\IncludeGroupFilterIterator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Filter/IncludeGroupFilterIterator.php', + 'PHPUnit\\Runner\\Filter\\NameFilterIterator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Filter/NameFilterIterator.php', + 'PHPUnit\\Runner\\Hook' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Hook/Hook.php', + 'PHPUnit\\Runner\\NullTestResultCache' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/NullTestResultCache.php', + 'PHPUnit\\Runner\\PhptTestCase' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/PhptTestCase.php', + 'PHPUnit\\Runner\\ResultCacheExtension' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/ResultCacheExtension.php', + 'PHPUnit\\Runner\\StandardTestSuiteLoader' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/StandardTestSuiteLoader.php', + 'PHPUnit\\Runner\\TestHook' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Hook/TestHook.php', + 'PHPUnit\\Runner\\TestListenerAdapter' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Hook/TestListenerAdapter.php', + 'PHPUnit\\Runner\\TestResultCache' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/TestResultCache.php', + 'PHPUnit\\Runner\\TestSuiteLoader' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/TestSuiteLoader.php', + 'PHPUnit\\Runner\\TestSuiteSorter' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/TestSuiteSorter.php', + 'PHPUnit\\Runner\\Version' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Version.php', + 'PHPUnit\\TextUI\\CliArguments\\Builder' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/CliArguments/Builder.php', + 'PHPUnit\\TextUI\\CliArguments\\Configuration' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/CliArguments/Configuration.php', + 'PHPUnit\\TextUI\\CliArguments\\Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/CliArguments/Exception.php', + 'PHPUnit\\TextUI\\CliArguments\\Mapper' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/CliArguments/Mapper.php', + 'PHPUnit\\TextUI\\Command' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/Command.php', + 'PHPUnit\\TextUI\\DefaultResultPrinter' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/DefaultResultPrinter.php', + 'PHPUnit\\TextUI\\Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/Exception/Exception.php', + 'PHPUnit\\TextUI\\Help' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/Help.php', + 'PHPUnit\\TextUI\\ReflectionException' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/Exception/ReflectionException.php', + 'PHPUnit\\TextUI\\ResultPrinter' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/ResultPrinter.php', + 'PHPUnit\\TextUI\\RuntimeException' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/Exception/RuntimeException.php', + 'PHPUnit\\TextUI\\TestDirectoryNotFoundException' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/Exception/TestDirectoryNotFoundException.php', + 'PHPUnit\\TextUI\\TestFileNotFoundException' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/Exception/TestFileNotFoundException.php', + 'PHPUnit\\TextUI\\TestRunner' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/TestRunner.php', + 'PHPUnit\\TextUI\\TestSuiteMapper' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/TestSuiteMapper.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\CodeCoverage\\CodeCoverage' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/CodeCoverage.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\CodeCoverage\\FilterMapper' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/FilterMapper.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\CodeCoverage\\Filter\\Directory' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Filter/Directory.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\CodeCoverage\\Filter\\DirectoryCollection' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Filter/DirectoryCollection.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\CodeCoverage\\Filter\\DirectoryCollectionIterator' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Filter/DirectoryCollectionIterator.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\CodeCoverage\\Report\\Clover' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Clover.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\CodeCoverage\\Report\\Cobertura' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Cobertura.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\CodeCoverage\\Report\\Crap4j' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Crap4j.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\CodeCoverage\\Report\\Html' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Html.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\CodeCoverage\\Report\\Php' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Php.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\CodeCoverage\\Report\\Text' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Text.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\CodeCoverage\\Report\\Xml' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Xml.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Configuration' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Configuration.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Constant' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/Constant.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\ConstantCollection' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/ConstantCollection.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\ConstantCollectionIterator' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/ConstantCollectionIterator.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\ConvertLogTypes' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/ConvertLogTypes.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\CoverageCloverToReport' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageCloverToReport.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\CoverageCrap4jToReport' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageCrap4jToReport.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\CoverageHtmlToReport' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageHtmlToReport.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\CoveragePhpToReport' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoveragePhpToReport.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\CoverageTextToReport' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageTextToReport.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\CoverageXmlToReport' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageXmlToReport.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Directory' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/Directory.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\DirectoryCollection' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/DirectoryCollection.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\DirectoryCollectionIterator' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/DirectoryCollectionIterator.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Exception.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Extension' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/PHPUnit/Extension.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\ExtensionCollection' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/PHPUnit/ExtensionCollection.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\ExtensionCollectionIterator' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/PHPUnit/ExtensionCollectionIterator.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\File' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/File.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\FileCollection' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/FileCollection.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\FileCollectionIterator' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/FileCollectionIterator.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Generator' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Generator.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Group' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Group/Group.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\GroupCollection' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Group/GroupCollection.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\GroupCollectionIterator' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Group/GroupCollectionIterator.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Groups' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Group/Groups.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\IniSetting' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/IniSetting.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\IniSettingCollection' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/IniSettingCollection.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\IniSettingCollectionIterator' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/IniSettingCollectionIterator.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\IntroduceCoverageElement' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/IntroduceCoverageElement.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Loader' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Loader.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\LogToReportMigration' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/LogToReportMigration.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Logging\\Junit' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/Junit.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Logging\\Logging' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/Logging.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Logging\\TeamCity' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/TeamCity.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Logging\\TestDox\\Html' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/TestDox/Html.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Logging\\TestDox\\Text' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/TestDox/Text.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Logging\\TestDox\\Xml' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/TestDox/Xml.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Logging\\Text' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/Text.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Migration' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/Migration.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\MigrationBuilder' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/MigrationBuilder.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\MigrationBuilderException' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/MigrationBuilderException.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\MigrationException' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/MigrationException.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Migrator' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrator.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\MoveAttributesFromFilterWhitelistToCoverage' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/MoveAttributesFromFilterWhitelistToCoverage.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\MoveAttributesFromRootToCoverage' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/MoveAttributesFromRootToCoverage.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\MoveWhitelistExcludesToCoverage' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/MoveWhitelistExcludesToCoverage.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\MoveWhitelistIncludesToCoverage' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/MoveWhitelistIncludesToCoverage.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\PHPUnit' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/PHPUnit/PHPUnit.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Php' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/Php.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\PhpHandler' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/PhpHandler.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\RemoveCacheTokensAttribute' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/RemoveCacheTokensAttribute.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\RemoveEmptyFilter' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/RemoveEmptyFilter.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\RemoveLogTypes' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/RemoveLogTypes.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\TestDirectory' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestDirectory.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\TestDirectoryCollection' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestDirectoryCollection.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\TestDirectoryCollectionIterator' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestDirectoryCollectionIterator.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\TestFile' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestFile.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\TestFileCollection' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestFileCollection.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\TestFileCollectionIterator' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestFileCollectionIterator.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\TestSuite' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestSuite.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\TestSuiteCollection' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestSuiteCollection.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\TestSuiteCollectionIterator' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestSuiteCollectionIterator.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\UpdateSchemaLocationTo93' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/UpdateSchemaLocationTo93.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\Variable' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/Variable.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\VariableCollection' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/VariableCollection.php', + 'PHPUnit\\TextUI\\XmlConfiguration\\VariableCollectionIterator' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/VariableCollectionIterator.php', + 'PHPUnit\\Util\\Annotation\\DocBlock' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Annotation/DocBlock.php', + 'PHPUnit\\Util\\Annotation\\Registry' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Annotation/Registry.php', + 'PHPUnit\\Util\\Blacklist' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Blacklist.php', + 'PHPUnit\\Util\\Cloner' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Cloner.php', + 'PHPUnit\\Util\\Color' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Color.php', + 'PHPUnit\\Util\\ErrorHandler' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/ErrorHandler.php', + 'PHPUnit\\Util\\Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Exception.php', + 'PHPUnit\\Util\\ExcludeList' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/ExcludeList.php', + 'PHPUnit\\Util\\FileLoader' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/FileLoader.php', + 'PHPUnit\\Util\\Filesystem' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Filesystem.php', + 'PHPUnit\\Util\\Filter' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Filter.php', + 'PHPUnit\\Util\\GlobalState' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/GlobalState.php', + 'PHPUnit\\Util\\InvalidDataSetException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/InvalidDataSetException.php', + 'PHPUnit\\Util\\Json' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Json.php', + 'PHPUnit\\Util\\Log\\JUnit' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Log/JUnit.php', + 'PHPUnit\\Util\\Log\\TeamCity' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Log/TeamCity.php', + 'PHPUnit\\Util\\PHP\\AbstractPhpProcess' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/PHP/AbstractPhpProcess.php', + 'PHPUnit\\Util\\PHP\\DefaultPhpProcess' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/PHP/DefaultPhpProcess.php', + 'PHPUnit\\Util\\PHP\\WindowsPhpProcess' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/PHP/WindowsPhpProcess.php', + 'PHPUnit\\Util\\Printer' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Printer.php', + 'PHPUnit\\Util\\Reflection' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Reflection.php', + 'PHPUnit\\Util\\RegularExpression' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/RegularExpression.php', + 'PHPUnit\\Util\\Test' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Test.php', + 'PHPUnit\\Util\\TestDox\\CliTestDoxPrinter' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TestDox/CliTestDoxPrinter.php', + 'PHPUnit\\Util\\TestDox\\HtmlResultPrinter' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TestDox/HtmlResultPrinter.php', + 'PHPUnit\\Util\\TestDox\\NamePrettifier' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TestDox/NamePrettifier.php', + 'PHPUnit\\Util\\TestDox\\ResultPrinter' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TestDox/ResultPrinter.php', + 'PHPUnit\\Util\\TestDox\\TestDoxPrinter' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TestDox/TestDoxPrinter.php', + 'PHPUnit\\Util\\TestDox\\TextResultPrinter' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TestDox/TextResultPrinter.php', + 'PHPUnit\\Util\\TestDox\\XmlResultPrinter' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TestDox/XmlResultPrinter.php', + 'PHPUnit\\Util\\TextTestListRenderer' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TextTestListRenderer.php', + 'PHPUnit\\Util\\Type' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Type.php', + 'PHPUnit\\Util\\VersionComparisonOperator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/VersionComparisonOperator.php', + 'PHPUnit\\Util\\XdebugFilterScriptGenerator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/XdebugFilterScriptGenerator.php', + 'PHPUnit\\Util\\Xml' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Xml.php', + 'PHPUnit\\Util\\XmlTestListRenderer' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/XmlTestListRenderer.php', + 'PHPUnit\\Util\\Xml\\Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Xml/Exception.php', + 'PHPUnit\\Util\\Xml\\FailedSchemaDetectionResult' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Xml/FailedSchemaDetectionResult.php', + 'PHPUnit\\Util\\Xml\\Loader' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Xml/Loader.php', + 'PHPUnit\\Util\\Xml\\SchemaDetectionResult' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Xml/SchemaDetectionResult.php', + 'PHPUnit\\Util\\Xml\\SchemaDetector' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Xml/SchemaDetector.php', + 'PHPUnit\\Util\\Xml\\SchemaFinder' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Xml/SchemaFinder.php', + 'PHPUnit\\Util\\Xml\\SnapshotNodeList' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Xml/SnapshotNodeList.php', + 'PHPUnit\\Util\\Xml\\SuccessfulSchemaDetectionResult' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Xml/SuccessfulSchemaDetectionResult.php', + 'PHPUnit\\Util\\Xml\\ValidationResult' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Xml/ValidationResult.php', + 'PHPUnit\\Util\\Xml\\Validator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Xml/Validator.php', + 'PharIo\\Manifest\\Application' => __DIR__ . '/..' . '/phar-io/manifest/src/values/Application.php', + 'PharIo\\Manifest\\ApplicationName' => __DIR__ . '/..' . '/phar-io/manifest/src/values/ApplicationName.php', + 'PharIo\\Manifest\\Author' => __DIR__ . '/..' . '/phar-io/manifest/src/values/Author.php', + 'PharIo\\Manifest\\AuthorCollection' => __DIR__ . '/..' . '/phar-io/manifest/src/values/AuthorCollection.php', + 'PharIo\\Manifest\\AuthorCollectionIterator' => __DIR__ . '/..' . '/phar-io/manifest/src/values/AuthorCollectionIterator.php', + 'PharIo\\Manifest\\AuthorElement' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/AuthorElement.php', + 'PharIo\\Manifest\\AuthorElementCollection' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/AuthorElementCollection.php', + 'PharIo\\Manifest\\BundledComponent' => __DIR__ . '/..' . '/phar-io/manifest/src/values/BundledComponent.php', + 'PharIo\\Manifest\\BundledComponentCollection' => __DIR__ . '/..' . '/phar-io/manifest/src/values/BundledComponentCollection.php', + 'PharIo\\Manifest\\BundledComponentCollectionIterator' => __DIR__ . '/..' . '/phar-io/manifest/src/values/BundledComponentCollectionIterator.php', + 'PharIo\\Manifest\\BundlesElement' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/BundlesElement.php', + 'PharIo\\Manifest\\ComponentElement' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/ComponentElement.php', + 'PharIo\\Manifest\\ComponentElementCollection' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/ComponentElementCollection.php', + 'PharIo\\Manifest\\ContainsElement' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/ContainsElement.php', + 'PharIo\\Manifest\\CopyrightElement' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/CopyrightElement.php', + 'PharIo\\Manifest\\CopyrightInformation' => __DIR__ . '/..' . '/phar-io/manifest/src/values/CopyrightInformation.php', + 'PharIo\\Manifest\\ElementCollection' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/ElementCollection.php', + 'PharIo\\Manifest\\ElementCollectionException' => __DIR__ . '/..' . '/phar-io/manifest/src/exceptions/ElementCollectionException.php', + 'PharIo\\Manifest\\Email' => __DIR__ . '/..' . '/phar-io/manifest/src/values/Email.php', + 'PharIo\\Manifest\\Exception' => __DIR__ . '/..' . '/phar-io/manifest/src/exceptions/Exception.php', + 'PharIo\\Manifest\\ExtElement' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/ExtElement.php', + 'PharIo\\Manifest\\ExtElementCollection' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/ExtElementCollection.php', + 'PharIo\\Manifest\\Extension' => __DIR__ . '/..' . '/phar-io/manifest/src/values/Extension.php', + 'PharIo\\Manifest\\ExtensionElement' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/ExtensionElement.php', + 'PharIo\\Manifest\\InvalidApplicationNameException' => __DIR__ . '/..' . '/phar-io/manifest/src/exceptions/InvalidApplicationNameException.php', + 'PharIo\\Manifest\\InvalidEmailException' => __DIR__ . '/..' . '/phar-io/manifest/src/exceptions/InvalidEmailException.php', + 'PharIo\\Manifest\\InvalidUrlException' => __DIR__ . '/..' . '/phar-io/manifest/src/exceptions/InvalidUrlException.php', + 'PharIo\\Manifest\\Library' => __DIR__ . '/..' . '/phar-io/manifest/src/values/Library.php', + 'PharIo\\Manifest\\License' => __DIR__ . '/..' . '/phar-io/manifest/src/values/License.php', + 'PharIo\\Manifest\\LicenseElement' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/LicenseElement.php', + 'PharIo\\Manifest\\Manifest' => __DIR__ . '/..' . '/phar-io/manifest/src/values/Manifest.php', + 'PharIo\\Manifest\\ManifestDocument' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/ManifestDocument.php', + 'PharIo\\Manifest\\ManifestDocumentException' => __DIR__ . '/..' . '/phar-io/manifest/src/exceptions/ManifestDocumentException.php', + 'PharIo\\Manifest\\ManifestDocumentLoadingException' => __DIR__ . '/..' . '/phar-io/manifest/src/exceptions/ManifestDocumentLoadingException.php', + 'PharIo\\Manifest\\ManifestDocumentMapper' => __DIR__ . '/..' . '/phar-io/manifest/src/ManifestDocumentMapper.php', + 'PharIo\\Manifest\\ManifestDocumentMapperException' => __DIR__ . '/..' . '/phar-io/manifest/src/exceptions/ManifestDocumentMapperException.php', + 'PharIo\\Manifest\\ManifestElement' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/ManifestElement.php', + 'PharIo\\Manifest\\ManifestElementException' => __DIR__ . '/..' . '/phar-io/manifest/src/exceptions/ManifestElementException.php', + 'PharIo\\Manifest\\ManifestLoader' => __DIR__ . '/..' . '/phar-io/manifest/src/ManifestLoader.php', + 'PharIo\\Manifest\\ManifestLoaderException' => __DIR__ . '/..' . '/phar-io/manifest/src/exceptions/ManifestLoaderException.php', + 'PharIo\\Manifest\\ManifestSerializer' => __DIR__ . '/..' . '/phar-io/manifest/src/ManifestSerializer.php', + 'PharIo\\Manifest\\NoEmailAddressException' => __DIR__ . '/..' . '/phar-io/manifest/src/exceptions/NoEmailAddressException.php', + 'PharIo\\Manifest\\PhpElement' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/PhpElement.php', + 'PharIo\\Manifest\\PhpExtensionRequirement' => __DIR__ . '/..' . '/phar-io/manifest/src/values/PhpExtensionRequirement.php', + 'PharIo\\Manifest\\PhpVersionRequirement' => __DIR__ . '/..' . '/phar-io/manifest/src/values/PhpVersionRequirement.php', + 'PharIo\\Manifest\\Requirement' => __DIR__ . '/..' . '/phar-io/manifest/src/values/Requirement.php', + 'PharIo\\Manifest\\RequirementCollection' => __DIR__ . '/..' . '/phar-io/manifest/src/values/RequirementCollection.php', + 'PharIo\\Manifest\\RequirementCollectionIterator' => __DIR__ . '/..' . '/phar-io/manifest/src/values/RequirementCollectionIterator.php', + 'PharIo\\Manifest\\RequiresElement' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/RequiresElement.php', + 'PharIo\\Manifest\\Type' => __DIR__ . '/..' . '/phar-io/manifest/src/values/Type.php', + 'PharIo\\Manifest\\Url' => __DIR__ . '/..' . '/phar-io/manifest/src/values/Url.php', + 'PharIo\\Version\\AbstractVersionConstraint' => __DIR__ . '/..' . '/phar-io/version/src/constraints/AbstractVersionConstraint.php', + 'PharIo\\Version\\AndVersionConstraintGroup' => __DIR__ . '/..' . '/phar-io/version/src/constraints/AndVersionConstraintGroup.php', + 'PharIo\\Version\\AnyVersionConstraint' => __DIR__ . '/..' . '/phar-io/version/src/constraints/AnyVersionConstraint.php', + 'PharIo\\Version\\BuildMetaData' => __DIR__ . '/..' . '/phar-io/version/src/BuildMetaData.php', + 'PharIo\\Version\\ExactVersionConstraint' => __DIR__ . '/..' . '/phar-io/version/src/constraints/ExactVersionConstraint.php', + 'PharIo\\Version\\Exception' => __DIR__ . '/..' . '/phar-io/version/src/exceptions/Exception.php', + 'PharIo\\Version\\GreaterThanOrEqualToVersionConstraint' => __DIR__ . '/..' . '/phar-io/version/src/constraints/GreaterThanOrEqualToVersionConstraint.php', + 'PharIo\\Version\\InvalidPreReleaseSuffixException' => __DIR__ . '/..' . '/phar-io/version/src/exceptions/InvalidPreReleaseSuffixException.php', + 'PharIo\\Version\\InvalidVersionException' => __DIR__ . '/..' . '/phar-io/version/src/exceptions/InvalidVersionException.php', + 'PharIo\\Version\\NoBuildMetaDataException' => __DIR__ . '/..' . '/phar-io/version/src/exceptions/NoBuildMetaDataException.php', + 'PharIo\\Version\\NoPreReleaseSuffixException' => __DIR__ . '/..' . '/phar-io/version/src/exceptions/NoPreReleaseSuffixException.php', + 'PharIo\\Version\\OrVersionConstraintGroup' => __DIR__ . '/..' . '/phar-io/version/src/constraints/OrVersionConstraintGroup.php', + 'PharIo\\Version\\PreReleaseSuffix' => __DIR__ . '/..' . '/phar-io/version/src/PreReleaseSuffix.php', + 'PharIo\\Version\\SpecificMajorAndMinorVersionConstraint' => __DIR__ . '/..' . '/phar-io/version/src/constraints/SpecificMajorAndMinorVersionConstraint.php', + 'PharIo\\Version\\SpecificMajorVersionConstraint' => __DIR__ . '/..' . '/phar-io/version/src/constraints/SpecificMajorVersionConstraint.php', + 'PharIo\\Version\\UnsupportedVersionConstraintException' => __DIR__ . '/..' . '/phar-io/version/src/exceptions/UnsupportedVersionConstraintException.php', + 'PharIo\\Version\\Version' => __DIR__ . '/..' . '/phar-io/version/src/Version.php', + 'PharIo\\Version\\VersionConstraint' => __DIR__ . '/..' . '/phar-io/version/src/constraints/VersionConstraint.php', + 'PharIo\\Version\\VersionConstraintParser' => __DIR__ . '/..' . '/phar-io/version/src/VersionConstraintParser.php', + 'PharIo\\Version\\VersionConstraintValue' => __DIR__ . '/..' . '/phar-io/version/src/VersionConstraintValue.php', + 'PharIo\\Version\\VersionNumber' => __DIR__ . '/..' . '/phar-io/version/src/VersionNumber.php', + 'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', + 'ReturnTypeWillChange' => __DIR__ . '/..' . '/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php', + 'SebastianBergmann\\CliParser\\AmbiguousOptionException' => __DIR__ . '/..' . '/sebastian/cli-parser/src/exceptions/AmbiguousOptionException.php', + 'SebastianBergmann\\CliParser\\Exception' => __DIR__ . '/..' . '/sebastian/cli-parser/src/exceptions/Exception.php', + 'SebastianBergmann\\CliParser\\OptionDoesNotAllowArgumentException' => __DIR__ . '/..' . '/sebastian/cli-parser/src/exceptions/OptionDoesNotAllowArgumentException.php', + 'SebastianBergmann\\CliParser\\Parser' => __DIR__ . '/..' . '/sebastian/cli-parser/src/Parser.php', + 'SebastianBergmann\\CliParser\\RequiredOptionArgumentMissingException' => __DIR__ . '/..' . '/sebastian/cli-parser/src/exceptions/RequiredOptionArgumentMissingException.php', + 'SebastianBergmann\\CliParser\\UnknownOptionException' => __DIR__ . '/..' . '/sebastian/cli-parser/src/exceptions/UnknownOptionException.php', + 'SebastianBergmann\\CodeCoverage\\BranchAndPathCoverageNotSupportedException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/BranchAndPathCoverageNotSupportedException.php', + 'SebastianBergmann\\CodeCoverage\\CodeCoverage' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage.php', + 'SebastianBergmann\\CodeCoverage\\DeadCodeDetectionNotSupportedException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/DeadCodeDetectionNotSupportedException.php', + 'SebastianBergmann\\CodeCoverage\\Driver\\Driver' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Driver/Driver.php', + 'SebastianBergmann\\CodeCoverage\\Driver\\PathExistsButIsNotDirectoryException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/PathExistsButIsNotDirectoryException.php', + 'SebastianBergmann\\CodeCoverage\\Driver\\PcovDriver' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Driver/PcovDriver.php', + 'SebastianBergmann\\CodeCoverage\\Driver\\PcovNotAvailableException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/PcovNotAvailableException.php', + 'SebastianBergmann\\CodeCoverage\\Driver\\PhpdbgDriver' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Driver/PhpdbgDriver.php', + 'SebastianBergmann\\CodeCoverage\\Driver\\PhpdbgNotAvailableException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/PhpdbgNotAvailableException.php', + 'SebastianBergmann\\CodeCoverage\\Driver\\Selector' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Driver/Selector.php', + 'SebastianBergmann\\CodeCoverage\\Driver\\WriteOperationFailedException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/WriteOperationFailedException.php', + 'SebastianBergmann\\CodeCoverage\\Driver\\WrongXdebugVersionException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/WrongXdebugVersionException.php', + 'SebastianBergmann\\CodeCoverage\\Driver\\Xdebug2Driver' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Driver/Xdebug2Driver.php', + 'SebastianBergmann\\CodeCoverage\\Driver\\Xdebug2NotEnabledException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/Xdebug2NotEnabledException.php', + 'SebastianBergmann\\CodeCoverage\\Driver\\Xdebug3Driver' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Driver/Xdebug3Driver.php', + 'SebastianBergmann\\CodeCoverage\\Driver\\Xdebug3NotEnabledException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/Xdebug3NotEnabledException.php', + 'SebastianBergmann\\CodeCoverage\\Driver\\XdebugNotAvailableException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/XdebugNotAvailableException.php', + 'SebastianBergmann\\CodeCoverage\\Exception' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/Exception.php', + 'SebastianBergmann\\CodeCoverage\\Filter' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Filter.php', + 'SebastianBergmann\\CodeCoverage\\InvalidArgumentException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/InvalidArgumentException.php', + 'SebastianBergmann\\CodeCoverage\\NoCodeCoverageDriverAvailableException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/NoCodeCoverageDriverAvailableException.php', + 'SebastianBergmann\\CodeCoverage\\NoCodeCoverageDriverWithPathCoverageSupportAvailableException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/NoCodeCoverageDriverWithPathCoverageSupportAvailableException.php', + 'SebastianBergmann\\CodeCoverage\\Node\\AbstractNode' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Node/AbstractNode.php', + 'SebastianBergmann\\CodeCoverage\\Node\\Builder' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Node/Builder.php', + 'SebastianBergmann\\CodeCoverage\\Node\\CrapIndex' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Node/CrapIndex.php', + 'SebastianBergmann\\CodeCoverage\\Node\\Directory' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Node/Directory.php', + 'SebastianBergmann\\CodeCoverage\\Node\\File' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Node/File.php', + 'SebastianBergmann\\CodeCoverage\\Node\\Iterator' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Node/Iterator.php', + 'SebastianBergmann\\CodeCoverage\\ParserException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/ParserException.php', + 'SebastianBergmann\\CodeCoverage\\ProcessedCodeCoverageData' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/ProcessedCodeCoverageData.php', + 'SebastianBergmann\\CodeCoverage\\RawCodeCoverageData' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/RawCodeCoverageData.php', + 'SebastianBergmann\\CodeCoverage\\ReflectionException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/ReflectionException.php', + 'SebastianBergmann\\CodeCoverage\\ReportAlreadyFinalizedException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/ReportAlreadyFinalizedException.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Clover' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Clover.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Cobertura' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Cobertura.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Crap4j' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Crap4j.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Html\\Dashboard' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Html/Renderer/Dashboard.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Html\\Directory' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Html/Renderer/Directory.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Html\\Facade' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Html/Facade.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Html\\File' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Html/Renderer/File.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Html\\Renderer' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Html/Renderer.php', + 'SebastianBergmann\\CodeCoverage\\Report\\PHP' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/PHP.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Text' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Text.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\BuildInformation' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/BuildInformation.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Coverage' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Coverage.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Directory' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Directory.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Facade' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Facade.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\File' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/File.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Method' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Method.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Node' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Node.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Project' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Project.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Report' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Report.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Source' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Source.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Tests' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Tests.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Totals' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Totals.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Unit' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Unit.php', + 'SebastianBergmann\\CodeCoverage\\StaticAnalysisCacheNotConfiguredException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/StaticAnalysisCacheNotConfiguredException.php', + 'SebastianBergmann\\CodeCoverage\\StaticAnalysis\\CacheWarmer' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/StaticAnalysis/CacheWarmer.php', + 'SebastianBergmann\\CodeCoverage\\StaticAnalysis\\CachingFileAnalyser' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/StaticAnalysis/CachingFileAnalyser.php', + 'SebastianBergmann\\CodeCoverage\\StaticAnalysis\\CodeUnitFindingVisitor' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/StaticAnalysis/CodeUnitFindingVisitor.php', + 'SebastianBergmann\\CodeCoverage\\StaticAnalysis\\ExecutableLinesFindingVisitor' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/StaticAnalysis/ExecutableLinesFindingVisitor.php', + 'SebastianBergmann\\CodeCoverage\\StaticAnalysis\\FileAnalyser' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/StaticAnalysis/FileAnalyser.php', + 'SebastianBergmann\\CodeCoverage\\StaticAnalysis\\IgnoredLinesFindingVisitor' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/StaticAnalysis/IgnoredLinesFindingVisitor.php', + 'SebastianBergmann\\CodeCoverage\\StaticAnalysis\\ParsingFileAnalyser' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/StaticAnalysis/ParsingFileAnalyser.php', + 'SebastianBergmann\\CodeCoverage\\TestIdMissingException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/TestIdMissingException.php', + 'SebastianBergmann\\CodeCoverage\\UnintentionallyCoveredCodeException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/UnintentionallyCoveredCodeException.php', + 'SebastianBergmann\\CodeCoverage\\Util\\DirectoryCouldNotBeCreatedException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/DirectoryCouldNotBeCreatedException.php', + 'SebastianBergmann\\CodeCoverage\\Util\\Filesystem' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Util/Filesystem.php', + 'SebastianBergmann\\CodeCoverage\\Util\\Percentage' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Util/Percentage.php', + 'SebastianBergmann\\CodeCoverage\\Version' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Version.php', + 'SebastianBergmann\\CodeCoverage\\XmlException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/XmlException.php', + 'SebastianBergmann\\CodeUnitReverseLookup\\Wizard' => __DIR__ . '/..' . '/sebastian/code-unit-reverse-lookup/src/Wizard.php', + 'SebastianBergmann\\CodeUnit\\ClassMethodUnit' => __DIR__ . '/..' . '/sebastian/code-unit/src/ClassMethodUnit.php', + 'SebastianBergmann\\CodeUnit\\ClassUnit' => __DIR__ . '/..' . '/sebastian/code-unit/src/ClassUnit.php', + 'SebastianBergmann\\CodeUnit\\CodeUnit' => __DIR__ . '/..' . '/sebastian/code-unit/src/CodeUnit.php', + 'SebastianBergmann\\CodeUnit\\CodeUnitCollection' => __DIR__ . '/..' . '/sebastian/code-unit/src/CodeUnitCollection.php', + 'SebastianBergmann\\CodeUnit\\CodeUnitCollectionIterator' => __DIR__ . '/..' . '/sebastian/code-unit/src/CodeUnitCollectionIterator.php', + 'SebastianBergmann\\CodeUnit\\Exception' => __DIR__ . '/..' . '/sebastian/code-unit/src/exceptions/Exception.php', + 'SebastianBergmann\\CodeUnit\\FunctionUnit' => __DIR__ . '/..' . '/sebastian/code-unit/src/FunctionUnit.php', + 'SebastianBergmann\\CodeUnit\\InterfaceMethodUnit' => __DIR__ . '/..' . '/sebastian/code-unit/src/InterfaceMethodUnit.php', + 'SebastianBergmann\\CodeUnit\\InterfaceUnit' => __DIR__ . '/..' . '/sebastian/code-unit/src/InterfaceUnit.php', + 'SebastianBergmann\\CodeUnit\\InvalidCodeUnitException' => __DIR__ . '/..' . '/sebastian/code-unit/src/exceptions/InvalidCodeUnitException.php', + 'SebastianBergmann\\CodeUnit\\Mapper' => __DIR__ . '/..' . '/sebastian/code-unit/src/Mapper.php', + 'SebastianBergmann\\CodeUnit\\NoTraitException' => __DIR__ . '/..' . '/sebastian/code-unit/src/exceptions/NoTraitException.php', + 'SebastianBergmann\\CodeUnit\\ReflectionException' => __DIR__ . '/..' . '/sebastian/code-unit/src/exceptions/ReflectionException.php', + 'SebastianBergmann\\CodeUnit\\TraitMethodUnit' => __DIR__ . '/..' . '/sebastian/code-unit/src/TraitMethodUnit.php', + 'SebastianBergmann\\CodeUnit\\TraitUnit' => __DIR__ . '/..' . '/sebastian/code-unit/src/TraitUnit.php', + 'SebastianBergmann\\Comparator\\ArrayComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/ArrayComparator.php', + 'SebastianBergmann\\Comparator\\Comparator' => __DIR__ . '/..' . '/sebastian/comparator/src/Comparator.php', + 'SebastianBergmann\\Comparator\\ComparisonFailure' => __DIR__ . '/..' . '/sebastian/comparator/src/ComparisonFailure.php', + 'SebastianBergmann\\Comparator\\DOMNodeComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/DOMNodeComparator.php', + 'SebastianBergmann\\Comparator\\DateTimeComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/DateTimeComparator.php', + 'SebastianBergmann\\Comparator\\DoubleComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/DoubleComparator.php', + 'SebastianBergmann\\Comparator\\Exception' => __DIR__ . '/..' . '/sebastian/comparator/src/exceptions/Exception.php', + 'SebastianBergmann\\Comparator\\ExceptionComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/ExceptionComparator.php', + 'SebastianBergmann\\Comparator\\Factory' => __DIR__ . '/..' . '/sebastian/comparator/src/Factory.php', + 'SebastianBergmann\\Comparator\\MockObjectComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/MockObjectComparator.php', + 'SebastianBergmann\\Comparator\\NumericComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/NumericComparator.php', + 'SebastianBergmann\\Comparator\\ObjectComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/ObjectComparator.php', + 'SebastianBergmann\\Comparator\\ResourceComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/ResourceComparator.php', + 'SebastianBergmann\\Comparator\\RuntimeException' => __DIR__ . '/..' . '/sebastian/comparator/src/exceptions/RuntimeException.php', + 'SebastianBergmann\\Comparator\\ScalarComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/ScalarComparator.php', + 'SebastianBergmann\\Comparator\\SplObjectStorageComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/SplObjectStorageComparator.php', + 'SebastianBergmann\\Comparator\\TypeComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/TypeComparator.php', + 'SebastianBergmann\\Complexity\\Calculator' => __DIR__ . '/..' . '/sebastian/complexity/src/Calculator.php', + 'SebastianBergmann\\Complexity\\Complexity' => __DIR__ . '/..' . '/sebastian/complexity/src/Complexity/Complexity.php', + 'SebastianBergmann\\Complexity\\ComplexityCalculatingVisitor' => __DIR__ . '/..' . '/sebastian/complexity/src/Visitor/ComplexityCalculatingVisitor.php', + 'SebastianBergmann\\Complexity\\ComplexityCollection' => __DIR__ . '/..' . '/sebastian/complexity/src/Complexity/ComplexityCollection.php', + 'SebastianBergmann\\Complexity\\ComplexityCollectionIterator' => __DIR__ . '/..' . '/sebastian/complexity/src/Complexity/ComplexityCollectionIterator.php', + 'SebastianBergmann\\Complexity\\CyclomaticComplexityCalculatingVisitor' => __DIR__ . '/..' . '/sebastian/complexity/src/Visitor/CyclomaticComplexityCalculatingVisitor.php', + 'SebastianBergmann\\Complexity\\Exception' => __DIR__ . '/..' . '/sebastian/complexity/src/Exception/Exception.php', + 'SebastianBergmann\\Complexity\\RuntimeException' => __DIR__ . '/..' . '/sebastian/complexity/src/Exception/RuntimeException.php', + 'SebastianBergmann\\Diff\\Chunk' => __DIR__ . '/..' . '/sebastian/diff/src/Chunk.php', + 'SebastianBergmann\\Diff\\ConfigurationException' => __DIR__ . '/..' . '/sebastian/diff/src/Exception/ConfigurationException.php', + 'SebastianBergmann\\Diff\\Diff' => __DIR__ . '/..' . '/sebastian/diff/src/Diff.php', + 'SebastianBergmann\\Diff\\Differ' => __DIR__ . '/..' . '/sebastian/diff/src/Differ.php', + 'SebastianBergmann\\Diff\\Exception' => __DIR__ . '/..' . '/sebastian/diff/src/Exception/Exception.php', + 'SebastianBergmann\\Diff\\InvalidArgumentException' => __DIR__ . '/..' . '/sebastian/diff/src/Exception/InvalidArgumentException.php', + 'SebastianBergmann\\Diff\\Line' => __DIR__ . '/..' . '/sebastian/diff/src/Line.php', + 'SebastianBergmann\\Diff\\LongestCommonSubsequenceCalculator' => __DIR__ . '/..' . '/sebastian/diff/src/LongestCommonSubsequenceCalculator.php', + 'SebastianBergmann\\Diff\\MemoryEfficientLongestCommonSubsequenceCalculator' => __DIR__ . '/..' . '/sebastian/diff/src/MemoryEfficientLongestCommonSubsequenceCalculator.php', + 'SebastianBergmann\\Diff\\Output\\AbstractChunkOutputBuilder' => __DIR__ . '/..' . '/sebastian/diff/src/Output/AbstractChunkOutputBuilder.php', + 'SebastianBergmann\\Diff\\Output\\DiffOnlyOutputBuilder' => __DIR__ . '/..' . '/sebastian/diff/src/Output/DiffOnlyOutputBuilder.php', + 'SebastianBergmann\\Diff\\Output\\DiffOutputBuilderInterface' => __DIR__ . '/..' . '/sebastian/diff/src/Output/DiffOutputBuilderInterface.php', + 'SebastianBergmann\\Diff\\Output\\StrictUnifiedDiffOutputBuilder' => __DIR__ . '/..' . '/sebastian/diff/src/Output/StrictUnifiedDiffOutputBuilder.php', + 'SebastianBergmann\\Diff\\Output\\UnifiedDiffOutputBuilder' => __DIR__ . '/..' . '/sebastian/diff/src/Output/UnifiedDiffOutputBuilder.php', + 'SebastianBergmann\\Diff\\Parser' => __DIR__ . '/..' . '/sebastian/diff/src/Parser.php', + 'SebastianBergmann\\Diff\\TimeEfficientLongestCommonSubsequenceCalculator' => __DIR__ . '/..' . '/sebastian/diff/src/TimeEfficientLongestCommonSubsequenceCalculator.php', + 'SebastianBergmann\\Environment\\Console' => __DIR__ . '/..' . '/sebastian/environment/src/Console.php', + 'SebastianBergmann\\Environment\\OperatingSystem' => __DIR__ . '/..' . '/sebastian/environment/src/OperatingSystem.php', + 'SebastianBergmann\\Environment\\Runtime' => __DIR__ . '/..' . '/sebastian/environment/src/Runtime.php', + 'SebastianBergmann\\Exporter\\Exporter' => __DIR__ . '/..' . '/sebastian/exporter/src/Exporter.php', + 'SebastianBergmann\\FileIterator\\Facade' => __DIR__ . '/..' . '/phpunit/php-file-iterator/src/Facade.php', + 'SebastianBergmann\\FileIterator\\Factory' => __DIR__ . '/..' . '/phpunit/php-file-iterator/src/Factory.php', + 'SebastianBergmann\\FileIterator\\Iterator' => __DIR__ . '/..' . '/phpunit/php-file-iterator/src/Iterator.php', + 'SebastianBergmann\\GlobalState\\CodeExporter' => __DIR__ . '/..' . '/sebastian/global-state/src/CodeExporter.php', + 'SebastianBergmann\\GlobalState\\Exception' => __DIR__ . '/..' . '/sebastian/global-state/src/exceptions/Exception.php', + 'SebastianBergmann\\GlobalState\\ExcludeList' => __DIR__ . '/..' . '/sebastian/global-state/src/ExcludeList.php', + 'SebastianBergmann\\GlobalState\\Restorer' => __DIR__ . '/..' . '/sebastian/global-state/src/Restorer.php', + 'SebastianBergmann\\GlobalState\\RuntimeException' => __DIR__ . '/..' . '/sebastian/global-state/src/exceptions/RuntimeException.php', + 'SebastianBergmann\\GlobalState\\Snapshot' => __DIR__ . '/..' . '/sebastian/global-state/src/Snapshot.php', + 'SebastianBergmann\\Invoker\\Exception' => __DIR__ . '/..' . '/phpunit/php-invoker/src/exceptions/Exception.php', + 'SebastianBergmann\\Invoker\\Invoker' => __DIR__ . '/..' . '/phpunit/php-invoker/src/Invoker.php', + 'SebastianBergmann\\Invoker\\ProcessControlExtensionNotLoadedException' => __DIR__ . '/..' . '/phpunit/php-invoker/src/exceptions/ProcessControlExtensionNotLoadedException.php', + 'SebastianBergmann\\Invoker\\TimeoutException' => __DIR__ . '/..' . '/phpunit/php-invoker/src/exceptions/TimeoutException.php', + 'SebastianBergmann\\LinesOfCode\\Counter' => __DIR__ . '/..' . '/sebastian/lines-of-code/src/Counter.php', + 'SebastianBergmann\\LinesOfCode\\Exception' => __DIR__ . '/..' . '/sebastian/lines-of-code/src/Exception/Exception.php', + 'SebastianBergmann\\LinesOfCode\\IllogicalValuesException' => __DIR__ . '/..' . '/sebastian/lines-of-code/src/Exception/IllogicalValuesException.php', + 'SebastianBergmann\\LinesOfCode\\LineCountingVisitor' => __DIR__ . '/..' . '/sebastian/lines-of-code/src/LineCountingVisitor.php', + 'SebastianBergmann\\LinesOfCode\\LinesOfCode' => __DIR__ . '/..' . '/sebastian/lines-of-code/src/LinesOfCode.php', + 'SebastianBergmann\\LinesOfCode\\NegativeValueException' => __DIR__ . '/..' . '/sebastian/lines-of-code/src/Exception/NegativeValueException.php', + 'SebastianBergmann\\LinesOfCode\\RuntimeException' => __DIR__ . '/..' . '/sebastian/lines-of-code/src/Exception/RuntimeException.php', + 'SebastianBergmann\\ObjectEnumerator\\Enumerator' => __DIR__ . '/..' . '/sebastian/object-enumerator/src/Enumerator.php', + 'SebastianBergmann\\ObjectEnumerator\\Exception' => __DIR__ . '/..' . '/sebastian/object-enumerator/src/Exception.php', + 'SebastianBergmann\\ObjectEnumerator\\InvalidArgumentException' => __DIR__ . '/..' . '/sebastian/object-enumerator/src/InvalidArgumentException.php', + 'SebastianBergmann\\ObjectReflector\\Exception' => __DIR__ . '/..' . '/sebastian/object-reflector/src/Exception.php', + 'SebastianBergmann\\ObjectReflector\\InvalidArgumentException' => __DIR__ . '/..' . '/sebastian/object-reflector/src/InvalidArgumentException.php', + 'SebastianBergmann\\ObjectReflector\\ObjectReflector' => __DIR__ . '/..' . '/sebastian/object-reflector/src/ObjectReflector.php', + 'SebastianBergmann\\RecursionContext\\Context' => __DIR__ . '/..' . '/sebastian/recursion-context/src/Context.php', + 'SebastianBergmann\\RecursionContext\\Exception' => __DIR__ . '/..' . '/sebastian/recursion-context/src/Exception.php', + 'SebastianBergmann\\RecursionContext\\InvalidArgumentException' => __DIR__ . '/..' . '/sebastian/recursion-context/src/InvalidArgumentException.php', + 'SebastianBergmann\\ResourceOperations\\ResourceOperations' => __DIR__ . '/..' . '/sebastian/resource-operations/src/ResourceOperations.php', + 'SebastianBergmann\\Template\\Exception' => __DIR__ . '/..' . '/phpunit/php-text-template/src/exceptions/Exception.php', + 'SebastianBergmann\\Template\\InvalidArgumentException' => __DIR__ . '/..' . '/phpunit/php-text-template/src/exceptions/InvalidArgumentException.php', + 'SebastianBergmann\\Template\\RuntimeException' => __DIR__ . '/..' . '/phpunit/php-text-template/src/exceptions/RuntimeException.php', + 'SebastianBergmann\\Template\\Template' => __DIR__ . '/..' . '/phpunit/php-text-template/src/Template.php', + 'SebastianBergmann\\Timer\\Duration' => __DIR__ . '/..' . '/phpunit/php-timer/src/Duration.php', + 'SebastianBergmann\\Timer\\Exception' => __DIR__ . '/..' . '/phpunit/php-timer/src/exceptions/Exception.php', + 'SebastianBergmann\\Timer\\NoActiveTimerException' => __DIR__ . '/..' . '/phpunit/php-timer/src/exceptions/NoActiveTimerException.php', + 'SebastianBergmann\\Timer\\ResourceUsageFormatter' => __DIR__ . '/..' . '/phpunit/php-timer/src/ResourceUsageFormatter.php', + 'SebastianBergmann\\Timer\\TimeSinceStartOfRequestNotAvailableException' => __DIR__ . '/..' . '/phpunit/php-timer/src/exceptions/TimeSinceStartOfRequestNotAvailableException.php', + 'SebastianBergmann\\Timer\\Timer' => __DIR__ . '/..' . '/phpunit/php-timer/src/Timer.php', + 'SebastianBergmann\\Type\\CallableType' => __DIR__ . '/..' . '/sebastian/type/src/type/CallableType.php', + 'SebastianBergmann\\Type\\Exception' => __DIR__ . '/..' . '/sebastian/type/src/exception/Exception.php', + 'SebastianBergmann\\Type\\FalseType' => __DIR__ . '/..' . '/sebastian/type/src/type/FalseType.php', + 'SebastianBergmann\\Type\\GenericObjectType' => __DIR__ . '/..' . '/sebastian/type/src/type/GenericObjectType.php', + 'SebastianBergmann\\Type\\IntersectionType' => __DIR__ . '/..' . '/sebastian/type/src/type/IntersectionType.php', + 'SebastianBergmann\\Type\\IterableType' => __DIR__ . '/..' . '/sebastian/type/src/type/IterableType.php', + 'SebastianBergmann\\Type\\MixedType' => __DIR__ . '/..' . '/sebastian/type/src/type/MixedType.php', + 'SebastianBergmann\\Type\\NeverType' => __DIR__ . '/..' . '/sebastian/type/src/type/NeverType.php', + 'SebastianBergmann\\Type\\NullType' => __DIR__ . '/..' . '/sebastian/type/src/type/NullType.php', + 'SebastianBergmann\\Type\\ObjectType' => __DIR__ . '/..' . '/sebastian/type/src/type/ObjectType.php', + 'SebastianBergmann\\Type\\Parameter' => __DIR__ . '/..' . '/sebastian/type/src/Parameter.php', + 'SebastianBergmann\\Type\\ReflectionMapper' => __DIR__ . '/..' . '/sebastian/type/src/ReflectionMapper.php', + 'SebastianBergmann\\Type\\RuntimeException' => __DIR__ . '/..' . '/sebastian/type/src/exception/RuntimeException.php', + 'SebastianBergmann\\Type\\SimpleType' => __DIR__ . '/..' . '/sebastian/type/src/type/SimpleType.php', + 'SebastianBergmann\\Type\\StaticType' => __DIR__ . '/..' . '/sebastian/type/src/type/StaticType.php', + 'SebastianBergmann\\Type\\TrueType' => __DIR__ . '/..' . '/sebastian/type/src/type/TrueType.php', + 'SebastianBergmann\\Type\\Type' => __DIR__ . '/..' . '/sebastian/type/src/type/Type.php', + 'SebastianBergmann\\Type\\TypeName' => __DIR__ . '/..' . '/sebastian/type/src/TypeName.php', + 'SebastianBergmann\\Type\\UnionType' => __DIR__ . '/..' . '/sebastian/type/src/type/UnionType.php', + 'SebastianBergmann\\Type\\UnknownType' => __DIR__ . '/..' . '/sebastian/type/src/type/UnknownType.php', + 'SebastianBergmann\\Type\\VoidType' => __DIR__ . '/..' . '/sebastian/type/src/type/VoidType.php', + 'SebastianBergmann\\Version' => __DIR__ . '/..' . '/sebastian/version/src/Version.php', + 'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'TheSeer\\Tokenizer\\Exception' => __DIR__ . '/..' . '/theseer/tokenizer/src/Exception.php', + 'TheSeer\\Tokenizer\\NamespaceUri' => __DIR__ . '/..' . '/theseer/tokenizer/src/NamespaceUri.php', + 'TheSeer\\Tokenizer\\NamespaceUriException' => __DIR__ . '/..' . '/theseer/tokenizer/src/NamespaceUriException.php', + 'TheSeer\\Tokenizer\\Token' => __DIR__ . '/..' . '/theseer/tokenizer/src/Token.php', + 'TheSeer\\Tokenizer\\TokenCollection' => __DIR__ . '/..' . '/theseer/tokenizer/src/TokenCollection.php', + 'TheSeer\\Tokenizer\\TokenCollectionException' => __DIR__ . '/..' . '/theseer/tokenizer/src/TokenCollectionException.php', + 'TheSeer\\Tokenizer\\Tokenizer' => __DIR__ . '/..' . '/theseer/tokenizer/src/Tokenizer.php', + 'TheSeer\\Tokenizer\\XMLSerializer' => __DIR__ . '/..' . '/theseer/tokenizer/src/XMLSerializer.php', + 'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', + 'phpCAS' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInitafdf4419c977e377e00fce23bd5a0744::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInitafdf4419c977e377e00fce23bd5a0744::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInitafdf4419c977e377e00fce23bd5a0744::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json new file mode 100644 index 00000000..02e76d1e --- /dev/null +++ b/vendor/composer/installed.json @@ -0,0 +1,4481 @@ +{ + "packages": [ + { + "name": "apereo/phpcas", + "version": "1.6.1", + "version_normalized": "1.6.1.0", + "source": { + "type": "git", + "url": "https://github.com/apereo/phpCAS.git", + "reference": "c129708154852656aabb13d8606cd5b12dbbabac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/apereo/phpCAS/zipball/c129708154852656aabb13d8606cd5b12dbbabac", + "reference": "c129708154852656aabb13d8606cd5b12dbbabac", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-dom": "*", + "php": ">=7.1.0", + "psr/log": "^1.0 || ^2.0 || ^3.0" + }, + "require-dev": { + "monolog/monolog": "^1.0.0 || ^2.0.0", + "phpstan/phpstan": "^1.5", + "phpunit/phpunit": ">=7.5" + }, + "time": "2023-02-19T19:52:35+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "source/CAS.php" + ], + "classmap": [ + "source/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Joachim Fritschi", + "email": "jfritschi@freenet.de", + "homepage": "https://github.com/jfritschi" + }, + { + "name": "Adam Franco", + "homepage": "https://github.com/adamfranco" + }, + { + "name": "Henry Pan", + "homepage": "https://github.com/phy25" + } + ], + "description": "Provides a simple API for authenticating users against a CAS server", + "homepage": "https://wiki.jasig.org/display/CASC/phpCAS", + "keywords": [ + "apereo", + "cas", + "jasig" + ], + "support": { + "issues": "https://github.com/apereo/phpCAS/issues", + "source": "https://github.com/apereo/phpCAS/tree/1.6.1" + }, + "install-path": "../apereo/phpcas" + }, + { + "name": "composer/pcre", + "version": "3.1.2", + "version_normalized": "3.1.2.0", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "4775f35b2d70865807c89d32c8e7385b86eb0ace" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/4775f35b2d70865807c89d32c8e7385b86eb0ace", + "reference": "4775f35b2d70865807c89d32c8e7385b86eb0ace", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.3", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/phpunit-bridge": "^5" + }, + "time": "2024-03-07T15:38:35+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.1.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "install-path": "./pcre" + }, + { + "name": "composer/semver", + "version": "3.4.0", + "version_normalized": "3.4.0.0", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32", + "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "time": "2023-08-31T09:50:34+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "install-path": "./semver" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.3", + "version_normalized": "3.0.3.0", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "ced299686f41dce890debac69273b47ffe98a40c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ced299686f41dce890debac69273b47ffe98a40c", + "reference": "ced299686f41dce890debac69273b47ffe98a40c", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/phpunit-bridge": "^6.0" + }, + "time": "2022-02-25T21:32:43+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.3" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "install-path": "./xdebug-handler" + }, + { + "name": "doctrine/instantiator", + "version": "2.0.0", + "version_normalized": "2.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^11", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^5.4" + }, + "time": "2022-12-30T00:23:10+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/2.0.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "install-path": "../doctrine/instantiator" + }, + { + "name": "friendsofphp/php-cs-fixer", + "version": "v3.51.0", + "version_normalized": "3.51.0.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", + "reference": "127fa74f010da99053e3f5b62672615b72dd6efd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/127fa74f010da99053e3f5b62672615b72dd6efd", + "reference": "127fa74f010da99053e3f5b62672615b72dd6efd", + "shasum": "" + }, + "require": { + "composer/semver": "^3.4", + "composer/xdebug-handler": "^3.0.3", + "ext-filter": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0", + "sebastian/diff": "^4.0 || ^5.0 || ^6.0", + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/event-dispatcher": "^5.4 || ^6.0 || ^7.0", + "symfony/filesystem": "^5.4 || ^6.0 || ^7.0", + "symfony/finder": "^5.4 || ^6.0 || ^7.0", + "symfony/options-resolver": "^5.4 || ^6.0 || ^7.0", + "symfony/polyfill-mbstring": "^1.28", + "symfony/polyfill-php80": "^1.28", + "symfony/polyfill-php81": "^1.28", + "symfony/process": "^5.4 || ^6.0 || ^7.0", + "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0" + }, + "require-dev": { + "facile-it/paraunit": "^1.3 || ^2.0", + "justinrainbow/json-schema": "^5.2", + "keradus/cli-executor": "^2.1", + "mikey179/vfsstream": "^1.6.11", + "php-coveralls/php-coveralls": "^2.7", + "php-cs-fixer/accessible-object": "^1.1", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.4", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.4", + "phpunit/phpunit": "^9.6 || ^10.5.5 || ^11.0.2", + "symfony/var-dumper": "^5.4 || ^6.0 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "time": "2024-02-28T19:50:06+00:00", + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "installation-source": "dist", + "autoload": { + "psr-4": { + "PhpCsFixer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "keywords": [ + "Static code analysis", + "fixer", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.51.0" + }, + "funding": [ + { + "url": "https://github.com/keradus", + "type": "github" + } + ], + "install-path": "../friendsofphp/php-cs-fixer" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.8.1", + "version_normalized": "7.8.1.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "41042bc7ab002487b876a0683fc8dce04ddce104" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104", + "reference": "41042bc7ab002487b876a0683fc8dce04ddce104", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5.3 || ^2.0.1", + "guzzlehttp/psr7": "^1.9.1 || ^2.5.1", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.36 || ^9.6.15", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "time": "2023-12-03T20:35:24+00:00", + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.8.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "install-path": "../guzzlehttp/guzzle" + }, + { + "name": "guzzlehttp/promises", + "version": "2.0.2", + "version_normalized": "2.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/bbff78d96034045e58e13dedd6ad91b5d1253223", + "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.36 || ^9.6.15" + }, + "time": "2023-12-03T20:19:20+00:00", + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "install-path": "../guzzlehttp/promises" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.6.2", + "version_normalized": "2.6.2.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/45b30f99ac27b5ca93cb4831afe16285f57b8221", + "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.36 || ^9.6.15" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "time": "2023-12-03T20:05:35+00:00", + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.6.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "install-path": "../guzzlehttp/psr7" + }, + { + "name": "kubawerlos/php-cs-fixer-custom-fixers", + "version": "v3.21.0", + "version_normalized": "3.21.0.0", + "source": { + "type": "git", + "url": "https://github.com/kubawerlos/php-cs-fixer-custom-fixers.git", + "reference": "3d1bb75be0df6c6fba4487c75b9e425a2c1d27c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kubawerlos/php-cs-fixer-custom-fixers/zipball/3d1bb75be0df6c6fba4487c75b9e425a2c1d27c9", + "reference": "3d1bb75be0df6c6fba4487c75b9e425a2c1d27c9", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "ext-tokenizer": "*", + "friendsofphp/php-cs-fixer": "^3.50", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6.4 || ^10.0.14" + }, + "time": "2024-02-24T08:53:34+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "PhpCsFixerCustomFixers\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kuba Werłos", + "email": "werlos@gmail.com" + } + ], + "description": "A set of custom fixers for PHP CS Fixer", + "support": { + "issues": "https://github.com/kubawerlos/php-cs-fixer-custom-fixers/issues", + "source": "https://github.com/kubawerlos/php-cs-fixer-custom-fixers/tree/v3.21.0" + }, + "install-path": "../kubawerlos/php-cs-fixer-custom-fixers" + }, + { + "name": "league/oauth2-client", + "version": "2.7.0", + "version_normalized": "2.7.0.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth2-client.git", + "reference": "160d6274b03562ebeb55ed18399281d8118b76c8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/160d6274b03562ebeb55ed18399281d8118b76c8", + "reference": "160d6274b03562ebeb55ed18399281d8118b76c8", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^6.0 || ^7.0", + "paragonie/random_compat": "^1 || ^2 || ^9.99", + "php": "^5.6 || ^7.0 || ^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.3.5", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpunit/phpunit": "^5.7 || ^6.0 || ^9.5", + "squizlabs/php_codesniffer": "^2.3 || ^3.0" + }, + "time": "2023-04-16T18:19:15+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "League\\OAuth2\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Bilbie", + "email": "hello@alexbilbie.com", + "homepage": "http://www.alexbilbie.com", + "role": "Developer" + }, + { + "name": "Woody Gilk", + "homepage": "https://github.com/shadowhand", + "role": "Contributor" + } + ], + "description": "OAuth 2.0 Client Library", + "keywords": [ + "Authentication", + "SSO", + "authorization", + "identity", + "idp", + "oauth", + "oauth2", + "single sign on" + ], + "support": { + "issues": "https://github.com/thephpleague/oauth2-client/issues", + "source": "https://github.com/thephpleague/oauth2-client/tree/2.7.0" + }, + "install-path": "../league/oauth2-client" + }, + { + "name": "myclabs/deep-copy", + "version": "1.11.1", + "version_normalized": "1.11.1.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3,<3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "time": "2023-03-08T13:26:56+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "install-path": "../myclabs/deep-copy" + }, + { + "name": "nikic/php-parser", + "version": "v5.0.2", + "version_normalized": "5.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/139676794dc1e9231bf7bcd123cfc0c99182cb13", + "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + }, + "time": "2024-03-05T20:51:40+00:00", + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.2" + }, + "install-path": "../nikic/php-parser" + }, + { + "name": "onelogin/php-saml", + "version": "3.6.1", + "version_normalized": "3.6.1.0", + "source": { + "type": "git", + "url": "https://github.com/onelogin/php-saml.git", + "reference": "a7328b11887660ad248ea10952dd67a5aa73ba3b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/onelogin/php-saml/zipball/a7328b11887660ad248ea10952dd67a5aa73ba3b", + "reference": "a7328b11887660ad248ea10952dd67a5aa73ba3b", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "robrichards/xmlseclibs": ">=3.1.1" + }, + "require-dev": { + "pdepend/pdepend": "^2.5.0", + "php-coveralls/php-coveralls": "^1.0.2 || ^2.0", + "phploc/phploc": "^2.1 || ^3.0 || ^4.0", + "phpunit/phpunit": "<7.5.18", + "sebastian/phpcpd": "^2.0 || ^3.0 || ^4.0", + "squizlabs/php_codesniffer": "^3.1.1" + }, + "suggest": { + "ext-curl": "Install curl lib to be able to use the IdPMetadataParser for parsing remote XMLs", + "ext-gettext": "Install gettext and php5-gettext libs to handle translations", + "ext-openssl": "Install openssl lib in order to handle with x509 certs (require to support sign and encryption)" + }, + "time": "2021-03-02T10:13:07+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "OneLogin\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "OneLogin PHP SAML Toolkit", + "homepage": "https://developers.onelogin.com/saml/php", + "keywords": [ + "SAML2", + "onelogin", + "saml" + ], + "support": { + "email": "sixto.garcia@onelogin.com", + "issues": "https://github.com/onelogin/php-saml/issues", + "source": "https://github.com/onelogin/php-saml/" + }, + "install-path": "../onelogin/php-saml" + }, + { + "name": "paragonie/random_compat", + "version": "v9.99.100", + "version_normalized": "9.99.100.0", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", + "shasum": "" + }, + "require": { + "php": ">= 7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "time": "2020-10-15T08:29:30+00:00", + "type": "library", + "installation-source": "dist", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/random_compat/issues", + "source": "https://github.com/paragonie/random_compat" + }, + "install-path": "../paragonie/random_compat" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "version_normalized": "2.0.4.0", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "time": "2024-03-03T12:33:53+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "install-path": "../phar-io/manifest" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "version_normalized": "3.2.1.0", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "time": "2022-02-21T01:04:05+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "install-path": "../phar-io/version" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.31", + "version_normalized": "9.2.31.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/48c34b5d8d983006bd2adc2d0de92963b9155965", + "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0.3", + "sebastian/version": "^3.0.1", + "theseer/tokenizer": "^1.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "time": "2024-03-02T06:37:42+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.2-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.31" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "install-path": "../phpunit/php-code-coverage" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.6", + "version_normalized": "3.0.6.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "time": "2021-12-02T12:48:52+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "install-path": "../phpunit/php-file-iterator" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "version_normalized": "3.1.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "time": "2020-09-28T05:58:55+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "install-path": "../phpunit/php-invoker" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.4", + "version_normalized": "2.0.4.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "time": "2020-10-26T05:33:50+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "install-path": "../phpunit/php-text-template" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.3", + "version_normalized": "5.0.3.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "time": "2020-10-26T13:16:10+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "install-path": "../phpunit/php-timer" + }, + { + "name": "phpunit/phpunit", + "version": "9.6.17", + "version_normalized": "9.6.17.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "1a156980d78a6666721b7e8e8502fe210b587fcd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1a156980d78a6666721b7e8e8502fe210b587fcd", + "reference": "1a156980d78a6666721b7e8e8502fe210b587fcd", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.3.1 || ^2", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=7.3", + "phpunit/php-code-coverage": "^9.2.28", + "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.3", + "phpunit/php-timer": "^5.0.2", + "sebastian/cli-parser": "^1.0.1", + "sebastian/code-unit": "^1.0.6", + "sebastian/comparator": "^4.0.8", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.5", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^3.2", + "sebastian/version": "^3.0.2" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "time": "2024-02-23T13:14:51+00:00", + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.6-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.17" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "install-path": "../phpunit/phpunit" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "version_normalized": "1.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "time": "2019-01-08T18:20:26+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "install-path": "../psr/event-dispatcher" + }, + { + "name": "psr/http-client", + "version": "1.0.3", + "version_normalized": "1.0.3.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "time": "2023-09-23T14:17:50+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "install-path": "../psr/http-client" + }, + { + "name": "psr/http-factory", + "version": "1.0.2", + "version_normalized": "1.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "e616d01114759c4c489f93b099585439f795fe35" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", + "reference": "e616d01114759c4c489f93b099585439f795fe35", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "time": "2023-04-10T20:10:41+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/1.0.2" + }, + "install-path": "../psr/http-factory" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "version_normalized": "3.0.3.0", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "time": "2019-03-08T08:55:37+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "install-path": "../ralouphie/getallheaders" + }, + { + "name": "redaxo/php-cs-fixer-config", + "version": "2.7.0", + "version_normalized": "2.7.0.0", + "source": { + "type": "git", + "url": "https://github.com/redaxo/php-cs-fixer-config.git", + "reference": "4a76aa9b8ade7dd440ac35eaaacf6df7daa275fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/redaxo/php-cs-fixer-config/zipball/4a76aa9b8ade7dd440ac35eaaacf6df7daa275fb", + "reference": "4a76aa9b8ade7dd440ac35eaaacf6df7daa275fb", + "shasum": "" + }, + "require": { + "friendsofphp/php-cs-fixer": "^3.51.0", + "kubawerlos/php-cs-fixer-custom-fixers": "^3.21.0", + "php": "^8.1" + }, + "time": "2024-03-12T14:03:46+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Redaxo\\PhpCsFixerConfig\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "REDAXO Team", + "email": "info@redaxo.org" + } + ], + "description": "php-cs-fixer config for REDAXO", + "keywords": [ + "Static code analysis", + "fixer", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/redaxo/php-cs-fixer-config/issues", + "source": "https://github.com/redaxo/php-cs-fixer-config/tree/2.7.0" + }, + "install-path": "../redaxo/php-cs-fixer-config" + }, + { + "name": "robrichards/xmlseclibs", + "version": "3.1.1", + "version_normalized": "3.1.1.0", + "source": { + "type": "git", + "url": "https://github.com/robrichards/xmlseclibs.git", + "reference": "f8f19e58f26cdb42c54b214ff8a820760292f8df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/robrichards/xmlseclibs/zipball/f8f19e58f26cdb42c54b214ff8a820760292f8df", + "reference": "f8f19e58f26cdb42c54b214ff8a820760292f8df", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "php": ">= 5.4" + }, + "time": "2020-09-05T13:00:25+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "RobRichards\\XMLSecLibs\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "A PHP library for XML Security", + "homepage": "https://github.com/robrichards/xmlseclibs", + "keywords": [ + "security", + "signature", + "xml", + "xmldsig" + ], + "support": { + "issues": "https://github.com/robrichards/xmlseclibs/issues", + "source": "https://github.com/robrichards/xmlseclibs/tree/3.1.1" + }, + "install-path": "../robrichards/xmlseclibs" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.2", + "version_normalized": "1.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "time": "2024-03-02T06:27:43+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "install-path": "../sebastian/cli-parser" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "version_normalized": "1.0.8.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "time": "2020-10-26T13:08:54+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "install-path": "../sebastian/code-unit" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "version_normalized": "2.0.3.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "time": "2020-09-28T05:30:19+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "install-path": "../sebastian/code-unit-reverse-lookup" + }, + { + "name": "sebastian/comparator", + "version": "4.0.8", + "version_normalized": "4.0.8.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "time": "2022-09-14T12:41:17+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "install-path": "../sebastian/comparator" + }, + { + "name": "sebastian/complexity", + "version": "2.0.3", + "version_normalized": "2.0.3.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "time": "2023-12-22T06:19:30+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "install-path": "../sebastian/complexity" + }, + { + "name": "sebastian/diff", + "version": "4.0.6", + "version_normalized": "4.0.6.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "time": "2024-03-02T06:30:58+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "install-path": "../sebastian/diff" + }, + { + "name": "sebastian/environment", + "version": "5.1.5", + "version_normalized": "5.1.5.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "time": "2023-02-03T06:03:51+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "install-path": "../sebastian/environment" + }, + { + "name": "sebastian/exporter", + "version": "4.0.6", + "version_normalized": "4.0.6.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "time": "2024-03-02T06:33:00+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "install-path": "../sebastian/exporter" + }, + { + "name": "sebastian/global-state", + "version": "5.0.7", + "version_normalized": "5.0.7.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "time": "2024-03-02T06:35:11+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "install-path": "../sebastian/global-state" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.4", + "version_normalized": "1.0.4.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "time": "2023-12-22T06:20:34+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "install-path": "../sebastian/lines-of-code" + }, + { + "name": "sebastian/object-enumerator", + "version": "4.0.4", + "version_normalized": "4.0.4.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "time": "2020-10-26T13:12:34+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "install-path": "../sebastian/object-enumerator" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.4", + "version_normalized": "2.0.4.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "time": "2020-10-26T13:14:26+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "install-path": "../sebastian/object-reflector" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.5", + "version_normalized": "4.0.5.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "time": "2023-02-03T06:07:39+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "install-path": "../sebastian/recursion-context" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.3", + "version_normalized": "3.0.3.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "time": "2020-09-28T06:45:17+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "install-path": "../sebastian/resource-operations" + }, + { + "name": "sebastian/type", + "version": "3.2.1", + "version_normalized": "3.2.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "time": "2023-02-03T06:13:03+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "install-path": "../sebastian/type" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "version_normalized": "3.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "time": "2020-09-28T06:39:44+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "install-path": "../sebastian/version" + }, + { + "name": "symfony/console", + "version": "v7.0.4", + "version_normalized": "7.0.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "6b099f3306f7c9c2d2786ed736d0026b2903205f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/6b099f3306f7c9c2d2786ed736d0026b2903205f", + "reference": "6b099f3306f7c9c2d2786ed736d0026b2903205f", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^6.4|^7.0" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "time": "2024-02-22T20:27:20+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v7.0.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/console" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.4.0", + "version_normalized": "3.4.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "time": "2023-05-23T14:45:45+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/deprecation-contracts" + }, + { + "name": "symfony/event-dispatcher", + "version": "v7.0.3", + "version_normalized": "7.0.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "834c28d533dd0636f910909d01b9ff45cc094b5e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/834c28d533dd0636f910909d01b9ff45cc094b5e", + "reference": "834c28d533dd0636f910909d01b9ff45cc094b5e", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/error-handler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^6.4|^7.0" + }, + "time": "2024-01-23T15:02:46+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v7.0.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/event-dispatcher" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.4.0", + "version_normalized": "3.4.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "a76aed96a42d2b521153fb382d418e30d18b59df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/a76aed96a42d2b521153fb382d418e30d18b59df", + "reference": "a76aed96a42d2b521153fb382d418e30d18b59df", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "time": "2023-05-23T14:45:45+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/event-dispatcher-contracts" + }, + { + "name": "symfony/filesystem", + "version": "v7.0.3", + "version_normalized": "7.0.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "2890e3a825bc0c0558526c04499c13f83e1b6b12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/2890e3a825bc0c0558526c04499c13f83e1b6b12", + "reference": "2890e3a825bc0c0558526c04499c13f83e1b6b12", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "time": "2024-01-23T15:02:46+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v7.0.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/filesystem" + }, + { + "name": "symfony/finder", + "version": "v7.0.0", + "version_normalized": "7.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "6e5688d69f7cfc4ed4a511e96007e06c2d34ce56" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/6e5688d69f7cfc4ed4a511e96007e06c2d34ce56", + "reference": "6e5688d69f7cfc4ed4a511e96007e06c2d34ce56", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/filesystem": "^6.4|^7.0" + }, + "time": "2023-10-31T17:59:56+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v7.0.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/finder" + }, + { + "name": "symfony/options-resolver", + "version": "v7.0.0", + "version_normalized": "7.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "700ff4096e346f54cb628ea650767c8130f1001f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/700ff4096e346f54cb628ea650767c8130f1001f", + "reference": "700ff4096e346f54cb628ea650767c8130f1001f", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "time": "2023-08-08T10:20:21+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an improved replacement for the array_replace PHP function", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v7.0.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/options-resolver" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.29.0", + "version_normalized": "1.29.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4", + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "time": "2024-01-29T20:11:03+00:00", + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-ctype" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.29.0", + "version_normalized": "1.29.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/32a9da87d7b3245e09ac426c83d334ae9f06f80f", + "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "time": "2024-01-29T20:11:03+00:00", + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-intl-grapheme" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.29.0", + "version_normalized": "1.29.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "bc45c394692b948b4d383a08d7753968bed9a83d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/bc45c394692b948b4d383a08d7753968bed9a83d", + "reference": "bc45c394692b948b4d383a08d7753968bed9a83d", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "time": "2024-01-29T20:11:03+00:00", + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-intl-normalizer" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.29.0", + "version_normalized": "1.29.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "time": "2024-01-29T20:11:03+00:00", + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-mbstring" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.29.0", + "version_normalized": "1.29.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "time": "2024-01-29T20:11:03+00:00", + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-php80" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.29.0", + "version_normalized": "1.29.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "c565ad1e63f30e7477fc40738343c62b40bc672d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/c565ad1e63f30e7477fc40738343c62b40bc672d", + "reference": "c565ad1e63f30e7477fc40738343c62b40bc672d", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "time": "2024-01-29T20:11:03+00:00", + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-php81" + }, + { + "name": "symfony/process", + "version": "v7.0.4", + "version_normalized": "7.0.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "0e7727191c3b71ebec6d529fa0e50a01ca5679e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/0e7727191c3b71ebec6d529fa0e50a01ca5679e9", + "reference": "0e7727191c3b71ebec6d529fa0e50a01ca5679e9", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "time": "2024-02-22T20:27:20+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v7.0.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/process" + }, + { + "name": "symfony/service-contracts", + "version": "v3.4.1", + "version_normalized": "3.4.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "fe07cbc8d837f60caf7018068e350cc5163681a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/fe07cbc8d837f60caf7018068e350cc5163681a0", + "reference": "fe07cbc8d837f60caf7018068e350cc5163681a0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "time": "2023-12-26T14:02:43+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.4.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/service-contracts" + }, + { + "name": "symfony/stopwatch", + "version": "v7.0.3", + "version_normalized": "7.0.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "983900d6fddf2b0cbaacacbbad07610854bd8112" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/983900d6fddf2b0cbaacacbbad07610854bd8112", + "reference": "983900d6fddf2b0cbaacacbbad07610854bd8112", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/service-contracts": "^2.5|^3" + }, + "time": "2024-01-23T15:02:46+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a way to profile code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/stopwatch/tree/v7.0.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/stopwatch" + }, + { + "name": "symfony/string", + "version": "v7.0.4", + "version_normalized": "7.0.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "f5832521b998b0bec40bee688ad5de98d4cf111b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/f5832521b998b0bec40bee688ad5de98d4cf111b", + "reference": "f5832521b998b0bec40bee688ad5de98d4cf111b", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/error-handler": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0" + }, + "time": "2024-02-01T13:17:36+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v7.0.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/string" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.3", + "version_normalized": "1.2.3.0", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "time": "2024-03-03T12:36:25+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "install-path": "../theseer/tokenizer" + } + ], + "dev": true, + "dev-package-names": [ + "composer/pcre", + "composer/semver", + "composer/xdebug-handler", + "doctrine/instantiator", + "friendsofphp/php-cs-fixer", + "kubawerlos/php-cs-fixer-custom-fixers", + "myclabs/deep-copy", + "nikic/php-parser", + "phar-io/manifest", + "phar-io/version", + "phpunit/php-code-coverage", + "phpunit/php-file-iterator", + "phpunit/php-invoker", + "phpunit/php-text-template", + "phpunit/php-timer", + "phpunit/phpunit", + "psr/event-dispatcher", + "redaxo/php-cs-fixer-config", + "sebastian/cli-parser", + "sebastian/code-unit", + "sebastian/code-unit-reverse-lookup", + "sebastian/comparator", + "sebastian/complexity", + "sebastian/diff", + "sebastian/environment", + "sebastian/exporter", + "sebastian/global-state", + "sebastian/lines-of-code", + "sebastian/object-enumerator", + "sebastian/object-reflector", + "sebastian/recursion-context", + "sebastian/resource-operations", + "sebastian/type", + "sebastian/version", + "symfony/console", + "symfony/event-dispatcher", + "symfony/event-dispatcher-contracts", + "symfony/filesystem", + "symfony/finder", + "symfony/options-resolver", + "symfony/polyfill-ctype", + "symfony/polyfill-intl-grapheme", + "symfony/polyfill-intl-normalizer", + "symfony/polyfill-mbstring", + "symfony/polyfill-php80", + "symfony/polyfill-php81", + "symfony/process", + "symfony/service-contracts", + "symfony/stopwatch", + "symfony/string", + "theseer/tokenizer" + ] +} diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php new file mode 100644 index 00000000..e7ea7ec4 --- /dev/null +++ b/vendor/composer/installed.php @@ -0,0 +1,644 @@ + array( + 'name' => 'yakamara/ycom', + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'reference' => '9a8ed33fae3ebd89d132278f5e58cf60786e83e6', + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev' => true, + ), + 'versions' => array( + 'apereo/phpcas' => array( + 'pretty_version' => '1.6.1', + 'version' => '1.6.1.0', + 'reference' => 'c129708154852656aabb13d8606cd5b12dbbabac', + 'type' => 'library', + 'install_path' => __DIR__ . '/../apereo/phpcas', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'composer/pcre' => array( + 'pretty_version' => '3.1.2', + 'version' => '3.1.2.0', + 'reference' => '4775f35b2d70865807c89d32c8e7385b86eb0ace', + 'type' => 'library', + 'install_path' => __DIR__ . '/./pcre', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'composer/semver' => array( + 'pretty_version' => '3.4.0', + 'version' => '3.4.0.0', + 'reference' => '35e8d0af4486141bc745f23a29cc2091eb624a32', + 'type' => 'library', + 'install_path' => __DIR__ . '/./semver', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'composer/xdebug-handler' => array( + 'pretty_version' => '3.0.3', + 'version' => '3.0.3.0', + 'reference' => 'ced299686f41dce890debac69273b47ffe98a40c', + 'type' => 'library', + 'install_path' => __DIR__ . '/./xdebug-handler', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'doctrine/instantiator' => array( + 'pretty_version' => '2.0.0', + 'version' => '2.0.0.0', + 'reference' => 'c6222283fa3f4ac679f8b9ced9a4e23f163e80d0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../doctrine/instantiator', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'friendsofphp/php-cs-fixer' => array( + 'pretty_version' => 'v3.51.0', + 'version' => '3.51.0.0', + 'reference' => '127fa74f010da99053e3f5b62672615b72dd6efd', + 'type' => 'application', + 'install_path' => __DIR__ . '/../friendsofphp/php-cs-fixer', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'guzzlehttp/guzzle' => array( + 'pretty_version' => '7.8.1', + 'version' => '7.8.1.0', + 'reference' => '41042bc7ab002487b876a0683fc8dce04ddce104', + 'type' => 'library', + 'install_path' => __DIR__ . '/../guzzlehttp/guzzle', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'guzzlehttp/promises' => array( + 'pretty_version' => '2.0.2', + 'version' => '2.0.2.0', + 'reference' => 'bbff78d96034045e58e13dedd6ad91b5d1253223', + 'type' => 'library', + 'install_path' => __DIR__ . '/../guzzlehttp/promises', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'guzzlehttp/psr7' => array( + 'pretty_version' => '2.6.2', + 'version' => '2.6.2.0', + 'reference' => '45b30f99ac27b5ca93cb4831afe16285f57b8221', + 'type' => 'library', + 'install_path' => __DIR__ . '/../guzzlehttp/psr7', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'kubawerlos/php-cs-fixer-custom-fixers' => array( + 'pretty_version' => 'v3.21.0', + 'version' => '3.21.0.0', + 'reference' => '3d1bb75be0df6c6fba4487c75b9e425a2c1d27c9', + 'type' => 'library', + 'install_path' => __DIR__ . '/../kubawerlos/php-cs-fixer-custom-fixers', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'league/oauth2-client' => array( + 'pretty_version' => '2.7.0', + 'version' => '2.7.0.0', + 'reference' => '160d6274b03562ebeb55ed18399281d8118b76c8', + 'type' => 'library', + 'install_path' => __DIR__ . '/../league/oauth2-client', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'myclabs/deep-copy' => array( + 'pretty_version' => '1.11.1', + 'version' => '1.11.1.0', + 'reference' => '7284c22080590fb39f2ffa3e9057f10a4ddd0e0c', + 'type' => 'library', + 'install_path' => __DIR__ . '/../myclabs/deep-copy', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'nikic/php-parser' => array( + 'pretty_version' => 'v5.0.2', + 'version' => '5.0.2.0', + 'reference' => '139676794dc1e9231bf7bcd123cfc0c99182cb13', + 'type' => 'library', + 'install_path' => __DIR__ . '/../nikic/php-parser', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'onelogin/php-saml' => array( + 'pretty_version' => '3.6.1', + 'version' => '3.6.1.0', + 'reference' => 'a7328b11887660ad248ea10952dd67a5aa73ba3b', + 'type' => 'library', + 'install_path' => __DIR__ . '/../onelogin/php-saml', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'paragonie/random_compat' => array( + 'pretty_version' => 'v9.99.100', + 'version' => '9.99.100.0', + 'reference' => '996434e5492cb4c3edcb9168db6fbb1359ef965a', + 'type' => 'library', + 'install_path' => __DIR__ . '/../paragonie/random_compat', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'phar-io/manifest' => array( + 'pretty_version' => '2.0.4', + 'version' => '2.0.4.0', + 'reference' => '54750ef60c58e43759730615a392c31c80e23176', + 'type' => 'library', + 'install_path' => __DIR__ . '/../phar-io/manifest', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'phar-io/version' => array( + 'pretty_version' => '3.2.1', + 'version' => '3.2.1.0', + 'reference' => '4f7fd7836c6f332bb2933569e566a0d6c4cbed74', + 'type' => 'library', + 'install_path' => __DIR__ . '/../phar-io/version', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'phpunit/php-code-coverage' => array( + 'pretty_version' => '9.2.31', + 'version' => '9.2.31.0', + 'reference' => '48c34b5d8d983006bd2adc2d0de92963b9155965', + 'type' => 'library', + 'install_path' => __DIR__ . '/../phpunit/php-code-coverage', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'phpunit/php-file-iterator' => array( + 'pretty_version' => '3.0.6', + 'version' => '3.0.6.0', + 'reference' => 'cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf', + 'type' => 'library', + 'install_path' => __DIR__ . '/../phpunit/php-file-iterator', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'phpunit/php-invoker' => array( + 'pretty_version' => '3.1.1', + 'version' => '3.1.1.0', + 'reference' => '5a10147d0aaf65b58940a0b72f71c9ac0423cc67', + 'type' => 'library', + 'install_path' => __DIR__ . '/../phpunit/php-invoker', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'phpunit/php-text-template' => array( + 'pretty_version' => '2.0.4', + 'version' => '2.0.4.0', + 'reference' => '5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28', + 'type' => 'library', + 'install_path' => __DIR__ . '/../phpunit/php-text-template', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'phpunit/php-timer' => array( + 'pretty_version' => '5.0.3', + 'version' => '5.0.3.0', + 'reference' => '5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2', + 'type' => 'library', + 'install_path' => __DIR__ . '/../phpunit/php-timer', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'phpunit/phpunit' => array( + 'pretty_version' => '9.6.17', + 'version' => '9.6.17.0', + 'reference' => '1a156980d78a6666721b7e8e8502fe210b587fcd', + 'type' => 'library', + 'install_path' => __DIR__ . '/../phpunit/phpunit', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'psr/container' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => '*', + ), + ), + 'psr/event-dispatcher' => array( + 'pretty_version' => '1.0.0', + 'version' => '1.0.0.0', + 'reference' => 'dbefd12671e8a14ec7f180cab83036ed26714bb0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/event-dispatcher', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'psr/event-dispatcher-implementation' => array( + 'dev_requirement' => true, + 'provided' => array( + 0 => '1.0', + ), + ), + 'psr/http-client' => array( + 'pretty_version' => '1.0.3', + 'version' => '1.0.3.0', + 'reference' => 'bb5906edc1c324c9a05aa0873d40117941e5fa90', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/http-client', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/http-client-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0', + ), + ), + 'psr/http-factory' => array( + 'pretty_version' => '1.0.2', + 'version' => '1.0.2.0', + 'reference' => 'e616d01114759c4c489f93b099585439f795fe35', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/http-factory', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/http-factory-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0', + ), + ), + 'psr/http-message' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => '*', + ), + ), + 'psr/http-message-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0', + ), + ), + 'psr/log' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => '*', + ), + ), + 'psr/log-implementation' => array( + 'dev_requirement' => true, + 'provided' => array( + 0 => '1.0|2.0|3.0', + ), + ), + 'ralouphie/getallheaders' => array( + 'pretty_version' => '3.0.3', + 'version' => '3.0.3.0', + 'reference' => '120b605dfeb996808c31b6477290a714d356e822', + 'type' => 'library', + 'install_path' => __DIR__ . '/../ralouphie/getallheaders', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'redaxo/php-cs-fixer-config' => array( + 'pretty_version' => '2.7.0', + 'version' => '2.7.0.0', + 'reference' => '4a76aa9b8ade7dd440ac35eaaacf6df7daa275fb', + 'type' => 'library', + 'install_path' => __DIR__ . '/../redaxo/php-cs-fixer-config', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'robrichards/xmlseclibs' => array( + 'pretty_version' => '3.1.1', + 'version' => '3.1.1.0', + 'reference' => 'f8f19e58f26cdb42c54b214ff8a820760292f8df', + 'type' => 'library', + 'install_path' => __DIR__ . '/../robrichards/xmlseclibs', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'sebastian/cli-parser' => array( + 'pretty_version' => '1.0.2', + 'version' => '1.0.2.0', + 'reference' => '2b56bea83a09de3ac06bb18b92f068e60cc6f50b', + 'type' => 'library', + 'install_path' => __DIR__ . '/../sebastian/cli-parser', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'sebastian/code-unit' => array( + 'pretty_version' => '1.0.8', + 'version' => '1.0.8.0', + 'reference' => '1fc9f64c0927627ef78ba436c9b17d967e68e120', + 'type' => 'library', + 'install_path' => __DIR__ . '/../sebastian/code-unit', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'sebastian/code-unit-reverse-lookup' => array( + 'pretty_version' => '2.0.3', + 'version' => '2.0.3.0', + 'reference' => 'ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5', + 'type' => 'library', + 'install_path' => __DIR__ . '/../sebastian/code-unit-reverse-lookup', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'sebastian/comparator' => array( + 'pretty_version' => '4.0.8', + 'version' => '4.0.8.0', + 'reference' => 'fa0f136dd2334583309d32b62544682ee972b51a', + 'type' => 'library', + 'install_path' => __DIR__ . '/../sebastian/comparator', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'sebastian/complexity' => array( + 'pretty_version' => '2.0.3', + 'version' => '2.0.3.0', + 'reference' => '25f207c40d62b8b7aa32f5ab026c53561964053a', + 'type' => 'library', + 'install_path' => __DIR__ . '/../sebastian/complexity', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'sebastian/diff' => array( + 'pretty_version' => '4.0.6', + 'version' => '4.0.6.0', + 'reference' => 'ba01945089c3a293b01ba9badc29ad55b106b0bc', + 'type' => 'library', + 'install_path' => __DIR__ . '/../sebastian/diff', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'sebastian/environment' => array( + 'pretty_version' => '5.1.5', + 'version' => '5.1.5.0', + 'reference' => '830c43a844f1f8d5b7a1f6d6076b784454d8b7ed', + 'type' => 'library', + 'install_path' => __DIR__ . '/../sebastian/environment', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'sebastian/exporter' => array( + 'pretty_version' => '4.0.6', + 'version' => '4.0.6.0', + 'reference' => '78c00df8f170e02473b682df15bfcdacc3d32d72', + 'type' => 'library', + 'install_path' => __DIR__ . '/../sebastian/exporter', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'sebastian/global-state' => array( + 'pretty_version' => '5.0.7', + 'version' => '5.0.7.0', + 'reference' => 'bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9', + 'type' => 'library', + 'install_path' => __DIR__ . '/../sebastian/global-state', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'sebastian/lines-of-code' => array( + 'pretty_version' => '1.0.4', + 'version' => '1.0.4.0', + 'reference' => 'e1e4a170560925c26d424b6a03aed157e7dcc5c5', + 'type' => 'library', + 'install_path' => __DIR__ . '/../sebastian/lines-of-code', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'sebastian/object-enumerator' => array( + 'pretty_version' => '4.0.4', + 'version' => '4.0.4.0', + 'reference' => '5c9eeac41b290a3712d88851518825ad78f45c71', + 'type' => 'library', + 'install_path' => __DIR__ . '/../sebastian/object-enumerator', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'sebastian/object-reflector' => array( + 'pretty_version' => '2.0.4', + 'version' => '2.0.4.0', + 'reference' => 'b4f479ebdbf63ac605d183ece17d8d7fe49c15c7', + 'type' => 'library', + 'install_path' => __DIR__ . '/../sebastian/object-reflector', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'sebastian/recursion-context' => array( + 'pretty_version' => '4.0.5', + 'version' => '4.0.5.0', + 'reference' => 'e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1', + 'type' => 'library', + 'install_path' => __DIR__ . '/../sebastian/recursion-context', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'sebastian/resource-operations' => array( + 'pretty_version' => '3.0.3', + 'version' => '3.0.3.0', + 'reference' => '0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8', + 'type' => 'library', + 'install_path' => __DIR__ . '/../sebastian/resource-operations', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'sebastian/type' => array( + 'pretty_version' => '3.2.1', + 'version' => '3.2.1.0', + 'reference' => '75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7', + 'type' => 'library', + 'install_path' => __DIR__ . '/../sebastian/type', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'sebastian/version' => array( + 'pretty_version' => '3.0.2', + 'version' => '3.0.2.0', + 'reference' => 'c6c1022351a901512170118436c764e473f6de8c', + 'type' => 'library', + 'install_path' => __DIR__ . '/../sebastian/version', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/console' => array( + 'pretty_version' => 'v7.0.4', + 'version' => '7.0.4.0', + 'reference' => '6b099f3306f7c9c2d2786ed736d0026b2903205f', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/console', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/deprecation-contracts' => array( + 'pretty_version' => 'v3.4.0', + 'version' => '3.4.0.0', + 'reference' => '7c3aff79d10325257a001fcf92d991f24fc967cf', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/deprecation-contracts', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/event-dispatcher' => array( + 'pretty_version' => 'v7.0.3', + 'version' => '7.0.3.0', + 'reference' => '834c28d533dd0636f910909d01b9ff45cc094b5e', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/event-dispatcher', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/event-dispatcher-contracts' => array( + 'pretty_version' => 'v3.4.0', + 'version' => '3.4.0.0', + 'reference' => 'a76aed96a42d2b521153fb382d418e30d18b59df', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/event-dispatcher-contracts', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/event-dispatcher-implementation' => array( + 'dev_requirement' => true, + 'provided' => array( + 0 => '2.0|3.0', + ), + ), + 'symfony/filesystem' => array( + 'pretty_version' => 'v7.0.3', + 'version' => '7.0.3.0', + 'reference' => '2890e3a825bc0c0558526c04499c13f83e1b6b12', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/filesystem', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/finder' => array( + 'pretty_version' => 'v7.0.0', + 'version' => '7.0.0.0', + 'reference' => '6e5688d69f7cfc4ed4a511e96007e06c2d34ce56', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/finder', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/options-resolver' => array( + 'pretty_version' => 'v7.0.0', + 'version' => '7.0.0.0', + 'reference' => '700ff4096e346f54cb628ea650767c8130f1001f', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/options-resolver', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/polyfill-ctype' => array( + 'pretty_version' => 'v1.29.0', + 'version' => '1.29.0.0', + 'reference' => 'ef4d7e442ca910c4764bce785146269b30cb5fc4', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-ctype', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/polyfill-intl-grapheme' => array( + 'pretty_version' => 'v1.29.0', + 'version' => '1.29.0.0', + 'reference' => '32a9da87d7b3245e09ac426c83d334ae9f06f80f', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-intl-grapheme', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/polyfill-intl-normalizer' => array( + 'pretty_version' => 'v1.29.0', + 'version' => '1.29.0.0', + 'reference' => 'bc45c394692b948b4d383a08d7753968bed9a83d', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-intl-normalizer', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/polyfill-mbstring' => array( + 'pretty_version' => 'v1.29.0', + 'version' => '1.29.0.0', + 'reference' => '9773676c8a1bb1f8d4340a62efe641cf76eda7ec', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-mbstring', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/polyfill-php80' => array( + 'pretty_version' => 'v1.29.0', + 'version' => '1.29.0.0', + 'reference' => '87b68208d5c1188808dd7839ee1e6c8ec3b02f1b', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-php80', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/polyfill-php81' => array( + 'pretty_version' => 'v1.29.0', + 'version' => '1.29.0.0', + 'reference' => 'c565ad1e63f30e7477fc40738343c62b40bc672d', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-php81', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/process' => array( + 'pretty_version' => 'v7.0.4', + 'version' => '7.0.4.0', + 'reference' => '0e7727191c3b71ebec6d529fa0e50a01ca5679e9', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/process', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/service-contracts' => array( + 'pretty_version' => 'v3.4.1', + 'version' => '3.4.1.0', + 'reference' => 'fe07cbc8d837f60caf7018068e350cc5163681a0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/service-contracts', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/stopwatch' => array( + 'pretty_version' => 'v7.0.3', + 'version' => '7.0.3.0', + 'reference' => '983900d6fddf2b0cbaacacbbad07610854bd8112', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/stopwatch', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/string' => array( + 'pretty_version' => 'v7.0.4', + 'version' => '7.0.4.0', + 'reference' => 'f5832521b998b0bec40bee688ad5de98d4cf111b', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/string', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'theseer/tokenizer' => array( + 'pretty_version' => '1.2.3', + 'version' => '1.2.3.0', + 'reference' => '737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2', + 'type' => 'library', + 'install_path' => __DIR__ . '/../theseer/tokenizer', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'yakamara/ycom' => array( + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'reference' => '9a8ed33fae3ebd89d132278f5e58cf60786e83e6', + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev_requirement' => false, + ), + ), +); diff --git a/vendor/composer/pcre/LICENSE b/vendor/composer/pcre/LICENSE new file mode 100644 index 00000000..c5a282ff --- /dev/null +++ b/vendor/composer/pcre/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2021 Composer + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/composer/pcre/README.md b/vendor/composer/pcre/README.md new file mode 100644 index 00000000..973b17d8 --- /dev/null +++ b/vendor/composer/pcre/README.md @@ -0,0 +1,181 @@ +composer/pcre +============= + +PCRE wrapping library that offers type-safe `preg_*` replacements. + +This library gives you a way to ensure `preg_*` functions do not fail silently, returning +unexpected `null`s that may not be handled. + +As of 3.0 this library enforces [`PREG_UNMATCHED_AS_NULL`](#preg_unmatched_as_null) usage +for all matching and replaceCallback functions, [read more below](#preg_unmatched_as_null) +to understand the implications. + +It thus makes it easier to work with static analysis tools like PHPStan or Psalm as it +simplifies and reduces the possible return values from all the `preg_*` functions which +are quite packed with edge cases. + +This library is a thin wrapper around `preg_*` functions with [some limitations](#restrictions--limitations). +If you are looking for a richer API to handle regular expressions have a look at +[rawr/t-regx](https://packagist.org/packages/rawr/t-regx) instead. + +[![Continuous Integration](https://github.com/composer/pcre/workflows/Continuous%20Integration/badge.svg?branch=main)](https://github.com/composer/pcre/actions) + + +Installation +------------ + +Install the latest version with: + +```bash +$ composer require composer/pcre +``` + + +Requirements +------------ + +* PHP 7.4.0 is required for 3.x versions +* PHP 7.2.0 is required for 2.x versions +* PHP 5.3.2 is required for 1.x versions + + +Basic usage +----------- + +Instead of: + +```php +if (preg_match('{fo+}', $string, $matches)) { ... } +if (preg_match('{fo+}', $string, $matches, PREG_OFFSET_CAPTURE)) { ... } +if (preg_match_all('{fo+}', $string, $matches)) { ... } +$newString = preg_replace('{fo+}', 'bar', $string); +$newString = preg_replace_callback('{fo+}', function ($match) { return strtoupper($match[0]); }, $string); +$newString = preg_replace_callback_array(['{fo+}' => fn ($match) => strtoupper($match[0])], $string); +$filtered = preg_grep('{[a-z]}', $elements); +$array = preg_split('{[a-z]+}', $string); +``` + +You can now call these on the `Preg` class: + +```php +use Composer\Pcre\Preg; + +if (Preg::match('{fo+}', $string, $matches)) { ... } +if (Preg::matchWithOffsets('{fo+}', $string, $matches)) { ... } +if (Preg::matchAll('{fo+}', $string, $matches)) { ... } +$newString = Preg::replace('{fo+}', 'bar', $string); +$newString = Preg::replaceCallback('{fo+}', function ($match) { return strtoupper($match[0]); }, $string); +$newString = Preg::replaceCallbackArray(['{fo+}' => fn ($match) => strtoupper($match[0])], $string); +$filtered = Preg::grep('{[a-z]}', $elements); +$array = Preg::split('{[a-z]+}', $string); +``` + +The main difference is if anything fails to match/replace/.., it will throw a `Composer\Pcre\PcreException` +instead of returning `null` (or false in some cases), so you can now use the return values safely relying on +the fact that they can only be strings (for replace), ints (for match) or arrays (for grep/split). + +Additionally the `Preg` class provides match methods that return `bool` rather than `int`, for stricter type safety +when the number of pattern matches is not useful: + +```php +use Composer\Pcre\Preg; + +if (Preg::isMatch('{fo+}', $string, $matches)) // bool +if (Preg::isMatchAll('{fo+}', $string, $matches)) // bool +``` + +Finally the `Preg` class provides a few `*StrictGroups` method variants that ensure match groups +are always present and thus non-nullable, making it easier to write type-safe code: + +```php +use Composer\Pcre\Preg; + +// $matches is guaranteed to be an array of strings, if a subpattern does not match and produces a null it will throw +if (Preg::matchStrictGroups('{fo+}', $string, $matches)) +if (Preg::matchAllStrictGroups('{fo+}', $string, $matches)) +``` + +**Note:** This is generally safe to use as long as you do not have optional subpatterns (i.e. `(something)?` +or `(something)*` or branches with a `|` that result in some groups not being matched at all). +A subpattern that can match an empty string like `(.*)` is **not** optional, it will be present as an +empty string in the matches. A non-matching subpattern, even if optional like `(?:foo)?` will anyway not be present in +matches so it is also not a problem to use these with `*StrictGroups` methods. + +If you would prefer a slightly more verbose usage, replacing by-ref arguments by result objects, you can use the `Regex` class: + +```php +use Composer\Pcre\Regex; + +// this is useful when you are just interested in knowing if something matched +// as it returns a bool instead of int(1/0) for match +$bool = Regex::isMatch('{fo+}', $string); + +$result = Regex::match('{fo+}', $string); +if ($result->matched) { something($result->matches); } + +$result = Regex::matchWithOffsets('{fo+}', $string); +if ($result->matched) { something($result->matches); } + +$result = Regex::matchAll('{fo+}', $string); +if ($result->matched && $result->count > 3) { something($result->matches); } + +$newString = Regex::replace('{fo+}', 'bar', $string)->result; +$newString = Regex::replaceCallback('{fo+}', function ($match) { return strtoupper($match[0]); }, $string)->result; +$newString = Regex::replaceCallbackArray(['{fo+}' => fn ($match) => strtoupper($match[0])], $string)->result; +``` + +Note that `preg_grep` and `preg_split` are only callable via the `Preg` class as they do not have +complex return types warranting a specific result object. + +See the [MatchResult](src/MatchResult.php), [MatchWithOffsetsResult](src/MatchWithOffsetsResult.php), [MatchAllResult](src/MatchAllResult.php), +[MatchAllWithOffsetsResult](src/MatchAllWithOffsetsResult.php), and [ReplaceResult](src/ReplaceResult.php) class sources for more details. + +Restrictions / Limitations +-------------------------- + +Due to type safety requirements a few restrictions are in place. + +- matching using `PREG_OFFSET_CAPTURE` is made available via `matchWithOffsets` and `matchAllWithOffsets`. + You cannot pass the flag to `match`/`matchAll`. +- `Preg::split` will also reject `PREG_SPLIT_OFFSET_CAPTURE` and you should use `splitWithOffsets` + instead. +- `matchAll` rejects `PREG_SET_ORDER` as it also changes the shape of the returned matches. There + is no alternative provided as you can fairly easily code around it. +- `preg_filter` is not supported as it has a rather crazy API, most likely you should rather + use `Preg::grep` in combination with some loop and `Preg::replace`. +- `replace`, `replaceCallback` and `replaceCallbackArray` do not support an array `$subject`, + only simple strings. +- As of 2.0, the library always uses `PREG_UNMATCHED_AS_NULL` for matching, which offers [much + saner/more predictable results](#preg_unmatched_as_null). As of 3.0 the flag is also set for + `replaceCallback` and `replaceCallbackArray`. + +#### PREG_UNMATCHED_AS_NULL + +As of 2.0, this library always uses PREG_UNMATCHED_AS_NULL for all `match*` and `isMatch*` +functions. As of 3.0 it is also done for `replaceCallback` and `replaceCallbackArray`. + +This means your matches will always contain all matching groups, either as null if unmatched +or as string if it matched. + +The advantages in clarity and predictability are clearer if you compare the two outputs of +running this with and without PREG_UNMATCHED_AS_NULL in $flags: + +```php +preg_match('/(a)(b)*(c)(d)*/', 'ac', $matches, $flags); +``` + +| no flag | PREG_UNMATCHED_AS_NULL | +| --- | --- | +| array (size=4) | array (size=5) | +| 0 => string 'ac' (length=2) | 0 => string 'ac' (length=2) | +| 1 => string 'a' (length=1) | 1 => string 'a' (length=1) | +| 2 => string '' (length=0) | 2 => null | +| 3 => string 'c' (length=1) | 3 => string 'c' (length=1) | +| | 4 => null | +| group 2 (any unmatched group preceding one that matched) is set to `''`. You cannot tell if it matched an empty string or did not match at all | group 2 is `null` when unmatched and a string if it matched, easy to check for | +| group 4 (any optional group without a matching one following) is missing altogether. So you have to check with `isset()`, but really you want `isset($m[4]) && $m[4] !== ''` for safety unless you are very careful to check that a non-optional group follows it | group 4 is always set, and null in this case as there was no match, easy to check for with `$m[4] !== null` | + +License +------- + +composer/pcre is licensed under the MIT License, see the LICENSE file for details. diff --git a/vendor/composer/pcre/composer.json b/vendor/composer/pcre/composer.json new file mode 100644 index 00000000..40477ff4 --- /dev/null +++ b/vendor/composer/pcre/composer.json @@ -0,0 +1,46 @@ +{ + "name": "composer/pcre", + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "type": "library", + "license": "MIT", + "keywords": [ + "pcre", + "regex", + "preg", + "regular expression" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^5", + "phpstan/phpstan": "^1.3", + "phpstan/phpstan-strict-rules": "^1.1" + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Composer\\Pcre\\": "tests" + } + }, + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "scripts": { + "test": "vendor/bin/simple-phpunit", + "phpstan": "phpstan analyse" + } +} diff --git a/vendor/composer/pcre/src/MatchAllResult.php b/vendor/composer/pcre/src/MatchAllResult.php new file mode 100644 index 00000000..b22b52d6 --- /dev/null +++ b/vendor/composer/pcre/src/MatchAllResult.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Pcre; + +final class MatchAllResult +{ + /** + * An array of match group => list of matched strings + * + * @readonly + * @var array> + */ + public $matches; + + /** + * @readonly + * @var 0|positive-int + */ + public $count; + + /** + * @readonly + * @var bool + */ + public $matched; + + /** + * @param 0|positive-int $count + * @param array> $matches + */ + public function __construct(int $count, array $matches) + { + $this->matches = $matches; + $this->matched = (bool) $count; + $this->count = $count; + } +} diff --git a/vendor/composer/pcre/src/MatchAllStrictGroupsResult.php b/vendor/composer/pcre/src/MatchAllStrictGroupsResult.php new file mode 100644 index 00000000..b7ec3974 --- /dev/null +++ b/vendor/composer/pcre/src/MatchAllStrictGroupsResult.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Pcre; + +final class MatchAllStrictGroupsResult +{ + /** + * An array of match group => list of matched strings + * + * @readonly + * @var array> + */ + public $matches; + + /** + * @readonly + * @var 0|positive-int + */ + public $count; + + /** + * @readonly + * @var bool + */ + public $matched; + + /** + * @param 0|positive-int $count + * @param array> $matches + */ + public function __construct(int $count, array $matches) + { + $this->matches = $matches; + $this->matched = (bool) $count; + $this->count = $count; + } +} diff --git a/vendor/composer/pcre/src/MatchAllWithOffsetsResult.php b/vendor/composer/pcre/src/MatchAllWithOffsetsResult.php new file mode 100644 index 00000000..032a02cd --- /dev/null +++ b/vendor/composer/pcre/src/MatchAllWithOffsetsResult.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Pcre; + +final class MatchAllWithOffsetsResult +{ + /** + * An array of match group => list of matches, every match being a pair of string matched + offset in bytes (or -1 if no match) + * + * @readonly + * @var array> + * @phpstan-var array}>> + */ + public $matches; + + /** + * @readonly + * @var 0|positive-int + */ + public $count; + + /** + * @readonly + * @var bool + */ + public $matched; + + /** + * @param 0|positive-int $count + * @param array> $matches + * @phpstan-param array}>> $matches + */ + public function __construct(int $count, array $matches) + { + $this->matches = $matches; + $this->matched = (bool) $count; + $this->count = $count; + } +} diff --git a/vendor/composer/pcre/src/MatchResult.php b/vendor/composer/pcre/src/MatchResult.php new file mode 100644 index 00000000..e951a5ee --- /dev/null +++ b/vendor/composer/pcre/src/MatchResult.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Pcre; + +final class MatchResult +{ + /** + * An array of match group => string matched + * + * @readonly + * @var array + */ + public $matches; + + /** + * @readonly + * @var bool + */ + public $matched; + + /** + * @param 0|positive-int $count + * @param array $matches + */ + public function __construct(int $count, array $matches) + { + $this->matches = $matches; + $this->matched = (bool) $count; + } +} diff --git a/vendor/composer/pcre/src/MatchStrictGroupsResult.php b/vendor/composer/pcre/src/MatchStrictGroupsResult.php new file mode 100644 index 00000000..126ee629 --- /dev/null +++ b/vendor/composer/pcre/src/MatchStrictGroupsResult.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Pcre; + +final class MatchStrictGroupsResult +{ + /** + * An array of match group => string matched + * + * @readonly + * @var array + */ + public $matches; + + /** + * @readonly + * @var bool + */ + public $matched; + + /** + * @param 0|positive-int $count + * @param array $matches + */ + public function __construct(int $count, array $matches) + { + $this->matches = $matches; + $this->matched = (bool) $count; + } +} diff --git a/vendor/composer/pcre/src/MatchWithOffsetsResult.php b/vendor/composer/pcre/src/MatchWithOffsetsResult.php new file mode 100644 index 00000000..ba4d4bc4 --- /dev/null +++ b/vendor/composer/pcre/src/MatchWithOffsetsResult.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Pcre; + +final class MatchWithOffsetsResult +{ + /** + * An array of match group => pair of string matched + offset in bytes (or -1 if no match) + * + * @readonly + * @var array + * @phpstan-var array}> + */ + public $matches; + + /** + * @readonly + * @var bool + */ + public $matched; + + /** + * @param 0|positive-int $count + * @param array $matches + * @phpstan-param array}> $matches + */ + public function __construct(int $count, array $matches) + { + $this->matches = $matches; + $this->matched = (bool) $count; + } +} diff --git a/vendor/composer/pcre/src/PcreException.php b/vendor/composer/pcre/src/PcreException.php new file mode 100644 index 00000000..218b2f2d --- /dev/null +++ b/vendor/composer/pcre/src/PcreException.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Pcre; + +class PcreException extends \RuntimeException +{ + /** + * @param string $function + * @param string|string[] $pattern + * @return self + */ + public static function fromFunction($function, $pattern) + { + $code = preg_last_error(); + + if (is_array($pattern)) { + $pattern = implode(', ', $pattern); + } + + return new PcreException($function.'(): failed executing "'.$pattern.'": '.self::pcreLastErrorMessage($code), $code); + } + + /** + * @param int $code + * @return string + */ + private static function pcreLastErrorMessage($code) + { + if (function_exists('preg_last_error_msg')) { + return preg_last_error_msg(); + } + + // older php versions did not set the code properly in all cases + if (PHP_VERSION_ID < 70201 && $code === 0) { + return 'UNDEFINED_ERROR'; + } + + $constants = get_defined_constants(true); + if (!isset($constants['pcre'])) { + return 'UNDEFINED_ERROR'; + } + + foreach ($constants['pcre'] as $const => $val) { + if ($val === $code && substr($const, -6) === '_ERROR') { + return $const; + } + } + + return 'UNDEFINED_ERROR'; + } +} diff --git a/vendor/composer/pcre/src/Preg.php b/vendor/composer/pcre/src/Preg.php new file mode 100644 index 00000000..71ed46ef --- /dev/null +++ b/vendor/composer/pcre/src/Preg.php @@ -0,0 +1,430 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Pcre; + +class Preg +{ + /** @internal */ + public const ARRAY_MSG = '$subject as an array is not supported. You can use \'foreach\' instead.'; + /** @internal */ + public const INVALID_TYPE_MSG = '$subject must be a string, %s given.'; + + /** + * @param non-empty-string $pattern + * @param array $matches Set by method + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + * @return 0|1 + * + * @param-out array $matches + */ + public static function match(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int + { + self::checkOffsetCapture($flags, 'matchWithOffsets'); + + $result = preg_match($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL, $offset); + if ($result === false) { + throw PcreException::fromFunction('preg_match', $pattern); + } + + return $result; + } + + /** + * Variant of `match()` which outputs non-null matches (or throws) + * + * @param non-empty-string $pattern + * @param array $matches Set by method + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + * @return 0|1 + * @throws UnexpectedNullMatchException + * + * @param-out array $matches + */ + public static function matchStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int + { + $result = self::match($pattern, $subject, $matchesInternal, $flags, $offset); + $matches = self::enforceNonNullMatches($pattern, $matchesInternal, 'match'); + + return $result; + } + + /** + * Runs preg_match with PREG_OFFSET_CAPTURE + * + * @param non-empty-string $pattern + * @param array $matches Set by method + * @param int-mask $flags PREG_UNMATCHED_AS_NULL and PREG_OFFSET_CAPTURE are always set, no other flags are supported + * @return 0|1 + * + * @param-out array}> $matches + */ + public static function matchWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0): int + { + $result = preg_match($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL | PREG_OFFSET_CAPTURE, $offset); + if ($result === false) { + throw PcreException::fromFunction('preg_match', $pattern); + } + + return $result; + } + + /** + * @param non-empty-string $pattern + * @param array $matches Set by method + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + * @return 0|positive-int + * + * @param-out array> $matches + */ + public static function matchAll(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int + { + self::checkOffsetCapture($flags, 'matchAllWithOffsets'); + self::checkSetOrder($flags); + + $result = preg_match_all($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL, $offset); + if (!is_int($result)) { // PHP < 8 may return null, 8+ returns int|false + throw PcreException::fromFunction('preg_match_all', $pattern); + } + + return $result; + } + + /** + * Variant of `match()` which outputs non-null matches (or throws) + * + * @param non-empty-string $pattern + * @param array $matches Set by method + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + * @return 0|positive-int + * @throws UnexpectedNullMatchException + * + * @param-out array> $matches + */ + public static function matchAllStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int + { + $result = self::matchAll($pattern, $subject, $matchesInternal, $flags, $offset); + $matches = self::enforceNonNullMatchAll($pattern, $matchesInternal, 'matchAll'); + + return $result; + } + + /** + * Runs preg_match_all with PREG_OFFSET_CAPTURE + * + * @param non-empty-string $pattern + * @param array $matches Set by method + * @param int-mask $flags PREG_UNMATCHED_AS_NULL and PREG_MATCH_OFFSET are always set, no other flags are supported + * @return 0|positive-int + * + * @param-out array}>> $matches + */ + public static function matchAllWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0): int + { + self::checkSetOrder($flags); + + $result = preg_match_all($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL | PREG_OFFSET_CAPTURE, $offset); + if (!is_int($result)) { // PHP < 8 may return null, 8+ returns int|false + throw PcreException::fromFunction('preg_match_all', $pattern); + } + + return $result; + } + + /** + * @param string|string[] $pattern + * @param string|string[] $replacement + * @param string $subject + * @param int $count Set by method + * + * @param-out int<0, max> $count + */ + public static function replace($pattern, $replacement, $subject, int $limit = -1, int &$count = null): string + { + if (!is_scalar($subject)) { + if (is_array($subject)) { + throw new \InvalidArgumentException(static::ARRAY_MSG); + } + + throw new \TypeError(sprintf(static::INVALID_TYPE_MSG, gettype($subject))); + } + + $result = preg_replace($pattern, $replacement, $subject, $limit, $count); + if ($result === null) { + throw PcreException::fromFunction('preg_replace', $pattern); + } + + return $result; + } + + /** + * @param string|string[] $pattern + * @param ($flags is PREG_OFFSET_CAPTURE ? (callable(array}>): string) : callable(array): string) $replacement + * @param string $subject + * @param int $count Set by method + * @param int-mask $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set + * + * @param-out int<0, max> $count + */ + public static function replaceCallback($pattern, callable $replacement, $subject, int $limit = -1, int &$count = null, int $flags = 0): string + { + if (!is_scalar($subject)) { + if (is_array($subject)) { + throw new \InvalidArgumentException(static::ARRAY_MSG); + } + + throw new \TypeError(sprintf(static::INVALID_TYPE_MSG, gettype($subject))); + } + + $result = preg_replace_callback($pattern, $replacement, $subject, $limit, $count, $flags | PREG_UNMATCHED_AS_NULL); + if ($result === null) { + throw PcreException::fromFunction('preg_replace_callback', $pattern); + } + + return $result; + } + + /** + * Variant of `replaceCallback()` which outputs non-null matches (or throws) + * + * @param string $pattern + * @param ($flags is PREG_OFFSET_CAPTURE ? (callable(array}>): string) : callable(array): string) $replacement + * @param string $subject + * @param int $count Set by method + * @param int-mask $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set + * + * @param-out int<0, max> $count + */ + public static function replaceCallbackStrictGroups(string $pattern, callable $replacement, $subject, int $limit = -1, int &$count = null, int $flags = 0): string + { + return self::replaceCallback($pattern, function (array $matches) use ($pattern, $replacement) { + return $replacement(self::enforceNonNullMatches($pattern, $matches, 'replaceCallback')); + }, $subject, $limit, $count, $flags); + } + + /** + * @param ($flags is PREG_OFFSET_CAPTURE ? (array}>): string>) : array): string>) $pattern + * @param string $subject + * @param int $count Set by method + * @param int-mask $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set + * + * @param-out int<0, max> $count + */ + public static function replaceCallbackArray(array $pattern, $subject, int $limit = -1, int &$count = null, int $flags = 0): string + { + if (!is_scalar($subject)) { + if (is_array($subject)) { + throw new \InvalidArgumentException(static::ARRAY_MSG); + } + + throw new \TypeError(sprintf(static::INVALID_TYPE_MSG, gettype($subject))); + } + + $result = preg_replace_callback_array($pattern, $subject, $limit, $count, $flags | PREG_UNMATCHED_AS_NULL); + if ($result === null) { + $pattern = array_keys($pattern); + throw PcreException::fromFunction('preg_replace_callback_array', $pattern); + } + + return $result; + } + + /** + * @param int-mask $flags PREG_SPLIT_NO_EMPTY or PREG_SPLIT_DELIM_CAPTURE + * @return list + */ + public static function split(string $pattern, string $subject, int $limit = -1, int $flags = 0): array + { + if (($flags & PREG_SPLIT_OFFSET_CAPTURE) !== 0) { + throw new \InvalidArgumentException('PREG_SPLIT_OFFSET_CAPTURE is not supported as it changes the type of $matches, use splitWithOffsets() instead'); + } + + $result = preg_split($pattern, $subject, $limit, $flags); + if ($result === false) { + throw PcreException::fromFunction('preg_split', $pattern); + } + + return $result; + } + + /** + * @param int-mask $flags PREG_SPLIT_NO_EMPTY or PREG_SPLIT_DELIM_CAPTURE, PREG_SPLIT_OFFSET_CAPTURE is always set + * @return list + * @phpstan-return list}> + */ + public static function splitWithOffsets(string $pattern, string $subject, int $limit = -1, int $flags = 0): array + { + $result = preg_split($pattern, $subject, $limit, $flags | PREG_SPLIT_OFFSET_CAPTURE); + if ($result === false) { + throw PcreException::fromFunction('preg_split', $pattern); + } + + return $result; + } + + /** + * @template T of string|\Stringable + * @param string $pattern + * @param array $array + * @param int-mask $flags PREG_GREP_INVERT + * @return array + */ + public static function grep(string $pattern, array $array, int $flags = 0): array + { + $result = preg_grep($pattern, $array, $flags); + if ($result === false) { + throw PcreException::fromFunction('preg_grep', $pattern); + } + + return $result; + } + + /** + * Variant of match() which returns a bool instead of int + * + * @param non-empty-string $pattern + * @param array $matches Set by method + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + * + * @param-out array $matches + */ + public static function isMatch(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool + { + return (bool) static::match($pattern, $subject, $matches, $flags, $offset); + } + + /** + * Variant of `isMatch()` which outputs non-null matches (or throws) + * + * @param non-empty-string $pattern + * @param array $matches Set by method + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + * @throws UnexpectedNullMatchException + * + * @param-out array $matches + */ + public static function isMatchStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool + { + return (bool) self::matchStrictGroups($pattern, $subject, $matches, $flags, $offset); + } + + /** + * Variant of matchAll() which returns a bool instead of int + * + * @param non-empty-string $pattern + * @param array $matches Set by method + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + * + * @param-out array> $matches + */ + public static function isMatchAll(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool + { + return (bool) static::matchAll($pattern, $subject, $matches, $flags, $offset); + } + + /** + * Variant of `isMatchAll()` which outputs non-null matches (or throws) + * + * @param non-empty-string $pattern + * @param array $matches Set by method + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + * + * @param-out array> $matches + */ + public static function isMatchAllStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool + { + return (bool) self::matchAllStrictGroups($pattern, $subject, $matches, $flags, $offset); + } + + /** + * Variant of matchWithOffsets() which returns a bool instead of int + * + * Runs preg_match with PREG_OFFSET_CAPTURE + * + * @param non-empty-string $pattern + * @param array $matches Set by method + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + * + * @param-out array}> $matches + */ + public static function isMatchWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0): bool + { + return (bool) static::matchWithOffsets($pattern, $subject, $matches, $flags, $offset); + } + + /** + * Variant of matchAllWithOffsets() which returns a bool instead of int + * + * Runs preg_match_all with PREG_OFFSET_CAPTURE + * + * @param non-empty-string $pattern + * @param array $matches Set by method + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + * + * @param-out array}>> $matches + */ + public static function isMatchAllWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0): bool + { + return (bool) static::matchAllWithOffsets($pattern, $subject, $matches, $flags, $offset); + } + + private static function checkOffsetCapture(int $flags, string $useFunctionName): void + { + if (($flags & PREG_OFFSET_CAPTURE) !== 0) { + throw new \InvalidArgumentException('PREG_OFFSET_CAPTURE is not supported as it changes the type of $matches, use ' . $useFunctionName . '() instead'); + } + } + + private static function checkSetOrder(int $flags): void + { + if (($flags & PREG_SET_ORDER) !== 0) { + throw new \InvalidArgumentException('PREG_SET_ORDER is not supported as it changes the type of $matches'); + } + } + + /** + * @param array $matches + * @return array + * @throws UnexpectedNullMatchException + */ + private static function enforceNonNullMatches(string $pattern, array $matches, string $variantMethod) + { + foreach ($matches as $group => $match) { + if (is_string($match) || (is_array($match) && is_string($match[0]))) { + continue; + } + + throw new UnexpectedNullMatchException('Pattern "'.$pattern.'" had an unexpected unmatched group "'.$group.'", make sure the pattern always matches or use '.$variantMethod.'() instead.'); + } + + /** @var array */ + return $matches; + } + + /** + * @param array> $matches + * @return array> + * @throws UnexpectedNullMatchException + */ + private static function enforceNonNullMatchAll(string $pattern, array $matches, string $variantMethod) + { + foreach ($matches as $group => $groupMatches) { + foreach ($groupMatches as $match) { + if (null === $match) { + throw new UnexpectedNullMatchException('Pattern "'.$pattern.'" had an unexpected unmatched group "'.$group.'", make sure the pattern always matches or use '.$variantMethod.'() instead.'); + } + } + } + + /** @var array> */ + return $matches; + } +} diff --git a/vendor/composer/pcre/src/Regex.php b/vendor/composer/pcre/src/Regex.php new file mode 100644 index 00000000..21564a47 --- /dev/null +++ b/vendor/composer/pcre/src/Regex.php @@ -0,0 +1,174 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Pcre; + +class Regex +{ + /** + * @param non-empty-string $pattern + */ + public static function isMatch(string $pattern, string $subject, int $offset = 0): bool + { + return (bool) Preg::match($pattern, $subject, $matches, 0, $offset); + } + + /** + * @param non-empty-string $pattern + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + */ + public static function match(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchResult + { + self::checkOffsetCapture($flags, 'matchWithOffsets'); + + $count = Preg::match($pattern, $subject, $matches, $flags, $offset); + + return new MatchResult($count, $matches); + } + + /** + * Variant of `match()` which returns non-null matches (or throws) + * + * @param non-empty-string $pattern + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + * @throws UnexpectedNullMatchException + */ + public static function matchStrictGroups(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchStrictGroupsResult + { + $count = Preg::matchStrictGroups($pattern, $subject, $matches, $flags, $offset); + + return new MatchStrictGroupsResult($count, $matches); + } + + /** + * Runs preg_match with PREG_OFFSET_CAPTURE + * + * @param non-empty-string $pattern + * @param int-mask $flags PREG_UNMATCHED_AS_NULL and PREG_MATCH_OFFSET are always set, no other flags are supported + */ + public static function matchWithOffsets(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchWithOffsetsResult + { + $count = Preg::matchWithOffsets($pattern, $subject, $matches, $flags, $offset); + + return new MatchWithOffsetsResult($count, $matches); + } + + /** + * @param non-empty-string $pattern + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + */ + public static function matchAll(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchAllResult + { + self::checkOffsetCapture($flags, 'matchAllWithOffsets'); + self::checkSetOrder($flags); + + $count = Preg::matchAll($pattern, $subject, $matches, $flags, $offset); + + return new MatchAllResult($count, $matches); + } + + /** + * Variant of `matchAll()` which returns non-null matches (or throws) + * + * @param non-empty-string $pattern + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + * @throws UnexpectedNullMatchException + */ + public static function matchAllStrictGroups(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchAllStrictGroupsResult + { + self::checkOffsetCapture($flags, 'matchAllWithOffsets'); + self::checkSetOrder($flags); + + $count = Preg::matchAllStrictGroups($pattern, $subject, $matches, $flags, $offset); + + return new MatchAllStrictGroupsResult($count, $matches); + } + + /** + * Runs preg_match_all with PREG_OFFSET_CAPTURE + * + * @param non-empty-string $pattern + * @param int-mask $flags PREG_UNMATCHED_AS_NULL and PREG_MATCH_OFFSET are always set, no other flags are supported + */ + public static function matchAllWithOffsets(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchAllWithOffsetsResult + { + self::checkSetOrder($flags); + + $count = Preg::matchAllWithOffsets($pattern, $subject, $matches, $flags, $offset); + + return new MatchAllWithOffsetsResult($count, $matches); + } + /** + * @param string|string[] $pattern + * @param string|string[] $replacement + * @param string $subject + */ + public static function replace($pattern, $replacement, $subject, int $limit = -1): ReplaceResult + { + $result = Preg::replace($pattern, $replacement, $subject, $limit, $count); + + return new ReplaceResult($count, $result); + } + + /** + * @param string|string[] $pattern + * @param ($flags is PREG_OFFSET_CAPTURE ? (callable(array}>): string) : callable(array): string) $replacement + * @param string $subject + * @param int-mask $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set + */ + public static function replaceCallback($pattern, callable $replacement, $subject, int $limit = -1, int $flags = 0): ReplaceResult + { + $result = Preg::replaceCallback($pattern, $replacement, $subject, $limit, $count, $flags); + + return new ReplaceResult($count, $result); + } + + /** + * Variant of `replaceCallback()` which outputs non-null matches (or throws) + * + * @param string $pattern + * @param ($flags is PREG_OFFSET_CAPTURE ? (callable(array}>): string) : callable(array): string) $replacement + * @param string $subject + * @param int-mask $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set + */ + public static function replaceCallbackStrictGroups($pattern, callable $replacement, $subject, int $limit = -1, int $flags = 0): ReplaceResult + { + $result = Preg::replaceCallbackStrictGroups($pattern, $replacement, $subject, $limit, $count, $flags); + + return new ReplaceResult($count, $result); + } + + /** + * @param ($flags is PREG_OFFSET_CAPTURE ? (array}>): string>) : array): string>) $pattern + * @param string $subject + * @param int-mask $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set + */ + public static function replaceCallbackArray(array $pattern, $subject, int $limit = -1, int $flags = 0): ReplaceResult + { + $result = Preg::replaceCallbackArray($pattern, $subject, $limit, $count, $flags); + + return new ReplaceResult($count, $result); + } + + private static function checkOffsetCapture(int $flags, string $useFunctionName): void + { + if (($flags & PREG_OFFSET_CAPTURE) !== 0) { + throw new \InvalidArgumentException('PREG_OFFSET_CAPTURE is not supported as it changes the return type, use '.$useFunctionName.'() instead'); + } + } + + private static function checkSetOrder(int $flags): void + { + if (($flags & PREG_SET_ORDER) !== 0) { + throw new \InvalidArgumentException('PREG_SET_ORDER is not supported as it changes the return type'); + } + } +} diff --git a/vendor/composer/pcre/src/ReplaceResult.php b/vendor/composer/pcre/src/ReplaceResult.php new file mode 100644 index 00000000..33847712 --- /dev/null +++ b/vendor/composer/pcre/src/ReplaceResult.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Pcre; + +final class ReplaceResult +{ + /** + * @readonly + * @var string + */ + public $result; + + /** + * @readonly + * @var 0|positive-int + */ + public $count; + + /** + * @readonly + * @var bool + */ + public $matched; + + /** + * @param 0|positive-int $count + */ + public function __construct(int $count, string $result) + { + $this->count = $count; + $this->matched = (bool) $count; + $this->result = $result; + } +} diff --git a/vendor/composer/pcre/src/UnexpectedNullMatchException.php b/vendor/composer/pcre/src/UnexpectedNullMatchException.php new file mode 100644 index 00000000..f123828b --- /dev/null +++ b/vendor/composer/pcre/src/UnexpectedNullMatchException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Pcre; + +class UnexpectedNullMatchException extends PcreException +{ + public static function fromFunction($function, $pattern) + { + throw new \LogicException('fromFunction should not be called on '.self::class.', use '.PcreException::class); + } +} diff --git a/vendor/composer/platform_check.php b/vendor/composer/platform_check.php new file mode 100644 index 00000000..4c3a5d68 --- /dev/null +++ b/vendor/composer/platform_check.php @@ -0,0 +1,26 @@ += 80100)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.0". You are running ' . PHP_VERSION . '.'; +} + +if ($issues) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); + } elseif (!headers_sent()) { + echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; + } + } + trigger_error( + 'Composer detected issues in your platform: ' . implode(' ', $issues), + E_USER_ERROR + ); +} diff --git a/vendor/composer/semver/CHANGELOG.md b/vendor/composer/semver/CHANGELOG.md new file mode 100644 index 00000000..3b111612 --- /dev/null +++ b/vendor/composer/semver/CHANGELOG.md @@ -0,0 +1,214 @@ +# Change Log + +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/). + +### [3.4.0] 2023-08-31 + + * Support larger major version numbers (#149) + +### [3.3.2] 2022-04-01 + + * Fixed handling of non-string values (#134) + +### [3.3.1] 2022-03-16 + + * Fixed possible cache key clash in the CompilingMatcher memoization (#132) + +### [3.3.0] 2022-03-15 + + * Improved performance of CompilingMatcher by memoizing more (#131) + * Added CompilingMatcher::clear to clear all memoization caches + +### [3.2.9] 2022-02-04 + + * Revert #129 (Fixed MultiConstraint with MatchAllConstraint) which caused regressions + +### [3.2.8] 2022-02-04 + + * Updates to latest phpstan / CI by @Seldaek in https://github.com/composer/semver/pull/130 + * Fixed MultiConstraint with MatchAllConstraint by @Toflar in https://github.com/composer/semver/pull/129 + +### [3.2.7] 2022-01-04 + + * Fixed: typo in type definition of Intervals class causing issues with Psalm scanning vendors + +### [3.2.6] 2021-10-25 + + * Fixed: type improvements to parseStability + +### [3.2.5] 2021-05-24 + + * Fixed: issue comparing disjunctive MultiConstraints to conjunctive ones (#127) + * Fixed: added complete type information using phpstan annotations + +### [3.2.4] 2020-11-13 + + * Fixed: code clean-up + +### [3.2.3] 2020-11-12 + + * Fixed: constraints in the form of `X || Y, >=Y.1` and other such complex constructs were in some cases being optimized into a more restrictive constraint + +### [3.2.2] 2020-10-14 + + * Fixed: internal code cleanups + +### [3.2.1] 2020-09-27 + + * Fixed: accidental validation of broken constraints combining ^/~ and wildcards, and -dev suffix allowing weird cases + * Fixed: normalization of beta0 and such which was dropping the 0 + +### [3.2.0] 2020-09-09 + + * Added: support for `x || @dev`, not very useful but seen in the wild and failed to validate with 1.5.2/1.6.0 + * Added: support for `foobar-dev` being equal to `dev-foobar`, dev-foobar is the official way to write it but we need to support the other for BC and convenience + +### [3.1.0] 2020-09-08 + + * Added: support for constraints like `^2.x-dev` and `~2.x-dev`, not very useful but seen in the wild and failed to validate with 3.0.1 + * Fixed: invalid aliases will no longer throw, unless explicitly validated by Composer in the root package + +### [3.0.1] 2020-09-08 + + * Fixed: handling of some invalid -dev versions which were seen as valid + +### [3.0.0] 2020-05-26 + + * Break: Renamed `EmptyConstraint`, replace it with `MatchAllConstraint` + * Break: Unlikely to affect anyone but strictly speaking a breaking change, `*.*` and such variants will not match all `dev-*` versions anymore, only `*` does + * Break: ConstraintInterface is now considered internal/private and not meant to be implemented by third parties anymore + * Added `Intervals` class to check if a constraint is a subsets of another one, and allow compacting complex MultiConstraints into simpler ones + * Added `CompilingMatcher` class to speed up constraint matching against simple Constraint instances + * Added `MatchAllConstraint` and `MatchNoneConstraint` which match everything and nothing + * Added more advanced optimization of contiguous constraints inside MultiConstraint + * Added tentative support for PHP 8 + * Fixed ConstraintInterface::matches to be commutative in all cases + +### [2.0.0] 2020-04-21 + + * Break: `dev-master`, `dev-trunk` and `dev-default` now normalize to `dev-master`, `dev-trunk` and `dev-default` instead of `9999999-dev` in 1.x + * Break: Removed the deprecated `AbstractConstraint` + * Added `getUpperBound` and `getLowerBound` to ConstraintInterface. They return `Composer\Semver\Constraint\Bound` instances + * Added `MultiConstraint::create` to create the most-optimal form of ConstraintInterface from an array of constraint strings + +### [1.7.2] 2020-12-03 + + * Fixed: Allow installing on php 8 + +### [1.7.1] 2020-09-27 + + * Fixed: accidental validation of broken constraints combining ^/~ and wildcards, and -dev suffix allowing weird cases + * Fixed: normalization of beta0 and such which was dropping the 0 + +### [1.7.0] 2020-09-09 + + * Added: support for `x || @dev`, not very useful but seen in the wild and failed to validate with 1.5.2/1.6.0 + * Added: support for `foobar-dev` being equal to `dev-foobar`, dev-foobar is the official way to write it but we need to support the other for BC and convenience + +### [1.6.0] 2020-09-08 + + * Added: support for constraints like `^2.x-dev` and `~2.x-dev`, not very useful but seen in the wild and failed to validate with 1.5.2 + * Fixed: invalid aliases will no longer throw, unless explicitly validated by Composer in the root package + +### [1.5.2] 2020-09-08 + + * Fixed: handling of some invalid -dev versions which were seen as valid + * Fixed: some doctypes + +### [1.5.1] 2020-01-13 + + * Fixed: Parsing of aliased version was not validating the alias to be a valid version + +### [1.5.0] 2019-03-19 + + * Added: some support for date versions (e.g. 201903) in `~` operator + * Fixed: support for stabilities in `~` operator was inconsistent + +### [1.4.2] 2016-08-30 + + * Fixed: collapsing of complex constraints lead to buggy constraints + +### [1.4.1] 2016-06-02 + + * Changed: branch-like requirements no longer strip build metadata - [composer/semver#38](https://github.com/composer/semver/pull/38). + +### [1.4.0] 2016-03-30 + + * Added: getters on MultiConstraint - [composer/semver#35](https://github.com/composer/semver/pull/35). + +### [1.3.0] 2016-02-25 + + * Fixed: stability parsing - [composer/composer#1234](https://github.com/composer/composer/issues/4889). + * Changed: collapse contiguous constraints when possible. + +### [1.2.0] 2015-11-10 + + * Changed: allow multiple numerical identifiers in 'pre-release' version part. + * Changed: add more 'v' prefix support. + +### [1.1.0] 2015-11-03 + + * Changed: dropped redundant `test` namespace. + * Changed: minor adjustment in datetime parsing normalization. + * Changed: `ConstraintInterface` relaxed, setPrettyString is not required anymore. + * Changed: `AbstractConstraint` marked deprecated, will be removed in 2.0. + * Changed: `Constraint` is now extensible. + +### [1.0.0] 2015-09-21 + + * Break: `VersionConstraint` renamed to `Constraint`. + * Break: `SpecificConstraint` renamed to `AbstractConstraint`. + * Break: `LinkConstraintInterface` renamed to `ConstraintInterface`. + * Break: `VersionParser::parseNameVersionPairs` was removed. + * Changed: `VersionParser::parseConstraints` allows (but ignores) build metadata now. + * Changed: `VersionParser::parseConstraints` allows (but ignores) prefixing numeric versions with a 'v' now. + * Changed: Fixed namespace(s) of test files. + * Changed: `Comparator::compare` no longer throws `InvalidArgumentException`. + * Changed: `Constraint` now throws `InvalidArgumentException`. + +### [0.1.0] 2015-07-23 + + * Added: `Composer\Semver\Comparator`, various methods to compare versions. + * Added: various documents such as README.md, LICENSE, etc. + * Added: configuration files for Git, Travis, php-cs-fixer, phpunit. + * Break: the following namespaces were renamed: + - Namespace: `Composer\Package\Version` -> `Composer\Semver` + - Namespace: `Composer\Package\LinkConstraint` -> `Composer\Semver\Constraint` + - Namespace: `Composer\Test\Package\Version` -> `Composer\Test\Semver` + - Namespace: `Composer\Test\Package\LinkConstraint` -> `Composer\Test\Semver\Constraint` + * Changed: code style using php-cs-fixer. + +[3.4.0]: https://github.com/composer/semver/compare/3.3.2...3.4.0 +[3.3.2]: https://github.com/composer/semver/compare/3.3.1...3.3.2 +[3.3.1]: https://github.com/composer/semver/compare/3.3.0...3.3.1 +[3.3.0]: https://github.com/composer/semver/compare/3.2.9...3.3.0 +[3.2.9]: https://github.com/composer/semver/compare/3.2.8...3.2.9 +[3.2.8]: https://github.com/composer/semver/compare/3.2.7...3.2.8 +[3.2.7]: https://github.com/composer/semver/compare/3.2.6...3.2.7 +[3.2.6]: https://github.com/composer/semver/compare/3.2.5...3.2.6 +[3.2.5]: https://github.com/composer/semver/compare/3.2.4...3.2.5 +[3.2.4]: https://github.com/composer/semver/compare/3.2.3...3.2.4 +[3.2.3]: https://github.com/composer/semver/compare/3.2.2...3.2.3 +[3.2.2]: https://github.com/composer/semver/compare/3.2.1...3.2.2 +[3.2.1]: https://github.com/composer/semver/compare/3.2.0...3.2.1 +[3.2.0]: https://github.com/composer/semver/compare/3.1.0...3.2.0 +[3.1.0]: https://github.com/composer/semver/compare/3.0.1...3.1.0 +[3.0.1]: https://github.com/composer/semver/compare/3.0.0...3.0.1 +[3.0.0]: https://github.com/composer/semver/compare/2.0.0...3.0.0 +[2.0.0]: https://github.com/composer/semver/compare/1.5.1...2.0.0 +[1.7.2]: https://github.com/composer/semver/compare/1.7.1...1.7.2 +[1.7.1]: https://github.com/composer/semver/compare/1.7.0...1.7.1 +[1.7.0]: https://github.com/composer/semver/compare/1.6.0...1.7.0 +[1.6.0]: https://github.com/composer/semver/compare/1.5.2...1.6.0 +[1.5.2]: https://github.com/composer/semver/compare/1.5.1...1.5.2 +[1.5.1]: https://github.com/composer/semver/compare/1.5.0...1.5.1 +[1.5.0]: https://github.com/composer/semver/compare/1.4.2...1.5.0 +[1.4.2]: https://github.com/composer/semver/compare/1.4.1...1.4.2 +[1.4.1]: https://github.com/composer/semver/compare/1.4.0...1.4.1 +[1.4.0]: https://github.com/composer/semver/compare/1.3.0...1.4.0 +[1.3.0]: https://github.com/composer/semver/compare/1.2.0...1.3.0 +[1.2.0]: https://github.com/composer/semver/compare/1.1.0...1.2.0 +[1.1.0]: https://github.com/composer/semver/compare/1.0.0...1.1.0 +[1.0.0]: https://github.com/composer/semver/compare/0.1.0...1.0.0 +[0.1.0]: https://github.com/composer/semver/compare/5e0b9a4da...0.1.0 diff --git a/vendor/composer/semver/LICENSE b/vendor/composer/semver/LICENSE new file mode 100644 index 00000000..46697586 --- /dev/null +++ b/vendor/composer/semver/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2015 Composer + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/composer/semver/README.md b/vendor/composer/semver/README.md new file mode 100644 index 00000000..76778490 --- /dev/null +++ b/vendor/composer/semver/README.md @@ -0,0 +1,99 @@ +composer/semver +=============== + +Semver (Semantic Versioning) library that offers utilities, version constraint parsing and validation. + +Originally written as part of [composer/composer](https://github.com/composer/composer), +now extracted and made available as a stand-alone library. + +[![Continuous Integration](https://github.com/composer/semver/actions/workflows/continuous-integration.yml/badge.svg?branch=main)](https://github.com/composer/semver/actions/workflows/continuous-integration.yml) +[![PHP Lint](https://github.com/composer/semver/actions/workflows/lint.yml/badge.svg?branch=main)](https://github.com/composer/semver/actions/workflows/lint.yml) +[![PHPStan](https://github.com/composer/semver/actions/workflows/phpstan.yml/badge.svg?branch=main)](https://github.com/composer/semver/actions/workflows/phpstan.yml) + +Installation +------------ + +Install the latest version with: + +```bash +composer require composer/semver +``` + + +Requirements +------------ + +* PHP 5.3.2 is required but using the latest version of PHP is highly recommended. + + +Version Comparison +------------------ + +For details on how versions are compared, refer to the [Versions](https://getcomposer.org/doc/articles/versions.md) +article in the documentation section of the [getcomposer.org](https://getcomposer.org) website. + + +Basic usage +----------- + +### Comparator + +The [`Composer\Semver\Comparator`](https://github.com/composer/semver/blob/main/src/Comparator.php) class provides the following methods for comparing versions: + +* greaterThan($v1, $v2) +* greaterThanOrEqualTo($v1, $v2) +* lessThan($v1, $v2) +* lessThanOrEqualTo($v1, $v2) +* equalTo($v1, $v2) +* notEqualTo($v1, $v2) + +Each function takes two version strings as arguments and returns a boolean. For example: + +```php +use Composer\Semver\Comparator; + +Comparator::greaterThan('1.25.0', '1.24.0'); // 1.25.0 > 1.24.0 +``` + +### Semver + +The [`Composer\Semver\Semver`](https://github.com/composer/semver/blob/main/src/Semver.php) class provides the following methods: + +* satisfies($version, $constraints) +* satisfiedBy(array $versions, $constraint) +* sort($versions) +* rsort($versions) + +### Intervals + +The [`Composer\Semver\Intervals`](https://github.com/composer/semver/blob/main/src/Intervals.php) static class provides +a few utilities to work with complex constraints or read version intervals from a constraint: + +```php +use Composer\Semver\Intervals; + +// Checks whether $candidate is a subset of $constraint +Intervals::isSubsetOf(ConstraintInterface $candidate, ConstraintInterface $constraint); + +// Checks whether $a and $b have any intersection, equivalent to $a->matches($b) +Intervals::haveIntersections(ConstraintInterface $a, ConstraintInterface $b); + +// Optimizes a complex multi constraint by merging all intervals down to the smallest +// possible multi constraint. The drawbacks are this is not very fast, and the resulting +// multi constraint will have no human readable prettyConstraint configured on it +Intervals::compactConstraint(ConstraintInterface $constraint); + +// Creates an array of numeric intervals and branch constraints representing a given constraint +Intervals::get(ConstraintInterface $constraint); + +// Clears the memoization cache when you are done processing constraints +Intervals::clear() +``` + +See the class docblocks for more details. + + +License +------- + +composer/semver is licensed under the MIT License, see the LICENSE file for details. diff --git a/vendor/composer/semver/composer.json b/vendor/composer/semver/composer.json new file mode 100644 index 00000000..f3a6f4cc --- /dev/null +++ b/vendor/composer/semver/composer.json @@ -0,0 +1,59 @@ +{ + "name": "composer/semver", + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "type": "library", + "license": "MIT", + "keywords": [ + "semver", + "semantic", + "versioning", + "validation" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.2 || ^5", + "phpstan/phpstan": "^1.4" + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Composer\\Semver\\": "tests" + } + }, + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "scripts": { + "test": "SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT=1 vendor/bin/simple-phpunit", + "phpstan": "@php vendor/bin/phpstan analyse" + } +} diff --git a/vendor/composer/semver/phpstan-baseline.neon b/vendor/composer/semver/phpstan-baseline.neon new file mode 100644 index 00000000..933cf203 --- /dev/null +++ b/vendor/composer/semver/phpstan-baseline.neon @@ -0,0 +1,11 @@ +parameters: + ignoreErrors: + - + message: "#^Parameter \\#1 \\$operator of class Composer\\\\Semver\\\\Constraint\\\\Constraint constructor expects '\\!\\='\\|'\\<'\\|'\\<\\='\\|'\\<\\>'\\|'\\='\\|'\\=\\='\\|'\\>'\\|'\\>\\=', non\\-falsy\\-string given\\.$#" + count: 1 + path: src/VersionParser.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and non\\-empty\\-string will always evaluate to false\\.$#" + count: 2 + path: src/VersionParser.php diff --git a/vendor/composer/semver/src/Comparator.php b/vendor/composer/semver/src/Comparator.php new file mode 100644 index 00000000..38f483aa --- /dev/null +++ b/vendor/composer/semver/src/Comparator.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Semver; + +use Composer\Semver\Constraint\Constraint; + +class Comparator +{ + /** + * Evaluates the expression: $version1 > $version2. + * + * @param string $version1 + * @param string $version2 + * + * @return bool + */ + public static function greaterThan($version1, $version2) + { + return self::compare($version1, '>', $version2); + } + + /** + * Evaluates the expression: $version1 >= $version2. + * + * @param string $version1 + * @param string $version2 + * + * @return bool + */ + public static function greaterThanOrEqualTo($version1, $version2) + { + return self::compare($version1, '>=', $version2); + } + + /** + * Evaluates the expression: $version1 < $version2. + * + * @param string $version1 + * @param string $version2 + * + * @return bool + */ + public static function lessThan($version1, $version2) + { + return self::compare($version1, '<', $version2); + } + + /** + * Evaluates the expression: $version1 <= $version2. + * + * @param string $version1 + * @param string $version2 + * + * @return bool + */ + public static function lessThanOrEqualTo($version1, $version2) + { + return self::compare($version1, '<=', $version2); + } + + /** + * Evaluates the expression: $version1 == $version2. + * + * @param string $version1 + * @param string $version2 + * + * @return bool + */ + public static function equalTo($version1, $version2) + { + return self::compare($version1, '==', $version2); + } + + /** + * Evaluates the expression: $version1 != $version2. + * + * @param string $version1 + * @param string $version2 + * + * @return bool + */ + public static function notEqualTo($version1, $version2) + { + return self::compare($version1, '!=', $version2); + } + + /** + * Evaluates the expression: $version1 $operator $version2. + * + * @param string $version1 + * @param string $operator + * @param string $version2 + * + * @return bool + * + * @phpstan-param Constraint::STR_OP_* $operator + */ + public static function compare($version1, $operator, $version2) + { + $constraint = new Constraint($operator, $version2); + + return $constraint->matchSpecific(new Constraint('==', $version1), true); + } +} diff --git a/vendor/composer/semver/src/CompilingMatcher.php b/vendor/composer/semver/src/CompilingMatcher.php new file mode 100644 index 00000000..45bce70a --- /dev/null +++ b/vendor/composer/semver/src/CompilingMatcher.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Semver; + +use Composer\Semver\Constraint\Constraint; +use Composer\Semver\Constraint\ConstraintInterface; + +/** + * Helper class to evaluate constraint by compiling and reusing the code to evaluate + */ +class CompilingMatcher +{ + /** + * @var array + * @phpstan-var array + */ + private static $compiledCheckerCache = array(); + /** + * @var array + * @phpstan-var array + */ + private static $resultCache = array(); + + /** @var bool */ + private static $enabled; + + /** + * @phpstan-var array + */ + private static $transOpInt = array( + Constraint::OP_EQ => Constraint::STR_OP_EQ, + Constraint::OP_LT => Constraint::STR_OP_LT, + Constraint::OP_LE => Constraint::STR_OP_LE, + Constraint::OP_GT => Constraint::STR_OP_GT, + Constraint::OP_GE => Constraint::STR_OP_GE, + Constraint::OP_NE => Constraint::STR_OP_NE, + ); + + /** + * Clears the memoization cache once you are done + * + * @return void + */ + public static function clear() + { + self::$resultCache = array(); + self::$compiledCheckerCache = array(); + } + + /** + * Evaluates the expression: $constraint match $operator $version + * + * @param ConstraintInterface $constraint + * @param int $operator + * @phpstan-param Constraint::OP_* $operator + * @param string $version + * + * @return mixed + */ + public static function match(ConstraintInterface $constraint, $operator, $version) + { + $resultCacheKey = $operator.$constraint.';'.$version; + + if (isset(self::$resultCache[$resultCacheKey])) { + return self::$resultCache[$resultCacheKey]; + } + + if (self::$enabled === null) { + self::$enabled = !\in_array('eval', explode(',', (string) ini_get('disable_functions')), true); + } + if (!self::$enabled) { + return self::$resultCache[$resultCacheKey] = $constraint->matches(new Constraint(self::$transOpInt[$operator], $version)); + } + + $cacheKey = $operator.$constraint; + if (!isset(self::$compiledCheckerCache[$cacheKey])) { + $code = $constraint->compile($operator); + self::$compiledCheckerCache[$cacheKey] = $function = eval('return function($v, $b){return '.$code.';};'); + } else { + $function = self::$compiledCheckerCache[$cacheKey]; + } + + return self::$resultCache[$resultCacheKey] = $function($version, strpos($version, 'dev-') === 0); + } +} diff --git a/vendor/composer/semver/src/Constraint/Bound.php b/vendor/composer/semver/src/Constraint/Bound.php new file mode 100644 index 00000000..7effb11a --- /dev/null +++ b/vendor/composer/semver/src/Constraint/Bound.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Semver\Constraint; + +class Bound +{ + /** + * @var string + */ + private $version; + + /** + * @var bool + */ + private $isInclusive; + + /** + * @param string $version + * @param bool $isInclusive + */ + public function __construct($version, $isInclusive) + { + $this->version = $version; + $this->isInclusive = $isInclusive; + } + + /** + * @return string + */ + public function getVersion() + { + return $this->version; + } + + /** + * @return bool + */ + public function isInclusive() + { + return $this->isInclusive; + } + + /** + * @return bool + */ + public function isZero() + { + return $this->getVersion() === '0.0.0.0-dev' && $this->isInclusive(); + } + + /** + * @return bool + */ + public function isPositiveInfinity() + { + return $this->getVersion() === PHP_INT_MAX.'.0.0.0' && !$this->isInclusive(); + } + + /** + * Compares a bound to another with a given operator. + * + * @param Bound $other + * @param string $operator + * + * @return bool + */ + public function compareTo(Bound $other, $operator) + { + if (!\in_array($operator, array('<', '>'), true)) { + throw new \InvalidArgumentException('Does not support any other operator other than > or <.'); + } + + // If they are the same it doesn't matter + if ($this == $other) { + return false; + } + + $compareResult = version_compare($this->getVersion(), $other->getVersion()); + + // Not the same version means we don't need to check if the bounds are inclusive or not + if (0 !== $compareResult) { + return (('>' === $operator) ? 1 : -1) === $compareResult; + } + + // Question we're answering here is "am I higher than $other?" + return '>' === $operator ? $other->isInclusive() : !$other->isInclusive(); + } + + public function __toString() + { + return sprintf( + '%s [%s]', + $this->getVersion(), + $this->isInclusive() ? 'inclusive' : 'exclusive' + ); + } + + /** + * @return self + */ + public static function zero() + { + return new Bound('0.0.0.0-dev', true); + } + + /** + * @return self + */ + public static function positiveInfinity() + { + return new Bound(PHP_INT_MAX.'.0.0.0', false); + } +} diff --git a/vendor/composer/semver/src/Constraint/Constraint.php b/vendor/composer/semver/src/Constraint/Constraint.php new file mode 100644 index 00000000..dc394829 --- /dev/null +++ b/vendor/composer/semver/src/Constraint/Constraint.php @@ -0,0 +1,435 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Semver\Constraint; + +/** + * Defines a constraint. + */ +class Constraint implements ConstraintInterface +{ + /* operator integer values */ + const OP_EQ = 0; + const OP_LT = 1; + const OP_LE = 2; + const OP_GT = 3; + const OP_GE = 4; + const OP_NE = 5; + + /* operator string values */ + const STR_OP_EQ = '=='; + const STR_OP_EQ_ALT = '='; + const STR_OP_LT = '<'; + const STR_OP_LE = '<='; + const STR_OP_GT = '>'; + const STR_OP_GE = '>='; + const STR_OP_NE = '!='; + const STR_OP_NE_ALT = '<>'; + + /** + * Operator to integer translation table. + * + * @var array + * @phpstan-var array + */ + private static $transOpStr = array( + '=' => self::OP_EQ, + '==' => self::OP_EQ, + '<' => self::OP_LT, + '<=' => self::OP_LE, + '>' => self::OP_GT, + '>=' => self::OP_GE, + '<>' => self::OP_NE, + '!=' => self::OP_NE, + ); + + /** + * Integer to operator translation table. + * + * @var array + * @phpstan-var array + */ + private static $transOpInt = array( + self::OP_EQ => '==', + self::OP_LT => '<', + self::OP_LE => '<=', + self::OP_GT => '>', + self::OP_GE => '>=', + self::OP_NE => '!=', + ); + + /** + * @var int + * @phpstan-var self::OP_* + */ + protected $operator; + + /** @var string */ + protected $version; + + /** @var string|null */ + protected $prettyString; + + /** @var Bound */ + protected $lowerBound; + + /** @var Bound */ + protected $upperBound; + + /** + * Sets operator and version to compare with. + * + * @param string $operator + * @param string $version + * + * @throws \InvalidArgumentException if invalid operator is given. + * + * @phpstan-param self::STR_OP_* $operator + */ + public function __construct($operator, $version) + { + if (!isset(self::$transOpStr[$operator])) { + throw new \InvalidArgumentException(sprintf( + 'Invalid operator "%s" given, expected one of: %s', + $operator, + implode(', ', self::getSupportedOperators()) + )); + } + + $this->operator = self::$transOpStr[$operator]; + $this->version = $version; + } + + /** + * @return string + */ + public function getVersion() + { + return $this->version; + } + + /** + * @return string + * + * @phpstan-return self::STR_OP_* + */ + public function getOperator() + { + return self::$transOpInt[$this->operator]; + } + + /** + * @param ConstraintInterface $provider + * + * @return bool + */ + public function matches(ConstraintInterface $provider) + { + if ($provider instanceof self) { + return $this->matchSpecific($provider); + } + + // turn matching around to find a match + return $provider->matches($this); + } + + /** + * {@inheritDoc} + */ + public function setPrettyString($prettyString) + { + $this->prettyString = $prettyString; + } + + /** + * {@inheritDoc} + */ + public function getPrettyString() + { + if ($this->prettyString) { + return $this->prettyString; + } + + return $this->__toString(); + } + + /** + * Get all supported comparison operators. + * + * @return array + * + * @phpstan-return list + */ + public static function getSupportedOperators() + { + return array_keys(self::$transOpStr); + } + + /** + * @param string $operator + * @return int + * + * @phpstan-param self::STR_OP_* $operator + * @phpstan-return self::OP_* + */ + public static function getOperatorConstant($operator) + { + return self::$transOpStr[$operator]; + } + + /** + * @param string $a + * @param string $b + * @param string $operator + * @param bool $compareBranches + * + * @throws \InvalidArgumentException if invalid operator is given. + * + * @return bool + * + * @phpstan-param self::STR_OP_* $operator + */ + public function versionCompare($a, $b, $operator, $compareBranches = false) + { + if (!isset(self::$transOpStr[$operator])) { + throw new \InvalidArgumentException(sprintf( + 'Invalid operator "%s" given, expected one of: %s', + $operator, + implode(', ', self::getSupportedOperators()) + )); + } + + $aIsBranch = strpos($a, 'dev-') === 0; + $bIsBranch = strpos($b, 'dev-') === 0; + + if ($operator === '!=' && ($aIsBranch || $bIsBranch)) { + return $a !== $b; + } + + if ($aIsBranch && $bIsBranch) { + return $operator === '==' && $a === $b; + } + + // when branches are not comparable, we make sure dev branches never match anything + if (!$compareBranches && ($aIsBranch || $bIsBranch)) { + return false; + } + + return \version_compare($a, $b, $operator); + } + + /** + * {@inheritDoc} + */ + public function compile($otherOperator) + { + if (strpos($this->version, 'dev-') === 0) { + if (self::OP_EQ === $this->operator) { + if (self::OP_EQ === $otherOperator) { + return sprintf('$b && $v === %s', \var_export($this->version, true)); + } + if (self::OP_NE === $otherOperator) { + return sprintf('!$b || $v !== %s', \var_export($this->version, true)); + } + return 'false'; + } + + if (self::OP_NE === $this->operator) { + if (self::OP_EQ === $otherOperator) { + return sprintf('!$b || $v !== %s', \var_export($this->version, true)); + } + if (self::OP_NE === $otherOperator) { + return 'true'; + } + return '!$b'; + } + + return 'false'; + } + + if (self::OP_EQ === $this->operator) { + if (self::OP_EQ === $otherOperator) { + return sprintf('\version_compare($v, %s, \'==\')', \var_export($this->version, true)); + } + if (self::OP_NE === $otherOperator) { + return sprintf('$b || \version_compare($v, %s, \'!=\')', \var_export($this->version, true)); + } + + return sprintf('!$b && \version_compare(%s, $v, \'%s\')', \var_export($this->version, true), self::$transOpInt[$otherOperator]); + } + + if (self::OP_NE === $this->operator) { + if (self::OP_EQ === $otherOperator) { + return sprintf('$b || (!$b && \version_compare($v, %s, \'!=\'))', \var_export($this->version, true)); + } + + if (self::OP_NE === $otherOperator) { + return 'true'; + } + return '!$b'; + } + + if (self::OP_LT === $this->operator || self::OP_LE === $this->operator) { + if (self::OP_LT === $otherOperator || self::OP_LE === $otherOperator) { + return '!$b'; + } + } else { // $this->operator must be self::OP_GT || self::OP_GE here + if (self::OP_GT === $otherOperator || self::OP_GE === $otherOperator) { + return '!$b'; + } + } + + if (self::OP_NE === $otherOperator) { + return 'true'; + } + + $codeComparison = sprintf('\version_compare($v, %s, \'%s\')', \var_export($this->version, true), self::$transOpInt[$this->operator]); + if ($this->operator === self::OP_LE) { + if ($otherOperator === self::OP_GT) { + return sprintf('!$b && \version_compare($v, %s, \'!=\') && ', \var_export($this->version, true)) . $codeComparison; + } + } elseif ($this->operator === self::OP_GE) { + if ($otherOperator === self::OP_LT) { + return sprintf('!$b && \version_compare($v, %s, \'!=\') && ', \var_export($this->version, true)) . $codeComparison; + } + } + + return sprintf('!$b && %s', $codeComparison); + } + + /** + * @param Constraint $provider + * @param bool $compareBranches + * + * @return bool + */ + public function matchSpecific(Constraint $provider, $compareBranches = false) + { + $noEqualOp = str_replace('=', '', self::$transOpInt[$this->operator]); + $providerNoEqualOp = str_replace('=', '', self::$transOpInt[$provider->operator]); + + $isEqualOp = self::OP_EQ === $this->operator; + $isNonEqualOp = self::OP_NE === $this->operator; + $isProviderEqualOp = self::OP_EQ === $provider->operator; + $isProviderNonEqualOp = self::OP_NE === $provider->operator; + + // '!=' operator is match when other operator is not '==' operator or version is not match + // these kinds of comparisons always have a solution + if ($isNonEqualOp || $isProviderNonEqualOp) { + if ($isNonEqualOp && !$isProviderNonEqualOp && !$isProviderEqualOp && strpos($provider->version, 'dev-') === 0) { + return false; + } + + if ($isProviderNonEqualOp && !$isNonEqualOp && !$isEqualOp && strpos($this->version, 'dev-') === 0) { + return false; + } + + if (!$isEqualOp && !$isProviderEqualOp) { + return true; + } + return $this->versionCompare($provider->version, $this->version, '!=', $compareBranches); + } + + // an example for the condition is <= 2.0 & < 1.0 + // these kinds of comparisons always have a solution + if ($this->operator !== self::OP_EQ && $noEqualOp === $providerNoEqualOp) { + return !(strpos($this->version, 'dev-') === 0 || strpos($provider->version, 'dev-') === 0); + } + + $version1 = $isEqualOp ? $this->version : $provider->version; + $version2 = $isEqualOp ? $provider->version : $this->version; + $operator = $isEqualOp ? $provider->operator : $this->operator; + + if ($this->versionCompare($version1, $version2, self::$transOpInt[$operator], $compareBranches)) { + // special case, e.g. require >= 1.0 and provide < 1.0 + // 1.0 >= 1.0 but 1.0 is outside of the provided interval + + return !(self::$transOpInt[$provider->operator] === $providerNoEqualOp + && self::$transOpInt[$this->operator] !== $noEqualOp + && \version_compare($provider->version, $this->version, '==')); + } + + return false; + } + + /** + * @return string + */ + public function __toString() + { + return self::$transOpInt[$this->operator] . ' ' . $this->version; + } + + /** + * {@inheritDoc} + */ + public function getLowerBound() + { + $this->extractBounds(); + + return $this->lowerBound; + } + + /** + * {@inheritDoc} + */ + public function getUpperBound() + { + $this->extractBounds(); + + return $this->upperBound; + } + + /** + * @return void + */ + private function extractBounds() + { + if (null !== $this->lowerBound) { + return; + } + + // Branches + if (strpos($this->version, 'dev-') === 0) { + $this->lowerBound = Bound::zero(); + $this->upperBound = Bound::positiveInfinity(); + + return; + } + + switch ($this->operator) { + case self::OP_EQ: + $this->lowerBound = new Bound($this->version, true); + $this->upperBound = new Bound($this->version, true); + break; + case self::OP_LT: + $this->lowerBound = Bound::zero(); + $this->upperBound = new Bound($this->version, false); + break; + case self::OP_LE: + $this->lowerBound = Bound::zero(); + $this->upperBound = new Bound($this->version, true); + break; + case self::OP_GT: + $this->lowerBound = new Bound($this->version, false); + $this->upperBound = Bound::positiveInfinity(); + break; + case self::OP_GE: + $this->lowerBound = new Bound($this->version, true); + $this->upperBound = Bound::positiveInfinity(); + break; + case self::OP_NE: + $this->lowerBound = Bound::zero(); + $this->upperBound = Bound::positiveInfinity(); + break; + } + } +} diff --git a/vendor/composer/semver/src/Constraint/ConstraintInterface.php b/vendor/composer/semver/src/Constraint/ConstraintInterface.php new file mode 100644 index 00000000..389b935b --- /dev/null +++ b/vendor/composer/semver/src/Constraint/ConstraintInterface.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Semver\Constraint; + +/** + * DO NOT IMPLEMENT this interface. It is only meant for usage as a type hint + * in libraries relying on composer/semver but creating your own constraint class + * that implements this interface is not a supported use case and will cause the + * composer/semver components to return unexpected results. + */ +interface ConstraintInterface +{ + /** + * Checks whether the given constraint intersects in any way with this constraint + * + * @param ConstraintInterface $provider + * + * @return bool + */ + public function matches(ConstraintInterface $provider); + + /** + * Provides a compiled version of the constraint for the given operator + * The compiled version must be a PHP expression. + * Executor of compile version must provide 2 variables: + * - $v = the string version to compare with + * - $b = whether or not the version is a non-comparable branch (starts with "dev-") + * + * @see Constraint::OP_* for the list of available operators. + * @example return '!$b && version_compare($v, '1.0', '>')'; + * + * @param int $otherOperator one Constraint::OP_* + * + * @return string + * + * @phpstan-param Constraint::OP_* $otherOperator + */ + public function compile($otherOperator); + + /** + * @return Bound + */ + public function getUpperBound(); + + /** + * @return Bound + */ + public function getLowerBound(); + + /** + * @return string + */ + public function getPrettyString(); + + /** + * @param string|null $prettyString + * + * @return void + */ + public function setPrettyString($prettyString); + + /** + * @return string + */ + public function __toString(); +} diff --git a/vendor/composer/semver/src/Constraint/MatchAllConstraint.php b/vendor/composer/semver/src/Constraint/MatchAllConstraint.php new file mode 100644 index 00000000..5e51af95 --- /dev/null +++ b/vendor/composer/semver/src/Constraint/MatchAllConstraint.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Semver\Constraint; + +/** + * Defines the absence of a constraint. + * + * This constraint matches everything. + */ +class MatchAllConstraint implements ConstraintInterface +{ + /** @var string|null */ + protected $prettyString; + + /** + * @param ConstraintInterface $provider + * + * @return bool + */ + public function matches(ConstraintInterface $provider) + { + return true; + } + + /** + * {@inheritDoc} + */ + public function compile($otherOperator) + { + return 'true'; + } + + /** + * {@inheritDoc} + */ + public function setPrettyString($prettyString) + { + $this->prettyString = $prettyString; + } + + /** + * {@inheritDoc} + */ + public function getPrettyString() + { + if ($this->prettyString) { + return $this->prettyString; + } + + return (string) $this; + } + + /** + * {@inheritDoc} + */ + public function __toString() + { + return '*'; + } + + /** + * {@inheritDoc} + */ + public function getUpperBound() + { + return Bound::positiveInfinity(); + } + + /** + * {@inheritDoc} + */ + public function getLowerBound() + { + return Bound::zero(); + } +} diff --git a/vendor/composer/semver/src/Constraint/MatchNoneConstraint.php b/vendor/composer/semver/src/Constraint/MatchNoneConstraint.php new file mode 100644 index 00000000..dadcf622 --- /dev/null +++ b/vendor/composer/semver/src/Constraint/MatchNoneConstraint.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Semver\Constraint; + +/** + * Blackhole of constraints, nothing escapes it + */ +class MatchNoneConstraint implements ConstraintInterface +{ + /** @var string|null */ + protected $prettyString; + + /** + * @param ConstraintInterface $provider + * + * @return bool + */ + public function matches(ConstraintInterface $provider) + { + return false; + } + + /** + * {@inheritDoc} + */ + public function compile($otherOperator) + { + return 'false'; + } + + /** + * {@inheritDoc} + */ + public function setPrettyString($prettyString) + { + $this->prettyString = $prettyString; + } + + /** + * {@inheritDoc} + */ + public function getPrettyString() + { + if ($this->prettyString) { + return $this->prettyString; + } + + return (string) $this; + } + + /** + * {@inheritDoc} + */ + public function __toString() + { + return '[]'; + } + + /** + * {@inheritDoc} + */ + public function getUpperBound() + { + return new Bound('0.0.0.0-dev', false); + } + + /** + * {@inheritDoc} + */ + public function getLowerBound() + { + return new Bound('0.0.0.0-dev', false); + } +} diff --git a/vendor/composer/semver/src/Constraint/MultiConstraint.php b/vendor/composer/semver/src/Constraint/MultiConstraint.php new file mode 100644 index 00000000..1f4c0061 --- /dev/null +++ b/vendor/composer/semver/src/Constraint/MultiConstraint.php @@ -0,0 +1,325 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Semver\Constraint; + +/** + * Defines a conjunctive or disjunctive set of constraints. + */ +class MultiConstraint implements ConstraintInterface +{ + /** + * @var ConstraintInterface[] + * @phpstan-var non-empty-array + */ + protected $constraints; + + /** @var string|null */ + protected $prettyString; + + /** @var string|null */ + protected $string; + + /** @var bool */ + protected $conjunctive; + + /** @var Bound|null */ + protected $lowerBound; + + /** @var Bound|null */ + protected $upperBound; + + /** + * @param ConstraintInterface[] $constraints A set of constraints + * @param bool $conjunctive Whether the constraints should be treated as conjunctive or disjunctive + * + * @throws \InvalidArgumentException If less than 2 constraints are passed + */ + public function __construct(array $constraints, $conjunctive = true) + { + if (\count($constraints) < 2) { + throw new \InvalidArgumentException( + 'Must provide at least two constraints for a MultiConstraint. Use '. + 'the regular Constraint class for one constraint only or MatchAllConstraint for none. You may use '. + 'MultiConstraint::create() which optimizes and handles those cases automatically.' + ); + } + + $this->constraints = $constraints; + $this->conjunctive = $conjunctive; + } + + /** + * @return ConstraintInterface[] + */ + public function getConstraints() + { + return $this->constraints; + } + + /** + * @return bool + */ + public function isConjunctive() + { + return $this->conjunctive; + } + + /** + * @return bool + */ + public function isDisjunctive() + { + return !$this->conjunctive; + } + + /** + * {@inheritDoc} + */ + public function compile($otherOperator) + { + $parts = array(); + foreach ($this->constraints as $constraint) { + $code = $constraint->compile($otherOperator); + if ($code === 'true') { + if (!$this->conjunctive) { + return 'true'; + } + } elseif ($code === 'false') { + if ($this->conjunctive) { + return 'false'; + } + } else { + $parts[] = '('.$code.')'; + } + } + + if (!$parts) { + return $this->conjunctive ? 'true' : 'false'; + } + + return $this->conjunctive ? implode('&&', $parts) : implode('||', $parts); + } + + /** + * @param ConstraintInterface $provider + * + * @return bool + */ + public function matches(ConstraintInterface $provider) + { + if (false === $this->conjunctive) { + foreach ($this->constraints as $constraint) { + if ($provider->matches($constraint)) { + return true; + } + } + + return false; + } + + // when matching a conjunctive and a disjunctive multi constraint we have to iterate over the disjunctive one + // otherwise we'd return true if different parts of the disjunctive constraint match the conjunctive one + // which would lead to incorrect results, e.g. [>1 and <2] would match [<1 or >2] although they do not intersect + if ($provider instanceof MultiConstraint && $provider->isDisjunctive()) { + return $provider->matches($this); + } + + foreach ($this->constraints as $constraint) { + if (!$provider->matches($constraint)) { + return false; + } + } + + return true; + } + + /** + * {@inheritDoc} + */ + public function setPrettyString($prettyString) + { + $this->prettyString = $prettyString; + } + + /** + * {@inheritDoc} + */ + public function getPrettyString() + { + if ($this->prettyString) { + return $this->prettyString; + } + + return (string) $this; + } + + /** + * {@inheritDoc} + */ + public function __toString() + { + if ($this->string !== null) { + return $this->string; + } + + $constraints = array(); + foreach ($this->constraints as $constraint) { + $constraints[] = (string) $constraint; + } + + return $this->string = '[' . implode($this->conjunctive ? ' ' : ' || ', $constraints) . ']'; + } + + /** + * {@inheritDoc} + */ + public function getLowerBound() + { + $this->extractBounds(); + + if (null === $this->lowerBound) { + throw new \LogicException('extractBounds should have populated the lowerBound property'); + } + + return $this->lowerBound; + } + + /** + * {@inheritDoc} + */ + public function getUpperBound() + { + $this->extractBounds(); + + if (null === $this->upperBound) { + throw new \LogicException('extractBounds should have populated the upperBound property'); + } + + return $this->upperBound; + } + + /** + * Tries to optimize the constraints as much as possible, meaning + * reducing/collapsing congruent constraints etc. + * Does not necessarily return a MultiConstraint instance if + * things can be reduced to a simple constraint + * + * @param ConstraintInterface[] $constraints A set of constraints + * @param bool $conjunctive Whether the constraints should be treated as conjunctive or disjunctive + * + * @return ConstraintInterface + */ + public static function create(array $constraints, $conjunctive = true) + { + if (0 === \count($constraints)) { + return new MatchAllConstraint(); + } + + if (1 === \count($constraints)) { + return $constraints[0]; + } + + $optimized = self::optimizeConstraints($constraints, $conjunctive); + if ($optimized !== null) { + list($constraints, $conjunctive) = $optimized; + if (\count($constraints) === 1) { + return $constraints[0]; + } + } + + return new self($constraints, $conjunctive); + } + + /** + * @param ConstraintInterface[] $constraints + * @param bool $conjunctive + * @return ?array + * + * @phpstan-return array{0: list, 1: bool}|null + */ + private static function optimizeConstraints(array $constraints, $conjunctive) + { + // parse the two OR groups and if they are contiguous we collapse + // them into one constraint + // [>= 1 < 2] || [>= 2 < 3] || [>= 3 < 4] => [>= 1 < 4] + if (!$conjunctive) { + $left = $constraints[0]; + $mergedConstraints = array(); + $optimized = false; + for ($i = 1, $l = \count($constraints); $i < $l; $i++) { + $right = $constraints[$i]; + if ( + $left instanceof self + && $left->conjunctive + && $right instanceof self + && $right->conjunctive + && \count($left->constraints) === 2 + && \count($right->constraints) === 2 + && ($left0 = (string) $left->constraints[0]) + && $left0[0] === '>' && $left0[1] === '=' + && ($left1 = (string) $left->constraints[1]) + && $left1[0] === '<' + && ($right0 = (string) $right->constraints[0]) + && $right0[0] === '>' && $right0[1] === '=' + && ($right1 = (string) $right->constraints[1]) + && $right1[0] === '<' + && substr($left1, 2) === substr($right0, 3) + ) { + $optimized = true; + $left = new MultiConstraint( + array( + $left->constraints[0], + $right->constraints[1], + ), + true); + } else { + $mergedConstraints[] = $left; + $left = $right; + } + } + if ($optimized) { + $mergedConstraints[] = $left; + return array($mergedConstraints, false); + } + } + + // TODO: Here's the place to put more optimizations + + return null; + } + + /** + * @return void + */ + private function extractBounds() + { + if (null !== $this->lowerBound) { + return; + } + + foreach ($this->constraints as $constraint) { + if (null === $this->lowerBound || null === $this->upperBound) { + $this->lowerBound = $constraint->getLowerBound(); + $this->upperBound = $constraint->getUpperBound(); + continue; + } + + if ($constraint->getLowerBound()->compareTo($this->lowerBound, $this->isConjunctive() ? '>' : '<')) { + $this->lowerBound = $constraint->getLowerBound(); + } + + if ($constraint->getUpperBound()->compareTo($this->upperBound, $this->isConjunctive() ? '<' : '>')) { + $this->upperBound = $constraint->getUpperBound(); + } + } + } +} diff --git a/vendor/composer/semver/src/Interval.php b/vendor/composer/semver/src/Interval.php new file mode 100644 index 00000000..43d5a4f5 --- /dev/null +++ b/vendor/composer/semver/src/Interval.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Semver; + +use Composer\Semver\Constraint\Constraint; + +class Interval +{ + /** @var Constraint */ + private $start; + /** @var Constraint */ + private $end; + + public function __construct(Constraint $start, Constraint $end) + { + $this->start = $start; + $this->end = $end; + } + + /** + * @return Constraint + */ + public function getStart() + { + return $this->start; + } + + /** + * @return Constraint + */ + public function getEnd() + { + return $this->end; + } + + /** + * @return Constraint + */ + public static function fromZero() + { + static $zero; + + if (null === $zero) { + $zero = new Constraint('>=', '0.0.0.0-dev'); + } + + return $zero; + } + + /** + * @return Constraint + */ + public static function untilPositiveInfinity() + { + static $positiveInfinity; + + if (null === $positiveInfinity) { + $positiveInfinity = new Constraint('<', PHP_INT_MAX.'.0.0.0'); + } + + return $positiveInfinity; + } + + /** + * @return self + */ + public static function any() + { + return new self(self::fromZero(), self::untilPositiveInfinity()); + } + + /** + * @return array{'names': string[], 'exclude': bool} + */ + public static function anyDev() + { + // any == exclude nothing + return array('names' => array(), 'exclude' => true); + } + + /** + * @return array{'names': string[], 'exclude': bool} + */ + public static function noDev() + { + // nothing == no names included + return array('names' => array(), 'exclude' => false); + } +} diff --git a/vendor/composer/semver/src/Intervals.php b/vendor/composer/semver/src/Intervals.php new file mode 100644 index 00000000..d889d0ad --- /dev/null +++ b/vendor/composer/semver/src/Intervals.php @@ -0,0 +1,478 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Semver; + +use Composer\Semver\Constraint\Constraint; +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Semver\Constraint\MatchAllConstraint; +use Composer\Semver\Constraint\MatchNoneConstraint; +use Composer\Semver\Constraint\MultiConstraint; + +/** + * Helper class generating intervals from constraints + * + * This contains utilities for: + * + * - compacting an existing constraint which can be used to combine several into one + * by creating a MultiConstraint out of the many constraints you have. + * + * - checking whether one subset is a subset of another. + * + * Note: You should call clear to free memoization memory usage when you are done using this class + */ +class Intervals +{ + /** + * @phpstan-var array + */ + private static $intervalsCache = array(); + + /** + * @phpstan-var array + */ + private static $opSortOrder = array( + '>=' => -3, + '<' => -2, + '>' => 2, + '<=' => 3, + ); + + /** + * Clears the memoization cache once you are done + * + * @return void + */ + public static function clear() + { + self::$intervalsCache = array(); + } + + /** + * Checks whether $candidate is a subset of $constraint + * + * @return bool + */ + public static function isSubsetOf(ConstraintInterface $candidate, ConstraintInterface $constraint) + { + if ($constraint instanceof MatchAllConstraint) { + return true; + } + + if ($candidate instanceof MatchNoneConstraint || $constraint instanceof MatchNoneConstraint) { + return false; + } + + $intersectionIntervals = self::get(new MultiConstraint(array($candidate, $constraint), true)); + $candidateIntervals = self::get($candidate); + if (\count($intersectionIntervals['numeric']) !== \count($candidateIntervals['numeric'])) { + return false; + } + + foreach ($intersectionIntervals['numeric'] as $index => $interval) { + if (!isset($candidateIntervals['numeric'][$index])) { + return false; + } + + if ((string) $candidateIntervals['numeric'][$index]->getStart() !== (string) $interval->getStart()) { + return false; + } + + if ((string) $candidateIntervals['numeric'][$index]->getEnd() !== (string) $interval->getEnd()) { + return false; + } + } + + if ($intersectionIntervals['branches']['exclude'] !== $candidateIntervals['branches']['exclude']) { + return false; + } + if (\count($intersectionIntervals['branches']['names']) !== \count($candidateIntervals['branches']['names'])) { + return false; + } + foreach ($intersectionIntervals['branches']['names'] as $index => $name) { + if ($name !== $candidateIntervals['branches']['names'][$index]) { + return false; + } + } + + return true; + } + + /** + * Checks whether $a and $b have any intersection, equivalent to $a->matches($b) + * + * @return bool + */ + public static function haveIntersections(ConstraintInterface $a, ConstraintInterface $b) + { + if ($a instanceof MatchAllConstraint || $b instanceof MatchAllConstraint) { + return true; + } + + if ($a instanceof MatchNoneConstraint || $b instanceof MatchNoneConstraint) { + return false; + } + + $intersectionIntervals = self::generateIntervals(new MultiConstraint(array($a, $b), true), true); + + return \count($intersectionIntervals['numeric']) > 0 || $intersectionIntervals['branches']['exclude'] || \count($intersectionIntervals['branches']['names']) > 0; + } + + /** + * Attempts to optimize a MultiConstraint + * + * When merging MultiConstraints together they can get very large, this will + * compact it by looking at the real intervals covered by all the constraints + * and then creates a new constraint containing only the smallest amount of rules + * to match the same intervals. + * + * @return ConstraintInterface + */ + public static function compactConstraint(ConstraintInterface $constraint) + { + if (!$constraint instanceof MultiConstraint) { + return $constraint; + } + + $intervals = self::generateIntervals($constraint); + $constraints = array(); + $hasNumericMatchAll = false; + + if (\count($intervals['numeric']) === 1 && (string) $intervals['numeric'][0]->getStart() === (string) Interval::fromZero() && (string) $intervals['numeric'][0]->getEnd() === (string) Interval::untilPositiveInfinity()) { + $constraints[] = $intervals['numeric'][0]->getStart(); + $hasNumericMatchAll = true; + } else { + $unEqualConstraints = array(); + for ($i = 0, $count = \count($intervals['numeric']); $i < $count; $i++) { + $interval = $intervals['numeric'][$i]; + + // if current interval ends with < N and next interval begins with > N we can swap this out for != N + // but this needs to happen as a conjunctive expression together with the start of the current interval + // and end of next interval, so [>=M, N, [>=M, !=N, getEnd()->getOperator() === '<' && $i+1 < $count) { + $nextInterval = $intervals['numeric'][$i+1]; + if ($interval->getEnd()->getVersion() === $nextInterval->getStart()->getVersion() && $nextInterval->getStart()->getOperator() === '>') { + // only add a start if we didn't already do so, can be skipped if we're looking at second + // interval in [>=M, N, P, =M, !=N] already and we only want to add !=P right now + if (\count($unEqualConstraints) === 0 && (string) $interval->getStart() !== (string) Interval::fromZero()) { + $unEqualConstraints[] = $interval->getStart(); + } + $unEqualConstraints[] = new Constraint('!=', $interval->getEnd()->getVersion()); + continue; + } + } + + if (\count($unEqualConstraints) > 0) { + // this is where the end of the following interval of a != constraint is added as explained above + if ((string) $interval->getEnd() !== (string) Interval::untilPositiveInfinity()) { + $unEqualConstraints[] = $interval->getEnd(); + } + + // count is 1 if entire constraint is just one != expression + if (\count($unEqualConstraints) > 1) { + $constraints[] = new MultiConstraint($unEqualConstraints, true); + } else { + $constraints[] = $unEqualConstraints[0]; + } + + $unEqualConstraints = array(); + continue; + } + + // convert back >= x - <= x intervals to == x + if ($interval->getStart()->getVersion() === $interval->getEnd()->getVersion() && $interval->getStart()->getOperator() === '>=' && $interval->getEnd()->getOperator() === '<=') { + $constraints[] = new Constraint('==', $interval->getStart()->getVersion()); + continue; + } + + if ((string) $interval->getStart() === (string) Interval::fromZero()) { + $constraints[] = $interval->getEnd(); + } elseif ((string) $interval->getEnd() === (string) Interval::untilPositiveInfinity()) { + $constraints[] = $interval->getStart(); + } else { + $constraints[] = new MultiConstraint(array($interval->getStart(), $interval->getEnd()), true); + } + } + } + + $devConstraints = array(); + + if (0 === \count($intervals['branches']['names'])) { + if ($intervals['branches']['exclude']) { + if ($hasNumericMatchAll) { + return new MatchAllConstraint; + } + // otherwise constraint should contain a != operator and already cover this + } + } else { + foreach ($intervals['branches']['names'] as $branchName) { + if ($intervals['branches']['exclude']) { + $devConstraints[] = new Constraint('!=', $branchName); + } else { + $devConstraints[] = new Constraint('==', $branchName); + } + } + + // excluded branches, e.g. != dev-foo are conjunctive with the interval, so + // > 2.0 != dev-foo must return a conjunctive constraint + if ($intervals['branches']['exclude']) { + if (\count($constraints) > 1) { + return new MultiConstraint(array_merge( + array(new MultiConstraint($constraints, false)), + $devConstraints + ), true); + } + + if (\count($constraints) === 1 && (string)$constraints[0] === (string)Interval::fromZero()) { + if (\count($devConstraints) > 1) { + return new MultiConstraint($devConstraints, true); + } + return $devConstraints[0]; + } + + return new MultiConstraint(array_merge($constraints, $devConstraints), true); + } + + // otherwise devConstraints contains a list of == operators for branches which are disjunctive with the + // rest of the constraint + $constraints = array_merge($constraints, $devConstraints); + } + + if (\count($constraints) > 1) { + return new MultiConstraint($constraints, false); + } + + if (\count($constraints) === 1) { + return $constraints[0]; + } + + return new MatchNoneConstraint; + } + + /** + * Creates an array of numeric intervals and branch constraints representing a given constraint + * + * if the returned numeric array is empty it means the constraint matches nothing in the numeric range (0 - +inf) + * if the returned branches array is empty it means no dev-* versions are matched + * if a constraint matches all possible dev-* versions, branches will contain Interval::anyDev() + * + * @return array + * @phpstan-return array{'numeric': Interval[], 'branches': array{'names': string[], 'exclude': bool}} + */ + public static function get(ConstraintInterface $constraint) + { + $key = (string) $constraint; + + if (!isset(self::$intervalsCache[$key])) { + self::$intervalsCache[$key] = self::generateIntervals($constraint); + } + + return self::$intervalsCache[$key]; + } + + /** + * @param bool $stopOnFirstValidInterval + * + * @phpstan-return array{'numeric': Interval[], 'branches': array{'names': string[], 'exclude': bool}} + */ + private static function generateIntervals(ConstraintInterface $constraint, $stopOnFirstValidInterval = false) + { + if ($constraint instanceof MatchAllConstraint) { + return array('numeric' => array(new Interval(Interval::fromZero(), Interval::untilPositiveInfinity())), 'branches' => Interval::anyDev()); + } + + if ($constraint instanceof MatchNoneConstraint) { + return array('numeric' => array(), 'branches' => array('names' => array(), 'exclude' => false)); + } + + if ($constraint instanceof Constraint) { + return self::generateSingleConstraintIntervals($constraint); + } + + if (!$constraint instanceof MultiConstraint) { + throw new \UnexpectedValueException('The constraint passed in should be an MatchAllConstraint, Constraint or MultiConstraint instance, got '.\get_class($constraint).'.'); + } + + $constraints = $constraint->getConstraints(); + + $numericGroups = array(); + $constraintBranches = array(); + foreach ($constraints as $c) { + $res = self::get($c); + $numericGroups[] = $res['numeric']; + $constraintBranches[] = $res['branches']; + } + + if ($constraint->isDisjunctive()) { + $branches = Interval::noDev(); + foreach ($constraintBranches as $b) { + if ($b['exclude']) { + if ($branches['exclude']) { + // disjunctive constraint, so only exclude what's excluded in all constraints + // !=a,!=b || !=b,!=c => !=b + $branches['names'] = array_intersect($branches['names'], $b['names']); + } else { + // disjunctive constraint so exclude all names which are not explicitly included in the alternative + // (==b || ==c) || !=a,!=b => !=a + $branches['exclude'] = true; + $branches['names'] = array_diff($b['names'], $branches['names']); + } + } else { + if ($branches['exclude']) { + // disjunctive constraint so exclude all names which are not explicitly included in the alternative + // !=a,!=b || (==b || ==c) => !=a + $branches['names'] = array_diff($branches['names'], $b['names']); + } else { + // disjunctive constraint, so just add all the other branches + // (==a || ==b) || ==c => ==a || ==b || ==c + $branches['names'] = array_merge($branches['names'], $b['names']); + } + } + } + } else { + $branches = Interval::anyDev(); + foreach ($constraintBranches as $b) { + if ($b['exclude']) { + if ($branches['exclude']) { + // conjunctive, so just add all branch names to be excluded + // !=a && !=b => !=a,!=b + $branches['names'] = array_merge($branches['names'], $b['names']); + } else { + // conjunctive, so only keep included names which are not excluded + // (==a||==c) && !=a,!=b => ==c + $branches['names'] = array_diff($branches['names'], $b['names']); + } + } else { + if ($branches['exclude']) { + // conjunctive, so only keep included names which are not excluded + // !=a,!=b && (==a||==c) => ==c + $branches['names'] = array_diff($b['names'], $branches['names']); + $branches['exclude'] = false; + } else { + // conjunctive, so only keep names that are included in both + // (==a||==b) && (==a||==c) => ==a + $branches['names'] = array_intersect($branches['names'], $b['names']); + } + } + } + } + + $branches['names'] = array_unique($branches['names']); + + if (\count($numericGroups) === 1) { + return array('numeric' => $numericGroups[0], 'branches' => $branches); + } + + $borders = array(); + foreach ($numericGroups as $group) { + foreach ($group as $interval) { + $borders[] = array('version' => $interval->getStart()->getVersion(), 'operator' => $interval->getStart()->getOperator(), 'side' => 'start'); + $borders[] = array('version' => $interval->getEnd()->getVersion(), 'operator' => $interval->getEnd()->getOperator(), 'side' => 'end'); + } + } + + $opSortOrder = self::$opSortOrder; + usort($borders, function ($a, $b) use ($opSortOrder) { + $order = version_compare($a['version'], $b['version']); + if ($order === 0) { + return $opSortOrder[$a['operator']] - $opSortOrder[$b['operator']]; + } + + return $order; + }); + + $activeIntervals = 0; + $intervals = array(); + $index = 0; + $activationThreshold = $constraint->isConjunctive() ? \count($numericGroups) : 1; + $start = null; + foreach ($borders as $border) { + if ($border['side'] === 'start') { + $activeIntervals++; + } else { + $activeIntervals--; + } + if (!$start && $activeIntervals >= $activationThreshold) { + $start = new Constraint($border['operator'], $border['version']); + } elseif ($start && $activeIntervals < $activationThreshold) { + // filter out invalid intervals like > x - <= x, or >= x - < x + if ( + version_compare($start->getVersion(), $border['version'], '=') + && ( + ($start->getOperator() === '>' && $border['operator'] === '<=') + || ($start->getOperator() === '>=' && $border['operator'] === '<') + ) + ) { + unset($intervals[$index]); + } else { + $intervals[$index] = new Interval($start, new Constraint($border['operator'], $border['version'])); + $index++; + + if ($stopOnFirstValidInterval) { + break; + } + } + + $start = null; + } + } + + return array('numeric' => $intervals, 'branches' => $branches); + } + + /** + * @phpstan-return array{'numeric': Interval[], 'branches': array{'names': string[], 'exclude': bool}} + */ + private static function generateSingleConstraintIntervals(Constraint $constraint) + { + $op = $constraint->getOperator(); + + // handle branch constraints first + if (strpos($constraint->getVersion(), 'dev-') === 0) { + $intervals = array(); + $branches = array('names' => array(), 'exclude' => false); + + // != dev-foo means any numeric version may match, we treat >/< like != they are not really defined for branches + if ($op === '!=') { + $intervals[] = new Interval(Interval::fromZero(), Interval::untilPositiveInfinity()); + $branches = array('names' => array($constraint->getVersion()), 'exclude' => true); + } elseif ($op === '==') { + $branches['names'][] = $constraint->getVersion(); + } + + return array( + 'numeric' => $intervals, + 'branches' => $branches, + ); + } + + if ($op[0] === '>') { // > & >= + return array('numeric' => array(new Interval($constraint, Interval::untilPositiveInfinity())), 'branches' => Interval::noDev()); + } + if ($op[0] === '<') { // < & <= + return array('numeric' => array(new Interval(Interval::fromZero(), $constraint)), 'branches' => Interval::noDev()); + } + if ($op === '!=') { + // convert !=x to intervals of 0 - x - +inf + dev* + return array('numeric' => array( + new Interval(Interval::fromZero(), new Constraint('<', $constraint->getVersion())), + new Interval(new Constraint('>', $constraint->getVersion()), Interval::untilPositiveInfinity()), + ), 'branches' => Interval::anyDev()); + } + + // convert ==x to an interval of >=x - <=x + return array('numeric' => array( + new Interval(new Constraint('>=', $constraint->getVersion()), new Constraint('<=', $constraint->getVersion())), + ), 'branches' => Interval::noDev()); + } +} diff --git a/vendor/composer/semver/src/Semver.php b/vendor/composer/semver/src/Semver.php new file mode 100644 index 00000000..4d6de3c2 --- /dev/null +++ b/vendor/composer/semver/src/Semver.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Semver; + +use Composer\Semver\Constraint\Constraint; + +class Semver +{ + const SORT_ASC = 1; + const SORT_DESC = -1; + + /** @var VersionParser */ + private static $versionParser; + + /** + * Determine if given version satisfies given constraints. + * + * @param string $version + * @param string $constraints + * + * @return bool + */ + public static function satisfies($version, $constraints) + { + if (null === self::$versionParser) { + self::$versionParser = new VersionParser(); + } + + $versionParser = self::$versionParser; + $provider = new Constraint('==', $versionParser->normalize($version)); + $parsedConstraints = $versionParser->parseConstraints($constraints); + + return $parsedConstraints->matches($provider); + } + + /** + * Return all versions that satisfy given constraints. + * + * @param string[] $versions + * @param string $constraints + * + * @return string[] + */ + public static function satisfiedBy(array $versions, $constraints) + { + $versions = array_filter($versions, function ($version) use ($constraints) { + return Semver::satisfies($version, $constraints); + }); + + return array_values($versions); + } + + /** + * Sort given array of versions. + * + * @param string[] $versions + * + * @return string[] + */ + public static function sort(array $versions) + { + return self::usort($versions, self::SORT_ASC); + } + + /** + * Sort given array of versions in reverse. + * + * @param string[] $versions + * + * @return string[] + */ + public static function rsort(array $versions) + { + return self::usort($versions, self::SORT_DESC); + } + + /** + * @param string[] $versions + * @param int $direction + * + * @return string[] + */ + private static function usort(array $versions, $direction) + { + if (null === self::$versionParser) { + self::$versionParser = new VersionParser(); + } + + $versionParser = self::$versionParser; + $normalized = array(); + + // Normalize outside of usort() scope for minor performance increase. + // Creates an array of arrays: [[normalized, key], ...] + foreach ($versions as $key => $version) { + $normalizedVersion = $versionParser->normalize($version); + $normalizedVersion = $versionParser->normalizeDefaultBranch($normalizedVersion); + $normalized[] = array($normalizedVersion, $key); + } + + usort($normalized, function (array $left, array $right) use ($direction) { + if ($left[0] === $right[0]) { + return 0; + } + + if (Comparator::lessThan($left[0], $right[0])) { + return -$direction; + } + + return $direction; + }); + + // Recreate input array, using the original indexes which are now in sorted order. + $sorted = array(); + foreach ($normalized as $item) { + $sorted[] = $versions[$item[1]]; + } + + return $sorted; + } +} diff --git a/vendor/composer/semver/src/VersionParser.php b/vendor/composer/semver/src/VersionParser.php new file mode 100644 index 00000000..9318629a --- /dev/null +++ b/vendor/composer/semver/src/VersionParser.php @@ -0,0 +1,586 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Semver; + +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Semver\Constraint\MatchAllConstraint; +use Composer\Semver\Constraint\MultiConstraint; +use Composer\Semver\Constraint\Constraint; + +/** + * Version parser. + * + * @author Jordi Boggiano + */ +class VersionParser +{ + /** + * Regex to match pre-release data (sort of). + * + * Due to backwards compatibility: + * - Instead of enforcing hyphen, an underscore, dot or nothing at all are also accepted. + * - Only stabilities as recognized by Composer are allowed to precede a numerical identifier. + * - Numerical-only pre-release identifiers are not supported, see tests. + * + * |--------------| + * [major].[minor].[patch] -[pre-release] +[build-metadata] + * + * @var string + */ + private static $modifierRegex = '[._-]?(?:(stable|beta|b|RC|alpha|a|patch|pl|p)((?:[.-]?\d+)*+)?)?([.-]?dev)?'; + + /** @var string */ + private static $stabilitiesRegex = 'stable|RC|beta|alpha|dev'; + + /** + * Returns the stability of a version. + * + * @param string $version + * + * @return string + * @phpstan-return 'stable'|'RC'|'beta'|'alpha'|'dev' + */ + public static function parseStability($version) + { + $version = (string) preg_replace('{#.+$}', '', (string) $version); + + if (strpos($version, 'dev-') === 0 || '-dev' === substr($version, -4)) { + return 'dev'; + } + + preg_match('{' . self::$modifierRegex . '(?:\+.*)?$}i', strtolower($version), $match); + + if (!empty($match[3])) { + return 'dev'; + } + + if (!empty($match[1])) { + if ('beta' === $match[1] || 'b' === $match[1]) { + return 'beta'; + } + if ('alpha' === $match[1] || 'a' === $match[1]) { + return 'alpha'; + } + if ('rc' === $match[1]) { + return 'RC'; + } + } + + return 'stable'; + } + + /** + * @param string $stability + * + * @return string + */ + public static function normalizeStability($stability) + { + $stability = strtolower((string) $stability); + + return $stability === 'rc' ? 'RC' : $stability; + } + + /** + * Normalizes a version string to be able to perform comparisons on it. + * + * @param string $version + * @param ?string $fullVersion optional complete version string to give more context + * + * @throws \UnexpectedValueException + * + * @return string + */ + public function normalize($version, $fullVersion = null) + { + $version = trim((string) $version); + $origVersion = $version; + if (null === $fullVersion) { + $fullVersion = $version; + } + + // strip off aliasing + if (preg_match('{^([^,\s]++) ++as ++([^,\s]++)$}', $version, $match)) { + $version = $match[1]; + } + + // strip off stability flag + if (preg_match('{@(?:' . self::$stabilitiesRegex . ')$}i', $version, $match)) { + $version = substr($version, 0, strlen($version) - strlen($match[0])); + } + + // normalize master/trunk/default branches to dev-name for BC with 1.x as these used to be valid constraints + if (\in_array($version, array('master', 'trunk', 'default'), true)) { + $version = 'dev-' . $version; + } + + // if requirement is branch-like, use full name + if (stripos($version, 'dev-') === 0) { + return 'dev-' . substr($version, 4); + } + + // strip off build metadata + if (preg_match('{^([^,\s+]++)\+[^\s]++$}', $version, $match)) { + $version = $match[1]; + } + + // match classical versioning + if (preg_match('{^v?(\d{1,5}+)(\.\d++)?(\.\d++)?(\.\d++)?' . self::$modifierRegex . '$}i', $version, $matches)) { + $version = $matches[1] + . (!empty($matches[2]) ? $matches[2] : '.0') + . (!empty($matches[3]) ? $matches[3] : '.0') + . (!empty($matches[4]) ? $matches[4] : '.0'); + $index = 5; + // match date(time) based versioning + } elseif (preg_match('{^v?(\d{4}(?:[.:-]?\d{2}){1,6}(?:[.:-]?\d{1,3}){0,2})' . self::$modifierRegex . '$}i', $version, $matches)) { + $version = (string) preg_replace('{\D}', '.', $matches[1]); + $index = 2; + } + + // add version modifiers if a version was matched + if (isset($index)) { + if (!empty($matches[$index])) { + if ('stable' === $matches[$index]) { + return $version; + } + $version .= '-' . $this->expandStability($matches[$index]) . (isset($matches[$index + 1]) && '' !== $matches[$index + 1] ? ltrim($matches[$index + 1], '.-') : ''); + } + + if (!empty($matches[$index + 2])) { + $version .= '-dev'; + } + + return $version; + } + + // match dev branches + if (preg_match('{(.*?)[.-]?dev$}i', $version, $match)) { + try { + $normalized = $this->normalizeBranch($match[1]); + // a branch ending with -dev is only valid if it is numeric + // if it gets prefixed with dev- it means the branch name should + // have had a dev- prefix already when passed to normalize + if (strpos($normalized, 'dev-') === false) { + return $normalized; + } + } catch (\Exception $e) { + } + } + + $extraMessage = ''; + if (preg_match('{ +as +' . preg_quote($version) . '(?:@(?:'.self::$stabilitiesRegex.'))?$}', $fullVersion)) { + $extraMessage = ' in "' . $fullVersion . '", the alias must be an exact version'; + } elseif (preg_match('{^' . preg_quote($version) . '(?:@(?:'.self::$stabilitiesRegex.'))? +as +}', $fullVersion)) { + $extraMessage = ' in "' . $fullVersion . '", the alias source must be an exact version, if it is a branch name you should prefix it with dev-'; + } + + throw new \UnexpectedValueException('Invalid version string "' . $origVersion . '"' . $extraMessage); + } + + /** + * Extract numeric prefix from alias, if it is in numeric format, suitable for version comparison. + * + * @param string $branch Branch name (e.g. 2.1.x-dev) + * + * @return string|false Numeric prefix if present (e.g. 2.1.) or false + */ + public function parseNumericAliasPrefix($branch) + { + if (preg_match('{^(?P(\d++\\.)*\d++)(?:\.x)?-dev$}i', (string) $branch, $matches)) { + return $matches['version'] . '.'; + } + + return false; + } + + /** + * Normalizes a branch name to be able to perform comparisons on it. + * + * @param string $name + * + * @return string + */ + public function normalizeBranch($name) + { + $name = trim((string) $name); + + if (preg_match('{^v?(\d++)(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?$}i', $name, $matches)) { + $version = ''; + for ($i = 1; $i < 5; ++$i) { + $version .= isset($matches[$i]) ? str_replace(array('*', 'X'), 'x', $matches[$i]) : '.x'; + } + + return str_replace('x', '9999999', $version) . '-dev'; + } + + return 'dev-' . $name; + } + + /** + * Normalizes a default branch name (i.e. master on git) to 9999999-dev. + * + * @param string $name + * + * @return string + * + * @deprecated No need to use this anymore in theory, Composer 2 does not normalize any branch names to 9999999-dev anymore + */ + public function normalizeDefaultBranch($name) + { + if ($name === 'dev-master' || $name === 'dev-default' || $name === 'dev-trunk') { + return '9999999-dev'; + } + + return (string) $name; + } + + /** + * Parses a constraint string into MultiConstraint and/or Constraint objects. + * + * @param string $constraints + * + * @return ConstraintInterface + */ + public function parseConstraints($constraints) + { + $prettyConstraint = (string) $constraints; + + $orConstraints = preg_split('{\s*\|\|?\s*}', trim((string) $constraints)); + if (false === $orConstraints) { + throw new \RuntimeException('Failed to preg_split string: '.$constraints); + } + $orGroups = array(); + + foreach ($orConstraints as $orConstraint) { + $andConstraints = preg_split('{(?< ,]) *(? 1) { + $constraintObjects = array(); + foreach ($andConstraints as $andConstraint) { + foreach ($this->parseConstraint($andConstraint) as $parsedAndConstraint) { + $constraintObjects[] = $parsedAndConstraint; + } + } + } else { + $constraintObjects = $this->parseConstraint($andConstraints[0]); + } + + if (1 === \count($constraintObjects)) { + $constraint = $constraintObjects[0]; + } else { + $constraint = new MultiConstraint($constraintObjects); + } + + $orGroups[] = $constraint; + } + + $parsedConstraint = MultiConstraint::create($orGroups, false); + + $parsedConstraint->setPrettyString($prettyConstraint); + + return $parsedConstraint; + } + + /** + * @param string $constraint + * + * @throws \UnexpectedValueException + * + * @return array + * + * @phpstan-return non-empty-array + */ + private function parseConstraint($constraint) + { + // strip off aliasing + if (preg_match('{^([^,\s]++) ++as ++([^,\s]++)$}', $constraint, $match)) { + $constraint = $match[1]; + } + + // strip @stability flags, and keep it for later use + if (preg_match('{^([^,\s]*?)@(' . self::$stabilitiesRegex . ')$}i', $constraint, $match)) { + $constraint = '' !== $match[1] ? $match[1] : '*'; + if ($match[2] !== 'stable') { + $stabilityModifier = $match[2]; + } + } + + // get rid of #refs as those are used by composer only + if (preg_match('{^(dev-[^,\s@]+?|[^,\s@]+?\.x-dev)#.+$}i', $constraint, $match)) { + $constraint = $match[1]; + } + + if (preg_match('{^(v)?[xX*](\.[xX*])*$}i', $constraint, $match)) { + if (!empty($match[1]) || !empty($match[2])) { + return array(new Constraint('>=', '0.0.0.0-dev')); + } + + return array(new MatchAllConstraint()); + } + + $versionRegex = 'v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.(\d++))?(?:' . self::$modifierRegex . '|\.([xX*][.-]?dev))(?:\+[^\s]+)?'; + + // Tilde Range + // + // Like wildcard constraints, unsuffixed tilde constraints say that they must be greater than the previous + // version, to ensure that unstable instances of the current version are allowed. However, if a stability + // suffix is added to the constraint, then a >= match on the current version is used instead. + if (preg_match('{^~>?' . $versionRegex . '$}i', $constraint, $matches)) { + if (strpos($constraint, '~>') === 0) { + throw new \UnexpectedValueException( + 'Could not parse version constraint ' . $constraint . ': ' . + 'Invalid operator "~>", you probably meant to use the "~" operator' + ); + } + + // Work out which position in the version we are operating at + if (isset($matches[4]) && '' !== $matches[4] && null !== $matches[4]) { + $position = 4; + } elseif (isset($matches[3]) && '' !== $matches[3] && null !== $matches[3]) { + $position = 3; + } elseif (isset($matches[2]) && '' !== $matches[2] && null !== $matches[2]) { + $position = 2; + } else { + $position = 1; + } + + // when matching 2.x-dev or 3.0.x-dev we have to shift the second or third number, despite no second/third number matching above + if (!empty($matches[8])) { + $position++; + } + + // Calculate the stability suffix + $stabilitySuffix = ''; + if (empty($matches[5]) && empty($matches[7]) && empty($matches[8])) { + $stabilitySuffix .= '-dev'; + } + + $lowVersion = $this->normalize(substr($constraint . $stabilitySuffix, 1)); + $lowerBound = new Constraint('>=', $lowVersion); + + // For upper bound, we increment the position of one more significance, + // but highPosition = 0 would be illegal + $highPosition = max(1, $position - 1); + $highVersion = $this->manipulateVersionString($matches, $highPosition, 1) . '-dev'; + $upperBound = new Constraint('<', $highVersion); + + return array( + $lowerBound, + $upperBound, + ); + } + + // Caret Range + // + // Allows changes that do not modify the left-most non-zero digit in the [major, minor, patch] tuple. + // In other words, this allows patch and minor updates for versions 1.0.0 and above, patch updates for + // versions 0.X >=0.1.0, and no updates for versions 0.0.X + if (preg_match('{^\^' . $versionRegex . '($)}i', $constraint, $matches)) { + // Work out which position in the version we are operating at + if ('0' !== $matches[1] || '' === $matches[2] || null === $matches[2]) { + $position = 1; + } elseif ('0' !== $matches[2] || '' === $matches[3] || null === $matches[3]) { + $position = 2; + } else { + $position = 3; + } + + // Calculate the stability suffix + $stabilitySuffix = ''; + if (empty($matches[5]) && empty($matches[7]) && empty($matches[8])) { + $stabilitySuffix .= '-dev'; + } + + $lowVersion = $this->normalize(substr($constraint . $stabilitySuffix, 1)); + $lowerBound = new Constraint('>=', $lowVersion); + + // For upper bound, we increment the position of one more significance, + // but highPosition = 0 would be illegal + $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev'; + $upperBound = new Constraint('<', $highVersion); + + return array( + $lowerBound, + $upperBound, + ); + } + + // X Range + // + // Any of X, x, or * may be used to "stand in" for one of the numeric values in the [major, minor, patch] tuple. + // A partial version range is treated as an X-Range, so the special character is in fact optional. + if (preg_match('{^v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.[xX*])++$}', $constraint, $matches)) { + if (isset($matches[3]) && '' !== $matches[3] && null !== $matches[3]) { + $position = 3; + } elseif (isset($matches[2]) && '' !== $matches[2] && null !== $matches[2]) { + $position = 2; + } else { + $position = 1; + } + + $lowVersion = $this->manipulateVersionString($matches, $position) . '-dev'; + $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev'; + + if ($lowVersion === '0.0.0.0-dev') { + return array(new Constraint('<', $highVersion)); + } + + return array( + new Constraint('>=', $lowVersion), + new Constraint('<', $highVersion), + ); + } + + // Hyphen Range + // + // Specifies an inclusive set. If a partial version is provided as the first version in the inclusive range, + // then the missing pieces are replaced with zeroes. If a partial version is provided as the second version in + // the inclusive range, then all versions that start with the supplied parts of the tuple are accepted, but + // nothing that would be greater than the provided tuple parts. + if (preg_match('{^(?P' . $versionRegex . ') +- +(?P' . $versionRegex . ')($)}i', $constraint, $matches)) { + // Calculate the stability suffix + $lowStabilitySuffix = ''; + if (empty($matches[6]) && empty($matches[8]) && empty($matches[9])) { + $lowStabilitySuffix = '-dev'; + } + + $lowVersion = $this->normalize($matches['from']); + $lowerBound = new Constraint('>=', $lowVersion . $lowStabilitySuffix); + + $empty = function ($x) { + return ($x === 0 || $x === '0') ? false : empty($x); + }; + + if ((!$empty($matches[12]) && !$empty($matches[13])) || !empty($matches[15]) || !empty($matches[17]) || !empty($matches[18])) { + $highVersion = $this->normalize($matches['to']); + $upperBound = new Constraint('<=', $highVersion); + } else { + $highMatch = array('', $matches[11], $matches[12], $matches[13], $matches[14]); + + // validate to version + $this->normalize($matches['to']); + + $highVersion = $this->manipulateVersionString($highMatch, $empty($matches[12]) ? 1 : 2, 1) . '-dev'; + $upperBound = new Constraint('<', $highVersion); + } + + return array( + $lowerBound, + $upperBound, + ); + } + + // Basic Comparators + if (preg_match('{^(<>|!=|>=?|<=?|==?)?\s*(.*)}', $constraint, $matches)) { + try { + try { + $version = $this->normalize($matches[2]); + } catch (\UnexpectedValueException $e) { + // recover from an invalid constraint like foobar-dev which should be dev-foobar + // except if the constraint uses a known operator, in which case it must be a parse error + if (substr($matches[2], -4) === '-dev' && preg_match('{^[0-9a-zA-Z-./]+$}', $matches[2])) { + $version = $this->normalize('dev-'.substr($matches[2], 0, -4)); + } else { + throw $e; + } + } + + $op = $matches[1] ?: '='; + + if ($op !== '==' && $op !== '=' && !empty($stabilityModifier) && self::parseStability($version) === 'stable') { + $version .= '-' . $stabilityModifier; + } elseif ('<' === $op || '>=' === $op) { + if (!preg_match('/-' . self::$modifierRegex . '$/', strtolower($matches[2]))) { + if (strpos($matches[2], 'dev-') !== 0) { + $version .= '-dev'; + } + } + } + + return array(new Constraint($matches[1] ?: '=', $version)); + } catch (\Exception $e) { + } + } + + $message = 'Could not parse version constraint ' . $constraint; + if (isset($e)) { + $message .= ': ' . $e->getMessage(); + } + + throw new \UnexpectedValueException($message); + } + + /** + * Increment, decrement, or simply pad a version number. + * + * Support function for {@link parseConstraint()} + * + * @param array $matches Array with version parts in array indexes 1,2,3,4 + * @param int $position 1,2,3,4 - which segment of the version to increment/decrement + * @param int $increment + * @param string $pad The string to pad version parts after $position + * + * @return string|null The new version + * + * @phpstan-param string[] $matches + */ + private function manipulateVersionString(array $matches, $position, $increment = 0, $pad = '0') + { + for ($i = 4; $i > 0; --$i) { + if ($i > $position) { + $matches[$i] = $pad; + } elseif ($i === $position && $increment) { + $matches[$i] += $increment; + // If $matches[$i] was 0, carry the decrement + if ($matches[$i] < 0) { + $matches[$i] = $pad; + --$position; + + // Return null on a carry overflow + if ($i === 1) { + return null; + } + } + } + } + + return $matches[1] . '.' . $matches[2] . '.' . $matches[3] . '.' . $matches[4]; + } + + /** + * Expand shorthand stability string to long version. + * + * @param string $stability + * + * @return string + */ + private function expandStability($stability) + { + $stability = strtolower($stability); + + switch ($stability) { + case 'a': + return 'alpha'; + case 'b': + return 'beta'; + case 'p': + case 'pl': + return 'patch'; + case 'rc': + return 'RC'; + default: + return $stability; + } + } +} diff --git a/vendor/composer/xdebug-handler/CHANGELOG.md b/vendor/composer/xdebug-handler/CHANGELOG.md new file mode 100644 index 00000000..c5b5bcf4 --- /dev/null +++ b/vendor/composer/xdebug-handler/CHANGELOG.md @@ -0,0 +1,134 @@ +## [Unreleased] + +## [3.0.3] - 2022-02-25 + * Added: support for composer/pcre versions 2 and 3. + +## [3.0.2] - 2022-02-24 + * Fixed: regression in 3.0.1 affecting Xdebug 2 + +## [3.0.1] - 2022-01-04 + * Fixed: error when calling `isXdebugActive` before class instantiation. + +## [3.0.0] - 2021-12-23 + * Removed: support for legacy PHP versions (< PHP 7.2.5). + * Added: type declarations to arguments and return values. + * Added: strict typing to all classes. + +## [2.0.3] - 2021-12-08 + * Added: support, type annotations and refactoring for stricter PHPStan analysis. + +## [2.0.2] - 2021-07-31 + * Added: support for `xdebug_info('mode')` in Xdebug 3.1. + * Added: support for Psr\Log versions 2 and 3. + * Fixed: remove ini directives from non-cli HOST/PATH sections. + +## [2.0.1] - 2021-05-05 + * Fixed: don't restart if the cwd is a UNC path and cmd.exe will be invoked. + +## [2.0.0] - 2021-04-09 + * Break: this is a major release, see [UPGRADE.md](UPGRADE.md) for more information. + * Break: removed optional `$colorOption` constructor param and passthru fallback. + * Break: renamed `requiresRestart` param from `$isLoaded` to `$default`. + * Break: changed `restart` param `$command` from a string to an array. + * Added: support for Xdebug3 to only restart if Xdebug is not running with `xdebug.mode=off`. + * Added: `isXdebugActive()` method to determine if Xdebug is still running in the restart. + * Added: feature to bypass the shell in PHP-7.4+ by giving `proc_open` an array of arguments. + * Added: Process utility class to the API. + +## [1.4.6] - 2021-03-25 + * Fixed: fail restart if `proc_open` has been disabled in `disable_functions`. + * Fixed: enable Windows CTRL event handling in the restarted process. + +## [1.4.5] - 2020-11-13 + * Fixed: use `proc_open` when available for correct FD forwarding to the restarted process. + +## [1.4.4] - 2020-10-24 + * Fixed: exception if 'pcntl_signal' is disabled. + +## [1.4.3] - 2020-08-19 + * Fixed: restore SIGINT to default handler in restarted process if no other handler exists. + +## [1.4.2] - 2020-06-04 + * Fixed: ignore SIGINTs to let the restarted process handle them. + +## [1.4.1] - 2020-03-01 + * Fixed: restart fails if an ini file is empty. + +## [1.4.0] - 2019-11-06 + * Added: support for `NO_COLOR` environment variable: https://no-color.org + * Added: color support for Hyper terminal: https://github.com/zeit/hyper + * Fixed: correct capitalization of Xdebug (apparently). + * Fixed: improved handling for uopz extension. + +## [1.3.3] - 2019-05-27 + * Fixed: add environment changes to `$_ENV` if it is being used. + +## [1.3.2] - 2019-01-28 + * Fixed: exit call being blocked by uopz extension, resulting in application code running twice. + +## [1.3.1] - 2018-11-29 + * Fixed: fail restart if `passthru` has been disabled in `disable_functions`. + * Fixed: fail restart if an ini file cannot be opened, otherwise settings will be missing. + +## [1.3.0] - 2018-08-31 + * Added: `setPersistent` method to use environment variables for the restart. + * Fixed: improved debugging by writing output to stderr. + * Fixed: no restart when `php_ini_scanned_files` is not functional and is needed. + +## [1.2.1] - 2018-08-23 + * Fixed: fatal error with apc, when using `apc.mmap_file_mask`. + +## [1.2.0] - 2018-08-16 + * Added: debug information using `XDEBUG_HANDLER_DEBUG`. + * Added: fluent interface for setters. + * Added: `PhpConfig` helper class for calling PHP sub-processes. + * Added: `PHPRC` original value to restart stettings, for use in a restarted process. + * Changed: internal procedure to disable ini-scanning, using `-n` command-line option. + * Fixed: replaced `escapeshellarg` usage to avoid locale problems. + * Fixed: improved color-option handling to respect double-dash delimiter. + * Fixed: color-option handling regression from main script changes. + * Fixed: improved handling when checking main script. + * Fixed: handling for standard input, that never actually did anything. + * Fixed: fatal error when ctype extension is not available. + +## [1.1.0] - 2018-04-11 + * Added: `getRestartSettings` method for calling PHP processes in a restarted process. + * Added: API definition and @internal class annotations. + * Added: protected `requiresRestart` method for extending classes. + * Added: `setMainScript` method for applications that change the working directory. + * Changed: private `tmpIni` variable to protected for extending classes. + * Fixed: environment variables not available in $_SERVER when restored in the restart. + * Fixed: relative path problems caused by Phar::interceptFileFuncs. + * Fixed: incorrect handling when script file cannot be found. + +## [1.0.0] - 2018-03-08 + * Added: PSR3 logging for optional status output. + * Added: existing ini settings are merged to catch command-line overrides. + * Added: code, tests and other artefacts to decouple from Composer. + * Break: the following class was renamed: + - `Composer\XdebugHandler` -> `Composer\XdebugHandler\XdebugHandler` + +[Unreleased]: https://github.com/composer/xdebug-handler/compare/3.0.3...HEAD +[3.0.2]: https://github.com/composer/xdebug-handler/compare/3.0.2...3.0.3 +[3.0.2]: https://github.com/composer/xdebug-handler/compare/3.0.1...3.0.2 +[3.0.1]: https://github.com/composer/xdebug-handler/compare/3.0.0...3.0.1 +[3.0.0]: https://github.com/composer/xdebug-handler/compare/2.0.3...3.0.0 +[2.0.3]: https://github.com/composer/xdebug-handler/compare/2.0.2...2.0.3 +[2.0.2]: https://github.com/composer/xdebug-handler/compare/2.0.1...2.0.2 +[2.0.1]: https://github.com/composer/xdebug-handler/compare/2.0.0...2.0.1 +[2.0.0]: https://github.com/composer/xdebug-handler/compare/1.4.6...2.0.0 +[1.4.6]: https://github.com/composer/xdebug-handler/compare/1.4.5...1.4.6 +[1.4.5]: https://github.com/composer/xdebug-handler/compare/1.4.4...1.4.5 +[1.4.4]: https://github.com/composer/xdebug-handler/compare/1.4.3...1.4.4 +[1.4.3]: https://github.com/composer/xdebug-handler/compare/1.4.2...1.4.3 +[1.4.2]: https://github.com/composer/xdebug-handler/compare/1.4.1...1.4.2 +[1.4.1]: https://github.com/composer/xdebug-handler/compare/1.4.0...1.4.1 +[1.4.0]: https://github.com/composer/xdebug-handler/compare/1.3.3...1.4.0 +[1.3.3]: https://github.com/composer/xdebug-handler/compare/1.3.2...1.3.3 +[1.3.2]: https://github.com/composer/xdebug-handler/compare/1.3.1...1.3.2 +[1.3.1]: https://github.com/composer/xdebug-handler/compare/1.3.0...1.3.1 +[1.3.0]: https://github.com/composer/xdebug-handler/compare/1.2.1...1.3.0 +[1.2.1]: https://github.com/composer/xdebug-handler/compare/1.2.0...1.2.1 +[1.2.0]: https://github.com/composer/xdebug-handler/compare/1.1.0...1.2.0 +[1.1.0]: https://github.com/composer/xdebug-handler/compare/1.0.0...1.1.0 +[1.0.0]: https://github.com/composer/xdebug-handler/compare/d66f0d15cb57...1.0.0 diff --git a/vendor/composer/xdebug-handler/LICENSE b/vendor/composer/xdebug-handler/LICENSE new file mode 100644 index 00000000..963618a1 --- /dev/null +++ b/vendor/composer/xdebug-handler/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Composer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/composer/xdebug-handler/README.md b/vendor/composer/xdebug-handler/README.md new file mode 100644 index 00000000..56618fc1 --- /dev/null +++ b/vendor/composer/xdebug-handler/README.md @@ -0,0 +1,298 @@ +# composer/xdebug-handler + +[![packagist](https://img.shields.io/packagist/v/composer/xdebug-handler)](https://packagist.org/packages/composer/xdebug-handler) +[![Continuous Integration](https://github.com/composer/xdebug-handler/actions/workflows/continuous-integration.yml/badge.svg?branch=main)](https://github.com/composer/xdebug-handler/actions?query=branch:main) +![license](https://img.shields.io/github/license/composer/xdebug-handler.svg) +![php](https://img.shields.io/packagist/php-v/composer/xdebug-handler?colorB=8892BF) + +Restart a CLI process without loading the Xdebug extension, unless `xdebug.mode=off`. + +Originally written as part of [composer/composer](https://github.com/composer/composer), +now extracted and made available as a stand-alone library. + +### Version 3 + +Removed support for legacy PHP versions and added type declarations. + +Long term support for version 2 (PHP 5.3.2 - 7.2.4) follows [Composer 2.2 LTS](https://blog.packagist.com/composer-2-2/) policy. + +## Installation + +Install the latest version with: + +```bash +$ composer require composer/xdebug-handler +``` + +## Requirements + +* PHP 7.2.5 minimum, although using the latest PHP version is highly recommended. + +## Basic Usage +```php +use Composer\XdebugHandler\XdebugHandler; + +$xdebug = new XdebugHandler('myapp'); +$xdebug->check(); +unset($xdebug); +``` + +The constructor takes a single parameter, `$envPrefix`, which is upper-cased and prepended to default base values to create two distinct environment variables. The above example enables the use of: + +- `MYAPP_ALLOW_XDEBUG=1` to override automatic restart and allow Xdebug +- `MYAPP_ORIGINAL_INIS` to obtain ini file locations in a restarted process + +## Advanced Usage + +* [How it works](#how-it-works) +* [Limitations](#limitations) +* [Helper methods](#helper-methods) +* [Setter methods](#setter-methods) +* [Process configuration](#process-configuration) +* [Troubleshooting](#troubleshooting) +* [Extending the library](#extending-the-library) + +### How it works + +A temporary ini file is created from the loaded (and scanned) ini files, with any references to the Xdebug extension commented out. Current ini settings are merged, so that most ini settings made on the command-line or by the application are included (see [Limitations](#limitations)) + +* `MYAPP_ALLOW_XDEBUG` is set with internal data to flag and use in the restart. +* The command-line and environment are [configured](#process-configuration) for the restart. +* The application is restarted in a new process. + * The restart settings are stored in the environment. + * `MYAPP_ALLOW_XDEBUG` is unset. + * The application runs and exits. +* The main process exits with the exit code from the restarted process. + +#### Signal handling +Asynchronous signal handling is automatically enabled if the pcntl extension is loaded. `SIGINT` is set to `SIG_IGN` in the parent +process and restored to `SIG_DFL` in the restarted process (if no other handler has been set). + +From PHP 7.4 on Windows, `CTRL+C` and `CTRL+BREAK` handling is automatically enabled in the restarted process and ignored in the parent process. + +### Limitations +There are a few things to be aware of when running inside a restarted process. + +* Extensions set on the command-line will not be loaded. +* Ini file locations will be reported as per the restart - see [getAllIniFiles()](#getallinifiles). +* Php sub-processes may be loaded with Xdebug enabled - see [Process configuration](#process-configuration). + +### Helper methods +These static methods provide information from the current process, regardless of whether it has been restarted or not. + +#### _getAllIniFiles(): array_ +Returns an array of the original ini file locations. Use this instead of calling `php_ini_loaded_file` and `php_ini_scanned_files`, which will report the wrong values in a restarted process. + +```php +use Composer\XdebugHandler\XdebugHandler; + +$files = XdebugHandler::getAllIniFiles(); + +# $files[0] always exists, it could be an empty string +$loadedIni = array_shift($files); +$scannedInis = $files; +``` + +These locations are also available in the `MYAPP_ORIGINAL_INIS` environment variable. This is a path-separated string comprising the location returned from `php_ini_loaded_file`, which could be empty, followed by locations parsed from calling `php_ini_scanned_files`. + +#### _getRestartSettings(): ?array_ +Returns an array of settings that can be used with PHP [sub-processes](#sub-processes), or null if the process was not restarted. + +```php +use Composer\XdebugHandler\XdebugHandler; + +$settings = XdebugHandler::getRestartSettings(); +/** + * $settings: array (if the current process was restarted, + * or called with the settings from a previous restart), or null + * + * 'tmpIni' => the temporary ini file used in the restart (string) + * 'scannedInis' => if there were any scanned inis (bool) + * 'scanDir' => the original PHP_INI_SCAN_DIR value (false|string) + * 'phprc' => the original PHPRC value (false|string) + * 'inis' => the original inis from getAllIniFiles (array) + * 'skipped' => the skipped version from getSkippedVersion (string) + */ +``` + +#### _getSkippedVersion(): string_ +Returns the Xdebug version string that was skipped by the restart, or an empty string if there was no restart (or Xdebug is still loaded, perhaps by an extending class restarting for a reason other than removing Xdebug). + +```php +use Composer\XdebugHandler\XdebugHandler; + +$version = XdebugHandler::getSkippedVersion(); +# $version: '3.1.1' (for example), or an empty string +``` + +#### _isXdebugActive(): bool_ +Returns true if Xdebug is loaded and is running in an active mode (if it supports modes). Returns false if Xdebug is not loaded, or it is running with `xdebug.mode=off`. + +### Setter methods +These methods implement a fluent interface and must be called before the main `check()` method. + +#### _setLogger(LoggerInterface $logger): self_ +Enables the output of status messages to an external PSR3 logger. All messages are reported with either `DEBUG` or `WARNING` log levels. For example (showing the level and message): + +``` +// No restart +DEBUG Checking MYAPP_ALLOW_XDEBUG +DEBUG The Xdebug extension is loaded (3.1.1) xdebug.mode=off +DEBUG No restart (APP_ALLOW_XDEBUG=0) Allowed by xdebug.mode + +// Restart overridden +DEBUG Checking MYAPP_ALLOW_XDEBUG +DEBUG The Xdebug extension is loaded (3.1.1) xdebug.mode=coverage,debug,develop +DEBUG No restart (MYAPP_ALLOW_XDEBUG=1) + +// Failed restart +DEBUG Checking MYAPP_ALLOW_XDEBUG +DEBUG The Xdebug extension is loaded (3.1.0) +WARNING No restart (Unable to create temp ini file at: ...) +``` + +Status messages can also be output with `XDEBUG_HANDLER_DEBUG`. See [Troubleshooting](#troubleshooting). + +#### _setMainScript(string $script): self_ +Sets the location of the main script to run in the restart. This is only needed in more esoteric use-cases, or if the `argv[0]` location is inaccessible. The script name `--` is supported for standard input. + +#### _setPersistent(): self_ +Configures the restart using [persistent settings](#persistent-settings), so that Xdebug is not loaded in any sub-process. + +Use this method if your application invokes one or more PHP sub-process and the Xdebug extension is not needed. This avoids the overhead of implementing specific [sub-process](#sub-processes) strategies. + +Alternatively, this method can be used to set up a default _Xdebug-free_ environment which can be changed if a sub-process requires Xdebug, then restored afterwards: + +```php +function SubProcessWithXdebug() +{ + $phpConfig = new Composer\XdebugHandler\PhpConfig(); + + # Set the environment to the original configuration + $phpConfig->useOriginal(); + + # run the process with Xdebug loaded + ... + + # Restore Xdebug-free environment + $phpConfig->usePersistent(); +} +``` + +### Process configuration +The library offers two strategies to invoke a new PHP process without loading Xdebug, using either _standard_ or _persistent_ settings. Note that this is only important if the application calls a PHP sub-process. + +#### Standard settings +Uses command-line options to remove Xdebug from the new process only. + +* The -n option is added to the command-line. This tells PHP not to scan for additional inis. +* The temporary ini is added to the command-line with the -c option. + +>_If the new process calls a PHP sub-process, Xdebug will be loaded in that sub-process (unless it implements xdebug-handler, in which case there will be another restart)._ + +This is the default strategy used in the restart. + +#### Persistent settings +Uses environment variables to remove Xdebug from the new process and persist these settings to any sub-process. + +* `PHP_INI_SCAN_DIR` is set to an empty string. This tells PHP not to scan for additional inis. +* `PHPRC` is set to the temporary ini. + +>_If the new process calls a PHP sub-process, Xdebug will not be loaded in that sub-process._ + +This strategy can be used in the restart by calling [setPersistent()](#setpersistent). + +#### Sub-processes +The `PhpConfig` helper class makes it easy to invoke a PHP sub-process (with or without Xdebug loaded), regardless of whether there has been a restart. + +Each of its methods returns an array of PHP options (to add to the command-line) and sets up the environment for the required strategy. The [getRestartSettings()](#getrestartsettings) method is used internally. + +* `useOriginal()` - Xdebug will be loaded in the new process. +* `useStandard()` - Xdebug will **not** be loaded in the new process - see [standard settings](#standard-settings). +* `userPersistent()` - Xdebug will **not** be loaded in the new process - see [persistent settings](#persistent-settings) + +If there was no restart, an empty options array is returned and the environment is not changed. + +```php +use Composer\XdebugHandler\PhpConfig; + +$config = new PhpConfig; + +$options = $config->useOriginal(); +# $options: empty array +# environment: PHPRC and PHP_INI_SCAN_DIR set to original values + +$options = $config->useStandard(); +# $options: [-n, -c, tmpIni] +# environment: PHPRC and PHP_INI_SCAN_DIR set to original values + +$options = $config->usePersistent(); +# $options: empty array +# environment: PHPRC=tmpIni, PHP_INI_SCAN_DIR='' +``` + +### Troubleshooting +The following environment settings can be used to troubleshoot unexpected behavior: + +* `XDEBUG_HANDLER_DEBUG=1` Outputs status messages to `STDERR`, if it is defined, irrespective of any PSR3 logger. Each message is prefixed `xdebug-handler[pid]`, where pid is the process identifier. + +* `XDEBUG_HANDLER_DEBUG=2` As above, but additionally saves the temporary ini file and reports its location in a status message. + +### Extending the library +The API is defined by classes and their accessible elements that are not annotated as @internal. The main class has two protected methods that can be overridden to provide additional functionality: + +#### _requiresRestart(bool $default): bool_ +By default the process will restart if Xdebug is loaded and not running with `xdebug.mode=off`. Extending this method allows an application to decide, by returning a boolean (or equivalent) value. +It is only called if `MYAPP_ALLOW_XDEBUG` is empty, so it will not be called in the restarted process (where this variable contains internal data), or if the restart has been overridden. + +Note that the [setMainScript()](#setmainscriptscript) and [setPersistent()](#setpersistent) setters can be used here, if required. + +#### _restart(array $command): void_ +An application can extend this to modify the temporary ini file, its location given in the `tmpIni` property. New settings can be safely appended to the end of the data, which is `PHP_EOL` terminated. + +The `$command` parameter is an array of unescaped command-line arguments that will be used for the new process. + +Remember to finish with `parent::restart($command)`. + +#### Example +This example demonstrates two ways to extend basic functionality: + +* To avoid the overhead of spinning up a new process, the restart is skipped if a simple help command is requested. + +* The application needs write-access to phar files, so it will force a restart if `phar.readonly` is set (regardless of whether Xdebug is loaded) and change this value in the temporary ini file. + +```php +use Composer\XdebugHandler\XdebugHandler; +use MyApp\Command; + +class MyRestarter extends XdebugHandler +{ + private $required; + + protected function requiresRestart(bool $default): bool + { + if (Command::isHelp()) { + # No need to disable Xdebug for this + return false; + } + + $this->required = (bool) ini_get('phar.readonly'); + return $this->required || $default; + } + + protected function restart(array $command): void + { + if ($this->required) { + # Add required ini setting to tmpIni + $content = file_get_contents($this->tmpIni); + $content .= 'phar.readonly=0'.PHP_EOL; + file_put_contents($this->tmpIni, $content); + } + + parent::restart($command); + } +} +``` + +## License +composer/xdebug-handler is licensed under the MIT License, see the LICENSE file for details. diff --git a/vendor/composer/xdebug-handler/composer.json b/vendor/composer/xdebug-handler/composer.json new file mode 100644 index 00000000..6b649dab --- /dev/null +++ b/vendor/composer/xdebug-handler/composer.json @@ -0,0 +1,44 @@ +{ + "name": "composer/xdebug-handler", + "description": "Restarts a process without Xdebug.", + "type": "library", + "license": "MIT", + "keywords": [ + "xdebug", + "performance" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/xdebug-handler/issues" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3", + "composer/pcre": "^1 || ^2 || ^3" + }, + "require-dev": { + "symfony/phpunit-bridge": "^6.0", + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1" + }, + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Composer\\XdebugHandler\\Tests\\": "tests" + } + }, + "scripts": { + "test": "@php vendor/bin/simple-phpunit", + "phpstan": "@php vendor/bin/phpstan analyse" + } +} diff --git a/vendor/composer/xdebug-handler/src/PhpConfig.php b/vendor/composer/xdebug-handler/src/PhpConfig.php new file mode 100644 index 00000000..7edac888 --- /dev/null +++ b/vendor/composer/xdebug-handler/src/PhpConfig.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\XdebugHandler; + +/** + * @author John Stevenson + * + * @phpstan-type restartData array{tmpIni: string, scannedInis: bool, scanDir: false|string, phprc: false|string, inis: string[], skipped: string} + */ +class PhpConfig +{ + /** + * Use the original PHP configuration + * + * @return string[] Empty array of PHP cli options + */ + public function useOriginal(): array + { + $this->getDataAndReset(); + return []; + } + + /** + * Use standard restart settings + * + * @return string[] PHP cli options + */ + public function useStandard(): array + { + $data = $this->getDataAndReset(); + if ($data !== null) { + return ['-n', '-c', $data['tmpIni']]; + } + + return []; + } + + /** + * Use environment variables to persist settings + * + * @return string[] Empty array of PHP cli options + */ + public function usePersistent(): array + { + $data = $this->getDataAndReset(); + if ($data !== null) { + $this->updateEnv('PHPRC', $data['tmpIni']); + $this->updateEnv('PHP_INI_SCAN_DIR', ''); + } + + return []; + } + + /** + * Returns restart data if available and resets the environment + * + * @phpstan-return restartData|null + */ + private function getDataAndReset(): ?array + { + $data = XdebugHandler::getRestartSettings(); + if ($data !== null) { + $this->updateEnv('PHPRC', $data['phprc']); + $this->updateEnv('PHP_INI_SCAN_DIR', $data['scanDir']); + } + + return $data; + } + + /** + * Updates a restart settings value in the environment + * + * @param string $name + * @param string|false $value + */ + private function updateEnv(string $name, $value): void + { + Process::setEnv($name, false !== $value ? $value : null); + } +} diff --git a/vendor/composer/xdebug-handler/src/Process.php b/vendor/composer/xdebug-handler/src/Process.php new file mode 100644 index 00000000..c612200b --- /dev/null +++ b/vendor/composer/xdebug-handler/src/Process.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Composer\XdebugHandler; + +use Composer\Pcre\Preg; + +/** + * Process utility functions + * + * @author John Stevenson + */ +class Process +{ + /** + * Escapes a string to be used as a shell argument. + * + * From https://github.com/johnstevenson/winbox-args + * MIT Licensed (c) John Stevenson + * + * @param string $arg The argument to be escaped + * @param bool $meta Additionally escape cmd.exe meta characters + * @param bool $module The argument is the module to invoke + */ + public static function escape(string $arg, bool $meta = true, bool $module = false): string + { + if (!defined('PHP_WINDOWS_VERSION_BUILD')) { + return "'".str_replace("'", "'\\''", $arg)."'"; + } + + $quote = strpbrk($arg, " \t") !== false || $arg === ''; + + $arg = Preg::replace('/(\\\\*)"/', '$1$1\\"', $arg, -1, $dquotes); + + if ($meta) { + $meta = $dquotes || Preg::isMatch('/%[^%]+%/', $arg); + + if (!$meta) { + $quote = $quote || strpbrk($arg, '^&|<>()') !== false; + } elseif ($module && !$dquotes && $quote) { + $meta = false; + } + } + + if ($quote) { + $arg = '"'.(Preg::replace('/(\\\\*)$/', '$1$1', $arg)).'"'; + } + + if ($meta) { + $arg = Preg::replace('/(["^&|<>()%])/', '^$1', $arg); + } + + return $arg; + } + + /** + * Escapes an array of arguments that make up a shell command + * + * @param string[] $args Argument list, with the module name first + */ + public static function escapeShellCommand(array $args): string + { + $command = ''; + $module = array_shift($args); + + if ($module !== null) { + $command = self::escape($module, true, true); + + foreach ($args as $arg) { + $command .= ' '.self::escape($arg); + } + } + + return $command; + } + + /** + * Makes putenv environment changes available in $_SERVER and $_ENV + * + * @param string $name + * @param ?string $value A null value unsets the variable + */ + public static function setEnv(string $name, ?string $value = null): bool + { + $unset = null === $value; + + if (!putenv($unset ? $name : $name.'='.$value)) { + return false; + } + + if ($unset) { + unset($_SERVER[$name]); + } else { + $_SERVER[$name] = $value; + } + + // Update $_ENV if it is being used + if (false !== stripos((string) ini_get('variables_order'), 'E')) { + if ($unset) { + unset($_ENV[$name]); + } else { + $_ENV[$name] = $value; + } + } + + return true; + } +} diff --git a/vendor/composer/xdebug-handler/src/Status.php b/vendor/composer/xdebug-handler/src/Status.php new file mode 100644 index 00000000..b434f859 --- /dev/null +++ b/vendor/composer/xdebug-handler/src/Status.php @@ -0,0 +1,203 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Composer\XdebugHandler; + +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; + +/** + * @author John Stevenson + * @internal + */ +class Status +{ + const ENV_RESTART = 'XDEBUG_HANDLER_RESTART'; + const CHECK = 'Check'; + const ERROR = 'Error'; + const INFO = 'Info'; + const NORESTART = 'NoRestart'; + const RESTART = 'Restart'; + const RESTARTING = 'Restarting'; + const RESTARTED = 'Restarted'; + + /** @var bool */ + private $debug; + + /** @var string */ + private $envAllowXdebug; + + /** @var string|null */ + private $loaded; + + /** @var LoggerInterface|null */ + private $logger; + + /** @var bool */ + private $modeOff; + + /** @var float */ + private $time; + + /** + * @param string $envAllowXdebug Prefixed _ALLOW_XDEBUG name + * @param bool $debug Whether debug output is required + */ + public function __construct(string $envAllowXdebug, bool $debug) + { + $start = getenv(self::ENV_RESTART); + Process::setEnv(self::ENV_RESTART); + $this->time = is_numeric($start) ? round((microtime(true) - $start) * 1000) : 0; + + $this->envAllowXdebug = $envAllowXdebug; + $this->debug = $debug && defined('STDERR'); + $this->modeOff = false; + } + + /** + * Activates status message output to a PSR3 logger + * + * @return void + */ + public function setLogger(LoggerInterface $logger): void + { + $this->logger = $logger; + } + + /** + * Calls a handler method to report a message + * + * @throws \InvalidArgumentException If $op is not known + */ + public function report(string $op, ?string $data): void + { + if ($this->logger !== null || $this->debug) { + $callable = [$this, 'report'.$op]; + + if (!is_callable($callable)) { + throw new \InvalidArgumentException('Unknown op handler: '.$op); + } + + $params = $data !== null ? [$data] : []; + call_user_func_array($callable, $params); + } + } + + /** + * Outputs a status message + */ + private function output(string $text, ?string $level = null): void + { + if ($this->logger !== null) { + $this->logger->log($level !== null ? $level: LogLevel::DEBUG, $text); + } + + if ($this->debug) { + fwrite(STDERR, sprintf('xdebug-handler[%d] %s', getmypid(), $text.PHP_EOL)); + } + } + + /** + * Checking status message + */ + private function reportCheck(string $loaded): void + { + list($version, $mode) = explode('|', $loaded); + + if ($version !== '') { + $this->loaded = '('.$version.')'.($mode !== '' ? ' xdebug.mode='.$mode : ''); + } + $this->modeOff = $mode === 'off'; + $this->output('Checking '.$this->envAllowXdebug); + } + + /** + * Error status message + */ + private function reportError(string $error): void + { + $this->output(sprintf('No restart (%s)', $error), LogLevel::WARNING); + } + + /** + * Info status message + */ + private function reportInfo(string $info): void + { + $this->output($info); + } + + /** + * No restart status message + */ + private function reportNoRestart(): void + { + $this->output($this->getLoadedMessage()); + + if ($this->loaded !== null) { + $text = sprintf('No restart (%s)', $this->getEnvAllow()); + if (!((bool) getenv($this->envAllowXdebug))) { + $text .= ' Allowed by '.($this->modeOff ? 'xdebug.mode' : 'application'); + } + $this->output($text); + } + } + + /** + * Restart status message + */ + private function reportRestart(): void + { + $this->output($this->getLoadedMessage()); + Process::setEnv(self::ENV_RESTART, (string) microtime(true)); + } + + /** + * Restarted status message + */ + private function reportRestarted(): void + { + $loaded = $this->getLoadedMessage(); + $text = sprintf('Restarted (%d ms). %s', $this->time, $loaded); + $level = $this->loaded !== null ? LogLevel::WARNING : null; + $this->output($text, $level); + } + + /** + * Restarting status message + */ + private function reportRestarting(string $command): void + { + $text = sprintf('Process restarting (%s)', $this->getEnvAllow()); + $this->output($text); + $text = 'Running '.$command; + $this->output($text); + } + + /** + * Returns the _ALLOW_XDEBUG environment variable as name=value + */ + private function getEnvAllow(): string + { + return $this->envAllowXdebug.'='.getenv($this->envAllowXdebug); + } + + /** + * Returns the Xdebug status and version + */ + private function getLoadedMessage(): string + { + $loaded = $this->loaded !== null ? sprintf('loaded %s', $this->loaded) : 'not loaded'; + return 'The Xdebug extension is '.$loaded; + } +} diff --git a/vendor/composer/xdebug-handler/src/XdebugHandler.php b/vendor/composer/xdebug-handler/src/XdebugHandler.php new file mode 100644 index 00000000..9052bfa4 --- /dev/null +++ b/vendor/composer/xdebug-handler/src/XdebugHandler.php @@ -0,0 +1,668 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Composer\XdebugHandler; + +use Composer\Pcre\Preg; +use Psr\Log\LoggerInterface; + +/** + * @author John Stevenson + * + * @phpstan-import-type restartData from PhpConfig + */ +class XdebugHandler +{ + const SUFFIX_ALLOW = '_ALLOW_XDEBUG'; + const SUFFIX_INIS = '_ORIGINAL_INIS'; + const RESTART_ID = 'internal'; + const RESTART_SETTINGS = 'XDEBUG_HANDLER_SETTINGS'; + const DEBUG = 'XDEBUG_HANDLER_DEBUG'; + + /** @var string|null */ + protected $tmpIni; + + /** @var bool */ + private static $inRestart; + + /** @var string */ + private static $name; + + /** @var string|null */ + private static $skipped; + + /** @var bool */ + private static $xdebugActive; + + /** @var string|null */ + private static $xdebugMode; + + /** @var string|null */ + private static $xdebugVersion; + + /** @var bool */ + private $cli; + + /** @var string|null */ + private $debug; + + /** @var string */ + private $envAllowXdebug; + + /** @var string */ + private $envOriginalInis; + + /** @var bool */ + private $persistent; + + /** @var string|null */ + private $script; + + /** @var Status */ + private $statusWriter; + + /** + * Constructor + * + * The $envPrefix is used to create distinct environment variables. It is + * uppercased and prepended to the default base values. For example 'myapp' + * would result in MYAPP_ALLOW_XDEBUG and MYAPP_ORIGINAL_INIS. + * + * @param string $envPrefix Value used in environment variables + * @throws \RuntimeException If the parameter is invalid + */ + public function __construct(string $envPrefix) + { + if ($envPrefix === '') { + throw new \RuntimeException('Invalid constructor parameter'); + } + + self::$name = strtoupper($envPrefix); + $this->envAllowXdebug = self::$name.self::SUFFIX_ALLOW; + $this->envOriginalInis = self::$name.self::SUFFIX_INIS; + + self::setXdebugDetails(); + self::$inRestart = false; + + if ($this->cli = PHP_SAPI === 'cli') { + $this->debug = (string) getenv(self::DEBUG); + } + + $this->statusWriter = new Status($this->envAllowXdebug, (bool) $this->debug); + } + + /** + * Activates status message output to a PSR3 logger + */ + public function setLogger(LoggerInterface $logger): self + { + $this->statusWriter->setLogger($logger); + return $this; + } + + /** + * Sets the main script location if it cannot be called from argv + */ + public function setMainScript(string $script): self + { + $this->script = $script; + return $this; + } + + /** + * Persist the settings to keep Xdebug out of sub-processes + */ + public function setPersistent(): self + { + $this->persistent = true; + return $this; + } + + /** + * Checks if Xdebug is loaded and the process needs to be restarted + * + * This behaviour can be disabled by setting the MYAPP_ALLOW_XDEBUG + * environment variable to 1. This variable is used internally so that + * the restarted process is created only once. + */ + public function check(): void + { + $this->notify(Status::CHECK, self::$xdebugVersion.'|'.self::$xdebugMode); + $envArgs = explode('|', (string) getenv($this->envAllowXdebug)); + + if (!((bool) $envArgs[0]) && $this->requiresRestart(self::$xdebugActive)) { + // Restart required + $this->notify(Status::RESTART); + + if ($this->prepareRestart()) { + $command = $this->getCommand(); + $this->restart($command); + } + return; + } + + if (self::RESTART_ID === $envArgs[0] && count($envArgs) === 5) { + // Restarted, so unset environment variable and use saved values + $this->notify(Status::RESTARTED); + + Process::setEnv($this->envAllowXdebug); + self::$inRestart = true; + + if (self::$xdebugVersion === null) { + // Skipped version is only set if Xdebug is not loaded + self::$skipped = $envArgs[1]; + } + + $this->tryEnableSignals(); + + // Put restart settings in the environment + $this->setEnvRestartSettings($envArgs); + return; + } + + $this->notify(Status::NORESTART); + $settings = self::getRestartSettings(); + + if ($settings !== null) { + // Called with existing settings, so sync our settings + $this->syncSettings($settings); + } + } + + /** + * Returns an array of php.ini locations with at least one entry + * + * The equivalent of calling php_ini_loaded_file then php_ini_scanned_files. + * The loaded ini location is the first entry and may be empty. + * + * @return string[] + */ + public static function getAllIniFiles(): array + { + if (self::$name !== null) { + $env = getenv(self::$name.self::SUFFIX_INIS); + + if (false !== $env) { + return explode(PATH_SEPARATOR, $env); + } + } + + $paths = [(string) php_ini_loaded_file()]; + $scanned = php_ini_scanned_files(); + + if ($scanned !== false) { + $paths = array_merge($paths, array_map('trim', explode(',', $scanned))); + } + + return $paths; + } + + /** + * Returns an array of restart settings or null + * + * Settings will be available if the current process was restarted, or + * called with the settings from an existing restart. + * + * @phpstan-return restartData|null + */ + public static function getRestartSettings(): ?array + { + $envArgs = explode('|', (string) getenv(self::RESTART_SETTINGS)); + + if (count($envArgs) !== 6 + || (!self::$inRestart && php_ini_loaded_file() !== $envArgs[0])) { + return null; + } + + return [ + 'tmpIni' => $envArgs[0], + 'scannedInis' => (bool) $envArgs[1], + 'scanDir' => '*' === $envArgs[2] ? false : $envArgs[2], + 'phprc' => '*' === $envArgs[3] ? false : $envArgs[3], + 'inis' => explode(PATH_SEPARATOR, $envArgs[4]), + 'skipped' => $envArgs[5], + ]; + } + + /** + * Returns the Xdebug version that triggered a successful restart + */ + public static function getSkippedVersion(): string + { + return (string) self::$skipped; + } + + /** + * Returns whether Xdebug is loaded and active + * + * true: if Xdebug is loaded and is running in an active mode. + * false: if Xdebug is not loaded, or it is running with xdebug.mode=off. + */ + public static function isXdebugActive(): bool + { + self::setXdebugDetails(); + return self::$xdebugActive; + } + + /** + * Allows an extending class to decide if there should be a restart + * + * The default is to restart if Xdebug is loaded and its mode is not "off". + */ + protected function requiresRestart(bool $default): bool + { + return $default; + } + + /** + * Allows an extending class to access the tmpIni + * + * @param string[] $command * + */ + protected function restart(array $command): void + { + $this->doRestart($command); + } + + /** + * Executes the restarted command then deletes the tmp ini + * + * @param string[] $command + * @phpstan-return never + */ + private function doRestart(array $command): void + { + $this->tryEnableSignals(); + $this->notify(Status::RESTARTING, implode(' ', $command)); + + if (PHP_VERSION_ID >= 70400) { + $cmd = $command; + } else { + $cmd = Process::escapeShellCommand($command); + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + // Outer quotes required on cmd string below PHP 8 + $cmd = '"'.$cmd.'"'; + } + } + + $process = proc_open($cmd, [], $pipes); + if (is_resource($process)) { + $exitCode = proc_close($process); + } + + if (!isset($exitCode)) { + // Unlikely that php or the default shell cannot be invoked + $this->notify(Status::ERROR, 'Unable to restart process'); + $exitCode = -1; + } else { + $this->notify(Status::INFO, 'Restarted process exited '.$exitCode); + } + + if ($this->debug === '2') { + $this->notify(Status::INFO, 'Temp ini saved: '.$this->tmpIni); + } else { + @unlink((string) $this->tmpIni); + } + + exit($exitCode); + } + + /** + * Returns true if everything was written for the restart + * + * If any of the following fails (however unlikely) we must return false to + * stop potential recursion: + * - tmp ini file creation + * - environment variable creation + */ + private function prepareRestart(): bool + { + $error = null; + $iniFiles = self::getAllIniFiles(); + $scannedInis = count($iniFiles) > 1; + $tmpDir = sys_get_temp_dir(); + + if (!$this->cli) { + $error = 'Unsupported SAPI: '.PHP_SAPI; + } elseif (!$this->checkConfiguration($info)) { + $error = $info; + } elseif (!$this->checkMainScript()) { + $error = 'Unable to access main script: '.$this->script; + } elseif (!$this->writeTmpIni($iniFiles, $tmpDir, $error)) { + $error = $error !== null ? $error : 'Unable to create temp ini file at: '.$tmpDir; + } elseif (!$this->setEnvironment($scannedInis, $iniFiles)) { + $error = 'Unable to set environment variables'; + } + + if ($error !== null) { + $this->notify(Status::ERROR, $error); + } + + return $error === null; + } + + /** + * Returns true if the tmp ini file was written + * + * @param string[] $iniFiles All ini files used in the current process + */ + private function writeTmpIni(array $iniFiles, string $tmpDir, ?string &$error): bool + { + if (($tmpfile = @tempnam($tmpDir, '')) === false) { + return false; + } + + $this->tmpIni = $tmpfile; + + // $iniFiles has at least one item and it may be empty + if ($iniFiles[0] === '') { + array_shift($iniFiles); + } + + $content = ''; + $sectionRegex = '/^\s*\[(?:PATH|HOST)\s*=/mi'; + $xdebugRegex = '/^\s*(zend_extension\s*=.*xdebug.*)$/mi'; + + foreach ($iniFiles as $file) { + // Check for inaccessible ini files + if (($data = @file_get_contents($file)) === false) { + $error = 'Unable to read ini: '.$file; + return false; + } + // Check and remove directives after HOST and PATH sections + if (Preg::isMatchWithOffsets($sectionRegex, $data, $matches, PREG_OFFSET_CAPTURE)) { + $data = substr($data, 0, $matches[0][1]); + } + $content .= Preg::replace($xdebugRegex, ';$1', $data).PHP_EOL; + } + + // Merge loaded settings into our ini content, if it is valid + $config = parse_ini_string($content); + $loaded = ini_get_all(null, false); + + if (false === $config || false === $loaded) { + $error = 'Unable to parse ini data'; + return false; + } + + $content .= $this->mergeLoadedConfig($loaded, $config); + + // Work-around for https://bugs.php.net/bug.php?id=75932 + $content .= 'opcache.enable_cli=0'.PHP_EOL; + + return (bool) @file_put_contents($this->tmpIni, $content); + } + + /** + * Returns the command line arguments for the restart + * + * @return string[] + */ + private function getCommand(): array + { + $php = [PHP_BINARY]; + $args = array_slice($_SERVER['argv'], 1); + + if (!$this->persistent) { + // Use command-line options + array_push($php, '-n', '-c', $this->tmpIni); + } + + return array_merge($php, [$this->script], $args); + } + + /** + * Returns true if the restart environment variables were set + * + * No need to update $_SERVER since this is set in the restarted process. + * + * @param string[] $iniFiles All ini files used in the current process + */ + private function setEnvironment(bool $scannedInis, array $iniFiles): bool + { + $scanDir = getenv('PHP_INI_SCAN_DIR'); + $phprc = getenv('PHPRC'); + + // Make original inis available to restarted process + if (!putenv($this->envOriginalInis.'='.implode(PATH_SEPARATOR, $iniFiles))) { + return false; + } + + if ($this->persistent) { + // Use the environment to persist the settings + if (!putenv('PHP_INI_SCAN_DIR=') || !putenv('PHPRC='.$this->tmpIni)) { + return false; + } + } + + // Flag restarted process and save values for it to use + $envArgs = [ + self::RESTART_ID, + self::$xdebugVersion, + (int) $scannedInis, + false === $scanDir ? '*' : $scanDir, + false === $phprc ? '*' : $phprc, + ]; + + return putenv($this->envAllowXdebug.'='.implode('|', $envArgs)); + } + + /** + * Logs status messages + */ + private function notify(string $op, ?string $data = null): void + { + $this->statusWriter->report($op, $data); + } + + /** + * Returns default, changed and command-line ini settings + * + * @param mixed[] $loadedConfig All current ini settings + * @param mixed[] $iniConfig Settings from user ini files + * + */ + private function mergeLoadedConfig(array $loadedConfig, array $iniConfig): string + { + $content = ''; + + foreach ($loadedConfig as $name => $value) { + // Value will either be null, string or array (HHVM only) + if (!is_string($value) + || strpos($name, 'xdebug') === 0 + || $name === 'apc.mmap_file_mask') { + continue; + } + + if (!isset($iniConfig[$name]) || $iniConfig[$name] !== $value) { + // Double-quote escape each value + $content .= $name.'="'.addcslashes($value, '\\"').'"'.PHP_EOL; + } + } + + return $content; + } + + /** + * Returns true if the script name can be used + */ + private function checkMainScript(): bool + { + if ($this->script !== null) { + // Allow an application to set -- for standard input + return file_exists($this->script) || '--' === $this->script; + } + + if (file_exists($this->script = $_SERVER['argv'][0])) { + return true; + } + + // Use a backtrace to resolve Phar and chdir issues. + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + $main = end($trace); + + if ($main !== false && isset($main['file'])) { + return file_exists($this->script = $main['file']); + } + + return false; + } + + /** + * Adds restart settings to the environment + * + * @param string[] $envArgs + */ + private function setEnvRestartSettings(array $envArgs): void + { + $settings = [ + php_ini_loaded_file(), + $envArgs[2], + $envArgs[3], + $envArgs[4], + getenv($this->envOriginalInis), + self::$skipped, + ]; + + Process::setEnv(self::RESTART_SETTINGS, implode('|', $settings)); + } + + /** + * Syncs settings and the environment if called with existing settings + * + * @phpstan-param restartData $settings + */ + private function syncSettings(array $settings): void + { + if (false === getenv($this->envOriginalInis)) { + // Called by another app, so make original inis available + Process::setEnv($this->envOriginalInis, implode(PATH_SEPARATOR, $settings['inis'])); + } + + self::$skipped = $settings['skipped']; + $this->notify(Status::INFO, 'Process called with existing restart settings'); + } + + /** + * Returns true if there are no known configuration issues + */ + private function checkConfiguration(?string &$info): bool + { + if (!function_exists('proc_open')) { + $info = 'proc_open function is disabled'; + return false; + } + + if (extension_loaded('uopz') && !((bool) ini_get('uopz.disable'))) { + // uopz works at opcode level and disables exit calls + if (function_exists('uopz_allow_exit')) { + @uopz_allow_exit(true); + } else { + $info = 'uopz extension is not compatible'; + return false; + } + } + + // Check UNC paths when using cmd.exe + if (defined('PHP_WINDOWS_VERSION_BUILD') && PHP_VERSION_ID < 70400) { + $workingDir = getcwd(); + + if ($workingDir === false) { + $info = 'unable to determine working directory'; + return false; + } + + if (0 === strpos($workingDir, '\\\\')) { + $info = 'cmd.exe does not support UNC paths: '.$workingDir; + return false; + } + } + + return true; + } + + /** + * Enables async signals and control interrupts in the restarted process + * + * Available on Unix PHP 7.1+ with the pcntl extension and Windows PHP 7.4+. + */ + private function tryEnableSignals(): void + { + if (function_exists('pcntl_async_signals') && function_exists('pcntl_signal')) { + pcntl_async_signals(true); + $message = 'Async signals enabled'; + + if (!self::$inRestart) { + // Restarting, so ignore SIGINT in parent + pcntl_signal(SIGINT, SIG_IGN); + } elseif (is_int(pcntl_signal_get_handler(SIGINT))) { + // Restarted, no handler set so force default action + pcntl_signal(SIGINT, SIG_DFL); + } + } + + if (!self::$inRestart && function_exists('sapi_windows_set_ctrl_handler')) { + // Restarting, so set a handler to ignore CTRL events in the parent. + // This ensures that CTRL+C events will be available in the child + // process without having to enable them there, which is unreliable. + sapi_windows_set_ctrl_handler(function ($evt) {}); + } + } + + /** + * Sets static properties $xdebugActive, $xdebugVersion and $xdebugMode + */ + private static function setXdebugDetails(): void + { + if (self::$xdebugActive !== null) { + return; + } + + self::$xdebugActive = false; + if (!extension_loaded('xdebug')) { + return; + } + + $version = phpversion('xdebug'); + self::$xdebugVersion = $version !== false ? $version : 'unknown'; + + if (version_compare(self::$xdebugVersion, '3.1', '>=')) { + $modes = xdebug_info('mode'); + self::$xdebugMode = count($modes) === 0 ? 'off' : implode(',', $modes); + self::$xdebugActive = self::$xdebugMode !== 'off'; + return; + } + + // See if xdebug.mode is supported in this version + $iniMode = ini_get('xdebug.mode'); + if ($iniMode === false) { + self::$xdebugActive = true; + return; + } + + // Environment value wins but cannot be empty + $envMode = (string) getenv('XDEBUG_MODE'); + if ($envMode !== '') { + self::$xdebugMode = $envMode; + } else { + self::$xdebugMode = $iniMode !== '' ? $iniMode : 'off'; + } + + // An empty comma-separated list is treated as mode 'off' + if (Preg::isMatch('/^,+$/', str_replace(' ', '', self::$xdebugMode))) { + self::$xdebugMode = 'off'; + } + + self::$xdebugActive = self::$xdebugMode !== 'off'; + } +} diff --git a/vendor/doctrine/instantiator/.doctrine-project.json b/vendor/doctrine/instantiator/.doctrine-project.json new file mode 100644 index 00000000..24ae36e0 --- /dev/null +++ b/vendor/doctrine/instantiator/.doctrine-project.json @@ -0,0 +1,47 @@ +{ + "active": true, + "name": "Instantiator", + "slug": "instantiator", + "docsSlug": "doctrine-instantiator", + "codePath": "/src", + "versions": [ + { + "name": "1.5", + "branchName": "1.5.x", + "slug": "latest", + "upcoming": true + }, + { + "name": "1.4", + "branchName": "1.4.x", + "slug": "1.4", + "aliases": [ + "current", + "stable" + ], + "maintained": true, + "current": true + }, + { + "name": "1.3", + "branchName": "1.3.x", + "slug": "1.3", + "maintained": false + }, + { + "name": "1.2", + "branchName": "1.2.x", + "slug": "1.2" + }, + { + "name": "1.1", + "branchName": "1.1.x", + "slug": "1.1" + }, + { + "name": "1.0", + "branchName": "1.0.x", + "slug": "1.0" + } + ] +} diff --git a/vendor/doctrine/instantiator/CONTRIBUTING.md b/vendor/doctrine/instantiator/CONTRIBUTING.md new file mode 100644 index 00000000..c1a2c42e --- /dev/null +++ b/vendor/doctrine/instantiator/CONTRIBUTING.md @@ -0,0 +1,35 @@ +# Contributing + + * Follow the [Doctrine Coding Standard](https://github.com/doctrine/coding-standard) + * The project will follow strict [object calisthenics](http://www.slideshare.net/guilhermeblanco/object-calisthenics-applied-to-php) + * Any contribution must provide tests for additional introduced conditions + * Any un-confirmed issue needs a failing test case before being accepted + * Pull requests must be sent from a new hotfix/feature branch, not from `master`. + +## Installation + +To install the project and run the tests, you need to clone it first: + +```sh +$ git clone git://github.com/doctrine/instantiator.git +``` + +You will then need to run a composer installation: + +```sh +$ cd Instantiator +$ curl -s https://getcomposer.org/installer | php +$ php composer.phar update +``` + +## Testing + +The PHPUnit version to be used is the one installed as a dev- dependency via composer: + +```sh +$ ./vendor/bin/phpunit +``` + +Accepted coverage for new contributions is 80%. Any contribution not satisfying this requirement +won't be merged. + diff --git a/vendor/doctrine/instantiator/LICENSE b/vendor/doctrine/instantiator/LICENSE new file mode 100644 index 00000000..4d983d1a --- /dev/null +++ b/vendor/doctrine/instantiator/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014 Doctrine Project + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/doctrine/instantiator/README.md b/vendor/doctrine/instantiator/README.md new file mode 100644 index 00000000..1fa95679 --- /dev/null +++ b/vendor/doctrine/instantiator/README.md @@ -0,0 +1,38 @@ +# Instantiator + +This library provides a way of avoiding usage of constructors when instantiating PHP classes. + +[![Build Status](https://travis-ci.org/doctrine/instantiator.svg?branch=master)](https://travis-ci.org/doctrine/instantiator) +[![Code Coverage](https://codecov.io/gh/doctrine/instantiator/branch/master/graph/badge.svg)](https://codecov.io/gh/doctrine/instantiator/branch/master) +[![Dependency Status](https://www.versioneye.com/package/php--doctrine--instantiator/badge.svg)](https://www.versioneye.com/package/php--doctrine--instantiator) + +[![Latest Stable Version](https://poser.pugx.org/doctrine/instantiator/v/stable.png)](https://packagist.org/packages/doctrine/instantiator) +[![Latest Unstable Version](https://poser.pugx.org/doctrine/instantiator/v/unstable.png)](https://packagist.org/packages/doctrine/instantiator) + +## Installation + +The suggested installation method is via [composer](https://getcomposer.org/): + +```sh +composer require doctrine/instantiator +``` + +## Usage + +The instantiator is able to create new instances of any class without using the constructor or any API of the class +itself: + +```php +$instantiator = new \Doctrine\Instantiator\Instantiator(); + +$instance = $instantiator->instantiate(\My\ClassName\Here::class); +``` + +## Contributing + +Please read the [CONTRIBUTING.md](CONTRIBUTING.md) contents if you wish to help out! + +## Credits + +This library was migrated from [ocramius/instantiator](https://github.com/Ocramius/Instantiator), which +has been donated to the doctrine organization, and which is now deprecated in favour of this package. diff --git a/vendor/doctrine/instantiator/composer.json b/vendor/doctrine/instantiator/composer.json new file mode 100644 index 00000000..179145e8 --- /dev/null +++ b/vendor/doctrine/instantiator/composer.json @@ -0,0 +1,48 @@ +{ + "name": "doctrine/instantiator", + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "type": "library", + "license": "MIT", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "instantiate", + "constructor" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "require": { + "php": "^8.1" + }, + "require-dev": { + "ext-phar": "*", + "ext-pdo": "*", + "doctrine/coding-standard": "^11", + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^5.4" + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "autoload-dev": { + "psr-0": { + "DoctrineTest\\InstantiatorPerformance\\": "tests", + "DoctrineTest\\InstantiatorTest\\": "tests", + "DoctrineTest\\InstantiatorTestAsset\\": "tests" + } + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + } +} diff --git a/vendor/doctrine/instantiator/docs/en/index.rst b/vendor/doctrine/instantiator/docs/en/index.rst new file mode 100644 index 00000000..0c85da0b --- /dev/null +++ b/vendor/doctrine/instantiator/docs/en/index.rst @@ -0,0 +1,68 @@ +Introduction +============ + +This library provides a way of avoiding usage of constructors when instantiating PHP classes. + +Installation +============ + +The suggested installation method is via `composer`_: + +.. code-block:: console + + $ composer require doctrine/instantiator + +Usage +===== + +The instantiator is able to create new instances of any class without +using the constructor or any API of the class itself: + +.. code-block:: php + + instantiate(User::class); + +Contributing +============ + +- Follow the `Doctrine Coding Standard`_ +- The project will follow strict `object calisthenics`_ +- Any contribution must provide tests for additional introduced + conditions +- Any un-confirmed issue needs a failing test case before being + accepted +- Pull requests must be sent from a new hotfix/feature branch, not from + ``master``. + +Testing +======= + +The PHPUnit version to be used is the one installed as a dev- dependency +via composer: + +.. code-block:: console + + $ ./vendor/bin/phpunit + +Accepted coverage for new contributions is 80%. Any contribution not +satisfying this requirement won’t be merged. + +Credits +======= + +This library was migrated from `ocramius/instantiator`_, which has been +donated to the doctrine organization, and which is now deprecated in +favour of this package. + +.. _composer: https://getcomposer.org/ +.. _CONTRIBUTING.md: CONTRIBUTING.md +.. _ocramius/instantiator: https://github.com/Ocramius/Instantiator +.. _Doctrine Coding Standard: https://github.com/doctrine/coding-standard +.. _object calisthenics: http://www.slideshare.net/guilhermeblanco/object-calisthenics-applied-to-php diff --git a/vendor/doctrine/instantiator/docs/en/sidebar.rst b/vendor/doctrine/instantiator/docs/en/sidebar.rst new file mode 100644 index 00000000..0c364791 --- /dev/null +++ b/vendor/doctrine/instantiator/docs/en/sidebar.rst @@ -0,0 +1,4 @@ +.. toctree:: + :depth: 3 + + index diff --git a/vendor/doctrine/instantiator/psalm.xml b/vendor/doctrine/instantiator/psalm.xml new file mode 100644 index 00000000..e9b622b3 --- /dev/null +++ b/vendor/doctrine/instantiator/psalm.xml @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/vendor/doctrine/instantiator/src/Doctrine/Instantiator/Exception/ExceptionInterface.php b/vendor/doctrine/instantiator/src/Doctrine/Instantiator/Exception/ExceptionInterface.php new file mode 100644 index 00000000..1e591928 --- /dev/null +++ b/vendor/doctrine/instantiator/src/Doctrine/Instantiator/Exception/ExceptionInterface.php @@ -0,0 +1,14 @@ + $reflectionClass + * + * @template T of object + */ + public static function fromAbstractClass(ReflectionClass $reflectionClass): self + { + return new self(sprintf( + 'The provided class "%s" is abstract, and cannot be instantiated', + $reflectionClass->getName(), + )); + } + + public static function fromEnum(string $className): self + { + return new self(sprintf( + 'The provided class "%s" is an enum, and cannot be instantiated', + $className, + )); + } +} diff --git a/vendor/doctrine/instantiator/src/Doctrine/Instantiator/Exception/UnexpectedValueException.php b/vendor/doctrine/instantiator/src/Doctrine/Instantiator/Exception/UnexpectedValueException.php new file mode 100644 index 00000000..4f70ded2 --- /dev/null +++ b/vendor/doctrine/instantiator/src/Doctrine/Instantiator/Exception/UnexpectedValueException.php @@ -0,0 +1,61 @@ + $reflectionClass + * + * @template T of object + */ + public static function fromSerializationTriggeredException( + ReflectionClass $reflectionClass, + Exception $exception, + ): self { + return new self( + sprintf( + 'An exception was raised while trying to instantiate an instance of "%s" via un-serialization', + $reflectionClass->getName(), + ), + 0, + $exception, + ); + } + + /** + * @phpstan-param ReflectionClass $reflectionClass + * + * @template T of object + */ + public static function fromUncleanUnSerialization( + ReflectionClass $reflectionClass, + string $errorString, + int $errorCode, + string $errorFile, + int $errorLine, + ): self { + return new self( + sprintf( + 'Could not produce an instance of "%s" via un-serialization, since an error was triggered ' + . 'in file "%s" at line "%d"', + $reflectionClass->getName(), + $errorFile, + $errorLine, + ), + 0, + new Exception($errorString, $errorCode), + ); + } +} diff --git a/vendor/doctrine/instantiator/src/Doctrine/Instantiator/Instantiator.php b/vendor/doctrine/instantiator/src/Doctrine/Instantiator/Instantiator.php new file mode 100644 index 00000000..f803f89a --- /dev/null +++ b/vendor/doctrine/instantiator/src/Doctrine/Instantiator/Instantiator.php @@ -0,0 +1,255 @@ + $className + * + * @phpstan-return T + * + * @throws ExceptionInterface + * + * @template T of object + */ + public function instantiate(string $className): object + { + if (isset(self::$cachedCloneables[$className])) { + /** @phpstan-var T */ + $cachedCloneable = self::$cachedCloneables[$className]; + + return clone $cachedCloneable; + } + + if (isset(self::$cachedInstantiators[$className])) { + $factory = self::$cachedInstantiators[$className]; + + return $factory(); + } + + return $this->buildAndCacheFromFactory($className); + } + + /** + * Builds the requested object and caches it in static properties for performance + * + * @phpstan-param class-string $className + * + * @phpstan-return T + * + * @template T of object + */ + private function buildAndCacheFromFactory(string $className): object + { + $factory = self::$cachedInstantiators[$className] = $this->buildFactory($className); + $instance = $factory(); + + if ($this->isSafeToClone(new ReflectionClass($instance))) { + self::$cachedCloneables[$className] = clone $instance; + } + + return $instance; + } + + /** + * Builds a callable capable of instantiating the given $className without + * invoking its constructor. + * + * @phpstan-param class-string $className + * + * @phpstan-return callable(): T + * + * @throws InvalidArgumentException + * @throws UnexpectedValueException + * @throws ReflectionException + * + * @template T of object + */ + private function buildFactory(string $className): callable + { + $reflectionClass = $this->getReflectionClass($className); + + if ($this->isInstantiableViaReflection($reflectionClass)) { + return [$reflectionClass, 'newInstanceWithoutConstructor']; + } + + $serializedString = sprintf( + '%s:%d:"%s":0:{}', + is_subclass_of($className, Serializable::class) ? self::SERIALIZATION_FORMAT_USE_UNSERIALIZER : self::SERIALIZATION_FORMAT_AVOID_UNSERIALIZER, + strlen($className), + $className, + ); + + $this->checkIfUnSerializationIsSupported($reflectionClass, $serializedString); + + return static fn () => unserialize($serializedString); + } + + /** + * @phpstan-param class-string $className + * + * @phpstan-return ReflectionClass + * + * @throws InvalidArgumentException + * @throws ReflectionException + * + * @template T of object + */ + private function getReflectionClass(string $className): ReflectionClass + { + if (! class_exists($className)) { + throw InvalidArgumentException::fromNonExistingClass($className); + } + + if (enum_exists($className, false)) { + throw InvalidArgumentException::fromEnum($className); + } + + $reflection = new ReflectionClass($className); + + if ($reflection->isAbstract()) { + throw InvalidArgumentException::fromAbstractClass($reflection); + } + + return $reflection; + } + + /** + * @phpstan-param ReflectionClass $reflectionClass + * + * @throws UnexpectedValueException + * + * @template T of object + */ + private function checkIfUnSerializationIsSupported(ReflectionClass $reflectionClass, string $serializedString): void + { + set_error_handler(static function (int $code, string $message, string $file, int $line) use ($reflectionClass, &$error): bool { + $error = UnexpectedValueException::fromUncleanUnSerialization( + $reflectionClass, + $message, + $code, + $file, + $line, + ); + + return true; + }); + + try { + $this->attemptInstantiationViaUnSerialization($reflectionClass, $serializedString); + } finally { + restore_error_handler(); + } + + if ($error) { + throw $error; + } + } + + /** + * @phpstan-param ReflectionClass $reflectionClass + * + * @throws UnexpectedValueException + * + * @template T of object + */ + private function attemptInstantiationViaUnSerialization(ReflectionClass $reflectionClass, string $serializedString): void + { + try { + unserialize($serializedString); + } catch (Exception $exception) { + throw UnexpectedValueException::fromSerializationTriggeredException($reflectionClass, $exception); + } + } + + /** + * @phpstan-param ReflectionClass $reflectionClass + * + * @template T of object + */ + private function isInstantiableViaReflection(ReflectionClass $reflectionClass): bool + { + return ! ($this->hasInternalAncestors($reflectionClass) && $reflectionClass->isFinal()); + } + + /** + * Verifies whether the given class is to be considered internal + * + * @phpstan-param ReflectionClass $reflectionClass + * + * @template T of object + */ + private function hasInternalAncestors(ReflectionClass $reflectionClass): bool + { + do { + if ($reflectionClass->isInternal()) { + return true; + } + + $reflectionClass = $reflectionClass->getParentClass(); + } while ($reflectionClass); + + return false; + } + + /** + * Checks if a class is cloneable + * + * Classes implementing `__clone` cannot be safely cloned, as that may cause side-effects. + * + * @phpstan-param ReflectionClass $reflectionClass + * + * @template T of object + */ + private function isSafeToClone(ReflectionClass $reflectionClass): bool + { + return $reflectionClass->isCloneable() + && ! $reflectionClass->hasMethod('__clone') + && ! $reflectionClass->isSubclassOf(ArrayIterator::class); + } +} diff --git a/vendor/doctrine/instantiator/src/Doctrine/Instantiator/InstantiatorInterface.php b/vendor/doctrine/instantiator/src/Doctrine/Instantiator/InstantiatorInterface.php new file mode 100644 index 00000000..c6ebe351 --- /dev/null +++ b/vendor/doctrine/instantiator/src/Doctrine/Instantiator/InstantiatorInterface.php @@ -0,0 +1,24 @@ + $className + * + * @phpstan-return T + * + * @throws ExceptionInterface + * + * @template T of object + */ + public function instantiate(string $className): object; +} diff --git a/vendor/friendsofphp/php-cs-fixer/CHANGELOG.md b/vendor/friendsofphp/php-cs-fixer/CHANGELOG.md new file mode 100644 index 00000000..ca87f947 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/CHANGELOG.md @@ -0,0 +1,5381 @@ +CHANGELOG for PHP CS Fixer +========================== + +This file contains changelogs for stable releases only. + +Changelog for v3.51.0 +--------------------- + +* chore: add missing tests for non-documentation classes (#7848) +* chore: do not perform type analysis in tests (#7852) +* chore: list over array in more places (#7857) +* chore: tests documentation classes (#7855) +* feat: `@Symfony` - add nullable_type_declaration (#7856) +* test: fix wrong type in param annotation (#7858) + +Changelog for v3.50.0 +--------------------- + +* chore: add missing types (#7842) +* chore: BlocksAnalyzer - raise exception on invalid index (#7819) +* chore: DataProviderAnalysis - expect list over array (#7800) +* chore: do not use `@large` on method level (#7832) +* chore: do not use `@medium` on method level (#7833) +* chore: Fix typos (#7835) +* chore: rename variables (#7847) +* chore: some improvements around array typehints (#7799) +* CI: fix PHP 8.4 job (#7829) +* DX: Include `symfony/var-dumper` in dev tools (#7795) +* feat: Ability to remove unused imports from multi-use statements (#7815) +* feat: allow PHPUnit 11 (#7824) +* feat: Allow shortening symbols from multi-use statements (only classes for now) (#7816) +* feat: introduce `PhpdocArrayTypeFixer` (#7812) +* feat: PhpUnitTestCaseStaticMethodCallsFixer - cover PHPUnit v11 methods (#7822) +* feat: Support for multi-use statements in `NamespaceUsesAnalyzer` (#7814) +* feat: `MbStrFunctionsFixer` - add support for `mb_trim`, `mb_ltrim` and `mb_rtrim` functions (#7840) +* feat: `NoEmptyPhpdocFixer` - do not leave empty line after removing PHPDoc (#7820) +* feat: `no_superfluous_phpdoc_tags` - introduce `allow_future_params` option (#7743) +* fix: do not use wrongly named arguments in data providers (#7823) +* fix: Ensure PCNTL extension is always installed in Docker (#7782) +* fix: PhpdocListTypeFixer - support key types containing `<…>` (#7817) +* fix: Proper build target for local Docker Compose (#7834) +* fix: union PHPDoc support in `fully_qualified_strict_types` fixer (#7719) +* fix: `ExecutorWithoutErrorHandler` - remove invalid PHP 7.4 type (#7845) +* fix: `fully_qualified_strict_types` must honor template/local type identifiers (#7724) +* fix: `MethodArgumentSpaceFixer` - do not break heredoc/nowdoc (#7828) +* fix: `NumericLiteralSeparatorFixer` - do not change `float` to `int` when there is nothing after the dot (#7805) +* fix: `PhpUnitStrictFixer` - do not crash on property having the name of method to fix (#7804) +* fix: `SingleSpaceAroundConstructFixer` - correctly recognise multiple constants (#7700) +* fix: `TypeExpression` - handle array shape key with dash (#7841) + +Changelog for v3.49.0 +--------------------- + +* chore(checkbashisms): update to 2.23.7 (#7780) +* chore: add missing key types in PHPDoc types (#7779) +* chore: Exclude `topic/core` issues/PRs from Stale Bot (#7788) +* chore: `DescribeCommand` - better handling of deprecations (#7778) +* docs: docker - use gitlab reporter in GitLab integration example (#7764) +* docs: docker in CI - don't suggest command that overrides path from config file (#7763) +* DX: check deprecations exactly (#7742) +* feat: Add `ordered_types` to `@Symfony` (#7356) +* feat: introduce `PhpdocListTypeFixer` (#7796) +* feat: introduce `string_implicit_backslashes` as `escape_implicit_backslashes` replacement (#7669) +* feat: update `Symfony.nullable_type_declaration_for_default_null_value` config (#7773) +* feat: `@PhpCsFixer` ruleset - enable `php_unit_data_provider_static` (#7685) +* fix: Allow using cache when running in Docker distribution (#7769) +* fix: ClassDefinitionFixer for anonymous class with phpdoc/attribute on separate line (#7546) +* fix: `ClassKeywordFixer` must run before `FullyQualifiedStrictTypesFixer` (#7767) +* fix: `function_to_constant` `get_class()` replacement (#7770) +* fix: `LowercaseStaticReferenceFixer` - do not change typed constants (#7775) +* fix: `PhpdocTypesFixer` - handle more complex types (#7791) +* fix: `TypeExpression` - do not break type using `walkTypes` method (#7785) + +Changelog for v3.48.0 +--------------------- + +* chore: `FullyQualifiedStrictTypesFixer` must run before `OrderedInterfacesFixer` (#7762) +* docs: Add PHP-CS-Fixer integration in a GitHub Action step (#7757) +* feat: `PhpdocTypesOrderFixer` Support DNF types (#7732) +* fix: Support shebang in fixers operating on PHP opening tag (#7687) +* fix: work correctly for a switch/case with ternary operator (#7756) +* fix: `NoUselessConcatOperatorFixer` - do not remove new line (#7759) + +Changelog for v3.47.1 +--------------------- + +* fix: Do not override short name with relative reference (#7752) +* fix: make `BinaryOperatorSpacesFixer` work as pre-v3.47 (#7751) +* fix: Proper Docker image name suffix (#7739) +* fix: `FullyQualifiedStrictTypesFixer` - do not change case of the symbol when there's name collision between imported class and imported function (#7750) +* fix: `FullyQualifiedStrictTypesFixer` - do not modify statements with property fetch and `::` (#7749) + +Changelog for v3.47.0 +--------------------- + +* chore: better identify EXPERIMENTAL rules (#7729) +* chore: fix issue detected by unlocked PHPStan + upgrade dev-tools (#7678) +* chore: handle extract() (#7684) +* chore: Mention contributors in app info (#7668) +* chore: no need to mark private methods as internal (#7715) +* chore: ProjectCodeTests - dry for function usage extractions (#7690) +* chore: reduce PHPStan baseline (#7644) +* chore: use numeric literal separator for PHP version IDs (#7712) +* chore: use numeric_literal_separator for project (#7713) +* chore: Utils::sortElements - better typing (#7646) +* CI: Allow running Stale Bot on demand (#7711) +* CI: Fix PHP 8.4 (#7702) +* CI: Give write permissions to Stale Bot (#7716) +* CI: Use `actions/stale` v9 (#7710) +* docs: Add information about allowing maintainers to update PRs (#7683) +* docs: CONTRIBUTING.md - update Opening a PR (#7691) +* docs: Display/include tool info/version by default in commands and reports (#7733) +* DX: fix deprecation tests warnings for PHP 7.4 (#7725) +* DX: update `host.docker.internal` in Compose override template (#7661) +* DX: `NumericLiteralSeparatorFixer` - change default strategy to `use_separator` (#7730) +* feat: Add support for official Docker images of Fixer (#7555) +* feat: Add `spacing` option to `PhpdocAlignFixer` (#6505) +* feat: Add `union_types` option to `phpdoc_to_param_type`, `phpdoc_to_property_type`, and `phpdoc_to_return_type` fixers (#7672) +* feat: Introduce `heredoc_closing_marker` fixer (#7660) +* feat: Introduce `multiline_string_to_heredoc` fixer (#7665) +* feat: Introduce `NumericLiteralSeparatorFixer` (#6761) +* feat: no_superfluous_phpdoc_tags - support for arrow function (#7666) +* feat: Simplify closing marker when possible in `heredoc_closing_marker` fixer (#7676) +* feat: Support typed properties and attributes in `fully_qualified_strict_types` (#7659) +* feat: `@PhpCsFixer` ruleset - enable no_whitespace_before_comma_in_array.after_heredoc (#7670) +* fix: Improve progress bar visual layer (#7708) +* fix: indentation of control structure body without braces (#7663) +* fix: make sure all PHP extensions required by PHPUnit are installed (#7727) +* fix: PhpdocToReturnTypeFixerTest - support for arrow functions (#7645) +* fix: Several improvements for `fully_qualified_strict_types` (respect declared symbols, relative imports, leading backslash in global namespace) (#7679) +* fix: SimplifiedNullReturnFixer - support array return typehint (#7728) +* fix: Support numeric values without leading zero in `numeric_literal_separator` (#7735) +* fix: `BinaryOperatorSpacesFixer` - align correctly when multiple shifts occurs in single line (#7593) +* fix: `ClassReferenceNameCasingFixer` capitalizes the property name after the nullsafe operator (#7696) +* fix: `fully_qualified_strict_types` with `leading_backslash_in_global_namespace` enabled - handle reserved types in phpDoc (#7648) +* fix: `NoSpaceAroundDoubleColonFixer` must run before `MethodChainingIndentationFixer` (#7723) +* fix: `no_superfluous_phpdoc_tags` must honor multiline docs (#7697) +* fix: `numeric_literal_separator` - Handle zero-leading floats properly (#7737) +* refactor: increase performance by ~7% thanks to `Tokens::block*Cache` hit increased by ~12% (#6176) +* refactor: Tokens - fast check for non-block in 'detectBlockType', evaluate definitions only once in 'getBlockEdgeDefinitions' (#7655) +* refactor: `Tokens::clearEmptyTokens` - play defensive with cache clearing (#7658) +* test: ensure we do not forget to test any short_open_tag test (#7638) + +Changelog for v3.46.0 +--------------------- + +* chore: fix internal typehints in Tokens (#7656) +* chore: reduce PHPStan baseline (#7643) +* docs: Show class with unit tests and BC promise info (#7667) +* feat: change default ruleset to `@PER-CS` (only behind PHP_CS_FIXER_FUTURE_MODE=1) (#7650) +* feat: Support new/instanceof/use trait in `fully_qualified_strict_types` (#7653) +* fix: FQCN parse phpdoc using full grammar regex (#7649) +* fix: Handle FQCN properly with `leading_backslash_in_global_namespace` option enabled (#7654) +* fix: PhpdocToParamTypeFixerTest - support for arrow functions (#7647) +* fix: PHP_CS_FIXER_FUTURE_MODE - proper boolean validation (#7651) + +Changelog for v3.45.0 +--------------------- + +* feat: Enable symbol importing in `@PhpCsFixer` ruleset (#7629) +* fix: NoUnneededBracesFixer - improve handling of global namespace (#7639) +* test: run tests with "short_open_tag" enabled (#7637) + +Changelog for v3.44.0 +--------------------- + +* feat: Introduce percentage bar as new default progress output (#7603) + +Changelog for v3.43.1 +--------------------- + +* fix: Import only unique symbols' short names (#7635) + +Changelog for v3.43.0 +--------------------- + +* chore: change base of `@Symfony` set to `@PER-CS2.0` (#7627) +* chore: PHPUnit - allow for v10 (#7606) +* chore: Preg - rework catching the error (#7616) +* chore: Revert unneeded peer-dep-pin and re-gen lock file (#7618) +* docs: drop extra note about 8.0.0 bug in README.md (#7614) +* feat: add cast_spaces into `@PER-CS2.0` (#7625) +* feat: Configurable phpDoc tags for FQCN processing (#7628) +* feat: StatementIndentationFixer - introduce stick_comment_to_next_continuous_control_statement config (#7624) +* feat: UnaryOperatorSpacesFixer - introduce only_dec_inc config (#7626) +* fix: FullyQualifiedStrictTypesFixer - better support annotations in inline {} (#7633) +* fix: Improve how FQCN is handled in phpDoc (#7622) +* fix: phpdoc_align - fix multiline tag alignment issue (#7630) + +Changelog for v3.42.0 +--------------------- + +* chore: aim to not rely on internal array pointer but use array_key_first (#7613) +* chore: deprecate Token::isKeyCaseSensitive (#7599) +* chore: deprecate Token::isKeyCaseSensitive, 2nd part (#7601) +* chore: do not check PHP_VERSION_ID (#7602) +* chore: FileFilterIteratorTest - more accurate type in docs (#7542) +* chore: minor code cleanup (#7607) +* chore: more types (#7598) +* chore: PHPDoc key-value spacing (#7592) +* chore: PHPUnit - run defects first (#7570) +* chore: ProjectCodeTest - DRY on Tokens creation (#7574) +* chore: ProjectCodeTest - prepare for symfony/console v7 (#7605) +* chore: ProjectCodeTest::provide*ClassCases to return iterable with key for better tests execution log (#7572) +* chore: ProjectCodeTest::testDataProvidersDeclaredReturnType - use better DataProvider to simplify test logic (#7573) +* chore: TokensAnalyzer - string-enum for better typehinting (#7571) +* chore: unify tests not agnostic of PHP version (#7581) +* chore: use ::class more (#7545) +* CI: Introduce `composer-unused` (#7536) +* DX: add types to anonymous functions (#7561) +* DX: Allow running smoke tests within Docker runtime (#7608) +* DX: check fixer's options for wording (#7543) +* DX: cleanup deprecation message (#7576) +* DX: do not allow overriding constructor of `PHPUnit\Framework\TestCase` (#7563) +* DX: do not import ExpectDeprecationTrait in UtilsTest (#7562) +* DX: Enforce consistent naming in tests (#7556) +* DX: fix checking test class extends `PhpCsFixer\Tests\TestCase` (#7567) +* DX: make sure that exceptions in `AbstractFixerTestCase::testProperMethodNaming` are not already fixed (#7588) +* DX: remove recursion from AbstractIntegrationTestCase::testIntegration (#7577) +* DX: remove `PhpUnitNamespacedFixerTest::testClassIsFixed` (#7564) +* DX: remove `symfony/phpunit-bridge` (#7578) +* DX: replace fixture classes with anonymous ones (#7533) +* DX: Unify Docker mount points and paths (#7549) +* DX: unify fixer's test method names - quick wins (#7584) +* DX: unify tests for casing fixers (#7558) +* DX: use anonymous function over concrete classes (#7553) +* feat(EXPERIMENTAL): ClassKeywordFixer (#2918) +* feat(EXPERIMENTAL): ClassKeywordFixer, part 2 (#7550) +* feat(PhpdocToCommentFixer): Add option to handle return as valid docblock usage (#7401) (#7402) +* feat: Ability to import FQCNs found during analysis (#7597) +* feat: add phpDoc support for `fully_qualified_strict_types` fixer (#5620) +* feat: Handle deprecated rule sets similarly to deprecated fixers (#7288) +* feat: PhpUnitTestCaseStaticMethodCallsFixer - cover PHPUnit v10 methods (#7604) +* feat: Support more FQCNs cases in `fully_qualified_strict_types` (#7459) +* fix: AbstractFixerTestCase - fix checking for correct casing (#7540) +* fix: Better OS detection in integration tests (#7547) +* fix: NativeTypeDeclarationCasingFixe - handle static property without type (#7589) +* test: AutoReview - unify data provider returns (#7544) +* test: check to have DataProviders code agnostic of PHP version (#7575) + +Changelog for v3.41.1 +--------------------- + +* DX: Change `@testWith` to `@dataProvider` (#7535) +* DX: Introduce Markdownlint (#7534) +* fix: NativeTypeDeclarationCasingFixer - do not crash on `var` keyword (#7538) + +Changelog for v3.41.0 +--------------------- + +* chore: Move `mb_str_functions` PHP 8.3 cases to separate test (#7505) +* chore: Symfony v7 is now stable (#7469) +* CI: drop PHP 8.3 hacks (#7519) +* docs: Improve docs for `no_spaces_after_function_name` (#7520) +* DX: Ability to run Sphinx linter locally (#7481) +* DX: AbstractFixerTest - use anonymous classes (#7527) +* DX: Add progress output for `cs:check` script (#7514) +* DX: align doubles naming (#7525) +* DX: remove AbstractFixerTestCase::getTestFile() (#7495) +* DX: remove jangregor/phpstan-prophecy (#7524) +* DX: remove Prophecy (#7509) +* DX: replace Prophecy with anonymous classes in CacheTest (#7503) +* DX: replace Prophecy with anonymous classes in ProcessLintingResultTest (#7501) +* DX: Utilise auto-discovery for PHPStan formatter (#7490) +* feat: Support `mb_str_pad` function in `mb_str_functions` rule (#7499) +* fix: BinaryOperatorSpacesFixer - do not add whitespace inside short function (#7523) +* fix: Downgrade PDepend to version not supporting Symfony 7 (#7513) +* fix: GlobalNamespaceImportFixer - key in PHPDoc's array shape matching class name (#7522) +* fix: SpacesInsideParenthesesFixer - handle class instantiation parentheses (#7531) +* Update PHPstan to 1.10.48 (#7532) + +Changelog for v3.40.2 +--------------------- + +* docs: fix link to source classes (#7493) + +Changelog for v3.40.1 +--------------------- + +* chore: Delete stray file x (#7473) +* chore: Fix editorconfig (#7478) +* chore: Fix typos (#7474) +* chore: Fix YAML line length (#7476) +* chore: Indent JSON files with 4 spaces (#7480) +* chore: Make YAML workflow git-based (#7477) +* chore: Use stable XDebug (#7489) +* CI: Lint docs (#7479) +* CI: Use PHPStan's native Github error formatter (#7487) +* DX: fix PHPStan error (#7488) +* DX: PsrAutoloadingFixerTest - do not build mock in data provider (#7491) +* DX: PsrAutoloadingFixerTest - merge all data providers into one (#7492) +* DX: Update PHPStan to 1.10.46 (#7486) +* fix: `NoSpacesAfterFunctionNameFixer` - do not remove space if the opening parenthesis part of an expression (#7430) + +Changelog for v3.40.0 +--------------------- + +* chore: officially support PHP 8.3 (#7466) +* chore: update deps (#7471) +* CI: add --no-update while dropping non-compat `facile-it/paraunit` (#7470) +* CI: automate --ignore-platform-req=PHP (#7467) +* CI: bump actions/github-script to v7 (#7468) +* CI: move humbug/box out of dev-tools/composer.json (#7472) + +Changelog for v3.39.1 +--------------------- + +* DX: introduce SwitchAnalyzer (#7456) +* fix: NoExtraBlankLinesFixer - do not remove blank line after `? : throw` (#7457) +* fix: OrderedInterfacesFixer - do not comment out interface (#7464) +* test: Improve `ExplicitIndirectVariableFixerTest` (#7451) + +Changelog for v3.39.0 +--------------------- + +* chore: Add support for Symfony 7 (#7453) +* chore: IntegrationTest - move support of php< requirement to main Integration classes (#7448) +* CI: drop Symfony ^7 incompat exceptions of php-coveralls and cli-executor (#7455) +* CI: early compatibility checks with Symfony 7 (#7431) +* docs: drop list.rst and code behind it (#7436) +* docs: remove Gitter mentions (#7441) +* DX: Ability to run Fixer on PHP8.3 for development (#7449) +* DX: describe command - for rules, list also sets that are including them (#7419) +* DX: Docker clean up (#7450) +* DX: more usage of spaceship operator (#7438) +* DX: Put `Preg`'s last error message in exception message (#7443) +* feat: Introduce `@PHP83Migration` ruleset and PHP 8.3 integration test (#7439) +* test: Improve `AbstractIntegrationTestCase` description (#7452) + +Changelog for v3.38.2 +--------------------- + +* docs: fix 'Could not lex literal_block as "php". Highlighting skipped.' (#7433) +* docs: small unification between FixerDocumentGenerator and ListDocumentGenerator (#7435) +* docs: unify ../ <> ./../ (#7434) + +Changelog for v3.38.1 +--------------------- + +* chore: ListSetsCommand::execute - add missing return type (#7432) +* chore: PHPStan - add counter to dataProvider exception, so we do not increase the tech debt on it (#7425) +* CI: Use `actions/checkout` v4 (#7423) +* fix: ClassAttributesSeparationFixer - handle Disjunctive Normal Form types parentheses (#7428) +* fix: Remove all variable names in `@var` callable signature (#7429) +* fix: Satisfy `composer normalize` (#7424) + +Changelog for v3.38.0 +--------------------- + +* chore: upgrade phpstan (#7421) +* CI: add curl and mbstring to build php (#7409) +* CI: cache dev-tools/bin (#7416) +* CI: Composer - move prefer-stable to file config (#7406) +* CI: conditionally install flex (#7412) +* CI: dev-tools/build.sh - no need to repeat 'prefer-stable', but let's use '--no-scripts' (#7408) +* CI: Do not run post-autoload-dump on Composer install (#7403) +* CI: general restructure (#7407) +* CI: GitHub Actions - use actions/cache for Composer in composite action (#7415) +* CI: Improve QA process - suplement (#7411) +* CI: prevent Infection plugins during build time, as we do not use it (#7422) +* CI: simplify setup-php config (#7404) +* DX: Do not mark as stale issues/PRs with milestone assigned (#7398) +* DX: Improve QA process (#7366) +* feat: phpDoc to property/return/param Fixer - allow fixing mixed on PHP >= 8 (#6356) +* feat: phpDoc to property/return/param Fixer - allow fixing union types on PHP >= 8 (#6359) +* feat: Support for array destructuring in `array_indentation` (#7405) +* feat: `@Symfony` - keep Annotation,NamedArgumentConstructor,Target annotations as single group (#7399) +* fix(SelfAccessorFixer): do not touch references inside lambda and/or arrow function (#7349) +* fix: long_to_shorthand_operator - mark as risky fixer (#7418) +* fix: OrderedImportsFixer - handle non-grouped list of const/function imports (#7397) + +Changelog for v3.37.1 +--------------------- + +* docs: config file - provide better examples (#7396) +* docs: config file - provide better link to Finder docs (#6992) + +Changelog for v3.37.0 +--------------------- + +* feat: add parallel cache support (#7131) + +Changelog for v3.36.0 +--------------------- + +* chore: disable `infection-installer` plugin, as we do not use `infection/*` yet (#7391) +* chore: Run dev-tools on PHP 8.2 (#7389) +* CI: Run Symfony 6 compat check on PHP 8.1 (#7383) +* CI: use fast-linter when calculating code coverage (#7390) +* docs: extend example for nullable_type_declaration (#7381) +* DX: FixerFactoryTest - make assertion failing msg more descriptive (#7387) +* feat: PhpdocSummaryFixer - support lists in description (#7385) +* feat: PSR12 - configure unary_operator_spaces (#7388) +* feat: StatementIndentationFixer - support comment for continuous control statement (#7384) + +Changelog for v3.35.1 +--------------------- + +* fix: Mark `PhpdocReadonlyClassCommentToKeywordFixer` as risky (#7372) + +Changelog for v3.35.0 +--------------------- + +* chore: Autoreview: test all formats are listed in `usage.rst` (#7357) +* chore: no need for `phpunitgoodpractices/traits` anymore (#7362) +* chore: Rename `indexes` to `indices` (#7368) +* chore: stop using `phpunitgoodpractices/traits` (#7363) +* chore: typo (#7367) +* docs: Sort options in documentation (#7345) +* feat(PhpdocReadonlyClassCommentToKeywordFixer): Introduction (#7353) +* feat: Ability to keep/enforce leading `\` when in global namespace (#7186) +* feat: Update `@PER-CS2.0` to match short closure space (#6970) +* feat: use `ordered_types` in `@PhpCsFixer` (#7361) +* fix(SingleLineThrowFixer): fixer goes out of range on close tag (#7369) + +Changelog for v3.34.1 +--------------------- + +* deps: revert "prevent using PHPCSFixer along with unfinalize package (#7343)" (#7348) + +Changelog for v3.34.0 +--------------------- + +* feat: Introduce `check` command (alias for `fix --dry-run`) (#7322) + +Changelog for v3.33.0 +--------------------- + +* feat: Introduce `native_type_declaration_casing` fixer (#7330) + +Changelog for v3.32.0 +--------------------- + +* deps: Prevent using PHPCSFixer along with `unfinalize` package (#7343) +* feat: Deprecate `CompactNullableTypehintFixer` and proxy to `CompactNullableTypeDeclarationFixer` (#7339) +* feat: Deprecate `CurlyBracesPositionFixer` and proxy to `BracesPositionFixer` (#7334) +* feat: Deprecate `NewWithBracesFixer` and proxy to `NewWithParenthesesFixer` (#7331) +* feat: Deprecate `NoUnneededCurlyBracesFixer` and proxy to `NoUnneededBracesFixer` (#7335) +* feat: Rename `CurlyBraceTransformer` to `BraceTransformer` (#7333) + +Changelog for v3.31.0 +--------------------- + +* chore: Use type declaration instead of type hint (#7338) +* feat: Introduce `attribute_placement` option for `MethodArgumentSpaceFixer` (#7320) +* fix: Adjust wording related to deprecations (#7332) +* fix: Correct deprecation header in rules' docs (#7337) +* fix: Replace mention of bracket with parenthesis (#7336) +* fix: `FunctionToConstantFixer` should run before `NativeConstantInvocationFixer` (#7344) + +Changelog for v3.30.0 +--------------------- + +* feat: Introduce `AttributeEmptyParenthesesFixer` (#7284) +* fix(method_argument_space): inject new line after trailing space on current line (#7327) +* fix(`YodaStyleFixer`): do not touch `require(_once)`, `include(_once)` and `yield from` statements (#7325) +* fix: illegal offset type on file-wide return in `ReturnToYieldFromFixer` (#7318) + +Changelog for v3.29.0 +--------------------- + +* chore: fix TODO tasks about T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG support (#7316) +* feat(`@PhpCsFixer:risky`): use newest `@PER-CS:risky` instead of locked `@PER-CS2.0:risky` (#7323) +* feat: Introduce `@PER-CS` ruleset (#7321) +* fix: priority issue between array_syntax and space after rules (#7324) + +Changelog for v3.28.0 +--------------------- + +* chore(prlint): allow for 'deps' type (#7304) +* CI(prlint): allow for special chars in parentheses (#7308) +* deps(dev-tools): update dev-tools (#7309) +* DX: Bump XDebug version in Docker services (#7300) +* feat(`@PER-CS2.0`): Add `concat_space` to the ruleset (#7302) + +Changelog for v3.27.0 +--------------------- + +* docs: cleanup old mention of `--show-progress=estimating` in docs (#7287) +* DX: add Composer script for applying CS fixes in parallel (#7274) +* feat: Clone PER-CS1.0 to PER-CS2.0 to prepare for adding new rules (#7249) +* feat: Introduce `LongToShorthandOperatorFixer` (#7295) +* feat: Mark PER-CS v1 as deprecated (#7283) +* feat: Move `single_line_empty_body` to `@PER-CS2.0` (#7282) +* fix: Priorities for fixers related to curly braces, empty lines and trailing whitespace (#7296) +* fix: `OrderedTraitsFixer` - better support for multiple traits in one `use` statement (#7289) + +Changelog for v3.26.1 +--------------------- + +* fix: Handle superfluous asterisk in `no_superfluous_phpdoc_tags` (#7279) + +Changelog for v3.26.0 +--------------------- + +* chore(checkbashisms): update to 2.23.6 (#7276) +* chore(phpstan): reduce baseline (#7275) +* feat: Add `single_line_empty_body` to `@PhpCsFixer` (#7266) +* fix(YieldFromArrayToYieldsFixer): mark as Risky (#7272) +* fix(YieldFromArrayToYieldsFixer): skip touching empty array (#7273) +* test: Introduce common way of creating fake Composer project in `InstallViaComposerTest` (#7265) + +Changelog for v3.25.1 +--------------------- + +* fix: PhpdocTypesFixer - do not crash for type followed by braces/brackets/chevrons/parentheses (#7268) + +Changelog for v3.25.0 +--------------------- + +* feat: Remove Doctrine dependencies (#7263) + +Changelog for v3.24.0 +--------------------- + +* chore: apply CS (#7240) +* chore: apply static_lambda rule (#7239) +* chore: Improve template for creating new issue (#7255) +* CI: Conventional Commits support in PRLint config (#7037) +* CI: Remove Travis leftovers (#7259) +* docs: Add information about installing Fixer as dev dependency (#7129) +* docs: document composer script aliases (#7230) +* DX: Add script for running Composer Require Checker (#7252) +* DX: composer script aliases - ensure templated description (#7235) +* DX: composer-script - count PHPMD as static-analysis (#7231) +* DX: do not allow version specific code sample with minimum PHP version lower than the lowest supported one (#7207) +* DX: ensure version specific code samples are suitable for at least 1 supported PHP version (#7212) +* DX: Improve contributing guide (#7241) +* DX: More descriptive stale messages (#7236) +* feat(@PhpCsFixer:risky): add static_lambda rule (#7237) +* feat: Add literal separator support for `integer_literal_case` (#7081) +* feat: Configurable case sensitivity for more ordering fixers (#7021) +* feat: Support for attributes in `method_argument_space` (#7242) +* fix: import detection for attributes at `NoUnusedImportsFixer` (#7246) +* fix: `no_superfluous_phpdoc_tags` with `null` phpdoc (#7234) +* fix: `phpdoc_types` must not lowercase literal types (#7108) +* test: Add static methods from PHPUnit 9.6.11 (#7243) + +Changelog for v3.23.0 +--------------------- + +* bug: BlankLineBeforeStatementFixer - do not enforce/add a blank line when there is a blank line between the comment and the statement already (#7190) +* bug: Fix detecting classy invocation in catch (#7191) +* bug: Fix names resolving in `no_superfluous_phpdoc_tags` fixer (#7189) +* bug: Fix various bugs in `FullyQualifiedStrictTypesFixer` fixer (#7188) +* bug: Fixed line between general script documentation and require (#7177) +* bug: Support annotations with arguments in `FinalInternalClassFixer` (#7160) +* bug: YieldFromArrayToYieldsFixer - fix for `yield from` after `}` (#7169) +* bug: YieldFromArrayToYieldsFixer - fix handling the comment before the first array element (#7193) +* bug: `HeaderCommentFixer` must run before `BlankLinesBeforeNamespaceFixer` (#7205) +* bug: `NoUselessReturnFixer` must run before `SingleLineEmptyBodyFixer` (#7226) +* bug: `PhpdocInlineTagNormalizerFixer` - do not break tags (#7227) +* docs: Add allowed values of tags in the `phpdoc_align` (#7120) +* docs: Add extra information for GitLab reporter's integration with GitLab Code Quality (#7172) +* docs: Change the single backticks to double in description of the rules option (#7173) +* docs: Condensed output for rule sets' list that fixer is included in (#7182) +* docs: Improve contributing guide (#7204) +* docs: `MethodArgumentSpaceFixer` - mention PSR in Fixer definition (#7157) +* DX: add first auto-review tests for composer.json file (#7210) +* DX: add `YieldFromArrayToYieldsFixer` to `PhpCsFixer` set (#7115) +* DX: Allow OS conditions for integration tests (#7161) +* DX: Apply current CS rules (#7178) +* DX: Apply suggestions from PR 7210 (#7213) +* DX: apply `ReturnToYieldFromFixer` (#7181) +* DX: Do not mark "long term ideas" as stale (#7206) +* DX: enable `HeredocIndentationFixer` for the codebase (#7195) +* DX: enable `UseArrowFunctionsFixer` for the codebase (#7194) +* DX: few phpstan fixes (#7208) +* DX: fix contravariant types in PHPDocs (#7167) +* DX: Fix detecting trailing spaces (#7216) +* DX: Fix some PHPStan issues (#7180) +* DX: Get rid of deprecation warnings in Mess Detector (#7215) +* DX: Improve Composer scripts (#7214) +* DX: Improve Mess Detector Integration (#7224) +* DX: Introduce Composer scripts as common DX (#6839) +* DX: refactor `ErrorOutputTest` (#7183) +* DX: remove unnecessary arrays from data providers (#7170) +* DX: update `CurlyBracesPositionFixer` code samples (#7198) +* DX: update `HeredocIndentationFixer` code samples (#7197) +* DX: update `PhpdocToReturnTypeFixer` code samples (#7199) +* feature: add at least one space around binary operators (#7175) +* feature: BlankLineBeforeStatementFixer - take into account comment before statement (#7166) +* feature: Introduce `ReturnToYieldFromFixer` (#7168) +* feature: Introduce `SpacesInsideParenthesesFixer` (#5709) +* feature: Support array destructuring in `trim_array_spaces` (#7218) +* feature: `BlankLineBeforeStatementFixer` - skip enum cases (#7203) +* minor: more arrow function usage (#7223) +* minor: PhpdocAlignFixerTest - convert CUSTOM tags test to not rely on non-custom tag from TAGS_WITH_NAME (#7209) +* minor: use JSON_THROW_ON_ERROR for trivial cases (#7221) +* minor: use more spread operator (#7222) + +Changelog for v3.22.0 +--------------------- + +* DX: add proper test for `SelfAccessorFixer` must run before `SelfAccessorFixer` (#7153) +* DX: FixerFactoryTest - apply CS (#7154) +* feature: Introduce `PhpUnitDataProviderReturnTypeFixer` (#7156) +* feature: Introduce `YieldFromArrayToYieldsFixer` (#7114) + +Changelog for v3.21.3 +--------------------- + +* Revert "DX: encourage to provide wider description" (#7155) + +Changelog for v3.21.2 +--------------------- + +* docs: check format of FixerDefinition::getDescription() (#7127) +* DX: add phpstan/phpstan-strict-rules (#7143) +* DX: allow for progressive cache (#7132) +* DX: Copy-pasteable `class::getPriority` for phpDoc diffs (#7148) +* DX: do not allow linebreak at the beginning of code sample (#7126) +* DX: encourage to provide wider description (#7128) +* DX: fix function calls (#7136) +* DX: fix PHPDoc types issues (#7135) +* DX: improve `Tokens` checking for found tokens (#7139) +* DX: Make `AbstractFixerTestCase::getTestFile()` final (#7116) +* DX: make `array_search` call strict (#7145) +* DX: remove `empty` calls (#7138) +* DX: store cache to file only if content will get modified (#7151) +* DX: unify Preg:match in logical conditions (#7146) +* DX: use booleans in conditions (#7149) +* DX: Use ParaUnit to speed up tests (#6883) +* DX: Use relative fixture path as integration test case's name (#7147) +* DX: use strict assertions (#7144) +* DX: `AbstractIntegrationTestCase::provideIntegrationCases` - yield over array, better typehinting (#7150) + +Changelog for v3.21.1 +--------------------- + +experimental release + +* Require PHP ^8.0.1 + +Changelog for v3.21.0 +--------------------- + +* bug: Fix and enhance Gitlab reporter (#7089) +* bug: Import with different case must not be removed by non-risky fixer (#7095) +* bug: ordered imports fixer top group only (#7023) +* bug: `FinalPublicMethodForAbstractClassFixer` - fix for readonly classes (#7123) +* DX: do not nest ".editorconfig" files (#7112) +* DX: exclude Dockerfile from dist (#7113) +* DX: fix checkbashisms installation (#7102) +* DX: fix Smoke tests for various git default branch name (#7119) +* DX: Fix `FileRemovalTest` (do not fail when running it standalone) (#7104) +* DX: Progress output refactor (#6848) +* DX: Rename abstract test classes to `*TestCase` convention (#7100) +* DX: test all PHPUnit migration sets (#7107) +* DX: [Docker] Distinguish Alpine version between PHP versions (#7105) +* feature: Create cache path if it does not exist (#7109) +* feature: Introduce `NullableTypeDeclarationFixer` (#7002) +* feature: Introduce `TypeDeclarationSpacesFixer` (#7001) +* feature: `BlankLineBetweenImportGroupsFixer` - keep indent (#7122) +* minor: Parse callable using full phpdoc grammar (#7094) +* minor: PHP8.3 const type tokenizing (#7055) + +Changelog for v3.20.0 +--------------------- + +* DX: fix priority of `FinalClassFixer` (#7091) +* DX: use FAST_LINT_TEST_CASES=1 for CI run on macOS (#7092) +* feature: SingleLineEmptyBodyFixer - support interfaces, traits and enums (#7096) +* feature: `NullableTypeDeclarationForDefaultNullValue` - support for nullability in union types (#5819) + +Changelog for v3.19.2 +--------------------- + +* bug: NoMultipleStatementsPerLineFixer must run before CurlyBracesPositionFixer (#7087) +* bug: PhpdocAddMissingParamAnnotationFixer - fix for promoted properties (#7090) +* DX: fix priority of SingleBlankLineBeforeNamespaceFixer (#7088) +* minor: Parse all phpdoc types using full grammar (#7010) + +Changelog for v3.19.1 +--------------------- + +* bug: CurlyBracesPositionFixer must run before StatementIndentationFixer (#7085) + +Changelog for v3.19.0 +--------------------- + +* bug: SelfAccessorFixer - fix for union types (#7080) +* DX: add `php_unit_data_provider_name` to `@PhpCsFixer:risky` set (#7069) +* DX: make data providers return type "iterable" (#7072) +* DX: rename tests and data providers (#7070) +* feature: Introduce `PhpUnitDataProviderNameFixer` (#7057) + +Changelog for v3.18.0 +--------------------- + +* bug: Fix tokenizing of type hints (#7054) +* bug: CompactNullableTypehintFixer - fix for whitespace between `?` and `static` (#6993) +* bug: consider function modifiers for `statement_indentation` (#6978) +* bug: Exclude `$this` from `TernaryToNullCoalescingFixer` (#7052) +* bug: False positive on used imports when docblock includes it with mismatching case (#6909) +* bug: Fix chained calls semicolon indent in switch case (#7045) +* bug: Fix multiline_whitespace_before_semicolons for echo tags (#7019) +* bug: Fix phpDoc align when there is inconsistent spacing after comment star (#7012) +* bug: Fix phpDoc parsing without PCRE JIT (#7031) +* bug: Fix PhpdocVarWithoutNameFixer with Closure with $this (#6979) +* bug: Fix `return_assignment` not formatting when variables are used in `catch` and `finally` (#6960) +* bug: Fix `TypeExpression::allowsNull()` with nullable (#7000) +* bug: Improve definition of conflicting fixers (#7066) +* bug: LambdaNotUsedImportFixer - fix for anonymous class with a string argument (#6972) +* bug: ListFilesCommand - fix computing of relative path (#7028) +* bug: make `php_unit_namespaced` less greedy (#6952) +* bug: PhpdocToCommentFixer - fix for PHPDoc before fn (#6973) +* bug: Restructure PER-CS rule sets (#6707) +* bug: SelfStaticAccessor - fix static access inside enums (#7024) +* bug: SingleSpaceAroundConstructFixer - fix more cases involving `static` (#6995) +* bug: `FullyQualifiedStrictTypesFixer` - fix shortening when namespace is not empty and import exists (#7027) +* bug: `NoUnneededControlParenthesesFixer` PHP8.0 null-safe operator (#7056) +* bug: `PhpdocToCommentFixer` support for enum cases (#7040) +* DX: add more tests to CommentsAnalyzer (#7041) +* DX: Cleanup duplicate files in finder (#7042) +* DX: ControlCaseStructuresAnalyzerTest - cleanup (#6874) +* DX: Fix warning when running test on PHP<8 (#7008) +* DX: handle `@` in PR title (#6982) +* DX: officially deprecate internal Utils anti-pattern class (#7039) +* DX: Remove Fabbot.io conditional configuration (#7038) +* DX: rename data providers (#7058) +* DX: Use `actions/stale` to handle stale issues and pull requests (#5085) +* DX: Use `Utils::naturalLanguageJoin()` in implode calls (#7032) +* feature: Add support for custom method placement in `ordered_class_elements` (#6360) +* feature: Allow case sensitive order for OrderedClassElementsFixer (#7020) +* feature: PHP8.3 - Add CT and block type for `Dynamic class constant fetch` (#7004) +* feature: Support attributes in `FinalClassFixer` (#6893) +* minor: "Callback" must not be fixed to "callback" by default (#7011) +* minor: Add `Util::naturalLanguageJoin()` (#7022) +* minor: ClassDefinitionFixer - handle attributes and `readonly` in anonymous class definitions (#7014) +* minor: FixerFactory::getFixersConflicts - better type hinting (#7044) +* minor: PHP8.3 - Fix TokensAnalyzer::isAnonymousClass support for `readonly` (#7013) +* minor: PHP8.3 - Typed class constants - handle nullable by transformer (#7009) +* minor: Reduce phpDoc type parser complexity from O(n^2) to O(nlog(n)) (#6988) +* minor: ReturnAssignmentFixer - Better handling of anonymous classes (#7015) +* minor: Transfer `HelpCommand::toString()` to `Utils` (#7034) +* minor: Unify "blank lines before namespace" fixers (#7053) +* minor: `SelfStaticAccessorFixer` improvements for enums (#7026) +* minor: `SingleSpaceAroundConstructFixer` - support space before `as` (#7029) +* minor: `UseArrowFunctionsFixer` - run before `FunctionDeclarationFixer` (#7065) + +Changelog for v3.17.0 +--------------------- + +* bug: Allow string quote to be escaped within phpdoc constant (#6798) +* bug: ConfigurationResolver - fix running without cache (#6915) +* bug: Fix array/object shape phpdoc type parse (#6962) +* bug: Fix FullyQualifiedStrictTypesFixer common prefix bug (#6898) +* bug: Fix non-parenthesized callable return type parse (#6961) +* bug: Fix parsing of edge cases phpdoc types (#6977) +* bug: FullyQualifiedStrictTypesFixer - fix for FQCN type with class with the same name being imported (#6923) +* bug: GroupImportFixer - support for aliased imports (#6951) +* bug: MultilineWhitespaceBeforeSemicolonsFixer - fix chained calls (#6926) +* bug: MultilineWhitespaceBeforeSemicolonsFixer - fix for discovering multi line calls (#6938) +* bug: NoBreakCommentFixer - fix for nested match (#6899) +* bug: NoExtraBlankLinesFixer - fix for attribute in abstract function (#6920) +* bug: PhpdocTypesFixer - handle types with no space between type and variable (#6922) +* bug: PhpUnitMockShortWillReturnFixer - fix for trailing commas (#6900) +* bug: StatementIndentationFixer - fix comments at the end of if/elseif/else blocks (#6918) +* bug: StatementIndentationFixer - fix for multiline arguments starting with "new" keyword (#6913) +* bug: StatementIndentationFixer - fix for multiline arguments starting with "new" keyword preceded by class instantiation (#6914) +* bug: VoidReturnFixer - fix for intervening attributes (#6863) +* docs: improve code samples for MultilineWhitespaceBeforeSemicolonsFixer (#6919) +* docs: improve cookbook (#6880) +* DX: add cache related tests (#6916) +* DX: Apply `self_static_accessor` fixer to the project (again) (#6927) +* DX: cancel running builds on subsequent pushes in CI (#6940) +* DX: convert more `static` to `self` assert calls (#6931) +* DX: fix GitHub Actions errors and warnings (#6917) +* DX: fix Unsafe call to private method errors reported by PHPStan (#6879) +* DX: Improve performance of FunctionsAnalyzer (#6939) +* DX: improve test method names to avoid confusion (#6974) +* DX: Include self_static_accessor fixer in PhpCsFixer set (#6882) +* DX: make data providers static with straight-forward changes (#6907) +* DX: Mark Tokens::getNamespaceDeclarations as @internal (#6949) +* DX: PHPStan improvements (#6868) +* DX: refactor PhpdocAlignFixerTest (#6925) +* DX: Remove @inheritdoc PHPDoc (#6955) +* DX: Run AutoReview tests only once (#6889) +* DX: simplify EncodingFixer (#6956) +* DX: update Symfony rule set (#6958) +* DX: Use $tokens->getNamespaceDeclarations() to improve performance (#6942) +* DX: use force option for php_unit_data_provider_static in PHPUnit 10.0 migration set (#6908) +* DX: use only PHP modules that are required (#6954) +* DX: use PHPUnit's "requires" instead of "if" condition (#6975) +* feature: Add align_multiline_comment rule to @Symfony (#6875) +* feature: Add no_null_property_initialization rule to @Symfony (#6876) +* feature: Add operator_linebreak rule to @Symfony (#6877) +* feature: add SingleLineEmptyBodyFixer (#6933) +* feature: DescribeCommand - allow describing custom fixers (#6957) +* feature: Introduce `OrderedTypesFixer` (#6571) +* feature: Order of PHPDoc @param annotations (#3909) +* feature: Parse parenthesized & conditional phpdoc type (#6796) +* feature: PhpUnitInternalClassFixer - add empty line before PHPDoc (#6924) +* feature: [PhpdocAlignFixer] Add support for every tag (#6564) +* minor: align NoSuperfluousPhpdocTagsFixer with actual Symfony configuration (#6953) +* minor: do not add empty line in PHPDoc when adding annotation in PHPUnit class (#6928) +* minor: PhpdocAlignFixer - support cases with type and variable separated with no space (#6921) +* minor: PhpdocSeparationFixer - add integration tests (#6929) +* minor: update PHPStan (to fix CI on master branch) (#6901) +* minor: Use single Dockerfile with multiple build targets (#6840) + +Changelog for v3.16.0 +--------------------- + +* bug: ControlStructureBracesFixer - handle closing tag (#6873) +* bug: CurlyBracesPositionFixer - fix for callable return type (#6855) +* bug: CurlyBracesPositionFixer - fix for DNF types (#6859) +* bug: Fix MultilineWhitespaceBeforeSemicolonsFixer (#5126) +* docs: Fix rule description (#6844) +* DX: fix checkbashisms installation (#6843) +* DX: make data providers static for fixer's tests (#6860) +* DX: refactor PHPUnit fixers adding class-level annotation to use shared code (#6756) +* DX: unify option's descriptions (#6856) +* feature: AbstractPhpUnitFixer - support attribute detection in docblock insertion (#6858) +* feature: add "force" option to PhpUnitDataProviderStaticFixer (#6757) +* feature: introduce single_space_around_construct, deprecate single_space_after_construct (#6857) +* feature: PhpUnitTestClassRequiresCoversFixer - support single-line PHPDocs (#6847) +* minor: Deprecate BracesFixer (#4885) +* minor: Fix autocompletion for `Tokens::offsetGet()` (#6838) +* minor: PHP8.2 Docker runtime (#6833) +* minor: Use Composer binary-only images instead of installer script (#6834) + +Changelog for v3.15.1 +--------------------- + +* bug: BinaryOperatorSpacesFixer - fix for static in type (#6835) +* bug: BinaryOperatorSpacesFixer - fix parameters with union types passed by reference (#6826) +* bug: NoUnusedImportsFixer - fix for splat operator (#6836) +* DX: fix CI (#6837) +* feature: Support for type casing in arrow functions (#6831) +* minor: fix CI on PHP 8.3 (#6827) + +Changelog for v3.15.0 +--------------------- + +* bug: VisibilityRequiredFixer - handle DNF types (#6806) +* DX: officially enable 8.2 support (#6825) + +Changelog for v3.14.5 +--------------------- + +* bug: EmptyLoopBodyFixer must keep comments inside (#6800) +* bug: FunctionsAnalyzer - fix detecting global function (#6792) +* bug: NativeFunctionTypeDeclarationCasingFixer - do not require T_STRING present in code (#6812) +* bug: PhpdocTypesFixer - do not change case of array keys (#6810) +* bug: PhpUnitTestAnnotationFixer - do not break single line @depends (#6824) +* docs: Add supported PHP versions section to the README (#6768) +* docs: drop Atom from readme, due to it's sunsetting (#6778) +* DX: Add composer keywords (#6781) +* DX: update PHPStan to 1.10.3 (#6805) +* feature: [PHP8.2] Support for readonly classes (#6745) +* minor: add custom tokens for Disjunctive Normal Form types parentheses (#6823) +* minor: PHP8.2 - handle union and intersection types for DNF types (#6804) +* minor: PHP8.2 - support property in const expressions (#6803) + +Changelog for v3.14.4 +--------------------- + +* bug: CurlyBracesPositionFixer - fix for open brace not preceded by space and followed by a comment (#6776) +* docs: drop license end year (#6767) +* DX: use numeric_literal_separator (#6766) +* feature: Allow installation of `sebastian/diff:^5.0.0` (#6771) + +Changelog for v3.14.3 +--------------------- + +* DX: Drop doctrine/annotations 1, allow doctrine/lexer 3 (#6730) + +Changelog for v3.14.2 +--------------------- + +* DX: Drop support for doctrine/lexer 1 (#6729) + +Changelog for v3.14.1 +--------------------- + +* DX: Allow doctrine/annotations 2 (#6721) + +Changelog for v3.14.0 +--------------------- + +* bug: Fix indentation for comment at end of function followed by a comma (#6542) +* bug: Fix PHPDoc alignment fixer containing callbacks using `\Closure` (#6746) +* bug: Fix type error when using paths intersection mode (#6734) +* bug: PhpdocSeparationFixer - Make groups handling more flexible (#6668) +* docs: make bug_report.md template more explicit (#6736) +* docs: PhpUnitTestCaseIndicator - fix docs (#6727) +* DX: apply CS (#6759) +* DX: bump doctrine/annotations to prevent installing version with unintentional BC break (#6739) +* DX: update deps (#6760) +* DX: upgrade dev-tools/composer.json (#6737) +* DX: upgrade PHPStan to 1.9.7 (#6741) +* feature: Add php 7.4 types to Cookbook docs (#6763) +* feature: add PhpUnitDataProviderStaticFixer (#6702) +* feature: binary_operator_spaces - Revert change about => alignment and use option instead (#6724) +* feature: make OrderedInterfacesFixer non-risky (#6722) +* feature: OctalNotationFixer - support _ notation (#6762) +* fix: enum case "PARENT" must not be renamed (#6732) +* minor: Follow PSR12 ordered imports in Symfony ruleset (#6712) +* minor: improve rule sets order (#6738) + +Changelog for v3.13.2 +--------------------- + +* bug: Fix type error when using paths intersection mode (#6734) + +Changelog for v3.13.1 +--------------------- + +* bug: Align all the arrows inside the same array (#6590) +* bug: Fix priority between `modernize_types_casting` and `no_unneeded_control_parentheses` (#6687) +* bug: TrailingCommaInMultilineFixer - do not add trailing comma when there is no break line after last element (#6677) +* docs: Fix docs for disabled rules in rulesets (#6679) +* docs: fix the cookbook_fixers.rst (#6672) +* docs: Update installation recommended commands for `mkdir` argument (`-p` insteadof `--parents`). (#6689) +* Make static data providers that are not using dynamic calls (#6696) +* minor: displaying number of checked files (#6674) + +Changelog for v3.13.0 +--------------------- + +* bug: BracesFixer - Fix unexpected extra blank line (#6667) +* bug: fix CI on master branch (#6663) +* bug: IsNullFixer - handle casting (#6661) +* docs: feature or bug (#6652) +* docs: Use case insensitive sorting for options (#6666) +* docs: [DateTimeCreateFromFormatCallFixer] Fix typos in the code sample (#6671) +* DX: update cli-executor (#6664) +* DX: update dev-tools (#6665) +* feature: Add global_namespace_import to @Symfony ruleset (#6662) +* feature: Add separate option for closure_fn_spacing (#6658) +* feature: general_phpdoc_annotation_remove - allow add case_sensitive option (#6660) +* minor: AllowedValueSubset - possible values are sorted (#6651) +* minor: Use md5 for file hashing to reduce possible collisions (#6597) + +Changelog for v3.12.0 +--------------------- + +* bug: SingleLineThrowFixer - Handle throw expression inside block (#6653) +* DX: create TODO to change default ruleset for v4 (#6601) +* DX: Fix SCA findings (#6626) +* DX: HelpCommand - fix docblock (#6584) +* DX: Narrow some docblock types (#6581) +* DX: Remove redundant check for PHP <5.2.7 (#6620) +* DX: Restore PHPDoc to type rules workflow step (#6615) +* DX: SCA - scope down types (#6630) +* DX: Specify value type in iterables in tests (#6594) +* DX: Test on PHP 8.2 (#6558) +* DX: Update GitHub Actions (#6606) +* DX: Update PHPStan (#6616) +* feature: Add `@PHP82Migration` ruleset (#6621) +* feature: ArrayPushFixer now fix short arrays (#6639) +* feature: NoSuperfluousPhpdocTagsFixer - support untyped and empty annotations in phpdoc (#5792) +* feature: NoUselessConcatOperatorFixer - Introduction (#6447) +* feature: Support for constants in traits (#6607) +* feature: [PHP8.2] Support for new standalone types (`null`, `true`, `false`) (#6623) +* minor: GitHub Workflows security hardening (#6644) +* minor: prevent BC break in ErrorOutput (#6633) +* minor: prevent BC break in Runner (#6634) +* minor: Revert "minor: prevent BC break in Runner" (#6637) +* minor: Update dev tools (#6554) + +Changelog for v3.11.0 +--------------------- + +* bug: DateTimeCreateFromFormatCallFixer - Mark as risky (#6575) +* bug: Do not treat implements list comma as array comma (#6595) +* bug: Fix MethodChainingIndentationFixer with arrow functions and class instantiation (#5587) +* bug: MethodChainingIndentationFixer - Fix bug with attribute access (#6573) +* bug: NoMultilineWhitespaceAroundDoubleArrowFixer - fix for single line comment (#6589) +* bug: TypeAlternationTransformer - TypeIntersectionTransformer - Bug: handle attributes (#6579) +* bug: [BinaryOperatorFixer] Fix more issues with scoped operators (#6559) +* docs: Remove `$` from console command snippets (#6600) +* docs: Remove `$` from console command snippets in documentation (#6599) +* DX: AllowedValueSubset::getAllowedValues - fix method prototype (#6585) +* DX: Narrow docblock types in FixerConfiguration (#6580) +* DX: updagte @PhpCsFixer set config for phpdoc_order rule (#6555) +* DX: Update PHPUnit config (#6566) +* feature: Introduce configurability to PhpdocSeparationFixer (#6501) +* feature: Introduce PER set (#6545) +* feature: NoTrailingCommaInSinglelineFixer - Introduction (#6529) +* feature: Support removing superfluous PHPDocs involving `self` (#6583) +* minor: NoUnneededControlParenthesesFixer - Support instanceof static cases (#6587) +* minor: PhpdocToCommentFixer - allow phpdoc comments before trait use statement. Fixes #6092 (#6565) + +Changelog for v3.10.0 +--------------------- + +* bug: Fix error in `regular_callable_call` with static property (#6539) +* bug: Fix indentation for multiline class definition (#6540) +* bug: Fix indentation for switch ending with empty case (#6538) +* bug: Fix indentation of comment at end of switch case (#6493) +* bug: PhpdocAlignFixer - fix static `@method` (#6366) +* bug: SingleSpaceAfterConstructFixer - fix handling open tag (#6549) +* bug: VisibilityRequiredFixer must run before ClassAttributesSeparationFixer (#6548) +* DX: Assert dataproviders of tests of project itself return "array" or "iterable". (#6524) +* feature: Introduce configurability to PhpdocOrderFixer (#6466) +* feature: WhitespaceAfterCommaInArrayFixer - add option "ensure_single_space" (#6527) +* minor: Add test for indentation of trait conflict resolution (#6541) +* minor: Split BracesFixer (#4884) +* minor: TrailingCommaInMultilineFixer - Add comma to multiline `new static` (#6380) + +Changelog for v3.9.6 +-------------------- + +* bug: BinaryOperatorSpacesFixer: Solve issues with scoped arrow and equal alignments (#6515) +* bug: Fix 3 weird behavior about BinaryOperatorSpacesFixer (#6450) +* docs: Add intersection type to types_spaces rule description (#6479) +* DX: no need to use forked diff anymore (#6526) +* DX: remove unused FixerFileProcessedEvent::STATUS_UNKNOWN (#6516) +* Improve `statement_indentation` compatibility with `braces` (#6401) +* minor: add test: multi-line comments before else indented correctly. (#3573) +* minor: ReturnAssignmentFixer - Support for anonymous classes, lambda and match (#6391) + +Changelog for v3.9.5 +-------------------- + +* bug: AlternativeSyntaxAnalyzer - fix for nested else (#6495) +* bug: Fix cases related to binary strings (#6432) +* bug: Fix trailing whitespace after moving brace (#6489) +* bug: NoUnneededControlParenthesesFixer - Fix some curly close cases (#6502) +* bug: TypeColonTransformer - fix for backed enum types (#6494) +* DX: Add tests for type colon in backed enums (#6497) +* DX: Fix CI static analysis workflow (#6506) +* DX: Fix PHPStan errors (#6504) +* DX: Increase PHPStan level to 6 (#6468) +* DX: Narrow docblock types in Runner and Report (#6465) +* DX: Narrow docblock types in Tokenizer (#6293) +* minor: extract NoMultipleStatementsPerLineFixer from BracesFixer (#6458) +* minor: Let PhpdocLineSpan fixer detect docblocks when separator from token with attribute (#6343) + +Changelog for v3.9.4 +-------------------- + +* bug: Fix various indentation issues (#6480) +* bug: Fix wrong brace position after static return type (#6485) +* bug: Prevent breaking functions returning by reference (#6487) + +Changelog for v3.9.3 +-------------------- + +* bug: Fix BinaryOperatorSpacesFixer adding whitespace outside PHP blocks (#6476) +* bug: Fix brace location after multiline function signature (#6475) + +Changelog for v3.9.2 +-------------------- + +* bug: Fix indentation after control structure in switch (#6473) + +Changelog for v3.9.1 +-------------------- + +* bug: Add attributes support to `statement_indentation` (#6429) +* bug: BinaryOperatorSpacesFixer - Allow to align `=` inside array definitions (#6444) +* bug: BinaryOperatorSpacesFixer - Fix align of operator with function declaration (#6445) +* bug: ConstantCaseFixer - Do not touch enum case (#6367) +* bug: CurlyBracesPositionFixer - multiple elseifs (#6459) +* bug: Fix #6439 issue in `StaticLambda` fixer (#6440) +* bug: FullOpeningTagFixer - fix substr check for pre PHP8 (#6388) +* bug: IncrementStyleFixer - NoSpacesInsideParenthesisFixer - prio (#6416) +* bug: LambdaNotUsedImportFixer must run before MethodArgumentSpaceFixer (#6453) +* bug: MethodArgumentSpaceFixer - first element in same line, space before comma and inconsistent indent (#6438) +* bug: NoSuperfluousPhpdocTagsFixer - fix for promoted properties (#6403) +* bug: StatementIndentationFixer - Fix indentation for multiline traits use (#6402) +* bug: StrictComparisonFixer must rune before ModernizeStrposFixer (#6455) +* bug: TokensAnalyzer - fix intersection types considered as binary operator (#6414) +* DX: `ISSUE_TEMPLATE` hints to check applied rules (#6398) +* DX: Add more type hints (#6383) +* DX: Fix CI/CD issues (#6411) +* DX: cleanup test (#6410) +* DX: integrate PRLint (#6406) +* feature: BlankLineBetweenImportGroupsFixer - Introduction (#6365) +* feature: DateTimeCreateFromFormatCallFixer - Add DateTimeImmutable support (#6350) +* feature: Extract StatementIndentationFixer from BracesFixer (#5960) +* feature: ModernizeStrposFixer - fix leading backslash with yoda (#6377) +* feature: NoExtraBlankLinesFixer - Add `attributes` option - Fix support for `enum` `case` (#6426) +* feature: NoUnneededControlParenthesesFixer - Fix more cases (#6409) +* feature: NoUselessNullsafeOperatorFixer - Introduction (#6425) +* feature: OrderedTrait - Move Phpdoc with trait import (#6361) +* feature: PhpdocOrderByValueFixer - Allow sorting of mixin annotations by value (#6446) +* feature: TrailingCommaInMultiline - Add `match` support (#6381) +* minor: Allow Composer Normalize plugin (#6454) +* minor: ExplicitStringVariableFixer - Fix to PHP8.2 compat code (#6424) +* minor: Extract ControlStructureBracesFixer from BracesFixer (#6399) +* minor: NoBinaryStringFixer - Fix more cases (#6442) +* minor: NoSuperfluousPhpdocTagsFixer - Attribute handling (#6382) +* minor: PhpCsFixerSet - Update blank_line_before_statement config (#6389) +* minor: Remove unnecessary PHP version constraints (#6461) +* minor: SingleImportPerStatementFixer - fix PSR12 set (#6415) +* minor: SingleSpaceAfterConstructFixer - add option `type_colon` (#6434) +* minor: SymfonySet - Add SimpleToComplexStringVariableFixer (#6423) +* minor: Update PHPStan (#6467) +* minor: extract CurlyBracesPositionFixer from BracesFixer (#6452) + +Changelog for v3.8.0 +-------------------- + +* bug #6322 PhpdocTypesFixer - fix recognizing callable (kubawerlos) +* bug #6331 ClassReferenceNameCasingFixer - Fix false hits (SpacePossum) +* bug #6333 BinaryOperatorSpacesFixer - Fix for alignment in `elseif` (paulbalandan, SpacePossum) +* bug #6337 PhpdocTypesFixer - fix recognising callable without return type (kubawerlos) +* feature #6286 DateTimeCreateFromFormatCallFixer - Introduction (liquid207) +* feature #6312 TypesSpacesFixer - add option for CS of catching multiple types of exceptions (SpacePossum) +* minor #6326 Bump migration sets used to PHP7.4 (SpacePossum) +* minor #6328 DX: .gitignore ASC file (keradus) + +Changelog for v3.7.0 +-------------------- + +* bug #6112 [BinaryOperatorSpacesFixer] Fix align of `=` inside calls of methods (VincentLanglet) +* bug #6279 ClassReferenceNameCasingFixer - Fix for double arrow (SpacePossum) +* bug #6280 Fix bunch of enum issues (SpacePossum) +* bug #6283 ClassReferenceNameCasingFixer - detect imports (SpacePossum) +* feature #5892 NewWithBracesFixer - option to remove braces (jrmajor) +* feature #6081 Allow multiline constructor arguments in an anonymous classes (jrmajor, SpacePossum) +* feature #6274 SingleLineCommentSpacingFixer - Introduction (SpacePossum) +* feature #6300 OrderedClassElementsFixer - handle enums (gharlan) +* feature #6304 NoTrailingCommaInSinglelineFunctionCallFixer - Introduction (SpacePossum) +* minor #6277 Add `is_scalar`, `sizeof`, `ini_get` in list of compiled functions (jderusse) +* minor #6284 ClassReferenceNameCasingFixer - Update doc (SpacePossum) +* minor #6289 PHP7.4 - clean up tests (SpacePossum) +* minor #6290 PHP7.4 - properties types (SpacePossum) +* minor #6291 PHP7.4 - remove run time checks (SpacePossum) +* minor #6292 PhpUnitDedicateAssertFixer - Fix more count cases (SpacePossum) +* minor #6294 PhpUnitDedicateAssertFixer - add assertInstanceOf support (SpacePossum) +* minor #6295 PhpUnitTestCaseIndicator - Check if PHPUnit-test class extends anothe… (SpacePossum) +* minor #6298 Fix checkbashisms download ans SCA violations (SpacePossum) +* minor #6301 BracesFixer - handle enums (gharlan) +* minor #6302 Bump checkbashisms version (kubawerlos) +* minor #6303 PHP8 - Utilize "get_debug_type" (SpacePossum) +* minor #6316 bump xdebug-handler (SpacePossum) +* minor #6327 bump polyfills (SpacePossum) + +Changelog for v3.6.0 +-------------------- + +* bug #6063 PhpdocTypesOrderFixer - Improve nested types support (ruudk, julienfalque) +* bug #6197 FullyQualifiedStrictTypesFixer - fix same classname is imported from … (SpacePossum) +* bug #6241 NoSuperfluousPhpdocTagsFixer - fix for reference and splat operator (kubawerlos) +* bug #6243 PhpdocTypesOrderFixer - fix for intersection types (kubawerlos) +* bug #6254 PhpUnitDedicateAssertFixer - remove `is_resource`. (drupol) +* bug #6264 TokensAnalyzer - fix isConstantInvocation detection for multiple exce… (SpacePossum) +* bug #6265 NullableTypeDeclarationForDefaultNullValueFixer - handle "readonly" a… (SpacePossum) +* bug #6266 SimplifiedIfReturnFixer - handle statement in loop without braces (SpacePossum) +* feature #6262 ClassReferenceNameCasingFixer - introduction (SpacePossum) +* feature #6267 NoUnneededImportAliasFixer - Introduction (SpacePossum) +* minor #6199 HeaderCommentFixer - support monolithic files with shebang (kubawerlos, keradus) +* minor #6231 Fix priority descriptions and tests. (SpacePossum) +* minor #6237 DX: Application - better display version when displaying gitSha (keradus) +* minor #6242 Annotation - improve on recognising types with reference and splat operator (kubawerlos) +* minor #6250 Tokens - optimize cache clear (SpacePossum) +* minor #6269 Docs: redo warnings in RST docs to fix issue on website docs (keradus) +* minor #6270 ClassReferenceNameCasingFixer - Add missing test cases for catch (SpacePossum) +* minor #6273 Add priority test (SpacePossum) + +Changelog for v3.5.0 +-------------------- + +* bug #6058 Fix `Tokens::insertSlices` not moving around all affected tokens (paulbalandan, SpacePossum) +* bug #6160 NonPrintableCharacterFixer - fix for when removing non-printable character break PHP syntax (kubawerlos) +* bug #6165 DeclareEqualNormalizeFixer - fix for declare having multiple directives (kubawerlos) +* bug #6170 NonPrintableCharacterFixer - fix for string in single quotes, having non-breaking space, linebreak, and single quote inside (kubawerlos) +* bug #6181 UseTransformer - Trait import in enum fix (PHP8.1) (SpacePossum) +* bug #6188 `PhpdocTo(Param|Property|Return)TypeFixer` - fix for type intersections (kubawerlos) +* bug #6202 SquareBraceTransformer - fix for destructing square brace after double arrow (kubawerlos) +* bug #6209 OrderedClassElementsFixer - PHP8.0 support abstract private methods in traits (SpacePossum) +* bug #6224 ArgumentsAnalyzer - support PHP8.1 readonly (SpacePossum) +* feature #4571 BlankLineBeforeStatementFixer - can now add blank lines before doc-comments (addiks, SpacePossum) +* feature #5953 GetClassToClassKeywordFixer - introduction (paulbalandan) +* minor #6108 Drop support for Symfony v4 (keradus) +* minor #6163 CI: update used PHP version (keradus) +* minor #6167 SingleSpaceAfterConstructFixer - allow multiline const (y_ahiru, SpacePossum) +* minor #6168 indexes -> indices (SpacePossum) +* minor #6171 Fix tests and CS (SpacePossum) +* minor #6172 DX: Tokens::insertSlices - groom code and fix tests (keradus) +* minor #6174 PhpdocAlignFixer: fix property-read/property-write descriptions not getting aligned (antichris) +* minor #6177 DX: chmod +x for benchmark.sh file (keradus) +* minor #6180 gitlab reporter - add fixed severity to match format (cbourreau) +* minor #6183 Simplify DiffConsoleFormatter (kubawerlos) +* minor #6184 Do not support array of patterns in Preg methods (kubawerlos) +* minor #6185 Upgrade PHPStan (kubawerlos) +* minor #6189 Finder - fix usage of ignoreDotFiles (kubawerlos) +* minor #6190 DX: DiffConsoleFormatter - escape - (keradus) +* minor #6194 Update Docker setup (julienfalque) +* minor #6196 clean ups (SpacePossum) +* minor #6198 DX: format dot files (kubawerlos) +* minor #6200 DX: Composer's branch-alias leftovers cleanup (kubawerlos) +* minor #6203 Bump required PHP to 7.4 (keradus) +* minor #6205 DX: bump PHPUnit to v9, PHPUnit bridge to v6 and Prophecy-PHPUnit to v2 (keradus) +* minor #6210 NullableTypeDeclarationForDefaultNullValueFixer - fix tests (HypeMC) +* minor #6212 bump year 2021 -> 2022 (SpacePossum) +* minor #6215 DX: Doctrine\Annotation\Tokens - fix phpstan violations (keradus) +* minor #6216 DX: Doctrine\Annotation\Tokens - drop unused methods (keradus) +* minor #6217 DX: lock SCA tools for PR builds (keradus) +* minor #6218 Use composer/xdebug-handler v3 (gharlan) +* minor #6222 Show runtime on version command (SpacePossum) +* minor #6229 Simplify Tokens::isMonolithicPhp tests (kubawerlos) +* minor #6232 Use expectNotToPerformAssertions where applicable (SpacePossum) +* minor #6233 Update Tokens::isMonolithicPhp (kubawerlos) +* minor #6236 Annotation - improve getting variable name (kubawerlos) + +Changelog for v3.4.0 +-------------------- + +* bug #6117 SingleSpaceAfterConstruct - handle before destructuring close brace (liquid207) +* bug #6122 NoMultilineWhitespaceAroundDoubleArrowFixer - must run before MethodArgumentSpaceFixer (kubawerlos) +* bug #6130 StrictParamFixer - must run before MethodArgumentSpaceFixer (kubawerlos) +* bug #6137 NewWithBracesFixer - must run before ClassDefinitionFixer (kubawerlos) +* bug #6139 PhpdocLineSpanFixer - must run before NoSuperfluousPhpdocTagsFixer (kubawerlos) +* bug #6143 OperatorLinebreakFixer - fix for alternative syntax (kubawerlos) +* bug #6159 ImportTransformer - fix for grouped constant and function imports (kubawerlos) +* bug #6161 NoUnreachableDefaultArgumentValueFixer - fix for attributes (kubawerlos) +* feature #5776 DX: test on PHP 8.1 (kubawerlos) +* feature #6152 PHP8.1 support (SpacePossum) +* minor #6095 Allow Symfony 6 (derrabus, keradus) +* minor #6107 Drop support of PHPUnit v7 dependency (keradus) +* minor #6109 Add return type to `DummyTestSplFileInfo::getRealPath()` (derrabus) +* minor #6115 Remove PHP 7.2 polyfill (derrabus) +* minor #6116 CI: remove installation of mbstring polyfill in build script, it's required dependency now (keradus) +* minor #6119 OrderedClassElementsFixer - PHPUnit `assert(Pre|Post)Conditions` methods support (meyerbaptiste) +* minor #6121 Use Tokens::ensureWhitespaceAtIndex to simplify code (kubawerlos) +* minor #6127 Remove 2nd parameter to XdebugHandler constructor (phil-davis) +* minor #6129 clean ups (SpacePossum) +* minor #6138 PHP8.1 - toString cannot return type hint void (SpacePossum) +* minor #6146 PHP 8.1: add new_in_initializers to PHP 8.1 integration test (keradus) +* minor #6147 DX: update composer-normalize (keradus) +* minor #6156 DX: drop hack for Prophecy incompatibility (keradus) + +Changelog for v3.3.1 +-------------------- + +* minor #6067 Bump minimum PHP version to 7.2 (keradus) + +Changelog for v3.3.0 +-------------------- + +* bug #6054 Utils - Add multibyte and UTF-8 support (paulbalandan) +* bug #6061 ModernizeStrposFixer - fix for negated with leading slash (kubawerlos) +* bug #6064 SquareBraceTransformer - fix detect array destructing in foreach (SpacePossum) +* bug #6082 PhpUnitDedicateAssertFixer must run before NoUnusedImportsFixer (kubawerlos) +* bug #6089 TokensAnalyzer.php - Fix T_ENCAPSED_AND_WHITESPACE handling in isBina… (SpacePossum) +* feature #5123 PhpdocTypesFixer - support generic types (kubawerlos) +* minor #5775 DX: run static code analysis on PHP 8.0 (kubawerlos) +* minor #6050 DX: TypeIntersectionTransformer - prove to not touch T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG (keradus) +* minor #6051 NoExtraBlankLinesFixer - Improve deprecation message (paulbalandan) +* minor #6060 DX: Add upgrade guide link when next Major is available (keradus) +* minor #6066 Clean ups (SpacePossum, kubawerlos) +* minor #6069 DX: cleanup stub file (keradus) +* minor #6070 Update UPGRADE-v3.md with php_unit_test_annotation/case deprecation (kubawerlos) +* minor #6072 Update usage doc to reflect change to PSR12 default. (hannob, keradus) +* minor #6084 Change: Remove __constructor() from RuleSetDescriptionInterface (niklam) +* minor #6085 Dx: reuse WhitespacesAnalyzer::detectIndent (kubawerlos) +* minor #6087 AbstractProxyFixer - more tests (SpacePossum) + +Changelog for v3.2.1 +--------------------- + +experimental release + +* Require PHP 7.2 + +Changelog for v3.2.0 +-------------------- + +* bug #5809 FunctionsAnalyzer - fix for recognizing global functions in attributes (kubawerlos) +* bug #5909 NativeFunctionCasingFixer - fix for attributes and imported functions (kubawerlos) +* bug #5920 ClassAttributesSeparationFixer - fixes & enhancements (SpacePossum) +* bug #5923 TypeAlternationTransformer - fix for promoted properties (kubawerlos) +* bug #5938 NoAliasFunctionsFixer - remove dir -> getdir mapping (SpacePossum) +* bug #5941 TokensAnalyzer - isAnonymousClass bug on PHP8 (SpacePossum) +* bug #5942 TokensAnalyzer - isConstantInvocation PHP 8 issue (SpacePossum) +* bug #5943 NoUnusedImportsFixer - use in attribute (SpacePossum) +* bug #5955 Fixed `class_attributes_separation` processing class with multiple trait imports (GrahamCampbell) +* bug #5977 LowercaseStaticReference - SingleClassElementPerStatement - union types (SpacePossum) +* bug #5984 RegularCallableCallFixer must run before NativeFunctionInvocationFixer (kubawerlos) +* bug #5986 CurlyBraceTransformer - count T_CURLY_OPEN itself as level as well (SpacePossum) +* bug #5989 NoAliasFunctionsFixer - Correct mapping (weshooper) +* bug #6004 SwitchContinueToBreakFixer - Fix candidate check (SpacePossum) +* bug #6005 CommentsAnalyzer - before static call (SpacePossum) +* bug #6007 YodaStyleFixer - PHP8 named arguments support (liquid207) +* bug #6015 CommentsAnalyzer - constructor property promotion support (liquid207) +* bug #6020 RegularCallableCallFixer - case insensitive fixing (SpacePossum) +* bug #6037 PhpdocLineSpanFixer - do not crash on trait imports (SpacePossum) +* feature #4834 AssignNullCoalescingToCoalesceEqualFixer - introduction (SpacePossum) +* feature #5754 ModernizeStrposFixer - introduction (derrabus, SpacePossum, keradus) +* feature #5858 EmptyLoopConditionFixer - introduction (SpacePossum) +* feature #5967 PHP8.1 - type "never" support (SpacePossum) +* feature #5968 PHP8.1 - "readonly" property modifier support (SpacePossum) +* feature #5970 IntegerLiteralCaseFixer - introduction (SpacePossum) +* feature #5971 PHP8.1 - Explicit octal integer literal notation (SpacePossum) +* feature #5997 NoSuperfluousPhpdocTagsFixer - Add union types support (julienfalque) +* feature #6026 TypeIntersectionTransformer - introduction (kubawerlos, SpacePossum) +* feature #6031 NoSpaceAroundDoubleColonFixer - introduction (SpacePossum) +* feature #6047 StringLengthToEmptyFixer - introduction (SpacePossum) +* minor #5773 NoAlternativeSyntaxFixer - Add option to not fix non-monolithic PHP code (paulbalandan) +* minor #5887 Detect renamed rules in configuration resolver (shakaran) +* minor #5901 DX: update PHPStan (kubawerlos) +* minor #5906 Remove references to PHP 7.0 in tests (with updates) (kubawerlos) +* minor #5918 Remove PHP version specific code sample constraint when not needed (kubawerlos) +* minor #5924 PSR12 - ClassDefinition - space_before_parenthesis (SpacePossum) +* minor #5925 DX: ProjectCodeTest - fix detection by testExpectedInputOrder (keradus) +* minor #5926 DX: remove not needed requirements from fixtures (kubawerlos) +* minor #5927 Symfonyset - EmptyLoopBody (SpacePossum) +* minor #5928 PhpdocTo*TypeFixer - add more test cases (keradus) +* minor #5929 Remove not needed PHP version checks (kubawerlos) +* minor #5930 simplify code, more tests (SpacePossum) +* minor #5931 logo copyright - bump year (SpacePossum) +* minor #5932 Extract ControlStructureContinuationPositionFixer from BracesFixer (julienfalque) +* minor #5933 Consistency invalid configuration exception for test (shakaran) +* minor #5934 Add return types (SpacePossum) +* minor #5949 Removed PHP 5 exception catch (GrahamCampbell) +* minor #5952 ClassAttributesSeparationFixer - Re-add omitted `only_if_meta` option (paulbalandan) +* minor #5957 Keep PHPStan cache between Docker runs (julienfalque) +* minor #5958 Fix STDIN test when path is one level deep (julienfalque) +* minor #5959 SymfonySet - add EmptyLoopConditionFixer (SpacePossum) +* minor #5961 Remove duplicated method (julienfalque) +* minor #5962 DX: Add return types (kubawerlos) +* minor #5963 DX: extract config for special CI jobs (keradus) +* minor #5964 DX: use modernize_strpos (keradus) +* minor #5965 CI: don't try to execute jobs with Symfony:^3 (keradus) +* minor #5972 PHP8.1 - FirstClassCallable (SpacePossum) +* minor #5973 PHP8.1 - "final const" support (SpacePossum) +* minor #5975 Tree shake PHP8.1 PRs (SpacePossum) +* minor #5978 PHP8.1 - Enum (start) (SpacePossum) +* minor #5982 Fix test warning (SpacePossum) +* minor #5987 PHP8.1 - Enum (start) (SpacePossum) +* minor #5995 Fix link to Code Climate SPEC.md in GitlabReporter (astehlik) +* minor #5996 Fix URL to Doctrine Annotations documentation (astehlik) +* minor #6000 Prevent PHP CS Fixer from fixing PHPStan cache files (julienfalque) +* minor #6006 SCA/utilize PHP8.1 (SpacePossum) +* minor #6008 SCA (SpacePossum) +* minor #6010 SCA (SpacePossum) +* minor #6011 NoSuperfluousPhpdocTagsFixer - Remove superfluous annotation `@abstract` and `@final` (liquid207, SpacePossum) +* minor #6018 PhpdocLineSpan - Allow certain types to be ignored (devfrey) +* minor #6019 Improve test coverage (SpacePossum) +* minor #6021 Linter/*Exception - Tag as final (SpacePossum) +* minor #6023 OrderedClassElementsFixer - PHP8.1 readonly properties support (SpacePossum) +* minor #6027 MbStrFunctionsFixer - more details about risky (SpacePossum) +* minor #6028 BinaryOperatorSpacesFixer - list all operators in doc (SpacePossum) +* minor #6029 PhpUnitDedicateAssertFixer - add "assertStringContainsString" and "as… (SpacePossum) +* minor #6030 SingleSpaceAfterConstructFixer - Add `switch` support (SpacePossum) +* minor #6033 ArgumentsAnalyzerTest - add more tests (SpacePossum) +* minor #6034 Cleanup tests for PHP 7.0 and 7.1 (SpacePossum) +* minor #6035 Documentation generation split up and add list. (SpacePossum) +* minor #6048 Fix "can not" spelling (mvorisek) + +Changelog for v3.1.0 +-------------------- + +* feature #5572 PhpdocToCommentFixer - Add `ignored_tags` option (VincentLanglet) +* feature #5588 NoAliasFunctionsFixer - Add more function aliases (danog) +* feature #5704 ClassAttributesSeparationFixer - Introduce `only_if_meta` spacing option (paulbalandan) +* feature #5734 TypesSpacesFixer - Introduction (kubawerlos) +* feature #5745 EmptyLoopBodyFixer - introduction (SpacePossum, keradus) +* feature #5751 Extract DeclareParenthesesFixer from BracesFixer (julienfalque, keradus) +* feature #5877 ClassDefinitionFixer - PSR12 for anonymous class (SpacePossum) +* minor #5875 EmptyLoopBodyFixer - NoTrailingWhitespaceFixer - priority test (SpacePossum) +* minor #5914 Deprecate ClassKeywordRemoveFixer (kubawerlos) + +Changelog for v3.0.3 +-------------------- + +* bug #4927 PhpdocAlignFixer - fix for whitespace in type (kubawerlos) +* bug #5720 NoUnusedImportsFixer - Fix undetected unused imports when type mismatch (julienfalque, SpacePossum) +* bug #5806 DoctrineAnnotationFixer - Add template to ignored_tags (akalineskou) +* bug #5849 PhpdocTagTypeFixer - must not remove inlined tags within other tags (boesing) +* bug #5853 BracesFixer - handle alternative short foreach with if (SpacePossum) +* bug #5855 GlobalNamespaceImportFixer - fix for attributes imported as constants (kubawerlos) +* bug #5881 SelfUpdateCommand - fix link to UPGRADE docs (keradus) +* bug #5884 CurlyBraceTransformer - fix handling dynamic property with string with variable (kubawerlos, keradus) +* bug #5912 TypeAlternationTransformer - fix for "callable" type (kubawerlos) +* bug #5913 SingleSpaceAfterConstructFixer - improve comma handling (keradus) +* minor #5829 DX: Fix SCA with PHPMD (paulbalandan) +* minor #5838 PHP7 - use spaceship (SpacePossum, keradus) +* minor #5848 Docs: update PhpStorm integration link (keradus) +* minor #5856 Add AttributeAnalyzer (kubawerlos) +* minor #5857 DX: PHPMD - exclude fixtures (keradus) +* minor #5859 Various fixes (kubawerlos) +* minor #5864 DX: update dev tools (kubawerlos) +* minor #5876 AttributeTransformerTest - add more tests (SpacePossum) +* minor #5879 Update UPGRADE-v3.md adding relative links (shakaran, keradus) +* minor #5882 Docs: don't use v2 for installation example (keradus) +* minor #5883 Docs: typo (brianteeman, keradus) +* minor #5890 DX: use PHP 8.1 polyfill (keradus) +* minor #5902 Remove references to PHP 7.0 in tests (only removing lines) (kubawerlos) +* minor #5905 DX: Use "yield from" in tests (kubawerlos, keradus) +* minor #5917 Use `@PHP71Migration` rules (kubawerlos, keradus) + +Changelog for v3.0.2 +-------------------- + +* bug #5816 FullyQualifiedStrictTypesFixer - fix for union types (kubawerlos, keradus) +* bug #5835 PhpdocTypesOrderFixer: fix for array shapes (kubawerlos) +* bug #5837 SingleImportPerStatementFixer - fix const and function imports (SpacePossum) +* bug #5844 PhpdocTypesOrderFixer: handle callable() type (Slamdunk) +* minor #5839 DX: automate checking 7.0 types on project itself (keradus) +* minor #5840 DX: drop v2 compatible config in project itself (keradus) + +Changelog for v3.0.1 +-------------------- + +* bug #5395 PhpdocTagTypeFixer: Do not modify array shapes (localheinz, julienfalque) +* bug #5678 UseArrowFunctionsFixer - fix for return without value (kubawerlos) +* bug #5679 PhpUnitNamespacedFixer - do not try to fix constant usage (kubawerlos) +* bug #5681 RegularCallableCallFixer - fix for function name with escaped slash (kubawerlos) +* bug #5687 FinalInternalClassFixer - fix for annotation with space after "@" (kubawerlos) +* bug #5688 ArrayIndentationFixer - fix for really long arrays (kubawerlos) +* bug #5690 PhpUnitNoExpectationAnnotationFixer - fix "expectedException" annotation with message below (kubawerlos) +* bug #5693 YodaStyleFixer - fix for assignment operators (kubawerlos) +* bug #5697 StrictParamFixer - fix for method definition (kubawerlos) +* bug #5702 CommentToPhpdocFixer - fix for single line comments starting with more than 2 slashes (kubawerlos) +* bug #5703 DateTimeImmutableFixer - fix for method definition (kubawerlos) +* bug #5718 VoidReturnFixer - do not break syntax with magic methods (kubawerlos) +* bug #5727 SingleSpaceAfterConstructFixer - Add support for `namespace` (julienfalque) +* bug #5730 Fix transforming deprecations into exceptions (julienfalque) +* bug #5738 TokensAnalyzer - fix for union types (kubawerlos) +* bug #5741 Fix constant invocation detection cases (kubawerlos) +* bug #5769 Fix priority between `phpdoc_to_property_type` and `no_superfluous_phpdoc_tags` (julienfalque) +* bug #5774 FunctionsAnalyzer::isTheSameClassCall - fix for $this with double colon following (kubawerlos) +* bug #5779 SingleLineThrowFixer - fix for throw in match (kubawerlos) +* bug #5781 ClassDefinition - fix for anonymous class with trailing comma (kubawerlos) +* bug #5783 StaticLambdaFixer - consider parent:: as a possible reference to $this (fancyweb) +* bug #5791 NoBlankLinesAfterPhpdoc - Add T_NAMESPACE in array of forbidden successors (paulbalandan) +* bug #5799 TypeAlternationTransformer - fix for multiple function parameters (kubawerlos) +* bug #5804 NoBreakCommentFixer - fix for "default" in "match" (kubawerlos) +* bug #5805 SingleLineCommentStyleFixer - run after HeaderCommentFixer (kubawerlos) +* bug #5817 NativeFunctionTypeDeclarationCasingFixer - fix for union types (kubawerlos) +* bug #5823 YodaStyleFixer - yield support (SpacePossum) +* minor #4914 Improve PHPDoc types support (julienfalque, keradus) +* minor #5592 Fix checking for default config used in rule sets (kubawerlos) +* minor #5675 Docs: extend Upgrade Guide (keradus) +* minor #5680 DX: benchmark.sh - ensure deps are updated to enable script working across less-similar branches (keradus) +* minor #5689 Calculate code coverage on PHP 8 (kubawerlos) +* minor #5694 DX: fail on risky tests (kubawerlos) +* minor #5695 Utils - save only unique deprecations to avoid memory issues (PetrHeinz) +* minor #5710 [typo] add correct backquotes (PhilETaylor) +* minor #5711 Fix doc, "run-in" show-progress option is no longer present (mvorisek) +* minor #5713 Upgrade-Guide: fix typo (staabm) +* minor #5717 Run migration rules on PHP 8 (kubawerlos, keradus) +* minor #5721 Fix reStructuredText markup (julienfalque) +* minor #5725 Update LICENSE (exussum12) +* minor #5731 CI - Fix checkbashisms installation (julienfalque) +* minor #5736 Remove references to PHP 5.6 (kubawerlos, keradus) +* minor #5739 DX: more typehinting (keradus) +* minor #5740 DX: more type-related docblocks (keradus) +* minor #5746 Config - Improve deprecation message with details (SpacePossum) +* minor #5747 RandomApiMigrationFixer - better docs and better "random_int" support (SpacePossum) +* minor #5748 Updated the link to netbeans plugins page (cyberguroo) +* minor #5750 Test all const are in uppercase (SpacePossum) +* minor #5752 NoNullPropertyInitializationFixer - fix static properties as well (HypeMC) +* minor #5756 Fix rule sets descriptions (kubawerlos) +* minor #5761 Fix links in custom rules documentation (julienfalque) +* minor #5771 doc(config): change set's name (Kocal) +* minor #5777 DX: update PHPStan (kubawerlos) +* minor #5789 DX: update PHPStan (kubawerlos) +* minor #5808 Update PHPStan to 0.12.92 (kubawerlos) +* minor #5813 Docs: point to v3 in installation description (Jimbolino) +* minor #5824 Deprecate v2 (keradus) +* minor #5825 DX: update checkbashisms to v2.21.3 (keradus) +* minor #5826 SCA: check both composer files (keradus) +* minor #5827 ClassAttributesSeparationFixer - Add `trait_import` support (SpacePossum) +* minor #5831 DX: fix SCA violations (keradus) + +Changelog for v3.0.0 +-------------------- + +* bug #5164 Differ - surround file name with double quotes if it contains spacing. (SpacePossum) +* bug #5560 PSR2: require visibility only for properties and methods (kubawerlos) +* bug #5576 ClassAttributesSeparationFixer: do not allow using v2 config (kubawerlos) +* feature #4979 Pass file to differ (paulhenri-l, SpacePossum) +* minor #3374 show-progress option: drop run-in and estimating, rename estimating-max to dots (keradus) +* minor #3375 Fixers - stop exposing extra properties/consts (keradus) +* minor #3376 Tokenizer - remove deprecations and legacy mode (keradus) +* minor #3377 rules - change default options (keradus) +* minor #3378 SKIP_LINT_TEST_CASES - drop env (keradus) +* minor #3379 MethodArgumentSpaceFixer - fixSpace is now private (keradus) +* minor #3380 rules - drop rootless configurations (keradus) +* minor #3381 rules - drop deprecated configurations (keradus) +* minor #3382 DefinedFixerInterface - incorporate into FixerInterface (keradus) +* minor #3383 FixerDefinitionInterface - drop getConfigurationDescription and getDefaultConfiguration (keradus) +* minor #3384 diff-format option: drop sbd diff, use udiffer by default, drop SebastianBergmannDiffer and SebastianBergmannShortDiffer classes (keradus) +* minor #3385 ConfigurableFixerInterface::configure - param is now not nullable and not optional (keradus) +* minor #3386 ConfigurationDefinitionFixerInterface - incorporate into ConfigurableFixerInterface (keradus) +* minor #3387 FixCommand - forbid passing 'config' and 'rules' options together (keradus) +* minor #3388 Remove Helpers (keradus) +* minor #3389 AccessibleObject - drop class (keradus) +* minor #3390 Drop deprecated rules: blank_line_before_return, hash_to_slash_comment, method_separation, no_extra_consecutive_blank_lines, no_multiline_whitespace_before_semicolons and pre_increment (keradus) +* minor #3456 AutoReview - drop references to removed rule (keradus) +* minor #3659 use php-cs-fixer/diff ^2.0 (SpacePossum) +* minor #3681 CiIntegrationTest - fix incompatibility from 2.x line (keradus) +* minor #3740 NoUnusedImportsFixer - remove SF exception (SpacePossum) +* minor #3771 UX: always set error_reporting in entry file, not Application (keradus) +* minor #3922 Make some more classes final (ntzm, SpacePossum) +* minor #3995 Change default config of native_function_invocation (dunglas, SpacePossum) +* minor #4432 DX: remove empty sets from RuleSet (kubawerlos) +* minor #4489 Fix ruleset @PHPUnit50Migration:risky (kubawerlos) +* minor #4620 DX: cleanup additional, not used parameters (keradus) +* minor #4666 Remove deprecated rules: lowercase_constants, php_unit_ordered_covers, silenced_deprecation_error (keradus) +* minor #4697 Remove deprecated no_short_echo_tag rule (julienfalque) +* minor #4851 fix phpstan on 3.0 (SpacePossum) +* minor #4901 Fix SCA (SpacePossum) +* minor #5069 Fixed failing tests on 3.0 due to unused import after merge (GrahamCampbell) +* minor #5096 NativeFunctionInvocationFixer - BacktickToShellExecFixer - fix integration test (SpacePossum) +* minor #5171 Fix test (SpacePossum) +* minor #5245 Fix CI for 3.0 line (keradus) +* minor #5351 clean ups (SpacePossum) +* minor #5364 DX: Do not display runtime twice on 3.0 line (keradus) +* minor #5412 3.0 - cleanup (SpacePossum, keradus) +* minor #5417 Further BC cleanup for 3.0 (keradus) +* minor #5418 Drop src/Test namespace (keradus) +* minor #5436 Drop mapping of strings to boolean option other than yes/no (keradus) +* minor #5440 Change default ruleset to PSR-12 (keradus) +* minor #5477 Drop diff-format (keradus) +* minor #5478 Docs: Cleanup UPGRADE markdown files (keradus) +* minor #5479 ArraySyntaxFixer, ListSyntaxFixer - change default syntax to short (keradus) +* minor #5480 Tokens::findBlockEnd - drop deprecated argument (keradus) +* minor #5485 ClassAttributesSeparationFixer - drop deprecated flat list configuration (keradus) +* minor #5486 CI: drop unused env variables (keradus) +* minor #5488 Do not distribute documentation (szepeviktor) +* minor #5513 DX: Tokens::warnPhp8SplFixerArrayChange - drop unused method (keradus) +* minor #5520 DX: Drop IsIdenticalConstraint (keradus) +* minor #5521 DX: apply rules configuration cleanups for PHP 7.1+ (keradus) +* minor #5524 DX: drop support of very old deps (keradus) +* minor #5525 Drop phpunit-legacy-adapter (keradus) +* minor #5527 Bump required PHP to 7.1 (keradus) +* minor #5529 DX: bump required PHPUnit to v7+ (keradus) +* minor #5532 Apply PHP 7.1 typing (keradus) +* minor #5541 RuleSet - disallow null usage to disable the rule (keradus) +* minor #5555 DX: further typing improvements (keradus) +* minor #5562 Fix table row rendering for default values of array_syntax and list_syntax (derrabus) +* minor #5608 DX: new cache filename (keradus) +* minor #5609 Forbid old config filename usage (keradus) +* minor #5638 DX: remove Utils::calculateBitmask (keradus) +* minor #5641 DX: use constants for PHPUnit version on 3.0 line (keradus) +* minor #5643 FixCommand - simplify help (keradus) +* minor #5644 Token::toJson() - remove parameter (keradus) +* minor #5645 DX: YodaStyleFixerTest - fix CI (keradus) +* minor #5649 DX: YodaStyleFixerTest - fix 8.0 compat (keradus) +* minor #5650 DX: FixCommand - drop outdated/duplicated docs (keradus) +* minor #5656 DX: mark some constants as internal or private (keradus) +* minor #5657 DX: convert some properties to constants (keradus) +* minor #5669 Remove TrailingCommaInMultilineArrayFixer (kubawerlos, keradus) + +Changelog for v2.19.3 +--------------------- + +* minor #6060 DX: Add upgrade guide link when next Major is available (keradus) + +Changelog for v2.19.2 +--------------------- + +* bug #5881 SelfUpdateCommand - fix link to UPGRADE docs (keradus) + +Changelog for v2.19.1 +--------------------- + +* bug #5395 PhpdocTagTypeFixer: Do not modify array shapes (localheinz, julienfalque) +* bug #5678 UseArrowFunctionsFixer - fix for return without value (kubawerlos) +* bug #5679 PhpUnitNamespacedFixer - do not try to fix constant usage (kubawerlos) +* bug #5681 RegularCallableCallFixer - fix for function name with escaped slash (kubawerlos) +* bug #5687 FinalInternalClassFixer - fix for annotation with space after "@" (kubawerlos) +* bug #5688 ArrayIndentationFixer - fix for really long arrays (kubawerlos) +* bug #5690 PhpUnitNoExpectationAnnotationFixer - fix "expectedException" annotation with message below (kubawerlos) +* bug #5693 YodaStyleFixer - fix for assignment operators (kubawerlos) +* bug #5697 StrictParamFixer - fix for method definition (kubawerlos) +* bug #5702 CommentToPhpdocFixer - fix for single line comments starting with more than 2 slashes (kubawerlos) +* bug #5703 DateTimeImmutableFixer - fix for method definition (kubawerlos) +* bug #5718 VoidReturnFixer - do not break syntax with magic methods (kubawerlos) +* bug #5727 SingleSpaceAfterConstructFixer - Add support for `namespace` (julienfalque) +* bug #5730 Fix transforming deprecations into exceptions (julienfalque) +* bug #5738 TokensAnalyzer - fix for union types (kubawerlos) +* bug #5741 Fix constant invocation detection cases (kubawerlos) +* bug #5769 Fix priority between `phpdoc_to_property_type` and `no_superfluous_phpdoc_tags` (julienfalque) +* bug #5774 FunctionsAnalyzer::isTheSameClassCall - fix for $this with double colon following (kubawerlos) +* bug #5779 SingleLineThrowFixer - fix for throw in match (kubawerlos) +* bug #5781 ClassDefinition - fix for anonymous class with trailing comma (kubawerlos) +* bug #5783 StaticLambdaFixer - consider parent:: as a possible reference to $this (fancyweb) +* bug #5791 NoBlankLinesAfterPhpdoc - Add T_NAMESPACE in array of forbidden successors (paulbalandan) +* bug #5799 TypeAlternationTransformer - fix for multiple function parameters (kubawerlos) +* bug #5804 NoBreakCommentFixer - fix for "default" in "match" (kubawerlos) +* bug #5805 SingleLineCommentStyleFixer - run after HeaderCommentFixer (kubawerlos) +* bug #5817 NativeFunctionTypeDeclarationCasingFixer - fix for union types (kubawerlos) +* bug #5823 YodaStyleFixer - yield support (SpacePossum) +* minor #4914 Improve PHPDoc types support (julienfalque, keradus) +* minor #5680 DX: benchmark.sh - ensure deps are updated to enable script working across less-similar branches (keradus) +* minor #5689 Calculate code coverage on PHP 8 (kubawerlos) +* minor #5694 DX: fail on risky tests (kubawerlos) +* minor #5695 Utils - save only unique deprecations to avoid memory issues (PetrHeinz) +* minor #5710 [typo] add correct backquotes (PhilETaylor) +* minor #5717 Run migration rules on PHP 8 (kubawerlos, keradus) +* minor #5721 Fix reStructuredText markup (julienfalque) +* minor #5725 Update LICENSE (exussum12) +* minor #5731 CI - Fix checkbashisms installation (julienfalque) +* minor #5740 DX: more type-related docblocks (keradus) +* minor #5746 Config - Improve deprecation message with details (SpacePossum) +* minor #5747 RandomApiMigrationFixer - better docs and better "random_int" support (SpacePossum) +* minor #5748 Updated the link to netbeans plugins page (cyberguroo) +* minor #5750 Test all const are in uppercase (SpacePossum) +* minor #5752 NoNullPropertyInitializationFixer - fix static properties as well (HypeMC) +* minor #5756 Fix rule sets descriptions (kubawerlos) +* minor #5761 Fix links in custom rules documentation (julienfalque) +* minor #5777 DX: update PHPStan (kubawerlos) +* minor #5789 DX: update PHPStan (kubawerlos) +* minor #5808 Update PHPStan to 0.12.92 (kubawerlos) +* minor #5824 Deprecate v2 (keradus) +* minor #5825 DX: update checkbashisms to v2.21.3 (keradus) +* minor #5826 SCA: check both composer files (keradus) +* minor #5827 ClassAttributesSeparationFixer - Add `trait_import` support (SpacePossum) + +Changelog for v2.19.0 +--------------------- + +* feature #4238 TrailingCommaInMultilineFixer - introduction (kubawerlos) +* feature #4592 PhpdocToPropertyTypeFixer - introduction (julienfalque) +* feature #5390 feature #4024 added a `list-files` command (clxmstaab, keradus) +* feature #5635 Add list-sets command (keradus) +* feature #5674 UX: Display deprecations to end-user (keradus) +* minor #5601 Always stop when "PHP_CS_FIXER_FUTURE_MODE" is used (kubawerlos) +* minor #5607 DX: new config filename (keradus) +* minor #5613 DX: UtilsTest - add missing teardown (keradus) +* minor #5631 DX: config deduplication (keradus) +* minor #5633 fix typos (staabm) +* minor #5642 Deprecate parameter of Token::toJson() (keradus) +* minor #5672 DX: do not test deprecated fixer (kubawerlos) + +Changelog for v2.18.7 +--------------------- + +* bug #5593 SingleLineThrowFixer - fix handling anonymous classes (kubawerlos) +* bug #5654 SingleLineThrowFixer - fix for match expression (kubawerlos) +* bug #5660 TypeAlternationTransformer - fix for "array" type in type alternation (kubawerlos) +* bug #5665 NullableTypeDeclarationForDefaultNullValueFixer - fix for nullable with attribute (kubawerlos) +* bug #5670 PhpUnitNamespacedFixer - do not try to fix constant (kubawerlos) +* bug #5671 PhpdocToParamTypeFixer - do not change function call (kubawerlos) +* bug #5673 GroupImportFixer - Fix failing case (julienfalque) +* minor #4591 Refactor conversion of PHPDoc to type declarations (julienfalque, keradus) +* minor #5611 DX: use method expectDeprecation from Symfony Bridge instead of annotation (kubawerlos) +* minor #5658 DX: use constants in tests for Fixer configuration (keradus) +* minor #5661 DX: remove PHPStan exceptions for "tests" from phpstan.neon (kubawerlos) +* minor #5662 Change wording from "merge" to "intersect" (jschaedl) +* minor #5663 DX: do not abuse "inheritdoc" tag (kubawerlos) +* minor #5664 DX: code grooming (keradus) + +Changelog for v2.18.6 +--------------------- + +* bug #5586 Add support for nullsafe object operator ("?->") (kubawerlos) +* bug #5597 Tokens - fix for checking block edges (kubawerlos) +* bug #5604 Custom annotations @type changed into @var (Leprechaunz) +* bug #5606 DoctrineAnnotationBracesFixer false positive (Leprechaunz) +* bug #5610 BracesFixer - fix braces of match expression (Leprechaunz) +* bug #5615 GroupImportFixer severely broken (Leprechaunz) +* bug #5617 ClassAttributesSeparationFixer - fix for using visibility for class elements (kubawerlos) +* bug #5618 GroupImportFixer - fix removal of import type when mixing multiple types (Leprechaunz) +* bug #5622 Exclude Doctrine documents from final fixer (ossinkine) +* bug #5630 PhpdocTypesOrderFixer - handle complex keys (Leprechaunz) +* minor #5554 DX: use tmp file in sys_temp_dir for integration tests (keradus) +* minor #5564 DX: make integration tests matching entries in FixerFactoryTest (kubawerlos) +* minor #5603 DX: DocumentationGenerator - no need to re-configure Differ (keradus) +* minor #5612 DX: use ::class whenever possible (kubawerlos) +* minor #5619 DX: allow XDebugHandler v2 (keradus) +* minor #5623 DX: when displaying app version, don't put extra space if there is no CODENAME available (keradus) +* minor #5626 DX: update PHPStan and way of ignoring flickering PHPStan exception (keradus) +* minor #5629 DX: fix CiIntegrationTest (keradus) +* minor #5636 DX: remove 'create' method in internal classes (keradus) +* minor #5637 DX: do not calculate bitmap via helper anymore (keradus) +* minor #5639 Move fix reports (classes and schemas) (keradus) +* minor #5640 DX: use constants for PHPUnit version (keradus) +* minor #5646 Cleanup YodaStyleFixerTest (kubawerlos) + +Changelog for v2.18.5 +--------------------- + +* bug #5561 NoMixedEchoPrintFixer: fix for conditions without curly brackets (kubawerlos) +* bug #5563 Priority fix: SingleSpaceAfterConstructFixer must run before BracesFixer (kubawerlos) +* bug #5567 Fix order of BracesFixer and ClassDefinitionFixer (Daeroni) +* bug #5596 NullableTypeTransformer - fix for attributes (kubawerlos, jrmajor) +* bug #5598 GroupImportFixer - fix breaking code when fixing root classes (Leprechaunz) +* minor #5571 DX: add test to make sure SingleSpaceAfterConstructFixer runs before FunctionDeclarationFixer (kubawerlos) +* minor #5577 Extend priority test for "class_definition" vs "braces" (kubawerlos) +* minor #5585 DX: make doc examples prettier (kubawerlos) +* minor #5590 Docs: HeaderCommentFixer - document example how to remove header comment (keradus) +* minor #5602 DX: regenerate docs (keradus) + +Changelog for v2.18.4 +--------------------- + +* bug #4085 Priority: AlignMultilineComment should run before every PhpdocFixer (dmvdbrugge) +* bug #5421 PsrAutoloadingFixer - Fix PSR autoloading outside configured directory (kelunik, keradus) +* bug #5464 NativeFunctionInvocationFixer - PHP 8 attributes (HypeMC, keradus) +* bug #5548 NullableTypeDeclarationForDefaultNullValueFixer - fix handling promoted properties (jrmajor, keradus) +* bug #5550 TypeAlternationTransformer - fix for typed static properties (kubawerlos) +* bug #5551 ClassAttributesSeparationFixer - fix for properties with type alternation (kubawerlos, keradus) +* bug #5552 DX: test relation between function_declaration and method_argument_space (keradus) +* minor #5540 DX: RuleSet - convert null handling to soft-warning (keradus) +* minor #5545 DX: update checkbashisms (keradus) + +Changelog for v2.18.3 +--------------------- + +* bug #5484 NullableTypeDeclarationForDefaultNullValueFixer - handle mixed pseudotype (keradus) +* minor #5470 Disable CI fail-fast (mvorisek) +* minor #5491 Support php8 static return type for NoSuperfluousPhpdocTagsFixer (tigitz) +* minor #5494 BinaryOperatorSpacesFixer - extend examples (keradus) +* minor #5499 DX: add TODOs for PHP requirements cleanup (keradus) +* minor #5500 DX: Test that Transformers are adding only CustomTokens that they define and nothing else (keradus) +* minor #5507 Fix quoting in exception message (gquemener) +* minor #5514 DX: PHP 7.0 integration test - solve TODO for random_api_migration usage (keradus) +* minor #5515 DX: do not override getConfigurationDefinition (keradus) +* minor #5516 DX: AbstractDoctrineAnnotationFixer - no need for import aliases (keradus) +* minor #5518 DX: minor typing and validation fixes (keradus) +* minor #5522 Token - add handling json_encode crash (keradus) +* minor #5523 DX: EregToPregFixer - fix sorting (keradus) +* minor #5528 DX: code cleanup (keradus) + +Changelog for v2.18.2 +--------------------- + +* bug #5466 Fix runtime check of PHP version (keradus) +* minor #4250 POC Tokens::insertSlices (keradus) + +Changelog for v2.18.1 +--------------------- + +* bug #5447 switch_case_semicolon_to_colon should skip match/default statements (derrabus) +* bug #5453 SingleSpaceAfterConstructFixer - better handling of closing parenthesis and brace (keradus) +* bug #5454 NullableTypeDeclarationForDefaultNullValueFixer - support property promotion via constructor (keradus) +* bug #5455 PhpdocToCommentFixer - add support for attributes (keradus) +* bug #5462 NullableTypeDeclarationForDefaultNullValueFixer - support union types (keradus) +* minor #5444 Fix PHP version number in PHP54MigrationSet description (jdreesen, keradus) +* minor #5445 DX: update usage of old TraversableContains in tests (keradus) +* minor #5456 DX: Fix CiIntegrationTest (keradus) +* minor #5457 CI: fix params order (keradus) +* minor #5458 CI: fix migration workflow (keradus) +* minor #5459 DX: cleanup PHP Migration rulesets (keradus) + +Changelog for v2.18.0 +--------------------- + +* feature #4943 Add PSR12 ruleset (julienfalque, keradus) +* feature #5426 Update Symfony ruleset (keradus) +* feature #5428 Add/Change PHP.MigrationSet to update array/list syntax to short one (keradus) +* minor #5441 Allow execution under PHP 8 (keradus) + +Changelog for v2.17.5 +--------------------- + +* bug #5447 switch_case_semicolon_to_colon should skip match/default statements (derrabus) +* bug #5453 SingleSpaceAfterConstructFixer - better handling of closing parenthesis and brace (keradus) +* bug #5454 NullableTypeDeclarationForDefaultNullValueFixer - support property promotion via constructor (keradus) +* bug #5455 PhpdocToCommentFixer - add support for attributes (keradus) +* bug #5462 NullableTypeDeclarationForDefaultNullValueFixer - support union types (keradus) +* minor #5445 DX: update usage of old TraversableContains in tests (keradus) +* minor #5456 DX: Fix CiIntegrationTest (keradus) +* minor #5457 CI: fix params order (keradus) +* minor #5459 DX: cleanup PHP Migration rulesets (keradus) + +Changelog for v2.17.4 +--------------------- + +* bug #5379 PhpUnitMethodCasingFixer - Do not modify class name (localheinz) +* bug #5404 NullableTypeTransformer - constructor property promotion support (Wirone) +* bug #5433 PhpUnitTestCaseStaticMethodCallsFixer - fix for abstract static method (kubawerlos) +* minor #5234 DX: Add Docker dev setup (julienfalque, keradus) +* minor #5391 PhpdocOrderByValueFixer - Add additional annotations to sort (localheinz) +* minor #5392 PhpdocScalarFixer - Fix description (localheinz) +* minor #5397 NoExtraBlankLinesFixer - PHP8 throw support (SpacePossum) +* minor #5399 Add PHP8 integration test (keradus) +* minor #5405 TypeAlternationTransformer - add support for PHP8 (SpacePossum) +* minor #5406 SingleSpaceAfterConstructFixer - Attributes, comments and PHPDoc support (SpacePossum) +* minor #5407 TokensAnalyzer::getClassyElements - return trait imports (SpacePossum) +* minor #5410 minors (SpacePossum) +* minor #5411 bump year in LICENSE file (SpacePossum) +* minor #5414 TypeAlternationTransformer - T_FN support (SpacePossum) +* minor #5415 Forbid execution under PHP 8.0.0 (keradus) +* minor #5416 Drop Travis CI (keradus) +* minor #5419 CI: separate SCA checks to dedicated jobs (keradus) +* minor #5420 DX: unblock PHPUnit 9.5 (keradus) +* minor #5423 DX: PHPUnit - disable verbose by default (keradus) +* minor #5425 Cleanup 3.0 todos (keradus) +* minor #5427 Plan changing defaults for array_syntax and list_syntax in 3.0 release (keradus) +* minor #5429 DX: Drop speedtrap PHPUnit listener (keradus) +* minor #5432 Don't allow unserializing classes with a destructor (jderusse) +* minor #5435 DX: PHPUnit - groom configuration of time limits (keradus) +* minor #5439 VisibilityRequiredFixer - support type alternation for properties (keradus) +* minor #5442 DX: FunctionsAnalyzerTest - add missing 7.0 requirement (keradus) + +Changelog for v2.17.3 +--------------------- + +* bug #5384 PsrAutoloadingFixer - do not remove directory structure from the Class name (kubawerlos, keradus) +* bug #5385 SingleLineCommentStyleFixer- run before NoUselessReturnFixer (kubawerlos) +* bug #5387 SingleSpaceAfterConstructFixer - do not touch multi line implements (SpacePossum) +* minor #5329 DX: collect coverage with Github Actions (kubawerlos) +* minor #5380 PhpdocOrderByValueFixer - Allow sorting of throws annotations by value (localheinz, keradus) +* minor #5383 DX: fail PHPUnit tests on warning (kubawerlos) +* minor #5386 DX: remove incorrect priority relations (kubawerlos) + +Changelog for v2.17.2 +--------------------- + +* bug #5345 CleanNamespaceFixer - preserve trailing comments (SpacePossum) +* bug #5348 PsrAutoloadingFixer - fix for class without namespace (kubawerlos) +* bug #5362 SingleSpaceAfterConstructFixer: Do not adjust whitespace before multiple multi-line extends (localheinz, SpacePossum) +* minor #5314 Enable testing with PHPUnit 9.x (sanmai) +* minor #5319 Clean ups (SpacePossum) +* minor #5338 clean ups (SpacePossum) +* minor #5339 NoEmptyStatementFixer - fix more cases (SpacePossum) +* minor #5340 NamedArgumentTransformer - Introduction (SpacePossum) +* minor #5344 Update docs: do not use deprecated create method (SpacePossum) +* minor #5353 Fix typo in issue template (stof) +* minor #5355 OrderedTraitsFixer - mark as risky (SpacePossum) +* minor #5356 RuleSet description fixes (SpacePossum) +* minor #5359 Add application version to "fix" out put when verbosity flag is set (SpacePossum) +* minor #5360 DX: clean up detectIndent methods (kubawerlos) +* minor #5363 Added missing self return type to ConfigInterface::registerCustomFixers() (vudaltsov) +* minor #5366 PhpUnitDedicateAssertInternalTypeFixer - recover target option (keradus) +* minor #5368 DX: PHPUnit 9 compatibility for 2.17 (keradus) +* minor #5370 DX: update PHPUnit usage to use external Prophecy trait and solve warning (keradus) +* minor #5371 Update documentation about PHP_CS_FIXER_IGNORE_ENV (SanderSander, keradus) +* minor #5373 DX: MagicMethodCasingFixerTest - fix test case description (keradus) +* minor #5374 DX: PhpUnitDedicateAssertInternalTypeFixer - add code sample for non-default config (keradus) + +Changelog for v2.17.1 +--------------------- + +* bug #5325 NoBreakCommentFixer - better throw handling (SpacePossum) +* bug #5327 StaticLambdaFixer - fix for arrow function used in class with $this (kubawerlos, SpacePossum) +* bug #5332 Fix file missing for php8 (jderusse) +* bug #5333 Fix file missing for php8 (jderusse) +* minor #5328 Fixed deprecation message version (GrahamCampbell) +* minor #5330 DX: cleanup Github Actions configs (kubawerlos) + +Changelog for v2.17.0 +--------------------- + +* bug #4752 SimpleLambdaCallFixer - bug fixes (SpacePossum) +* bug #4794 TernaryToElvisOperatorFixer - fix open tag with echo (SpacePossum) +* bug #5084 Fix for variables within string interpolation in lambda_not_used_import (GrahamCampbell) +* bug #5094 SwitchContinueToBreakFixer - do not support alternative syntax (SpacePossum) +* feature #2619 PSR-5 @inheritDoc support (julienfalque) +* feature #3253 Add SimplifiedIfReturnFixer (Slamdunk, SpacePossum) +* feature #4005 GroupImportFixer - introduction (greeflas) +* feature #4012 BracesFixer - add "allow_single_line_anonymous_class_with_empty_body" option (kubawerlos) +* feature #4021 OperatorLinebreakFixer - Introduction (kubawerlos, SpacePossum) +* feature #4259 PsrAutoloadingFixer - introduction (kubawerlos) +* feature #4375 extend ruleset "@PHP73Migration" (gharlan) +* feature #4435 SingleSpaceAfterConstructFixer - Introduction (localheinz) +* feature #4493 Add echo_tag_syntax rule (mlocati, kubawerlos) +* feature #4544 SimpleLambdaCallFixer - introduction (keradus) +* feature #4569 PhpdocOrderByValueFixer - Introduction (localheinz) +* feature #4590 SwitchContinueToBreakFixer - Introduction (SpacePossum) +* feature #4679 NativeConstantInvocationFixer - add "strict" flag (kubawerlos) +* feature #4701 OrderedTraitsFixer - introduction (julienfalque) +* feature #4704 LambdaNotUsedImportFixer - introduction (SpacePossum) +* feature #4740 NoAliasLanguageConstructCallFixer - introduction (SpacePossum) +* feature #4741 TernaryToElvisOperatorFixer - introduction (SpacePossum) +* feature #4778 UseArrowFunctionsFixer - introduction (gharlan) +* feature #4790 ArrayPushFixer - introduction (SpacePossum) +* feature #4800 NoUnneededFinalMethodFixer - Add "private_methods" option (SpacePossum) +* feature #4831 BlankLineBeforeStatementFixer - add yield from (SpacePossum) +* feature #4832 NoUnneededControlParenthesesFixer - add yield from (SpacePossum) +* feature #4863 NoTrailingWhitespaceInStringFixer - introduction (gharlan) +* feature #4875 ClassAttributesSeparationFixer - add option for no new lines between properties (adri, ruudk) +* feature #4880 HeredocIndentationFixer - config option for indentation level (gharlan) +* feature #4908 PhpUnitExpectationFixer - update for Phpunit 8.4 (ktomk) +* feature #4942 OrderedClassElementsFixer - added support for abstract method sorting (carlalexander, SpacePossum) +* feature #4947 NativeConstantInvocation - Add "PHP_INT_SIZE" to SF rule set (kubawerlos) +* feature #4953 Add support for custom differ (paulhenri-l, SpacePossum) +* feature #5264 CleanNamespaceFixer - Introduction (SpacePossum) +* feature #5280 NoUselessSprintfFixer - Introduction (SpacePossum) +* minor #4634 Make all options snake_case (kubawerlos) +* minor #4667 PhpUnitOrderedCoversFixer - stop using deprecated fixer (keradus) +* minor #4673 FinalStaticAccessFixer - deprecate (julienfalque) +* minor #4762 Rename simple_lambda_call to regular_callable_call (julienfalque) +* minor #4782 Update RuleSets (SpacePossum) +* minor #4802 Master cleanup (SpacePossum) +* minor #4828 Deprecate Config::create() (DocFX) +* minor #4872 Update RuleSet SF and PHP-CS-Fixer with new config for `no_extra_blan… (SpacePossum) +* minor #4900 Move "no_trailing_whitespace_in_string" to SF ruleset. (SpacePossum) +* minor #4903 Docs: extend regular_callable_call rule docs (keradus, SpacePossum) +* minor #4910 Add use_arrow_functions rule to PHP74Migration:risky set (keradus) +* minor #5025 PhpUnitDedicateAssertInternalTypeFixer - deprecate "target" option (kubawerlos) +* minor #5037 FinalInternalClassFixer- Rename option (SpacePossum) +* minor #5093 LambdaNotUsedImportFixer - add heredoc test (SpacePossum) +* minor #5163 Fix CS (SpacePossum) +* minor #5169 PHP8 care package master (SpacePossum) +* minor #5186 Fix tests (SpacePossum) +* minor #5192 GotoLabelAnalyzer - introduction (SpacePossum) +* minor #5230 Fix: Reference (localheinz) +* minor #5240 PHP8 - Allow trailing comma in parameter list support (SpacePossum) +* minor #5244 Fix 2.17 build (keradus) +* minor #5251 PHP8 - match support (SpacePossum) +* minor #5252 Update RuleSets (SpacePossum) +* minor #5278 PHP8 constructor property promotion support (SpacePossum) +* minor #5284 PHP8 - Attribute support (SpacePossum) +* minor #5323 NoUselessSprintfFixer - Fix test on PHP5.6 (SpacePossum) +* minor #5326 DX: relax composer requirements to not block installation under PHP v8, support for PHP v8 is not yet ready (keradus) + +Changelog for v2.16.10 +---------------------- + +* minor #5314 Enable testing with PHPUnit 9.x (sanmai) +* minor #5338 clean ups (SpacePossum) +* minor #5339 NoEmptyStatementFixer - fix more cases (SpacePossum) +* minor #5340 NamedArgumentTransformer - Introduction (SpacePossum) +* minor #5344 Update docs: do not use deprecated create method (SpacePossum) +* minor #5356 RuleSet description fixes (SpacePossum) +* minor #5360 DX: clean up detectIndent methods (kubawerlos) +* minor #5370 DX: update PHPUnit usage to use external Prophecy trait and solve warning (keradus) +* minor #5373 DX: MagicMethodCasingFixerTest - fix test case description (keradus) +* minor #5374 DX: PhpUnitDedicateAssertInternalTypeFixer - add code sample for non-default config (keradus) + +Changelog for v2.16.9 +--------------------- + +* bug #5095 Annotation - fix for Windows line endings (SpacePossum) +* bug #5221 NoSuperfluousPhpdocTagsFixer - fix for single line PHPDoc (kubawerlos) +* bug #5225 TernaryOperatorSpacesFixer - fix for alternative control structures (kubawerlos) +* bug #5235 ArrayIndentationFixer - fix for nested arrays (kubawerlos) +* bug #5248 NoBreakCommentFixer - fix throw detect (SpacePossum) +* bug #5250 SwitchAnalyzer - fix for semicolon after case/default (kubawerlos) +* bug #5253 IO - fix cache info message (SpacePossum) +* bug #5273 Fix PHPDoc line span fixer when property has array typehint (ossinkine) +* bug #5274 TernaryToNullCoalescingFixer - concat precedence fix (SpacePossum) +* feature #5216 Add RuleSets to docs (SpacePossum) +* minor #5226 Applied CS fixes from 2.17-dev (GrahamCampbell) +* minor #5229 Fixed incorrect phpdoc (GrahamCampbell) +* minor #5231 CS: unify styling with younger branches (keradus) +* minor #5232 PHP8 - throw expression support (SpacePossum) +* minor #5233 DX: simplify check_file_permissions.sh (kubawerlos) +* minor #5236 Improve handling of unavailable code samples (julienfalque, keradus) +* minor #5239 PHP8 - Allow trailing comma in parameter list support (SpacePossum) +* minor #5254 PHP8 - mixed type support (SpacePossum) +* minor #5255 Tests: do not skip documentation test (keradus) +* minor #5256 Docs: phpdoc_to_return_type - add new example in docs (keradus) +* minor #5261 Do not update Composer twice (sanmai) +* minor #5263 PHP8 support (SpacePossum) +* minor #5266 PhpUnitTestCaseStaticMethodCallsFixer - PHPUnit 9.x support (sanmai) +* minor #5267 Improve InstallViaComposerTest (sanmai) +* minor #5268 Add GitHub Workflows CI, including testing on PHP 8 and on macOS/Windows/Ubuntu (sanmai) +* minor #5269 Prep work to migrate to PHPUnit 9.x (sanmai, keradus) +* minor #5275 remove not supported verbose options (SpacePossum) +* minor #5276 PHP8 - add NoUnreachableDefaultArgumentValueFixer to risky set (SpacePossum) +* minor #5277 PHP8 - Constructor Property Promotion support (SpacePossum) +* minor #5292 Disable blank issue template and expose community chat (keradus) +* minor #5293 Add documentation to "yoda_style" sniff to convert Yoda style to non-Yoda style (Luc45) +* minor #5295 Run static code analysis off GitHub Actions (sanmai) +* minor #5298 Add yamllint workflow, validates .yaml files (sanmai) +* minor #5302 SingleLineCommentStyleFixer - do not fix possible attributes (PHP8) (SpacePossum) +* minor #5303 Drop CircleCI and AppVeyor (keradus) +* minor #5304 DX: rename TravisTest, as we no longer test only Travis there (keradus) +* minor #5305 Groom GitHub CI and move some checks from TravisCI to GitHub CI (keradus) +* minor #5308 Only run yamllint when a YAML file is changed (julienfalque, keradus) +* minor #5309 CICD: create yamllint config file (keradus) +* minor #5311 OrderedClassElementsFixer - PHPUnit Bridge support (ktomk) +* minor #5316 PHP8 - Attribute support (SpacePossum) +* minor #5321 DX: little code grooming (keradus) + +Changelog for v2.16.8 +--------------------- + +* bug #5325 NoBreakCommentFixer - better throw handling (SpacePossum) +* bug #5327 StaticLambdaFixer - fix for arrow function used in class with $this (kubawerlos, SpacePossum) +* bug #5333 Fix file missing for php8 (jderusse) +* minor #5328 Fixed deprecation message version (GrahamCampbell) +* minor #5330 DX: cleanup Github Actions configs (kubawerlos) + +Changelog for v2.16.5 +--------------------- + +* bug #4378 PhpUnitNoExpectationAnnotationFixer - annotation in single line doc comment (kubawerlos) +* bug #4936 HeaderCommentFixer - Fix unexpected removal of regular comments (julienfalque) +* bug #5006 PhpdocToParamTypeFixer - fix for breaking PHP syntax for type having reserved name (kubawerlos) +* bug #5016 NoSuperfluousPhpdocTagsFixer - fix for @return with @inheritDoc in description (kubawerlos) +* bug #5017 PhpdocTrimConsecutiveBlankLineSeparationFixer - must run after AlignMultilineCommentFixer (kubawerlos) +* bug #5032 SingleLineAfterImportsFixer - fix for line after import (and before another import) already added using CRLF (kubawerlos) +* bug #5033 VoidReturnFixer - must run after NoSuperfluousPhpdocTagsFixer (kubawerlos) +* bug #5038 HelpCommandTest - toString nested array (SpacePossum) +* bug #5040 LinebreakAfterOpeningTagFixer - do not change code if linebreak already present (kubawerlos) +* bug #5044 StandardizeIncrementFixer - fix handling static properties (kubawerlos) +* bug #5045 BacktickToShellExecFixer - add priority relation to NativeFunctionInvocationFixer and SingleQuoteFixer (kubawerlos) +* bug #5054 PhpdocTypesFixer - fix for multidimensional array (kubawerlos) +* bug #5065 TernaryOperatorSpacesFixer - fix for discovering ":" correctly (kubawerlos) +* bug #5068 Fixed php-cs-fixer crashes on input file syntax error (GrahamCampbell) +* bug #5087 NoAlternativeSyntaxFixer - add support for switch and declare (SpacePossum) +* bug #5092 PhpdocToParamTypeFixer - remove not used option (SpacePossum) +* bug #5105 ClassKeywordRemoveFixer - fix for fully qualified class (kubawerlos) +* bug #5113 TernaryOperatorSpacesFixer - handle goto labels (SpacePossum) +* bug #5124 Fix TernaryToNullCoalescingFixer when dealing with object properties (HypeMC) +* bug #5137 DoctrineAnnotationSpacesFixer - fix for typed properties (kubawerlos) +* bug #5180 Always lint test cases with the stricter process linter (GrahamCampbell) +* bug #5190 PhpUnit*Fixers - Only fix in unit test class scope (SpacePossum) +* bug #5195 YodaStyle - statements in braces should be treated as variables in strict … (SpacePossum) +* bug #5220 NoUnneededFinalMethodFixer - do not fix private constructors (SpacePossum) +* feature #3475 Rework documentation (julienfalque, SpacePossum) +* feature #5166 PHP8 (SpacePossum) +* minor #4878 ArrayIndentationFixer - refactor (julienfalque) +* minor #5031 CI: skip_cleanup: true (keradus) +* minor #5035 PhpdocToParamTypeFixer - Rename attribute (SpacePossum) +* minor #5048 Allow composer/semver ^2.0 and ^3.0 (thomasvargiu) +* minor #5050 DX: moving integration test for braces, indentation_type and no_break_comment into right place (kubawerlos) +* minor #5051 DX: move all tests from AutoReview\FixerTest to Test\AbstractFixerTestCase (kubawerlos) +* minor #5053 DX: cleanup FunctionTypehintSpaceFixer (kubawerlos) +* minor #5056 DX: add missing priority test for indentation_type and phpdoc_indent (kubawerlos) +* minor #5077 DX: add missing priority test between NoUnsetCastFixer and BinaryOperatorSpacesFixer (kubawerlos) +* minor #5083 Update composer.json to prevent issue #5030 (mvorisek) +* minor #5088 NoBreakCommentFixer - NoUselessElseFixer - priority test (SpacePossum) +* minor #5100 Fixed invalid PHP 5.6 syntax (GrahamCampbell) +* minor #5106 Symfony's finder already ignores vcs and dot files by default (GrahamCampbell) +* minor #5112 DX: check file permissions (kubawerlos, SpacePossum) +* minor #5122 Show runtime PHP version (kubawerlos) +* minor #5132 Do not allow assignments in if statements (SpacePossum) +* minor #5133 RuleSetTest - Early return for boolean and detect more defaults (SpacePossum) +* minor #5139 revert some unneeded exclusions (SpacePossum) +* minor #5148 Upgrade Xcode (kubawerlos) +* minor #5149 NoUnsetOnPropertyFixer - risky description tweaks (SpacePossum) +* minor #5161 minors (SpacePossum) +* minor #5170 Fix test on PHP8 (SpacePossum) +* minor #5172 Remove accidentally inserted newlines (GrahamCampbell) +* minor #5173 Fix PHP8 RuleSet inherit (SpacePossum) +* minor #5174 Corrected linting error messages (GrahamCampbell) +* minor #5177 PHP8 (SpacePossum) +* minor #5178 Fix tests (SpacePossum) +* minor #5184 [FinalStaticAccessFixer] Handle new static() in final class (localheinz) +* minor #5188 DX: Update sibling debs to version supporting PHP8/PHPUnit9 (keradus) +* minor #5189 Create temporary linting file in system temp dir (keradus) +* minor #5191 MethodArgumentSpaceFixer - support use/import of anonymous functions. (undefinedor) +* minor #5193 DX: add AbstractPhpUnitFixer (kubawerlos) +* minor #5204 DX: cleanup NullableTypeTransformerTest (kubawerlos) +* minor #5207 Add © for logo (keradus) +* minor #5208 DX: cleanup php-cs-fixer entry file (keradus) +* minor #5210 CICD - temporarily disable problematic test (keradus) +* minor #5211 CICD: fix file permissions (keradus) +* minor #5213 DX: move report schemas to dedicated dir (keradus) +* minor #5214 CICD: fix file permissions (keradus) +* minor #5215 CICD: update checkbashisms (keradus) +* minor #5217 CICD: use Composer v2 and drop hirak/prestissimo plugin (keradus) +* minor #5218 DX: .gitignore - add .phpunit.result.cache (keradus) +* minor #5222 Upgrade Xcode (kubawerlos) +* minor #5223 Docs: regenerate docs on 2.16 line (keradus) + +Changelog for v2.16.4 +--------------------- + +* bug #3893 Fix handling /** and */ on the same line as the first and/or last annotation (dmvdbrugge) +* bug #4919 PhpUnitTestAnnotationFixer - fix function starting with "test" and having lowercase letter after (kubawerlos) +* bug #4929 YodaStyleFixer - handling equals empty array (kubawerlos) +* bug #4934 YodaStyleFixer - fix for conditions weird are (kubawerlos) +* bug #4958 OrderedImportsFixer - fix for trailing comma in group (kubawerlos) +* bug #4959 BlankLineBeforeStatementFixer - handle comment case (SpacePossum) +* bug #4962 MethodArgumentSpaceFixer - must run after MethodChainingIndentationFixer (kubawerlos) +* bug #4963 PhpdocToReturnTypeFixer - fix for breaking PHP syntax for type having reserved name (kubawerlos, Slamdunk) +* bug #4978 ArrayIndentationFixer - must run after MethodArgumentSpaceFixer (kubawerlos) +* bug #4994 FinalInternalClassFixer - must run before ProtectedToPrivateFixer (kubawerlos) +* bug #4996 NoEmptyCommentFixer - handle multiline comments (kubawerlos) +* bug #4999 BlankLineBeforeStatementFixer - better comment handling (SpacePossum) +* bug #5009 NoEmptyCommentFixer - better handle comments sequence (kubawerlos) +* bug #5010 SimplifiedNullReturnFixer - must run before VoidReturnFixer (kubawerlos) +* bug #5011 SingleClassElementPerStatementFixer - must run before ClassAttributesSeparationFixer (kubawerlos) +* bug #5012 StrictParamFixer - must run before NativeFunctionInvocationFixer (kubawerlos) +* bug #5014 PhpdocToParamTypeFixer - fix for void as param (kubawerlos) +* bug #5018 PhpdocScalarFixer - fix for comment with Windows line endings (kubawerlos) +* bug #5029 SingleLineAfterImportsFixer - fix for line after import already added using CRLF (kubawerlos) +* minor #4904 Increase PHPStan level to 8 with strict rules (julienfalque) +* minor #4920 Enhancement: Use DocBlock itself to make it multi-line (localheinz) +* minor #4930 DX: ensure PhpUnitNamespacedFixer handles all classes (kubawerlos) +* minor #4931 DX: add test to ensure each target version in PhpUnitTargetVersion has its set in RuleSet (kubawerlos) +* minor #4932 DX: Travis CI config - fix warnings and infos (kubawerlos) +* minor #4940 Reject empty path (julienfalque) +* minor #4944 Fix grammar (julienfalque) +* minor #4946 Allow "const" option on PHP <7.1 (julienfalque) +* minor #4948 Added describe command to readme (david, 8ctopus) +* minor #4949 Fixed build readme on Windows fails if using Git Bash (Mintty) (8ctopus) +* minor #4954 Config - Trim path (julienfalque) +* minor #4957 DX: Check trailing spaces in project files only (ktomk) +* minor #4961 Assert all project source files are monolithic. (SpacePossum) +* minor #4964 Fix PHPStan baseline (julienfalque) +* minor #4965 Fix PHPStan baseline (julienfalque) +* minor #4973 DX: test "isRisky" method in fixer tests, not as auto review (kubawerlos) +* minor #4974 Minor: Fix typo (ktomk) +* minor #4975 Revert PHPStan level to 5 (julienfalque) +* minor #4976 Add instructions for PHPStan (julienfalque) +* minor #4980 Introduce new issue templates (julienfalque) +* minor #4981 Prevent error in CTTest::testConstants (for PHP8) (guilliamxavier) +* minor #4982 Remove PHIVE (kubawerlos) +* minor #4985 Fix tests with Symfony 5.1 (julienfalque) +* minor #4987 PhpdocAnnotationWithoutDotFixer - handle unicode characters using mb_* (SpacePossum) +* minor #5008 Enhancement: Social justification applied (gbyrka-fingo) +* minor #5023 Fix issue templates (kubawerlos) +* minor #5024 DX: add missing non-default code samples (kubawerlos) + +Changelog for v2.16.3 +--------------------- + +* bug #4915 Fix handling property PHPDocs with unsupported type (julienfalque) +* minor #4916 Fix AppVeyor build (julienfalque) +* minor #4917 CircleCI - Bump xcode to 11.4 (GrahamCampbell) +* minor #4918 DX: do not fix ".phpt" files by default (kubawerlos) + +Changelog for v2.16.2 +--------------------- + +* bug #3820 Braces - (re)indenting comment issues (SpacePossum) +* bug #3911 PhpdocVarWithoutNameFixer - fix for properties only (dmvdbrugge) +* bug #4601 ClassKeywordRemoveFixer - Fix for namespace (yassine-ah, kubawerlos) +* bug #4630 FullyQualifiedStrictTypesFixer - Ignore partial class names which look like FQCNs (localheinz, SpacePossum) +* bug #4661 ExplicitStringVariableFixer - variables pair if one is already explicit (kubawerlos) +* bug #4675 NonPrintableCharacterFixer - fix for backslash and quotes when changing to escape sequences (kubawerlos) +* bug #4678 TokensAnalyzer::isConstantInvocation - fix for importing multiple classes with single "use" (kubawerlos) +* bug #4682 Fix handling array type declaration in properties (julienfalque) +* bug #4685 Improve Symfony 5 compatibility (keradus) +* bug #4688 TokensAnalyzer::isConstantInvocation - Fix detection for fully qualified return type (julienfalque) +* bug #4689 DeclareStrictTypesFixer - fix for "strict_types" set to "0" (kubawerlos) +* bug #4690 PhpdocVarAnnotationCorrectOrderFixer - fix for multiline `@var` without type (kubawerlos) +* bug #4710 SingleTraitInsertPerStatement - fix formatting for multiline "use" (kubawerlos) +* bug #4711 Ensure that files from "tests" directory in release are autoloaded (kubawerlos) +* bug #4749 TokensAnalyze::isUnaryPredecessorOperator fix for CT::T_ARRAY_INDEX_C… (SpacePossum) +* bug #4759 Add more priority cases (SpacePossum) +* bug #4761 NoSuperfluousElseifFixer - handle single line (SpacePossum) +* bug #4783 NoSuperfluousPhpdocTagsFixer - fix for really big PHPDoc (kubawerlos, mvorisek) +* bug #4787 NoUnneededFinalMethodFixer - Mark as risky (SpacePossum) +* bug #4795 OrderedClassElementsFixer - Fix (SpacePossum) +* bug #4801 GlobalNamespaceImportFixer - fix docblock handling (gharlan) +* bug #4804 TokensAnalyzer::isUnarySuccessorOperator fix for array curly braces (SpacePossum) +* bug #4807 IncrementStyleFixer - handle after ")" (SpacePossum) +* bug #4808 Modernize types casting fixer array curly (SpacePossum) +* bug #4809 Fix "braces" and "method_argument_space" priority (julienfalque) +* bug #4813 BracesFixer - fix invalid code generation on alternative syntax (SpacePossum) +* bug #4822 fix 2 bugs in phpdoc_line_span (lmichelin) +* bug #4823 ReturnAssignmentFixer - repeat fix (SpacePossum) +* bug #4824 NoUnusedImportsFixer - SingleLineAfterImportsFixer - fix priority (SpacePossum) +* bug #4825 GlobalNamespaceImportFixer - do not import global into global (SpacePossum) +* bug #4829 YodaStyleFixer - fix precedence for T_MOD_EQUAL and T_COALESCE_EQUAL (SpacePossum) +* bug #4830 TernaryToNullCoalescingFixer - handle yield from (SpacePossum) +* bug #4835 Remove duplicate "function_to_constant" from RuleSet (SpacePossum) +* bug #4840 LineEndingFixer - T_CLOSE_TAG support, StringLineEndingFixer - T_INLI… (SpacePossum) +* bug #4846 FunctionsAnalyzer - better isGlobalFunctionCall detection (SpacePossum) +* bug #4852 Priority issues (SpacePossum) +* bug #4870 HeaderCommentFixer - do not remove class docs (gharlan) +* bug #4871 NoExtraBlankLinesFixer - handle cases on same line (SpacePossum) +* bug #4895 Fix conflict between header_comment and declare_strict_types (BackEndTea, julienfalque) +* bug #4911 PhpdocSeparationFixer - fix regression with lack of next line (keradus) +* feature #4742 FunctionToConstantFixer - get_class($this) support (SpacePossum) +* minor #4377 CommentsAnalyzer - fix for declare before header comment (kubawerlos) +* minor #4636 DX: do not check for PHPDBG when collecting coverage (kubawerlos) +* minor #4644 Docs: add info about "-vv..." (voku) +* minor #4691 Run Travis CI on stable PHP 7.4 (kubawerlos) +* minor #4693 Increase Travis CI Git clone depth (julienfalque) +* minor #4699 LineEndingFixer - handle "\r\r\n" (kubawerlos) +* minor #4703 NoSuperfluousPhpdocTagsFixer,PhpdocAddMissingParamAnnotationFixer - p… (SpacePossum) +* minor #4707 Fix typos (TysonAndre) +* minor #4712 NoBlankLinesAfterPhpdocFixer — Do not strip newline between docblock and use statements (mollierobbert) +* minor #4715 Enhancement: Install ergebnis/composer-normalize via Phive (localheinz) +* minor #4722 Fix Circle CI build (julienfalque) +* minor #4724 DX: Simplify installing PCOV (kubawerlos) +* minor #4736 NoUnusedImportsFixer - do not match variable name as import (SpacePossum) +* minor #4746 NoSuperfluousPhpdocTagsFixer - Remove for typed properties (PHP 7.4) (ruudk) +* minor #4753 Do not apply any text/.git filters to fixtures (mvorisek) +* minor #4757 Test $expected is used before $input (SpacePossum) +* minor #4758 Autoreview the PHPDoc of *Fixer::getPriority based on the priority map (SpacePossum) +* minor #4765 Add test on some return types (SpacePossum) +* minor #4766 Remove false test skip (SpacePossum) +* minor #4767 Remove useless priority comments (kubawerlos) +* minor #4769 DX: add missing priority tests (kubawerlos) +* minor #4772 NoUnneededFinalMethodFixer - update description (kubawerlos) +* minor #4774 DX: simplify Utils::camelCaseToUnderscore (kubawerlos) +* minor #4781 NoUnneededCurlyBracesFixer - handle namespaces (SpacePossum) +* minor #4784 Travis CI - Use multiple keyservers (ktomk) +* minor #4785 Improve static analysis (enumag) +* minor #4788 Configurable fixers code sample (SpacePossum) +* minor #4791 Increase PHPStan level to 3 (julienfalque) +* minor #4797 clean ups (SpacePossum) +* minor #4803 FinalClassFixer - Doctrine\ORM\Mapping as ORM alias should not be required (localheinz) +* minor #4839 2.15 - clean ups (SpacePossum) +* minor #4842 ReturnAssignmentFixer - Support more cases (julienfalque) +* minor #4843 NoSuperfluousPhpdocTagsFixer - fix typo in option description (OndraM) +* minor #4844 Same requirements for descriptions (SpacePossum) +* minor #4849 Increase PHPStan level to 5 (julienfalque) +* minor #4850 Fix phpstan (SpacePossum) +* minor #4857 Fixed the unit tests (GrahamCampbell) +* minor #4865 Use latest xcode image (GrahamCampbell) +* minor #4892 CombineNestedDirnameFixer - Add space after comma (julienfalque) +* minor #4894 DX: PhpdocToParamTypeFixer - improve typing (keradus) +* minor #4898 FixerTest - yield the data in AutoReview (Nyholm) +* minor #4899 Fix exception message format for fabbot.io (SpacePossum) +* minor #4905 Support composer v2 installed.json files (GrahamCampbell) +* minor #4906 CI: use Composer stable release for AppVeyor (kubawerlos) +* minor #4909 DX: HeaderCommentFixer - use non-aliased version of option name in code (keradus) +* minor #4912 CI: Fix AppVeyor integration (keradus) + +Changelog for v2.16.1 +--------------------- + +* bug #4476 FunctionsAnalyzer - add "isTheSameClassCall" for correct verifying of function calls (kubawerlos) +* bug #4605 PhpdocToParamTypeFixer - cover more cases (keradus, julienfalque) +* bug #4626 FinalPublicMethodForAbstractClassFixer - Do not attempt to mark abstract public methods as final (localheinz) +* bug #4632 NullableTypeDeclarationForDefaultNullValueFixer - fix for not lowercase "null" (kubawerlos) +* bug #4638 Ensure compatibility with PHP 7.4 (julienfalque) +* bug #4641 Add typed properties test to VisibilityRequiredFixerTest (GawainLynch, julienfalque) +* bug #4654 ArrayIndentationFixer - Fix array indentation for multiline values (julienfalque) +* bug #4660 TokensAnalyzer::isConstantInvocation - fix for extending multiple interfaces (kubawerlos) +* bug #4668 TokensAnalyzer::isConstantInvocation - fix for interface method return type (kubawerlos) +* minor #4608 Allow Symfony 5 components (l-vo) +* minor #4622 Disallow PHP 7.4 failures on Travis CI (julienfalque) +* minor #4623 README - Mark up as code (localheinz) +* minor #4637 PHP 7.4 integration test (GawainLynch, julienfalque) +* minor #4643 DX: Update .gitattributes and move ci-integration.sh to root of the project (kubawerlos, keradus) +* minor #4645 Check PHP extensions on runtime (kubawerlos) +* minor #4655 Improve docs - README (mvorisek) +* minor #4662 DX: generate headers in README.rst (kubawerlos) +* minor #4669 Enable execution under PHP 7.4 (keradus) +* minor #4670 TravisTest - rewrite tests to allow last supported by tool PHP version to be snapshot (keradus) +* minor #4671 TravisTest - rewrite tests to allow last supported by tool PHP version to be snapshot (keradus) + +Changelog for v2.16.0 +--------------------- + +* feature #3810 PhpdocLineSpanFixer - Introduction (BackEndTea) +* feature #3928 Add FinalPublicMethodForAbstractClassFixer (Slamdunk) +* feature #4000 FinalStaticAccessFixer - Introduction (ntzm) +* feature #4275 Issue #4274: Let lowercase_constants directive to be configurable. (drupol) +* feature #4355 GlobalNamespaceImportFixer - Introduction (gharlan) +* feature #4358 SelfStaticAccessorFixer - Introduction (SpacePossum) +* feature #4385 CommentToPhpdocFixer - allow to ignore tags (kubawerlos) +* feature #4401 Add NullableTypeDeclarationForDefaultNullValueFixer (HypeMC) +* feature #4452 Add SingleLineThrowFixer (kubawerlos) +* feature #4500 NoSuperfluousPhpdocTags - Add remove_inheritdoc option (julienfalque) +* feature #4505 NoSuperfluousPhpdocTagsFixer - allow params that aren't on the signature (azjezz) +* feature #4531 PhpdocAlignFixer - add "property-read" and "property-write" to allowed tags (kubawerlos) +* feature #4583 Phpdoc to param type fixer rebase (jg-development) +* minor #4033 Raise deprecation warnings on usage of deprecated aliases (ntzm) +* minor #4423 DX: update branch alias (keradus) +* minor #4537 SelfStaticAccessor - extend itests (keradus) +* minor #4607 Configure no_superfluous_phpdoc_tags for Symfony (keradus) +* minor #4618 DX: fix usage of deprecated options (0x450x6c) +* minor #4619 Fix PHP 7.3 strict mode warnings (keradus) +* minor #4621 Add single_line_throw to Symfony ruleset (keradus) + +Changelog for v2.15.10 +---------------------- + +* bug #5095 Annotation - fix for Windows line endings (SpacePossum) +* bug #5221 NoSuperfluousPhpdocTagsFixer - fix for single line PHPDoc (kubawerlos) +* bug #5225 TernaryOperatorSpacesFixer - fix for alternative control structures (kubawerlos) +* bug #5235 ArrayIndentationFixer - fix for nested arrays (kubawerlos) +* bug #5248 NoBreakCommentFixer - fix throw detect (SpacePossum) +* bug #5250 SwitchAnalyzer - fix for semicolon after case/default (kubawerlos) +* bug #5253 IO - fix cache info message (SpacePossum) +* bug #5274 TernaryToNullCoalescingFixer - concat precedence fix (SpacePossum) +* feature #5216 Add RuleSets to docs (SpacePossum) +* minor #5226 Applied CS fixes from 2.17-dev (GrahamCampbell) +* minor #5229 Fixed incorrect phpdoc (GrahamCampbell) +* minor #5231 CS: unify styling with younger branches (keradus) +* minor #5232 PHP8 - throw expression support (SpacePossum) +* minor #5233 DX: simplify check_file_permissions.sh (kubawerlos) +* minor #5236 Improve handling of unavailable code samples (julienfalque, keradus) +* minor #5239 PHP8 - Allow trailing comma in parameter list support (SpacePossum) +* minor #5254 PHP8 - mixed type support (SpacePossum) +* minor #5255 Tests: do not skip documentation test (keradus) +* minor #5261 Do not update Composer twice (sanmai) +* minor #5263 PHP8 support (SpacePossum) +* minor #5266 PhpUnitTestCaseStaticMethodCallsFixer - PHPUnit 9.x support (sanmai) +* minor #5267 Improve InstallViaComposerTest (sanmai) +* minor #5276 PHP8 - add NoUnreachableDefaultArgumentValueFixer to risky set (SpacePossum) + +Changelog for v2.15.9 +--------------------- + +* bug #4378 PhpUnitNoExpectationAnnotationFixer - annotation in single line doc comment (kubawerlos) +* bug #4936 HeaderCommentFixer - Fix unexpected removal of regular comments (julienfalque) +* bug #5017 PhpdocTrimConsecutiveBlankLineSeparationFixer - must run after AlignMultilineCommentFixer (kubawerlos) +* bug #5033 VoidReturnFixer - must run after NoSuperfluousPhpdocTagsFixer (kubawerlos) +* bug #5038 HelpCommandTest - toString nested array (SpacePossum) +* bug #5040 LinebreakAfterOpeningTagFixer - do not change code if linebreak already present (kubawerlos) +* bug #5044 StandardizeIncrementFixer - fix handling static properties (kubawerlos) +* bug #5045 BacktickToShellExecFixer - add priority relation to NativeFunctionInvocationFixer and SingleQuoteFixer (kubawerlos) +* bug #5054 PhpdocTypesFixer - fix for multidimensional array (kubawerlos) +* bug #5065 TernaryOperatorSpacesFixer - fix for discovering ":" correctly (kubawerlos) +* bug #5068 Fixed php-cs-fixer crashes on input file syntax error (GrahamCampbell) +* bug #5087 NoAlternativeSyntaxFixer - add support for switch and declare (SpacePossum) +* bug #5105 ClassKeywordRemoveFixer - fix for fully qualified class (kubawerlos) +* bug #5113 TernaryOperatorSpacesFixer - handle goto labels (SpacePossum) +* bug #5124 Fix TernaryToNullCoalescingFixer when dealing with object properties (HypeMC) +* bug #5137 DoctrineAnnotationSpacesFixer - fix for typed properties (kubawerlos) +* bug #5180 Always lint test cases with the stricter process linter (GrahamCampbell) +* bug #5190 PhpUnit*Fixers - Only fix in unit test class scope (SpacePossum) +* bug #5195 YodaStyle - statements in braces should be treated as variables in strict … (SpacePossum) +* bug #5220 NoUnneededFinalMethodFixer - do not fix private constructors (SpacePossum) +* feature #3475 Rework documentation (julienfalque, SpacePossum) +* feature #5166 PHP8 (SpacePossum) +* minor #4878 ArrayIndentationFixer - refactor (julienfalque) +* minor #5031 CI: skip_cleanup: true (keradus) +* minor #5048 Allow composer/semver ^2.0 and ^3.0 (thomasvargiu) +* minor #5050 DX: moving integration test for braces, indentation_type and no_break_comment into right place (kubawerlos) +* minor #5051 DX: move all tests from AutoReview\FixerTest to Test\AbstractFixerTestCase (kubawerlos) +* minor #5053 DX: cleanup FunctionTypehintSpaceFixer (kubawerlos) +* minor #5056 DX: add missing priority test for indentation_type and phpdoc_indent (kubawerlos) +* minor #5077 DX: add missing priority test between NoUnsetCastFixer and BinaryOperatorSpacesFixer (kubawerlos) +* minor #5083 Update composer.json to prevent issue #5030 (mvorisek) +* minor #5088 NoBreakCommentFixer - NoUselessElseFixer - priority test (SpacePossum) +* minor #5100 Fixed invalid PHP 5.6 syntax (GrahamCampbell) +* minor #5106 Symfony's finder already ignores vcs and dot files by default (GrahamCampbell) +* minor #5112 DX: check file permissions (kubawerlos, SpacePossum) +* minor #5122 Show runtime PHP version (kubawerlos) +* minor #5132 Do not allow assignments in if statements (SpacePossum) +* minor #5133 RuleSetTest - Early return for boolean and detect more defaults (SpacePossum) +* minor #5139 revert some unneeded exclusions (SpacePossum) +* minor #5148 Upgrade Xcode (kubawerlos) +* minor #5149 NoUnsetOnPropertyFixer - risky description tweaks (SpacePossum) +* minor #5161 minors (SpacePossum) +* minor #5172 Remove accidentally inserted newlines (GrahamCampbell) +* minor #5173 Fix PHP8 RuleSet inherit (SpacePossum) +* minor #5174 Corrected linting error messages (GrahamCampbell) +* minor #5177 PHP8 (SpacePossum) +* minor #5188 DX: Update sibling debs to version supporting PHP8/PHPUnit9 (keradus) +* minor #5189 Create temporary linting file in system temp dir (keradus) +* minor #5191 MethodArgumentSpaceFixer - support use/import of anonymous functions. (undefinedor) +* minor #5193 DX: add AbstractPhpUnitFixer (kubawerlos) +* minor #5204 DX: cleanup NullableTypeTransformerTest (kubawerlos) +* minor #5207 Add © for logo (keradus) +* minor #5208 DX: cleanup php-cs-fixer entry file (keradus) +* minor #5210 CICD - temporarily disable problematic test (keradus) +* minor #5211 CICD: fix file permissions (keradus) +* minor #5213 DX: move report schemas to dedicated dir (keradus) +* minor #5214 CICD: fix file permissions (keradus) +* minor #5215 CICD: update checkbashisms (keradus) +* minor #5217 CICD: use Composer v2 and drop hirak/prestissimo plugin (keradus) +* minor #5218 DX: .gitignore - add .phpunit.result.cache (keradus) +* minor #5222 Upgrade Xcode (kubawerlos) + +Changelog for v2.15.8 +--------------------- + +* bug #3893 Fix handling /** and */ on the same line as the first and/or last annotation (dmvdbrugge) +* bug #4919 PhpUnitTestAnnotationFixer - fix function starting with "test" and having lowercase letter after (kubawerlos) +* bug #4929 YodaStyleFixer - handling equals empty array (kubawerlos) +* bug #4934 YodaStyleFixer - fix for conditions weird are (kubawerlos) +* bug #4958 OrderedImportsFixer - fix for trailing comma in group (kubawerlos) +* bug #4959 BlankLineBeforeStatementFixer - handle comment case (SpacePossum) +* bug #4962 MethodArgumentSpaceFixer - must run after MethodChainingIndentationFixer (kubawerlos) +* bug #4963 PhpdocToReturnTypeFixer - fix for breaking PHP syntax for type having reserved name (kubawerlos, Slamdunk) +* bug #4978 ArrayIndentationFixer - must run after MethodArgumentSpaceFixer (kubawerlos) +* bug #4994 FinalInternalClassFixer - must run before ProtectedToPrivateFixer (kubawerlos) +* bug #4996 NoEmptyCommentFixer - handle multiline comments (kubawerlos) +* bug #4999 BlankLineBeforeStatementFixer - better comment handling (SpacePossum) +* bug #5009 NoEmptyCommentFixer - better handle comments sequence (kubawerlos) +* bug #5010 SimplifiedNullReturnFixer - must run before VoidReturnFixer (kubawerlos) +* bug #5011 SingleClassElementPerStatementFixer - must run before ClassAttributesSeparationFixer (kubawerlos) +* bug #5012 StrictParamFixer - must run before NativeFunctionInvocationFixer (kubawerlos) +* bug #5029 SingleLineAfterImportsFixer - fix for line after import already added using CRLF (kubawerlos) +* minor #4904 Increase PHPStan level to 8 with strict rules (julienfalque) +* minor #4930 DX: ensure PhpUnitNamespacedFixer handles all classes (kubawerlos) +* minor #4931 DX: add test to ensure each target version in PhpUnitTargetVersion has its set in RuleSet (kubawerlos) +* minor #4932 DX: Travis CI config - fix warnings and infos (kubawerlos) +* minor #4940 Reject empty path (julienfalque) +* minor #4944 Fix grammar (julienfalque) +* minor #4946 Allow "const" option on PHP <7.1 (julienfalque) +* minor #4948 Added describe command to readme (david, 8ctopus) +* minor #4949 Fixed build readme on Windows fails if using Git Bash (Mintty) (8ctopus) +* minor #4954 Config - Trim path (julienfalque) +* minor #4957 DX: Check trailing spaces in project files only (ktomk) +* minor #4961 Assert all project source files are monolithic. (SpacePossum) +* minor #4964 Fix PHPStan baseline (julienfalque) +* minor #4973 DX: test "isRisky" method in fixer tests, not as auto review (kubawerlos) +* minor #4974 Minor: Fix typo (ktomk) +* minor #4975 Revert PHPStan level to 5 (julienfalque) +* minor #4976 Add instructions for PHPStan (julienfalque) +* minor #4980 Introduce new issue templates (julienfalque) +* minor #4981 Prevent error in CTTest::testConstants (for PHP8) (guilliamxavier) +* minor #4982 Remove PHIVE (kubawerlos) +* minor #4985 Fix tests with Symfony 5.1 (julienfalque) +* minor #4987 PhpdocAnnotationWithoutDotFixer - handle unicode characters using mb_* (SpacePossum) +* minor #5008 Enhancement: Social justification applied (gbyrka-fingo) +* minor #5023 Fix issue templates (kubawerlos) +* minor #5024 DX: add missing non-default code samples (kubawerlos) + +Changelog for v2.15.7 +--------------------- + +* bug #4915 Fix handling property PHPDocs with unsupported type (julienfalque) +* minor #4916 Fix AppVeyor build (julienfalque) +* minor #4917 CircleCI - Bump xcode to 11.4 (GrahamCampbell) +* minor #4918 DX: do not fix ".phpt" files by default (kubawerlos) + +Changelog for v2.15.6 +--------------------- + +* bug #3820 Braces - (re)indenting comment issues (SpacePossum) +* bug #3911 PhpdocVarWithoutNameFixer - fix for properties only (dmvdbrugge) +* bug #4601 ClassKeywordRemoveFixer - Fix for namespace (yassine-ah, kubawerlos) +* bug #4630 FullyQualifiedStrictTypesFixer - Ignore partial class names which look like FQCNs (localheinz, SpacePossum) +* bug #4661 ExplicitStringVariableFixer - variables pair if one is already explicit (kubawerlos) +* bug #4675 NonPrintableCharacterFixer - fix for backslash and quotes when changing to escape sequences (kubawerlos) +* bug #4678 TokensAnalyzer::isConstantInvocation - fix for importing multiple classes with single "use" (kubawerlos) +* bug #4682 Fix handling array type declaration in properties (julienfalque) +* bug #4685 Improve Symfony 5 compatibility (keradus) +* bug #4688 TokensAnalyzer::isConstantInvocation - Fix detection for fully qualified return type (julienfalque) +* bug #4689 DeclareStrictTypesFixer - fix for "strict_types" set to "0" (kubawerlos) +* bug #4690 PhpdocVarAnnotationCorrectOrderFixer - fix for multiline `@var` without type (kubawerlos) +* bug #4710 SingleTraitInsertPerStatement - fix formatting for multiline "use" (kubawerlos) +* bug #4711 Ensure that files from "tests" directory in release are autoloaded (kubawerlos) +* bug #4749 TokensAnalyze::isUnaryPredecessorOperator fix for CT::T_ARRAY_INDEX_C… (SpacePossum) +* bug #4759 Add more priority cases (SpacePossum) +* bug #4761 NoSuperfluousElseifFixer - handle single line (SpacePossum) +* bug #4783 NoSuperfluousPhpdocTagsFixer - fix for really big PHPDoc (kubawerlos, mvorisek) +* bug #4787 NoUnneededFinalMethodFixer - Mark as risky (SpacePossum) +* bug #4795 OrderedClassElementsFixer - Fix (SpacePossum) +* bug #4804 TokensAnalyzer::isUnarySuccessorOperator fix for array curly braces (SpacePossum) +* bug #4807 IncrementStyleFixer - handle after ")" (SpacePossum) +* bug #4808 Modernize types casting fixer array curly (SpacePossum) +* bug #4809 Fix "braces" and "method_argument_space" priority (julienfalque) +* bug #4813 BracesFixer - fix invalid code generation on alternative syntax (SpacePossum) +* bug #4823 ReturnAssignmentFixer - repeat fix (SpacePossum) +* bug #4824 NoUnusedImportsFixer - SingleLineAfterImportsFixer - fix priority (SpacePossum) +* bug #4829 YodaStyleFixer - fix precedence for T_MOD_EQUAL and T_COALESCE_EQUAL (SpacePossum) +* bug #4830 TernaryToNullCoalescingFixer - handle yield from (SpacePossum) +* bug #4835 Remove duplicate "function_to_constant" from RuleSet (SpacePossum) +* bug #4840 LineEndingFixer - T_CLOSE_TAG support, StringLineEndingFixer - T_INLI… (SpacePossum) +* bug #4846 FunctionsAnalyzer - better isGlobalFunctionCall detection (SpacePossum) +* bug #4852 Priority issues (SpacePossum) +* bug #4870 HeaderCommentFixer - do not remove class docs (gharlan) +* bug #4871 NoExtraBlankLinesFixer - handle cases on same line (SpacePossum) +* bug #4895 Fix conflict between header_comment and declare_strict_types (BackEndTea, julienfalque) +* bug #4911 PhpdocSeparationFixer - fix regression with lack of next line (keradus) +* feature #4742 FunctionToConstantFixer - get_class($this) support (SpacePossum) +* minor #4377 CommentsAnalyzer - fix for declare before header comment (kubawerlos) +* minor #4636 DX: do not check for PHPDBG when collecting coverage (kubawerlos) +* minor #4644 Docs: add info about "-vv..." (voku) +* minor #4691 Run Travis CI on stable PHP 7.4 (kubawerlos) +* minor #4693 Increase Travis CI Git clone depth (julienfalque) +* minor #4699 LineEndingFixer - handle "\r\r\n" (kubawerlos) +* minor #4703 NoSuperfluousPhpdocTagsFixer,PhpdocAddMissingParamAnnotationFixer - p… (SpacePossum) +* minor #4707 Fix typos (TysonAndre) +* minor #4712 NoBlankLinesAfterPhpdocFixer — Do not strip newline between docblock and use statements (mollierobbert) +* minor #4715 Enhancement: Install ergebnis/composer-normalize via Phive (localheinz) +* minor #4722 Fix Circle CI build (julienfalque) +* minor #4724 DX: Simplify installing PCOV (kubawerlos) +* minor #4736 NoUnusedImportsFixer - do not match variable name as import (SpacePossum) +* minor #4746 NoSuperfluousPhpdocTagsFixer - Remove for typed properties (PHP 7.4) (ruudk) +* minor #4753 Do not apply any text/.git filters to fixtures (mvorisek) +* minor #4757 Test $expected is used before $input (SpacePossum) +* minor #4758 Autoreview the PHPDoc of *Fixer::getPriority based on the priority map (SpacePossum) +* minor #4765 Add test on some return types (SpacePossum) +* minor #4766 Remove false test skip (SpacePossum) +* minor #4767 Remove useless priority comments (kubawerlos) +* minor #4769 DX: add missing priority tests (kubawerlos) +* minor #4772 NoUnneededFinalMethodFixer - update description (kubawerlos) +* minor #4774 DX: simplify Utils::camelCaseToUnderscore (kubawerlos) +* minor #4781 NoUnneededCurlyBracesFixer - handle namespaces (SpacePossum) +* minor #4784 Travis CI - Use multiple keyservers (ktomk) +* minor #4785 Improve static analysis (enumag) +* minor #4788 Configurable fixers code sample (SpacePossum) +* minor #4791 Increase PHPStan level to 3 (julienfalque) +* minor #4797 clean ups (SpacePossum) +* minor #4803 FinalClassFixer - Doctrine\ORM\Mapping as ORM alias should not be required (localheinz) +* minor #4839 2.15 - clean ups (SpacePossum) +* minor #4842 ReturnAssignmentFixer - Support more cases (julienfalque) +* minor #4844 Same requirements for descriptions (SpacePossum) +* minor #4849 Increase PHPStan level to 5 (julienfalque) +* minor #4857 Fixed the unit tests (GrahamCampbell) +* minor #4865 Use latest xcode image (GrahamCampbell) +* minor #4892 CombineNestedDirnameFixer - Add space after comma (julienfalque) +* minor #4898 FixerTest - yield the data in AutoReview (Nyholm) +* minor #4899 Fix exception message format for fabbot.io (SpacePossum) +* minor #4905 Support composer v2 installed.json files (GrahamCampbell) +* minor #4906 CI: use Composer stable release for AppVeyor (kubawerlos) +* minor #4909 DX: HeaderCommentFixer - use non-aliased version of option name in code (keradus) +* minor #4912 CI: Fix AppVeyor integration (keradus) + +Changelog for v2.15.5 +--------------------- + +* bug #4476 FunctionsAnalyzer - add "isTheSameClassCall" for correct verifying of function calls (kubawerlos) +* bug #4641 Add typed properties test to VisibilityRequiredFixerTest (GawainLynch, julienfalque) +* bug #4654 ArrayIndentationFixer - Fix array indentation for multiline values (julienfalque) +* bug #4660 TokensAnalyzer::isConstantInvocation - fix for extending multiple interfaces (kubawerlos) +* bug #4668 TokensAnalyzer::isConstantInvocation - fix for interface method return type (kubawerlos) +* minor #4608 Allow Symfony 5 components (l-vo) +* minor #4622 Disallow PHP 7.4 failures on Travis CI (julienfalque) +* minor #4637 PHP 7.4 integration test (GawainLynch, julienfalque) +* minor #4643 DX: Update .gitattributes and move ci-integration.sh to root of the project (kubawerlos, keradus) +* minor #4645 Check PHP extensions on runtime (kubawerlos) +* minor #4655 Improve docs - README (mvorisek) +* minor #4662 DX: generate headers in README.rst (kubawerlos) +* minor #4669 Enable execution under PHP 7.4 (keradus) +* minor #4671 TravisTest - rewrite tests to allow last supported by tool PHP version to be snapshot (keradus) + +Changelog for v2.15.4 +--------------------- + +* bug #4183 IndentationTypeFixer - fix handling 2 spaces indent (kubawerlos) +* bug #4406 NoSuperfluousElseifFixer - fix invalid escape sequence in character class (remicollet, SpacePossum) +* bug #4416 NoUnusedImports - Fix imports detected as used in namespaces (julienfalque, SpacePossum) +* bug #4518 PhpUnitNoExpectationAnnotationFixer - fix handling expect empty exception message (ktomk) +* bug #4548 HeredocIndentationFixer - remove whitespace in empty lines (gharlan) +* bug #4556 ClassKeywordRemoveFixer - fix for self,static and parent keywords (kubawerlos) +* bug #4572 TokensAnalyzer - handle nested anonymous classes (SpacePossum) +* bug #4573 CombineConsecutiveIssetsFixer - fix stop based on precedence (SpacePossum) +* bug #4577 Fix command exit code on lint error after fixing fix. (SpacePossum) +* bug #4581 FunctionsAnalyzer: fix for comment in type (kubawerlos) +* bug #4586 BracesFixer - handle dynamic static method call (SpacePossum) +* bug #4594 Braces - fix both single line comment styles (SpacePossum) +* bug #4609 PhpdocTypesOrderFixer - Prevent unexpected default value change (laurent35240) +* minor #4458 Add PHPStan (julienfalque) +* minor #4479 IncludeFixer - remove braces when the statement is wrapped in block (kubawerlos) +* minor #4490 Allow running if installed as project specific (ticktackk) +* minor #4517 Verify PCRE pattern before use (ktomk) +* minor #4521 Remove superfluous leading backslash, closes 4520 (ktomk) +* minor #4532 DX: ensure data providers are used (kubawerlos) +* minor #4534 Redo PHP7.4 - Add "str_split" => "mb_str_split" mapping (keradus, Slamdunk) +* minor #4536 DX: use PHIVE for dev tools (keradus) +* minor #4538 Docs: update Cookbook (keradus) +* minor #4541 Enhancement: Use default name property to configure command names (localheinz) +* minor #4546 DX: removing unnecessary variable initialization (kubawerlos) +* minor #4549 DX: use ::class whenever possible (keradus, kubawerlos) +* minor #4550 DX: travis_retry for dev-tools install (ktomk, keradus) +* minor #4559 Allow 7.4snapshot to fail due to a bug on it (kubawerlos) +* minor #4563 GitlabReporter - fix report output (mjanser) +* minor #4564 Move readme-update command to Section 3 (iwasherefirst2) +* minor #4566 Update symfony ruleset (gharlan) +* minor #4570 Command::execute() should always return an integer (derrabus) +* minor #4580 Add support for true/false return type hints. (SpacePossum) +* minor #4584 Increase PHPStan level to 1 (julienfalque) +* minor #4585 Fix deprecation notices (julienfalque) +* minor #4587 Output details - Explain why a file was skipped (SpacePossum) +* minor #4588 Fix STDIN test when path is one level deep (julienfalque) +* minor #4589 PhpdocToReturnType - Add support for Foo[][] (SpacePossum) +* minor #4593 Ensure compatibility with PHP 7.4 typed properties (julienfalque) +* minor #4595 Import cannot be used after `::` so can be removed (SpacePossum) +* minor #4596 Ensure compatibility with PHP 7.4 numeric literal separator (julienfalque) +* minor #4597 Fix PHP 7.4 deprecation notices (julienfalque) +* minor #4600 Ensure compatibility with PHP 7.4 arrow functions (julienfalque) +* minor #4602 Ensure compatibility with PHP 7.4 spread operator in array expression (julienfalque) +* minor #4603 Ensure compatibility with PHP 7.4 null coalescing assignment operator (julienfalque) +* minor #4606 Configure no_superfluous_phpdoc_tags for Symfony (keradus) +* minor #4610 Travis CI - Update known files list (julienfalque) +* minor #4615 Remove workaround for dev-tools install reg. Phive (ktomk) + +Changelog for v2.15.3 +--------------------- + +* bug #4533 Revert PHP7.4 - Add "str_split" => "mb_str_split" mapping (keradus) +* minor #4264 DX: AutoReview - ensure Travis handle all needed PHP versions (keradus) +* minor #4524 MethodArgumentSpaceFixerTest - make explicit configuration to prevent fail on configuration change (keradus) + +Changelog for v2.15.2 +--------------------- + +* bug #4132 BlankLineAfterNamespaceFixer - do not remove indent, handle comments (kubawerlos) +* bug #4384 MethodArgumentSpaceFixer - fix for on_multiline:ensure_fully_multiline with trailing comma in function call (kubawerlos) +* bug #4404 FileLintingIterator - fix current value on end/invalid (SpacePossum) +* bug #4421 FunctionTypehintSpaceFixer - Ensure single space between type declaration and parameter (localheinz) +* bug #4436 MethodArgumentSpaceFixer - handle misplaced ) (keradus) +* bug #4439 NoLeadingImportSlashFixer - Add space if needed (SpacePossum) +* bug #4440 SimpleToComplexStringVariableFixer - Fix $ bug (dmvdbrugge) +* bug #4453 Fix preg_match error on 7.4snapshot (kubawerlos) +* bug #4461 IsNullFixer - fix null coalescing operator handling (linniksa) +* bug #4467 ToolInfo - fix access to reference without checking existence (black-silence) +* bug #4472 Fix non-static closure unbinding this on PHP 7.4 (kelunik) +* minor #3726 Use Box 3 to build the PHAR (theofidry, keradus) +* minor #4412 PHP 7.4 - Tests for support (SpacePossum) +* minor #4431 DX: test that default config is not passed in RuleSet (kubawerlos) +* minor #4433 DX: test to ensure @PHPUnitMigration rule sets are correctly defined (kubawerlos) +* minor #4445 DX: static call of markTestSkippedOrFail (kubawerlos) +* minor #4463 Add apostrophe to possessive "team's" (ChandlerSwift) +* minor #4471 ReadmeCommandTest - use CommandTester (kubawerlos) +* minor #4477 DX: control names of public methods in test's classes (kubawerlos) +* minor #4483 NewWithBracesFixer - Fix object operator and curly brace open cases (SpacePossum) +* minor #4484 fix typos in README (Sven Ludwig) +* minor #4494 DX: Fix shell script syntax in order to fix Travis builds (drupol) +* minor #4516 DX: Lock binary SCA tools versions (keradus) + +Changelog for v2.15.1 +--------------------- + +* bug #4418 PhpUnitNamespacedFixer - properly translate classes which do not follow translation pattern (ktomk) +* bug #4419 PhpUnitTestCaseStaticMethodCallsFixer - skip anonymous classes and lambda (SpacePossum) +* bug #4420 MethodArgumentSpaceFixer - PHP7.3 trailing commas in function calls (SpacePossum) +* minor #4345 Travis: PHP 7.4 isn't allowed to fail anymore (Slamdunk) +* minor #4403 LowercaseStaticReferenceFixer - Fix invalid PHP version in example (HypeMC) +* minor #4424 DX: cleanup of composer.json - no need for branch-alias (keradus) +* minor #4425 DX: assertions are static, adjust custom assertions (keradus) +* minor #4426 DX: handle deprecations of symfony/event-dispatcher:4.3 (keradus) +* minor #4427 DX: stop using reserved T_FN in code samples (keradus) +* minor #4428 DX: update dev-tools (keradus) +* minor #4429 DX: MethodArgumentSpaceFixerTest - fix hidden merge conflict (keradus) + +Changelog for v2.15.0 +--------------------- + +* feature #3927 Add FinalClassFixer (Slamdunk) +* feature #3939 Add PhpUnitSizeClassFixer (Jefersson Nathan) +* feature #3942 SimpleToComplexStringVariableFixer - Introduction (dmvdbrugge, SpacePossum) +* feature #4113 OrderedInterfacesFixer - Introduction (dmvdbrugge) +* feature #4121 SingleTraitInsertPerStatementFixer - Introduction (SpacePossum) +* feature #4126 NativeFunctionTypeDeclarationCasingFixer - Introduction (SpacePossum) +* feature #4167 PhpUnitMockShortWillReturnFixer - Introduction (michadam-pearson) +* feature #4191 [7.3] NoWhitespaceBeforeCommaInArrayFixer - fix comma after heredoc-end (gharlan) +* feature #4288 Add Gitlab Reporter (hco) +* feature #4328 Add PhpUnitDedicateAssertInternalTypeFixer (Slamdunk) +* feature #4341 [7.3] TrailingCommaInMultilineArrayFixer - fix comma after heredoc-end (gharlan) +* feature #4342 [7.3] MethodArgumentSpaceFixer - fix comma after heredoc-end (gharlan) +* minor #4112 NoSuperfluousPhpdocTagsFixer - Add missing code sample, groom tests (keradus, SpacePossum) +* minor #4360 Add gitlab as output format in the README/help doc. (SpacePossum) +* minor #4386 Add PhpUnitMockShortWillReturnFixer to @Symfony:risky rule set (kubawerlos) +* minor #4398 New ruleset "@PHP73Migration" (gharlan) +* minor #4399 Fix 2.15 line (keradus) + +Changelog for v2.14.6 +--------------------- + +* bug #4533 Revert PHP7.4 - Add "str_split" => "mb_str_split" mapping (keradus) +* minor #4264 DX: AutoReview - ensure Travis handle all needed PHP versions (keradus) +* minor #4524 MethodArgumentSpaceFixerTest - make explicit configuration to prevent fail on configuration change (keradus) + +Changelog for v2.14.5 +--------------------- + +* bug #4132 BlankLineAfterNamespaceFixer - do not remove indent, handle comments (kubawerlos) +* bug #4384 MethodArgumentSpaceFixer - fix for on_multiline:ensure_fully_multiline with trailing comma in function call (kubawerlos) +* bug #4404 FileLintingIterator - fix current value on end/invalid (SpacePossum) +* bug #4421 FunctionTypehintSpaceFixer - Ensure single space between type declaration and parameter (localheinz) +* bug #4436 MethodArgumentSpaceFixer - handle misplaced ) (keradus) +* bug #4439 NoLeadingImportSlashFixer - Add space if needed (SpacePossum) +* bug #4453 Fix preg_match error on 7.4snapshot (kubawerlos) +* bug #4461 IsNullFixer - fix null coalescing operator handling (linniksa) +* bug #4467 ToolInfo - fix access to reference without checking existence (black-silence) +* bug #4472 Fix non-static closure unbinding this on PHP 7.4 (kelunik) +* minor #3726 Use Box 3 to build the PHAR (theofidry, keradus) +* minor #4412 PHP 7.4 - Tests for support (SpacePossum) +* minor #4431 DX: test that default config is not passed in RuleSet (kubawerlos) +* minor #4433 DX: test to ensure @PHPUnitMigration rule sets are correctly defined (kubawerlos) +* minor #4445 DX: static call of markTestSkippedOrFail (kubawerlos) +* minor #4463 Add apostrophe to possessive "team's" (ChandlerSwift) +* minor #4471 ReadmeCommandTest - use CommandTester (kubawerlos) +* minor #4477 DX: control names of public methods in test's classes (kubawerlos) +* minor #4483 NewWithBracesFixer - Fix object operator and curly brace open cases (SpacePossum) +* minor #4484 fix typos in README (Sven Ludwig) +* minor #4494 DX: Fix shell script syntax in order to fix Travis builds (drupol) +* minor #4516 DX: Lock binary SCA tools versions (keradus) + +Changelog for v2.14.4 +--------------------- + +* bug #4418 PhpUnitNamespacedFixer - properly translate classes which do not follow translation pattern (ktomk) +* bug #4419 PhpUnitTestCaseStaticMethodCallsFixer - skip anonymous classes and lambda (SpacePossum) +* bug #4420 MethodArgumentSpaceFixer - PHP7.3 trailing commas in function calls (SpacePossum) +* minor #4345 Travis: PHP 7.4 isn't allowed to fail anymore (Slamdunk) +* minor #4403 LowercaseStaticReferenceFixer - Fix invalid PHP version in example (HypeMC) +* minor #4425 DX: assertions are static, adjust custom assertions (keradus) +* minor #4426 DX: handle deprecations of symfony/event-dispatcher:4.3 (keradus) +* minor #4427 DX: stop using reserved T_FN in code samples (keradus) +* minor #4428 DX: update dev-tools (keradus) + +Changelog for v2.14.3 +--------------------- + +* bug #4298 NoTrailingWhitespaceInCommentFixer - fix for non-Unix line separators (kubawerlos) +* bug #4303 FullyQualifiedStrictTypesFixer - Fix the short type detection when a question mark (nullable) is prefixing it. (drupol) +* bug #4313 SelfAccessorFixer - fix for part qualified class name (kubawerlos, SpacePossum) +* bug #4314 PhpUnitTestCaseStaticMethodCallsFixer - fix for having property with name as method to update (kubawerlos, SpacePossum) +* bug #4316 NoUnsetCastFixer - Test for higher-precedence operators (SpacePossum) +* bug #4327 TokensAnalyzer - add concat operator to list of binary operators (SpacePossum) +* bug #4335 Cache - add indent and line ending to cache signature (dmvdbrugge) +* bug #4344 VoidReturnFixer - handle yield from (SpacePossum) +* bug #4346 BracesFixer - Do not pull close tag onto same line as a comment (SpacePossum) +* bug #4350 StrictParamFixer - Don't detect functions in use statements (bolmstedt) +* bug #4357 Fix short list syntax detection. (SpacePossum) +* bug #4365 Fix output escaping of diff for text format when line is not changed (SpacePossum) +* bug #4370 PhpUnitConstructFixer - Fix handle different casing (SpacePossum) +* bug #4379 ExplicitStringVariableFixer - add test case for variable as an array key (kubawerlos, Slamdunk) +* feature #4337 PhpUnitTestCaseStaticMethodCallsFixer - prepare for PHPUnit 8 (kubawerlos) +* minor #3799 DX: php_unit_test_case_static_method_calls - use default config (keradus) +* minor #4103 NoExtraBlankLinesFixer - fix candidate detection (SpacePossum) +* minor #4245 LineEndingFixer - BracesFixer - Priority (dmvdbrugge) +* minor #4325 Use lowercase mikey179/vfsStream in composer.json (lolli42) +* minor #4336 Collect coverage with PCOV (kubawerlos) +* minor #4338 Fix wording (kmvan, kubawerlos) +* minor #4339 Change BracesFixer to avoid indenting PHP inline braces (alecgeatches) +* minor #4340 Travis: build against 7.4snapshot instead of nightly (Slamdunk) +* minor #4351 code grooming (SpacePossum) +* minor #4353 Add more priority tests (SpacePossum) +* minor #4364 DX: MethodChainingIndentationFixer - remove unnecessary loop (Sijun Zhu) +* minor #4366 Unset the auxiliary variable $a (GrahamCampbell) +* minor #4368 Fixed TypeShortNameResolverTest::testResolver (GrahamCampbell) +* minor #4380 PHP7.4 - Add "str_split" => "mb_str_split" mapping. (SpacePossum) +* minor #4381 PHP7.4 - Add support for magic methods (un)serialize. (SpacePossum) +* minor #4393 DX: add missing explicit return types (kubawerlos) + +Changelog for v2.14.2 +--------------------- + +* minor #4306 DX: Drop HHVM conflict on Composer level to help Composer with HHVM compatibility, we still prevent HHVM on runtime (keradus) + +Changelog for v2.14.1 +--------------------- + +* bug #4240 ModernizeTypesCastingFixer - fix for operators with higher precedence (kubawerlos) +* bug #4254 PhpUnitDedicateAssertFixer - fix for count with additional operations (kubawerlos) +* bug #4260 Psr0Fixer and Psr4Fixer - fix for multiple classes in file with anonymous class (kubawerlos) +* bug #4262 FixCommand - fix help (keradus) +* bug #4276 MethodChainingIndentationFixer, ArrayIndentationFixer - Fix priority issue (dmvdbrugge) +* bug #4280 MethodArgumentSpaceFixer - Fix method argument alignment (Billz95) +* bug #4286 IncrementStyleFixer - fix for static statement (kubawerlos) +* bug #4291 ArrayIndentationFixer - Fix indentation after trailing spaces (julienfalque, keradus) +* bug #4292 NoSuperfluousPhpdocTagsFixer - Make null only type not considered superfluous (julienfalque) +* minor #4204 DX: Tokens - do not unregister/register found tokens when collection is not changing (kubawerlos) +* minor #4235 DX: more specific @param types (kubawerlos) +* minor #4263 DX: AppVeyor - bump PHP version (keradus) +* minor #4293 Add official support for PHP 7.3 (keradus) +* minor #4295 DX: MethodArgumentSpaceFixerTest - fix edge case for handling different line ending when only expected code is provided (keradus) +* minor #4296 DX: cleanup testing with fixer config (keradus) +* minor #4299 NativeFunctionInvocationFixer - add array_key_exists (deguif, keradus) +* minor #4300 DX: cleanup testing with fixer config (keradus) + +Changelog for v2.14.0 +--------------------- + +* bug #4220 NativeFunctionInvocationFixer - namespaced strict to remove backslash (kubawerlos) +* feature #3881 Add PhpdocVarAnnotationCorrectOrderFixer (kubawerlos) +* feature #3915 Add HeredocIndentationFixer (gharlan) +* feature #4002 NoSuperfluousPhpdocTagsFixer - Allow `mixed` in superfluous PHPDoc by configuration (MortalFlesh) +* feature #4030 Add get_required_files and user_error aliases (ntzm) +* feature #4043 NativeFunctionInvocationFixer - add option to remove redundant backslashes (kubawerlos) +* feature #4102 Add NoUnsetCastFixer (SpacePossum) +* minor #4025 Add phpdoc_types_order rule to Symfony's ruleset (carusogabriel) +* minor #4213 [7.3] PHP7.3 integration tests (SpacePossum) +* minor #4233 Add official support for PHP 7.3 (keradus) + +Changelog for v2.13.3 +--------------------- + +* bug #4216 Psr4Fixer - fix for multiple classy elements in file (keradus, kubawerlos) +* bug #4217 Psr0Fixer - class with anonymous class (kubawerlos) +* bug #4219 NativeFunctionCasingFixer - handle T_RETURN_REF (kubawerlos) +* bug #4224 FunctionToConstantFixer - handle T_RETURN_REF (SpacePossum) +* bug #4229 IsNullFixer - fix parenthesis not closed (guilliamxavier) +* minor #4193 [7.3] CombineNestedDirnameFixer - support PHP 7.3 (kubawerlos) +* minor #4198 [7.3] PowToExponentiationFixer - adding to PHP7.3 integration test (kubawerlos) +* minor #4199 [7.3] MethodChainingIndentationFixer - add tests for PHP 7.3 (kubawerlos) +* minor #4200 [7.3] ModernizeTypesCastingFixer - support PHP 7.3 (kubawerlos) +* minor #4201 [7.3] MultilineWhitespaceBeforeSemicolonsFixer - add tests for PHP 7.3 (kubawerlos) +* minor #4202 [7.3] ErrorSuppressionFixer - support PHP 7.3 (kubawerlos) +* minor #4205 DX: PhpdocAlignFixer - refactor to use DocBlock (kubawerlos) +* minor #4206 DX: enable multiline_whitespace_before_semicolons (keradus) +* minor #4207 [7.3] RandomApiMigrationFixerTest - tests for 7.3 (SpacePossum) +* minor #4208 [7.3] NativeFunctionCasingFixerTest - tests for 7.3 (SpacePossum) +* minor #4209 [7.3] PhpUnitStrictFixerTest - tests for 7.3 (SpacePossum) +* minor #4210 [7.3] PhpUnitConstructFixer - add test for PHP 7.3 (kubawerlos) +* minor #4211 [7.3] PhpUnitDedicateAssertFixer - support PHP 7.3 (kubawerlos) +* minor #4214 [7.3] NoUnsetOnPropertyFixerTest - tests for 7.3 (SpacePossum) +* minor #4222 [7.3] PhpUnitExpectationFixer - support PHP 7.3 (kubawerlos) +* minor #4223 [7.3] PhpUnitMockFixer - add tests for PHP 7.3 (kubawerlos) +* minor #4230 [7.3] IsNullFixer - fix trailing comma (guilliamxavier) +* minor #4232 DX: remove Utils::splitLines (kubawerlos) +* minor #4234 [7.3] Test that "LITERAL instanceof X" is valid (guilliamxavier) + +Changelog for v2.13.2 +--------------------- + +* bug #3968 SelfAccessorFixer - support FQCN (kubawerlos) +* bug #3974 Psr4Fixer - class with anonymous class (kubawerlos) +* bug #3987 Run HeaderCommentFixer after NoBlankLinesAfterPhpdocFixer (StanAngeloff) +* bug #4009 TypeAlternationTransformer - Fix pipes in function call with constants being classified incorrectly (ntzm, SpacePossum) +* bug #4022 NoUnsetOnPropertyFixer - refactor and bugfixes (kubawerlos) +* bug #4036 ExplicitStringVariableFixer - fixes for backticks and for 2 variables next to each other (kubawerlos, Slamdunk) +* bug #4038 CommentToPhpdocFixer - handling nested PHPDoc (kubawerlos) +* bug #4064 Ignore invalid mode strings, add option to remove the "b" flag. (SpacePossum) +* bug #4071 DX: do not insert Token when calling removeLeadingWhitespace/removeTrailingWhitespace from Tokens (kubawerlos) +* bug #4073 IsNullFixer - fix function detection (kubawerlos) +* bug #4074 FileFilterIterator - do not filter out files that need fixing (SpacePossum) +* bug #4076 EregToPregFixer - fix function detection (kubawerlos) +* bug #4084 MethodChainingIndentation - fix priority with Braces (dmvdbrugge) +* bug #4099 HeaderCommentFixer - throw exception on invalid header configuration (SpacePossum) +* bug #4100 PhpdocAddMissingParamAnnotationFixer - Handle variable number of arguments and pass by reference cases (SpacePossum) +* bug #4101 ReturnAssignmentFixer - do not touch invalid code (SpacePossum) +* bug #4104 Change transformers order, fixing untransformed T_USE (dmvdbrugge) +* bug #4107 Preg::split - fix for non-UTF8 subject (ostrolucky, kubawerlos) +* bug #4109 NoBlankLines*: fix removing lines consisting only of spaces (kubawerlos, keradus) +* bug #4114 VisibilityRequiredFixer - don't remove comments (kubawerlos) +* bug #4116 OrderedImportsFixer - fix sorting without any grouping (SpacePossum) +* bug #4119 PhpUnitNoExpectationAnnotationFixer - fix extracting content from annotation (kubawerlos) +* bug #4127 LowercaseConstantsFixer - Fix case with properties using constants as their name (srathbone) +* bug #4134 [7.3] SquareBraceTransformer - nested array destructuring not handled correctly (SpacePossum) +* bug #4153 PhpUnitFqcnAnnotationFixer - handle only PhpUnit classes (kubawerlos) +* bug #4169 DirConstantFixer - Fixes for PHP7.3 syntax (SpacePossum) +* bug #4181 MultilineCommentOpeningClosingFixer - fix handling empty comment (kubawerlos) +* bug #4186 Tokens - fix removal of leading/trailing whitespace with empty token in collection (kubawerlos) +* minor #3436 Add a handful of integration tests (BackEndTea) +* minor #3774 PhpUnitTestClassRequiresCoversFixer - Remove unneeded loop and use phpunit indicator class (BackEndTea, SpacePossum) +* minor #3778 DX: Throw an exception if FileReader::read fails (ntzm) +* minor #3916 New ruleset "@PhpCsFixer" (gharlan) +* minor #4007 Fixes cookbook for fixers (greeflas) +* minor #4031 Correct FixerOptionBuilder::getOption return type (ntzm) +* minor #4046 Token - Added fast isset() path to token->equals() (staabm) +* minor #4047 Token - inline $other->getPrototype() to speedup equals() (staabm, keradus) +* minor #4048 Tokens - inlined extractTokenKind() call on the hot path (staabm) +* minor #4069 DX: Add dev-tools directory to gitattributes as export-ignore (alexmanno) +* minor #4070 Docs: Add link to a VS Code extension in readme (jakebathman) +* minor #4077 DX: cleanup - NoAliasFunctionsFixer - use FunctionsAnalyzer (kubawerlos) +* minor #4088 Add Travis test with strict types (kubawerlos) +* minor #4091 Adjust misleading sentence in CONTRIBUTING.md (ostrolucky) +* minor #4092 UseTransformer - simplify/optimize (SpacePossum) +* minor #4095 DX: Use ::class (keradus) +* minor #4096 DX: fixing typo (kubawerlos) +* minor #4097 DX: namespace casing (kubawerlos) +* minor #4110 Enhancement: Update localheinz/composer-normalize (localheinz) +* minor #4115 Changes for upcoming Travis' infra migration (sergeyklay) +* minor #4122 DX: AppVeyor - Update Composer download link (SpacePossum) +* minor #4128 DX: cleanup - AbstractFunctionReferenceFixer - use FunctionsAnalyzer (SpacePossum, kubawerlos) +* minor #4129 Fix: Symfony 4.2 deprecations (kubawerlos) +* minor #4139 DX: Fix CircleCI (kubawerlos) +* minor #4142 [7.3] NoAliasFunctionsFixer - mbregex_encoding' => 'mb_regex_encoding (SpacePossum) +* minor #4143 PhpUnitTestCaseStaticMethodCallsFixer - Add PHPUnit 7.5 new assertions (Slamdunk) +* minor #4149 [7.3] ArgumentsAnalyzer - PHP7.3 support (SpacePossum) +* minor #4161 DX: CI - show packages installed via Composer (keradus) +* minor #4162 DX: Drop symfony/lts (keradus) +* minor #4166 DX: do not use AbstractFunctionReferenceFixer when no need to (kubawerlos) +* minor #4168 DX: FopenFlagsFixer - remove useless proxy method (SpacePossum) +* minor #4171 Fix CircleCI cache (kubawerlos) +* minor #4173 [7.3] PowToExponentiationFixer - add support for PHP7.3 (SpacePossum) +* minor #4175 Fixing typo (kubawerlos) +* minor #4177 CI: Check that tag is matching version of PHP CS Fixer during deployment (keradus) +* minor #4180 Fixing typo (kubawerlos) +* minor #4182 DX: update php-cs-fixer file style (kubawerlos) +* minor #4185 [7.3] ImplodeCallFixer - add tests for PHP7.3 (kubawerlos) +* minor #4187 [7.3] IsNullFixer - support PHP 7.3 (kubawerlos) +* minor #4188 DX: cleanup (keradus) +* minor #4189 Travis - add PHP 7.3 job (keradus) +* minor #4190 Travis CI - fix config (kubawerlos) +* minor #4192 [7.3] MagicMethodCasingFixer - add tests for PHP 7.3 (kubawerlos) +* minor #4194 [7.3] NativeFunctionInvocationFixer - add tests for PHP 7.3 (kubawerlos) +* minor #4195 [7.3] SetTypeToCastFixer - support PHP 7.3 (kubawerlos) +* minor #4196 Update website (keradus) +* minor #4197 [7.3] StrictParamFixer - support PHP 7.3 (kubawerlos) + +Changelog for v2.13.1 +--------------------- + +* bug #3977 NoSuperfluousPhpdocTagsFixer - Fix handling of description with variable (julienfalque) +* bug #4027 PhpdocAnnotationWithoutDotFixer - add failing cases (keradus) +* bug #4028 PhpdocNoEmptyReturnFixer - handle single line PHPDoc (kubawerlos) +* bug #4034 PhpUnitTestCaseIndicator - handle anonymous class (kubawerlos) +* bug #4037 NativeFunctionInvocationFixer - fix function detection (kubawerlos) +* feature #4019 PhpdocTypesFixer - allow for configuration (keradus) +* minor #3980 Clarifies allow-risky usage (josephzidell) +* minor #4016 Bump console component due to it's bug (keradus) +* minor #4023 Enhancement: Update localheinz/composer-normalize (localheinz) +* minor #4049 use parent::offset*() methods when moving items around in insertAt() (staabm) + +Changelog for v2.13.0 +--------------------- + +* feature #3739 Add MagicMethodCasingFixer (SpacePossum) +* feature #3812 Add FopenFlagOrderFixer & FopenFlagsFixer (SpacePossum) +* feature #3826 Add CombineNestedDirnameFixer (gharlan) +* feature #3833 BinaryOperatorSpacesFixer - Add "no space" fix strategy (SpacePossum) +* feature #3841 NoAliasFunctionsFixer - add opt in option for ext-mbstring aliases (SpacePossum) +* feature #3876 NativeConstantInvocationFixer - add the scope option (stof, keradus) +* feature #3886 Add PhpUnitMethodCasingFixer (Slamdunk) +* feature #3907 Add ImplodeCallFixer (kubawerlos) +* feature #3914 NoUnreachableDefaultArgumentValueFixer - remove `null` for nullable typehints (gharlan, keradus) +* minor #3813 PhpUnitDedicateAssertFixer - fix "sizeOf" same as "count". (SpacePossum) +* minor #3873 Add the native_function_invocation fixer in the Symfony:risky ruleset (stof) +* minor #3979 DX: enable php_unit_method_casing (keradus) + +Changelog for v2.12.12 +---------------------- + +* bug #4533 Revert PHP7.4 - Add "str_split" => "mb_str_split" mapping (keradus) +* minor #4264 DX: AutoReview - ensure Travis handle all needed PHP versions (keradus) +* minor #4524 MethodArgumentSpaceFixerTest - make explicit configuration to prevent fail on configuration change (keradus) + +Changelog for v2.12.11 +---------------------- + +* bug #4132 BlankLineAfterNamespaceFixer - do not remove indent, handle comments (kubawerlos) +* bug #4384 MethodArgumentSpaceFixer - fix for on_multiline:ensure_fully_multiline with trailing comma in function call (kubawerlos) +* bug #4404 FileLintingIterator - fix current value on end/invalid (SpacePossum) +* bug #4421 FunctionTypehintSpaceFixer - Ensure single space between type declaration and parameter (localheinz) +* bug #4436 MethodArgumentSpaceFixer - handle misplaced ) (keradus) +* bug #4439 NoLeadingImportSlashFixer - Add space if needed (SpacePossum) +* bug #4453 Fix preg_match error on 7.4snapshot (kubawerlos) +* bug #4461 IsNullFixer - fix null coalescing operator handling (linniksa) +* bug #4467 ToolInfo - fix access to reference without checking existence (black-silence) +* bug #4472 Fix non-static closure unbinding this on PHP 7.4 (kelunik) +* minor #3726 Use Box 3 to build the PHAR (theofidry, keradus) +* minor #4412 PHP 7.4 - Tests for support (SpacePossum) +* minor #4431 DX: test that default config is not passed in RuleSet (kubawerlos) +* minor #4433 DX: test to ensure @PHPUnitMigration rule sets are correctly defined (kubawerlos) +* minor #4445 DX: static call of markTestSkippedOrFail (kubawerlos) +* minor #4463 Add apostrophe to possessive "team's" (ChandlerSwift) +* minor #4471 ReadmeCommandTest - use CommandTester (kubawerlos) +* minor #4477 DX: control names of public methods in test's classes (kubawerlos) +* minor #4483 NewWithBracesFixer - Fix object operator and curly brace open cases (SpacePossum) +* minor #4484 fix typos in README (Sven Ludwig) +* minor #4494 DX: Fix shell script syntax in order to fix Travis builds (drupol) +* minor #4516 DX: Lock binary SCA tools versions (keradus) + +Changelog for v2.12.10 +---------------------- + +* bug #4418 PhpUnitNamespacedFixer - properly translate classes which do not follow translation pattern (ktomk) +* bug #4419 PhpUnitTestCaseStaticMethodCallsFixer - skip anonymous classes and lambda (SpacePossum) +* bug #4420 MethodArgumentSpaceFixer - PHP7.3 trailing commas in function calls (SpacePossum) +* minor #4345 Travis: PHP 7.4 isn't allowed to fail anymore (Slamdunk) +* minor #4403 LowercaseStaticReferenceFixer - Fix invalid PHP version in example (HypeMC) +* minor #4425 DX: assertions are static, adjust custom assertions (keradus) +* minor #4426 DX: handle deprecations of symfony/event-dispatcher:4.3 (keradus) +* minor #4427 DX: stop using reserved T_FN in code samples (keradus) + +Changelog for v2.12.9 +--------------------- + +* bug #4298 NoTrailingWhitespaceInCommentFixer - fix for non-Unix line separators (kubawerlos) +* bug #4303 FullyQualifiedStrictTypesFixer - Fix the short type detection when a question mark (nullable) is prefixing it. (drupol) +* bug #4313 SelfAccessorFixer - fix for part qualified class name (kubawerlos, SpacePossum) +* bug #4314 PhpUnitTestCaseStaticMethodCallsFixer - fix for having property with name as method to update (kubawerlos, SpacePossum) +* bug #4327 TokensAnalyzer - add concat operator to list of binary operators (SpacePossum) +* bug #4335 Cache - add indent and line ending to cache signature (dmvdbrugge) +* bug #4344 VoidReturnFixer - handle yield from (SpacePossum) +* bug #4346 BracesFixer - Do not pull close tag onto same line as a comment (SpacePossum) +* bug #4350 StrictParamFixer - Don't detect functions in use statements (bolmstedt) +* bug #4357 Fix short list syntax detection. (SpacePossum) +* bug #4365 Fix output escaping of diff for text format when line is not changed (SpacePossum) +* bug #4370 PhpUnitConstructFixer - Fix handle different casing (SpacePossum) +* bug #4379 ExplicitStringVariableFixer - add test case for variable as an array key (kubawerlos, Slamdunk) +* feature #4337 PhpUnitTestCaseStaticMethodCallsFixer - prepare for PHPUnit 8 (kubawerlos) +* minor #3799 DX: php_unit_test_case_static_method_calls - use default config (keradus) +* minor #4103 NoExtraBlankLinesFixer - fix candidate detection (SpacePossum) +* minor #4245 LineEndingFixer - BracesFixer - Priority (dmvdbrugge) +* minor #4325 Use lowercase mikey179/vfsStream in composer.json (lolli42) +* minor #4336 Collect coverage with PCOV (kubawerlos) +* minor #4338 Fix wording (kmvan, kubawerlos) +* minor #4339 Change BracesFixer to avoid indenting PHP inline braces (alecgeatches) +* minor #4340 Travis: build against 7.4snapshot instead of nightly (Slamdunk) +* minor #4351 code grooming (SpacePossum) +* minor #4353 Add more priority tests (SpacePossum) +* minor #4364 DX: MethodChainingIndentationFixer - remove unnecessary loop (Sijun Zhu) +* minor #4366 Unset the auxiliary variable $a (GrahamCampbell) +* minor #4368 Fixed TypeShortNameResolverTest::testResolver (GrahamCampbell) +* minor #4380 PHP7.4 - Add "str_split" => "mb_str_split" mapping. (SpacePossum) +* minor #4393 DX: add missing explicit return types (kubawerlos) + +Changelog for v2.12.8 +--------------------- + +* minor #4306 DX: Drop HHVM conflict on Composer level to help Composer with HHVM compatibility, we still prevent HHVM on runtime (keradus) + +Changelog for v2.12.7 +--------------------- + +* bug #4240 ModernizeTypesCastingFixer - fix for operators with higher precedence (kubawerlos) +* bug #4254 PhpUnitDedicateAssertFixer - fix for count with additional operations (kubawerlos) +* bug #4260 Psr0Fixer and Psr4Fixer - fix for multiple classes in file with anonymous class (kubawerlos) +* bug #4262 FixCommand - fix help (keradus) +* bug #4276 MethodChainingIndentationFixer, ArrayIndentationFixer - Fix priority issue (dmvdbrugge) +* bug #4280 MethodArgumentSpaceFixer - Fix method argument alignment (Billz95) +* bug #4286 IncrementStyleFixer - fix for static statement (kubawerlos) +* bug #4291 ArrayIndentationFixer - Fix indentation after trailing spaces (julienfalque, keradus) +* bug #4292 NoSuperfluousPhpdocTagsFixer - Make null only type not considered superfluous (julienfalque) +* minor #4204 DX: Tokens - do not unregister/register found tokens when collection is not changing (kubawerlos) +* minor #4235 DX: more specific @param types (kubawerlos) +* minor #4263 DX: AppVeyor - bump PHP version (keradus) +* minor #4293 Add official support for PHP 7.3 (keradus) +* minor #4295 DX: MethodArgumentSpaceFixerTest - fix edge case for handling different line ending when only expected code is provided (keradus) +* minor #4296 DX: cleanup testing with fixer config (keradus) +* minor #4299 NativeFunctionInvocationFixer - add array_key_exists (deguif, keradus) + +Changelog for v2.12.6 +--------------------- + +* bug #4216 Psr4Fixer - fix for multiple classy elements in file (keradus, kubawerlos) +* bug #4217 Psr0Fixer - class with anonymous class (kubawerlos) +* bug #4219 NativeFunctionCasingFixer - handle T_RETURN_REF (kubawerlos) +* bug #4224 FunctionToConstantFixer - handle T_RETURN_REF (SpacePossum) +* bug #4229 IsNullFixer - fix parenthesis not closed (guilliamxavier) +* minor #4198 [7.3] PowToExponentiationFixer - adding to PHP7.3 integration test (kubawerlos) +* minor #4199 [7.3] MethodChainingIndentationFixer - add tests for PHP 7.3 (kubawerlos) +* minor #4200 [7.3] ModernizeTypesCastingFixer - support PHP 7.3 (kubawerlos) +* minor #4201 [7.3] MultilineWhitespaceBeforeSemicolonsFixer - add tests for PHP 7.3 (kubawerlos) +* minor #4202 [7.3] ErrorSuppressionFixer - support PHP 7.3 (kubawerlos) +* minor #4205 DX: PhpdocAlignFixer - refactor to use DocBlock (kubawerlos) +* minor #4206 DX: enable multiline_whitespace_before_semicolons (keradus) +* minor #4207 [7.3] RandomApiMigrationFixerTest - tests for 7.3 (SpacePossum) +* minor #4208 [7.3] NativeFunctionCasingFixerTest - tests for 7.3 (SpacePossum) +* minor #4209 [7.3] PhpUnitStrictFixerTest - tests for 7.3 (SpacePossum) +* minor #4210 [7.3] PhpUnitConstructFixer - add test for PHP 7.3 (kubawerlos) +* minor #4211 [7.3] PhpUnitDedicateAssertFixer - support PHP 7.3 (kubawerlos) +* minor #4214 [7.3] NoUnsetOnPropertyFixerTest - tests for 7.3 (SpacePossum) +* minor #4222 [7.3] PhpUnitExpectationFixer - support PHP 7.3 (kubawerlos) +* minor #4223 [7.3] PhpUnitMockFixer - add tests for PHP 7.3 (kubawerlos) +* minor #4230 [7.3] IsNullFixer - fix trailing comma (guilliamxavier) +* minor #4232 DX: remove Utils::splitLines (kubawerlos) +* minor #4234 [7.3] Test that "LITERAL instanceof X" is valid (guilliamxavier) + +Changelog for v2.12.5 +--------------------- + +* bug #3968 SelfAccessorFixer - support FQCN (kubawerlos) +* bug #3974 Psr4Fixer - class with anonymous class (kubawerlos) +* bug #3987 Run HeaderCommentFixer after NoBlankLinesAfterPhpdocFixer (StanAngeloff) +* bug #4009 TypeAlternationTransformer - Fix pipes in function call with constants being classified incorrectly (ntzm, SpacePossum) +* bug #4022 NoUnsetOnPropertyFixer - refactor and bugfixes (kubawerlos) +* bug #4036 ExplicitStringVariableFixer - fixes for backticks and for 2 variables next to each other (kubawerlos, Slamdunk) +* bug #4038 CommentToPhpdocFixer - handling nested PHPDoc (kubawerlos) +* bug #4071 DX: do not insert Token when calling removeLeadingWhitespace/removeTrailingWhitespace from Tokens (kubawerlos) +* bug #4073 IsNullFixer - fix function detection (kubawerlos) +* bug #4074 FileFilterIterator - do not filter out files that need fixing (SpacePossum) +* bug #4076 EregToPregFixer - fix function detection (kubawerlos) +* bug #4084 MethodChainingIndentation - fix priority with Braces (dmvdbrugge) +* bug #4099 HeaderCommentFixer - throw exception on invalid header configuration (SpacePossum) +* bug #4100 PhpdocAddMissingParamAnnotationFixer - Handle variable number of arguments and pass by reference cases (SpacePossum) +* bug #4101 ReturnAssignmentFixer - do not touch invalid code (SpacePossum) +* bug #4104 Change transformers order, fixing untransformed T_USE (dmvdbrugge) +* bug #4107 Preg::split - fix for non-UTF8 subject (ostrolucky, kubawerlos) +* bug #4109 NoBlankLines*: fix removing lines consisting only of spaces (kubawerlos, keradus) +* bug #4114 VisibilityRequiredFixer - don't remove comments (kubawerlos) +* bug #4116 OrderedImportsFixer - fix sorting without any grouping (SpacePossum) +* bug #4119 PhpUnitNoExpectationAnnotationFixer - fix extracting content from annotation (kubawerlos) +* bug #4127 LowercaseConstantsFixer - Fix case with properties using constants as their name (srathbone) +* bug #4134 [7.3] SquareBraceTransformer - nested array destructuring not handled correctly (SpacePossum) +* bug #4153 PhpUnitFqcnAnnotationFixer - handle only PhpUnit classes (kubawerlos) +* bug #4169 DirConstantFixer - Fixes for PHP7.3 syntax (SpacePossum) +* bug #4181 MultilineCommentOpeningClosingFixer - fix handling empty comment (kubawerlos) +* bug #4186 Tokens - fix removal of leading/trailing whitespace with empty token in collection (kubawerlos) +* minor #3436 Add a handful of integration tests (BackEndTea) +* minor #3774 PhpUnitTestClassRequiresCoversFixer - Remove unneeded loop and use phpunit indicator class (BackEndTea, SpacePossum) +* minor #3778 DX: Throw an exception if FileReader::read fails (ntzm) +* minor #3916 New ruleset "@PhpCsFixer" (gharlan) +* minor #4007 Fixes cookbook for fixers (greeflas) +* minor #4031 Correct FixerOptionBuilder::getOption return type (ntzm) +* minor #4046 Token - Added fast isset() path to token->equals() (staabm) +* minor #4047 Token - inline $other->getPrototype() to speedup equals() (staabm, keradus) +* minor #4048 Tokens - inlined extractTokenKind() call on the hot path (staabm) +* minor #4069 DX: Add dev-tools directory to gitattributes as export-ignore (alexmanno) +* minor #4070 Docs: Add link to a VS Code extension in readme (jakebathman) +* minor #4077 DX: cleanup - NoAliasFunctionsFixer - use FunctionsAnalyzer (kubawerlos) +* minor #4088 Add Travis test with strict types (kubawerlos) +* minor #4091 Adjust misleading sentence in CONTRIBUTING.md (ostrolucky) +* minor #4092 UseTransformer - simplify/optimize (SpacePossum) +* minor #4095 DX: Use ::class (keradus) +* minor #4097 DX: namespace casing (kubawerlos) +* minor #4110 Enhancement: Update localheinz/composer-normalize (localheinz) +* minor #4115 Changes for upcoming Travis' infra migration (sergeyklay) +* minor #4122 DX: AppVeyor - Update Composer download link (SpacePossum) +* minor #4128 DX: cleanup - AbstractFunctionReferenceFixer - use FunctionsAnalyzer (SpacePossum, kubawerlos) +* minor #4129 Fix: Symfony 4.2 deprecations (kubawerlos) +* minor #4139 DX: Fix CircleCI (kubawerlos) +* minor #4143 PhpUnitTestCaseStaticMethodCallsFixer - Add PHPUnit 7.5 new assertions (Slamdunk) +* minor #4149 [7.3] ArgumentsAnalyzer - PHP7.3 support (SpacePossum) +* minor #4161 DX: CI - show packages installed via Composer (keradus) +* minor #4162 DX: Drop symfony/lts (keradus) +* minor #4166 DX: do not use AbstractFunctionReferenceFixer when no need to (kubawerlos) +* minor #4171 Fix CircleCI cache (kubawerlos) +* minor #4173 [7.3] PowToExponentiationFixer - add support for PHP7.3 (SpacePossum) +* minor #4175 Fixing typo (kubawerlos) +* minor #4177 CI: Check that tag is matching version of PHP CS Fixer during deployment (keradus) +* minor #4182 DX: update php-cs-fixer file style (kubawerlos) +* minor #4187 [7.3] IsNullFixer - support PHP 7.3 (kubawerlos) +* minor #4188 DX: cleanup (keradus) +* minor #4189 Travis - add PHP 7.3 job (keradus) +* minor #4190 Travis CI - fix config (kubawerlos) +* minor #4194 [7.3] NativeFunctionInvocationFixer - add tests for PHP 7.3 (kubawerlos) +* minor #4195 [7.3] SetTypeToCastFixer - support PHP 7.3 (kubawerlos) +* minor #4196 Update website (keradus) +* minor #4197 [7.3] StrictParamFixer - support PHP 7.3 (kubawerlos) + +Changelog for v2.12.4 +--------------------- + +* bug #3977 NoSuperfluousPhpdocTagsFixer - Fix handling of description with variable (julienfalque) +* bug #4027 PhpdocAnnotationWithoutDotFixer - add failing cases (keradus) +* bug #4028 PhpdocNoEmptyReturnFixer - handle single line PHPDoc (kubawerlos) +* bug #4034 PhpUnitTestCaseIndicator - handle anonymous class (kubawerlos) +* bug #4037 NativeFunctionInvocationFixer - fix function detection (kubawerlos) +* feature #4019 PhpdocTypesFixer - allow for configuration (keradus) +* minor #3980 Clarifies allow-risky usage (josephzidell) +* minor #4016 Bump console component due to it's bug (keradus) +* minor #4023 Enhancement: Update localheinz/composer-normalize (localheinz) +* minor #4049 use parent::offset*() methods when moving items around in insertAt() (staabm) + +Changelog for v2.12.3 +--------------------- + +* bug #3867 PhpdocAnnotationWithoutDotFixer - Handle trailing whitespaces (kubawerlos) +* bug #3884 NoSuperfluousPhpdocTagsFixer - handle null in every position (dmvdbrugge, julienfalque) +* bug #3885 AlignMultilineCommentFixer - ArrayIndentationFixer - Priority (dmvdbrugge) +* bug #3887 ArrayIndentFixer - Don't indent empty lines (dmvdbrugge) +* bug #3888 NoExtraBlankLinesFixer - remove blank lines after open tag (kubawerlos) +* bug #3890 StrictParamFixer - make it case-insensitive (kubawerlos) +* bug #3895 FunctionsAnalyzer - false positive for constant and function definition (kubawerlos) +* bug #3908 StrictParamFixer - fix edge case (kubawerlos) +* bug #3910 FunctionsAnalyzer - fix isGlobalFunctionCall (gharlan) +* bug #3912 FullyQualifiedStrictTypesFixer - NoSuperfluousPhpdocTagsFixer - adjust priority (dmvdbrugge) +* bug #3913 TokensAnalyzer - fix isConstantInvocation (gharlan, keradus) +* bug #3921 TypeAnalysis - Fix iterable not being detected as a reserved type (ntzm) +* bug #3924 FullyQualifiedStrictTypesFixer - space bug (dmvdbrugge) +* bug #3937 LowercaseStaticReferenceFixer - Fix "Parent" word in namespace (kubawerlos) +* bug #3944 ExplicitStringVariableFixer - fix array handling (gharlan) +* bug #3951 NoSuperfluousPhpdocTagsFixer - do not call strtolower with null (SpacePossum) +* bug #3954 NoSuperfluousPhpdocTagsFixer - Index invalid or out of range (kubawerlos) +* bug #3957 NoTrailingWhitespaceFixer - trim space after opening tag (kubawerlos) +* minor #3798 DX: enable native_function_invocation (keradus) +* minor #3882 PhpdocAnnotationWithoutDotFixer - Handle empty line in comment (kubawerlos) +* minor #3889 DX: Cleanup - remove unused variables (kubawerlos, SpacePossum) +* minor #3891 PhpdocNoEmptyReturnFixer - account for null[] (dmvdbrugge) +* minor #3892 PhpdocNoEmptyReturnFixer - fix docs (keradus) +* minor #3897 DX: FunctionsAnalyzer - simplifying return expression (kubawerlos) +* minor #3903 DX: cleanup - remove special treatment for PHP <5.6 (kubawerlos) +* minor #3905 DX: Upgrade composer-require-checker to stable version (keradus) +* minor #3919 Simplify single uses of Token::isGivenKind (ntzm) +* minor #3920 Docs: Fix typo (ntzm) +* minor #3940 DX: fix phpdoc parameter type (malukenho) +* minor #3948 DX: cleanup - remove redundant @param annotations (kubawerlos) +* minor #3950 Circle CI v2 yml (siad007) +* minor #3952 DX: AbstractFixerTestCase - drop testing method already provided by trait (keradus) +* minor #3973 Bump xdebug-handler (keradus) + +Changelog for v2.12.2 +--------------------- + +* bug #3823 NativeConstantInvocationFixer - better constant detection (gharlan, SpacePossum, keradus) +* bug #3832 "yield from" as keyword (SpacePossum) +* bug #3835 Fix priority between PHPDoc return type fixers (julienfalque, keradus) +* bug #3839 MethodArgumentSpaceFixer - add empty line incorrectly (SpacePossum) +* bug #3866 SpaceAfterSemicolonFixer - loop over all tokens (SpacePossum) +* minor #3817 Update integrations tests (SpacePossum) +* minor #3829 Fix typos in changelog (mnabialek) +* minor #3848 Add install/update instructions for PHIVE to the README (SpacePossum) +* minor #3877 NamespacesAnalyzer - Optimize performance (stof) +* minor #3878 NativeFunctionInvocationFixer - use the NamespacesAnalyzer to remove duplicated code (stof) + +Changelog for v2.12.1 +--------------------- + +* bug #3808 LowercaseStaticReferenceFixer - Fix constants handling (kubawerlos, keradus) +* bug #3815 NoSuperfluousPhpdocTagsFixer - support array/callable type hints (gharlan) +* minor #3824 DX: Support PHPUnit 7.2 (keradus) +* minor #3825 UX: Provide full diff for code samples (keradus) + +Changelog for v2.12.0 +--------------------- + +* feature #2577 Add LogicalOperatorsFixer (hkdobrev, keradus) +* feature #3060 Add ErrorSuppressionFixer (kubawerlos) +* feature #3127 Add NativeConstantInvocationFixer (Slamdunk, keradus) +* feature #3223 NativeFunctionInvocationFixer - add namespace scope and include sets (SpacePossum) +* feature #3453 PhpdocAlignFixer - add align option (robert.ahmerov) +* feature #3476 Add PhpUnitTestCaseStaticMethodCallsFixer (Slamdunk, keradus) +* feature #3524 MethodArgumentSpaceFixer - Add ensure_single_line option (julienfalque, keradus) +* feature #3534 MultilineWhitespaceBeforeSemicolonsFixer - support static calls (ntzm) +* feature #3585 Add ReturnAssignmentFixer (SpacePossum, keradus) +* feature #3640 Add PhpdocToReturnTypeFixer (Slamdunk, keradus) +* feature #3691 Add PhpdocTrimAfterDescriptionFixer (nobuf, keradus) +* feature #3698 YodaStyleFixer - Add always_move_variable option (julienfalque, SpacePossum) +* feature #3709 Add SetTypeToCastFixer (SpacePossum) +* feature #3724 BlankLineBeforeStatementFixer - Add case and default as options (dmvdbrugge) +* feature #3734 Add NoSuperfluousPhpdocTagsFixer (julienfalque) +* feature #3735 Add LowercaseStaticReferenceFixer (kubawerlos, SpacePossum) +* feature #3737 Add NoUnsetOnPropertyFixer (BackEndTea, SpacePossum) +* feature #3745 Add PhpUnitInternalClassFixer (BackEndTea, SpacePossum, keradus) +* feature #3766 Add NoBinaryStringFixer (ntzm, SpacePossum, keradus) +* feature #3780 ShortScalarCastFixer - Change binary cast to string cast as well (ntzm) +* feature #3785 PhpUnitDedicateAssertFixer - fix to assertCount too (SpacePossum) +* feature #3802 Convert PhpdocTrimAfterDescriptionFixer into PhpdocTrimConsecutiveBlankLineSeparationFixer (keradus) +* minor #3738 ReturnAssignmentFixer description update (kubawerlos) +* minor #3761 Application: when run with FUTURE_MODE, error_reporting(-1) is done in entry file instead (keradus) +* minor #3772 DX: use PhpUnitTestCaseIndicator->isPhpUnitClass to discover PHPUnit classes (keradus) +* minor #3783 CI: Split COLLECT_COVERAGE job (keradus) +* minor #3789 DX: ProjectCodeTest.testThatDataProvidersAreCorrectlyNamed - performance optimization (keradus) +* minor #3791 DX: Fix collecting code coverage (keradus) +* minor #3792 DX: Upgrade DX deps (keradus) +* minor #3797 DX: ProjectCodeTest - shall not depends on xdebug/phpdbg anymore (keradus, SpacePossum) +* minor #3800 Symfony:risky ruleset: include set_type_to_cast rule (keradus) +* minor #3801 NativeFunctionInvocationFixer - fix buggy config validation (keradus, SpacePossum) + +Changelog for v2.11.2 +--------------------- + +* bug #3233 PhpdocAlignFixer - Fix linebreak inconsistency (SpacePossum, keradus) +* bug #3445 Rewrite NoUnusedImportsFixer (kubawerlos, julienfalque) +* bug #3528 MethodChainingIndentationFixer - nested params bugfix (Slamdunk) +* bug #3547 MultilineWhitespaceBeforeSemicolonsFixer - chained call for a return fix (egircys, keradus) +* bug #3597 DeclareStrictTypesFixer - fix bug of removing line (kubawerlos, keradus) +* bug #3605 DoctrineAnnotationIndentationFixer - Fix indentation with mixed lines (julienfalque) +* bug #3606 PhpdocToCommentFixer - allow multiple ( (SpacePossum) +* bug #3614 Refactor PhpdocToCommentFixer - extract checking to CommentsAnalyzer (kubawerlos) +* bug #3668 Rewrite NoUnusedImportsFixer (kubawerlos, julienfalque) +* bug #3670 PhpdocTypesOrderFixer - Fix ordering of nested generics (julienfalque) +* bug #3671 ArrayIndentationFixer - Fix indentation in HTML (julienfalque) +* bug #3673 PhpdocScalarFixer - Add "types" option (julienfalque, keradus) +* bug #3674 YodaStyleFixer - Fix variable detection for multidimensional arrays (julienfalque, SpacePossum) +* bug #3684 PhpUnitStrictFixer - Do not fix if not correct # of arguments are used (SpacePossum) +* bug #3708 EscapeImplicitBackslashesFixer - Fix escaping multiple backslashes (julienfalque) +* bug #3715 SingleImportPerStatementFixer - Fix handling whitespace before opening brace (julienfalque) +* bug #3731 PhpdocIndentFixer - crash fix (SpacePossum) +* bug #3755 YodaStyleFixer - handle space between var name and index (SpacePossum) +* bug #3765 Fix binary-prefixed double-quoted strings to single quotes (ntzm) +* bug #3770 Handle binary flags in heredoc_to_nowdoc (ntzm) +* bug #3776 ExplicitStringVariableFixer - handle binary strings (ntzm) +* bug #3777 EscapeImplicitBackslashesFixer - handle binary strings (ntzm) +* bug #3790 ProcessLinter - don't execute external process without timeout! It can freeze! (keradus) +* minor #3188 AppVeyor - add PHP 7.x (keradus, julienfalque) +* minor #3451 Update findPHPUnit functions (BackEndTea, SpacePossum, keradus) +* minor #3548 Make shell scripts POSIX-compatible (EvgenyOrekhov, keradus) +* minor #3568 New Autoreview: Correct option casing (ntzm) +* minor #3578 Add interface for deprecated options (julienfalque, keradus) +* minor #3590 Use XdebugHandler to avoid perormance penalty (AJenbo, keradus) +* minor #3607 PhpdocVarWithoutNameFixer - update sample with @ type (SpacePossum) +* minor #3617 Tests stability patches (Tom Klingenberg, keradus) +* minor #3622 Docs: Update descriptions (localheinz) +* minor #3627 Fix tests execution under phpdbg (keradus) +* minor #3629 ProjectFixerConfigurationTest - test rules are sorted (SpacePossum) +* minor #3639 DX: use benefits of symfony/force-lowest (keradus) +* minor #3641 Update check_trailing_spaces script with upstream (keradus) +* minor #3646 Extract SameStringsConstraint and XmlMatchesXsdConstraint (keradus) +* minor #3647 DX: Add CachingLinter for tests (keradus) +* minor #3649 Update check_trailing_spaces script with upstream (keradus) +* minor #3652 CiIntegrationTest - run tests with POSIX sh, not Bash (keradus) +* minor #3656 DX: Clean ups (SpacePossum) +* minor #3657 update phpunitgoodpractices/traits (SpacePossum, keradus) +* minor #3658 DX: Clean ups (SpacePossum) +* minor #3660 Fix do not rely on order of fixing in CiIntegrationTest (kubawerlos) +* minor #3661 Fix: covers annotation for NoAlternativeSyntaxFixerTest (kubawerlos) +* minor #3662 DX: Add Smoke/InstallViaComposerTest (keradus) +* minor #3663 DX: During deployment, run all smoke tests and don't allow to skip phar-related ones (keradus) +* minor #3665 CircleCI fix (kubawerlos) +* minor #3666 Use "set -eu" in shell scripts (EvgenyOrekhov) +* minor #3669 Document possible values for subset options (julienfalque, keradus) +* minor #3672 Remove SameStringsConstraint and XmlMatchesXsdConstraint (keradus) +* minor #3676 RunnerTest - workaround for failing Symfony v2.8.37 (kubawerlos) +* minor #3680 DX: Tokens - removeLeadingWhitespace and removeTrailingWhitespace must act in same way (SpacePossum) +* minor #3686 README.rst - Format all code-like strings in fixer descriptions (ntzm, keradus) +* minor #3692 DX: Optimize tests (julienfalque) +* minor #3700 README.rst - Format all code-like strings in fixer description (ntzm) +* minor #3701 Use correct casing for "PHPDoc" (ntzm) +* minor #3703 DX: InstallViaComposerTest - groom naming (keradus) +* minor #3704 DX: Tokens - fix naming (keradus) +* minor #3706 Update homebrew installation instructions (ntzm) +* minor #3713 Use HTTPS whenever possible (fabpot) +* minor #3723 Extend tests coverage (ntzm) +* minor #3733 Disable Composer optimized autoloader by default (julienfalque) +* minor #3748 PhpUnitStrictFixer - extend risky note (jnvsor) +* minor #3749 Make sure PHPUnit is cased correctly in fixers descriptions (kubawerlos) +* minor #3768 Improve deprecation messages (julienfalque, SpacePossum) +* minor #3773 AbstractFixerWithAliasedOptionsTestCase - don't export (keradus) +* minor #3775 Add tests for binary strings in string_line_ending (ntzm) +* minor #3779 Misc fixes (ntzm, keradus) +* minor #3796 DX: StdinTest - do not assume name of folder, into which project was cloned (keradus) +* minor #3803 NoEmptyPhpdocFixer/PhpdocAddMissingParamAnnotationFixer - missing priority test (SpacePossum, keradus) +* minor #3804 Cleanup: remove useless constructor comment (kubawerlos) +* minor #3805 Cleanup: add missing @param type (kubawerlos, keradus) + +Changelog for v2.11.1 +--------------------- + +* bug #3626 ArrayIndentationFixer: priority bug with BinaryOperatorSpacesFixer and MethodChainingIndentationFixer (Slamdunk) +* bug #3632 DateTimeImmutableFixer bug with adding tokens while iterating over them (kubawerlos) +* minor #3478 PhpUnitDedicateAssertFixer: handle static calls (Slamdunk) +* minor #3618 DateTimeImmutableFixer - grooming (keradus) + +Changelog for v2.11.0 +--------------------- + +* feature #3135 Add ArrayIndentationFixer (julienfalque) +* feature #3235 Implement StandardizeIncrementFixer (ntzm, SpacePossum) +* feature #3260 Add DateTimeImmutableFixer (kubawerlos) +* feature #3276 Transform Fully Qualified parameters and return types to short version (veewee, keradus) +* feature #3299 SingleQuoteFixer - fix single quote char (Slamdunk) +* feature #3340 Verbose LintingException after fixing (Slamdunk) +* feature #3423 FunctionToConstantFixer - add fix "get_called_class" option (SpacePossum) +* feature #3434 Add PhpUnitSetUpTearDownVisibilityFixer (BackEndTea, SpacePossum) +* feature #3442 Add CommentToPhpdocFixer (kubawerlos, keradus) +* feature #3448 OrderedClassElementsFixer - added sortAlgorithm option (meridius) +* feature #3454 Add StringLineEndingFixer (iluuu1994, SpacePossum, keradus, julienfalque) +* feature #3477 PhpUnitStrictFixer: handle static calls (Slamdunk) +* feature #3479 PhpUnitConstructFixer: handle static calls (Slamdunk) +* feature #3507 Add PhpUnitOrderedCoversFixer (Slamdunk) +* feature #3545 Add the 'none' sort algorithm to OrderedImportsFixer (EvgenyOrekhov) +* feature #3588 Add NoAlternativeSyntaxFixer (eddmash, keradus) +* minor #3414 DescribeCommand: add fixer class when verbose (Slamdunk) +* minor #3432 ConfigurationDefinitionFixerInterface - fix deprecation notice (keradus) +* minor #3527 Deprecate last param of Tokens::findBlockEnd (ntzm, keradus) +* minor #3539 Update UnifiedDiffOutputBuilder from gecko-packages/gecko-diff-output-builder usage after it was incorporated into sebastian/diff (keradus) +* minor #3549 DescribeCommand - use our Differ wrapper class, not external one directly (keradus) +* minor #3592 Support PHPUnit 7 (keradus) +* minor #3619 Travis - extend additional files list (keradus) + +Changelog for v2.10.5 +--------------------- + +* bug #3344 Fix method chaining indentation in HTML (julienfalque) +* bug #3594 ElseifFixer - Bug with alternative syntax (kubawerlos) +* bug #3600 StrictParamFixer - Fix issue when functions are imported (ntzm, keradus) +* minor #3589 FixerFactoryTest - add missing test (SpacePossum, keradus) +* minor #3610 make phar extension optional (Tom Klingenberg, keradus) +* minor #3612 Travis - allow for hhvm failures (keradus) +* minor #3615 Detect fabbot.io (julienfalque, keradus) +* minor #3616 FixerFactoryTest - Don't rely on autovivification (keradus) +* minor #3621 FixerFactoryTest - apply CS (keradus) + +Changelog for v2.10.4 +--------------------- + +* bug #3446 Add PregWrapper (kubawerlos) +* bug #3464 IncludeFixer - fix incorrect order of fixing (kubawerlos, SpacePossum) +* bug #3496 Bug in Tokens::removeLeadingWhitespace (kubawerlos, SpacePossum, keradus) +* bug #3557 AbstractDoctrineAnnotationFixer: edge case bugfix (Slamdunk) +* bug #3574 GeneralPhpdocAnnotationRemoveFixer - remove PHPDoc if no content is left (SpacePossum) +* minor #3563 DX add missing covers annotations (keradus) +* minor #3564 Use ::class keyword when possible (keradus) +* minor #3565 Use EventDispatcherInterface instead of EventDispatcher when possible (keradus) +* minor #3566 Update PHPUnitGoodPractices\Traits (keradus) +* minor #3572 DX: allow for more phpunit-speedtrap versions to support more PHPUnit versions (keradus) +* minor #3576 Fix Doctrine Annotation test cases merging (julienfalque) +* minor #3577 DoctrineAnnotationArrayAssignmentFixer - Add test case (julienfalque) + +Changelog for v2.10.3 +--------------------- + +* bug #3504 NoBlankLinesAfterPhpdocFixer - allow blank line before declare statement (julienfalque) +* bug #3522 Remove LOCK_EX (SpacePossum) +* bug #3560 SelfAccessorFixer is risky (Slamdunk) +* minor #3435 Add tests for general_phpdoc_annotation_remove (BackEndTea) +* minor #3484 Create Tokens::findBlockStart (ntzm) +* minor #3512 Add missing array typehints (ntzm) +* minor #3513 Making AppVeyor happy (kubawerlos) +* minor #3516 Use `null|type` instead of `?type` in PHPDocs (ntzm) +* minor #3518 FixerFactoryTest - Test each priority test file is listed as test (SpacePossum) +* minor #3519 Fix typo (SpacePossum) +* minor #3520 Fix typos: ran vs. run (SpacePossum) +* minor #3521 Use HTTPS (carusogabriel) +* minor #3526 Remove gecko dependency (SpacePossum, keradus, julienfalque) +* minor #3531 Backport PHPMD to LTS version to ease maintainability (keradus) +* minor #3532 Implement Tokens::findOppositeBlockEdge (ntzm) +* minor #3533 DX: SCA - drop src/Resources exclusion (keradus) +* minor #3538 Don't use third parameter of Tokens::findBlockStart (ntzm) +* minor #3542 Enhancement: Run composer-normalize on Travis CI (localheinz, keradus) +* minor #3550 AutoReview\FixerFactoryTest - fix missing priority test, mark not fully valid test as incomplete (keradus) +* minor #3555 DX: composer.json - drop branch-alias, branch is already following the version (keradus) +* minor #3556 DX: Add AutoReview/ComposerTest (keradus) +* minor #3559 Don't expose new files under Test namespace (keradus) +* minor #3561 PHPUnit5 - add in place missing compat layer for PHPUnit6 (keradus) + +Changelog for v2.10.2 +--------------------- + +* bug #3502 Fix missing file in export (keradus) + +Changelog for v2.10.1 +--------------------- + +* bug #3265 YodaFixer - fix problems of block statements followed by ternary statements (weareoutman, keradus, SpacePossum) +* bug #3367 NoUnusedImportsFixer - fix comment handling (SpacePossum, keradus) +* bug #3438 PhpUnitTestAnnotationFixer: Do not prepend with test if method is test() (localheinz, SpacePossum) +* bug #3455 NoEmptyCommentFixer - comment block detection for line ending different than LF (kubawerlos, SpacePossum) +* bug #3458 SilencedDeprecationErrorFixer - fix edge cases (kubawerlos) +* bug #3466 no_whitespace_in_blank_line and no_blank_lines_after_phpdoc fixers bug (kubawerlos, keradus) +* bug #3472 YodaStyleFixer - do not un-Yoda if right side is assignment (SpacePossum, keradus) +* bug #3492 PhpdocScalarFixer - Add callback pesudo-type to callable type (carusogabriel) +* minor #3354 Added missing types to the PhpdocTypesFixer (GrahamCampbell) +* minor #3406 Fix for escaping in README (kubawerlos) +* minor #3430 Fix integration test (SpacePossum) +* minor #3431 Add missing tests (SpacePossum) +* minor #3440 Add a handful of integration tests (BackEndTea) +* minor #3443 ConfigurableFixerInterface - not deprecated but TODO (SpacePossum) +* minor #3444 IntegrationTest - ensure tests in priority dir are priority tests indeed (keradus) +* minor #3494 Add missing PHPDoc param type (ntzm) +* minor #3495 Swap @var type and element (ntzm) +* minor #3498 NoUnusedImportsFixer - fix deprecation (keradus) + +Changelog for v2.10.0 +--------------------- + +* feature #3290 Add PhpdocOpeningClosingFixer (Slamdunk, keradus) +* feature #3327 Add MultilineWhitespaceBeforeSemicolonsFixer (egircys, keradus) +* feature #3351 PhuUnit: migrate getMock to createPartialMock when arguments count is 2 (Slamdunk) +* feature #3362 Add BacktickToShellExecFixer (Slamdunk) +* minor #3285 PHPUnit - use protective traits (keradus) +* minor #3329 ConfigurationResolver - detect deprecated fixers (keradus, SpacePossum) +* minor #3343 Tokens - improve block end lookup (keradus) +* minor #3360 Adjust Symfony ruleset (keradus) +* minor #3361 no_extra_consecutive_blank_lines - rename to no_extra_blank_lines (with BC layer) (keradus) +* minor #3363 progress-type - name main option value 'dots' (keradus) +* minor #3404 Deprecate "use_yoda_style" in IsNullFixer (kubawerlos, keradus) +* minor #3418 ConfigurableFixerInterface, ConfigurationDefinitionFixerInterface - update deprecations (keradus) +* minor #3419 Dont use deprecated fixer in itest (keradus) + +Changelog for v2.9.3 +-------------------- + +* bug #3502 Fix missing file in export (keradus) + +Changelog for v2.9.2 +-------------------- + +* bug #3265 YodaFixer - fix problems of block statements followed by ternary statements (weareoutman, keradus, SpacePossum) +* bug #3367 NoUnusedImportsFixer - fix comment handling (SpacePossum, keradus) +* bug #3438 PhpUnitTestAnnotationFixer: Do not prepend with test if method is test() (localheinz, SpacePossum) +* bug #3455 NoEmptyCommentFixer - comment block detection for line ending different than LF (kubawerlos, SpacePossum) +* bug #3458 SilencedDeprecationErrorFixer - fix edge cases (kubawerlos) +* bug #3466 no_whitespace_in_blank_line and no_blank_lines_after_phpdoc fixers bug (kubawerlos, keradus) +* bug #3472 YodaStyleFixer - do not un-Yoda if right side is assignment (SpacePossum, keradus) +* minor #3354 Added missing types to the PhpdocTypesFixer (GrahamCampbell) +* minor #3406 Fix for escaping in README (kubawerlos) +* minor #3430 Fix integration test (SpacePossum) +* minor #3431 Add missing tests (SpacePossum) +* minor #3440 Add a handful of integration tests (BackEndTea) +* minor #3444 IntegrationTest - ensure tests in priority dir are priority tests indeed (keradus) +* minor #3494 Add missing PHPDoc param type (ntzm) +* minor #3495 Swap @var type and element (ntzm) +* minor #3498 NoUnusedImportsFixer - fix deprecation (keradus) + +Changelog for v2.9.1 +-------------------- + +* bug #3298 DiffConsoleFormatter - fix output escaping. (SpacePossum) +* bug #3312 PhpUnitTestAnnotationFixer: Only remove prefix if it really is a prefix (localheinz) +* bug #3318 SingleLineCommentStyleFixer - fix closing tag inside comment causes an error (kubawerlos) +* bug #3334 ExplicitStringVariableFixer: handle parsed array and object (Slamdunk) +* bug #3337 BracesFixer: nowdoc bug on template files (Slamdunk) +* bug #3349 Fix stdin handling and add tests for it (keradus) +* bug #3350 PhpUnitNoExpectationAnnotationFixer - fix handling of multiline expectedExceptionMessage annotation (Slamdunk) +* bug #3352 FunctionToConstantFixer - bugfix for get_class with leading backslash (kubawerlos) +* bug #3359 BracesFixer - handle comment for content outside of given block (keradus) +* bug #3371 IsNullFixer must be run before YodaStyleFixer (kubawerlos) +* bug #3373 PhpdocAlignFixer - Fix removing of everything after @ when there is a space after the @ (ntzm) +* bug #3415 FileFilterIterator - input checks and utests (SpacePossum, keradus) +* bug #3420 SingleLineCommentStyleFixer - fix 'strpos() expects parameter 1 to be string, boolean given' (keradus, SpacePossum) +* bug #3428 Fix archive analysing (keradus) +* bug #3429 Fix archive analysing (keradus) +* minor #3137 PHPUnit - use common base class (keradus) +* minor #3311 FinalInternalClassFixer - fix typo (localheinz) +* minor #3328 Remove duplicated space in exceptions (keradus) +* minor #3342 PhpUnitDedicateAssertFixer - Remove unexistent method is_boolean (carusogabriel) +* minor #3345 StdinFileInfo - fix __toString (keradus) +* minor #3346 StdinFileInfo - drop getContents (keradus) +* minor #3347 DX: reapply newest CS (keradus) +* minor #3365 COOKBOOK-FIXERS.md - update to provide definition instead of description (keradus) +* minor #3370 AbstractFixer - FQCN in in exceptions (Slamdunk) +* minor #3372 ProjectCodeTest - fix comment (keradus) +* minor #3393 Method call typos (Slamdunk, keradus) +* minor #3402 Always provide delimiter to `preg_quote` calls (ntzm) +* minor #3403 Remove unused import (ntzm) +* minor #3405 Fix `fopen` mode (ntzm) +* minor #3407 CombineConsecutiveIssetsFixer - Improve description (kubawerlos) +* minor #3408 Improving fixers descriptions (kubawerlos) +* minor #3409 move itests from misc to priority (keradus) +* minor #3411 Better type hinting for AbstractFixerTestCase::$fixer (kubawerlos) +* minor #3412 Convert `strtolower` inside `strpos` to just `stripos` (ntzm) +* minor #3421 DX: Use ::class (keradus) +* minor #3424 AbstractFixerTest: fix expectException arguments (Slamdunk, keradus) +* minor #3425 FixerFactoryTest - test that priority pair fixers have itest (keradus, SpacePossum) +* minor #3427 ConfigurationResolver: fix @return annotation (Slamdunk) + +Changelog for v2.9.0 +-------------------- + +* feature #3063 Method chaining indentation fixer (boliev, julienfalque) +* feature #3076 Add ExplicitStringVariableFixer (Slamdunk, keradus) +* feature #3098 MethodSeparationFixer - add class elements separation options (SpacePossum, keradus) +* feature #3155 Add EscapeImplicitBackslashesFixer (Slamdunk) +* feature #3164 Add ExplicitIndirectVariableFixer (Slamdunk, keradus) +* feature #3183 FinalInternalClassFixer introduction (keradus, SpacePossum) +* feature #3187 StaticLambdaFixer - introduction (SpacePossum, keradus) +* feature #3209 PhpdocAlignFixer - Make @method alignable (ntzm) +* feature #3275 Add PhpUnitTestAnnotationFixer (BackEndTea, keradus) + +Changelog for v2.8.4 +-------------------- + +* bug #3281 SelfAccessorFixer - stop modifying traits (kubawerlos) +* minor #3195 Add self-update command test (julienfalque) +* minor #3287 FileCacheManagerTest - drop duplicated line (keradus) +* minor #3292 PHPUnit - set memory limit (veewee) +* minor #3306 Token - better input validation (keradus) +* minor #3310 Upgrade PHP Coveralls (keradus) + +Changelog for v2.8.3 +-------------------- + +* bug #3173 SimplifiedNullReturnFixer - handle nullable return types (Slamdunk) +* bug #3268 PhpUnitNoExpectationAnnotationFixer - add case with backslashes (keradus, Slamdunk) +* bug #3272 PhpdocTrimFixer - unicode support (SpacePossum) + +Changelog for v2.8.2 +-------------------- + +* bug #3225 PhpdocTrimFixer - Fix handling of lines without leading asterisk (julienfalque) +* bug #3241 NoExtraConsecutiveBlankLinesFixer - do not crash on ^M LF only (SpacePossum) +* bug #3242 PhpUnitNoExpectationAnnotationFixer - fix ' handling (keradus) +* bug #3243 PhpUnitExpectationFixer - don't create ->expectExceptionMessage(null) (keradus) +* bug #3244 PhpUnitNoExpectationAnnotationFixer - expectation extracted from annotation shall be separated from rest of code with one blank line (keradus) +* bug #3259 PhpUnitNamespacedFixer - fix isCandidate to not rely on class declaration (keradus) +* bug #3261 PhpUnitNamespacedFixer - properly fix next usage of already fixed class (keradus) +* bug #3262 ToolInfo - support installation by branch as well (keradus) +* bug #3263 NoBreakCommentFixer - Fix handling comment text with PCRE characters (julienfalque) +* bug #3266 PhpUnitConstructFixer - multiple asserts bug (kubawerlos) +* minor #3239 Improve contributing guide and issue template (julienfalque) +* minor #3246 Make ToolInfo methods non-static (julienfalque) +* minor #3249 PhpUnitNoExpectationAnnotationFixerTest - fix hidden conflict (keradus) +* minor #3250 Travis: fail early, spare resources, save the Earth (Slamdunk, keradus) +* minor #3251 Create Title for config file docs section (IanEdington) +* minor #3254 AutoReview/FixerFactoryTest::testFixersPriority: verbose assertion message (Slamdunk) +* minor #3255 IntegrationTest: output exception stack trace (Slamdunk) +* minor #3257 README.rst - Fixed bullet list formatting (moebrowne) + +Changelog for v2.8.1 +-------------------- + +* bug #3199 TokensAnalyzer - getClassyElements (SpacePossum) +* bug #3208 BracesFixer - Fix for instantiation in control structures (julienfalque, SpacePossum) +* bug #3215 BinaryOperatorSpacesFixer - Fix spaces around multiple exception catching (ntzm) +* bug #3216 AbstractLinesBeforeNamespaceFixer - add min. and max. option, not only single target count (SpacePossum) +* bug #3217 TokenizerLinter - fix lack of linting when code is cached (SpacePossum, keradus) +* minor #3200 Skip slow test when Xdebug is loaded (julienfalque) +* minor #3211 Use udiff format in CI (julienfalque) +* minor #3212 Handle rulesets unknown to fabbot.io (julienfalque) +* minor #3219 Normalise references to GitHub in docs (ntzm) +* minor #3226 Remove unused imports (ntzm) +* minor #3231 Fix typos (ntzm) +* minor #3234 Simplify Cache\Signature::equals (ntzm) +* minor #3237 UnconfigurableFixer - use only LF (keradus) +* minor #3238 AbstractFixerTest - fix @cover annotation (keradus) + +Changelog for v2.8.0 +-------------------- + +* feature #3065 Add IncrementStyleFixer (kubawerlos) +* feature #3119 Feature checkstyle reporter (K-Phoen) +* feature #3162 Add udiff as diff format (SpacePossum, keradus) +* feature #3170 Add CompactNullableTypehintFixer (jfcherng) +* feature #3189 Add PHP_CS_FIXER_FUTURE_MODE env (keradus) +* feature #3201 Add PHPUnit Migration rulesets and fixers (keradus) +* minor #3149 AbstractProxyFixer - Support multiple proxied fixers (julienfalque) +* minor #3160 Add DeprecatedFixerInterface (kubawerlos) +* minor #3185 IndentationTypeFixerTest - clean up (SpacePossum, keradus) +* minor #3198 Cleanup: add test that there is no deprecated fixer in rule set (kubawerlos) + +Changelog for v2.7.5 +-------------------- + +* bug #3225 PhpdocTrimFixer - Fix handling of lines without leading asterisk (julienfalque) +* bug #3241 NoExtraConsecutiveBlankLinesFixer - do not crash on ^M LF only (SpacePossum) +* bug #3262 ToolInfo - support installation by branch as well (keradus) +* bug #3263 NoBreakCommentFixer - Fix handling comment text with PCRE characters (julienfalque) +* bug #3266 PhpUnitConstructFixer - multiple asserts bug (kubawerlos) +* minor #3239 Improve contributing guide and issue template (julienfalque) +* minor #3246 Make ToolInfo methods non-static (julienfalque) +* minor #3250 Travis: fail early, spare resources, save the Earth (Slamdunk, keradus) +* minor #3251 Create Title for config file docs section (IanEdington) +* minor #3254 AutoReview/FixerFactoryTest::testFixersPriority: verbose assertion message (Slamdunk) +* minor #3255 IntegrationTest: output exception stack trace (Slamdunk) + +Changelog for v2.7.4 +-------------------- + +* bug #3199 TokensAnalyzer - getClassyElements (SpacePossum) +* bug #3208 BracesFixer - Fix for instantiation in control structures (julienfalque, SpacePossum) +* bug #3215 BinaryOperatorSpacesFixer - Fix spaces around multiple exception catching (ntzm) +* bug #3216 AbstractLinesBeforeNamespaceFixer - add min. and max. option, not only single target count (SpacePossum) +* bug #3217 TokenizerLinter - fix lack of linting when code is cached (SpacePossum, keradus) +* minor #3200 Skip slow test when Xdebug is loaded (julienfalque) +* minor #3219 Normalise references to GitHub in docs (ntzm) +* minor #3226 Remove unused imports (ntzm) +* minor #3231 Fix typos (ntzm) +* minor #3234 Simplify Cache\Signature::equals (ntzm) +* minor #3237 UnconfigurableFixer - use only LF (keradus) +* minor #3238 AbstractFixerTest - fix @cover annotation (keradus) + +Changelog for v2.7.3 +-------------------- + +* bug #3114 SelfAccessorFixer - Fix type declarations replacement (julienfalque) + +Changelog for v2.7.2 +-------------------- + +* bug #3062 BraceClassInstantiationTransformer - Fix instantiation inside method call braces case (julienfalque, keradus) +* bug #3083 SingleBlankLineBeforeNamespaceFixer - Fix handling namespace right after opening tag (mlocati) +* bug #3109 SwitchCaseSemicolonToColonFixer - Fix bug with nested constructs (SpacePossum) +* bug #3117 Multibyte character in array key makes alignment incorrect (kubawerlos) +* bug #3123 Cache - File permissions (SpacePossum) +* bug #3138 NoHomoglyphNamesFixer - fix crash on non-ascii but not mapped either (SpacePossum) +* bug #3172 IndentationTypeFixer - do not touch whitespace that is not indentation (SpacePossum) +* bug #3176 NoMultilineWhitespaceBeforeSemicolonsFixer - SpaceAfterSemicolonFixer - priority fix (SpacePossum) +* bug #3193 TokensAnalyzer::getClassyElements - sort result before returning (SpacePossum) +* bug #3196 SelfUpdateCommand - fix exit status when can't determine newest version (julienfalque) +* minor #3107 ConfigurationResolver - improve error message when rule is not found (SpacePossum) +* minor #3113 Add WordMatcher (keradus) +* minor #3128 README: remove deprecated rule from CLI examples (chteuchteu) +* minor #3133 Unify Reporter tests (keradus) +* minor #3134 Allow Symfony 4 (keradus, garak) +* minor #3136 PHPUnit - call hooks from parent class as well (keradus) +* minor #3141 Unify description of deprecated fixer (kubawerlos) +* minor #3144 PhpUnitDedicateAssertFixer - Sort map and array by function name (localheinz) +* minor #3145 misc - Typo (localheinz) +* minor #3150 Fix CircleCI (julienfalque) +* minor #3151 Update gitattributes to ignore next file (keradus) +* minor #3156 Update php-coveralls (keradus) +* minor #3166 README - add link to new gitter channel. (SpacePossum) +* minor #3174 Update UPGRADE.md (vitek-rostislav) +* minor #3180 Fix usage of static variables (kubawerlos) +* minor #3182 Add support for PHPUnit 6, drop PHPUnit 4 (keradus) +* minor #3184 Code grooming - sort content of arrays (keradus) +* minor #3191 Travis - add nightly build to allow_failures due to Travis issues (keradus) +* minor #3197 DX groom CS (keradus) + +Changelog for v2.7.1 +-------------------- + +* bug #3115 NoUnneededFinalMethodFixer - fix edge case (Slamdunk) + +Changelog for v2.7.0 +-------------------- + +* feature #2573 BinaryOperatorSpaces reworked (SpacePossum, keradus) +* feature #3073 SpaceAfterSemicolonFixer - Add option to remove space in empty for expressions (julienfalque) +* feature #3089 NoAliasFunctionsFixer - add imap aliases (Slamdunk) +* feature #3093 NoUnneededFinalMethodFixer - Remove final keyword from private methods (localheinz, keradus) +* minor #3068 Symfony:risky ruleset - add no_homoglyph_names (keradus) +* minor #3074 [IO] Replace Diff with fork version (SpacePossum) + +Changelog for v2.6.1 +-------------------- + +* bug #3052 Fix false positive warning about paths overridden by provided as command arguments (kubawerlos) +* bug #3053 CombineConsecutiveIssetsFixer - fix priority (SpacePossum) +* bug #3058 IsNullFixer - fix whitespace handling (roukmoute) +* bug #3069 MethodArgumentSpaceFixer - new test case (keradus) +* bug #3072 IsNullFixer - fix non_yoda_style edge case (keradus) +* bug #3088 Drop dedicated Phar stub (keradus) +* bug #3100 NativeFunctionInvocationFixer - Fix test if previous token is already namespace separator (SpacePossum) +* bug #3104 DoctrineAnnotationIndentationFixer - Fix str_repeat() error (julienfalque) +* minor #3038 Support PHP 7.2 (SpacePossum, keradus) +* minor #3064 Fix couple of typos (KKSzymanowski) +* minor #3070 YodaStyleFixer - Clarify configuration parameters (SteveJobzniak) +* minor #3078 ConfigurationResolver - hide context while including config file (keradus) +* minor #3080 Direct function call instead of by string (kubawerlos) +* minor #3085 CiIntegrationTest - skip when no git is available (keradus) +* minor #3087 phar-stub.php - allow PHP 7.2 (keradus) +* minor #3092 .travis.yml - fix matrix for PHP 7.1 (keradus) +* minor #3094 NoUnneededFinalMethodFixer - Add test cases (julienfalque) +* minor #3111 DoctrineAnnotationIndentationFixer - Restore test case (julienfalque) + +Changelog for v2.6.0 +-------------------- + +* bug #3039 YodaStyleFixer - Fix echo case (SpacePossum, keradus) +* feature #2446 Add YodaStyleFixer (SpacePossum) +* feature #2940 Add NoHomoglyphNamesFixer (mcfedr, keradus) +* feature #3012 Add CombineConsecutiveIssetsFixer (SpacePossum) +* minor #3037 Update SF rule set (SpacePossum) + +Changelog for v2.5.1 +-------------------- + +* bug #3002 Bugfix braces (mnabialek) +* bug #3010 Fix handling of Github releases (julienfalque, keradus) +* bug #3015 Fix exception arguments (julienfalque) +* bug #3016 Verify phar file (keradus) +* bug #3021 Risky rules cleanup (kubawerlos) +* bug #3023 RandomApiMigrationFixer - "rand();" to "random_int(0, getrandmax());" fixing (SpacePossum) +* bug #3024 ConfigurationResolver - Handle empty "rules" value (SpacePossum, keradus) +* bug #3031 IndentationTypeFixer - fix handling tabs in indented comments (keradus) +* minor #2999 Notice when paths from config file are overridden by command arguments (julienfalque, keradus) +* minor #3007 Add PHP 7.2 to Travis build matrix (Jean85) +* minor #3009 CiIntegrationTest - run local (SpacePossum) +* minor #3013 Adjust phpunit configuration (localheinz) +* minor #3017 Fix: Risky tests (localheinz) +* minor #3018 Fix: Make sure that data providers are named correctly (localheinz, keradus) +* minor #3032 .php_cs.dist - handling UnexpectedValueException (keradus) +* minor #3033 Use ::class (keradus) +* minor #3034 Follow newest CS (keradus) +* minor #3036 Drop not existing Standalone group from PHPUnit configuration and duplicated internal tags (keradus) +* minor #3042 Update gitter address (keradus) + +Changelog for v2.5.0 +-------------------- + +* feature #2770 DoctrineAnnotationSpaces - split assignments options (julienfalque) +* feature #2843 Add estimating-max progress output type (julienfalque) +* feature #2885 Add NoSuperfluousElseifFixer (julienfalque) +* feature #2929 Add NoUnneededCurlyBracesFixer (SpacePossum) +* feature #2944 FunctionToConstantFixer - handle get_class() -> __CLASS__ as well (SpacePossum) +* feature #2953 BlankLineBeforeStatementFixer - Add more statements (localheinz, keradus) +* feature #2972 Add NoUnneededFinalMethodFixer (Slamdunk, keradus) +* feature #2992 Add Doctrine Annotation ruleset (julienfalque) +* minor #2926 Token::getNameForId (SpacePossum) + +Changelog for v2.4.2 +-------------------- + +* bug #3002 Bugfix braces (mnabialek) +* bug #3010 Fix handling of Github releases (julienfalque, keradus) +* bug #3015 Fix exception arguments (julienfalque) +* bug #3016 Verify phar file (keradus) +* bug #3021 Risky rules cleanup (kubawerlos) +* bug #3023 RandomApiMigrationFixer - "rand();" to "random_int(0, getrandmax());" fixing (SpacePossum) +* bug #3024 ConfigurationResolver - Handle empty "rules" value (SpacePossum, keradus) +* bug #3031 IndentationTypeFixer - fix handling tabs in indented comments (keradus) +* minor #2999 Notice when paths from config file are overridden by command arguments (julienfalque, keradus) +* minor #3007 Add PHP 7.2 to Travis build matrix (Jean85) +* minor #3009 CiIntegrationTest - run local (SpacePossum) +* minor #3013 Adjust phpunit configuration (localheinz) +* minor #3017 Fix: Risky tests (localheinz) +* minor #3018 Fix: Make sure that data providers are named correctly (localheinz, keradus) +* minor #3032 .php_cs.dist - handling UnexpectedValueException (keradus) +* minor #3033 Use ::class (keradus) +* minor #3034 Follow newest CS (keradus) +* minor #3036 Drop not existing Standalone group from PHPUnit configuration and duplicated internal tags (keradus) +* minor #3042 Update gitter address (keradus) + +Changelog for v2.4.1 +-------------------- + +* bug #2925 Improve CI integration suggestion (julienfalque) +* bug #2928 TokensAnalyzer::getClassyElements - Anonymous class support (SpacePossum) +* bug #2931 Psr0Fixer, Psr4Fixer - ignore "new class" syntax (dg, keradus) +* bug #2934 Config - fix handling rule without value (keradus, SpacePossum) +* bug #2939 NoUnusedImportsFixer - Fix extra blank line (julienfalque) +* bug #2941 PHP 7.2 - Group imports with trailing comma support (SpacePossum, julienfalque) +* bug #2954 NoBreakCommentFixer - Disable case sensitivity (julienfalque) +* bug #2959 MethodArgumentSpaceFixer - Skip body of fixed function (greg0ire) +* bug #2984 AlignMultilineCommentFixer - handle uni code (SpacePossum) +* bug #2987 Fix incorrect indentation of comments in `braces` fixer (rob006) +* minor #2924 Add missing Token deprecations (julienfalque) +* minor #2927 WhiteSpaceConfig - update message copy and more strict tests (SpacePossum, keradus) +* minor #2930 Trigger website build (keradus) +* minor #2932 Integrate CircleCI (keradus, aidantwoods) +* minor #2933 ProcessLinterTest - Ensure Windows test only runs on Windows, add a Mac test execution (aidantwoods) +* minor #2935 special handling of fabbot.io service if it's using too old PHP CS Fixer version (keradus) +* minor #2937 Travis: execute 5.3 job on precise (keradus) +* minor #2938 Tests fix configuration of project (SpacePossum, keradus) +* minor #2943 FunctionToConstantFixer - test with diff. arguments than fixable (SpacePossum) +* minor #2945 BlankLineBeforeStatementFixerTest - Fix covered class (julienfalque) +* minor #2946 Detect extra old installations (keradus) +* minor #2947 Test suggested CI integration (keradus) +* minor #2951 AccessibleObject - remove most of usage (keradus) +* minor #2952 BlankLineBeforeStatementFixer - Reference fixer instead of test class (localheinz) +* minor #2955 Travis - stop using old TASK_SCA residue (keradus) +* minor #2968 AssertTokensTrait - don't use AccessibleObject (keradus) +* minor #2969 Shrink down AccessibleObject usage (keradus) +* minor #2982 TrailingCommaInMultilineArrayFixer - simplify isMultilineArray condition (TomasVotruba) +* minor #2989 CiIntegrationTest - fix min supported PHP versions (keradus) + +Changelog for v2.4.0 +-------------------- + +* bug #2880 NoBreakCommentFixer - fix edge case (julienfalque) +* bug #2900 VoidReturnFixer - handle functions containing anonymous functions/classes (bendavies, keradus) +* bug #2902 Fix test classes constructor (julienfalque) +* feature #2384 Add BlankLineBeforeStatementFixer (localheinz, keradus, SpacePossum) +* feature #2440 MethodArgumentSpaceFixer - add ensure_fully_multiline option (greg0ire) +* feature #2649 PhpdocAlignFixer - make fixer configurable (ntzm) +* feature #2664 Add DoctrineAnnotationArrayAssignmentFixer (julienfalque) +* feature #2667 Add NoBreakCommentFixer (julienfalque) +* feature #2684 BracesFixer - new options for braces position after control structures and anonymous constructs (aidantwoods, keradus) +* feature #2701 NoExtraConsecutiveBlankLinesFixer - Add more configuration options related to switch statements (SpacePossum) +* feature #2740 Add VoidReturnFixer (mrmark) +* feature #2765 DoctrineAnnotationIndentationFixer - add option to indent mixed lines (julienfalque) +* feature #2815 NonPrintableCharacterFixer - Add option to replace with escape sequences (julienfalque, keradus) +* feature #2822 Add NoNullPropertyInitializationFixer (ntzm, julienfalque, SpacePossum) +* feature #2825 Add PhpdocTypesOrderFixer (julienfalque, keradus) +* feature #2856 CastSpacesFixer - add space option (kubawerlos, keradus) +* feature #2857 Add AlignMultilineCommentFixer (Slamdunk, keradus) +* feature #2866 Add SingleLineCommentStyleFixer, deprecate HashToSlashCommentFixer (Slamdunk, keradus) +* minor #2773 Travis - use stages (keradus) +* minor #2794 Drop HHVM support (keradus, julienfalque) +* minor #2801 ProjectCodeTest - Fix typo in deprecation message (SpacePossum) +* minor #2818 Token become immutable, performance optimizations (keradus) +* minor #2877 Fix PHPMD report (julienfalque) +* minor #2894 NonPrintableCharacterFixer - fix handling required PHP version on PHPUnit 4.x (keradus) +* minor #2921 InvalidForEnvFixerConfigurationException - fix handling in tests on 2.4 line (keradus) + +Changelog for v2.3.3 +-------------------- + +* bug #2807 NoUselessElseFixer - Fix detection of conditional block (SpacePossum) +* bug #2809 Phar release - fix readme generation (SpacePossum, keradus) +* bug #2827 MethodArgumentSpaceFixer - Always remove trailing spaces (julienfalque) +* bug #2835 SelfAccessorFixer - class property fix (mnabialek) +* bug #2848 PhpdocIndentFixer - fix edge case with inline phpdoc (keradus) +* bug #2849 BracesFixer - Fix indentation issues with comments (julienfalque) +* bug #2851 Tokens - ensureWhitespaceAtIndex (GrahamCampbell, SpacePossum) +* bug #2854 NoLeadingImportSlashFixer - Removing leading slash from import even when in global space (kubawerlos) +* bug #2858 Support generic types (keradus) +* bug #2869 Fix handling required configuration (keradus) +* bug #2881 NoUnusedImportsFixer - Bug when trying to insert empty token (GrahamCampbell, keradus) +* bug #2882 DocBlock\Annotation - Fix parsing of collections with multiple key types (julienfalque) +* bug #2886 NoSpacesInsideParenthesisFixer - Do not remove whitespace if next token is comment (SpacePossum) +* bug #2888 SingleImportPerStatementFixer - Add support for function and const (SpacePossum) +* bug #2901 Add missing files to archive files (keradus) +* bug #2914 HeredocToNowdocFixer - works with CRLF line ending (dg) +* bug #2920 RuleSet - Update deprecated configuration of fixers (SpacePossum, keradus) +* minor #1531 Update docs for few generic types (keradus) +* minor #2793 COOKBOOK-FIXERS.md - update to current version, fix links (keradus) +* minor #2812 ProcessLinter - compatibility with Symfony 3.3 (keradus) +* minor #2816 Tokenizer - better docs and validation (keradus) +* minor #2817 Tokenizer - use future-compatible interface (keradus) +* minor #2819 Fix benchmark (keradus) +* minor #2820 MagicConstantCasingFixer - Remove defined check (SpacePossum) +* minor #2823 Tokenizer - use future-compatible interface (keradus) +* minor #2824 code grooming (keradus) +* minor #2826 Exceptions - provide utests (localheinz) +* minor #2828 Enhancement: Reference phpunit.xsd from phpunit.xml.dist (localheinz) +* minor #2830 Differs - add tests (localheinz) +* minor #2832 Fix: Use all the columns (localheinz) +* minor #2833 Doctrine\Annotation\Token - provide utests (localheinz) +* minor #2839 Use PHP 7.2 polyfill instead of xml one (keradus) +* minor #2842 Move null to first position in PHPDoc types (julienfalque) +* minor #2850 ReadmeCommandTest - Prevent diff output (julienfalque) +* minor #2859 Fixed typo and dead code removal (GrahamCampbell) +* minor #2863 FileSpecificCodeSample - add tests (localheinz) +* minor #2864 WhitespacesAwareFixerInterface clean up (Slamdunk) +* minor #2865 AutoReview\FixerTest - test configuration samples (SpacePossum, keradus) +* minor #2867 VersionSpecification - Fix copy-paste typo (SpacePossum) +* minor #2870 Tokens - ensureWhitespaceAtIndex - Clear tokens before compare. (SpacePossum) +* minor #2874 LineTest - fix typo (keradus) +* minor #2875 HelpCommand - recursive layout fix (SpacePossum) +* minor #2883 DescribeCommand - Show which sample uses the default configuration (SpacePossum) +* minor #2887 Housekeeping - Strict whitespace checks (SpacePossum) +* minor #2895 ProjectCodeTest - check that classes in no-tests exception exist (keradus) +* minor #2896 Move testing related classes from src to tests (keradus) +* minor #2904 Reapply CS (keradus) +* minor #2910 PhpdocAnnotationWithoutDotFixer - Restrict lowercasing (oschwald) +* minor #2913 Tests - tweaks (SpacePossum, keradus) +* minor #2916 FixerFactory - drop return in sortFixers(), never used (TomasVotruba) + +Changelog for v2.3.2 +-------------------- + +* bug #2682 DoctrineAnnotationIndentationFixer - fix handling nested annotations (edhgoose, julienfalque) +* bug #2700 Fix Doctrine Annotation end detection (julienfalque) +* bug #2715 OrderedImportsFixer - handle indented groups (pilgerone) +* bug #2732 HeaderCommentFixer - fix handling blank lines (s7b4) +* bug #2745 Fix Doctrine Annotation newlines (julienfalque) +* bug #2752 FixCommand - fix typo in warning message (mnapoli) +* bug #2757 GeckoPHPUnit is not dev dependency (keradus) +* bug #2759 Update gitattributes (SpacePossum) +* bug #2763 Fix describe command with PSR-0 fixer (julienfalque) +* bug #2768 Tokens::ensureWhitespaceAtIndex - clean up comment check, add check for T_OPEN (SpacePossum) +* bug #2783 Tokens::ensureWhitespaceAtIndex - Fix handling line endings (SpacePossum) +* minor #2304 DX: use PHPMD (keradus) +* minor #2663 Use colors for keywords in commands output (julienfalque, keradus) +* minor #2706 Update README (SpacePossum) +* minor #2714 README.rst - fix wrong value in example (mleko) +* minor #2718 Remove old Symfony exception message expectation (julienfalque) +* minor #2721 Update phpstorm article link to a fresh blog post (valeryan) +* minor #2725 Use method chaining for configuration definitions (julienfalque) +* minor #2727 PHPUnit - use speedtrap (keradus) +* minor #2728 SelfUpdateCommand - verify that it's possible to replace current file (keradus) +* minor #2729 DescribeCommand - add decorated output test (julienfalque) +* minor #2731 BracesFixer - properly pass config in utest dataProvider (keradus) +* minor #2738 Upgrade tests to use new, namespaced PHPUnit TestCase class (keradus) +* minor #2742 Code cleanup (GrahamCampbell, keradus) +* minor #2743 Fixing example and description for GeneralPhpdocAnnotationRemoveFixer (kubawerlos) +* minor #2744 AbstractDoctrineAnnotationFixerTestCase - split fixers test cases (julienfalque) +* minor #2755 Fix compatibility with PHPUnit 5.4.x (keradus) +* minor #2758 Readme - improve CI integration guidelines (keradus) +* minor #2769 Psr0Fixer - remove duplicated example (julienfalque) +* minor #2774 AssertTokens Trait (keradus) +* minor #2775 NoExtraConsecutiveBlankLinesFixer - remove duplicate code sample. (SpacePossum) +* minor #2778 AutoReview - watch that code samples are unique (keradus) +* minor #2787 Add warnings about missing dom ext and require json ext (keradus) +* minor #2792 Use composer-require-checker (keradus) +* minor #2796 Update .gitattributes (SpacePossum) +* minor #2797 Update .gitattributes (SpacePossum) +* minor #2800 PhpdocTypesFixerTest - Fix typo in covers annotation (SpacePossum) + +Changelog for v2.3.1 +-------------------- + +Port of v2.2.3. + +* bug #2724 Revert #2554 Add short diff. output format (keradus) + +Changelog for v2.3.0 +-------------------- + +* feature #2450 Add ListSyntaxFixer (SpacePossum) +* feature #2708 Add PhpUnitTestClassRequiresCoversFixer (keradus) +* minor #2568 Require PHP 5.6+ (keradus) +* minor #2672 Bump symfony/* deps (keradus) + +Changelog for v2.2.20 +--------------------- + +* bug #3233 PhpdocAlignFixer - Fix linebreak inconsistency (SpacePossum, keradus) +* bug #3445 Rewrite NoUnusedImportsFixer (kubawerlos, julienfalque) +* bug #3597 DeclareStrictTypesFixer - fix bug of removing line (kubawerlos, keradus) +* bug #3605 DoctrineAnnotationIndentationFixer - Fix indentation with mixed lines (julienfalque) +* bug #3606 PhpdocToCommentFixer - allow multiple ( (SpacePossum) +* bug #3684 PhpUnitStrictFixer - Do not fix if not correct # of arguments are used (SpacePossum) +* bug #3715 SingleImportPerStatementFixer - Fix handling whitespace before opening brace (julienfalque) +* bug #3731 PhpdocIndentFixer - crash fix (SpacePossum) +* bug #3765 Fix binary-prefixed double-quoted strings to single quotes (ntzm) +* bug #3770 Handle binary flags in heredoc_to_nowdoc (ntzm) +* bug #3790 ProcessLinter - don't execute external process without timeout! It can freeze! (keradus) +* minor #3548 Make shell scripts POSIX-compatible (EvgenyOrekhov, keradus) +* minor #3568 New Autoreview: Correct option casing (ntzm) +* minor #3590 Use XdebugHandler to avoid performance penalty (AJenbo, keradus) +* minor #3607 PhpdocVarWithoutNameFixer - update sample with @ type (SpacePossum) +* minor #3617 Tests stability patches (Tom Klingenberg, keradus) +* minor #3627 Fix tests execution under phpdbg (keradus) +* minor #3629 ProjectFixerConfigurationTest - test rules are sorted (SpacePossum) +* minor #3639 DX: use benefits of symfony/force-lowest (keradus) +* minor #3641 Update check_trailing_spaces script with upstream (keradus) +* minor #3646 Extract SameStringsConstraint and XmlMatchesXsdConstraint (keradus) +* minor #3647 DX: Add CachingLinter for tests (keradus) +* minor #3649 Update check_trailing_spaces script with upstream (keradus) +* minor #3652 CiIntegrationTest - run tests with POSIX sh, not Bash (keradus) +* minor #3656 DX: Clean ups (SpacePossum) +* minor #3660 Fix do not rely on order of fixing in CiIntegrationTest (kubawerlos) +* minor #3662 DX: Add Smoke/InstallViaComposerTest (keradus) +* minor #3663 DX: During deployment, run all smoke tests and don't allow to skip phar-related ones (keradus) +* minor #3665 CircleCI fix (kubawerlos) +* minor #3666 Use "set -eu" in shell scripts (EvgenyOrekhov) +* minor #3669 Document possible values for subset options (julienfalque, keradus) +* minor #3676 RunnerTest - workaround for failing Symfony v2.8.37 (kubawerlos) +* minor #3680 DX: Tokens - removeLeadingWhitespace and removeTrailingWhitespace must act in same way (SpacePossum) +* minor #3686 README.rst - Format all code-like strings in fixer descriptions (ntzm, keradus) +* minor #3692 DX: Optimize tests (julienfalque) +* minor #3701 Use correct casing for "PHPDoc" (ntzm) +* minor #3703 DX: InstallViaComposerTets - groom naming (keradus) +* minor #3704 DX: Tokens - fix naming (keradus) +* minor #3706 Update homebrew installation instructions (ntzm) +* minor #3713 Use HTTPS whenever possible (fabpot) +* minor #3723 Extend tests coverage (ntzm) +* minor #3733 Disable Composer optimized autoloader by default (julienfalque) +* minor #3748 PhpUnitStrictFixer - extend risky note (jnvsor) +* minor #3749 Make sure PHPUnit is cased correctly in fixers descriptions (kubawerlos) +* minor #3773 AbstractFixerWithAliasedOptionsTestCase - don't export (keradus) +* minor #3796 DX: StdinTest - do not assume name of folder, into which project was cloned (keradus) +* minor #3803 NoEmptyPhpdocFixer/PhpdocAddMissingParamAnnotationFixer - missing priority test (SpacePossum, keradus) +* minor #3804 Cleanup: remove useless constructor comment (kubawerlos) + +Changelog for v2.2.19 +--------------------- + +* bug #3594 ElseifFixer - Bug with alternative syntax (kubawerlos) +* bug #3600 StrictParamFixer - Fix issue when functions are imported (ntzm, keradus) +* minor #3589 FixerFactoryTest - add missing test (SpacePossum, keradus) +* minor #3610 make phar extension optional (Tom Klingenberg, keradus) +* minor #3612 Travis - allow for hhvm failures (keradus) +* minor #3615 Detect fabbot.io (julienfalque, keradus) +* minor #3616 FixerFactoryTest - Don't rely on autovivification (keradus) + +Changelog for v2.2.18 +--------------------- + +* bug #3446 Add PregWrapper (kubawerlos) +* bug #3464 IncludeFixer - fix incorrect order of fixing (kubawerlos, SpacePossum) +* bug #3496 Bug in Tokens::removeLeadingWhitespace (kubawerlos, SpacePossum, keradus) +* bug #3557 AbstractDoctrineAnnotationFixer: edge case bugfix (Slamdunk) +* bug #3574 GeneralPhpdocAnnotationRemoveFixer - remove PHPDoc if no content is left (SpacePossum) +* minor #3563 DX add missing covers annotations (keradus) +* minor #3565 Use EventDispatcherInterface instead of EventDispatcher when possible (keradus) +* minor #3572 DX: allow for more phpunit-speedtrap versions to support more PHPUnit versions (keradus) +* minor #3576 Fix Doctrine Annotation test cases merging (julienfalque) + +Changelog for v2.2.17 +--------------------- + +* bug #3504 NoBlankLinesAfterPhpdocFixer - allow blank line before declare statement (julienfalque) +* bug #3522 Remove LOCK_EX (SpacePossum) +* bug #3560 SelfAccessorFixer is risky (Slamdunk) +* minor #3435 Add tests for general_phpdoc_annotation_remove (BackEndTea) +* minor #3484 Create Tokens::findBlockStart (ntzm) +* minor #3512 Add missing array typehints (ntzm) +* minor #3516 Use `null|type` instead of `?type` in PHPDocs (ntzm) +* minor #3518 FixerFactoryTest - Test each priority test file is listed as test (SpacePossum) +* minor #3520 Fix typos: ran vs. run (SpacePossum) +* minor #3521 Use HTTPS (carusogabriel) +* minor #3526 Remove gecko dependency (SpacePossum, keradus, julienfalque) +* minor #3531 Backport PHPMD to LTS version to ease maintainability (keradus) +* minor #3532 Implement Tokens::findOppositeBlockEdge (ntzm) +* minor #3533 DX: SCA - drop src/Resources exclusion (keradus) +* minor #3538 Don't use third parameter of Tokens::findBlockStart (ntzm) +* minor #3542 Enhancement: Run composer-normalize on Travis CI (localheinz, keradus) +* minor #3555 DX: composer.json - drop branch-alias, branch is already following the version (keradus) +* minor #3556 DX: Add AutoReview/ComposerTest (keradus) +* minor #3559 Don't expose new files under Test namespace (keradus) + +Changelog for v2.2.16 +--------------------- + +* bug #3502 Fix missing file in export (keradus) + +Changelog for v2.2.15 +--------------------- + +* bug #3367 NoUnusedImportsFixer - fix comment handling (SpacePossum, keradus) +* bug #3455 NoEmptyCommentFixer - comment block detection for line ending different than LF (kubawerlos, SpacePossum) +* bug #3458 SilencedDeprecationErrorFixer - fix edge cases (kubawerlos) +* bug #3466 no_whitespace_in_blank_line and no_blank_lines_after_phpdoc fixers bug (kubawerlos, keradus) +* minor #3354 Added missing types to the PhpdocTypesFixer (GrahamCampbell) +* minor #3406 Fix for escaping in README (kubawerlos) +* minor #3431 Add missing tests (SpacePossum) +* minor #3440 Add a handful of integration tests (BackEndTea) +* minor #3444 IntegrationTest - ensure tests in priority dir are priority tests indeed (keradus) +* minor #3494 Add missing PHPDoc param type (ntzm) +* minor #3495 Swap @var type and element (ntzm) +* minor #3498 NoUnusedImportsFixer - fix deprecation (keradus) + +Changelog for v2.2.14 +--------------------- + +* bug #3298 DiffConsoleFormatter - fix output escaping. (SpacePossum) +* bug #3337 BracesFixer: nowdoc bug on template files (Slamdunk) +* bug #3349 Fix stdin handling and add tests for it (keradus) +* bug #3359 BracesFixer - handle comment for content outside of given block (keradus) +* bug #3415 FileFilterIterator - input checks and utests (SpacePossum, keradus) +* bug #3429 Fix archive analysing (keradus) +* minor #3137 PHPUnit - use common base class (keradus) +* minor #3342 PhpUnitDedicateAssertFixer - Remove unexistent method is_boolean (carusogabriel) +* minor #3345 StdinFileInfo - fix `__toString` (keradus) +* minor #3346 StdinFileInfo - drop getContents (keradus) +* minor #3347 DX: reapply newest CS (keradus) +* minor #3365 COOKBOOK-FIXERS.md - update to provide definition instead of description (keradus) +* minor #3370 AbstractFixer - FQCN in in exceptions (Slamdunk) +* minor #3372 ProjectCodeTest - fix comment (keradus) +* minor #3402 Always provide delimiter to `preg_quote` calls (ntzm) +* minor #3403 Remove unused import (ntzm) +* minor #3405 Fix `fopen` mode (ntzm) +* minor #3408 Improving fixers descriptions (kubawerlos) +* minor #3409 move itests from misc to priority (keradus) +* minor #3411 Better type hinting for AbstractFixerTestCase::$fixer (kubawerlos) +* minor #3412 Convert `strtolower` inside `strpos` to just `stripos` (ntzm) +* minor #3425 FixerFactoryTest - test that priority pair fixers have itest (keradus, SpacePossum) +* minor #3427 ConfigurationResolver: fix @return annotation (Slamdunk) + +Changelog for v2.2.13 +--------------------- + +* bug #3281 SelfAccessorFixer - stop modifying traits (kubawerlos) +* minor #3195 Add self-update command test (julienfalque) +* minor #3292 PHPUnit - set memory limit (veewee) +* minor #3306 Token - better input validation (keradus) + +Changelog for v2.2.12 +--------------------- + +* bug #3173 SimplifiedNullReturnFixer - handle nullable return types (Slamdunk) +* bug #3272 PhpdocTrimFixer - unicode support (SpacePossum) + +Changelog for v2.2.11 +--------------------- + +* bug #3225 PhpdocTrimFixer - Fix handling of lines without leading asterisk (julienfalque) +* bug #3262 ToolInfo - support installation by branch as well (keradus) +* bug #3266 PhpUnitConstructFixer - multiple asserts bug (kubawerlos) +* minor #3239 Improve contributing guide and issue template (julienfalque) +* minor #3246 Make ToolInfo methods non-static (julienfalque) +* minor #3250 Travis: fail early, spare resources, save the Earth (Slamdunk, keradus) +* minor #3251 Create Title for config file docs section (IanEdington) +* minor #3254 AutoReview/FixerFactoryTest::testFixersPriority: verbose assertion message (Slamdunk) + +Changelog for v2.2.10 +--------------------- + +* bug #3199 TokensAnalyzer - getClassyElements (SpacePossum) +* bug #3208 BracesFixer - Fix for instantiation in control structures (julienfalque, SpacePossum) +* bug #3215 BinaryOperatorSpacesFixer - Fix spaces around multiple exception catching (ntzm) +* bug #3216 AbstractLinesBeforeNamespaceFixer - add min. and max. option, not only single target count (SpacePossum) +* bug #3217 TokenizerLinter - fix lack of linting when code is cached (SpacePossum, keradus) +* minor #3200 Skip slow test when Xdebug is loaded (julienfalque) +* minor #3219 Normalise references to GitHub in docs (ntzm) +* minor #3226 Remove unused imports (ntzm) +* minor #3231 Fix typos (ntzm) +* minor #3234 Simplify Cache\Signature::equals (ntzm) +* minor #3237 UnconfigurableFixer - use only LF (keradus) +* minor #3238 AbstractFixerTest - fix @cover annotation (keradus) + +Changelog for v2.2.9 +-------------------- + +* bug #3062 BraceClassInstantiationTransformer - Fix instantiation inside method call braces case (julienfalque, keradus) +* bug #3083 SingleBlankLineBeforeNamespaceFixer - Fix handling namespace right after opening tag (mlocati) +* bug #3109 SwitchCaseSemicolonToColonFixer - Fix bug with nested constructs (SpacePossum) +* bug #3123 Cache - File permissions (SpacePossum) +* bug #3172 IndentationTypeFixer - do not touch whitespace that is not indentation (SpacePossum) +* bug #3176 NoMultilineWhitespaceBeforeSemicolonsFixer - SpaceAfterSemicolonFixer - priority fix (SpacePossum) +* bug #3193 TokensAnalyzer::getClassyElements - sort result before returning (SpacePossum) +* bug #3196 SelfUpdateCommand - fix exit status when can't determine newest version (julienfalque) +* minor #3107 ConfigurationResolver - improve error message when rule is not found (SpacePossum) +* minor #3113 Add WordMatcher (keradus) +* minor #3133 Unify Reporter tests (keradus) +* minor #3134 Allow Symfony 4 (keradus, garak) +* minor #3136 PHPUnit - call hooks from parent class as well (keradus) +* minor #3145 misc - Typo (localheinz) +* minor #3150 Fix CircleCI (julienfalque) +* minor #3151 Update gitattributes to ignore next file (keradus) +* minor #3156 Update php-coveralls (keradus) +* minor #3166 README - add link to new gitter channel. (SpacePossum) +* minor #3174 Update UPGRADE.md (vitek-rostislav) +* minor #3180 Fix usage of static variables (kubawerlos) +* minor #3184 Code grooming - sort content of arrays (keradus) +* minor #3191 Travis - add nightly build to allow_failures due to Travis issues (keradus) +* minor #3197 DX groom CS (keradus) + +Changelog for v2.2.8 +-------------------- + +* bug #3052 Fix false positive warning about paths overridden by provided as command arguments (kubawerlos) +* bug #3058 IsNullFixer - fix whitespace handling (roukmoute) +* bug #3072 IsNullFixer - fix non_yoda_style edge case (keradus) +* bug #3088 Drop dedicated Phar stub (keradus) +* bug #3100 NativeFunctionInvocationFixer - Fix test if previous token is already namespace separator (SpacePossum) +* bug #3104 DoctrineAnnotationIndentationFixer - Fix str_repeat() error (julienfalque) +* minor #3038 Support PHP 7.2 (SpacePossum, keradus) +* minor #3064 Fix couple of typos (KKSzymanowski) +* minor #3078 ConfigurationResolver - hide context while including config file (keradus) +* minor #3080 Direct function call instead of by string (kubawerlos) +* minor #3085 CiIntegrationTest - skip when no git is available (keradus) +* minor #3087 phar-stub.php - allow PHP 7.2 (keradus) + +Changelog for v2.2.7 +-------------------- + +* bug #3002 Bugfix braces (mnabialek) +* bug #3010 Fix handling of Github releases (julienfalque, keradus) +* bug #3015 Fix exception arguments (julienfalque) +* bug #3016 Verify phar file (keradus) +* bug #3021 Risky rules cleanup (kubawerlos) +* bug #3023 RandomApiMigrationFixer - "rand();" to "random_int(0, getrandmax());" fixing (SpacePossum) +* bug #3024 ConfigurationResolver - Handle empty "rules" value (SpacePossum, keradus) +* bug #3031 IndentationTypeFixer - fix handling tabs in indented comments (keradus) +* minor #2999 Notice when paths from config file are overridden by command arguments (julienfalque, keradus) +* minor #3007 Add PHP 7.2 to Travis build matrix (Jean85) +* minor #3009 CiIntegrationTest - run local (SpacePossum) +* minor #3013 Adjust phpunit configuration (localheinz) +* minor #3017 Fix: Risky tests (localheinz) +* minor #3018 Fix: Make sure that data providers are named correctly (localheinz, keradus) +* minor #3032 .php_cs.dist - handling UnexpectedValueException (keradus) +* minor #3034 Follow newest CS (keradus) +* minor #3036 Drop not existing Standalone group from PHPUnit configuration and duplicated internal tags (keradus) +* minor #3042 Update gitter address (keradus) + +Changelog for v2.2.6 +-------------------- + +* bug #2925 Improve CI integration suggestion (julienfalque) +* bug #2928 TokensAnalyzer::getClassyElements - Anonymous class support (SpacePossum) +* bug #2931 Psr0Fixer, Psr4Fixer - ignore "new class" syntax (dg, keradus) +* bug #2934 Config - fix handling rule without value (keradus, SpacePossum) +* bug #2939 NoUnusedImportsFixer - Fix extra blank line (julienfalque) +* bug #2941 PHP 7.2 - Group imports with trailing comma support (SpacePossum, julienfalque) +* bug #2987 Fix incorrect indentation of comments in `braces` fixer (rob006) +* minor #2927 WhiteSpaceConfig - update message copy and more strict tests (SpacePossum, keradus) +* minor #2930 Trigger website build (keradus) +* minor #2932 Integrate CircleCI (keradus, aidantwoods) +* minor #2933 ProcessLinterTest - Ensure Windows test only runs on Windows, add a Mac test execution (aidantwoods) +* minor #2935 special handling of fabbot.io service if it's using too old PHP CS Fixer version (keradus) +* minor #2937 Travis: execute 5.3 job on precise (keradus) +* minor #2938 Tests fix configuration of project (SpacePossum, keradus) +* minor #2943 FunctionToConstantFixer - test with diff. arguments than fixable (SpacePossum) +* minor #2946 Detect extra old installations (keradus) +* minor #2947 Test suggested CI integration (keradus) +* minor #2951 AccessibleObject - remove most of usage (keradus) +* minor #2969 Shrink down AccessibleObject usage (keradus) +* minor #2982 TrailingCommaInMultilineArrayFixer - simplify isMultilineArray condition (TomasVotruba) + +Changelog for v2.2.5 +-------------------- + +* bug #2807 NoUselessElseFixer - Fix detection of conditional block (SpacePossum) +* bug #2809 Phar release - fix readme generation (SpacePossum, keradus) +* bug #2827 MethodArgumentSpaceFixer - Always remove trailing spaces (julienfalque) +* bug #2835 SelfAccessorFixer - class property fix (mnabialek) +* bug #2848 PhpdocIndentFixer - fix edge case with inline phpdoc (keradus) +* bug #2849 BracesFixer - Fix indentation issues with comments (julienfalque) +* bug #2851 Tokens - ensureWhitespaceAtIndex (GrahamCampbell, SpacePossum) +* bug #2854 NoLeadingImportSlashFixer - Removing leading slash from import even when in global space (kubawerlos) +* bug #2858 Support generic types (keradus) +* bug #2869 Fix handling required configuration (keradus) +* bug #2881 NoUnusedImportsFixer - Bug when trying to insert empty token (GrahamCampbell, keradus) +* bug #2882 DocBlock\Annotation - Fix parsing of collections with multiple key types (julienfalque) +* bug #2886 NoSpacesInsideParenthesisFixer - Do not remove whitespace if next token is comment (SpacePossum) +* bug #2888 SingleImportPerStatementFixer - Add support for function and const (SpacePossum) +* bug #2901 Add missing files to archive files (keradus) +* bug #2914 HeredocToNowdocFixer - works with CRLF line ending (dg) +* bug #2920 RuleSet - Update deprecated configuration of fixers (SpacePossum, keradus) +* minor #1531 Update docs for few generic types (keradus) +* minor #2793 COOKBOOK-FIXERS.md - update to current version, fix links (keradus) +* minor #2812 ProcessLinter - compatibility with Symfony 3.3 (keradus) +* minor #2816 Tokenizer - better docs and validation (keradus) +* minor #2817 Tokenizer - use future-compatible interface (keradus) +* minor #2819 Fix benchmark (keradus) +* minor #2824 code grooming (keradus) +* minor #2826 Exceptions - provide utests (localheinz) +* minor #2828 Enhancement: Reference phpunit.xsd from phpunit.xml.dist (localheinz) +* minor #2830 Differs - add tests (localheinz) +* minor #2832 Fix: Use all the columns (localheinz) +* minor #2833 Doctrine\Annotation\Token - provide utests (localheinz) +* minor #2839 Use PHP 7.2 polyfill instead of xml one (keradus) +* minor #2842 Move null to first position in PHPDoc types (julienfalque) +* minor #2850 ReadmeCommandTest - Prevent diff output (julienfalque) +* minor #2859 Fixed typo and dead code removal (GrahamCampbell) +* minor #2863 FileSpecificCodeSample - add tests (localheinz) +* minor #2864 WhitespacesAwareFixerInterface clean up (Slamdunk) +* minor #2865 AutoReview\FixerTest - test configuration samples (SpacePossum, keradus) +* minor #2867 VersionSpecification - Fix copy-paste typo (SpacePossum) +* minor #2874 LineTest - fix typo (keradus) +* minor #2875 HelpCommand - recursive layout fix (SpacePossum) +* minor #2883 DescribeCommand - Show which sample uses the default configuration (SpacePossum) +* minor #2887 Housekeeping - Strict whitespace checks (SpacePossum) +* minor #2895 ProjectCodeTest - check that classes in no-tests exception exist (keradus) +* minor #2896 Move testing related classes from src to tests (keradus) +* minor #2904 Reapply CS (keradus) +* minor #2910 PhpdocAnnotationWithoutDotFixer - Restrict lowercasing (oschwald) +* minor #2913 Tests - tweaks (SpacePossum, keradus) +* minor #2916 FixerFactory - drop return in sortFixers(), never used (TomasVotruba) + +Changelog for v2.2.4 +-------------------- + +* bug #2682 DoctrineAnnotationIndentationFixer - fix handling nested annotations (edhgoose, julienfalque) +* bug #2700 Fix Doctrine Annotation end detection (julienfalque) +* bug #2715 OrderedImportsFixer - handle indented groups (pilgerone) +* bug #2732 HeaderCommentFixer - fix handling blank lines (s7b4) +* bug #2745 Fix Doctrine Annotation newlines (julienfalque) +* bug #2752 FixCommand - fix typo in warning message (mnapoli) +* bug #2757 GeckoPHPUnit is not dev dependency (keradus) +* bug #2759 Update gitattributes (SpacePossum) +* bug #2763 Fix describe command with PSR-0 fixer (julienfalque) +* bug #2768 Tokens::ensureWhitespaceAtIndex - clean up comment check, add check for T_OPEN (SpacePossum) +* bug #2783 Tokens::ensureWhitespaceAtIndex - Fix handling line endings (SpacePossum) +* minor #2663 Use colors for keywords in commands output (julienfalque, keradus) +* minor #2706 Update README (SpacePossum) +* minor #2714 README.rst - fix wrong value in example (mleko) +* minor #2721 Update phpstorm article link to a fresh blog post (valeryan) +* minor #2727 PHPUnit - use speedtrap (keradus) +* minor #2728 SelfUpdateCommand - verify that it's possible to replace current file (keradus) +* minor #2729 DescribeCommand - add decorated output test (julienfalque) +* minor #2731 BracesFixer - properly pass config in utest dataProvider (keradus) +* minor #2738 Upgrade tests to use new, namespaced PHPUnit TestCase class (keradus) +* minor #2743 Fixing example and description for GeneralPhpdocAnnotationRemoveFixer (kubawerlos) +* minor #2744 AbstractDoctrineAnnotationFixerTestCase - split fixers test cases (julienfalque) +* minor #2755 Fix compatibility with PHPUnit 5.4.x (keradus) +* minor #2758 Readme - improve CI integration guidelines (keradus) +* minor #2769 Psr0Fixer - remove duplicated example (julienfalque) +* minor #2775 NoExtraConsecutiveBlankLinesFixer - remove duplicate code sample. (SpacePossum) +* minor #2778 AutoReview - watch that code samples are unique (keradus) +* minor #2787 Add warnings about missing dom ext and require json ext (keradus) +* minor #2792 Use composer-require-checker (keradus) +* minor #2796 Update .gitattributes (SpacePossum) +* minor #2800 PhpdocTypesFixerTest - Fix typo in covers annotation (SpacePossum) + +Changelog for v2.2.3 +-------------------- + +* bug #2724 Revert #2554 Add short diff. output format (keradus) + +Changelog for v2.2.2 +-------------------- + +Warning, this release breaks BC due to introduction of: + +* minor #2554 Add short diff. output format (SpacePossum, keradus) + +That PR was reverted in v2.2.3, which should be used instead of v2.2.2. + +* bug #2545 RuleSet - change resolvement (SpacePossum) +* bug #2686 Commands readme and describe - fix rare casing when not displaying some possible options of configuration (keradus) +* bug #2711 FixCommand - fix diff optional value handling (keradus) +* minor #2688 AppVeyor - Remove github oauth (keradus) +* minor #2703 Clean ups - No mixed annotations (SpacePossum) +* minor #2704 Create PHP70Migration:risky ruleset (keradus) +* minor #2707 Deprecate other than "yes" or "no" for input options (SpacePossum) +* minor #2709 code grooming (keradus) +* minor #2710 Travis - run more rules on TASK_SCA (keradus) + +Changelog for v2.2.1 +-------------------- + +* bug #2621 Tokenizer - fix edge cases with empty code, registered found tokens and code hash (SpacePossum, keradus) +* bug #2674 SemicolonAfterInstructionFixer - Fix case where block ends with an opening curly brace (ntzm) +* bug #2675 ProcessOutputTest - update tests to pass on newest Symfony components under Windows (keradus) +* minor #2651 Fix UPGRADE.md table syntax so it works in GitHub (ntzm, keradus) +* minor #2665 Travis - Improve trailing spaces detection (julienfalque) +* minor #2666 TransformersTest - move test to auto-review group (keradus) +* minor #2668 add covers annotation (keradus) +* minor #2669 TokensTest - grooming (SpacePossum) +* minor #2670 AbstractFixer: use applyFix instead of fix (Slamdunk) +* minor #2677 README: Correct progressbar option support (Laurens St�tzel) + +Changelog for v2.2.0 +-------------------- + +* bug #2640 NoExtraConsecutiveBlankLinesFixer - Fix single indent characters not working (ntzm) +* feature #2220 Doctrine annotation fixers (julienfalque) +* feature #2431 MethodArgumentSpaceFixer: allow to retain multiple spaces after comma (Slamdunk) +* feature #2459 BracesFixer - Add option for keeping opening brackets on the same line (jtojnar, SpacePossum) +* feature #2486 Add FunctionToConstantFixer (SpacePossum, keradus) +* feature #2505 FunctionDeclarationFixer - Make space after anonymous function configurable (jtojnar, keradus) +* feature #2509 FullOpeningTagFixer - Ensure opening PHP tag is lowercase (jtojnar) +* feature #2532 FixCommand - add stop-on-violation option (keradus) +* feature #2591 Improve process output (julienfalque) +* feature #2603 Add InvisibleSymbols Fixer (ivan1986, keradus) +* feature #2642 Add MagicConstantCasingFixer (ntzm) +* feature #2657 PhpdocToCommentFixer - Allow phpdoc for language constructs (ceeram, SpacePossum) +* minor #2500 Configuration resolver (julienfalque, SpacePossum, keradus) +* minor #2566 Show more details on errors and exceptions. (SpacePossum, julienfalque) +* minor #2597 HHVM - bump required version to 3.18 (keradus) +* minor #2606 FixCommand - fix missing comment close tag (keradus) +* minor #2623 OrderedClassElementsFixer - remove dead code (SpacePossum) +* minor #2625 Update Symfony and Symfony:risky rulesets (keradus) +* minor #2626 TernaryToNullCoalescingFixer - adjust ruleset membership and description (keradus) +* minor #2635 ProjectCodeTest - watch that all classes have dedicated tests (keradus) +* minor #2647 DescribeCommandTest - remove deprecated code usage (julienfalque) +* minor #2648 Move non-code covering tests to AutoReview subnamespace (keradus) +* minor #2652 NoSpacesAroundOffsetFixerTest - fix deprecation (keradus) +* minor #2656 Code grooming (keradus) +* minor #2659 Travis - speed up preparation for phar building (keradus) +* minor #2660 Fixed typo in suggest for ext-mbstring (pascal-hofmann) +* minor #2661 NonPrintableCharacterFixer - include into Symfony:risky ruleset (keradus) + +Changelog for v2.1.3 +-------------------- + +* bug #2358 Cache - Deal with signature encoding (keradus, GrahamCampbell) +* bug #2475 Add shorthand array destructing support (SpacePossum, keradus) +* bug #2595 NoUnusedImportsFixer - Fix import usage detection with properties (julienfalque) +* bug #2605 PhpdocAddMissingParamAnnotationFixer, PhpdocOrderFixer - fix priority issue (SpacePossum) +* bug #2607 Fixers - better comments handling (SpacePossum) +* bug #2612 BracesFixer - Fix early bracket close for do-while loop inside an if without brackets (felixgomez) +* bug #2614 Ensure that '*Fixer::fix()' won't crash when running on non-candidate collection (keradus) +* bug #2630 HeaderCommentFixer - Fix trailing whitespace not removed after AliasFunctionsFixer (kalessil) +* feature #1275 Added PhpdocInlineTagFixer (SpacePossum, keradus) +* feature #1292 Added MethodSeparationFixer (SpacePossum) +* feature #1383 Introduce rules and sets (keradus) +* feature #1416 Mark fixers as risky (keradus) +* feature #1440 Made AbstractFixerTestCase and AbstractIntegrationTestCase public (keradus) +* feature #1489 Added Psr4Fixer (GrahamCampbell) +* feature #1497 ExtraEmptyLinesFixer - allow to remove empty blank lines after configured tags (SpacePossum) +* feature #1529 Added PhpdocPropertyFixer, refactored Tag and Annotation (GrahamCampbell) +* feature #1628 Added OrderedClassElementsFixer (gharlan) +* feature #1742 path argument is used to create an intersection with existing finder (keradus, gharlan) +* feature #1779 Added GeneralPhpdocAnnotationRemoveFixer, GeneralPhpdocAnnotationRenameFixer (keradus) +* feature #1811 Added NoSpacesInsideOffsetFixer (phansys) +* feature #1819 Added DirConstantFixer, ModernizeTypesCastingFixer, RandomApiMigrationFixer (kalessil, SpacePossum, keradus) +* feature #1825 Added junit format (ekho) +* feature #1862 FixerFactory - Do not allow conflicting fixers (SpacePossum) +* feature #1888 Cache refactoring, better cache handling in dry-run mode (localheinz) +* feature #1889 Added SingleClassElementPerStatementFixer (phansys, SpacePossum) +* feature #1903 FixCommand - allow to pass multiple path argument (keradus) +* feature #1913 Introduce path-mode CLI option (keradus) +* feature #1949 Added DeclareStrictTypesFixer, introduce options for HeaderCommentFixer (Seldaek, SpacePossum, keradus) +* feature #1955 Introduce CT_ARRAY_INDEX_CURLY_BRACE_OPEN and CT_ARRAY_INDEX_CURLY_BRACE_CLOSE (keradus) +* feature #1958 Added NormalizeIndexBraceFixer (keradus) +* feature #2069 Add semicolon after instruction fixer (SpacePossum) +* feature #2089 Add `no_spaces_around_offset` fixer (phansys) +* feature #2179 BinaryOperatorSpacesFixer - add (un)align configuration options (SpacePossum) +* feature #2192 Add PowToExponentiationFixer (SpacePossum, keradus) +* feature #2207 Added ReturnTypeDeclarationFixer (keradus) +* feature #2213 VisibilityRequiredFixer - Add support for class const visibility added in PHP7.1. (SpacePossum) +* feature #2221 Add support for user-defined whitespaces (keradus) +* feature #2244 Config cleanup (keradus, SpacePossum) +* feature #2247 PhpdocAnnotationWithoutDotFixer - support more cases (keradus) +* feature #2289 Add PhpdocAddMissingParamAnnotationFixer (keradus) +* feature #2331 Add DescribeCommand (keradus, SpacePossum) +* feature #2332 New colours of diff on console (keradus) +* feature #829 add support for .php_cs.dist file (keradus) +* feature #998 MethodArgumentSpaceFixer - enhance, now only one space after comma (trilopin, keradus) +* minor #1007 Simplify Transformers (keradus) +* minor #1050 Make Config's setDir() fluent like the rest of methods (gonzaloserrano) +* minor #1062 Added NamespaceOperatorTransformer (gharlan) +* minor #1078 Exit status should be 0 if there are no errors (gharlan) +* minor #1101 CS: fix project itself (localheinz) +* minor #1102 Enhancement: List errors occurred before, during and after fixing (localheinz) +* minor #1105 Token::isStructureAlternativeEnd - remove unused method (keradus) +* minor #1106 readme grooming (SpacePossum, keradus) +* minor #1115 Fixer - simplify flow (keradus) +* minor #1118 Process output refactor (SpacePossum) +* minor #1132 Linter - public methods should be first (keradus) +* minor #1134 Token::isWhitespace - simplify interface (keradus) +* minor #1140 FixerInterface - check if fixer should be applied by isCandidate method (keradus) +* minor #1146 Linter - detect executable (keradus) +* minor #1156 deleted old ConfigurationResolver class (keradus) +* minor #1160 Grammar fix to README (Falkirks) +* minor #1174 DefaultFinder - boost performance by not filtering when files array is empty (keradus) +* minor #1179 Exit with non-zero if invalid files were detected prior to fixing (localheinz) +* minor #1186 Finder - do not search for .xml and .yml files (keradus) +* minor #1206 BracesFixer::getClassyTokens - remove duplicated method (keradus) +* minor #1222 Made fixers final (GrahamCampbell) +* minor #1229 Tokens - Fix PHPDoc (SpacePossum) +* minor #1241 More details on exceptions. (SpacePossum) +* minor #1263 Made internal classes final (GrahamCampbell) +* minor #1272 Readme - Add spaces around PHP-CS-Fixer headers (Soullivaneuh) +* minor #1283 Error - Fixed type phpdoc (GrahamCampbell) +* minor #1284 Token - Fix PHPDoc (SpacePossum) +* minor #1314 Added missing internal annotations (keradus) +* minor #1329 Psr0Fixer - move to contrib level (gharlan) +* minor #1340 Clean ups (SpacePossum) +* minor #1341 Linter - throw exception when write fails (SpacePossum) +* minor #1348 Linter - Prefer error output when throwing a linting exception (GrahamCampbell) +* minor #1350 Add "phpt" as a valid extension (henriquemoody) +* minor #1376 Add time and memory to XML report (junichi11) +* minor #1387 Made all test classes final (keradus) +* minor #1388 Made all tests internal (keradus) +* minor #1390 Added ProjectCodeTest that tests if all classes inside tests are internal and final or abstract (keradus) +* minor #1391 Fixer::getLevelAsString is no longer static (keradus) +* minor #1392 Add report to XML report as the root node (junichi11) +* minor #1394 Stop mixing level from config file and fixers from CLI arg when one of fixers has dash (keradus) +* minor #1426 MethodSeparationFixer - Fix spacing around comments (SpacePossum, keradus) +* minor #1432 Fixer check on factory (Soullivaneuh) +* minor #1434 Add Test\AccessibleObject class (keradus) +* minor #1442 FixerFactory - disallow to register multiple fixers with same name (keradus) +* minor #1477 rename PhpdocShortDescriptionFixer into PhpdocSummaryFixer (keradus) +* minor #1481 Fix running the tests (keradus) +* minor #1482 move AbstractTransformerTestBase class outside Tests dir (keradus) +* minor #1530 Added missing internal annotation (GrahamCampbell) +* minor #1534 Clean ups (SpacePossum) +* minor #1536 Typo fix (fabpot) +* minor #1555 Fixed indentation in composer.json (GrahamCampbell) +* minor #1558 [2.0] Cleanup the tags property in the abstract phpdoc types fixer (GrahamCampbell) +* minor #1567 PrintToEchoFixer - add to symfony rule set (gharlan) +* minor #1607 performance improvement (gharlan) +* minor #1621 Switch to PSR-4 (keradus) +* minor #1631 Configuration exceptions exception cases on master. (SpacePossum) +* minor #1646 Remove non-default Config/Finder classes (keradus) +* minor #1648 Fixer - avoid extra calls to getFileRelativePathname (GrahamCampbell) +* minor #1649 Consider the php version when caching (GrahamCampbell) +* minor #1652 Rename namespace "Symfony\CS" to "PhpCsFixer" (gharlan) +* minor #1666 new Runner, ProcessOutputInterface, DifferInterface and ResultInterface (keradus) +* minor #1674 Config - add addCustomFixers method (PedroTroller) +* minor #1677 Enhance tests (keradus) +* minor #1695 Rename Fixers (keradus) +* minor #1702 Upgrade guide (keradus) +* minor #1707 ExtraEmptyLinesFixer - fix configure docs (keradus) +* minor #1712 NoExtraConsecutiveBlankLinesFixer - Remove blankline after curly brace open (SpacePossum) +* minor #1718 CLI: rename --config-file argument (keradus) +* minor #1722 Renamed not_operators_with_space to not_operator_with_space (GrahamCampbell) +* minor #1728 PhpdocNoSimplifiedNullReturnFixer - rename back to PhpdocNoEmptyReturnFixer (keradus) +* minor #1729 Renamed whitespacy_lines to no_whitespace_in_blank_lines (GrahamCampbell) +* minor #1731 FixCommand - value for config option is required (keradus) +* minor #1732 move fixer classes from level subdirs to thematic subdirs (gharlan, keradus) +* minor #1733 ConfigurationResolver - look for .php_cs file in cwd as well (keradus) +* minor #1737 RuleSet/FixerFactory - sort arrays content (keradus) +* minor #1751 FixerInterface::configure - method should always override configuration, not patch it (keradus) +* minor #1752 Remove unused code (keradus) +* minor #1756 Finder - clean up code (keradus) +* minor #1757 Psr0Fixer - change way of configuring the fixer (keradus) +* minor #1762 Remove ConfigInterface::getDir, ConfigInterface::setDir, Finder::setDir and whole FinderInterface (keradus) +* minor #1764 Remove ConfigAwareInterface (keradus) +* minor #1780 AbstractFixer - throw error on configuring non-configurable Fixer (keradus) +* minor #1782 rename fixers (gharlan) +* minor #1815 NoSpacesInsideParenthesisFixer - simplify implementation (keradus) +* minor #1821 Ensure that PhpUnitDedicateAssertFixer runs after NoAliasFunctionsFixer, clean up NoEmptyCommentFixer (SpacePossum) +* minor #1824 Reporting extracted to separate classes (ekho, keradus, SpacePossum) +* minor #1826 Fixer - remove measuring fixing time per file (keradus) +* minor #1843 FileFilterIterator - add missing import (GrahamCampbell) +* minor #1845 FileCacheManager - Allow linting to determine the cache state too (GrahamCampbell) +* minor #1846 FileFilterIterator - Corrected an iterator typehint (GrahamCampbell) +* minor #1848 DocBlock - Remove some old unused phpdoc tags (GrahamCampbell) +* minor #1856 NoDuplicateSemicolonsFixer - Remove overcomplete fixer (SpacePossum) +* minor #1861 Fix: Offset should be Offset (localheinz) +* minor #1867 Print non-report output to stdErr (SpacePossum, keradus) +* minor #1873 Enhancement: Show path to cache file if it exists (localheinz) +* minor #1875 renamed Composer package (fabpot) +* minor #1882 Runner - Handle throwables too (GrahamCampbell) +* minor #1886 PhpdocScalarFixer - Fix lowercase str to string too (GrahamCampbell) +* minor #1940 README.rst - update CI example (keradus) +* minor #1947 SCA, CS, add more tests (SpacePossum, keradus) +* minor #1954 tests - stop using deprecated method (sebastianbergmann) +* minor #1962 TextDiffTest - tests should not produce cache file (keradus) +* minor #1973 Introduce fast PHP7 based linter (keradus) +* minor #1999 Runner - No need to determine relative file name twice (localheinz) +* minor #2002 FileCacheManagerTest - Adjust name of test and variable (localheinz) +* minor #2010 NoExtraConsecutiveBlankLinesFixer - SF rule set, add 'extra' (SpacePossum) +* minor #2013 no_whitespace_in_blank_lines -> no_whitespace_in_blank_line (SpacePossum) +* minor #2024 AbstractFixerTestCase - check if there is no duplicated Token instance inside Tokens collection (keradus) +* minor #2031 COOKBOOK-FIXERS.md - update calling doTest method (keradus) +* minor #2032 code grooming (keradus) +* minor #2068 Code grooming (keradus) +* minor #2073 DeclareStrictTypesFixer - Remove fix CS fix logic from fixer. (SpacePossum) +* minor #2088 TokenizerLintingResult - expose line number of parsing error (keradus) +* minor #2093 Tokens - add block type BLOCK_TYPE_ARRAY_INDEX_CURLY_BRACE (SpacePossum) +* minor #2095 Transformers - add required PHP version (keradus) +* minor #2096 Introduce CT for PHP7 (keradus) +* minor #2119 Create @Symfony:risky ruleset (keradus) +* minor #2163 ClassKeywordRemoveFixerTest - Fix tests (SpacePossum) +* minor #2180 FixCommand - don't refer to renamed rules (keradus) +* minor #2181 Disallow to disable linter (keradus) +* minor #2194 semicolon_after_instruction,no_unneeded_control_parentheses prio issue (SpacePossum) +* minor #2199 make fixers less risky (SpacePossum) +* minor #2206 Add PHP70Migration ruleset (keradus) +* minor #2217 SelfUpdateCommand - Print version of update fixer (SpacePossum) +* minor #2223 update integration test format (keradus) +* minor #2227 Stop polluting global namespace with CT (keradus) +* minor #2237 DX: extend integration tests for PSR2 and Symfony rulesets (keradus) +* minor #2240 Make some objects immutable (keradus) +* minor #2251 ProtectedToPrivateFixer - fix priority, fix comments with new fixer names (SpacePossum) +* minor #2252 ClassDefinitionFixer - Set configuration of the fixer in the RuleSet of SF. (SpacePossum) +* minor #2257 extend Symfony_whitespaces itest (keradus) +* minor #2258 README.rst - indicate configurable rules (keradus) +* minor #2267 RuleSet - validate set (keradus) +* minor #2268 Use strict parameters for PHP functions (keradus) +* minor #2273 fixed typo (fabpot) +* minor #2274 ShortArraySyntaxFixer/LongArraySyntaxFixer - Merge conflicting fixers (SpacePossum) +* minor #2275 Clean ups (SpacePossum) +* minor #2278 Concat*Fixer - unify concat fixers (SpacePossum, keradus) +* minor #2279 Use Prophecy (keradus) +* minor #2284 Code grooming (SpacePossum) +* minor #2285 IntegrationCase is now aware about RuleSet but not Fixers (keradus, SpacePossum) +* minor #2286 Phpdoc*Fixer - unify rename fixers (SpacePossum, keradus) +* minor #2288 FixerInterface::configure(null) reset fixer to use default configuration (keradus) +* minor #2291 Make fixers ready to use directly after creation (keradus) +* minor #2295 Code grooming (keradus) +* minor #2296 ProjectCodeTest - make test part of regular testsuite, not standalone one (keradus) +* minor #2298 ConfigurationResolver - grooming (SpacePossum) +* minor #2300 Simplify rule set (SpacePossum, keradus) +* minor #2306 DeclareStrictTypesFixer - do not move tokens (SpacePossum) +* minor #2312 RuleSet - sort rules (localheinz) +* minor #2313 DX: provide doctyping for tests (keradus) +* minor #2317 Add utests (keradus) +* minor #2318 *TestCase - Reduce visibility of setUp() (localheinz) +* minor #2319 Code grooming (keradus) +* minor #2322 DX: use whitemessy aware assertion (keradus) +* minor #2324 `Echo|Print*Fixer` - unify printing fixers (SpacePossum, keradus) +* minor #2337 Normalize rule naming (keradus) +* minor #2338 Drop hacks for unsupported HHVM (keradus) +* minor #2339 Add some Fixer descriptions (SpacePossum, keradus) +* minor #2343 PowToExponentiationFixer - allow to run on 5.6.0 as well (keradus) +* minor #767 Add @internal tag (keradus) +* minor #807 Tokens::isMethodNameIsMagic - remove unused method (keradus) +* minor #809 Split Tokens into Tokens and TokensAnalyzer (keradus) +* minor #844 Renamed phpdoc_params to phpdoc_align (GrahamCampbell) +* minor #854 Change default level to PSR2 (keradus) +* minor #873 Config - using cache by default (keradus) +* minor #902 change FixerInterface (keradus) +* minor #911 remove Token::$line (keradus) +* minor #914 All Transformer classes should be named with Transformer as suffix (keradus) +* minor #915 add UseTransformer (keradus) +* minor #916 add ArraySquareBraceTransformer (keradus) +* minor #917 clean up Transformer tests (keradus) +* minor #919 CurlyBraceTransformer - one transformer to handle all curly braces transformations (keradus) +* minor #928 remove Token::getLine (keradus) +* minor #929 add WhitespacyCommentTransformer (keradus) +* minor #937 fix docs/typehinting in few classes (keradus) +* minor #958 FileCacheManager - remove code for BC support (keradus) +* minor #979 Improve Tokens::clearEmptyTokens performance (keradus) +* minor #981 Tokens - code grooming (keradus) +* minor #988 Fixers - no need to search for tokens of given kind in extra loop (keradus) +* minor #989 No need for loop in Token::equals (keradus) + +Changelog for v1.13.3 +--------------------- + +* minor #3042 Update gitter address (keradus) + +Changelog for v1.13.2 +--------------------- + +* minor #2946 Detect extra old installations (keradus) + +Changelog for v1.13.1 +--------------------- + +* minor #2342 Application - adjust test to not depend on symfony/console version (keradus) +* minor #2344 AppVeyor: enforce PHP version (keradus) + +Changelog for v1.13.0 +--------------------- + +* bug #2303 ClassDefinitionFixer - Anonymous classes fixing (SpacePossum) +* feature #2208 Added fixer for PHPUnit's @expectedException annotation (ro0NL) +* feature #2249 Added ProtectedToPrivateFixer (Slamdunk, SpacePossum) +* feature #2264 SelfUpdateCommand - Do not update to next major version by default (SpacePossum) +* feature #2328 ClassDefinitionFixer - Anonymous classes format by PSR12 (SpacePossum) +* feature #2333 PhpUnitFqcnAnnotationFixer - support more annotations (keradus) +* minor #2256 EmptyReturnFixer - it's now risky fixer due to null vs void (keradus) +* minor #2281 Add issue template (SpacePossum) +* minor #2307 Update .editorconfig (SpacePossum) +* minor #2310 CI: update AppVeyor to use newest PHP, silence the composer (keradus) +* minor #2315 Token - Deprecate getLine() (SpacePossum) +* minor #2320 Clear up status code on 1.x (SpacePossum) + +Changelog for v1.12.4 +--------------------- + +* bug #2235 OrderedImportsFixer - PHP 7 group imports support (SpacePossum) +* minor #2276 Tokens cleanup (keradus) +* minor #2277 Remove trailing spaces (keradus) +* minor #2294 Improve Travis configuration (keradus) +* minor #2297 Use phpdbg instead of xdebug (keradus) +* minor #2299 Travis: proper xdebug disabling (keradus) +* minor #2301 Travis: update platform adjusting (keradus) + +Changelog for v1.12.3 +--------------------- + +* bug #2155 ClassDefinitionFixer - overhaul (SpacePossum) +* bug #2187 MultipleUseFixer - Fix handling comments (SpacePossum) +* bug #2209 LinefeedFixer - Fix in a safe way (SpacePossum) +* bug #2228 NoEmptyLinesAfterPhpdocs, SingleBlankLineBeforeNamespace - Fix priority (SpacePossum) +* bug #2230 FunctionDeclarationFixer - Fix T_USE case (SpacePossum) +* bug #2232 Add a test for style of variable declaration : var (daiglej) +* bug #2246 Fix itest requirements (keradus) +* minor #2238 .gitattributes - specified line endings (keradus) +* minor #2239 IntegrationCase - no longer internal (keradus) + +Changelog for v1.12.2 +--------------------- + +* bug #2191 PhpdocToCommentFixer - fix false positive for docblock of variable (keradus) +* bug #2193 UnneededControlParenthesesFixer - Fix more return cases. (SpacePossum) +* bug #2198 FileCacheManager - fix exception message and undefined property (j0k3r) +* minor #2170 Add dollar sign prefix for consistency (bcremer) +* minor #2190 .travis.yml - improve Travis speed for tags (keradus) +* minor #2196 PhpdocTypesFixer - support iterable type (GrahamCampbell) +* minor #2197 Update cookbook and readme (g105b, SpacePossum) +* minor #2203 README.rst - change formatting (ro0NL) +* minor #2204 FixCommand - clean unused var (keradus) +* minor #2205 Add integration test for iterable type (keradus) + +Changelog for v1.12.1 +--------------------- + +* bug #2144 Remove temporary files not deleted by destructor on failure (adawolfa) +* bug #2150 SelfUpdateCommand: resolve symlink (julienfalque) +* bug #2162 Fix issue where an exception is thrown if the cache file exists but is empty. (ikari7789) +* bug #2164 OperatorsSpacesFixer - Do not unalign double arrow and equals operators (SpacePossum) +* bug #2167 Rewrite file removal (keradus) +* minor #2152 Code cleanup (keradus) +* minor #2154 ShutdownFileRemoval - Fixed file header (GrahamCampbell) + +Changelog for v1.12.0 +--------------------- + +* feature #1493 Added MethodArgumentDefaultValueFixer (lmanzke) +* feature #1495 BracesFixer - added support for declare (EspadaV8) +* feature #1518 Added ClassDefinitionFixer (SpacePossum) +* feature #1543 [PSR-2] Switch case space fixer (Soullivaneuh) +* feature #1577 Added SpacesAfterSemicolonFixer (SpacePossum) +* feature #1580 Added HeredocToNowdocFixer (gharlan) +* feature #1581 UnneededControlParenthesesFixer - add "break" and "continue" support (gharlan) +* feature #1610 HashToSlashCommentFixer - Add (SpacePossum) +* feature #1613 ScalarCastFixer - LowerCaseCastFixer - Add (SpacePossum) +* feature #1659 NativeFunctionCasingFixer - Add (SpacePossum) +* feature #1661 SwitchCaseSemicolonToColonFixer - Add (SpacePossum) +* feature #1662 Added CombineConsecutiveUnsetsFixer (SpacePossum) +* feature #1671 Added NoEmptyStatementFixer (SpacePossum) +* feature #1705 Added NoUselessReturnFixer (SpacePossum, keradus) +* feature #1735 Added NoTrailingWhitespaceInCommentFixer (keradus) +* feature #1750 Add PhpdocSingleLineVarSpacingFixer (SpacePossum) +* feature #1765 Added NoEmptyPhpdocFixer (SpacePossum) +* feature #1773 Add NoUselessElseFixer (gharlan, SpacePossum) +* feature #1786 Added NoEmptyCommentFixer (SpacePossum) +* feature #1792 Add PhpUnitDedicateAssertFixer. (SpacePossum) +* feature #1894 BracesFixer - correctly fix indents of anonymous functions/classes (gharlan) +* feature #1985 Added ClassKeywordRemoveFixer (Soullivaneuh) +* feature #2020 Added PhpdocAnnotationWithoutDotFixer (keradus) +* feature #2067 Added DeclareEqualNormalizeFixer (keradus) +* feature #2078 Added SilencedDeprecationErrorFixer (HeahDude) +* feature #2082 Added MbStrFunctionsFixer (Slamdunk) +* bug #1657 SwitchCaseSpaceFixer - Fix spacing between 'case' and semicolon (SpacePossum) +* bug #1684 SpacesAfterSemicolonFixer - fix loops handling (SpacePossum, keradus) +* bug #1700 Fixer - resolve import conflict (keradus) +* bug #1836 NoUselessReturnFixer - Do not remove return if last statement in short if statement (SpacePossum) +* bug #1879 HeredocToNowdocFixer - Handle space in heredoc token (SpacePossum) +* bug #1896 FixCommand - Fix escaping of diff output (SpacePossum) +* bug #2034 IncludeFixer - fix support for close tag (SpacePossum) +* bug #2040 PhpdocAnnotationWithoutDotFixer - fix crash on odd character (keradus) +* bug #2041 DefaultFinder should implement FinderInterface (keradus) +* bug #2050 PhpdocAnnotationWithoutDotFixer - handle ellipsis (keradus) +* bug #2051 NativeFunctionCasingFixer - call to constructor with default NS of class with name matching native function name fix (SpacePossum) +* minor #1538 Added possibility to lint tests (gharlan) +* minor #1569 Add sample to get a specific version of the fixer (Soullivaneuh) +* minor #1571 Enhance integration tests (keradus) +* minor #1578 Code grooming (keradus) +* minor #1583 Travis - update matrix (keradus) +* minor #1585 Code grooming - Improve utests code coverage (SpacePossum) +* minor #1586 Add configuration exception classes and exit codes (SpacePossum) +* minor #1594 Fix invalid PHP code samples in utests (SpacePossum) +* minor #1597 MethodArgumentDefaultValueFixer - refactoring and fix closures with "use" clause (gharlan) +* minor #1600 Added more integration tests (SpacePossum, keradus) +* minor #1605 integration tests - swap EXPECT and INPUT (optional INPUT) (gharlan) +* minor #1608 Travis - change matrix order for faster results (gharlan) +* minor #1609 CONTRIBUTING.md - Don't rebase always on master (SpacePossum) +* minor #1616 IncludeFixer - fix and test more cases (SpacePossum) +* minor #1622 AbstractIntegratationTest - fix linting test cases (gharlan) +* minor #1624 fix invalid code in test cases (gharlan) +* minor #1625 Travis - switch to trusty (keradus) +* minor #1627 FixCommand - fix output (keradus) +* minor #1630 Pass along the exception code. (SpacePossum) +* minor #1632 Php Inspections (EA Extended): SCA for 1.12 (kalessil) +* minor #1633 Fix CS for project itself (keradus) +* minor #1634 Backport some minor changes from 2.x line (keradus) +* minor #1637 update PHP Coveralls (keradus) +* minor #1639 Revert "Travis - set dist to trusty" (keradus) +* minor #1641 AppVeyor/Travis - use GITHUB_OAUTH_TOKEN (keradus) +* minor #1642 AppVeyor - install dev deps as well (keradus) +* minor #1647 Deprecate non-default Configs and Finders (keradus) +* minor #1654 Split output to stderr and stdout (SpacePossum) +* minor #1660 update phpunit version (gharlan) +* minor #1663 DuplicateSemicolonFixer - Remove duplicate semicolons even if there are comments between those (SpacePossum) +* minor #1664 IncludeFixer - Add missing test case (SpacePossum) +* minor #1668 Code grooming (keradus) +* minor #1669 NativeFunctionCasingFixer - move to Symfony level (keradus) +* minor #1670 Backport Finder and Config classes from 2.x line (keradus) +* minor #1682 ElseifFixer - handle comments (SpacePossum) +* minor #1689 AbstractIntegrationTest - no need for single-char group and docs grooming (keradus) +* minor #1690 Integration tests - allow to not check priority, introduce IntegrationCase (keradus) +* minor #1701 Fixer - Renamed import alias (GrahamCampbell) +* minor #1708 Update composer.json requirements (keradus) +* minor #1734 Travis: Turn on linting (keradus) +* minor #1736 Integration tests - don't check priority for tests using short_tag fixer (keradus) +* minor #1739 NoTrailingWhitespaceInCommentFixer - move to PSR2 level (keradus) +* minor #1763 Deprecate ConfigInterface::getDir, ConfigInterface::setDir, Finder::setDir (keradus) +* minor #1777 NoTrailingWhitespaceInCommentFixer - fix parent class (keradus) +* minor #1816 PhpUnitDedicateAssertFixer - configuration is not required anymore (keradus) +* minor #1849 DocBlock - The category tag should be together with package (GrahamCampbell) +* minor #1870 Update README.rst (glensc) +* minor #1880 FixCommand - fix stdErr detection (SpacePossum) +* minor #1881 NoEmptyStatementFixer - handle anonymous classes correctly (gharlan) +* minor #1906 .php_cs - use no_useless_else rule (keradus) +* minor #1915 NoEmptyComment - move to Symfony level (SpacePossum) +* minor #1917 BracesFixer - fixed comment handling (gharlan) +* minor #1919 EmptyReturnFixer - move fixer outside of Symfony level (keradus) +* minor #2036 OrderedUseFixer - adjust tests (keradus) +* minor #2056 Travis - run nightly PHP (keradus) +* minor #2061 UnusedUseFixer and LineAfterNamespace - add new integration test (keradus) +* minor #2097 Add lambda tests for 7.0 and 7.1 (SpacePossum) +* minor #2111 .travis.yml - rename PHP 7.1 env (keradus) +* minor #2112 Fix 1.12 line (keradus) +* minor #2118 SilencedDeprecationErrorFixer - adjust level (keradus) +* minor #2132 composer.json - rename package name (keradus) +* minor #2133 Apply ordered_class_elements rule (keradus) +* minor #2138 composer.json - disallow to run on PHP 7.2+ (keradus) + +Changelog for v1.11.8 +--------------------- + +* bug #2143 ReadmeCommand - fix running command on phar file (keradus) +* minor #2129 Add .gitattributes to remove unneeded files (Slamdunk) +* minor #2141 Move phar building to PHP 5.6 job as newest box.phar is no longer working on 5.3 (keradus) + +Changelog for v1.11.7 +--------------------- + +* bug #2108 ShortArraySyntaxFixer, TernarySpacesFixer, UnalignEqualsFixer - fix priority bug (SpacePossum) +* bug #2092 ConcatWithoutSpacesFixer, OperatorsSpacesFixer - fix too many spaces, fix incorrect fixing of lines with comments (SpacePossum) + +Changelog for v1.11.6 +--------------------- + +* bug #2086 Braces - fix bug with comment in method prototype (keradus) +* bug #2077 SingleLineAfterImportsFixer - Do not remove lines between use cases (SpacePossum) +* bug #2079 TernarySpacesFixer - Remove multiple spaces (SpacePossum) +* bug #2087 Fixer - handle PHP7 Errors as well (keradus) +* bug #2072 LowercaseKeywordsFixer - handle CT_CLASS_CONSTANT (tgabi333) +* bug #2066 LineAfterNamespaceFixer - Handle close tag (SpacePossum) +* bug #2057 LineAfterNamespaceFixer - adding too much extra lines where namespace is last statement (keradus) +* bug #2059 OperatorsSpacesFixer - handle declare statement (keradus) +* bug #2060 UnusedUseFixer - fix handling whitespaces around removed import (keradus) +* minor #2071 ShortEchoTagFixer - allow to run tests on PHP 5.3 (keradus) + +Changelog for v1.11.5 +--------------------- + +* bug #2012 Properly build phar file for lowest supported PHP version (keradus) +* bug #2037 BracesFixer - add support for anonymous classes (keradus) +* bug #1989 Add support for PHP 7 namespaces (SpacePossum) +* bug #2019 Fixing newlines added after curly brace string index access (jaydiablo) +* bug #1840 [Bug] BracesFixer - Do add a line before close tag (SpacePossum) +* bug #1994 EchoToPrintFixer - Fix T_OPEN_TAG_WITH_ECHO on hhvm (keradus) +* bug #1970 Tokens - handle semi-reserved PHP 7 keywords (keradus) +* minor #2017 PHP7 integration tests (keradus) +* minor #1465 Bump supported HHVM version, improve ShortEchoTagFixer on HHVM (keradus) +* minor #1995 Rely on own phpunit, not one from CI service (keradus) + +Changelog for v1.11.4 +--------------------- + +* bug #1956 SelfUpdateCommand - don't update to non-stable version (keradus) +* bug #1963 Fix not wanted unneeded_control_parentheses fixer for clone (Soullivaneuh) +* bug #1960 Fix invalid test cases (keradus) +* bug #1939 BracesFixer - fix handling comment around control token (keradus) +* minor #1927 NewWithBracesFixer - remove invalid testcase (keradus) + +Changelog for v1.11.3 +--------------------- + +* bug #1868 NewWithBracesFixer - fix handling more neighbor tokens (keradus) +* bug #1893 BracesFixer - handle comments inside lambda function prototype (keradus) +* bug #1806 SelfAccessorFixer - skip anonymous classes (gharlan) +* bug #1813 BlanklineAfterOpenTagFixer, NoBlankLinesBeforeNamespaceFixer - fix priority (SpacePossum) +* minor #1807 Tokens - simplify isLambda() (gharlan) + +Changelog for v1.11.2 +--------------------- + +* bug #1776 EofEndingFixer - new line on end line comment is allowed (Slamdunk) +* bug #1775 FileCacheManager - ignore corrupted serialized data (keradus) +* bug #1769 FunctionDeclarationFixer - fix more cases (keradus) +* bug #1747 Fixer - Fix ordering of fixer when both level and custom fixers are used (SpacePossum) +* bug #1744 Fixer - fix rare situation when file was visited twice (keradus) +* bug #1710 LowercaseConstantFixer - Fix comment cases. (SpacePossum) +* bug #1711 FunctioncallSpaceFixer - do not touch function declarations. (SpacePossum) +* minor #1798 LintManager - meaningful tempnam (Slamdunk) +* minor #1759 UniqueFileIterator - performance improvement (GrahamCampbell) +* minor #1745 appveyor - fix build (keradus) + +Changelog for v1.11.1 +--------------------- + +* bug #1680 NewWithBracesFixer - End tags (SpacePossum) +* bug #1685 EmptyReturnFixer - Make independent of LowercaseConstantsFixer (SpacePossum) +* bug #1640 IntegrationTest - fix directory separator (keradus) +* bug #1595 ShortTagFixer - fix priority (keradus) +* bug #1576 SpacesBeforeSemicolonFixer - do not remove space before semicolon if that space is after a semicolon (SpacePossum) +* bug #1570 UnneededControlParenthesesFixer - fix test samples (keradus) +* minor #1653 Update license year (gharlan) + +Changelog for v1.11 +------------------- + +* feature #1550 Added UnneededControlParenthesesFixer (Soullivaneuh, keradus) +* feature #1532 Added ShortBoolCastFixer (SpacePossum) +* feature #1523 Added EchoToPrintFixer and PrintToEchoFixer (Soullivaneuh) +* feature #1552 Warn when running with xdebug extension (SpacePossum) +* feature #1484 Added ArrayElementNoSpaceBeforeCommaFixer and ArrayElementWhiteSpaceAfterCommaFixer (amarczuk) +* feature #1449 PhpUnitConstructFixer - Fix more use cases (SpacePossum) +* feature #1382 Added PhpdocTypesFixer (GrahamCampbell) +* feature #1384 Add integration tests (SpacePossum) +* feature #1349 Added FunctionTypehintSpaceFixer (keradus) +* minor #1562 Fix invalid PHP code samples in utests (SpacePossum) +* minor #1560 Fixed project name in xdebug warning (gharlan) +* minor #1545 Fix invalid PHP code samples in utests (SpacePossum) +* minor #1554 Alphabetically sort entries in .gitignore (GrahamCampbell) +* minor #1527 Refactor the way types work on annotations (GrahamCampbell) +* minor #1546 Update coding guide in cookbook (keradus) +* minor #1526 Support more annotations when fixing types in phpdoc (GrahamCampbell) +* minor #1535 clean ups (SpacePossum) +* minor #1510 Added Symfony 3.0 support (Ener-Getick) +* minor #1520 Code grooming (keradus) +* minor #1515 Support property, property-read and property-write tags (GrahamCampbell) +* minor #1488 Added more inline phpdoc tests (GrahamCampbell) +* minor #1496 Add docblock to AbstractFixerTestBase::makeTest (lmanzke) +* minor #1467 PhpdocShortDescriptionFixer - add support for Japanese sentence-ending characters (fritz-c) +* minor #1453 remove calling array_keys in foreach loops (keradus) +* minor #1448 Code grooming (keradus) +* minor #1437 Added import fixers integration test (GrahamCampbell) +* minor #1433 phpunit.xml.dist - disable gc (keradus) +* minor #1427 Change arounded to surrounded in README.rst (36degrees) +* minor #1420 AlignDoubleArrowFixer, AlignEqualsFixer - add integration tests (keradus) +* minor #1423 appveyor.yml - do not cache C:\tools, its internal forAppVeyor (keradus) +* minor #1400 appveyor.yml - add file (keradus) +* minor #1396 AbstractPhpdocTypesFixer - instance method should be called on instance (keradus) +* minor #1395 code grooming (keradus) +* minor #1393 boost .travis.yml file (keradus) +* minor #1372 Don't allow PHP 7 to fail (GrahamCampbell) +* minor #1332 PhpUnitConstructFixer - fix more functions (keradus) +* minor #1339 CONTRIBUTING.md - add link to PSR-5 (keradus) +* minor #1346 Core grooming (SpacePossum) +* minor #1328 Tokens: added typehint for Iterator elements (gharlan) + +Changelog for v1.10.3 +--------------------- + +* bug #1559 WhitespacyLinesFixer - fix bug cases (SpacePossum, keradus) +* bug #1541 Psr0Fixer - Ignore filenames that are a reserved keyword or predefined constant (SpacePossum) +* bug #1537 Psr0Fixer - ignore file without name or with name started by digit (keradus) +* bug #1516 FixCommand - fix wrong message for dry-run (SpacePossum) +* bug #1486 ExtraEmptyLinesFixer - Remove extra lines after comment lines too (SpacePossum) +* bug #1503 Psr0Fixer - fix case with comments lying around (GrahamCampbell) +* bug #1474 PhpdocToCommentFixer - fix not properly fixing for block right after namespace (GrahamCampbell) +* bug #1478 BracesFixer - do not remove empty lines after class opening (keradus) +* bug #1468 Add missing ConfigInterface::getHideProgress() (Eugene Leonovich, rybakit) +* bug #1466 Fix bad indent on align double arrow fixer (Soullivaneuh, keradus) +* bug #1479 Tokens - fix detection of short array (keradus) + +Changelog for v1.10.2 +--------------------- + +* bug #1461 PhpUnitConstructFixer - fix case when first argument is an expression (keradus) +* bug #1460 AlignDoubleArrowFixer - fix handling of nested arrays (Soullivaneuh, keradus) + +Changelog for v1.10.1 +--------------------- + +* bug #1424 Fixed the import fixer priorities (GrahamCampbell) +* bug #1444 OrderedUseFixer - fix next case (keradus) +* bug #1441 BracesFixer - fix next case (keradus) +* bug #1422 AlignDoubleArrowFixer - fix handling of nested array (SpacePossum) +* bug #1425 PhpdocInlineTagFixerTest - fix case when met invalid PHPDoc (keradus) +* bug #1419 AlignDoubleArrowFixer, AlignEqualsFixer - fix priorities (keradus) +* bug #1415 BlanklineAfterOpenTagFixer - Do not add a line break if there is one already. (SpacePossum) +* bug #1410 PhpdocIndentFixer - Fix for open tag (SpacePossum) +* bug #1401 PhpdocVarWithoutNameFixer - Fixed the var without name fixer for inline docs (keradus, GrahamCampbell) +* bug #1369 Fix not well-formed XML output (junichi11) +* bug #1356 Psr0Fixer - disallow run on StdinFileInfo (keradus) + +Changelog for v1.10 +------------------- + +* feature #1306 Added LogicalNotOperatorsWithSuccessorSpaceFixer (phansys) +* feature #1286 Added PhpUnitConstructFixer (keradus) +* feature #1316 Added PhpdocInlineTagFixer (SpacePossum, keradus) +* feature #1303 Added LogicalNotOperatorsWithSpacesFixer (phansys) +* feature #1279 Added PhpUnitStrictFixer (keradus) +* feature #1267 SingleQuoteFixer fix more use cases (SpacePossum) +* minor #1319 PhpUnitConstructFixer - fix performance and add to local .php_cs (keradus) +* minor #1280 Fix non-utf characters in docs (keradus) +* minor #1274 Cookbook - No change auto-test note (Soullivaneuh) + +Changelog for v1.9.3 +-------------------- + +* bug #1327 DocBlock\Tag - keep the case of tags (GrahamCampbell) + +Changelog for v1.9.2 +-------------------- + +* bug #1313 AlignDoubleArrowFixer - fix aligning after UTF8 chars (keradus) +* bug #1296 PhpdocScalarFixer - fix property annotation too (GrahamCampbell) +* bug #1299 WhitespacyLinesFixer - spaces on next valid line must not be fixed (Slamdunk) + +Changelog for v1.9.1 +-------------------- + +* bug #1288 TrimArraySpacesFixer - fix moving first comment (keradus) +* bug #1287 PhpdocParamsFixer - now works on any indentation level (keradus) +* bug #1278 Travis - fix PHP7 build (keradus) +* bug #1277 WhitespacyLinesFixer - stop changing non-whitespacy tokens (SpacePossum, SamBurns-awin, keradus) +* bug #1224 TrailingSpacesFixer - stop changing non-whitespacy tokens (SpacePossum, SamBurns-awin, keradus) +* bug #1266 FunctionCallSpaceFixer - better detection of function call (funivan) +* bug #1255 make sure some phpdoc fixers are run in right order (SpacePossum) + +Changelog for v1.9 +------------------ + +* feature #1097 Added ShortEchoTagFixer (vinkla) +* minor #1238 Fixed error handler to respect current error_reporting (JanJakes) +* minor #1234 Add class to exception message, use sprintf for exceptions (SpacePossum) +* minor #1210 set custom error handler for application run (keradus) +* minor #1214 Tokens::isMonolithicPhp - enhance performance (keradus) +* minor #1207 Update code documentation (keradus) +* minor #1202 Update IDE tool urls (keradus) +* minor #1195 PreIncrementFixer - move to Symfony level (gharlan) + +Changelog for v1.8.1 +-------------------- + +* bug #1193 EofEndingFixer - do not add an empty line at EOF if the PHP tags have been closed (SpacePossum) +* bug #1209 PhpdocParamsFixer - fix corrupting following custom annotation (keradus) +* bug #1205 BracesFixer - fix missing indentation fixes for class level (keradus) +* bug #1204 Tag - fix treating complex tag as simple PhpDoc tag (keradus) +* bug #1198 Tokens - fixed unary/binary operator check for type-hinted reference arguments (gharlan) +* bug #1201 Php4ConstructorFixer - fix invalid handling of subnamespaces (gharlan) +* minor #1221 Add more tests (SpacePossum) +* minor #1216 Tokens - Add unit test for array detection (SpacePossum) + +Changelog for v1.8 +------------------ + +* feature #1168 Added UnalignEqualsFixer (keradus) +* feature #1167 Added UnalignDoubleArrowFixer (keradus) +* bug #1169 ToolInfo - Fix way to find script dir (sp-ian-monge) +* minor #1181 composer.json - Update description (SpacePossum) +* minor #1180 create Tokens::overrideAt method (keradus) + +Changelog for v1.7.1 +-------------------- + +* bug #1165 BracesFixer - fix bug when comment is a first statement in control structure without braces (keradus) + +Changelog for v1.7 +------------------ + +* feature #1113 Added PreIncrementFixer (gharlan) +* feature #1144 Added PhpdocNoAccessFixer (GrahamCampbell) +* feature #1116 Added SelfAccessorFixer (gharlan) +* feature #1064 OperatorsSpacesFixer enhancements (gharlan) +* bug #1151 Prevent token collection corruption by fixers (stof, keradus) +* bug #1152 LintManager - fix handling of temporary file (keradus) +* bug #1139 NamespaceNoLeadingWhitespaceFixer - remove need for ctype extension (keradus) +* bug #1117 Tokens - fix iterator used with foreach by reference (keradus) +* minor #1148 code grooming (keradus) +* minor #1142 We are actually PSR-4, not PSR-0 (GrahamCampbell) +* minor #1131 Phpdocs and typos (SpacePossum) +* minor #1069 state min HHVM version (keradus) +* minor #1129 [DX] Help developers choose the right branch (SpacePossum) +* minor #1138 PhpClosingTagFixer - simplify flow, no need for loop (keradus) +* minor #1123 Reference mismatches fixed, SCA (kalessil) +* minor #1109 SingleQuoteFixer - made fixer more accurate (gharlan) +* minor #1110 code grooming (kalessil) + +Changelog for v1.6.2 +-------------------- + +* bug #1149 UnusedUseFixer - must be run before LineAfterNamespaceFixer, fix token collection corruption (keradus) +* minor #1145 AbstractLinesBeforeNamespaceFixer - fix docs for fixLinesBeforeNamespace (GrahamCampbell) + +Changelog for v1.6.1 +-------------------- + +* bug #1108 UnusedUseFixer - fix false positive when name is used as part of another namespace (gharlan) +* bug #1114 Fixed PhpdocParamsFixer with malformed doc block (gharlan) +* minor #1135 PhpdocTrimFixer - fix doc typo (localheinz) +* minor #1093 Travis - test lowest dependencies (boekkooi) + +Changelog for v1.6 +------------------ + +* feature #1089 Added NewlineAfterOpenTagFixer and BlanklineAfterOpenTagFixer (ceeram, keradus) +* feature #1090 Added TrimArraySpacesFixer (jaredh159, keradus) +* feature #1058 Added SingleQuoteFixer (gharlan) +* feature #1059 Added LongArraySyntaxFixer (gharlan) +* feature #1037 Added PhpdocScalarFixer (GrahamCampbell, keradus) +* feature #1028 Add ListCommasFixer (keradus) +* bug #1047 Utils::camelCaseToUnderscore - fix regexp (odin-delrio) +* minor #1073 ShortTagFixer enhancement (gharlan) +* minor #1079 Use LongArraySyntaxFixer for this repo (gharlan) +* minor #1070 Tokens::isMonolithicPhp - remove unused T_CLOSE_TAG search (keradus) +* minor #1049 OrderedUseFixer - grooming (keradus) + +Changelog for v1.5.2 +-------------------- + +* bug #1025 Fixer - ignore symlinks (kix) +* bug #1071 Psr0Fixer - fix bug for fixing file with long extension like .class.php (keradus) +* bug #1080 ShortTagFixer - fix false positive (gharlan) +* bug #1066 Php4ConstructorFixer - fix causing infinite recursion (mbeccati) +* bug #1056 VisibilityFixer - fix T_VAR with multiple props (localheinz, keradus) +* bug #1065 Php4ConstructorFixer - fix detection of a PHP4 parent constructor variant (mbeccati) +* bug #1060 Tokens::isShortArray: tests and bugfixes (gharlan) +* bug #1057 unused_use: fix false positive when name is only used as variable name (gharlan) + +Changelog for v1.5.1 +-------------------- + +* bug #1054 VisibilityFixer - fix var with array value assigned (localheinz, keradus) +* bug #1048 MultilineArrayTrailingCommaFixer, SingleArrayNoTrailingCommaFixer - using heredoc inside array not causing to treat it as multiline array (keradus) +* bug #1043 PhpdocToCommentFixer - also check other control structures, besides foreach (ceeram) +* bug #1045 OrderedUseFixer - fix namespace order for trailing digits (rusitschka) +* bug #1035 PhpdocToCommentFixer - Add static as valid keyword for structural element (ceeram) +* bug #1020 BracesFixer - fix missing braces for nested if elseif else (malengrin) +* minor #1036 Added php7 to travis build (fonsecas72) +* minor #1026 Fix typo in ShortArraySyntaxFixer (tommygnr) +* minor #1024 code grooming (keradus) + +Changelog for v1.5 +------------------ + +* feature #887 Added More Phpdoc Fixers (GrahamCampbell, keradus) +* feature #1002 Add HeaderCommentFixer (ajgarlag) +* feature #974 Add EregToPregFixer (mbeccati) +* feature #970 Added Php4ConstructorFixer (mbeccati) +* feature #997 Add PhpdocToCommentFixer (ceeram, keradus) +* feature #932 Add NoBlankLinesAfterClassOpeningFixer (ceeram) +* feature #879 Add SingleBlankLineBeforeNamespaceFixer and NoBlankLinesBeforeNamespaceFixer (GrahamCampbell) +* feature #860 Add single_line_after_imports fixer (ceeram) +* minor #1014 Fixed a few file headers (GrahamCampbell) +* minor #1011 Fix HHVM as it works different than PHP (keradus) +* minor #1010 Fix invalid UTF-8 char in docs (ajgarlag) +* minor #1003 Fix header comment in php files (ajgarlag) +* minor #1005 Add Utils::calculateBitmask method (keradus) +* minor #973 Add Tokens::findSequence (mbeccati) +* minor #991 Longer explanation of how to use blacklist (bmitch, networkscraper) +* minor #972 Add case sensitive option to the tokenizer (mbeccati) +* minor #986 Add benchmark script (dericofilho) +* minor #985 Fix typo in COOKBOOK-FIXERS.md (mattleff) +* minor #978 Token - fix docs (keradus) +* minor #957 Fix Fixers methods order (GrahamCampbell) +* minor #944 Enable caching of composer downloads on Travis (stof) +* minor #941 EncodingFixer - enhance tests (keradus) +* minor #938 Psr0Fixer - remove unneeded assignment (keradus) +* minor #936 FixerTest - test description consistency (keradus) +* minor #933 NoEmptyLinesAfterPhpdocsFixer - remove unneeded code, clarify description (ceeram) +* minor #934 StdinFileInfo::getFilename - Replace phpdoc with normal comment and add back empty line before return (ceeram) +* minor #927 Exclude the resources folder from coverage reports (GrahamCampbell) +* minor #926 Update Token::isGivenKind phpdoc (GrahamCampbell) +* minor #925 Improved AbstractFixerTestBase (GrahamCampbell) +* minor #922 AbstractFixerTestBase::makeTest - test if input is different than expected (keradus) +* minor #904 Refactoring Utils (GrahamCampbell) +* minor #901 Improved Readme Formatting (GrahamCampbell) +* minor #898 Tokens::getImportUseIndexes - simplify function (keradus) +* minor #897 phpunit.xml.dist - split testsuite (keradus) + +Changelog for v1.4.2 +-------------------- + +* bug #994 Fix detecting of short arrays (keradus) +* bug #995 DuplicateSemicolonFixer - ignore duplicated semicolons inside T_FOR (keradus) + +Changelog for v1.4.1 +-------------------- + +* bug #990 MultilineArrayTrailingCommaFixer - fix case with short array on return (keradus) +* bug #975 NoEmptyLinesAfterPhpdocsFixer - fix only when documentation documents sth (keradus) +* bug #976 PhpdocIndentFixer - fix error when there is a comment between docblock and next meaningful token (keradus, ceeram) + +Changelog for v1.4 +------------------ + +* feature #841 PhpdocParamsFixer: added aligning var/type annotations (GrahamCampbell) +* bug #965 Fix detection of lambda function that returns a reference (keradus) +* bug #962 PhpdocIndentFixer - fix bug when documentation is on the end of braces block (keradus) +* bug #961 Fixer - fix handling of empty file (keradus) +* bug #960 IncludeFixer - fix bug when include is part of condition statement (keradus) +* bug #954 AlignDoubleArrowFixer - fix new buggy case (keradus) +* bug #955 ParenthesisFixer - fix case with list call with trailing comma (keradus) +* bug #950 Tokens::isLambda - fix detection near comments (keradus) +* bug #951 Tokens::getImportUseIndexes - fix detection near comments (keradus) +* bug #949 Tokens::isShortArray - fix detection near comments (keradus) +* bug #948 NewWithBracesFixer - fix case with multidimensional array (keradus) +* bug #945 Skip files containing __halt_compiler() on PHP 5.3 (stof) +* bug #946 BracesFixer - fix typo in exception name (keradus) +* bug #940 Tokens::setCode - apply missing transformation (keradus) +* bug #908 BracesFixer - fix invalid inserting brace for control structure without brace and lambda inside of it (keradus) +* bug #903 NoEmptyLinesAfterPhpdocsFixer - fix bug with Windows style lines (GrahamCampbell) +* bug #895 [PSR-2] Preserve blank line after control structure opening brace (marcaube) +* bug #892 Fixed the double arrow multiline whitespace fixer (GrahamCampbell) +* bug #874 BracesFixer - fix bug of removing empty lines after class' opening { (ceeram) +* bug #868 BracesFixer - fix missing braces when statement is not followed by ; (keradus) +* bug #861 Updated PhpdocParamsFixer not to change line endings (keradus, GrahamCampbell) +* bug #837 FixCommand - stop corrupting xml/json format (keradus) +* bug #846 Made phpdoc_params run after phpdoc_indent (GrahamCampbell) +* bug #834 Correctly handle tab indentation (ceeram) +* bug #822 PhpdocIndentFixer - Ignore inline docblocks (ceeram) +* bug #813 MultilineArrayTrailingCommaFixer - do not move array end to new line (keradus) +* bug #817 LowercaseConstantsFixer - ignore class' constants TRUE/FALSE/NULL (keradus) +* bug #821 JoinFunctionFixer - stop changing declaration method name (ceeram) +* minor #963 State the minimum version of PHPUnit in CONTRIBUTING.md (SpacePossum) +* minor #943 Improve the cookbook to use relative links (stof) +* minor #921 Add changelog file (keradus) +* minor #909 BracesFixerTest - no \n line in \r\n test (keradus) +* minor #864 Added NoEmptyLinesAfterPhpdocsFixer (GrahamCampbell) +* minor #871 Added missing author (GrahamCampbell) +* minor #852 Fixed the coveralls version constraint (GrahamCampbell) +* minor #863 Tweaked testRetainsNewLineCharacters (GrahamCampbell) +* minor #849 Removed old alias (GrahamCampbell) +* minor #843 integer should be int (GrahamCampbell) +* minor #830 Remove whitespace before opening tag (ceeram) +* minor #835 code grooming (keradus) +* minor #828 PhpdocIndentFixerTest - code grooming (keradus) +* minor #827 UnusedUseFixer - code grooming (keradus) +* minor #825 improve code coverage (keradus) +* minor #810 improve code coverage (keradus) +* minor #811 ShortArraySyntaxFixer - remove not needed if statement (keradus) + +Changelog for v1.3 +------------------ + +* feature #790 Add docblock indent fixer (ceeram) +* feature #771 Add JoinFunctionFixer (keradus) +* bug #798 Add DynamicVarBrace Transformer for properly handling ${$foo} syntax (keradus) +* bug #796 LowercaseConstantsFixer - rewrite to handle new test cases (keradus) +* bug #789 T_CASE is not succeeded by parentheses (dericofilho) +* minor #814 Minor improvements to the phpdoc_params fixer (GrahamCampbell) +* minor #815 Minor fixes (GrahamCampbell) +* minor #782 Cookbook on how to make a new fixer (dericofilho) +* minor #806 Fix Tokens::detectBlockType call (keradus) +* minor #758 travis - disable sudo (keradus) +* minor #808 Tokens - remove commented code (keradus) +* minor #802 Address Sensiolabs Insight's warning of code cloning. (dericofilho) +* minor #803 README.rst - fix \` into \`\` (keradus) + +Changelog for v1.2 +------------------ + +* feature #706 Remove lead slash (dericofilho) +* feature #740 Add EmptyReturnFixer (GrahamCampbell) +* bug #775 PhpClosingTagFixer - fix case with T_OPEN_TAG_WITH_ECHO (keradus) +* bug #756 Fix broken cases for AlignDoubleArrowFixer (dericofilho) +* bug #763 MethodArgumentSpaceFixer - fix receiving data in list context with omitted values (keradus) +* bug #759 Fix Tokens::isArrayMultiLine (stof, keradus) +* bug #754 LowercaseKeywordsFixer - __HALT_COMPILER must not be lowercased (keradus) +* bug #753 Fix for double arrow misalignment in deeply nested arrays. (dericofilho) +* bug #752 OrderedUseFixer should be case-insensitive (rusitschka) +* minor #779 Fixed a docblock type (GrahamCampbell) +* minor #765 Typehinting in FileCacheManager, remove unused variable in Tokens (keradus) +* minor #764 SelfUpdateCommand - get local version only if remote version was successfully obtained (keradus) +* minor #761 align => (keradus) +* minor #757 Some minor code simplify and extra test (keradus) +* minor #713 Download php-cs-fixer.phar without sudo (michaelsauter) +* minor #742 Various Minor Improvements (GrahamCampbell) + +Changelog for v1.1 +------------------ + +* feature #749 remove the --no-progress option (replaced by the standard -v) (fabpot, keradus) +* feature #728 AlignDoubleArrowFixer - standardize whitespace after => (keradus) +* feature #647 Add DoubleArrowMultilineWhitespacesFixer (dericofilho, keradus) +* bug #746 SpacesBeforeSemicolonFixerTest - fix bug with semicolon after comment (keradus) +* bug #741 Fix caching when composer is installed in custom path (cmodijk) +* bug #725 DuplicateSemicolonFixer - fix clearing whitespace after duplicated semicolon (keradus) +* bug #730 Cache busting when fixers list changes (Seldaek) +* bug #722 Fix lint for STDIN-files (ossinkine) +* bug #715 TrailingSpacesFixer - fix bug with french UTF-8 chars (keradus) +* bug #718 Fix package name for composer cache (Seldaek) +* bug #711 correct vendor name (keradus) +* minor #745 Show progress by default and allow to disable it (keradus) +* minor #731 Add a way to disable all default filters and really provide a whitelist (Seldaek) +* minor #737 Extract tool info into new class, self-update command works now only for PHAR version (keradus) +* minor #739 fix fabbot issues (keradus) +* minor #726 update CONTRIBUTING.md for installing dependencies (keradus) +* minor #736 Fix fabbot issues (GrahamCampbell) +* minor #727 Fixed typos (pborreli) +* minor #719 Add update instructions for composer and caching docs (Seldaek) + +Changelog for v1.0 +------------------ + +First stable release. diff --git a/vendor/friendsofphp/php-cs-fixer/CONTRIBUTING.md b/vendor/friendsofphp/php-cs-fixer/CONTRIBUTING.md new file mode 100644 index 00000000..f0f5a429 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/CONTRIBUTING.md @@ -0,0 +1,103 @@ +# Contributions Are Welcome! + +If you need any help, don't hesitate to ask the community using [GitHub Discussions](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/discussions/categories/q-a). + +## Glossary + +### Fixer + +A *fixer* is a class that tries to fix a single code style issue (a ``Fixer`` class must implement ``FixerInterface``). + +### Ruleset + +A *ruleset* is a collection of rules (*fixers*) that may be referenced in the config file similarly to a single *fixer*. When you work on existing fixer please keep in mind it can be a part of a *ruleset*(s) and changes may affect many users. When working on new *fixer* please consider if it should be added to some *ruleset*(s). + +### Config + +A *config* knows about the code style rules and the files and directories that must be scanned by the tool when run in the context of your project. It is useful for projects that follow a well-known directory structures, but the tool is not limited to any specific structure, and you can configure it in a very flexible way. + +## How to contribute + +> [!IMPORTANT] +> Before contributing with _really_ significant changes that require a lot of effort or are crucial from this tool's +> architecture perspective, please open [RFC on GitHub Discussion](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/discussions/categories/rfc). +> The development effort should start only after the proposal is discussed and the approach aligned. + +### Development + +* [Fork](https://help.github.com/articles/fork-a-repo/) this repository. You can use native Git approach or use [`gh` CLI tool](https://cli.github.com/). +* Create new branch on top of the latest revision of `master` branch (if you already had project locally, then make sure to update this branch before going to next steps). It's good when branch's name reflects intent of the changes, but this is not strict requirement since pull request provides description of the change. However, with good branch naming it's easier to work on multiple changes simultaneously. +* Install dependencies by running `composer update` (since project does not contain `composer.lock` it's better to ensure latest versions of packages by running `update` command instead of `install`). +* Make changes. Please remember that **all** changes have to be covered by tests. + * if you work on a bug fix, please start with reproducing the problem by adding failing test case(s). When you have failing test case(s), you can [create pull request](#opening-a-pull-request) just to reproduce fail in the CI. Then you can provide fix _in the subsequent commits_, it will make code review easier. It's allowed to modify existing test cases in bug fix pull request, but *only if* current behavior is proved to be invalid. + * if you work on existing fixers then don't change existing test cases, because these are contract between the maintainers and users (they ensure how tool works). Add new test cases that cover provided changes - preferred way of defining test cases is with [data provider](https://docs.phpunit.de/en/10.0/writing-tests-for-phpunit.html#data-providers) which uses `yield` with proper case description as a key (e.g. `yield 'Some specific scenario' => ['some', 'example', 'data'];`). Codebase may still contain test cases in different format, and it's totally acceptable to use `yield` approach next to existing `return` usages. +* Update documentation: `composer docs`. This requires the highest version of PHP supported by PHP CS Fixer. If it is not installed on your system, you can run it in a Docker container instead: `docker compose run php-8.2 php dev-tools/doc.php`. +* Run QA suite: `composer qa`. +* Fix project itself (if needed): `composer cs:fix`. + +### Opening a [pull request](https://help.github.com/articles/about-pull-requests/) + +You can do some things to increase the chance that your pull request is accepted without communication ping-pong between you and the reviewers: + +* Submit [single](https://en.wikipedia.org/wiki/Single-responsibility_principle) pull request per fix or feature. +* Keep meaningful commit logs, don't use meaningless messages (e.g. `foo`, `more work`, `more work`, `more work`) and don't push complex PR as a single commit. +* Don't [amend](https://git-scm.com/docs/git-commit#Documentation/git-commit.txt---amend) commits because it makes review rounds harder - all commits from your branch will be squashed (without commit messages) during the merge. +* Follow the conventions used in the project. +* Remember about tests and documentation. +* Don't bump `PhpCsFixer\Console\Application::VERSION`, it's done during release. + +> [!IMPORTANT] +> Your pull request will have much higher chance of getting merged if you allow maintainers to push changes to your +> branch. You can do it by ticking "Allow edits and access to secrets by maintainers" checkbox, but please keep in mind +> this option is available only if your PR is created from a user's fork. If your fork is a part of organisation, then +> you can add [Fixer maintainers](https://github.com/orgs/PHP-CS-Fixer/people) as members of that repository. This way +> maintainers will be able to provide required changes or rebase your branch (only up-to-date PRs can be merged). + +## Working With Docker + +This project provides a Docker setup that allows working on it using any of the PHP versions supported by the tool. + +To use it, you first need to install [Docker](https://docs.docker.com/get-docker/) ([Docker Compose](https://docs.docker.com/compose/) is a built-in plugin of the main tool). + +Next, copy [`compose.override.dist.yaml`](./compose.override.dist.yaml) to `compose.override.yaml` and edit it to your needs. The relevant parameters that might require some tweaking have comments to help you. + +You can then build the images: + +```console +docker compose build --parallel +``` + +Now you can run commands needed to work on the project. For example, say you want to run PHPUnit tests on PHP 8.2: + +```console +docker compose run php-8.2 vendor/bin/phpunit +``` + +Sometimes it can be more convenient to have a shell inside the container: + +```console +docker compose run php-7.4 sh +/fixer vendor/bin/phpunit +``` + +The images come with an [`xdebug` script](github.com/julienfalque/xdebug/) that allows running any PHP command with Xdebug enabled to help debug problems. + +```console +docker compose run php-8.2 xdebug vendor/bin/phpunit +``` + +If you're using PhpStorm, you need to create a [server](https://www.jetbrains.com/help/phpstorm/servers.html) with a name that matches the `PHP_IDE_CONFIG` environment variable defined in the Docker Compose configuration files, which is `php-cs-fixer` by default. + +All images use port 9003 for debug connections. + +## Making New Fixers + +There is a [cookbook](doc/cookbook_fixers.rst) with basic instructions on how to build a new fixer. Consider reading it before opening a PR. + +## Project's Standards + +* [PSR-1: Basic Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md) +* [PSR-2: Coding Style Guide](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) +* [PSR-4: Autoloading Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md) +* [PSR-5: PHPDoc (draft)](https://github.com/phpDocumentor/fig-standards/blob/master/proposed/phpdoc.md) +* [Symfony Coding Standards](https://symfony.com/doc/current/contributing/code/standards.html) diff --git a/vendor/friendsofphp/php-cs-fixer/LICENSE b/vendor/friendsofphp/php-cs-fixer/LICENSE new file mode 100644 index 00000000..871def02 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2012+ Fabien Potencier, Dariusz Rumiński + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/friendsofphp/php-cs-fixer/README.md b/vendor/friendsofphp/php-cs-fixer/README.md new file mode 100644 index 00000000..01d8d1f4 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/README.md @@ -0,0 +1,109 @@ +

+ + PHP CS Fixer logo + +

+ +# PHP Coding Standards Fixer + +The PHP Coding Standards Fixer (PHP CS Fixer) tool fixes your code to follow standards; +whether you want to follow PHP coding standards as defined in the PSR-1, PSR-2, etc., +or other community driven ones like the Symfony one. +You can **also** define your (team's) style through configuration. + +It can modernize your code (like converting the ``pow`` function to the ``**`` operator on PHP 5.6) +and (micro) optimize it. + +If you are already using a linter to identify coding standards problems in your +code, you know that fixing them by hand is tedious, especially on large +projects. This tool does not only detect them, but also fixes them for you. + +## Supported PHP Versions + +* PHP 7.4 +* PHP 8.0 +* PHP 8.1 +* PHP 8.2 +* PHP 8.3 + +> **Note** +> Each new PHP version requires a huge effort to support the new syntax. +> That's why the latest PHP version might not be supported yet. If you need it, +> please, consider supporting the project in any convenient way, for example +> with code contribution or reviewing existing PRs. To run PHP CS Fixer on yet +> unsupported versions "at your own risk" - leverage the +> [PHP_CS_FIXER_IGNORE_ENV](./doc/usage.rst#environment-options). + +## Documentation + +### Installation + +The recommended way to install PHP CS Fixer is to use [Composer](https://getcomposer.org/download/) +in a dedicated `composer.json` file in your project, for example in the +`tools/php-cs-fixer` directory: + +```console +mkdir -p tools/php-cs-fixer +composer require --working-dir=tools/php-cs-fixer friendsofphp/php-cs-fixer +``` + +Or using the main `composer.json`: + +```console +composer require --dev friendsofphp/php-cs-fixer +``` + +For more details and other installation methods, see +[installation instructions](./doc/installation.rst). + +### Run with Docker + +You can use pre-built Docker images to run ``php-cs-fixer``. + +```console +docker run -v $(pwd):/code ghcr.io/php-cs-fixer/php-cs-fixer:${FIXER_VERSION:-3-php8.3} fix src +``` + +`$FIXER_VERSION` used in example above is an identifier of a release you want to use, which is based on Fixer and PHP versions combined. There are different tags for each Fixer's SemVer level and PHP version with syntax `-php`. For example: + +* `3.47.0-php7.4` +* `3.47-php8.0` +* `3-php8.3` + +### Usage + +Assuming you installed PHP CS Fixer as instructed above, you can run the +following command to fix the files PHP files in the `src` directory: + +```console +tools/php-cs-fixer/vendor/bin/php-cs-fixer fix src +``` + +See [usage](./doc/usage.rst), list of [built-in rules](./doc/rules/index.rst), list of [rule sets](./doc/ruleSets/index.rst) +and [configuration file](./doc/config.rst) documentation for more details. + +If you need to apply code styles that are not supported by the tool, you can +[create custom rules](./doc/custom_rules.rst). + +## Editor Integration + +Dedicated plugins exist for: + +* [NetBeans](https://plugins.netbeans.apache.org/catalogue/?id=36) +* [PhpStorm](https://www.jetbrains.com/help/phpstorm/using-php-cs-fixer.html) +* [Sublime Text](https://github.com/benmatselby/sublime-phpcs) +* [Vim](https://github.com/stephpy/vim-php-cs-fixer) +* [VS Code](https://github.com/junstyle/vscode-php-cs-fixer) + +## Community + +The PHP CS Fixer is maintained on GitHub at . +Bug reports and ideas about new features are welcome there. + +You can reach us in the [GitHub Discussions](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/discussions/) regarding the +project, configuration, possible improvements, ideas and questions. Please visit us there! + +## Contribute + +The tool comes with quite a few built-in fixers, but everyone is more than +welcome to [contribute](CONTRIBUTING.md) more of them. diff --git a/vendor/friendsofphp/php-cs-fixer/UPGRADE-v3.md b/vendor/friendsofphp/php-cs-fixer/UPGRADE-v3.md new file mode 100644 index 00000000..bea1ff4f --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/UPGRADE-v3.md @@ -0,0 +1,162 @@ +# UPGRADE GUIDE FROM 2.x to 3.0 + +This is guide for upgrade from version 2.x to 3.0 for using the CLI tool. + +*Before following this guide, install [v2.19](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/releases/tag/v2.19.0) and run in verbose mode (`php-cs-fixer fix -v`) or in future mode (`PHP_CS_FIXER_FUTURE_MODE=1 php-cs-fixer fix`) to identify deprecations and fix them first.* + +## Rename of files + +| 2.x | 3.0 | Description | +|------------------|--------------------------|----------------------------------------| +| `.php_cs` | `.php-cs-fixer.php` | Configuration file (local) | +| `.php_cs.dist` | `.php-cs-fixer.dist.php` | Configuration file (to be distributed) | +| `.php_cs.cache` | `.php-cs-fixer.cache` | Cache file | + +## CLI options + +| 2.x | 3.0 | Description | Note | +| ---------------- | --------------- | ----------------------------------------------- | -------------------------------------- | +| --diff-format | | Type of differ | Option was removed, all diffs are now | +| | | | `udiff` | +| --show-progress | --show-progress | Type of progress indicator | Allowed values were modified: | +| | | | `run-in` and `estimating` was removed, | +| | | | `estimating-max` was renamed to `dots` | +| --rules | --rules | Default value changed from @PSR2 to @PSR12 | | +| --config --rules | | | No longer allowed to pass both | + +## Changes to rules + +### Renamed rules + +| Old name | New name | Note | +|--------------------------------------------|-----------------------------------------------------------------------------------|------------------------------------------------------| +|`blank_line_before_return` | `blank_line_before_statement` | use configuration `['statements' => ['return']]` | +|`final_static_access` | `self_static_accessor` | | +|`hash_to_slash_comment` | `single_line_comment_style` | use configuration `['comment_types' => ['hash']]` | +|`lowercase_constants` | `constant_case` | use configuration `['case' => 'lower']` | +|`method_separation` | `class_attributes_separation` | use configuration `['elements' => ['method']]` | +|`no_extra_consecutive_blank_lines` | `no_extra_blank_lines` | | +|`no_multiline_whitespace_before_semicolons` | `multiline_whitespace_before_semicolons` | | +|`no_short_echo_tag` | `echo_tag_syntax` | use configuration `['format' => 'long']` | +|`php_unit_ordered_covers` | `phpdoc_order_by_value` | use configuration `['annotations' => [ 'covers' ]]` | +|`phpdoc_inline_tag` | `general_phpdoc_tag_rename`, `phpdoc_inline_tag_normalizer` and `phpdoc_tag_type` | | +|`pre_increment` | `increment_style` | use configuration `['style' => 'pre']` | +|`psr0` | `psr_autoloading` | use configuration `['dir' => x ]` | +|`psr4` | `psr_autoloading` | | +|`silenced_deprecation_error` | `error_suppression` | | +|`trailing_comma_in_multiline_array` | `trailing_comma_in_multiline` | use configuration `['elements' => ['arrays']]` | + +### Removed rootless configuration + +| Rule | Root option | Note | +|--------------------------------------| -------------- |-----------------------------------------------------------| +| `general_phpdoc_annotation_remove` | `annotations` | | +| `no_extra_consecutive_blank_lines` | `tokens` | | +| `no_spaces_around_offset` | `positions` | | +| `no_unneeded_control_parentheses` | `statements` | | +| `ordered_class_elements` | `order` | | +| `php_unit_construct` | `assertions` | | +| `php_unit_dedicate_assert` | `target` | root option works differently than rootless configuration | +| `php_unit_strict` | `assertions` | | +| `phpdoc_no_alias_tag` | `replacements` | | +| `phpdoc_return_self_reference` | `replacements` | | +| `random_api_migration` | `replacements` | | +| `single_class_element_per_statement` | `elements` | | +| `visibility_required` | `elements` | | + +### Changed options + +| Rule | Option | Change | +|------------------------------------|----------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `binary_operator_spaces` | `align_double_arrow` | option was removed, use `operators` instead | +| `binary_operator_spaces` | `align_equals` | option was removed use `operators` instead | +| `blank_line_before_statement` | `statements: die` | option `die` was removed from `statements`, use `exit` instead | +| `class_attributes_separation` | `elements` | option does no longer accept flat array as a value, use map instead | +| `class_definition` | `multiLineExtendsEachSingleLine` | option was renamed to `multi_line_extends_each_single_line` | +| `class_definition` | `singleItemSingleLine` | option was renamed to `single_item_single_line` | +| `class_definition` | `singleLine` | option was renamed to `single_line` | +| `doctrine_annotation_spaces` | `around_argument_assignments` | option was removed, use `before_argument_assignments` and `after_argument_assignments` instead | +| `doctrine_annotation_spaces` | `around_array_assignments` | option was removed, use `after_array_assignments_colon`, `after_array_assignments_equals`, `before_array_assignments_colon` and `before_array_assignments_equals` instead | +| `final_internal_class` | `annotation-black-list` | option was renamed, use `annotation_exclude` | +| `final_internal_class` | `annotation-white-list` | option was renamed, use `annotation_include` | +| `final_internal_class` | `consider-absent-docblock-as-internal-class` | option was renamed, use `consider_absent_docblock_as_internal_class` | +| `header_comment` | `commentType` | option was renamed to `comment_type` | +| `is_null` | `use_yoda_style` | option was removed, use `yoda_style` rule instead | +| `no_extra_consecutive_blank_lines` | `tokens` | one of possible values, `useTrait`, was renamed to `use_trait` | +| `ordered_class_elements` | `sortAlgorithm` | option was renamed, use `sort_algorithm` instead | +| `ordered_imports` | `importsOrder` | option was renamed, use `imports_order` | +| `ordered_imports` | `sortAlgorithm` | option was renamed, use `sort_algorithm` | +| `php_unit_dedicate_assert` | `functions` | option was removed, use `target` instead | +| `php_unit_test_annotation` | `case` | option was removed, use `php_unit_method_casing` rule instead | + +### Changed default values of options + +| Rule | Option | Old value | New value | +|------------------------------|-----------------------------------|------------------------------------------------------|--------------------------------------------------------------------------| +| `array_syntax` | `syntax` | `'long'` | `'short'` | +| `function_to_constant` | `functions` | `['get_class', 'php_sapi_name', 'phpversion', 'pi']` | `['get_called_class', 'get_class', 'php_sapi_name', 'phpversion', 'pi']` | +| `list_syntax` | `syntax` | `'long'` | `'short'` | +| `method_argument_space` | `on_multiline` | `'ignore'` | `'ensure_fully_multiline'` | +| `native_constant_invocation` | `strict` | `false` | `true` | +| `native_function_casing` | `include` | `'@internal'` | `'@compiler_optimized'` | +| `native_function_invocation` | `include` | `'@internal'` | `'@compiler_optimized'` | +| `native_function_invocation` | `strict` | `false` | `true` | +| `non_printable_character` | `use_escape_sequences_in_strings` | `false` | `true` (when running on PHP 7.0 and up) | +| `php_unit_dedicate_assert` | `target` | `'5.0'` | `'newest'` | +| `phpdoc_align` | `tags` | `['param', 'return', 'throws', 'type', 'var']` | `['method', 'param', 'property', 'return', 'throws', 'type', 'var']` | +| `phpdoc_scalar` | `types` | `['boolean', 'double', 'integer', 'real', 'str']` | `['boolean', 'callback', 'double', 'integer', 'real', 'str']` | + +### Removed rule sets + +| Rule set | Note | +|-------------------|------------| +| `@PHP56Migration` | was empty | + +### Rule behavior changes + +- `no_unused_imports` now runs all files defined in the configuration (used to exclude some hardcoded directories) + +### Various + +- `udiff` output now includes the file name in the output (if applicable) + +## Code BC changes + +### Removed; various + +- class `AbstractAlignFixerHelper` has been removed +- class `AccessibleObject` has been removed +- class `AlignDoubleArrowFixerHelper` has been removed +- class `AlignEqualsFixerHelper` has been removed +- class `FixerConfigurationResolverRootless` has been removed +- `HeaderCommentFixer` deprecated properties have been removed +- `MethodArgumentSpaceFixer` deprecated methods have been removed +- `NoMixedEchoPrintFixer` the property `$defaultConfig` has been removed +- class `Tokens`, the following methods has been removed: + - `current()` + - `key()` + - `next()` + - `rewind()` + - `valid()` +- namespace `PhpCsFixer\Test\` and each class in it has been removed, as it served pure development purpose and should not be part of production code - reach out to community if you are willing to help building dev package + +### Interface changes + +- `ConfigurableFixerInterface` has been updated +- `ConfigurationDefinitionFixerInterface` has been removed in favor of the updated `ConfigurableFixerInterface` +- `DefinedFixerInterface` has been removed, related methods are now part of the updated `FixerInterface` interface +- `DifferInterface` has been updated +- `FixerInterface` interface has been updated +- `PhpCsFixer\RuleSetInterface` has been removed in favor of `\PhpCsFixer\RuleSet\RuleSetInterface` + +### BC breaks; various + +- class `Token` is now `final` +- class `Tokens` is now `final` +- method `create` of class `Config` has been removed, [use the constructor](./doc/config.rst) +- method `create` of class `RuleSet` has been removed, [use the constructor](./doc/custom_rules.rst) + +### BC breaks; common internal classes + +- method `getClassyElements` of class `TokensAnalyzer` parameter `$returnTraitsImports` has been removed; now always returns trait import information +- method `getSetDefinitionNames` of class `RuleSet` has been removed, use `RuleSets::getSetDefinitionNames()` diff --git a/vendor/friendsofphp/php-cs-fixer/ci-integration.sh b/vendor/friendsofphp/php-cs-fixer/ci-integration.sh new file mode 100644 index 00000000..39d99955 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/ci-integration.sh @@ -0,0 +1,8 @@ +#!/bin/sh +set -eu + +IFS=' +' +CHANGED_FILES=$(git diff --name-only --diff-filter=ACMRTUXB "${COMMIT_RANGE}") +if ! echo "${CHANGED_FILES}" | grep -qE "^(\\.php-cs-fixer(\\.dist)?\\.php|composer\\.lock)$"; then EXTRA_ARGS=$(printf -- '--path-mode=intersection\n--\n%s' "${CHANGED_FILES}"); else EXTRA_ARGS=''; fi +vendor/bin/php-cs-fixer check --config=.php-cs-fixer.dist.php -v --show-progress=dots --stop-on-violation --using-cache=no ${EXTRA_ARGS} diff --git a/vendor/friendsofphp/php-cs-fixer/composer.json b/vendor/friendsofphp/php-cs-fixer/composer.json new file mode 100644 index 00000000..b498074b --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/composer.json @@ -0,0 +1,176 @@ +{ + "name": "friendsofphp/php-cs-fixer", + "description": "A tool to automatically fix PHP code style", + "license": "MIT", + "type": "application", + "keywords": [ + "fixer", + "standards", + "static analysis", + "static code analysis" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "require": { + "php": "^7.4 || ^8.0", + "ext-filter": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "composer/semver": "^3.4", + "composer/xdebug-handler": "^3.0.3", + "sebastian/diff": "^4.0 || ^5.0 || ^6.0", + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/event-dispatcher": "^5.4 || ^6.0 || ^7.0", + "symfony/filesystem": "^5.4 || ^6.0 || ^7.0", + "symfony/finder": "^5.4 || ^6.0 || ^7.0", + "symfony/options-resolver": "^5.4 || ^6.0 || ^7.0", + "symfony/polyfill-mbstring": "^1.28", + "symfony/polyfill-php80": "^1.28", + "symfony/polyfill-php81": "^1.28", + "symfony/process": "^5.4 || ^6.0 || ^7.0", + "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0" + }, + "require-dev": { + "facile-it/paraunit": "^1.3 || ^2.0", + "justinrainbow/json-schema": "^5.2", + "keradus/cli-executor": "^2.1", + "mikey179/vfsstream": "^1.6.11", + "php-coveralls/php-coveralls": "^2.7", + "php-cs-fixer/accessible-object": "^1.1", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.4", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.4", + "phpunit/phpunit": "^9.6 || ^10.5.5 || ^11.0.2", + "symfony/var-dumper": "^5.4 || ^6.0 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "autoload": { + "psr-4": { + "PhpCsFixer\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "PhpCsFixer\\Tests\\": "tests/" + } + }, + "bin": [ + "php-cs-fixer" + ], + "config": { + "allow-plugins": { + "ergebnis/composer-normalize": true, + "infection/extension-installer": false + }, + "prefer-stable": true, + "sort-packages": true + }, + "scripts": { + "post-autoload-dump": [ + "@install-tools" + ], + "auto-review": [ + "Composer\\Config::disableProcessTimeout", + "paraunit run --testsuite auto-review" + ], + "cs:check": "@php php-cs-fixer check --verbose --diff", + "cs:fix": "@php php-cs-fixer fix", + "cs:fix:parallel": "echo '🔍 Will run in batches of 50 files.'; if [[ -f .php-cs-fixer.php ]]; then FIXER_CONFIG=.php-cs-fixer.php; else FIXER_CONFIG=.php-cs-fixer.dist.php; fi; php php-cs-fixer list-files --config=$FIXER_CONFIG | xargs -n 50 -P 8 php php-cs-fixer fix --config=$FIXER_CONFIG --path-mode intersection 2> /dev/null", + "docs": "@php dev-tools/doc.php", + "install-tools": "@composer --working-dir=dev-tools install", + "mess-detector": "@php dev-tools/vendor/bin/phpmd . ansi dev-tools/mess-detector/phpmd.xml --exclude vendor/*,dev-tools/vendor/*,dev-tools/phpstan/*,tests/Fixtures/*", + "normalize": [ + "@composer normalize --working-dir=dev-tools --dry-run ../composer.json", + "@composer normalize --working-dir=dev-tools --dry-run composer.json" + ], + "phpstan": "@php -d memory_limit=512M dev-tools/vendor/bin/phpstan analyse", + "phpstan:baseline": "@php -d memory_limit=512M dev-tools/vendor/bin/phpstan analyse --generate-baseline=./dev-tools/phpstan/baseline.php", + "qa": "@quality-assurance", + "quality-assurance": [ + "Composer\\Config::disableProcessTimeout", + "@install-tools --quiet", + "@self-check", + "@static-analysis", + "@test" + ], + "require-checker": "@php dev-tools/vendor/bin/composer-require-checker check composer.json --config-file .composer-require-checker.json", + "sa": "@static-analysis", + "self-check": [ + "./dev-tools/check_file_permissions.sh", + "./dev-tools/check_trailing_spaces.sh", + "@normalize", + "@unused-deps", + "@require-checker", + "@auto-review" + ], + "static-analysis": [ + "@cs:check", + "@phpstan", + "@mess-detector" + ], + "test": "@test:all", + "test:all": [ + "@test:unit", + "@test:integration" + ], + "test:coverage": [ + "Composer\\Config::disableProcessTimeout", + "@composer show facile-it/paraunit ^2 && (paraunit coverage --testsuite unit --pass-through=--exclude-group=covers-nothing) || (paraunit coverage --testsuite unit --exclude-group covers-nothing)" + ], + "test:integration": [ + "Composer\\Config::disableProcessTimeout", + "paraunit run --testsuite integration" + ], + "test:short-open-tag": [ + "Composer\\Config::disableProcessTimeout", + "@php -d short_open_tag=1 ./vendor/bin/phpunit --do-not-cache-result --testsuite short-open-tag" + ], + "test:smoke": [ + "Composer\\Config::disableProcessTimeout", + "paraunit run --testsuite smoke" + ], + "test:unit": [ + "Composer\\Config::disableProcessTimeout", + "paraunit run --testsuite unit" + ], + "unused-deps": "@php dev-tools/vendor/bin/composer-unused --excludePackage=composer/xdebug-handler" + }, + "scripts-descriptions": { + "auto-review": "Execute Auto-review", + "cs:check": "Check coding standards", + "cs:fix": "Fix coding standards", + "cs:fix:parallel": "Fix coding standards in naive parallel mode (using xargs)", + "docs": "Regenerate docs", + "install-tools": "Install DEV tools", + "mess-detector": "Analyse code with Mess Detector", + "normalize": "Run normalization for composer.json files", + "phpstan": "Run PHPStan analysis", + "phpstan:baseline": "Dump PHPStan baseline file - use only for updating, do not add new errors when possible", + "post-autoload-dump": "Run additional tasks after installing/updating main dependencies", + "qa": "Alias for 'quality-assurance'", + "quality-assurance": "Run QA suite", + "require-checker": "Verifies if codebase does not contain soft dependencies", + "sa": "Alias for 'static-analysis'", + "self-check": "Run set of self-checks ensuring repository's validity", + "static-analysis": "Run static analysis", + "test": "Alias for 'test:all'", + "test:all": "Run Unit and Integration tests (but *NOT* Smoke tests)", + "test:coverage": "Run tests that provide code coverage", + "test:integration": "Run Integration tests", + "test:short-open-tag": "Run tests with \"short_open_tag\" enabled", + "test:smoke": "Run Smoke tests", + "test:unit": "Run Unit tests", + "unused-deps": "Verifies if app has dependencies that are not used" + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/feature-or-bug.rst b/vendor/friendsofphp/php-cs-fixer/feature-or-bug.rst new file mode 100644 index 00000000..e5959422 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/feature-or-bug.rst @@ -0,0 +1,24 @@ +========================== +Is it a feature or a bug ? +========================== + +Sometimes it's a bit tricky to define if given change proposal or change request is adding new feature or fixing existing issue. This document is providing more clarity about categorisation we use. + +Bug +--- + +Example of bugs: + +- crash during application or rule execution +- wrong changes are applied during "fixing codebase" process +- issue with generated report + +Feature +------- + +Example of features: + +- introduction of new rule +- enhancement of existing rule to cover more cases (for example adding support for newly introduced PHP syntax) +- introduction of new ruleset +- update of existing ruleset (for example adjusting it to match newest style of given community or adding newly implemented rule that was supposed to be followed by style of given community, yet not implemented as a rule before) diff --git a/vendor/friendsofphp/php-cs-fixer/logo.md b/vendor/friendsofphp/php-cs-fixer/logo.md new file mode 100644 index 00000000..68e03a58 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/logo.md @@ -0,0 +1,3 @@ +The logo is © 2010+ Sensio Labs. + +Original resolution can be found at . diff --git a/vendor/friendsofphp/php-cs-fixer/logo.png b/vendor/friendsofphp/php-cs-fixer/logo.png new file mode 100644 index 00000000..0ee90a82 Binary files /dev/null and b/vendor/friendsofphp/php-cs-fixer/logo.png differ diff --git a/vendor/friendsofphp/php-cs-fixer/php-cs-fixer b/vendor/friendsofphp/php-cs-fixer/php-cs-fixer new file mode 100755 index 00000000..402b49ae --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/php-cs-fixer @@ -0,0 +1,104 @@ +#!/usr/bin/env php + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +error_reporting(E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED); + +set_error_handler(static function ($severity, $message, $file, $line) { + if ($severity & error_reporting()) { + throw new ErrorException($message, 0, $severity, $file, $line); + } +}); + +// check environment requirements +(function () { + if (\PHP_VERSION_ID === 80000) { + fwrite(STDERR, "PHP CS Fixer is not able run on PHP 8.0.0 due to bug in PHP tokenizer (https://bugs.php.net/bug.php?id=80462).\n"); + fwrite(STDERR, "Update PHP version to unblock execution.\n"); + + exit(1); + } + + if (\PHP_VERSION_ID < 70400 || \PHP_VERSION_ID >= 80400) { + fwrite(STDERR, "PHP needs to be a minimum version of PHP 7.4.0 and maximum version of PHP 8.3.*.\n"); + fwrite(STDERR, 'Current PHP version: '.PHP_VERSION.".\n"); + + if (getenv('PHP_CS_FIXER_IGNORE_ENV')) { + fwrite(STDERR, "Ignoring environment requirements because `PHP_CS_FIXER_IGNORE_ENV` is set. Execution may be unstable.\n"); + } else { + fwrite(STDERR, "To ignore this requirement please set `PHP_CS_FIXER_IGNORE_ENV`.\n"); + fwrite(STDERR, "If you use PHP version higher than supported, you may experience code modified in a wrong way.\n"); + fwrite(STDERR, "Please report such cases at https://github.com/PHP-CS-Fixer/PHP-CS-Fixer .\n"); + + exit(1); + } + } + + foreach (['json', 'tokenizer'] as $extension) { + if (!extension_loaded($extension)) { + fwrite(STDERR, sprintf("PHP extension ext-%s is missing from your system. Install or enable it.\n", $extension)); + + if (getenv('PHP_CS_FIXER_IGNORE_ENV')) { + fwrite(STDERR, "Ignoring environment requirements because `PHP_CS_FIXER_IGNORE_ENV` is set. Execution may be unstable.\n"); + } else { + exit(1); + } + } + } +})(); + +// load dependencies +(function () { + $require = true; + if (class_exists('Phar')) { + // Maybe this file is used as phar-stub? Let's try! + try { + Phar::mapPhar('php-cs-fixer.phar'); + + require_once 'phar://php-cs-fixer.phar/vendor/autoload.php'; + $require = false; + } catch (PharException $e) { + } + } + + if ($require) { + // OK, it's not, let give Composer autoloader a try! + $possibleFiles = [__DIR__.'/../../autoload.php', __DIR__.'/../autoload.php', __DIR__.'/vendor/autoload.php']; + $file = null; + foreach ($possibleFiles as $possibleFile) { + if (file_exists($possibleFile)) { + $file = $possibleFile; + + break; + } + } + + if (null === $file) { + throw new RuntimeException('Unable to locate autoload.php file.'); + } + + require_once $file; + } +})(); + +use Composer\XdebugHandler\XdebugHandler; +use PhpCsFixer\Console\Application; + +// Restart if xdebug is loaded, unless the environment variable PHP_CS_FIXER_ALLOW_XDEBUG is set. +$xdebug = new XdebugHandler('PHP_CS_FIXER'); +$xdebug->check(); +unset($xdebug); + +$application = new Application(); +$application->run(); + +__HALT_COMPILER(); diff --git a/vendor/friendsofphp/php-cs-fixer/src/AbstractDoctrineAnnotationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/AbstractDoctrineAnnotationFixer.php new file mode 100644 index 00000000..4ce212c1 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/AbstractDoctrineAnnotationFixer.php @@ -0,0 +1,235 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer; + +use PhpCsFixer\Doctrine\Annotation\Tokens as DoctrineAnnotationTokens; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @internal + */ +abstract class AbstractDoctrineAnnotationFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** + * @var array + */ + private array $classyElements; + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + // fetch indices one time, this is safe as we never add or remove a token during fixing + $analyzer = new TokensAnalyzer($tokens); + $this->classyElements = $analyzer->getClassyElements(); + + /** @var Token $docCommentToken */ + foreach ($tokens->findGivenKind(T_DOC_COMMENT) as $index => $docCommentToken) { + if (!$this->nextElementAcceptsDoctrineAnnotations($tokens, $index)) { + continue; + } + + $doctrineAnnotationTokens = DoctrineAnnotationTokens::createFromDocComment( + $docCommentToken, + $this->configuration['ignored_tags'] + ); + + $this->fixAnnotations($doctrineAnnotationTokens); + $tokens[$index] = new Token([T_DOC_COMMENT, $doctrineAnnotationTokens->getCode()]); + } + } + + /** + * Fixes Doctrine annotations from the given PHPDoc style comment. + */ + abstract protected function fixAnnotations(DoctrineAnnotationTokens $doctrineAnnotationTokens): void; + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('ignored_tags', 'List of tags that must not be treated as Doctrine Annotations.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([static function (array $values): bool { + foreach ($values as $value) { + if (!\is_string($value)) { + return false; + } + } + + return true; + }]) + ->setDefault([ + // PHPDocumentor 1 + 'abstract', + 'access', + 'code', + 'deprec', + 'encode', + 'exception', + 'final', + 'ingroup', + 'inheritdoc', + 'inheritDoc', + 'magic', + 'name', + 'toc', + 'tutorial', + 'private', + 'static', + 'staticvar', + 'staticVar', + 'throw', + + // PHPDocumentor 2 + 'api', + 'author', + 'category', + 'copyright', + 'deprecated', + 'example', + 'filesource', + 'global', + 'ignore', + 'internal', + 'license', + 'link', + 'method', + 'package', + 'param', + 'property', + 'property-read', + 'property-write', + 'return', + 'see', + 'since', + 'source', + 'subpackage', + 'throws', + 'todo', + 'TODO', + 'usedBy', + 'uses', + 'var', + 'version', + + // PHPUnit + 'after', + 'afterClass', + 'backupGlobals', + 'backupStaticAttributes', + 'before', + 'beforeClass', + 'codeCoverageIgnore', + 'codeCoverageIgnoreStart', + 'codeCoverageIgnoreEnd', + 'covers', + 'coversDefaultClass', + 'coversNothing', + 'dataProvider', + 'depends', + 'expectedException', + 'expectedExceptionCode', + 'expectedExceptionMessage', + 'expectedExceptionMessageRegExp', + 'group', + 'large', + 'medium', + 'preserveGlobalState', + 'requires', + 'runTestsInSeparateProcesses', + 'runInSeparateProcess', + 'small', + 'test', + 'testdox', + 'ticket', + 'uses', + + // PHPCheckStyle + 'SuppressWarnings', + + // PHPStorm + 'noinspection', + + // PEAR + 'package_version', + + // PlantUML + 'enduml', + 'startuml', + + // Psalm + 'psalm', + + // PHPStan + 'phpstan', + 'template', + + // other + 'fix', + 'FIXME', + 'fixme', + 'override', + ]) + ->getOption(), + ]); + } + + private function nextElementAcceptsDoctrineAnnotations(Tokens $tokens, int $index): bool + { + $classModifiers = [T_ABSTRACT, T_FINAL]; + + if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.2+ is required + $classModifiers[] = T_READONLY; + } + + do { + $index = $tokens->getNextMeaningfulToken($index); + + if (null === $index) { + return false; + } + } while ($tokens[$index]->isGivenKind($classModifiers)); + + if ($tokens[$index]->isGivenKind(T_CLASS)) { + return true; + } + + $modifierKinds = [T_PUBLIC, T_PROTECTED, T_PRIVATE, T_FINAL, T_ABSTRACT, T_NS_SEPARATOR, T_STRING, CT::T_NULLABLE_TYPE]; + + if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required + $modifierKinds[] = T_READONLY; + } + + while ($tokens[$index]->isGivenKind($modifierKinds)) { + $index = $tokens->getNextMeaningfulToken($index); + } + + if (!isset($this->classyElements[$index])) { + return false; + } + + return $tokens[$this->classyElements[$index]['classIndex']]->isGivenKind(T_CLASS); // interface, enums and traits cannot have doctrine annotations + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/AbstractFixer.php b/vendor/friendsofphp/php-cs-fixer/src/AbstractFixer.php new file mode 100644 index 00000000..7b9ac1a0 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/AbstractFixer.php @@ -0,0 +1,194 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer; + +use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException; +use PhpCsFixer\ConfigurationException\InvalidForEnvFixerConfigurationException; +use PhpCsFixer\ConfigurationException\RequiredFixerConfigurationException; +use PhpCsFixer\Console\Application; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\FixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\DeprecatedFixerOption; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\InvalidOptionsForEnvException; +use PhpCsFixer\Tokenizer\Tokens; +use Symfony\Component\OptionsResolver\Exception\ExceptionInterface; +use Symfony\Component\OptionsResolver\Exception\MissingOptionsException; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +abstract class AbstractFixer implements FixerInterface +{ + /** + * @var null|array + */ + protected $configuration; + + /** + * @var WhitespacesFixerConfig + */ + protected $whitespacesConfig; + + /** + * @var null|FixerConfigurationResolverInterface + */ + private $configurationDefinition; + + public function __construct() + { + if ($this instanceof ConfigurableFixerInterface) { + try { + $this->configure([]); + } catch (RequiredFixerConfigurationException $e) { + // ignore + } + } + + if ($this instanceof WhitespacesAwareFixerInterface) { + $this->whitespacesConfig = $this->getDefaultWhitespacesFixerConfig(); + } + } + + final public function fix(\SplFileInfo $file, Tokens $tokens): void + { + if ($this instanceof ConfigurableFixerInterface && null === $this->configuration) { + throw new RequiredFixerConfigurationException($this->getName(), 'Configuration is required.'); + } + + if (0 < $tokens->count() && $this->isCandidate($tokens) && $this->supports($file)) { + $this->applyFix($file, $tokens); + } + } + + public function isRisky(): bool + { + return false; + } + + public function getName(): string + { + $nameParts = explode('\\', static::class); + $name = substr(end($nameParts), 0, -\strlen('Fixer')); + + return Utils::camelCaseToUnderscore($name); + } + + public function getPriority(): int + { + return 0; + } + + public function supports(\SplFileInfo $file): bool + { + return true; + } + + /** + * @param array $configuration + */ + public function configure(array $configuration): void + { + if (!$this instanceof ConfigurableFixerInterface) { + throw new \LogicException('Cannot configure using Abstract parent, child not implementing "PhpCsFixer\Fixer\ConfigurableFixerInterface".'); + } + + foreach ($this->getConfigurationDefinition()->getOptions() as $option) { + if (!$option instanceof DeprecatedFixerOption) { + continue; + } + + $name = $option->getName(); + if (\array_key_exists($name, $configuration)) { + Utils::triggerDeprecation(new \InvalidArgumentException(sprintf( + 'Option "%s" for rule "%s" is deprecated and will be removed in version %d.0. %s', + $name, + $this->getName(), + Application::getMajorVersion() + 1, + str_replace('`', '"', $option->getDeprecationMessage()) + ))); + } + } + + try { + $this->configuration = $this->getConfigurationDefinition()->resolve($configuration); + } catch (MissingOptionsException $exception) { + throw new RequiredFixerConfigurationException( + $this->getName(), + sprintf('Missing required configuration: %s', $exception->getMessage()), + $exception + ); + } catch (InvalidOptionsForEnvException $exception) { + throw new InvalidForEnvFixerConfigurationException( + $this->getName(), + sprintf('Invalid configuration for env: %s', $exception->getMessage()), + $exception + ); + } catch (ExceptionInterface $exception) { + throw new InvalidFixerConfigurationException( + $this->getName(), + sprintf('Invalid configuration: %s', $exception->getMessage()), + $exception + ); + } + } + + public function getConfigurationDefinition(): FixerConfigurationResolverInterface + { + if (!$this instanceof ConfigurableFixerInterface) { + throw new \LogicException(sprintf('Cannot get configuration definition using Abstract parent, child "%s" not implementing "PhpCsFixer\Fixer\ConfigurableFixerInterface".', static::class)); + } + + if (null === $this->configurationDefinition) { + $this->configurationDefinition = $this->createConfigurationDefinition(); + } + + return $this->configurationDefinition; + } + + public function setWhitespacesConfig(WhitespacesFixerConfig $config): void + { + if (!$this instanceof WhitespacesAwareFixerInterface) { + throw new \LogicException('Cannot run method for class not implementing "PhpCsFixer\Fixer\WhitespacesAwareFixerInterface".'); + } + + $this->whitespacesConfig = $config; + } + + abstract protected function applyFix(\SplFileInfo $file, Tokens $tokens): void; + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + if (!$this instanceof ConfigurableFixerInterface) { + throw new \LogicException('Cannot create configuration definition using Abstract parent, child not implementing "PhpCsFixer\Fixer\ConfigurableFixerInterface".'); + } + + throw new \LogicException('Not implemented.'); + } + + private function getDefaultWhitespacesFixerConfig(): WhitespacesFixerConfig + { + static $defaultWhitespacesFixerConfig = null; + + if (null === $defaultWhitespacesFixerConfig) { + $defaultWhitespacesFixerConfig = new WhitespacesFixerConfig(' ', "\n"); + } + + return $defaultWhitespacesFixerConfig; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/AbstractFopenFlagFixer.php b/vendor/friendsofphp/php-cs-fixer/src/AbstractFopenFlagFixer.php new file mode 100644 index 00000000..da862da7 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/AbstractFopenFlagFixer.php @@ -0,0 +1,116 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer; + +use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @internal + */ +abstract class AbstractFopenFlagFixer extends AbstractFunctionReferenceFixer +{ + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAllTokenKindsFound([T_STRING, T_CONSTANT_ENCAPSED_STRING]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $argumentsAnalyzer = new ArgumentsAnalyzer(); + + $index = 0; + $end = $tokens->count() - 1; + while (true) { + $candidate = $this->find('fopen', $tokens, $index, $end); + + if (null === $candidate) { + break; + } + + $index = $candidate[1]; // proceed to '(' of `fopen` + + // fetch arguments + $arguments = $argumentsAnalyzer->getArguments( + $tokens, + $index, + $candidate[2] + ); + + $argumentsCount = \count($arguments); // argument count sanity check + + if ($argumentsCount < 2 || $argumentsCount > 4) { + continue; + } + + $argumentStartIndex = array_keys($arguments)[1]; // get second argument index + + $this->fixFopenFlagToken( + $tokens, + $argumentStartIndex, + $arguments[$argumentStartIndex] + ); + } + } + + abstract protected function fixFopenFlagToken(Tokens $tokens, int $argumentStartIndex, int $argumentEndIndex): void; + + protected function isValidModeString(string $mode): bool + { + $modeLength = \strlen($mode); + if ($modeLength < 1 || $modeLength > 13) { // 13 === length 'r+w+a+x+c+etb' + return false; + } + + $validFlags = [ + 'a' => true, + 'b' => true, + 'c' => true, + 'e' => true, + 'r' => true, + 't' => true, + 'w' => true, + 'x' => true, + ]; + + if (!isset($validFlags[$mode[0]])) { + return false; + } + + unset($validFlags[$mode[0]]); + + for ($i = 1; $i < $modeLength; ++$i) { + if (isset($validFlags[$mode[$i]])) { + unset($validFlags[$mode[$i]]); + + continue; + } + + if ('+' !== $mode[$i] + || ( + 'a' !== $mode[$i - 1] // 'a+','c+','r+','w+','x+' + && 'c' !== $mode[$i - 1] + && 'r' !== $mode[$i - 1] + && 'w' !== $mode[$i - 1] + && 'x' !== $mode[$i - 1] + ) + ) { + return false; + } + } + + return true; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/AbstractFunctionReferenceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/AbstractFunctionReferenceFixer.php new file mode 100644 index 00000000..dfddeb51 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/AbstractFunctionReferenceFixer.php @@ -0,0 +1,74 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer; + +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @internal + * + * @author Vladimir Reznichenko + */ +abstract class AbstractFunctionReferenceFixer extends AbstractFixer +{ + /** + * @var null|FunctionsAnalyzer + */ + private $functionsAnalyzer; + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_STRING); + } + + public function isRisky(): bool + { + return true; + } + + /** + * Looks up Tokens sequence for suitable candidates and delivers boundaries information, + * which can be supplied by other methods in this abstract class. + * + * @return ?array{int, int, int} returns $functionName, $openParenthesis, $closeParenthesis packed into array + */ + protected function find(string $functionNameToSearch, Tokens $tokens, int $start = 0, ?int $end = null): ?array + { + if (null === $this->functionsAnalyzer) { + $this->functionsAnalyzer = new FunctionsAnalyzer(); + } + + // make interface consistent with findSequence + $end ??= $tokens->count(); + + // find raw sequence which we can analyse for context + $candidateSequence = [[T_STRING, $functionNameToSearch], '(']; + $matches = $tokens->findSequence($candidateSequence, $start, $end, false); + + if (null === $matches) { + return null; // not found, simply return without further attempts + } + + // translate results for humans + [$functionName, $openParenthesis] = array_keys($matches); + + if (!$this->functionsAnalyzer->isGlobalFunctionCall($tokens, $functionName)) { + return $this->find($functionNameToSearch, $tokens, $openParenthesis, $end); + } + + return [$functionName, $openParenthesis, $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesis)]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/AbstractNoUselessElseFixer.php b/vendor/friendsofphp/php-cs-fixer/src/AbstractNoUselessElseFixer.php new file mode 100644 index 00000000..48cd7763 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/AbstractNoUselessElseFixer.php @@ -0,0 +1,204 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer; + +use PhpCsFixer\Tokenizer\Tokens; + +abstract class AbstractNoUselessElseFixer extends AbstractFixer +{ + public function getPriority(): int + { + // should be run before NoWhitespaceInBlankLineFixer, NoExtraBlankLinesFixer, BracesFixer and after NoEmptyStatementFixer. + return 39; + } + + protected function isSuperfluousElse(Tokens $tokens, int $index): bool + { + $previousBlockStart = $index; + + do { + // Check if all 'if', 'else if ' and 'elseif' blocks above this 'else' always end, + // if so this 'else' is overcomplete. + [$previousBlockStart, $previousBlockEnd] = $this->getPreviousBlock($tokens, $previousBlockStart); + + // short 'if' detection + $previous = $previousBlockEnd; + if ($tokens[$previous]->equals('}')) { + $previous = $tokens->getPrevMeaningfulToken($previous); + } + + if ( + !$tokens[$previous]->equals(';') // 'if' block doesn't end with semicolon, keep 'else' + || $tokens[$tokens->getPrevMeaningfulToken($previous)]->equals('{') // empty 'if' block, keep 'else' + ) { + return false; + } + + $candidateIndex = $tokens->getPrevTokenOfKind( + $previous, + [ + ';', + [T_BREAK], + [T_CLOSE_TAG], + [T_CONTINUE], + [T_EXIT], + [T_GOTO], + [T_IF], + [T_RETURN], + [T_THROW], + ] + ); + + if (null === $candidateIndex || $tokens[$candidateIndex]->equalsAny([';', [T_CLOSE_TAG], [T_IF]])) { + return false; + } + + if ($tokens[$candidateIndex]->isGivenKind(T_THROW)) { + $previousIndex = $tokens->getPrevMeaningfulToken($candidateIndex); + + if (!$tokens[$previousIndex]->equalsAny([';', '{'])) { + return false; + } + } + + if ($this->isInConditional($tokens, $candidateIndex, $previousBlockStart) + || $this->isInConditionWithoutBraces($tokens, $candidateIndex, $previousBlockStart) + ) { + return false; + } + + // implicit continue, i.e. delete candidate + } while (!$tokens[$previousBlockStart]->isGivenKind(T_IF)); + + return true; + } + + /** + * Return the first and last token index of the previous block. + * + * [0] First is either T_IF, T_ELSE or T_ELSEIF + * [1] Last is either '}' or ';' / T_CLOSE_TAG for short notation blocks + * + * @param int $index T_IF, T_ELSE, T_ELSEIF + * + * @return int[] + */ + private function getPreviousBlock(Tokens $tokens, int $index): array + { + $close = $previous = $tokens->getPrevMeaningfulToken($index); + // short 'if' detection + if ($tokens[$close]->equals('}')) { + $previous = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $close); + } + + $open = $tokens->getPrevTokenOfKind($previous, [[T_IF], [T_ELSE], [T_ELSEIF]]); + if ($tokens[$open]->isGivenKind(T_IF)) { + $elseCandidate = $tokens->getPrevMeaningfulToken($open); + if ($tokens[$elseCandidate]->isGivenKind(T_ELSE)) { + $open = $elseCandidate; + } + } + + return [$open, $close]; + } + + /** + * @param int $index Index of the token to check + * @param int $lowerLimitIndex Lower limit index. Since the token to check will always be in a conditional we must stop checking at this index + */ + private function isInConditional(Tokens $tokens, int $index, int $lowerLimitIndex): bool + { + $candidateIndex = $tokens->getPrevTokenOfKind($index, [')', ';', ':']); + if ($tokens[$candidateIndex]->equals(':')) { + return true; + } + + if (!$tokens[$candidateIndex]->equals(')')) { + return false; // token is ';' or close tag + } + + // token is always ')' here. + // If it is part of the condition the token is always in, return false. + // If it is not it is a nested condition so return true + $open = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $candidateIndex); + + return $tokens->getPrevMeaningfulToken($open) > $lowerLimitIndex; + } + + /** + * For internal use only, as it is not perfect. + * + * Returns if the token at given index is part of an if/elseif/else statement + * without {}. Assumes not passing the last `;`/close tag of the statement, not + * out of range index, etc. + * + * @param int $index Index of the token to check + */ + private function isInConditionWithoutBraces(Tokens $tokens, int $index, int $lowerLimitIndex): bool + { + do { + if ($tokens[$index]->isComment() || $tokens[$index]->isWhitespace()) { + $index = $tokens->getPrevMeaningfulToken($index); + } + + $token = $tokens[$index]; + if ($token->isGivenKind([T_IF, T_ELSEIF, T_ELSE])) { + return true; + } + + if ($token->equals(';')) { + return false; + } + + if ($token->equals('{')) { + $index = $tokens->getPrevMeaningfulToken($index); + + // OK if belongs to: for, do, while, foreach + // Not OK if belongs to: if, else, elseif + if ($tokens[$index]->isGivenKind(T_DO)) { + --$index; + + continue; + } + + if (!$tokens[$index]->equals(')')) { + return false; // like `else {` + } + + $index = $tokens->findBlockStart( + Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, + $index + ); + + $index = $tokens->getPrevMeaningfulToken($index); + if ($tokens[$index]->isGivenKind([T_IF, T_ELSEIF])) { + return false; + } + } elseif ($token->equals(')')) { + $type = Tokens::detectBlockType($token); + $index = $tokens->findBlockStart( + $type['type'], + $index + ); + + $index = $tokens->getPrevMeaningfulToken($index); + } else { + --$index; + } + } while ($index > $lowerLimitIndex); + + return false; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/AbstractPhpdocToTypeDeclarationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/AbstractPhpdocToTypeDeclarationFixer.php new file mode 100644 index 00000000..0f427f09 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/AbstractPhpdocToTypeDeclarationFixer.php @@ -0,0 +1,290 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer; + +use PhpCsFixer\DocBlock\Annotation; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\DocBlock\TypeExpression; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\Tokenizer\Analyzer\NamespacesAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @internal + * + * @phpstan-type _CommonTypeInfo array{commonType: string, isNullable: bool} + */ +abstract class AbstractPhpdocToTypeDeclarationFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + private const REGEX_CLASS = '(?:\\\\?+'.TypeExpression::REGEX_IDENTIFIER + .'(\\\\'.TypeExpression::REGEX_IDENTIFIER.')*+)'; + + /** + * @var array + */ + private array $versionSpecificTypes = [ + 'void' => 7_01_00, + 'iterable' => 7_01_00, + 'object' => 7_02_00, + 'mixed' => 8_00_00, + 'never' => 8_01_00, + ]; + + /** + * @var array + */ + private array $scalarTypes = [ + 'bool' => true, + 'float' => true, + 'int' => true, + 'string' => true, + ]; + + /** + * @var array + */ + private static array $syntaxValidationCache = []; + + public function isRisky(): bool + { + return true; + } + + abstract protected function isSkippedType(string $type): bool; + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('scalar_types', 'Fix also scalar types; may have unexpected behaviour due to PHP bad type coercion system.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), + (new FixerOptionBuilder('union_types', 'Fix also union types; turned on by default on PHP >= 8.0.0.')) + ->setAllowedTypes(['bool']) + ->setDefault(\PHP_VERSION_ID >= 8_00_00) + ->getOption(), + ]); + } + + /** + * @param int $index The index of the function token + */ + protected function findFunctionDocComment(Tokens $tokens, int $index): ?int + { + do { + $index = $tokens->getPrevNonWhitespace($index); + } while ($tokens[$index]->isGivenKind([ + T_COMMENT, + T_ABSTRACT, + T_FINAL, + T_PRIVATE, + T_PROTECTED, + T_PUBLIC, + T_STATIC, + ])); + + if ($tokens[$index]->isGivenKind(T_DOC_COMMENT)) { + return $index; + } + + return null; + } + + /** + * @return list + */ + protected function getAnnotationsFromDocComment(string $name, Tokens $tokens, int $docCommentIndex): array + { + $namespacesAnalyzer = new NamespacesAnalyzer(); + $namespace = $namespacesAnalyzer->getNamespaceAt($tokens, $docCommentIndex); + + $namespaceUsesAnalyzer = new NamespaceUsesAnalyzer(); + $namespaceUses = $namespaceUsesAnalyzer->getDeclarationsInNamespace($tokens, $namespace); + + $doc = new DocBlock( + $tokens[$docCommentIndex]->getContent(), + $namespace, + $namespaceUses + ); + + return $doc->getAnnotationsOfType($name); + } + + /** + * @return list + */ + protected function createTypeDeclarationTokens(string $type, bool $isNullable): array + { + $newTokens = []; + + if (true === $isNullable && 'mixed' !== $type) { + $newTokens[] = new Token([CT::T_NULLABLE_TYPE, '?']); + } + + $newTokens = array_merge( + $newTokens, + $this->createTokensFromRawType($type)->toArray() + ); + + // 'scalar's, 'void', 'iterable' and 'object' must be unqualified + foreach ($newTokens as $i => $token) { + if ($token->isGivenKind(T_STRING)) { + $typeUnqualified = $token->getContent(); + + if ( + (isset($this->scalarTypes[$typeUnqualified]) || isset($this->versionSpecificTypes[$typeUnqualified])) + && isset($newTokens[$i - 1]) + && '\\' === $newTokens[$i - 1]->getContent() + ) { + unset($newTokens[$i - 1]); + } + } + } + + return array_values($newTokens); + } + + /** + * Each fixer inheriting from this class must define a way of creating token collection representing type + * gathered from phpDoc, e.g. `Foo|Bar` should be transformed into 3 tokens (`Foo`, `|` and `Bar`). + * This can't be standardised, because some types may be allowed in one place, and invalid in others. + * + * @param string $type Type determined (and simplified) from phpDoc + */ + abstract protected function createTokensFromRawType(string $type): Tokens; + + /** + * @return ?_CommonTypeInfo + */ + protected function getCommonTypeInfo(TypeExpression $typesExpression, bool $isReturnType): ?array + { + $commonType = $typesExpression->getCommonType(); + $isNullable = $typesExpression->allowsNull(); + + if (null === $commonType) { + return null; + } + + if ($isNullable && 'void' === $commonType) { + return null; + } + + if ('static' === $commonType && (!$isReturnType || \PHP_VERSION_ID < 8_00_00)) { + $commonType = 'self'; + } + + if ($this->isSkippedType($commonType)) { + return null; + } + + if (isset($this->versionSpecificTypes[$commonType]) && \PHP_VERSION_ID < $this->versionSpecificTypes[$commonType]) { + return null; + } + + if (isset($this->scalarTypes[$commonType])) { + if (false === $this->configuration['scalar_types']) { + return null; + } + } elseif (!Preg::match('/^'.self::REGEX_CLASS.'$/', $commonType)) { + return null; + } + + return ['commonType' => $commonType, 'isNullable' => $isNullable]; + } + + protected function getUnionTypes(TypeExpression $typesExpression, bool $isReturnType): ?string + { + if (\PHP_VERSION_ID < 8_00_00) { + return null; + } + + if (!$typesExpression->isUnionType() || '|' !== $typesExpression->getTypesGlue()) { + return null; + } + + if (false === $this->configuration['union_types']) { + return null; + } + + $types = $typesExpression->getTypes(); + $isNullable = $typesExpression->allowsNull(); + $unionTypes = []; + $containsOtherThanIterableType = false; + $containsOtherThanEmptyType = false; + + foreach ($types as $type) { + if ('null' === $type) { + continue; + } + + if ($this->isSkippedType($type)) { + return null; + } + + if (isset($this->versionSpecificTypes[$type]) && \PHP_VERSION_ID < $this->versionSpecificTypes[$type]) { + return null; + } + + $typeExpression = new TypeExpression($type, null, []); + $commonType = $typeExpression->getCommonType(); + + if (!$containsOtherThanIterableType && !\in_array($commonType, ['array', \Traversable::class, 'iterable'], true)) { + $containsOtherThanIterableType = true; + } + if ($isReturnType && !$containsOtherThanEmptyType && !\in_array($commonType, ['null', 'void', 'never'], true)) { + $containsOtherThanEmptyType = true; + } + + if (!$isNullable && $typesExpression->allowsNull()) { + $isNullable = true; + } + + $unionTypes[] = $commonType; + } + + if (!$containsOtherThanIterableType) { + return null; + } + if ($isReturnType && !$containsOtherThanEmptyType) { + return null; + } + + if ($isNullable) { + $unionTypes[] = 'null'; + } + + return implode($typesExpression->getTypesGlue(), array_unique($unionTypes)); + } + + final protected function isValidSyntax(string $code): bool + { + if (!isset(self::$syntaxValidationCache[$code])) { + try { + Tokens::fromCode($code); + self::$syntaxValidationCache[$code] = true; + } catch (\ParseError $e) { + self::$syntaxValidationCache[$code] = false; + } + } + + return self::$syntaxValidationCache[$code]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/AbstractPhpdocTypesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/AbstractPhpdocTypesFixer.php new file mode 100644 index 00000000..82b8e57f --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/AbstractPhpdocTypesFixer.php @@ -0,0 +1,123 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer; + +use PhpCsFixer\DocBlock\Annotation; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\DocBlock\TypeExpression; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * This abstract fixer provides a base for fixers to fix types in PHPDoc. + * + * @author Graham Campbell + * + * @internal + */ +abstract class AbstractPhpdocTypesFixer extends AbstractFixer +{ + /** + * The annotation tags search inside. + * + * @var list + */ + protected array $tags; + + public function __construct() + { + parent::__construct(); + + $this->tags = Annotation::getTagsWithTypes(); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + $doc = new DocBlock($token->getContent()); + $annotations = $doc->getAnnotationsOfType($this->tags); + + if (0 === \count($annotations)) { + continue; + } + + foreach ($annotations as $annotation) { + $this->fixTypes($annotation); + } + + $tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]); + } + } + + /** + * Actually normalize the given type. + */ + abstract protected function normalize(string $type): string; + + /** + * Fix the types at the given line. + * + * We must be super careful not to modify parts of words. + * + * This will be nicely handled behind the scenes for us by the annotation class. + */ + private function fixTypes(Annotation $annotation): void + { + $types = $annotation->getTypes(); + + $new = $this->normalizeTypes($types); + + if ($types !== $new) { + $annotation->setTypes($new); + } + } + + /** + * @param list $types + * + * @return list + */ + private function normalizeTypes(array $types): array + { + return array_map( + function (string $type): string { + $typeExpression = new TypeExpression($type, null, []); + + $typeExpression->walkTypes(function (TypeExpression $type): void { + if (!$type->isUnionType()) { + $value = $this->normalize($type->toString()); + + // TODO TypeExpression should be immutable and walkTypes method should be changed to mapTypes method + \Closure::bind(static function () use ($type, $value): void { + $type->value = $value; + }, null, TypeExpression::class)(); + } + }); + + return $typeExpression->toString(); + }, + $types + ); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/AbstractProxyFixer.php b/vendor/friendsofphp/php-cs-fixer/src/AbstractProxyFixer.php new file mode 100644 index 00000000..d312bae9 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/AbstractProxyFixer.php @@ -0,0 +1,106 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer; + +use PhpCsFixer\Fixer\FixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +abstract class AbstractProxyFixer extends AbstractFixer +{ + /** + * @var array + */ + protected array $proxyFixers = []; + + public function __construct() + { + foreach (Utils::sortFixers($this->createProxyFixers()) as $proxyFixer) { + $this->proxyFixers[$proxyFixer->getName()] = $proxyFixer; + } + + parent::__construct(); + } + + public function isCandidate(Tokens $tokens): bool + { + foreach ($this->proxyFixers as $fixer) { + if ($fixer->isCandidate($tokens)) { + return true; + } + } + + return false; + } + + public function isRisky(): bool + { + foreach ($this->proxyFixers as $fixer) { + if ($fixer->isRisky()) { + return true; + } + } + + return false; + } + + public function getPriority(): int + { + if (\count($this->proxyFixers) > 1) { + throw new \LogicException('You need to override this method to provide the priority of combined fixers.'); + } + + return reset($this->proxyFixers)->getPriority(); + } + + public function supports(\SplFileInfo $file): bool + { + foreach ($this->proxyFixers as $fixer) { + if ($fixer->supports($file)) { + return true; + } + } + + return false; + } + + public function setWhitespacesConfig(WhitespacesFixerConfig $config): void + { + parent::setWhitespacesConfig($config); + + foreach ($this->proxyFixers as $fixer) { + if ($fixer instanceof WhitespacesAwareFixerInterface) { + $fixer->setWhitespacesConfig($config); + } + } + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($this->proxyFixers as $fixer) { + $fixer->fix($file, $tokens); + } + } + + /** + * @return FixerInterface[] + */ + abstract protected function createProxyFixers(): array; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Cache/Cache.php b/vendor/friendsofphp/php-cs-fixer/src/Cache/Cache.php new file mode 100644 index 00000000..1f53fd2b --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Cache/Cache.php @@ -0,0 +1,151 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Cache; + +use PhpCsFixer\Utils; + +/** + * @author Andreas Möller + * + * @internal + */ +final class Cache implements CacheInterface +{ + private SignatureInterface $signature; + + /** + * @var array + */ + private array $hashes = []; + + public function __construct(SignatureInterface $signature) + { + $this->signature = $signature; + } + + public function getSignature(): SignatureInterface + { + return $this->signature; + } + + public function has(string $file): bool + { + return \array_key_exists($file, $this->hashes); + } + + public function get(string $file): ?string + { + if (!$this->has($file)) { + return null; + } + + return $this->hashes[$file]; + } + + public function set(string $file, string $hash): void + { + $this->hashes[$file] = $hash; + } + + public function clear(string $file): void + { + unset($this->hashes[$file]); + } + + public function toJson(): string + { + $json = json_encode([ + 'php' => $this->getSignature()->getPhpVersion(), + 'version' => $this->getSignature()->getFixerVersion(), + 'indent' => $this->getSignature()->getIndent(), + 'lineEnding' => $this->getSignature()->getLineEnding(), + 'rules' => $this->getSignature()->getRules(), + 'hashes' => $this->hashes, + ]); + + if (JSON_ERROR_NONE !== json_last_error() || false === $json) { + throw new \UnexpectedValueException(sprintf( + 'Cannot encode cache signature to JSON, error: "%s". If you have non-UTF8 chars in your signature, like in license for `header_comment`, consider enabling `ext-mbstring` or install `symfony/polyfill-mbstring`.', + json_last_error_msg() + )); + } + + return $json; + } + + /** + * @throws \InvalidArgumentException + */ + public static function fromJson(string $json): self + { + $data = json_decode($json, true); + + if (null === $data && JSON_ERROR_NONE !== json_last_error()) { + throw new \InvalidArgumentException(sprintf( + 'Value needs to be a valid JSON string, got "%s", error: "%s".', + $json, + json_last_error_msg() + )); + } + + $requiredKeys = [ + 'php', + 'version', + 'indent', + 'lineEnding', + 'rules', + 'hashes', + ]; + + $missingKeys = array_diff_key(array_flip($requiredKeys), $data); + + if (\count($missingKeys) > 0) { + throw new \InvalidArgumentException(sprintf( + 'JSON data is missing keys %s', + Utils::naturalLanguageJoin(array_keys($missingKeys)) + )); + } + + $signature = new Signature( + $data['php'], + $data['version'], + $data['indent'], + $data['lineEnding'], + $data['rules'] + ); + + $cache = new self($signature); + + // before v3.11.1 the hashes were crc32 encoded and saved as integers + // @TODO: remove the to string cast/array_map in v4.0 + $cache->hashes = array_map(static fn ($v): string => \is_int($v) ? (string) $v : $v, $data['hashes']); + + return $cache; + } + + /** + * @internal + */ + public function backfillHashes(self $oldCache): bool + { + if (!$this->getSignature()->equals($oldCache->getSignature())) { + return false; + } + + $this->hashes = array_merge($oldCache->hashes, $this->hashes); + + return true; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Cache/CacheInterface.php b/vendor/friendsofphp/php-cs-fixer/src/Cache/CacheInterface.php new file mode 100644 index 00000000..29ab7197 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Cache/CacheInterface.php @@ -0,0 +1,35 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Cache; + +/** + * @author Andreas Möller + * + * @internal + */ +interface CacheInterface +{ + public function getSignature(): SignatureInterface; + + public function has(string $file): bool; + + public function get(string $file): ?string; + + public function set(string $file, string $hash): void; + + public function clear(string $file): void; + + public function toJson(): string; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Cache/CacheManagerInterface.php b/vendor/friendsofphp/php-cs-fixer/src/Cache/CacheManagerInterface.php new file mode 100644 index 00000000..4e82d0c9 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Cache/CacheManagerInterface.php @@ -0,0 +1,27 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Cache; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +interface CacheManagerInterface +{ + public function needFixing(string $file, string $fileContent): bool; + + public function setFile(string $file, string $fileContent): void; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Cache/Directory.php b/vendor/friendsofphp/php-cs-fixer/src/Cache/Directory.php new file mode 100644 index 00000000..26573896 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Cache/Directory.php @@ -0,0 +1,49 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Cache; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class Directory implements DirectoryInterface +{ + private string $directoryName; + + public function __construct(string $directoryName) + { + $this->directoryName = $directoryName; + } + + public function getRelativePathTo(string $file): string + { + $file = $this->normalizePath($file); + + if ( + '' === $this->directoryName + || 0 !== stripos($file, $this->directoryName.\DIRECTORY_SEPARATOR) + ) { + return $file; + } + + return substr($file, \strlen($this->directoryName) + 1); + } + + private function normalizePath(string $path): string + { + return str_replace(['\\', '/'], \DIRECTORY_SEPARATOR, $path); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Cache/DirectoryInterface.php b/vendor/friendsofphp/php-cs-fixer/src/Cache/DirectoryInterface.php new file mode 100644 index 00000000..2fdce86a --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Cache/DirectoryInterface.php @@ -0,0 +1,23 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Cache; + +/** + * @author Dariusz Rumiński + */ +interface DirectoryInterface +{ + public function getRelativePathTo(string $file): string; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Cache/FileCacheManager.php b/vendor/friendsofphp/php-cs-fixer/src/Cache/FileCacheManager.php new file mode 100644 index 00000000..1e3f64dc --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Cache/FileCacheManager.php @@ -0,0 +1,141 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Cache; + +/** + * Class supports caching information about state of fixing files. + * + * Cache is supported only for phar version and version installed via composer. + * + * File will be processed by PHP CS Fixer only if any of the following conditions is fulfilled: + * - cache is corrupt + * - fixer version changed + * - rules changed + * - file is new + * - file changed + * + * @author Dariusz Rumiński + * + * @internal + */ +final class FileCacheManager implements CacheManagerInterface +{ + public const WRITE_FREQUENCY = 10; + + private FileHandlerInterface $handler; + + private SignatureInterface $signature; + + private bool $isDryRun; + + private DirectoryInterface $cacheDirectory; + + private int $writeCounter = 0; + + private bool $signatureWasUpdated = false; + + /** + * @var CacheInterface + */ + private $cache; + + public function __construct( + FileHandlerInterface $handler, + SignatureInterface $signature, + bool $isDryRun = false, + ?DirectoryInterface $cacheDirectory = null + ) { + $this->handler = $handler; + $this->signature = $signature; + $this->isDryRun = $isDryRun; + $this->cacheDirectory = $cacheDirectory ?? new Directory(''); + + $this->readCache(); + } + + public function __destruct() + { + if (true === $this->signatureWasUpdated || 0 !== $this->writeCounter) { + $this->writeCache(); + } + } + + /** + * This class is not intended to be serialized, + * and cannot be deserialized (see __wakeup method). + */ + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + /** + * Disable the deserialization of the class to prevent attacker executing + * code by leveraging the __destruct method. + * + * @see https://owasp.org/www-community/vulnerabilities/PHP_Object_Injection + */ + public function __wakeup(): void + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function needFixing(string $file, string $fileContent): bool + { + $file = $this->cacheDirectory->getRelativePathTo($file); + + return !$this->cache->has($file) || $this->cache->get($file) !== $this->calcHash($fileContent); + } + + public function setFile(string $file, string $fileContent): void + { + $file = $this->cacheDirectory->getRelativePathTo($file); + + $hash = $this->calcHash($fileContent); + + if ($this->isDryRun && $this->cache->has($file) && $this->cache->get($file) !== $hash) { + $this->cache->clear($file); + } else { + $this->cache->set($file, $hash); + } + + if (self::WRITE_FREQUENCY === ++$this->writeCounter) { + $this->writeCounter = 0; + $this->writeCache(); + } + } + + private function readCache(): void + { + $cache = $this->handler->read(); + + if (null === $cache || !$this->signature->equals($cache->getSignature())) { + $cache = new Cache($this->signature); + $this->signatureWasUpdated = true; + } + + $this->cache = $cache; + } + + private function writeCache(): void + { + $this->handler->write($this->cache); + } + + private function calcHash(string $content): string + { + return md5($content); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Cache/FileHandler.php b/vendor/friendsofphp/php-cs-fixer/src/Cache/FileHandler.php new file mode 100644 index 00000000..51b4ca58 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Cache/FileHandler.php @@ -0,0 +1,184 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Cache; + +use Symfony\Component\Filesystem\Exception\IOException; + +/** + * @author Andreas Möller + * @author Dariusz Rumiński + * + * @internal + */ +final class FileHandler implements FileHandlerInterface +{ + private \SplFileInfo $fileInfo; + + private int $fileMTime = 0; + + public function __construct(string $file) + { + $this->fileInfo = new \SplFileInfo($file); + } + + public function getFile(): string + { + return $this->fileInfo->getPathname(); + } + + public function read(): ?CacheInterface + { + if (!$this->fileInfo->isFile() || !$this->fileInfo->isReadable()) { + return null; + } + + $fileObject = $this->fileInfo->openFile('r'); + + $cache = $this->readFromHandle($fileObject); + $this->fileMTime = $this->getFileCurrentMTime(); + + unset($fileObject); // explicitly close file handler + + return $cache; + } + + public function write(CacheInterface $cache): void + { + $this->ensureFileIsWriteable(); + + $fileObject = $this->fileInfo->openFile('r+'); + + if (method_exists($cache, 'backfillHashes') && $this->fileMTime < $this->getFileCurrentMTime()) { + $resultOfFlock = $fileObject->flock(LOCK_EX); + if (false === $resultOfFlock) { + // Lock failed, OK - we continue without the lock. + // noop + } + + $oldCache = $this->readFromHandle($fileObject); + + $fileObject->rewind(); + + if (null !== $oldCache) { + $cache->backfillHashes($oldCache); + } + } + + $resultOfTruncate = $fileObject->ftruncate(0); + if (false === $resultOfTruncate) { + // Truncate failed. OK - we do not save the cache. + return; + } + + $resultOfWrite = $fileObject->fwrite($cache->toJson()); + if (false === $resultOfWrite) { + // Write failed. OK - we did not save the cache. + return; + } + + $resultOfFlush = $fileObject->fflush(); + if (false === $resultOfFlush) { + // Flush failed. OK - part of cache can be missing, in case this was last chunk in this pid. + // noop + } + + $this->fileMTime = time(); // we could take the fresh `mtime` of file that we just modified with `$this->getFileCurrentMTime()`, but `time()` should be good enough here and reduce IO operation + } + + private function getFileCurrentMTime(): int + { + clearstatcache(true, $this->fileInfo->getPathname()); + + $mtime = $this->fileInfo->getMTime(); + + if (false === $mtime) { + // cannot check mtime? OK - let's pretend file is old. + $mtime = 0; + } + + return $mtime; + } + + private function readFromHandle(\SplFileObject $fileObject): ?CacheInterface + { + try { + $size = $fileObject->getSize(); + if (false === $size || 0 === $size) { + return null; + } + + $content = $fileObject->fread($size); + + if (false === $content) { + return null; + } + + return Cache::fromJson($content); + } catch (\InvalidArgumentException $exception) { + return null; + } + } + + private function ensureFileIsWriteable(): void + { + if ($this->fileInfo->isFile() && $this->fileInfo->isWritable()) { + // all good + return; + } + + if ($this->fileInfo->isDir()) { + throw new IOException( + sprintf('Cannot write cache file "%s" as the location exists as directory.', $this->fileInfo->getRealPath()), + 0, + null, + $this->fileInfo->getPathname() + ); + } + + if ($this->fileInfo->isFile() && !$this->fileInfo->isWritable()) { + throw new IOException( + sprintf('Cannot write to file "%s" as it is not writable.', $this->fileInfo->getRealPath()), + 0, + null, + $this->fileInfo->getPathname() + ); + } + + $this->createFile($this->fileInfo->getPathname()); + } + + private function createFile(string $file): void + { + $dir = \dirname($file); + + // Ensure path is created, but ignore if already exists. FYI: ignore EA suggestion in IDE, + // `mkdir()` returns `false` for existing paths, so we can't mix it with `is_dir()` in one condition. + if (!@is_dir($dir)) { + @mkdir($dir, 0777, true); + } + + if (!@is_dir($dir)) { + throw new IOException( + sprintf('Directory of cache file "%s" does not exists and couldn\'t be created.', $file), + 0, + null, + $file + ); + } + + @touch($file); + @chmod($file, 0666); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Cache/FileHandlerInterface.php b/vendor/friendsofphp/php-cs-fixer/src/Cache/FileHandlerInterface.php new file mode 100644 index 00000000..464b04cf --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Cache/FileHandlerInterface.php @@ -0,0 +1,29 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Cache; + +/** + * @author Andreas Möller + * + * @internal + */ +interface FileHandlerInterface +{ + public function getFile(): string; + + public function read(): ?CacheInterface; + + public function write(CacheInterface $cache): void; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Cache/NullCacheManager.php b/vendor/friendsofphp/php-cs-fixer/src/Cache/NullCacheManager.php new file mode 100644 index 00000000..ff0b6fed --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Cache/NullCacheManager.php @@ -0,0 +1,30 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Cache; + +/** + * @author Andreas Möller + * + * @internal + */ +final class NullCacheManager implements CacheManagerInterface +{ + public function needFixing(string $file, string $fileContent): bool + { + return true; + } + + public function setFile(string $file, string $fileContent): void {} +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Cache/Signature.php b/vendor/friendsofphp/php-cs-fixer/src/Cache/Signature.php new file mode 100644 index 00000000..48c96289 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Cache/Signature.php @@ -0,0 +1,98 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Cache; + +/** + * @author Andreas Möller + * + * @internal + */ +final class Signature implements SignatureInterface +{ + private string $phpVersion; + + private string $fixerVersion; + + private string $indent; + + private string $lineEnding; + + /** + * @var array|bool> + */ + private array $rules; + + /** + * @param array|bool> $rules + */ + public function __construct(string $phpVersion, string $fixerVersion, string $indent, string $lineEnding, array $rules) + { + $this->phpVersion = $phpVersion; + $this->fixerVersion = $fixerVersion; + $this->indent = $indent; + $this->lineEnding = $lineEnding; + $this->rules = self::makeJsonEncodable($rules); + } + + public function getPhpVersion(): string + { + return $this->phpVersion; + } + + public function getFixerVersion(): string + { + return $this->fixerVersion; + } + + public function getIndent(): string + { + return $this->indent; + } + + public function getLineEnding(): string + { + return $this->lineEnding; + } + + public function getRules(): array + { + return $this->rules; + } + + public function equals(SignatureInterface $signature): bool + { + return $this->phpVersion === $signature->getPhpVersion() + && $this->fixerVersion === $signature->getFixerVersion() + && $this->indent === $signature->getIndent() + && $this->lineEnding === $signature->getLineEnding() + && $this->rules === $signature->getRules(); + } + + /** + * @param array|bool> $data + * + * @return array|bool> + */ + private static function makeJsonEncodable(array $data): array + { + array_walk_recursive($data, static function (&$item): void { + if (\is_string($item) && !mb_detect_encoding($item, 'utf-8', true)) { + $item = base64_encode($item); + } + }); + + return $data; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Cache/SignatureInterface.php b/vendor/friendsofphp/php-cs-fixer/src/Cache/SignatureInterface.php new file mode 100644 index 00000000..cc952141 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Cache/SignatureInterface.php @@ -0,0 +1,38 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Cache; + +/** + * @author Andreas Möller + * + * @internal + */ +interface SignatureInterface +{ + public function getPhpVersion(): string; + + public function getFixerVersion(): string; + + public function getIndent(): string; + + public function getLineEnding(): string; + + /** + * @return array|bool> + */ + public function getRules(): array; + + public function equals(self $signature): bool; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Config.php b/vendor/friendsofphp/php-cs-fixer/src/Config.php new file mode 100644 index 00000000..1febd2a0 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Config.php @@ -0,0 +1,226 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer; + +use PhpCsFixer\Fixer\FixerInterface; + +/** + * @author Fabien Potencier + * @author Katsuhiro Ogawa + * @author Dariusz Rumiński + */ +class Config implements ConfigInterface +{ + private string $cacheFile = '.php-cs-fixer.cache'; + + /** + * @var FixerInterface[] + */ + private array $customFixers = []; + + /** + * @var null|iterable<\SplFileInfo> + */ + private ?iterable $finder = null; + + private string $format = 'txt'; + + private bool $hideProgress = false; + + private string $indent = ' '; + + private bool $isRiskyAllowed = false; + + private string $lineEnding = "\n"; + + private string $name; + + /** + * @var null|string + */ + private $phpExecutable; + + /** + * @TODO: 4.0 - update to @PER + * + * @var array|bool> + */ + private array $rules; + + private bool $usingCache = true; + + public function __construct(string $name = 'default') + { + // @TODO 4.0 cleanup + if (Utils::isFutureModeEnabled()) { + $this->name = $name.' (future mode)'; + $this->rules = ['@PER-CS' => true]; + } else { + $this->name = $name; + $this->rules = ['@PSR12' => true]; + } + } + + public function getCacheFile(): string + { + return $this->cacheFile; + } + + public function getCustomFixers(): array + { + return $this->customFixers; + } + + /** + * @return Finder + */ + public function getFinder(): iterable + { + if (null === $this->finder) { + $this->finder = new Finder(); + } + + return $this->finder; + } + + public function getFormat(): string + { + return $this->format; + } + + public function getHideProgress(): bool + { + return $this->hideProgress; + } + + public function getIndent(): string + { + return $this->indent; + } + + public function getLineEnding(): string + { + return $this->lineEnding; + } + + public function getName(): string + { + return $this->name; + } + + public function getPhpExecutable(): ?string + { + return $this->phpExecutable; + } + + public function getRiskyAllowed(): bool + { + return $this->isRiskyAllowed; + } + + public function getRules(): array + { + return $this->rules; + } + + public function getUsingCache(): bool + { + return $this->usingCache; + } + + public function registerCustomFixers(iterable $fixers): ConfigInterface + { + foreach ($fixers as $fixer) { + $this->addCustomFixer($fixer); + } + + return $this; + } + + public function setCacheFile(string $cacheFile): ConfigInterface + { + $this->cacheFile = $cacheFile; + + return $this; + } + + public function setFinder(iterable $finder): ConfigInterface + { + $this->finder = $finder; + + return $this; + } + + public function setFormat(string $format): ConfigInterface + { + $this->format = $format; + + return $this; + } + + public function setHideProgress(bool $hideProgress): ConfigInterface + { + $this->hideProgress = $hideProgress; + + return $this; + } + + public function setIndent(string $indent): ConfigInterface + { + $this->indent = $indent; + + return $this; + } + + public function setLineEnding(string $lineEnding): ConfigInterface + { + $this->lineEnding = $lineEnding; + + return $this; + } + + public function setPhpExecutable(?string $phpExecutable): ConfigInterface + { + $this->phpExecutable = $phpExecutable; + + return $this; + } + + public function setRiskyAllowed(bool $isRiskyAllowed): ConfigInterface + { + $this->isRiskyAllowed = $isRiskyAllowed; + + return $this; + } + + public function setRules(array $rules): ConfigInterface + { + $this->rules = $rules; + + return $this; + } + + public function setUsingCache(bool $usingCache): ConfigInterface + { + $this->usingCache = $usingCache; + + return $this; + } + + private function addCustomFixer(FixerInterface $fixer): void + { + $this->customFixers[] = $fixer; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/ConfigInterface.php b/vendor/friendsofphp/php-cs-fixer/src/ConfigInterface.php new file mode 100644 index 00000000..b46b191b --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/ConfigInterface.php @@ -0,0 +1,140 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer; + +use PhpCsFixer\Fixer\FixerInterface; + +/** + * @author Fabien Potencier + * @author Dariusz Rumiński + */ +interface ConfigInterface +{ + /** + * Returns the path to the cache file. + * + * @return null|string Returns null if not using cache + */ + public function getCacheFile(): ?string; + + /** + * Returns the custom fixers to use. + * + * @return FixerInterface[] + */ + public function getCustomFixers(): array; + + /** + * Returns files to scan. + * + * @return iterable<\SplFileInfo> + */ + public function getFinder(): iterable; + + public function getFormat(): string; + + /** + * Returns true if progress should be hidden. + */ + public function getHideProgress(): bool; + + public function getIndent(): string; + + public function getLineEnding(): string; + + /** + * Returns the name of the configuration. + * + * The name must be all lowercase and without any spaces. + * + * @return string The name of the configuration + */ + public function getName(): string; + + /** + * Get configured PHP executable, if any. + */ + public function getPhpExecutable(): ?string; + + /** + * Check if it is allowed to run risky fixers. + */ + public function getRiskyAllowed(): bool; + + /** + * Get rules. + * + * Keys of array are names of fixers/sets, values are true/false. + * + * @return array|bool> + */ + public function getRules(): array; + + /** + * Returns true if caching should be enabled. + */ + public function getUsingCache(): bool; + + /** + * Adds a suite of custom fixers. + * + * Name of custom fixer should follow `VendorName/rule_name` convention. + * + * @param FixerInterface[]|iterable|\Traversable $fixers + */ + public function registerCustomFixers(iterable $fixers): self; + + /** + * Sets the path to the cache file. + */ + public function setCacheFile(string $cacheFile): self; + + /** + * @param iterable<\SplFileInfo> $finder + */ + public function setFinder(iterable $finder): self; + + public function setFormat(string $format): self; + + public function setHideProgress(bool $hideProgress): self; + + public function setIndent(string $indent): self; + + public function setLineEnding(string $lineEnding): self; + + /** + * Set PHP executable. + */ + public function setPhpExecutable(?string $phpExecutable): self; + + /** + * Set if it is allowed to run risky fixers. + */ + public function setRiskyAllowed(bool $isRiskyAllowed): self; + + /** + * Set rules. + * + * Keys of array are names of fixers or sets. + * Value for set must be bool (turn it on or off). + * Value for fixer may be bool (turn it on or off) or array of configuration + * (turn it on and contains configuration for FixerInterface::configure method). + * + * @param array|bool> $rules + */ + public function setRules(array $rules): self; + + public function setUsingCache(bool $usingCache): self; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/ConfigurationException/InvalidConfigurationException.php b/vendor/friendsofphp/php-cs-fixer/src/ConfigurationException/InvalidConfigurationException.php new file mode 100644 index 00000000..87babf8f --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/ConfigurationException/InvalidConfigurationException.php @@ -0,0 +1,36 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\ConfigurationException; + +use PhpCsFixer\Console\Command\FixCommandExitStatusCalculator; + +/** + * Exceptions of this type are thrown on misconfiguration of the Fixer. + * + * @internal + * + * @final Only internal extending this class is supported + */ +class InvalidConfigurationException extends \InvalidArgumentException +{ + public function __construct(string $message, ?int $code = null, ?\Throwable $previous = null) + { + parent::__construct( + $message, + $code ?? FixCommandExitStatusCalculator::EXIT_STATUS_FLAG_HAS_INVALID_CONFIG, + $previous + ); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/ConfigurationException/InvalidFixerConfigurationException.php b/vendor/friendsofphp/php-cs-fixer/src/ConfigurationException/InvalidFixerConfigurationException.php new file mode 100644 index 00000000..140385c3 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/ConfigurationException/InvalidFixerConfigurationException.php @@ -0,0 +1,45 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\ConfigurationException; + +use PhpCsFixer\Console\Command\FixCommandExitStatusCalculator; + +/** + * Exception thrown by Fixers on misconfiguration. + * + * @internal + * + * @final Only internal extending this class is supported + */ +class InvalidFixerConfigurationException extends InvalidConfigurationException +{ + private string $fixerName; + + public function __construct(string $fixerName, string $message, ?\Throwable $previous = null) + { + parent::__construct( + sprintf('[%s] %s', $fixerName, $message), + FixCommandExitStatusCalculator::EXIT_STATUS_FLAG_HAS_INVALID_FIXER_CONFIG, + $previous + ); + + $this->fixerName = $fixerName; + } + + public function getFixerName(): string + { + return $this->fixerName; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/ConfigurationException/InvalidForEnvFixerConfigurationException.php b/vendor/friendsofphp/php-cs-fixer/src/ConfigurationException/InvalidForEnvFixerConfigurationException.php new file mode 100644 index 00000000..440b2470 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/ConfigurationException/InvalidForEnvFixerConfigurationException.php @@ -0,0 +1,22 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\ConfigurationException; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class InvalidForEnvFixerConfigurationException extends InvalidFixerConfigurationException {} diff --git a/vendor/friendsofphp/php-cs-fixer/src/ConfigurationException/RequiredFixerConfigurationException.php b/vendor/friendsofphp/php-cs-fixer/src/ConfigurationException/RequiredFixerConfigurationException.php new file mode 100644 index 00000000..bcc07bdf --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/ConfigurationException/RequiredFixerConfigurationException.php @@ -0,0 +1,22 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\ConfigurationException; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class RequiredFixerConfigurationException extends InvalidFixerConfigurationException {} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Application.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Application.php new file mode 100644 index 00000000..246e10c7 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Application.php @@ -0,0 +1,163 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console; + +use PhpCsFixer\Console\Command\CheckCommand; +use PhpCsFixer\Console\Command\DescribeCommand; +use PhpCsFixer\Console\Command\FixCommand; +use PhpCsFixer\Console\Command\HelpCommand; +use PhpCsFixer\Console\Command\ListFilesCommand; +use PhpCsFixer\Console\Command\ListSetsCommand; +use PhpCsFixer\Console\Command\SelfUpdateCommand; +use PhpCsFixer\Console\SelfUpdate\GithubClient; +use PhpCsFixer\Console\SelfUpdate\NewVersionChecker; +use PhpCsFixer\PharChecker; +use PhpCsFixer\ToolInfo; +use PhpCsFixer\Utils; +use Symfony\Component\Console\Application as BaseApplication; +use Symfony\Component\Console\Command\ListCommand; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Fabien Potencier + * @author Dariusz Rumiński + * + * @internal + */ +final class Application extends BaseApplication +{ + public const NAME = 'PHP CS Fixer'; + public const VERSION = '3.51.0'; + public const VERSION_CODENAME = 'Insomnia'; + + private ToolInfo $toolInfo; + + public function __construct() + { + parent::__construct(self::NAME, self::VERSION); + + $this->toolInfo = new ToolInfo(); + + // in alphabetical order + $this->add(new DescribeCommand()); + $this->add(new CheckCommand($this->toolInfo)); + $this->add(new FixCommand($this->toolInfo)); + $this->add(new ListFilesCommand($this->toolInfo)); + $this->add(new ListSetsCommand()); + $this->add(new SelfUpdateCommand( + new NewVersionChecker(new GithubClient()), + $this->toolInfo, + new PharChecker() + )); + } + + public static function getMajorVersion(): int + { + return (int) explode('.', self::VERSION)[0]; + } + + public function doRun(InputInterface $input, OutputInterface $output): int + { + $stdErr = $output instanceof ConsoleOutputInterface + ? $output->getErrorOutput() + : ($input->hasParameterOption('--format', true) && 'txt' !== $input->getParameterOption('--format', null, true) ? null : $output); + + if (null !== $stdErr) { + $warningsDetector = new WarningsDetector($this->toolInfo); + $warningsDetector->detectOldVendor(); + $warningsDetector->detectOldMajor(); + $warnings = $warningsDetector->getWarnings(); + + if (\count($warnings) > 0) { + foreach ($warnings as $warning) { + $stdErr->writeln(sprintf($stdErr->isDecorated() ? '%s' : '%s', $warning)); + } + $stdErr->writeln(''); + } + } + + $result = parent::doRun($input, $output); + + if ( + null !== $stdErr + && $output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE + ) { + $triggeredDeprecations = Utils::getTriggeredDeprecations(); + + if (\count($triggeredDeprecations) > 0) { + $stdErr->writeln(''); + $stdErr->writeln($stdErr->isDecorated() ? 'Detected deprecations in use:' : 'Detected deprecations in use:'); + foreach ($triggeredDeprecations as $deprecation) { + $stdErr->writeln(sprintf('- %s', $deprecation)); + } + } + } + + return $result; + } + + /** + * @internal + */ + public static function getAbout(bool $decorated = false): string + { + $longVersion = sprintf('%s %s', self::NAME, self::VERSION); + + $commit = '@git-commit@'; + $versionCommit = ''; + + if ('@'.'git-commit@' !== $commit) { /** @phpstan-ignore-line as `$commit` is replaced during phar building */ + $versionCommit = substr($commit, 0, 7); + } + + $about = implode('', [ + $longVersion, + $versionCommit ? sprintf(' (%s)', $versionCommit) : '', // @phpstan-ignore-line to avoid `Ternary operator condition is always true|false.` + self::VERSION_CODENAME ? sprintf(' %s', self::VERSION_CODENAME) : '', // @phpstan-ignore-line to avoid `Ternary operator condition is always true|false.` + ' by Fabien Potencier, Dariusz Ruminski and contributors.', + ]); + + if (false === $decorated) { + return strip_tags($about); + } + + return $about; + } + + /** + * @internal + */ + public static function getAboutWithRuntime(bool $decorated = false): string + { + $about = self::getAbout(true)."\nPHP runtime: ".PHP_VERSION.''; + if (false === $decorated) { + return strip_tags($about); + } + + return $about; + } + + public function getLongVersion(): string + { + return self::getAboutWithRuntime(true); + } + + protected function getDefaultCommands(): array + { + return [new HelpCommand(), new ListCommand()]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Command/CheckCommand.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/CheckCommand.php new file mode 100644 index 00000000..1e7d7537 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/CheckCommand.php @@ -0,0 +1,66 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Command; + +use PhpCsFixer\ToolInfoInterface; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; + +/** + * @author Greg Korba + * + * @internal + */ +#[AsCommand(name: 'check', description: 'Checks if configured files/directories comply with configured rules.')] +final class CheckCommand extends FixCommand +{ + /** @var string */ + protected static $defaultName = 'check'; + + /** @var string */ + protected static $defaultDescription = 'Checks if configured files/directories comply with configured rules.'; + + public function __construct(ToolInfoInterface $toolInfo) + { + parent::__construct($toolInfo); + } + + public function getHelp(): string + { + $help = explode('--dry-run', parent::getHelp()); + + return substr($help[0], 0, strrpos($help[0], "\n") - 1) + .substr($help[1], strpos($help[1], "\n")); + } + + protected function configure(): void + { + parent::configure(); + + $this->setDefinition([ + ...array_values($this->getDefinition()->getArguments()), + ...array_values(array_filter( + $this->getDefinition()->getOptions(), + static fn (InputOption $option): bool => 'dry-run' !== $option->getName() + )), + ]); + } + + protected function isDryRun(InputInterface $input): bool + { + return true; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Command/DescribeCommand.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/DescribeCommand.php new file mode 100644 index 00000000..08fd9b4c --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/DescribeCommand.php @@ -0,0 +1,467 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Command; + +use PhpCsFixer\Config; +use PhpCsFixer\Console\Application; +use PhpCsFixer\Console\ConfigurationResolver; +use PhpCsFixer\Differ\DiffConsoleFormatter; +use PhpCsFixer\Differ\FullDiffer; +use PhpCsFixer\Documentation\FixerDocumentGenerator; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; +use PhpCsFixer\Fixer\ExperimentalFixerInterface; +use PhpCsFixer\Fixer\FixerInterface; +use PhpCsFixer\FixerConfiguration\AliasedFixerOption; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\DeprecatedFixerOption; +use PhpCsFixer\FixerDefinition\CodeSampleInterface; +use PhpCsFixer\FixerDefinition\FileSpecificCodeSampleInterface; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSampleInterface; +use PhpCsFixer\FixerFactory; +use PhpCsFixer\Preg; +use PhpCsFixer\RuleSet\RuleSets; +use PhpCsFixer\StdinFileInfo; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\ToolInfo; +use PhpCsFixer\Utils; +use PhpCsFixer\WordMatcher; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +#[AsCommand(name: 'describe')] +final class DescribeCommand extends Command +{ + /** @var string */ + protected static $defaultName = 'describe'; + + /** + * @var ?list + */ + private $setNames; + + private FixerFactory $fixerFactory; + + /** + * @var array + */ + private $fixers; + + public function __construct(?FixerFactory $fixerFactory = null) + { + parent::__construct(); + + if (null === $fixerFactory) { + $fixerFactory = new FixerFactory(); + $fixerFactory->registerBuiltInFixers(); + } + + $this->fixerFactory = $fixerFactory; + } + + protected function configure(): void + { + $this + ->setDefinition( + [ + new InputArgument('name', InputArgument::REQUIRED, 'Name of rule / set.'), + new InputOption('config', '', InputOption::VALUE_REQUIRED, 'The path to a .php-cs-fixer.php file.'), + ] + ) + ->setDescription('Describe rule / ruleset.') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + if ($output instanceof ConsoleOutputInterface) { + $stdErr = $output->getErrorOutput(); + $stdErr->writeln(Application::getAboutWithRuntime(true)); + } + + $resolver = new ConfigurationResolver( + new Config(), + ['config' => $input->getOption('config')], + getcwd(), + new ToolInfo() + ); + + $this->fixerFactory->registerCustomFixers($resolver->getConfig()->getCustomFixers()); + + $name = $input->getArgument('name'); + + try { + if (str_starts_with($name, '@')) { + $this->describeSet($output, $name); + + return 0; + } + + $this->describeRule($output, $name); + } catch (DescribeNameNotFoundException $e) { + $matcher = new WordMatcher( + 'set' === $e->getType() ? $this->getSetNames() : array_keys($this->getFixers()) + ); + + $alternative = $matcher->match($name); + + $this->describeList($output, $e->getType()); + + throw new \InvalidArgumentException(sprintf( + '%s "%s" not found.%s', + ucfirst($e->getType()), + $name, + null === $alternative ? '' : ' Did you mean "'.$alternative.'"?' + )); + } + + return 0; + } + + private function describeRule(OutputInterface $output, string $name): void + { + $fixers = $this->getFixers(); + + if (!isset($fixers[$name])) { + throw new DescribeNameNotFoundException($name, 'rule'); + } + + /** @var FixerInterface $fixer */ + $fixer = $fixers[$name]; + + $definition = $fixer->getDefinition(); + + $output->writeln(sprintf('Description of the `%s` rule.', $name)); + $output->writeln(''); + + if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) { + $output->writeln(sprintf('Fixer class: %s.', \get_class($fixer))); + $output->writeln(''); + } + + if ($fixer instanceof DeprecatedFixerInterface) { + $successors = $fixer->getSuccessorsNames(); + $message = [] === $successors + ? sprintf('it will be removed in version %d.0', Application::getMajorVersion() + 1) + : sprintf('use %s instead', Utils::naturalLanguageJoinWithBackticks($successors)); + + $endMessage = '. '.ucfirst($message); + Utils::triggerDeprecation(new \RuntimeException(str_replace('`', '"', "Rule \"{$name}\" is deprecated{$endMessage}."))); + $message = Preg::replace('/(`[^`]+`)/', '$1', $message); + $output->writeln(sprintf('DEPRECATED: %s.', $message)); + $output->writeln(''); + } + + $output->writeln($definition->getSummary()); + + $description = $definition->getDescription(); + + if (null !== $description) { + $output->writeln($description); + } + + $output->writeln(''); + + if ($fixer instanceof ExperimentalFixerInterface) { + $output->writeln('Fixer applying this rule is EXPERIMENTAL..'); + $output->writeln('It is not covered with backward compatibility promise and may produce unstable or unexpected results.'); + + $output->writeln(''); + } + + if ($fixer->isRisky()) { + $output->writeln('Fixer applying this rule is RISKY.'); + + $riskyDescription = $definition->getRiskyDescription(); + + if (null !== $riskyDescription) { + $output->writeln($riskyDescription); + } + + $output->writeln(''); + } + + if ($fixer instanceof ConfigurableFixerInterface) { + $configurationDefinition = $fixer->getConfigurationDefinition(); + $options = $configurationDefinition->getOptions(); + + $output->writeln(sprintf('Fixer is configurable using following option%s:', 1 === \count($options) ? '' : 's')); + + foreach ($options as $option) { + $line = '* '.OutputFormatter::escape($option->getName()).''; + $allowed = HelpCommand::getDisplayableAllowedValues($option); + + if (null === $allowed) { + $allowed = array_map( + static fn (string $type): string => ''.$type.'', + $option->getAllowedTypes(), + ); + } else { + $allowed = array_map(static fn ($value): string => $value instanceof AllowedValueSubset + ? 'a subset of '.Utils::toString($value->getAllowedValues()).'' + : ''.Utils::toString($value).'', $allowed); + } + + $line .= ' ('.Utils::naturalLanguageJoin($allowed, '').')'; + + $description = Preg::replace('/(`.+?`)/', '$1', OutputFormatter::escape($option->getDescription())); + $line .= ': '.lcfirst(Preg::replace('/\.$/', '', $description)).'; '; + + if ($option->hasDefault()) { + $line .= sprintf( + 'defaults to %s', + Utils::toString($option->getDefault()) + ); + } else { + $line .= 'required'; + } + + if ($option instanceof DeprecatedFixerOption) { + $line .= '. DEPRECATED: '.Preg::replace( + '/(`.+?`)/', + '$1', + OutputFormatter::escape(lcfirst($option->getDeprecationMessage())) + ); + } + + if ($option instanceof AliasedFixerOption) { + $line .= '; DEPRECATED alias: '.$option->getAlias().''; + } + + $output->writeln($line); + } + + $output->writeln(''); + } + + /** @var CodeSampleInterface[] $codeSamples */ + $codeSamples = array_filter($definition->getCodeSamples(), static function (CodeSampleInterface $codeSample): bool { + if ($codeSample instanceof VersionSpecificCodeSampleInterface) { + return $codeSample->isSuitableFor(\PHP_VERSION_ID); + } + + return true; + }); + + if (0 === \count($definition->getCodeSamples())) { + $output->writeln([ + 'Fixing examples are not available for this rule.', + '', + ]); + } elseif (0 === \count($codeSamples)) { + $output->writeln([ + 'Fixing examples cannot be demonstrated on the current PHP version.', + '', + ]); + } else { + $output->writeln('Fixing examples:'); + + $differ = new FullDiffer(); + $diffFormatter = new DiffConsoleFormatter( + $output->isDecorated(), + sprintf( + ' ---------- begin diff ----------%s%%s%s ----------- end diff -----------', + PHP_EOL, + PHP_EOL + ) + ); + + foreach ($codeSamples as $index => $codeSample) { + $old = $codeSample->getCode(); + $tokens = Tokens::fromCode($old); + + $configuration = $codeSample->getConfiguration(); + + if ($fixer instanceof ConfigurableFixerInterface) { + $fixer->configure($configuration ?? []); + } + + $file = $codeSample instanceof FileSpecificCodeSampleInterface + ? $codeSample->getSplFileInfo() + : new StdinFileInfo(); + + $fixer->fix($file, $tokens); + + $diff = $differ->diff($old, $tokens->generateCode()); + + if ($fixer instanceof ConfigurableFixerInterface) { + if (null === $configuration) { + $output->writeln(sprintf(' * Example #%d. Fixing with the default configuration.', $index + 1)); + } else { + $output->writeln(sprintf(' * Example #%d. Fixing with configuration: %s.', $index + 1, Utils::toString($codeSample->getConfiguration()))); + } + } else { + $output->writeln(sprintf(' * Example #%d.', $index + 1)); + } + + $output->writeln([$diffFormatter->format($diff, ' %s'), '']); + } + } + + $ruleSetConfigs = FixerDocumentGenerator::getSetsOfRule($name); + + if ([] !== $ruleSetConfigs) { + ksort($ruleSetConfigs); + $plural = 1 !== \count($ruleSetConfigs) ? 's' : ''; + $output->writeln("Fixer is part of the following rule set{$plural}:"); + + foreach ($ruleSetConfigs as $set => $config) { + if (null !== $config) { + $output->writeln(sprintf('* %s with config: %s', $set, Utils::toString($config))); + } else { + $output->writeln(sprintf('* %s with default config', $set)); + } + } + + $output->writeln(''); + } + } + + private function describeSet(OutputInterface $output, string $name): void + { + if (!\in_array($name, $this->getSetNames(), true)) { + throw new DescribeNameNotFoundException($name, 'set'); + } + + $ruleSetDefinitions = RuleSets::getSetDefinitions(); + $fixers = $this->getFixers(); + + $output->writeln(sprintf('Description of the `%s` set.', $ruleSetDefinitions[$name]->getName())); + $output->writeln(''); + + $output->writeln($this->replaceRstLinks($ruleSetDefinitions[$name]->getDescription())); + $output->writeln(''); + + if ($ruleSetDefinitions[$name]->isRisky()) { + $output->writeln('This set contains risky rules.'); + $output->writeln(''); + } + + $help = ''; + + foreach ($ruleSetDefinitions[$name]->getRules() as $rule => $config) { + if (str_starts_with($rule, '@')) { + $set = $ruleSetDefinitions[$rule]; + $help .= sprintf( + " * %s%s\n | %s\n\n", + $rule, + $set->isRisky() ? ' risky' : '', + $this->replaceRstLinks($set->getDescription()) + ); + + continue; + } + + /** @var FixerInterface $fixer */ + $fixer = $fixers[$rule]; + + $definition = $fixer->getDefinition(); + $help .= sprintf( + " * %s%s\n | %s\n%s\n", + $rule, + $fixer->isRisky() ? ' risky' : '', + $definition->getSummary(), + true !== $config ? sprintf(" | Configuration: %s\n", Utils::toString($config)) : '' + ); + } + + $output->write($help); + } + + /** + * @return array + */ + private function getFixers(): array + { + if (null !== $this->fixers) { + return $this->fixers; + } + + $fixers = []; + + foreach ($this->fixerFactory->getFixers() as $fixer) { + $fixers[$fixer->getName()] = $fixer; + } + + $this->fixers = $fixers; + ksort($this->fixers); + + return $this->fixers; + } + + /** + * @return list + */ + private function getSetNames(): array + { + if (null !== $this->setNames) { + return $this->setNames; + } + + $this->setNames = RuleSets::getSetDefinitionNames(); + + return $this->setNames; + } + + /** + * @param string $type 'rule'|'set' + */ + private function describeList(OutputInterface $output, string $type): void + { + if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERY_VERBOSE) { + $describe = [ + 'sets' => $this->getSetNames(), + 'rules' => $this->getFixers(), + ]; + } elseif ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) { + $describe = 'set' === $type ? ['sets' => $this->getSetNames()] : ['rules' => $this->getFixers()]; + } else { + return; + } + + /** @var string[] $items */ + foreach ($describe as $list => $items) { + $output->writeln(sprintf('Defined %s:', $list)); + + foreach ($items as $name => $item) { + $output->writeln(sprintf('* %s', \is_string($name) ? $name : $item)); + } + } + } + + private function replaceRstLinks(string $content): string + { + return Preg::replaceCallback( + '/(`[^<]+<[^>]+>`_)/', + static fn (array $matches) => Preg::replaceCallback( + '/`(.*)<(.*)>`_/', + static fn (array $matches): string => $matches[1].'('.$matches[2].')', + $matches[1] + ), + $content + ); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Command/DescribeNameNotFoundException.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/DescribeNameNotFoundException.php new file mode 100644 index 00000000..27f5bcfc --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/DescribeNameNotFoundException.php @@ -0,0 +1,46 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Command; + +/** + * @internal + */ +final class DescribeNameNotFoundException extends \InvalidArgumentException +{ + private string $name; + + /** + * 'rule'|'set'. + */ + private string $type; + + public function __construct(string $name, string $type) + { + $this->name = $name; + $this->type = $type; + + parent::__construct(); + } + + public function getName(): string + { + return $this->name; + } + + public function getType(): string + { + return $this->type; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Command/DocumentationCommand.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/DocumentationCommand.php new file mode 100644 index 00000000..e1adc987 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/DocumentationCommand.php @@ -0,0 +1,124 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Command; + +use PhpCsFixer\Documentation\DocumentationLocator; +use PhpCsFixer\Documentation\FixerDocumentGenerator; +use PhpCsFixer\Documentation\RuleSetDocumentationGenerator; +use PhpCsFixer\FixerFactory; +use PhpCsFixer\RuleSet\RuleSets; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Finder\Finder; +use Symfony\Component\Finder\SplFileInfo; + +/** + * @internal + */ +#[AsCommand(name: 'documentation')] +final class DocumentationCommand extends Command +{ + /** @var string */ + protected static $defaultName = 'documentation'; + + private Filesystem $filesystem; + + public function __construct(Filesystem $filesystem) + { + parent::__construct(); + $this->filesystem = $filesystem; + } + + protected function configure(): void + { + $this + ->setAliases(['doc']) + ->setDescription('Dumps the documentation of the project into its "/doc" directory.') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $locator = new DocumentationLocator(); + + $fixerFactory = new FixerFactory(); + $fixerFactory->registerBuiltInFixers(); + $fixers = $fixerFactory->getFixers(); + + $setDefinitions = RuleSets::getSetDefinitions(); + + $fixerDocumentGenerator = new FixerDocumentGenerator($locator); + $ruleSetDocumentationGenerator = new RuleSetDocumentationGenerator($locator); + + // Array of existing fixer docs. + // We first override existing files, and then we will delete files that are no longer needed. + // We cannot remove all files first, as generation of docs is re-using existing docs to extract code-samples for + // VersionSpecificCodeSample under incompatible PHP version. + $docForFixerRelativePaths = []; + + foreach ($fixers as $fixer) { + $docForFixerRelativePaths[] = $locator->getFixerDocumentationFileRelativePath($fixer); + $this->filesystem->dumpFile( + $locator->getFixerDocumentationFilePath($fixer), + $fixerDocumentGenerator->generateFixerDocumentation($fixer) + ); + } + + /** @var SplFileInfo $file */ + foreach ( + (new Finder())->files() + ->in($locator->getFixersDocumentationDirectoryPath()) + ->notPath($docForFixerRelativePaths) as $file + ) { + $this->filesystem->remove($file->getPathname()); + } + + // Fixer doc. index + + $this->filesystem->dumpFile( + $locator->getFixersDocumentationIndexFilePath(), + $fixerDocumentGenerator->generateFixersDocumentationIndex($fixers) + ); + + // RuleSet docs. + + /** @var SplFileInfo $file */ + foreach ((new Finder())->files()->in($locator->getRuleSetsDocumentationDirectoryPath()) as $file) { + $this->filesystem->remove($file->getPathname()); + } + + $paths = []; + + foreach ($setDefinitions as $name => $definition) { + $path = $locator->getRuleSetsDocumentationFilePath($name); + $paths[$path] = $definition; + $this->filesystem->dumpFile($path, $ruleSetDocumentationGenerator->generateRuleSetsDocumentation($definition, $fixers)); + } + + // RuleSet doc. index + + $this->filesystem->dumpFile( + $locator->getRuleSetsDocumentationIndexFilePath(), + $ruleSetDocumentationGenerator->generateRuleSetsDocumentationIndex($paths) + ); + + $output->writeln('Docs updated.'); + + return 0; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Command/FixCommand.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/FixCommand.php new file mode 100644 index 00000000..7db27641 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/FixCommand.php @@ -0,0 +1,362 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Command; + +use PhpCsFixer\Config; +use PhpCsFixer\ConfigInterface; +use PhpCsFixer\ConfigurationException\InvalidConfigurationException; +use PhpCsFixer\Console\Application; +use PhpCsFixer\Console\ConfigurationResolver; +use PhpCsFixer\Console\Output\ErrorOutput; +use PhpCsFixer\Console\Output\OutputContext; +use PhpCsFixer\Console\Output\Progress\ProgressOutputFactory; +use PhpCsFixer\Console\Output\Progress\ProgressOutputType; +use PhpCsFixer\Console\Report\FixReport\ReportSummary; +use PhpCsFixer\Error\ErrorsManager; +use PhpCsFixer\FixerFileProcessedEvent; +use PhpCsFixer\Runner\Runner; +use PhpCsFixer\ToolInfoInterface; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Terminal; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Stopwatch\Stopwatch; + +/** + * @author Fabien Potencier + * @author Dariusz Rumiński + * + * @final + * + * @internal + */ +#[AsCommand(name: 'fix', description: 'Fixes a directory or a file.')] +/* final */ class FixCommand extends Command +{ + /** @var string */ + protected static $defaultName = 'fix'; + + /** @var string */ + protected static $defaultDescription = 'Fixes a directory or a file.'; + + private EventDispatcherInterface $eventDispatcher; + + private ErrorsManager $errorsManager; + + private Stopwatch $stopwatch; + + private ConfigInterface $defaultConfig; + + private ToolInfoInterface $toolInfo; + + private ProgressOutputFactory $progressOutputFactory; + + public function __construct(ToolInfoInterface $toolInfo) + { + parent::__construct(); + + $this->eventDispatcher = new EventDispatcher(); + $this->errorsManager = new ErrorsManager(); + $this->stopwatch = new Stopwatch(); + $this->defaultConfig = new Config(); + $this->toolInfo = $toolInfo; + $this->progressOutputFactory = new ProgressOutputFactory(); + } + + /** + * {@inheritdoc} + * + * Override here to only generate the help copy when used. + */ + public function getHelp(): string + { + return <<<'EOF' + The %command.name% command tries to %command.name% as much coding standards + problems as possible on a given file or files in a given directory and its subdirectories: + + $ php %command.full_name% /path/to/dir + $ php %command.full_name% /path/to/file + + By default --path-mode is set to `override`, which means, that if you specify the path to a file or a directory via + command arguments, then the paths provided to a `Finder` in config file will be ignored. You can use --path-mode=intersection + to merge paths from the config file and from the argument: + + $ php %command.full_name% --path-mode=intersection /path/to/dir + + The --format option for the output format. Supported formats are `txt` (default one), `json`, `xml`, `checkstyle`, `junit` and `gitlab`. + + NOTE: the output for the following formats are generated in accordance with schemas + + * `checkstyle` follows the common `"checkstyle" XML schema `_ + * `gitlab` follows the `codeclimate JSON schema `_ + * `json` follows the `own JSON schema `_ + * `junit` follows the `JUnit XML schema from Jenkins `_ + * `xml` follows the `own XML schema `_ + + The --quiet Do not output any message. + + The --verbose option will show the applied rules. When using the `txt` format it will also display progress notifications. + + NOTE: if there is an error like "errors reported during linting after fixing", you can use this to be even more verbose for debugging purpose + + * `-v`: verbose + * `-vv`: very verbose + * `-vvv`: debug + + The --rules option limits the rules to apply to the + project: + + EOF. /* @TODO: 4.0 - change to @PER */ <<<'EOF' + + $ php %command.full_name% /path/to/project --rules=@PSR12 + + By default the PSR-12 rules are used. + + The --rules option lets you choose the exact rules to + apply (the rule names must be separated by a comma): + + $ php %command.full_name% /path/to/dir --rules=line_ending,full_opening_tag,indentation_type + + You can also exclude the rules you don't want by placing a dash in front of the rule name, if this is more convenient, + using -name_of_fixer: + + $ php %command.full_name% /path/to/dir --rules=-full_opening_tag,-indentation_type + + When using combinations of exact and exclude rules, applying exact rules along with above excluded results: + + $ php %command.full_name% /path/to/project --rules=@Symfony,-@PSR1,-blank_line_before_statement,strict_comparison + + Complete configuration for rules can be supplied using a `json` formatted string. + + $ php %command.full_name% /path/to/project --rules='{"concat_space": {"spacing": "none"}}' + + The --dry-run flag will run the fixer without making changes to your files. + + The --diff flag can be used to let the fixer output all the changes it makes. + + The --allow-risky option (pass `yes` or `no`) allows you to set whether risky rules may run. Default value is taken from config file. + A rule is considered risky if it could change code behaviour. By default no risky rules are run. + + The --stop-on-violation flag stops the execution upon first file that needs to be fixed. + + The --show-progress option allows you to choose the way process progress is rendered: + + * none: disables progress output; + * dots: multiline progress output with number of files and percentage on each line. + * bar: single line progress output with number of files and calculated percentage. + + If the option is not provided, it defaults to bar unless a config file that disables output is used, in which case it defaults to none. This option has no effect if the verbosity of the command is less than verbose. + + $ php %command.full_name% --verbose --show-progress=dots + + By using --using-cache option with `yes` or `no` you can set if the caching + mechanism should be used. + + The command can also read from standard input, in which case it won't + automatically fix anything: + + $ cat foo.php | php %command.full_name% --diff - + + Finally, if you don't need BC kept on CLI level, you might use `PHP_CS_FIXER_FUTURE_MODE` to start using options that + would be default in next MAJOR release and to forbid using deprecated configuration: + + $ PHP_CS_FIXER_FUTURE_MODE=1 php %command.full_name% -v --diff + + Exit code + --------- + + Exit code of the `%command.name%` command is built using following bit flags: + + * 0 - OK. + * 1 - General error (or PHP minimal requirement not matched). + * 4 - Some files have invalid syntax (only in dry-run mode). + * 8 - Some files need fixing (only in dry-run mode). + * 16 - Configuration error of the application. + * 32 - Configuration error of a Fixer. + * 64 - Exception raised within the application. + + EOF; + } + + protected function configure(): void + { + $this->setDefinition( + [ + new InputArgument('path', InputArgument::IS_ARRAY, 'The path(s) that rules will be run against (each path can be a file or directory).'), + new InputOption('path-mode', '', InputOption::VALUE_REQUIRED, 'Specify path mode (can be `override` or `intersection`).', ConfigurationResolver::PATH_MODE_OVERRIDE), + new InputOption('allow-risky', '', InputOption::VALUE_REQUIRED, 'Are risky fixers allowed (can be `yes` or `no`).'), + new InputOption('config', '', InputOption::VALUE_REQUIRED, 'The path to a config file.'), + new InputOption('dry-run', '', InputOption::VALUE_NONE, 'Only shows which files would have been modified.'), + new InputOption('rules', '', InputOption::VALUE_REQUIRED, 'List of rules that should be run against configured paths.'), + new InputOption('using-cache', '', InputOption::VALUE_REQUIRED, 'Does cache should be used (can be `yes` or `no`).'), + new InputOption('cache-file', '', InputOption::VALUE_REQUIRED, 'The path to the cache file.'), + new InputOption('diff', '', InputOption::VALUE_NONE, 'Prints diff for each file.'), + new InputOption('format', '', InputOption::VALUE_REQUIRED, 'To output results in other formats.'), + new InputOption('stop-on-violation', '', InputOption::VALUE_NONE, 'Stop execution on first violation.'), + new InputOption('show-progress', '', InputOption::VALUE_REQUIRED, 'Type of progress indicator (none, dots).'), + ] + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $verbosity = $output->getVerbosity(); + + $passedConfig = $input->getOption('config'); + $passedRules = $input->getOption('rules'); + + if (null !== $passedConfig && null !== $passedRules) { + throw new InvalidConfigurationException('Passing both `--config` and `--rules` options is not allowed.'); + } + + $resolver = new ConfigurationResolver( + $this->defaultConfig, + [ + 'allow-risky' => $input->getOption('allow-risky'), + 'config' => $passedConfig, + 'dry-run' => $this->isDryRun($input), + 'rules' => $passedRules, + 'path' => $input->getArgument('path'), + 'path-mode' => $input->getOption('path-mode'), + 'using-cache' => $input->getOption('using-cache'), + 'cache-file' => $input->getOption('cache-file'), + 'format' => $input->getOption('format'), + 'diff' => $input->getOption('diff'), + 'stop-on-violation' => $input->getOption('stop-on-violation'), + 'verbosity' => $verbosity, + 'show-progress' => $input->getOption('show-progress'), + ], + getcwd(), + $this->toolInfo + ); + + $reporter = $resolver->getReporter(); + + $stdErr = $output instanceof ConsoleOutputInterface + ? $output->getErrorOutput() + : ('txt' === $reporter->getFormat() ? $output : null); + + if (null !== $stdErr) { + $stdErr->writeln(Application::getAboutWithRuntime(true)); + + $configFile = $resolver->getConfigFile(); + $stdErr->writeln(sprintf('Loaded config %s%s.', $resolver->getConfig()->getName(), null === $configFile ? '' : ' from "'.$configFile.'"')); + + if ($resolver->getUsingCache()) { + $cacheFile = $resolver->getCacheFile(); + + if (is_file($cacheFile)) { + $stdErr->writeln(sprintf('Using cache file "%s".', $cacheFile)); + } + } + } + + $finder = new \ArrayIterator(iterator_to_array($resolver->getFinder())); + + if (null !== $stdErr && $resolver->configFinderIsOverridden()) { + $stdErr->writeln( + sprintf($stdErr->isDecorated() ? '%s' : '%s', 'Paths from configuration file have been overridden by paths provided as command arguments.') + ); + } + + $progressType = $resolver->getProgressType(); + $progressOutput = $this->progressOutputFactory->create( + $progressType, + new OutputContext( + $stdErr, + (new Terminal())->getWidth(), + \count($finder) + ) + ); + + $runner = new Runner( + $finder, + $resolver->getFixers(), + $resolver->getDiffer(), + ProgressOutputType::NONE !== $progressType ? $this->eventDispatcher : null, + $this->errorsManager, + $resolver->getLinter(), + $resolver->isDryRun(), + $resolver->getCacheManager(), + $resolver->getDirectory(), + $resolver->shouldStopOnViolation() + ); + + $this->eventDispatcher->addListener(FixerFileProcessedEvent::NAME, [$progressOutput, 'onFixerFileProcessed']); + $this->stopwatch->start('fixFiles'); + $changed = $runner->fix(); + $this->stopwatch->stop('fixFiles'); + $this->eventDispatcher->removeListener(FixerFileProcessedEvent::NAME, [$progressOutput, 'onFixerFileProcessed']); + + $progressOutput->printLegend(); + + $fixEvent = $this->stopwatch->getEvent('fixFiles'); + + $reportSummary = new ReportSummary( + $changed, + \count($finder), + $fixEvent->getDuration(), + $fixEvent->getMemory(), + OutputInterface::VERBOSITY_VERBOSE <= $verbosity, + $resolver->isDryRun(), + $output->isDecorated() + ); + + $output->isDecorated() + ? $output->write($reporter->generate($reportSummary)) + : $output->write($reporter->generate($reportSummary), false, OutputInterface::OUTPUT_RAW); + + $invalidErrors = $this->errorsManager->getInvalidErrors(); + $exceptionErrors = $this->errorsManager->getExceptionErrors(); + $lintErrors = $this->errorsManager->getLintErrors(); + + if (null !== $stdErr) { + $errorOutput = new ErrorOutput($stdErr); + + if (\count($invalidErrors) > 0) { + $errorOutput->listErrors('linting before fixing', $invalidErrors); + } + + if (\count($exceptionErrors) > 0) { + $errorOutput->listErrors('fixing', $exceptionErrors); + } + + if (\count($lintErrors) > 0) { + $errorOutput->listErrors('linting after fixing', $lintErrors); + } + } + + $exitStatusCalculator = new FixCommandExitStatusCalculator(); + + return $exitStatusCalculator->calculate( + $resolver->isDryRun(), + \count($changed) > 0, + \count($invalidErrors) > 0, + \count($exceptionErrors) > 0, + \count($lintErrors) > 0 + ); + } + + protected function isDryRun(InputInterface $input): bool + { + return $input->getOption('dry-run'); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Command/FixCommandExitStatusCalculator.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/FixCommandExitStatusCalculator.php new file mode 100644 index 00000000..727dfff5 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/FixCommandExitStatusCalculator.php @@ -0,0 +1,51 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Command; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class FixCommandExitStatusCalculator +{ + // Exit status 1 is reserved for environment constraints not matched. + public const EXIT_STATUS_FLAG_HAS_INVALID_FILES = 4; + public const EXIT_STATUS_FLAG_HAS_CHANGED_FILES = 8; + public const EXIT_STATUS_FLAG_HAS_INVALID_CONFIG = 16; + public const EXIT_STATUS_FLAG_HAS_INVALID_FIXER_CONFIG = 32; + public const EXIT_STATUS_FLAG_EXCEPTION_IN_APP = 64; + + public function calculate(bool $isDryRun, bool $hasChangedFiles, bool $hasInvalidErrors, bool $hasExceptionErrors, bool $hasLintErrorsAfterFixing): int + { + $exitStatus = 0; + + if ($isDryRun) { + if ($hasChangedFiles) { + $exitStatus |= self::EXIT_STATUS_FLAG_HAS_CHANGED_FILES; + } + + if ($hasInvalidErrors) { + $exitStatus |= self::EXIT_STATUS_FLAG_HAS_INVALID_FILES; + } + } + + if ($hasExceptionErrors || $hasLintErrorsAfterFixing) { + $exitStatus |= self::EXIT_STATUS_FLAG_EXCEPTION_IN_APP; + } + + return $exitStatus; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Command/HelpCommand.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/HelpCommand.php new file mode 100644 index 00000000..192c735d --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/HelpCommand.php @@ -0,0 +1,77 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Command; + +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerOptionInterface; +use PhpCsFixer\Utils; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\HelpCommand as BaseHelpCommand; +use Symfony\Component\Console\Formatter\OutputFormatterStyle; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Fabien Potencier + * @author Dariusz Rumiński + * + * @internal + */ +#[AsCommand(name: 'help')] +final class HelpCommand extends BaseHelpCommand +{ + /** @var string */ + protected static $defaultName = 'help'; + + /** + * Returns the allowed values of the given option that can be converted to a string. + * + * @return null|list + */ + public static function getDisplayableAllowedValues(FixerOptionInterface $option): ?array + { + $allowed = $option->getAllowedValues(); + + if (null !== $allowed) { + $allowed = array_filter($allowed, static fn ($value): bool => !$value instanceof \Closure); + + usort($allowed, static function ($valueA, $valueB): int { + if ($valueA instanceof AllowedValueSubset) { + return -1; + } + + if ($valueB instanceof AllowedValueSubset) { + return 1; + } + + return strcasecmp( + Utils::toString($valueA), + Utils::toString($valueB) + ); + }); + + if (0 === \count($allowed)) { + $allowed = null; + } + } + + return $allowed; + } + + protected function initialize(InputInterface $input, OutputInterface $output): void + { + $output->getFormatter()->setStyle('url', new OutputFormatterStyle('blue')); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Command/ListFilesCommand.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/ListFilesCommand.php new file mode 100644 index 00000000..dcfabd09 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/ListFilesCommand.php @@ -0,0 +1,92 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Command; + +use PhpCsFixer\Config; +use PhpCsFixer\ConfigInterface; +use PhpCsFixer\Console\ConfigurationResolver; +use PhpCsFixer\ToolInfoInterface; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Filesystem\Path; + +/** + * @author Markus Staab + * + * @internal + */ +#[AsCommand(name: 'list-files')] +final class ListFilesCommand extends Command +{ + /** @var string */ + protected static $defaultName = 'list-files'; + + private ConfigInterface $defaultConfig; + + private ToolInfoInterface $toolInfo; + + public function __construct(ToolInfoInterface $toolInfo) + { + parent::__construct(); + + $this->defaultConfig = new Config(); + $this->toolInfo = $toolInfo; + } + + protected function configure(): void + { + $this + ->setDefinition( + [ + new InputOption('config', '', InputOption::VALUE_REQUIRED, 'The path to a .php-cs-fixer.php file.'), + ] + ) + ->setDescription('List all files being fixed by the given config.') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $passedConfig = $input->getOption('config'); + $cwd = getcwd(); + + $resolver = new ConfigurationResolver( + $this->defaultConfig, + [ + 'config' => $passedConfig, + ], + $cwd, + $this->toolInfo + ); + + $finder = $resolver->getFinder(); + + /** @var \SplFileInfo $file */ + foreach ($finder as $file) { + if ($file->isFile()) { + $relativePath = './'.Path::makeRelative($file->getRealPath(), $cwd); + // unify directory separators across operating system + $relativePath = str_replace('/', \DIRECTORY_SEPARATOR, $relativePath); + + $output->writeln(escapeshellarg($relativePath)); + } + } + + return 0; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Command/ListSetsCommand.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/ListSetsCommand.php new file mode 100644 index 00000000..c1fe5f79 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/ListSetsCommand.php @@ -0,0 +1,88 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Command; + +use PhpCsFixer\ConfigurationException\InvalidConfigurationException; +use PhpCsFixer\Console\Report\ListSetsReport\ReporterFactory; +use PhpCsFixer\Console\Report\ListSetsReport\ReporterInterface; +use PhpCsFixer\Console\Report\ListSetsReport\ReportSummary; +use PhpCsFixer\Console\Report\ListSetsReport\TextReporter; +use PhpCsFixer\RuleSet\RuleSets; +use PhpCsFixer\Utils; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +#[AsCommand(name: 'list-sets')] +final class ListSetsCommand extends Command +{ + /** @var string */ + protected static $defaultName = 'list-sets'; + + protected function configure(): void + { + $this + ->setDefinition( + [ + new InputOption('format', '', InputOption::VALUE_REQUIRED, 'To output results in other formats.', (new TextReporter())->getFormat()), + ] + ) + ->setDescription('List all available RuleSets.') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $reporter = $this->resolveReporterWithFactory( + $input->getOption('format'), + new ReporterFactory() + ); + + $reportSummary = new ReportSummary( + array_values(RuleSets::getSetDefinitions()) + ); + + $report = $reporter->generate($reportSummary); + + $output->isDecorated() + ? $output->write(OutputFormatter::escape($report)) + : $output->write($report, false, OutputInterface::OUTPUT_RAW); + + return 0; + } + + private function resolveReporterWithFactory(string $format, ReporterFactory $factory): ReporterInterface + { + try { + $factory->registerBuiltInReporters(); + $reporter = $factory->getReporter($format); + } catch (\UnexpectedValueException $e) { + $formats = $factory->getFormats(); + sort($formats); + + throw new InvalidConfigurationException(sprintf('The format "%s" is not defined, supported are %s.', $format, Utils::naturalLanguageJoin($formats))); + } + + return $reporter; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Command/SelfUpdateCommand.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/SelfUpdateCommand.php new file mode 100644 index 00000000..2be3adbc --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/SelfUpdateCommand.php @@ -0,0 +1,177 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Command; + +use PhpCsFixer\Console\Application; +use PhpCsFixer\Console\SelfUpdate\NewVersionCheckerInterface; +use PhpCsFixer\PharCheckerInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\ToolInfoInterface; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Igor Wiedler + * @author Stephane PY + * @author Grégoire Pineau + * @author Dariusz Rumiński + * + * @internal + */ +#[AsCommand(name: 'self-update')] +final class SelfUpdateCommand extends Command +{ + /** @var string */ + protected static $defaultName = 'self-update'; + + private NewVersionCheckerInterface $versionChecker; + + private ToolInfoInterface $toolInfo; + + private PharCheckerInterface $pharChecker; + + public function __construct( + NewVersionCheckerInterface $versionChecker, + ToolInfoInterface $toolInfo, + PharCheckerInterface $pharChecker + ) { + parent::__construct(); + + $this->versionChecker = $versionChecker; + $this->toolInfo = $toolInfo; + $this->pharChecker = $pharChecker; + } + + protected function configure(): void + { + $this + ->setAliases(['selfupdate']) + ->setDefinition( + [ + new InputOption('--force', '-f', InputOption::VALUE_NONE, 'Force update to next major version if available.'), + ] + ) + ->setDescription('Update php-cs-fixer.phar to the latest stable version.') + ->setHelp( + <<<'EOT' + The %command.name% command replace your php-cs-fixer.phar by the + latest version released on: + https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/releases + + $ php php-cs-fixer.phar %command.name% + + EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + if ($output instanceof ConsoleOutputInterface) { + $stdErr = $output->getErrorOutput(); + $stdErr->writeln(Application::getAboutWithRuntime(true)); + } + + if (!$this->toolInfo->isInstalledAsPhar()) { + $output->writeln('Self-update is available only for PHAR version.'); + + return 1; + } + + $currentVersion = $this->getApplication()->getVersion(); + Preg::match('/^v?(?\d+)\./', $currentVersion, $matches); + $currentMajor = (int) $matches['major']; + + try { + $latestVersion = $this->versionChecker->getLatestVersion(); + $latestVersionOfCurrentMajor = $this->versionChecker->getLatestVersionOfMajor($currentMajor); + } catch (\Exception $exception) { + $output->writeln(sprintf( + 'Unable to determine newest version: %s', + $exception->getMessage() + )); + + return 1; + } + + if (1 !== $this->versionChecker->compareVersions($latestVersion, $currentVersion)) { + $output->writeln('PHP CS Fixer is already up-to-date.'); + + return 0; + } + + $remoteTag = $latestVersion; + + if ( + 0 !== $this->versionChecker->compareVersions($latestVersionOfCurrentMajor, $latestVersion) + && true !== $input->getOption('force') + ) { + $output->writeln(sprintf('A new major version of PHP CS Fixer is available (%s)', $latestVersion)); + $output->writeln(sprintf('Before upgrading please read https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/%s/UPGRADE-v%s.md', $latestVersion, $currentMajor + 1)); + $output->writeln('If you are ready to upgrade run this command with -f'); + $output->writeln('Checking for new minor/patch version...'); + + if (1 !== $this->versionChecker->compareVersions($latestVersionOfCurrentMajor, $currentVersion)) { + $output->writeln('No minor update for PHP CS Fixer.'); + + return 0; + } + + $remoteTag = $latestVersionOfCurrentMajor; + } + + $localFilename = $_SERVER['argv'][0]; + $realPath = realpath($localFilename); + if (false !== $realPath) { + $localFilename = $realPath; + } + + if (!is_writable($localFilename)) { + $output->writeln(sprintf('No permission to update "%s" file.', $localFilename)); + + return 1; + } + + $tempFilename = \dirname($localFilename).'/'.basename($localFilename, '.phar').'-tmp.phar'; + $remoteFilename = $this->toolInfo->getPharDownloadUri($remoteTag); + + if (false === @copy($remoteFilename, $tempFilename)) { + $output->writeln(sprintf('Unable to download new version %s from the server.', $remoteTag)); + + return 1; + } + + chmod($tempFilename, 0777 & ~umask()); + + $pharInvalidityReason = $this->pharChecker->checkFileValidity($tempFilename); + if (null !== $pharInvalidityReason) { + unlink($tempFilename); + $output->writeln(sprintf('The download of %s is corrupt (%s).', $remoteTag, $pharInvalidityReason)); + $output->writeln('Please re-run the "self-update" command to try again.'); + + return 1; + } + + rename($tempFilename, $localFilename); + + $output->writeln(sprintf('PHP CS Fixer updated (%s -> %s)', $currentVersion, $remoteTag)); + + return 0; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/ConfigurationResolver.php b/vendor/friendsofphp/php-cs-fixer/src/Console/ConfigurationResolver.php new file mode 100644 index 00000000..5cb86a53 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/ConfigurationResolver.php @@ -0,0 +1,959 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console; + +use PhpCsFixer\Cache\CacheManagerInterface; +use PhpCsFixer\Cache\Directory; +use PhpCsFixer\Cache\DirectoryInterface; +use PhpCsFixer\Cache\FileCacheManager; +use PhpCsFixer\Cache\FileHandler; +use PhpCsFixer\Cache\NullCacheManager; +use PhpCsFixer\Cache\Signature; +use PhpCsFixer\ConfigInterface; +use PhpCsFixer\ConfigurationException\InvalidConfigurationException; +use PhpCsFixer\Console\Output\Progress\ProgressOutputType; +use PhpCsFixer\Console\Report\FixReport\ReporterFactory; +use PhpCsFixer\Console\Report\FixReport\ReporterInterface; +use PhpCsFixer\Differ\DifferInterface; +use PhpCsFixer\Differ\NullDiffer; +use PhpCsFixer\Differ\UnifiedDiffer; +use PhpCsFixer\Finder; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; +use PhpCsFixer\Fixer\FixerInterface; +use PhpCsFixer\FixerFactory; +use PhpCsFixer\Linter\Linter; +use PhpCsFixer\Linter\LinterInterface; +use PhpCsFixer\RuleSet\RuleSet; +use PhpCsFixer\RuleSet\RuleSetInterface; +use PhpCsFixer\StdinFileInfo; +use PhpCsFixer\ToolInfoInterface; +use PhpCsFixer\Utils; +use PhpCsFixer\WhitespacesFixerConfig; +use PhpCsFixer\WordMatcher; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Finder\Finder as SymfonyFinder; + +/** + * The resolver that resolves configuration to use by command line options and config. + * + * @author Fabien Potencier + * @author Katsuhiro Ogawa + * @author Dariusz Rumiński + * + * @internal + */ +final class ConfigurationResolver +{ + public const PATH_MODE_OVERRIDE = 'override'; + public const PATH_MODE_INTERSECTION = 'intersection'; + + /** + * @var null|bool + */ + private $allowRisky; + + /** + * @var null|ConfigInterface + */ + private $config; + + /** + * @var null|string + */ + private $configFile; + + private string $cwd; + + private ConfigInterface $defaultConfig; + + /** + * @var null|ReporterInterface + */ + private $reporter; + + /** + * @var null|bool + */ + private $isStdIn; + + /** + * @var null|bool + */ + private $isDryRun; + + /** + * @var null|FixerInterface[] + */ + private $fixers; + + /** + * @var null|bool + */ + private $configFinderIsOverridden; + + private ToolInfoInterface $toolInfo; + + /** + * @var array + */ + private array $options = [ + 'allow-risky' => null, + 'cache-file' => null, + 'config' => null, + 'diff' => null, + 'dry-run' => null, + 'format' => null, + 'path' => [], + 'path-mode' => self::PATH_MODE_OVERRIDE, + 'rules' => null, + 'show-progress' => null, + 'stop-on-violation' => null, + 'using-cache' => null, + 'verbosity' => null, + ]; + + /** + * @var null|string + */ + private $cacheFile; + + /** + * @var null|CacheManagerInterface + */ + private $cacheManager; + + /** + * @var null|DifferInterface + */ + private $differ; + + /** + * @var null|Directory + */ + private $directory; + + /** + * @var null|iterable<\SplFileInfo> + */ + private ?iterable $finder = null; + + private ?string $format = null; + + /** + * @var null|Linter + */ + private $linter; + + /** + * @var null|list + */ + private ?array $path = null; + + /** + * @var null|string + */ + private $progress; + + /** + * @var null|RuleSet + */ + private $ruleSet; + + /** + * @var null|bool + */ + private $usingCache; + + /** + * @var FixerFactory + */ + private $fixerFactory; + + /** + * @param array $options + */ + public function __construct( + ConfigInterface $config, + array $options, + string $cwd, + ToolInfoInterface $toolInfo + ) { + $this->defaultConfig = $config; + $this->cwd = $cwd; + $this->toolInfo = $toolInfo; + + foreach ($options as $name => $value) { + $this->setOption($name, $value); + } + } + + public function getCacheFile(): ?string + { + if (!$this->getUsingCache()) { + return null; + } + + if (null === $this->cacheFile) { + if (null === $this->options['cache-file']) { + $this->cacheFile = $this->getConfig()->getCacheFile(); + } else { + $this->cacheFile = $this->options['cache-file']; + } + } + + return $this->cacheFile; + } + + public function getCacheManager(): CacheManagerInterface + { + if (null === $this->cacheManager) { + $cacheFile = $this->getCacheFile(); + + if (null === $cacheFile) { + $this->cacheManager = new NullCacheManager(); + } else { + $this->cacheManager = new FileCacheManager( + new FileHandler($cacheFile), + new Signature( + PHP_VERSION, + $this->toolInfo->getVersion(), + $this->getConfig()->getIndent(), + $this->getConfig()->getLineEnding(), + $this->getRules() + ), + $this->isDryRun(), + $this->getDirectory() + ); + } + } + + return $this->cacheManager; + } + + public function getConfig(): ConfigInterface + { + if (null === $this->config) { + foreach ($this->computeConfigFiles() as $configFile) { + if (!file_exists($configFile)) { + continue; + } + + $configFileBasename = basename($configFile); + $deprecatedConfigs = [ + '.php_cs' => '.php-cs-fixer.php', + '.php_cs.dist' => '.php-cs-fixer.dist.php', + ]; + + if (isset($deprecatedConfigs[$configFileBasename])) { + throw new InvalidConfigurationException("Configuration file `{$configFileBasename}` is outdated, rename to `{$deprecatedConfigs[$configFileBasename]}`."); + } + + $this->config = self::separatedContextLessInclude($configFile); + $this->configFile = $configFile; + + break; + } + + if (null === $this->config) { + $this->config = $this->defaultConfig; + } + } + + return $this->config; + } + + public function getConfigFile(): ?string + { + if (null === $this->configFile) { + $this->getConfig(); + } + + return $this->configFile; + } + + public function getDiffer(): DifferInterface + { + if (null === $this->differ) { + $this->differ = (true === $this->options['diff']) ? new UnifiedDiffer() : new NullDiffer(); + } + + return $this->differ; + } + + public function getDirectory(): DirectoryInterface + { + if (null === $this->directory) { + $path = $this->getCacheFile(); + if (null === $path) { + $absolutePath = $this->cwd; + } else { + $filesystem = new Filesystem(); + + $absolutePath = $filesystem->isAbsolutePath($path) + ? $path + : $this->cwd.\DIRECTORY_SEPARATOR.$path; + $absolutePath = \dirname($absolutePath); + } + + $this->directory = new Directory($absolutePath); + } + + return $this->directory; + } + + /** + * @return FixerInterface[] An array of FixerInterface + */ + public function getFixers(): array + { + if (null === $this->fixers) { + $this->fixers = $this->createFixerFactory() + ->useRuleSet($this->getRuleSet()) + ->setWhitespacesConfig(new WhitespacesFixerConfig($this->config->getIndent(), $this->config->getLineEnding())) + ->getFixers() + ; + + if (false === $this->getRiskyAllowed()) { + $riskyFixers = array_map( + static fn (FixerInterface $fixer): string => $fixer->getName(), + array_filter( + $this->fixers, + static fn (FixerInterface $fixer): bool => $fixer->isRisky() + ) + ); + + if (\count($riskyFixers) > 0) { + throw new InvalidConfigurationException(sprintf('The rules contain risky fixers (%s), but they are not allowed to run. Perhaps you forget to use --allow-risky=yes option?', Utils::naturalLanguageJoin($riskyFixers))); + } + } + } + + return $this->fixers; + } + + public function getLinter(): LinterInterface + { + if (null === $this->linter) { + $this->linter = new Linter(); + } + + return $this->linter; + } + + /** + * Returns path. + * + * @return string[] + */ + public function getPath(): array + { + if (null === $this->path) { + $filesystem = new Filesystem(); + $cwd = $this->cwd; + + if (1 === \count($this->options['path']) && '-' === $this->options['path'][0]) { + $this->path = $this->options['path']; + } else { + $this->path = array_map( + static function (string $rawPath) use ($cwd, $filesystem): string { + $path = trim($rawPath); + + if ('' === $path) { + throw new InvalidConfigurationException("Invalid path: \"{$rawPath}\"."); + } + + $absolutePath = $filesystem->isAbsolutePath($path) + ? $path + : $cwd.\DIRECTORY_SEPARATOR.$path; + + if (!file_exists($absolutePath)) { + throw new InvalidConfigurationException(sprintf( + 'The path "%s" is not readable.', + $path + )); + } + + return $absolutePath; + }, + $this->options['path'] + ); + } + } + + return $this->path; + } + + /** + * @throws InvalidConfigurationException + */ + public function getProgressType(): string + { + if (null === $this->progress) { + if ('txt' === $this->getFormat()) { + $progressType = $this->options['show-progress']; + + if (null === $progressType) { + $progressType = $this->getConfig()->getHideProgress() + ? ProgressOutputType::NONE + : ProgressOutputType::BAR; + } elseif (!\in_array($progressType, ProgressOutputType::all(), true)) { + throw new InvalidConfigurationException(sprintf( + 'The progress type "%s" is not defined, supported are %s.', + $progressType, + Utils::naturalLanguageJoin(ProgressOutputType::all()) + )); + } + + $this->progress = $progressType; + } else { + $this->progress = ProgressOutputType::NONE; + } + } + + return $this->progress; + } + + public function getReporter(): ReporterInterface + { + if (null === $this->reporter) { + $reporterFactory = new ReporterFactory(); + $reporterFactory->registerBuiltInReporters(); + + $format = $this->getFormat(); + + try { + $this->reporter = $reporterFactory->getReporter($format); + } catch (\UnexpectedValueException $e) { + $formats = $reporterFactory->getFormats(); + sort($formats); + + throw new InvalidConfigurationException(sprintf('The format "%s" is not defined, supported are %s.', $format, Utils::naturalLanguageJoin($formats))); + } + } + + return $this->reporter; + } + + public function getRiskyAllowed(): bool + { + if (null === $this->allowRisky) { + if (null === $this->options['allow-risky']) { + $this->allowRisky = $this->getConfig()->getRiskyAllowed(); + } else { + $this->allowRisky = $this->resolveOptionBooleanValue('allow-risky'); + } + } + + return $this->allowRisky; + } + + /** + * Returns rules. + * + * @return array|bool> + */ + public function getRules(): array + { + return $this->getRuleSet()->getRules(); + } + + public function getUsingCache(): bool + { + if (null === $this->usingCache) { + if (null === $this->options['using-cache']) { + $this->usingCache = $this->getConfig()->getUsingCache(); + } else { + $this->usingCache = $this->resolveOptionBooleanValue('using-cache'); + } + } + + $this->usingCache = $this->usingCache && $this->isCachingAllowedForRuntime(); + + return $this->usingCache; + } + + /** + * @return iterable<\SplFileInfo> + */ + public function getFinder(): iterable + { + if (null === $this->finder) { + $this->finder = $this->resolveFinder(); + } + + return $this->finder; + } + + /** + * Returns dry-run flag. + */ + public function isDryRun(): bool + { + if (null === $this->isDryRun) { + if ($this->isStdIn()) { + // Can't write to STDIN + $this->isDryRun = true; + } else { + $this->isDryRun = $this->options['dry-run']; + } + } + + return $this->isDryRun; + } + + public function shouldStopOnViolation(): bool + { + return $this->options['stop-on-violation']; + } + + public function configFinderIsOverridden(): bool + { + if (null === $this->configFinderIsOverridden) { + $this->resolveFinder(); + } + + return $this->configFinderIsOverridden; + } + + /** + * Compute file candidates for config file. + * + * @return list + */ + private function computeConfigFiles(): array + { + $configFile = $this->options['config']; + + if (null !== $configFile) { + if (false === file_exists($configFile) || false === is_readable($configFile)) { + throw new InvalidConfigurationException(sprintf('Cannot read config file "%s".', $configFile)); + } + + return [$configFile]; + } + + $path = $this->getPath(); + + if ($this->isStdIn() || 0 === \count($path)) { + $configDir = $this->cwd; + } elseif (1 < \count($path)) { + throw new InvalidConfigurationException('For multiple paths config parameter is required.'); + } elseif (!is_file($path[0])) { + $configDir = $path[0]; + } else { + $dirName = pathinfo($path[0], PATHINFO_DIRNAME); + $configDir = $dirName ?: $path[0]; + } + + $candidates = [ + $configDir.\DIRECTORY_SEPARATOR.'.php-cs-fixer.php', + $configDir.\DIRECTORY_SEPARATOR.'.php-cs-fixer.dist.php', + $configDir.\DIRECTORY_SEPARATOR.'.php_cs', // old v2 config, present here only to throw nice error message later + $configDir.\DIRECTORY_SEPARATOR.'.php_cs.dist', // old v2 config, present here only to throw nice error message later + ]; + + if ($configDir !== $this->cwd) { + $candidates[] = $this->cwd.\DIRECTORY_SEPARATOR.'.php-cs-fixer.php'; + $candidates[] = $this->cwd.\DIRECTORY_SEPARATOR.'.php-cs-fixer.dist.php'; + $candidates[] = $this->cwd.\DIRECTORY_SEPARATOR.'.php_cs'; // old v2 config, present here only to throw nice error message later + $candidates[] = $this->cwd.\DIRECTORY_SEPARATOR.'.php_cs.dist'; // old v2 config, present here only to throw nice error message later + } + + return $candidates; + } + + private function createFixerFactory(): FixerFactory + { + if (null === $this->fixerFactory) { + $fixerFactory = new FixerFactory(); + $fixerFactory->registerBuiltInFixers(); + $fixerFactory->registerCustomFixers($this->getConfig()->getCustomFixers()); + + $this->fixerFactory = $fixerFactory; + } + + return $this->fixerFactory; + } + + private function getFormat(): string + { + if (null === $this->format) { + $this->format = $this->options['format'] ?? $this->getConfig()->getFormat(); + } + + return $this->format; + } + + private function getRuleSet(): RuleSetInterface + { + if (null === $this->ruleSet) { + $rules = $this->parseRules(); + $this->validateRules($rules); + + $this->ruleSet = new RuleSet($rules); + } + + return $this->ruleSet; + } + + private function isStdIn(): bool + { + if (null === $this->isStdIn) { + $this->isStdIn = 1 === \count($this->options['path']) && '-' === $this->options['path'][0]; + } + + return $this->isStdIn; + } + + /** + * @template T + * + * @param iterable $iterable + * + * @return \Traversable + */ + private function iterableToTraversable(iterable $iterable): \Traversable + { + return \is_array($iterable) ? new \ArrayIterator($iterable) : $iterable; + } + + /** + * @return array + */ + private function parseRules(): array + { + if (null === $this->options['rules']) { + return $this->getConfig()->getRules(); + } + + $rules = trim($this->options['rules']); + if ('' === $rules) { + throw new InvalidConfigurationException('Empty rules value is not allowed.'); + } + + if (str_starts_with($rules, '{')) { + $rules = json_decode($rules, true); + + if (JSON_ERROR_NONE !== json_last_error()) { + throw new InvalidConfigurationException(sprintf('Invalid JSON rules input: "%s".', json_last_error_msg())); + } + + return $rules; + } + + $rules = []; + + foreach (explode(',', $this->options['rules']) as $rule) { + $rule = trim($rule); + + if ('' === $rule) { + throw new InvalidConfigurationException('Empty rule name is not allowed.'); + } + + if (str_starts_with($rule, '-')) { + $rules[substr($rule, 1)] = false; + } else { + $rules[$rule] = true; + } + } + + return $rules; + } + + /** + * @param array $rules + * + * @throws InvalidConfigurationException + */ + private function validateRules(array $rules): void + { + /** + * Create a ruleset that contains all configured rules, even when they originally have been disabled. + * + * @see RuleSet::resolveSet() + */ + $ruleSet = []; + + foreach ($rules as $key => $value) { + if (\is_int($key)) { + throw new InvalidConfigurationException(sprintf('Missing value for "%s" rule/set.', $value)); + } + + $ruleSet[$key] = true; + } + + $ruleSet = new RuleSet($ruleSet); + + $configuredFixers = array_keys($ruleSet->getRules()); + + $fixers = $this->createFixerFactory()->getFixers(); + + $availableFixers = array_map(static fn (FixerInterface $fixer): string => $fixer->getName(), $fixers); + + $unknownFixers = array_diff($configuredFixers, $availableFixers); + + if (\count($unknownFixers) > 0) { + $renamedRules = [ + 'blank_line_before_return' => [ + 'new_name' => 'blank_line_before_statement', + 'config' => ['statements' => ['return']], + ], + 'final_static_access' => [ + 'new_name' => 'self_static_accessor', + ], + 'hash_to_slash_comment' => [ + 'new_name' => 'single_line_comment_style', + 'config' => ['comment_types' => ['hash']], + ], + 'lowercase_constants' => [ + 'new_name' => 'constant_case', + 'config' => ['case' => 'lower'], + ], + 'no_extra_consecutive_blank_lines' => [ + 'new_name' => 'no_extra_blank_lines', + ], + 'no_multiline_whitespace_before_semicolons' => [ + 'new_name' => 'multiline_whitespace_before_semicolons', + ], + 'no_short_echo_tag' => [ + 'new_name' => 'echo_tag_syntax', + 'config' => ['format' => 'long'], + ], + 'php_unit_ordered_covers' => [ + 'new_name' => 'phpdoc_order_by_value', + 'config' => ['annotations' => ['covers']], + ], + 'phpdoc_inline_tag' => [ + 'new_name' => 'general_phpdoc_tag_rename, phpdoc_inline_tag_normalizer and phpdoc_tag_type', + ], + 'pre_increment' => [ + 'new_name' => 'increment_style', + 'config' => ['style' => 'pre'], + ], + 'psr0' => [ + 'new_name' => 'psr_autoloading', + 'config' => ['dir' => 'x'], + ], + 'psr4' => [ + 'new_name' => 'psr_autoloading', + ], + 'silenced_deprecation_error' => [ + 'new_name' => 'error_suppression', + ], + 'trailing_comma_in_multiline_array' => [ + 'new_name' => 'trailing_comma_in_multiline', + 'config' => ['elements' => ['arrays']], + ], + ]; + + $message = 'The rules contain unknown fixers: '; + $hasOldRule = false; + + foreach ($unknownFixers as $unknownFixer) { + if (isset($renamedRules[$unknownFixer])) { // Check if present as old renamed rule + $hasOldRule = true; + $message .= sprintf( + '"%s" is renamed (did you mean "%s"?%s), ', + $unknownFixer, + $renamedRules[$unknownFixer]['new_name'], + isset($renamedRules[$unknownFixer]['config']) ? ' (note: use configuration "'.Utils::toString($renamedRules[$unknownFixer]['config']).'")' : '' + ); + } else { // Go to normal matcher if it is not a renamed rule + $matcher = new WordMatcher($availableFixers); + $alternative = $matcher->match($unknownFixer); + $message .= sprintf( + '"%s"%s, ', + $unknownFixer, + null === $alternative ? '' : ' (did you mean "'.$alternative.'"?)' + ); + } + } + + $message = substr($message, 0, -2).'.'; + + if ($hasOldRule) { + $message .= "\nFor more info about updating see: https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/v3.0.0/UPGRADE-v3.md#renamed-ruless."; + } + + throw new InvalidConfigurationException($message); + } + + foreach ($fixers as $fixer) { + $fixerName = $fixer->getName(); + if (isset($rules[$fixerName]) && $fixer instanceof DeprecatedFixerInterface) { + $successors = $fixer->getSuccessorsNames(); + $messageEnd = [] === $successors + ? sprintf(' and will be removed in version %d.0.', Application::getMajorVersion() + 1) + : sprintf('. Use %s instead.', str_replace('`', '"', Utils::naturalLanguageJoinWithBackticks($successors))); + + Utils::triggerDeprecation(new \RuntimeException("Rule \"{$fixerName}\" is deprecated{$messageEnd}")); + } + } + } + + /** + * Apply path on config instance. + * + * @return iterable<\SplFileInfo> + */ + private function resolveFinder(): iterable + { + $this->configFinderIsOverridden = false; + + if ($this->isStdIn()) { + return new \ArrayIterator([new StdinFileInfo()]); + } + + $modes = [self::PATH_MODE_OVERRIDE, self::PATH_MODE_INTERSECTION]; + + if (!\in_array( + $this->options['path-mode'], + $modes, + true + )) { + throw new InvalidConfigurationException(sprintf( + 'The path-mode "%s" is not defined, supported are %s.', + $this->options['path-mode'], + Utils::naturalLanguageJoin($modes) + )); + } + + $isIntersectionPathMode = self::PATH_MODE_INTERSECTION === $this->options['path-mode']; + + $paths = array_filter(array_map( + static fn (string $path) => realpath($path), + $this->getPath() + )); + + if (0 === \count($paths)) { + if ($isIntersectionPathMode) { + return new \ArrayIterator([]); + } + + return $this->iterableToTraversable($this->getConfig()->getFinder()); + } + + $pathsByType = [ + 'file' => [], + 'dir' => [], + ]; + + foreach ($paths as $path) { + if (is_file($path)) { + $pathsByType['file'][] = $path; + } else { + $pathsByType['dir'][] = $path.\DIRECTORY_SEPARATOR; + } + } + + $nestedFinder = null; + $currentFinder = $this->iterableToTraversable($this->getConfig()->getFinder()); + + try { + $nestedFinder = $currentFinder instanceof \IteratorAggregate ? $currentFinder->getIterator() : $currentFinder; + } catch (\Exception $e) { + } + + if ($isIntersectionPathMode) { + if (null === $nestedFinder) { + throw new InvalidConfigurationException( + 'Cannot create intersection with not-fully defined Finder in configuration file.' + ); + } + + return new \CallbackFilterIterator( + new \IteratorIterator($nestedFinder), + static function (\SplFileInfo $current) use ($pathsByType): bool { + $currentRealPath = $current->getRealPath(); + + if (\in_array($currentRealPath, $pathsByType['file'], true)) { + return true; + } + + foreach ($pathsByType['dir'] as $path) { + if (str_starts_with($currentRealPath, $path)) { + return true; + } + } + + return false; + } + ); + } + + if (null !== $this->getConfigFile() && null !== $nestedFinder) { + $this->configFinderIsOverridden = true; + } + + if ($currentFinder instanceof SymfonyFinder && null === $nestedFinder) { + // finder from configuration Symfony finder and it is not fully defined, we may fulfill it + return $currentFinder->in($pathsByType['dir'])->append($pathsByType['file']); + } + + return Finder::create()->in($pathsByType['dir'])->append($pathsByType['file']); + } + + /** + * Set option that will be resolved. + * + * @param mixed $value + */ + private function setOption(string $name, $value): void + { + if (!\array_key_exists($name, $this->options)) { + throw new InvalidConfigurationException(sprintf('Unknown option name: "%s".', $name)); + } + + $this->options[$name] = $value; + } + + private function resolveOptionBooleanValue(string $optionName): bool + { + $value = $this->options[$optionName]; + + if (!\is_string($value)) { + throw new InvalidConfigurationException(sprintf('Expected boolean or string value for option "%s".', $optionName)); + } + + if ('yes' === $value) { + return true; + } + + if ('no' === $value) { + return false; + } + + throw new InvalidConfigurationException(sprintf('Expected "yes" or "no" for option "%s", got "%s".', $optionName, $value)); + } + + private static function separatedContextLessInclude(string $path): ConfigInterface + { + $config = include $path; + + // verify that the config has an instance of Config + if (!$config instanceof ConfigInterface) { + throw new InvalidConfigurationException(sprintf('The config file: "%s" does not return a "PhpCsFixer\ConfigInterface" instance. Got: "%s".', $path, \is_object($config) ? \get_class($config) : \gettype($config))); + } + + return $config; + } + + private function isCachingAllowedForRuntime(): bool + { + return $this->toolInfo->isInstalledAsPhar() + || $this->toolInfo->isInstalledByComposer() + || $this->toolInfo->isRunInsideDocker(); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Output/ErrorOutput.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Output/ErrorOutput.php new file mode 100644 index 00000000..65150021 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Output/ErrorOutput.php @@ -0,0 +1,156 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Output; + +use PhpCsFixer\Differ\DiffConsoleFormatter; +use PhpCsFixer\Error\Error; +use PhpCsFixer\Linter\LintingException; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @internal + */ +final class ErrorOutput +{ + private OutputInterface $output; + + /** + * @var bool + */ + private $isDecorated; + + public function __construct(OutputInterface $output) + { + $this->output = $output; + $this->isDecorated = $output->isDecorated(); + } + + /** + * @param Error[] $errors + */ + public function listErrors(string $process, array $errors): void + { + $this->output->writeln(['', sprintf( + 'Files that were not fixed due to errors reported during %s:', + $process + )]); + + $showDetails = $this->output->getVerbosity() >= OutputInterface::VERBOSITY_VERY_VERBOSE; + $showTrace = $this->output->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG; + foreach ($errors as $i => $error) { + $this->output->writeln(sprintf('%4d) %s', $i + 1, $error->getFilePath())); + $e = $error->getSource(); + if (!$showDetails || null === $e) { + continue; + } + + $class = sprintf('[%s]', \get_class($e)); + $message = $e->getMessage(); + $code = $e->getCode(); + if (0 !== $code) { + $message .= " ({$code})"; + } + + $length = max(\strlen($class), \strlen($message)); + $lines = [ + '', + $class, + $message, + '', + ]; + + $this->output->writeln(''); + + foreach ($lines as $line) { + if (\strlen($line) < $length) { + $line .= str_repeat(' ', $length - \strlen($line)); + } + + $this->output->writeln(sprintf(' %s ', $this->prepareOutput($line))); + } + + if ($showTrace && !$e instanceof LintingException) { // stack trace of lint exception is of no interest + $this->output->writeln(''); + $stackTrace = $e->getTrace(); + foreach ($stackTrace as $trace) { + if (isset($trace['class']) && Command::class === $trace['class'] && 'run' === $trace['function']) { + $this->output->writeln(' [ ... ]'); + + break; + } + + $this->outputTrace($trace); + } + } + + if (Error::TYPE_LINT === $error->getType() && 0 < \count($error->getAppliedFixers())) { + $this->output->writeln(''); + $this->output->writeln(sprintf(' Applied fixers: %s', implode(', ', $error->getAppliedFixers()))); + + $diff = $error->getDiff(); + if (null !== $diff) { + $diffFormatter = new DiffConsoleFormatter( + $this->isDecorated, + sprintf( + ' ---------- begin diff ----------%s%%s%s ----------- end diff -----------', + PHP_EOL, + PHP_EOL + ) + ); + + $this->output->writeln($diffFormatter->format($diff)); + } + } + } + } + + /** + * @param array{ + * function?: string, + * line?: int, + * file?: string, + * class?: class-string, + * type?: '::'|'->', + * args?: mixed[], + * object?: object, + * } $trace + */ + private function outputTrace(array $trace): void + { + if (isset($trace['class'], $trace['type'], $trace['function'])) { + $this->output->writeln(sprintf( + ' %s%s%s()', + $this->prepareOutput($trace['class']), + $this->prepareOutput($trace['type']), + $this->prepareOutput($trace['function']) + )); + } elseif (isset($trace['function'])) { + $this->output->writeln(sprintf(' %s()', $this->prepareOutput($trace['function']))); + } + + if (isset($trace['file'])) { + $this->output->writeln(sprintf(' in %s at line %d', $this->prepareOutput($trace['file']), $trace['line'])); + } + } + + private function prepareOutput(string $string): string + { + return $this->isDecorated + ? OutputFormatter::escape($string) + : $string; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Output/OutputContext.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Output/OutputContext.php new file mode 100644 index 00000000..84a0ea6f --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Output/OutputContext.php @@ -0,0 +1,52 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Output; + +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @internal + */ +final class OutputContext +{ + private ?OutputInterface $output; + private int $terminalWidth; + private int $filesCount; + + public function __construct( + ?OutputInterface $output, + int $terminalWidth, + int $filesCount + ) { + $this->output = $output; + $this->terminalWidth = $terminalWidth; + $this->filesCount = $filesCount; + } + + public function getOutput(): ?OutputInterface + { + return $this->output; + } + + public function getTerminalWidth(): int + { + return $this->terminalWidth; + } + + public function getFilesCount(): int + { + return $this->filesCount; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Output/Progress/DotsOutput.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Output/Progress/DotsOutput.php new file mode 100644 index 00000000..0e47ae5f --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Output/Progress/DotsOutput.php @@ -0,0 +1,127 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Output\Progress; + +use PhpCsFixer\Console\Output\OutputContext; +use PhpCsFixer\FixerFileProcessedEvent; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Output writer to show the progress of a FixCommand using dots and meaningful letters. + * + * @internal + */ +final class DotsOutput implements ProgressOutputInterface +{ + /** + * File statuses map. + * + * @var array + */ + private static array $eventStatusMap = [ + FixerFileProcessedEvent::STATUS_NO_CHANGES => ['symbol' => '.', 'format' => '%s', 'description' => 'no changes'], + FixerFileProcessedEvent::STATUS_FIXED => ['symbol' => 'F', 'format' => '%s', 'description' => 'fixed'], + FixerFileProcessedEvent::STATUS_SKIPPED => ['symbol' => 'S', 'format' => '%s', 'description' => 'skipped (cached or empty file)'], + FixerFileProcessedEvent::STATUS_INVALID => ['symbol' => 'I', 'format' => '%s', 'description' => 'invalid file syntax (file ignored)'], + FixerFileProcessedEvent::STATUS_EXCEPTION => ['symbol' => 'E', 'format' => '%s', 'description' => 'error'], + FixerFileProcessedEvent::STATUS_LINT => ['symbol' => 'E', 'format' => '%s', 'description' => 'error'], + ]; + + /** @readonly */ + private OutputContext $context; + + private int $processedFiles = 0; + + /** + * @var int + */ + private $symbolsPerLine; + + public function __construct(OutputContext $context) + { + $this->context = $context; + + // max number of characters per line + // - total length x 2 (e.g. " 1 / 123" => 6 digits and padding spaces) + // - 11 (extra spaces, parentheses and percentage characters, e.g. " x / x (100%)") + $this->symbolsPerLine = max(1, $context->getTerminalWidth() - \strlen((string) $context->getFilesCount()) * 2 - 11); + } + + /** + * This class is not intended to be serialized, + * and cannot be deserialized (see __wakeup method). + */ + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.self::class); + } + + /** + * Disable the deserialization of the class to prevent attacker executing + * code by leveraging the __destruct method. + * + * @see https://owasp.org/www-community/vulnerabilities/PHP_Object_Injection + */ + public function __wakeup(): void + { + throw new \BadMethodCallException('Cannot unserialize '.self::class); + } + + public function onFixerFileProcessed(FixerFileProcessedEvent $event): void + { + $status = self::$eventStatusMap[$event->getStatus()]; + $this->getOutput()->write($this->getOutput()->isDecorated() ? sprintf($status['format'], $status['symbol']) : $status['symbol']); + + ++$this->processedFiles; + + $symbolsOnCurrentLine = $this->processedFiles % $this->symbolsPerLine; + $isLast = $this->processedFiles === $this->context->getFilesCount(); + + if (0 === $symbolsOnCurrentLine || $isLast) { + $this->getOutput()->write(sprintf( + '%s %'.\strlen((string) $this->context->getFilesCount()).'d / %d (%3d%%)', + $isLast && 0 !== $symbolsOnCurrentLine ? str_repeat(' ', $this->symbolsPerLine - $symbolsOnCurrentLine) : '', + $this->processedFiles, + $this->context->getFilesCount(), + round($this->processedFiles / $this->context->getFilesCount() * 100) + )); + + if (!$isLast) { + $this->getOutput()->writeln(''); + } + } + } + + public function printLegend(): void + { + $symbols = []; + + foreach (self::$eventStatusMap as $status) { + $symbol = $status['symbol']; + if ('' === $symbol || isset($symbols[$symbol])) { + continue; + } + + $symbols[$symbol] = sprintf('%s-%s', $this->getOutput()->isDecorated() ? sprintf($status['format'], $symbol) : $symbol, $status['description']); + } + + $this->getOutput()->write(sprintf("\nLegend: %s\n", implode(', ', $symbols))); + } + + private function getOutput(): OutputInterface + { + return $this->context->getOutput(); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Output/Progress/NullOutput.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Output/Progress/NullOutput.php new file mode 100644 index 00000000..3af7ee44 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Output/Progress/NullOutput.php @@ -0,0 +1,27 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Output\Progress; + +use PhpCsFixer\FixerFileProcessedEvent; + +/** + * @internal + */ +final class NullOutput implements ProgressOutputInterface +{ + public function printLegend(): void {} + + public function onFixerFileProcessed(FixerFileProcessedEvent $event): void {} +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Output/Progress/PercentageBarOutput.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Output/Progress/PercentageBarOutput.php new file mode 100644 index 00000000..369bed1d --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Output/Progress/PercentageBarOutput.php @@ -0,0 +1,76 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Output\Progress; + +use PhpCsFixer\Console\Output\OutputContext; +use PhpCsFixer\FixerFileProcessedEvent; +use Symfony\Component\Console\Helper\ProgressBar; + +/** + * Output writer to show the progress of a FixCommand using progress bar (percentage). + * + * @internal + */ +final class PercentageBarOutput implements ProgressOutputInterface +{ + /** @readonly */ + private OutputContext $context; + + private ProgressBar $progressBar; + + public function __construct(OutputContext $context) + { + $this->context = $context; + + $this->progressBar = new ProgressBar($context->getOutput(), $this->context->getFilesCount()); + $this->progressBar->setBarCharacter('▓'); // dark shade character \u2593 + $this->progressBar->setEmptyBarCharacter('░'); // light shade character \u2591 + $this->progressBar->setProgressCharacter(''); + $this->progressBar->setFormat('normal'); + + $this->progressBar->start(); + } + + /** + * This class is not intended to be serialized, + * and cannot be deserialized (see __wakeup method). + */ + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.self::class); + } + + /** + * Disable the deserialization of the class to prevent attacker executing + * code by leveraging the __destruct method. + * + * @see https://owasp.org/www-community/vulnerabilities/PHP_Object_Injection + */ + public function __wakeup(): void + { + throw new \BadMethodCallException('Cannot unserialize '.self::class); + } + + public function onFixerFileProcessed(FixerFileProcessedEvent $event): void + { + $this->progressBar->advance(1); + + if ($this->progressBar->getProgress() === $this->progressBar->getMaxSteps()) { + $this->context->getOutput()->write("\n\n"); + } + } + + public function printLegend(): void {} +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Output/Progress/ProgressOutputFactory.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Output/Progress/ProgressOutputFactory.php new file mode 100644 index 00000000..b1102e8f --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Output/Progress/ProgressOutputFactory.php @@ -0,0 +1,55 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Output\Progress; + +use PhpCsFixer\Console\Output\OutputContext; + +/** + * @internal + */ +final class ProgressOutputFactory +{ + /** + * @var array> + */ + private static array $outputTypeMap = [ + ProgressOutputType::NONE => NullOutput::class, + ProgressOutputType::DOTS => DotsOutput::class, + ProgressOutputType::BAR => PercentageBarOutput::class, + ]; + + public function create(string $outputType, OutputContext $context): ProgressOutputInterface + { + if (null === $context->getOutput()) { + $outputType = ProgressOutputType::NONE; + } + + if (!$this->isBuiltInType($outputType)) { + throw new \InvalidArgumentException( + sprintf( + 'Something went wrong, "%s" output type is not supported', + $outputType + ) + ); + } + + return new self::$outputTypeMap[$outputType]($context); + } + + private function isBuiltInType(string $outputType): bool + { + return \in_array($outputType, ProgressOutputType::all(), true); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Output/Progress/ProgressOutputInterface.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Output/Progress/ProgressOutputInterface.php new file mode 100644 index 00000000..b5ab9517 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Output/Progress/ProgressOutputInterface.php @@ -0,0 +1,27 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Output\Progress; + +use PhpCsFixer\FixerFileProcessedEvent; + +/** + * @internal + */ +interface ProgressOutputInterface +{ + public function printLegend(): void; + + public function onFixerFileProcessed(FixerFileProcessedEvent $event): void; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Output/Progress/ProgressOutputType.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Output/Progress/ProgressOutputType.php new file mode 100644 index 00000000..6f427a46 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Output/Progress/ProgressOutputType.php @@ -0,0 +1,37 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Output\Progress; + +/** + * @internal + */ +final class ProgressOutputType +{ + public const NONE = 'none'; + public const DOTS = 'dots'; + public const BAR = 'bar'; + + /** + * @return list + */ + public static function all(): array + { + return [ + self::BAR, + self::DOTS, + self::NONE, + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/CheckstyleReporter.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/CheckstyleReporter.php new file mode 100644 index 00000000..48daf7b2 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/CheckstyleReporter.php @@ -0,0 +1,69 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Report\FixReport; + +use PhpCsFixer\Console\Application; +use Symfony\Component\Console\Formatter\OutputFormatter; + +/** + * @author Kévin Gomez + * + * @internal + */ +final class CheckstyleReporter implements ReporterInterface +{ + public function getFormat(): string + { + return 'checkstyle'; + } + + public function generate(ReportSummary $reportSummary): string + { + if (!\extension_loaded('dom')) { + throw new \RuntimeException('Cannot generate report! `ext-dom` is not available!'); + } + + $dom = new \DOMDocument('1.0', 'UTF-8'); + + /** @var \DOMElement $checkstyles */ + $checkstyles = $dom->appendChild($dom->createElement('checkstyle')); + $checkstyles->setAttribute('version', Application::getAbout()); + + foreach ($reportSummary->getChanged() as $filePath => $fixResult) { + /** @var \DOMElement $file */ + $file = $checkstyles->appendChild($dom->createElement('file')); + $file->setAttribute('name', $filePath); + + foreach ($fixResult['appliedFixers'] as $appliedFixer) { + $error = $this->createError($dom, $appliedFixer); + $file->appendChild($error); + } + } + + $dom->formatOutput = true; + + return $reportSummary->isDecoratedOutput() ? OutputFormatter::escape($dom->saveXML()) : $dom->saveXML(); + } + + private function createError(\DOMDocument $dom, string $appliedFixer): \DOMElement + { + $error = $dom->createElement('error'); + $error->setAttribute('severity', 'warning'); + $error->setAttribute('source', 'PHP-CS-Fixer.'.$appliedFixer); + $error->setAttribute('message', 'Found violation(s) of type: '.$appliedFixer); + + return $error; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/GitlabReporter.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/GitlabReporter.php new file mode 100644 index 00000000..23aad2e8 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/GitlabReporter.php @@ -0,0 +1,94 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Report\FixReport; + +use PhpCsFixer\Console\Application; +use SebastianBergmann\Diff\Chunk; +use SebastianBergmann\Diff\Diff; +use SebastianBergmann\Diff\Parser; +use Symfony\Component\Console\Formatter\OutputFormatter; + +/** + * Generates a report according to gitlabs subset of codeclimate json files. + * + * @see https://github.com/codeclimate/platform/blob/master/spec/analyzers/SPEC.md#data-types + * + * @author Hans-Christian Otto + * + * @internal + */ +final class GitlabReporter implements ReporterInterface +{ + private Parser $diffParser; + + public function __construct() + { + $this->diffParser = new Parser(); + } + + public function getFormat(): string + { + return 'gitlab'; + } + + /** + * Process changed files array. Returns generated report. + */ + public function generate(ReportSummary $reportSummary): string + { + $about = Application::getAbout(); + + $report = []; + foreach ($reportSummary->getChanged() as $fileName => $change) { + foreach ($change['appliedFixers'] as $fixerName) { + $report[] = [ + 'check_name' => 'PHP-CS-Fixer.'.$fixerName, + 'description' => 'PHP-CS-Fixer.'.$fixerName.' by '.$about, + 'categories' => ['Style'], + 'fingerprint' => md5($fileName.$fixerName), + 'severity' => 'minor', + 'location' => [ + 'path' => $fileName, + 'lines' => self::getLines($this->diffParser->parse($change['diff'])), + ], + ]; + } + } + + $jsonString = json_encode($report, JSON_THROW_ON_ERROR); + + return $reportSummary->isDecoratedOutput() ? OutputFormatter::escape($jsonString) : $jsonString; + } + + /** + * @param list $diffs + * + * @return array{begin: int, end: int} + */ + private static function getLines(array $diffs): array + { + if (isset($diffs[0])) { + $firstDiff = $diffs[0]; + + $firstChunk = \Closure::bind(static fn (Diff $diff) => array_shift($diff->chunks), null, $firstDiff)($firstDiff); + + if ($firstChunk instanceof Chunk) { + return \Closure::bind(static fn (Chunk $chunk): array => ['begin' => $chunk->start, 'end' => $chunk->startRange], null, $firstChunk)($firstChunk); + } + } + + return ['begin' => 0, 'end' => 0]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/JsonReporter.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/JsonReporter.php new file mode 100644 index 00000000..0dc01dba --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/JsonReporter.php @@ -0,0 +1,63 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Report\FixReport; + +use PhpCsFixer\Console\Application; +use Symfony\Component\Console\Formatter\OutputFormatter; + +/** + * @author Boris Gorbylev + * + * @internal + */ +final class JsonReporter implements ReporterInterface +{ + public function getFormat(): string + { + return 'json'; + } + + public function generate(ReportSummary $reportSummary): string + { + $jsonFiles = []; + + foreach ($reportSummary->getChanged() as $file => $fixResult) { + $jsonFile = ['name' => $file]; + + if ($reportSummary->shouldAddAppliedFixers()) { + $jsonFile['appliedFixers'] = $fixResult['appliedFixers']; + } + + if ('' !== $fixResult['diff']) { + $jsonFile['diff'] = $fixResult['diff']; + } + + $jsonFiles[] = $jsonFile; + } + + $json = [ + 'about' => Application::getAbout(), + 'files' => $jsonFiles, + 'time' => [ + 'total' => round($reportSummary->getTime() / 1_000, 3), + ], + 'memory' => round($reportSummary->getMemory() / 1_024 / 1_024, 3), + ]; + + $json = json_encode($json, JSON_THROW_ON_ERROR); + + return $reportSummary->isDecoratedOutput() ? OutputFormatter::escape($json) : $json; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/JunitReporter.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/JunitReporter.php new file mode 100644 index 00000000..0334bee6 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/JunitReporter.php @@ -0,0 +1,143 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Report\FixReport; + +use PhpCsFixer\Console\Application; +use PhpCsFixer\Preg; +use Symfony\Component\Console\Formatter\OutputFormatter; + +/** + * @author Boris Gorbylev + * + * @internal + */ +final class JunitReporter implements ReporterInterface +{ + public function getFormat(): string + { + return 'junit'; + } + + public function generate(ReportSummary $reportSummary): string + { + if (!\extension_loaded('dom')) { + throw new \RuntimeException('Cannot generate report! `ext-dom` is not available!'); + } + + $dom = new \DOMDocument('1.0', 'UTF-8'); + $testsuites = $dom->appendChild($dom->createElement('testsuites')); + + /** @var \DOMElement $testsuite */ + $testsuite = $testsuites->appendChild($dom->createElement('testsuite')); + $testsuite->setAttribute('name', 'PHP CS Fixer'); + + $properties = $dom->createElement('properties'); + $property = $dom->createElement('property'); + $property->setAttribute('name', 'about'); + $property->setAttribute('value', Application::getAbout()); + $properties->appendChild($property); + $testsuite->appendChild($properties); + + if (\count($reportSummary->getChanged()) > 0) { + $this->createFailedTestCases($dom, $testsuite, $reportSummary); + } else { + $this->createSuccessTestCase($dom, $testsuite); + } + + if ($reportSummary->getTime() > 0) { + $testsuite->setAttribute( + 'time', + sprintf( + '%.3f', + $reportSummary->getTime() / 1_000 + ) + ); + } + + $dom->formatOutput = true; + + return $reportSummary->isDecoratedOutput() ? OutputFormatter::escape($dom->saveXML()) : $dom->saveXML(); + } + + private function createSuccessTestCase(\DOMDocument $dom, \DOMElement $testsuite): void + { + $testcase = $dom->createElement('testcase'); + $testcase->setAttribute('name', 'All OK'); + $testcase->setAttribute('assertions', '1'); + + $testsuite->appendChild($testcase); + $testsuite->setAttribute('tests', '1'); + $testsuite->setAttribute('assertions', '1'); + $testsuite->setAttribute('failures', '0'); + $testsuite->setAttribute('errors', '0'); + } + + private function createFailedTestCases(\DOMDocument $dom, \DOMElement $testsuite, ReportSummary $reportSummary): void + { + $assertionsCount = 0; + foreach ($reportSummary->getChanged() as $file => $fixResult) { + $testcase = $this->createFailedTestCase( + $dom, + $file, + $fixResult, + $reportSummary->shouldAddAppliedFixers() + ); + $testsuite->appendChild($testcase); + $assertionsCount += (int) $testcase->getAttribute('assertions'); + } + + $testsuite->setAttribute('tests', (string) \count($reportSummary->getChanged())); + $testsuite->setAttribute('assertions', (string) $assertionsCount); + $testsuite->setAttribute('failures', (string) $assertionsCount); + $testsuite->setAttribute('errors', '0'); + } + + /** + * @param array{appliedFixers: list, diff: string} $fixResult + */ + private function createFailedTestCase(\DOMDocument $dom, string $file, array $fixResult, bool $shouldAddAppliedFixers): \DOMElement + { + $appliedFixersCount = \count($fixResult['appliedFixers']); + + $testName = str_replace('.', '_DOT_', Preg::replace('@\.'.pathinfo($file, PATHINFO_EXTENSION).'$@', '', $file)); + + $testcase = $dom->createElement('testcase'); + $testcase->setAttribute('name', $testName); + $testcase->setAttribute('file', $file); + $testcase->setAttribute('assertions', (string) $appliedFixersCount); + + $failure = $dom->createElement('failure'); + $failure->setAttribute('type', 'code_style'); + $testcase->appendChild($failure); + + if ($shouldAddAppliedFixers) { + $failureContent = "applied fixers:\n---------------\n"; + + foreach ($fixResult['appliedFixers'] as $appliedFixer) { + $failureContent .= "* {$appliedFixer}\n"; + } + } else { + $failureContent = "Wrong code style\n"; + } + + if ('' !== $fixResult['diff']) { + $failureContent .= "\nDiff:\n---------------\n\n".$fixResult['diff']; + } + + $failure->appendChild($dom->createCDATASection(trim($failureContent))); + + return $testcase; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/ReportSummary.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/ReportSummary.php new file mode 100644 index 00000000..ccdfefb3 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/ReportSummary.php @@ -0,0 +1,101 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Report\FixReport; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class ReportSummary +{ + /** + * @var array, diff: string}> + */ + private array $changed; + + private int $filesCount; + + private int $time; + + private int $memory; + + private bool $addAppliedFixers; + + private bool $isDryRun; + + private bool $isDecoratedOutput; + + /** + * @param array, diff: string}> $changed + * @param int $time duration in milliseconds + * @param int $memory memory usage in bytes + */ + public function __construct( + array $changed, + int $filesCount, + int $time, + int $memory, + bool $addAppliedFixers, + bool $isDryRun, + bool $isDecoratedOutput + ) { + $this->changed = $changed; + $this->filesCount = $filesCount; + $this->time = $time; + $this->memory = $memory; + $this->addAppliedFixers = $addAppliedFixers; + $this->isDryRun = $isDryRun; + $this->isDecoratedOutput = $isDecoratedOutput; + } + + public function isDecoratedOutput(): bool + { + return $this->isDecoratedOutput; + } + + public function isDryRun(): bool + { + return $this->isDryRun; + } + + /** + * @return array, diff: string}> + */ + public function getChanged(): array + { + return $this->changed; + } + + public function getMemory(): int + { + return $this->memory; + } + + public function getTime(): int + { + return $this->time; + } + + public function getFilesCount(): int + { + return $this->filesCount; + } + + public function shouldAddAppliedFixers(): bool + { + return $this->addAppliedFixers; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/ReporterFactory.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/ReporterFactory.php new file mode 100644 index 00000000..e4efd990 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/ReporterFactory.php @@ -0,0 +1,90 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Report\FixReport; + +use Symfony\Component\Finder\Finder as SymfonyFinder; + +/** + * @author Boris Gorbylev + * + * @internal + */ +final class ReporterFactory +{ + /** @var array */ + private array $reporters = []; + + public function registerBuiltInReporters(): self + { + /** @var null|list $builtInReporters */ + static $builtInReporters; + + if (null === $builtInReporters) { + $builtInReporters = []; + + foreach (SymfonyFinder::create()->files()->name('*Reporter.php')->in(__DIR__) as $file) { + $relativeNamespace = $file->getRelativePath(); + $builtInReporters[] = sprintf( + '%s\\%s%s', + __NAMESPACE__, + '' !== $relativeNamespace ? $relativeNamespace.'\\' : '', + $file->getBasename('.php') + ); + } + } + + foreach ($builtInReporters as $reporterClass) { + $this->registerReporter(new $reporterClass()); + } + + return $this; + } + + /** + * @return $this + */ + public function registerReporter(ReporterInterface $reporter): self + { + $format = $reporter->getFormat(); + + if (isset($this->reporters[$format])) { + throw new \UnexpectedValueException(sprintf('Reporter for format "%s" is already registered.', $format)); + } + + $this->reporters[$format] = $reporter; + + return $this; + } + + /** + * @return list + */ + public function getFormats(): array + { + $formats = array_keys($this->reporters); + sort($formats); + + return $formats; + } + + public function getReporter(string $format): ReporterInterface + { + if (!isset($this->reporters[$format])) { + throw new \UnexpectedValueException(sprintf('Reporter for format "%s" is not registered.', $format)); + } + + return $this->reporters[$format]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/ReporterInterface.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/ReporterInterface.php new file mode 100644 index 00000000..44fab560 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/ReporterInterface.php @@ -0,0 +1,30 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Report\FixReport; + +/** + * @author Boris Gorbylev + * + * @internal + */ +interface ReporterInterface +{ + public function getFormat(): string; + + /** + * Process changed files array. Returns generated report. + */ + public function generate(ReportSummary $reportSummary): string; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/TextReporter.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/TextReporter.php new file mode 100644 index 00000000..31f11f35 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/TextReporter.php @@ -0,0 +1,102 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Report\FixReport; + +use PhpCsFixer\Differ\DiffConsoleFormatter; + +/** + * @author Boris Gorbylev + * + * @internal + */ +final class TextReporter implements ReporterInterface +{ + public function getFormat(): string + { + return 'txt'; + } + + public function generate(ReportSummary $reportSummary): string + { + $output = ''; + + $identifiedFiles = 0; + foreach ($reportSummary->getChanged() as $file => $fixResult) { + ++$identifiedFiles; + $output .= sprintf('%4d) %s', $identifiedFiles, $file); + + if ($reportSummary->shouldAddAppliedFixers()) { + $output .= $this->getAppliedFixers( + $reportSummary->isDecoratedOutput(), + $fixResult['appliedFixers'], + ); + } + + $output .= $this->getDiff($reportSummary->isDecoratedOutput(), $fixResult['diff']); + $output .= PHP_EOL; + } + + return $output.$this->getFooter( + $reportSummary->getTime(), + $identifiedFiles, + $reportSummary->getFilesCount(), + $reportSummary->getMemory(), + $reportSummary->isDryRun() + ); + } + + /** + * @param list $appliedFixers + */ + private function getAppliedFixers(bool $isDecoratedOutput, array $appliedFixers): string + { + return sprintf( + $isDecoratedOutput ? ' (%s)' : ' (%s)', + implode(', ', $appliedFixers) + ); + } + + private function getDiff(bool $isDecoratedOutput, string $diff): string + { + if ('' === $diff) { + return ''; + } + + $diffFormatter = new DiffConsoleFormatter($isDecoratedOutput, sprintf( + ' ---------- begin diff ----------%s%%s%s ----------- end diff -----------', + PHP_EOL, + PHP_EOL + )); + + return PHP_EOL.$diffFormatter->format($diff).PHP_EOL; + } + + private function getFooter(int $time, int $identifiedFiles, int $files, int $memory, bool $isDryRun): string + { + if (0 === $time || 0 === $memory) { + return ''; + } + + return PHP_EOL.sprintf( + '%s %d of %d %s in %.3f seconds, %.3f MB memory used'.PHP_EOL, + $isDryRun ? 'Found' : 'Fixed', + $identifiedFiles, + $files, + $isDryRun ? 'files that can be fixed' : 'files', + $time / 1_000, + $memory / 1_024 / 1_024 + ); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/XmlReporter.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/XmlReporter.php new file mode 100644 index 00000000..59562070 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/XmlReporter.php @@ -0,0 +1,134 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Report\FixReport; + +use PhpCsFixer\Console\Application; +use Symfony\Component\Console\Formatter\OutputFormatter; + +/** + * @author Boris Gorbylev + * + * @internal + */ +final class XmlReporter implements ReporterInterface +{ + public function getFormat(): string + { + return 'xml'; + } + + public function generate(ReportSummary $reportSummary): string + { + if (!\extension_loaded('dom')) { + throw new \RuntimeException('Cannot generate report! `ext-dom` is not available!'); + } + + $dom = new \DOMDocument('1.0', 'UTF-8'); + // new nodes should be added to this or existing children + $root = $dom->createElement('report'); + $dom->appendChild($root); + + $root->appendChild($this->createAboutElement($dom, Application::getAbout())); + + $filesXML = $dom->createElement('files'); + $root->appendChild($filesXML); + + $i = 1; + foreach ($reportSummary->getChanged() as $file => $fixResult) { + $fileXML = $dom->createElement('file'); + $fileXML->setAttribute('id', (string) $i++); + $fileXML->setAttribute('name', $file); + $filesXML->appendChild($fileXML); + + if ($reportSummary->shouldAddAppliedFixers()) { + $fileXML->appendChild( + $this->createAppliedFixersElement($dom, $fixResult['appliedFixers']), + ); + } + + if ('' !== $fixResult['diff']) { + $fileXML->appendChild($this->createDiffElement($dom, $fixResult['diff'])); + } + } + + if (0 !== $reportSummary->getTime()) { + $root->appendChild($this->createTimeElement($reportSummary->getTime(), $dom)); + } + + if (0 !== $reportSummary->getMemory()) { + $root->appendChild($this->createMemoryElement($reportSummary->getMemory(), $dom)); + } + + $dom->formatOutput = true; + + return $reportSummary->isDecoratedOutput() ? OutputFormatter::escape($dom->saveXML()) : $dom->saveXML(); + } + + /** + * @param list $appliedFixers + */ + private function createAppliedFixersElement(\DOMDocument $dom, array $appliedFixers): \DOMElement + { + $appliedFixersXML = $dom->createElement('applied_fixers'); + + foreach ($appliedFixers as $appliedFixer) { + $appliedFixerXML = $dom->createElement('applied_fixer'); + $appliedFixerXML->setAttribute('name', $appliedFixer); + $appliedFixersXML->appendChild($appliedFixerXML); + } + + return $appliedFixersXML; + } + + private function createDiffElement(\DOMDocument $dom, string $diff): \DOMElement + { + $diffXML = $dom->createElement('diff'); + $diffXML->appendChild($dom->createCDATASection($diff)); + + return $diffXML; + } + + private function createTimeElement(float $time, \DOMDocument $dom): \DOMElement + { + $time = round($time / 1_000, 3); + + $timeXML = $dom->createElement('time'); + $timeXML->setAttribute('unit', 's'); + $timeTotalXML = $dom->createElement('total'); + $timeTotalXML->setAttribute('value', (string) $time); + $timeXML->appendChild($timeTotalXML); + + return $timeXML; + } + + private function createMemoryElement(float $memory, \DOMDocument $dom): \DOMElement + { + $memory = round($memory / 1_024 / 1_024, 3); + + $memoryXML = $dom->createElement('memory'); + $memoryXML->setAttribute('value', (string) $memory); + $memoryXML->setAttribute('unit', 'MB'); + + return $memoryXML; + } + + private function createAboutElement(\DOMDocument $dom, string $about): \DOMElement + { + $XML = $dom->createElement('about'); + $XML->setAttribute('value', $about); + + return $XML; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/ListSetsReport/JsonReporter.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/ListSetsReport/JsonReporter.php new file mode 100644 index 00000000..ceebd89e --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/ListSetsReport/JsonReporter.php @@ -0,0 +1,50 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Report\ListSetsReport; + +use PhpCsFixer\RuleSet\RuleSetDescriptionInterface; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class JsonReporter implements ReporterInterface +{ + public function getFormat(): string + { + return 'json'; + } + + public function generate(ReportSummary $reportSummary): string + { + $sets = $reportSummary->getSets(); + + usort($sets, static fn (RuleSetDescriptionInterface $a, RuleSetDescriptionInterface $b): int => $a->getName() <=> $b->getName()); + + $json = ['sets' => []]; + + foreach ($sets as $set) { + $setName = $set->getName(); + $json['sets'][$setName] = [ + 'description' => $set->getDescription(), + 'isRisky' => $set->isRisky(), + 'name' => $setName, + ]; + } + + return json_encode($json, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/ListSetsReport/ReportSummary.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/ListSetsReport/ReportSummary.php new file mode 100644 index 00000000..c7d66e71 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/ListSetsReport/ReportSummary.php @@ -0,0 +1,46 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Report\ListSetsReport; + +use PhpCsFixer\RuleSet\RuleSetDescriptionInterface; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class ReportSummary +{ + /** + * @var list + */ + private array $sets; + + /** + * @param list $sets + */ + public function __construct(array $sets) + { + $this->sets = $sets; + } + + /** + * @return list + */ + public function getSets(): array + { + return $this->sets; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/ListSetsReport/ReporterFactory.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/ListSetsReport/ReporterFactory.php new file mode 100644 index 00000000..45f2a26f --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/ListSetsReport/ReporterFactory.php @@ -0,0 +1,89 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Report\ListSetsReport; + +use Symfony\Component\Finder\Finder as SymfonyFinder; + +/** + * @author Boris Gorbylev + * + * @internal + */ +final class ReporterFactory +{ + /** + * @var array + */ + private array $reporters = []; + + public function registerBuiltInReporters(): self + { + /** @var null|list $builtInReporters */ + static $builtInReporters; + + if (null === $builtInReporters) { + $builtInReporters = []; + + foreach (SymfonyFinder::create()->files()->name('*Reporter.php')->in(__DIR__) as $file) { + $relativeNamespace = $file->getRelativePath(); + $builtInReporters[] = sprintf( + '%s\\%s%s', + __NAMESPACE__, + '' !== $relativeNamespace ? $relativeNamespace.'\\' : '', + $file->getBasename('.php') + ); + } + } + + foreach ($builtInReporters as $reporterClass) { + $this->registerReporter(new $reporterClass()); + } + + return $this; + } + + public function registerReporter(ReporterInterface $reporter): self + { + $format = $reporter->getFormat(); + + if (isset($this->reporters[$format])) { + throw new \UnexpectedValueException(sprintf('Reporter for format "%s" is already registered.', $format)); + } + + $this->reporters[$format] = $reporter; + + return $this; + } + + /** + * @return list + */ + public function getFormats(): array + { + $formats = array_keys($this->reporters); + sort($formats); + + return $formats; + } + + public function getReporter(string $format): ReporterInterface + { + if (!isset($this->reporters[$format])) { + throw new \UnexpectedValueException(sprintf('Reporter for format "%s" is not registered.', $format)); + } + + return $this->reporters[$format]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/ListSetsReport/ReporterInterface.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/ListSetsReport/ReporterInterface.php new file mode 100644 index 00000000..4a03f330 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/ListSetsReport/ReporterInterface.php @@ -0,0 +1,30 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Report\ListSetsReport; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +interface ReporterInterface +{ + public function getFormat(): string; + + /** + * Process changed files array. Returns generated report. + */ + public function generate(ReportSummary $reportSummary): string; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/ListSetsReport/TextReporter.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/ListSetsReport/TextReporter.php new file mode 100644 index 00000000..9c851ef3 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/ListSetsReport/TextReporter.php @@ -0,0 +1,49 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Report\ListSetsReport; + +use PhpCsFixer\RuleSet\RuleSetDescriptionInterface; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class TextReporter implements ReporterInterface +{ + public function getFormat(): string + { + return 'txt'; + } + + public function generate(ReportSummary $reportSummary): string + { + $sets = $reportSummary->getSets(); + + usort($sets, static fn (RuleSetDescriptionInterface $a, RuleSetDescriptionInterface $b): int => $a->getName() <=> $b->getName()); + + $output = ''; + + foreach ($sets as $i => $set) { + $output .= sprintf('%2d) %s', $i + 1, $set->getName()).PHP_EOL.' '.$set->getDescription().PHP_EOL; + + if ($set->isRisky()) { + $output .= ' Set contains risky rules.'.PHP_EOL; + } + } + + return $output; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/GithubClient.php b/vendor/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/GithubClient.php new file mode 100644 index 00000000..10995cfb --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/GithubClient.php @@ -0,0 +1,62 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\SelfUpdate; + +/** + * @internal + */ +final class GithubClient implements GithubClientInterface +{ + private string $url = 'https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/tags'; + + public function getTags(): array + { + $result = @file_get_contents( + $this->url, + false, + stream_context_create([ + 'http' => [ + 'header' => 'User-Agent: PHP-CS-Fixer/PHP-CS-Fixer', + ], + ]) + ); + + if (false === $result) { + throw new \RuntimeException(sprintf('Failed to load tags at "%s".', $this->url)); + } + + /** + * @var list + */ + $result = json_decode($result, true); + if (JSON_ERROR_NONE !== json_last_error()) { + throw new \RuntimeException(sprintf( + 'Failed to read response from "%s" as JSON: %s.', + $this->url, + json_last_error_msg() + )); + } + + return array_map( + static fn (array $tagData): string => $tagData['name'], + $result + ); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/GithubClientInterface.php b/vendor/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/GithubClientInterface.php new file mode 100644 index 00000000..27728327 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/GithubClientInterface.php @@ -0,0 +1,26 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\SelfUpdate; + +/** + * @internal + */ +interface GithubClientInterface +{ + /** + * @return list + */ + public function getTags(): array; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/NewVersionChecker.php b/vendor/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/NewVersionChecker.php new file mode 100644 index 00000000..7945ac1e --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/NewVersionChecker.php @@ -0,0 +1,99 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\SelfUpdate; + +use Composer\Semver\Comparator; +use Composer\Semver\Semver; +use Composer\Semver\VersionParser; + +/** + * @internal + */ +final class NewVersionChecker implements NewVersionCheckerInterface +{ + private GithubClientInterface $githubClient; + + private VersionParser $versionParser; + + /** + * @var null|string[] + */ + private $availableVersions; + + public function __construct(GithubClientInterface $githubClient) + { + $this->githubClient = $githubClient; + $this->versionParser = new VersionParser(); + } + + public function getLatestVersion(): string + { + $this->retrieveAvailableVersions(); + + return $this->availableVersions[0]; + } + + public function getLatestVersionOfMajor(int $majorVersion): ?string + { + $this->retrieveAvailableVersions(); + + $semverConstraint = '^'.$majorVersion; + + foreach ($this->availableVersions as $availableVersion) { + if (Semver::satisfies($availableVersion, $semverConstraint)) { + return $availableVersion; + } + } + + return null; + } + + public function compareVersions(string $versionA, string $versionB): int + { + $versionA = $this->versionParser->normalize($versionA); + $versionB = $this->versionParser->normalize($versionB); + + if (Comparator::lessThan($versionA, $versionB)) { + return -1; + } + + if (Comparator::greaterThan($versionA, $versionB)) { + return 1; + } + + return 0; + } + + private function retrieveAvailableVersions(): void + { + if (null !== $this->availableVersions) { + return; + } + + foreach ($this->githubClient->getTags() as $version) { + try { + $this->versionParser->normalize($version); + + if ('stable' === VersionParser::parseStability($version)) { + $this->availableVersions[] = $version; + } + } catch (\UnexpectedValueException $exception) { + // not a valid version tag + } + } + + $this->availableVersions = Semver::rsort($this->availableVersions); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/NewVersionCheckerInterface.php b/vendor/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/NewVersionCheckerInterface.php new file mode 100644 index 00000000..c63b2a20 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/NewVersionCheckerInterface.php @@ -0,0 +1,37 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\SelfUpdate; + +/** + * @internal + */ +interface NewVersionCheckerInterface +{ + /** + * Returns the tag of the latest version. + */ + public function getLatestVersion(): string; + + /** + * Returns the tag of the latest minor/patch version of the given major version. + */ + public function getLatestVersionOfMajor(int $majorVersion): ?string; + + /** + * Returns -1, 0, or 1 if the first version is respectively less than, + * equal to, or greater than the second. + */ + public function compareVersions(string $versionA, string $versionB): int; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/WarningsDetector.php b/vendor/friendsofphp/php-cs-fixer/src/Console/WarningsDetector.php new file mode 100644 index 00000000..0f15baf4 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/WarningsDetector.php @@ -0,0 +1,76 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console; + +use PhpCsFixer\ToolInfo; +use PhpCsFixer\ToolInfoInterface; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class WarningsDetector +{ + private ToolInfoInterface $toolInfo; + + /** + * @var string[] + */ + private array $warnings = []; + + public function __construct(ToolInfoInterface $toolInfo) + { + $this->toolInfo = $toolInfo; + } + + public function detectOldMajor(): void + { + // @TODO 3.99 to be activated with new MAJOR release 4.0 + // $currentMajorVersion = \intval(explode('.', Application::VERSION)[0], 10); + // $nextMajorVersion = $currentMajorVersion + 1; + // $this->warnings[] = "You are running PHP CS Fixer v{$currentMajorVersion}, which is not maintained anymore. Please update to v{$nextMajorVersion}."; + // $this->warnings[] = "You may find an UPGRADE guide at https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/v{$nextMajorVersion}.0.0/UPGRADE-v{$nextMajorVersion}.md ."; + } + + public function detectOldVendor(): void + { + if ($this->toolInfo->isInstalledByComposer()) { + $details = $this->toolInfo->getComposerInstallationDetails(); + if (ToolInfo::COMPOSER_LEGACY_PACKAGE_NAME === $details['name']) { + $this->warnings[] = sprintf( + 'You are running PHP CS Fixer installed with old vendor `%s`. Please update to `%s`.', + ToolInfo::COMPOSER_LEGACY_PACKAGE_NAME, + ToolInfo::COMPOSER_PACKAGE_NAME + ); + } + } + } + + /** + * @return list + */ + public function getWarnings(): array + { + if (0 === \count($this->warnings)) { + return []; + } + + return array_unique(array_merge( + $this->warnings, + ['If you need help while solving warnings, ask at https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/discussions/, we will help you!'] + )); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Differ/DiffConsoleFormatter.php b/vendor/friendsofphp/php-cs-fixer/src/Differ/DiffConsoleFormatter.php new file mode 100644 index 00000000..f2896253 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Differ/DiffConsoleFormatter.php @@ -0,0 +1,83 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Differ; + +use PhpCsFixer\Preg; +use Symfony\Component\Console\Formatter\OutputFormatter; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class DiffConsoleFormatter +{ + private bool $isDecoratedOutput; + + private string $template; + + public function __construct(bool $isDecoratedOutput, string $template = '%s') + { + $this->isDecoratedOutput = $isDecoratedOutput; + $this->template = $template; + } + + public function format(string $diff, string $lineTemplate = '%s'): string + { + $isDecorated = $this->isDecoratedOutput; + + $template = $isDecorated + ? $this->template + : Preg::replace('/<[^<>]+>/', '', $this->template); + + return sprintf( + $template, + implode( + PHP_EOL, + array_map( + static function (string $line) use ($isDecorated, $lineTemplate): string { + if ($isDecorated) { + $count = 0; + $line = Preg::replaceCallback( + '/^([+\-@].*)/', + static function (array $matches): string { + if ('+' === $matches[0][0]) { + $colour = 'green'; + } elseif ('-' === $matches[0][0]) { + $colour = 'red'; + } else { + $colour = 'cyan'; + } + + return sprintf('%s', $colour, OutputFormatter::escape($matches[0]), $colour); + }, + $line, + 1, + $count + ); + + if (0 === $count) { + $line = OutputFormatter::escape($line); + } + } + + return sprintf($lineTemplate, $line); + }, + Preg::split('#\R#u', $diff) + ) + ) + ); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Differ/DifferInterface.php b/vendor/friendsofphp/php-cs-fixer/src/Differ/DifferInterface.php new file mode 100644 index 00000000..a66f165c --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Differ/DifferInterface.php @@ -0,0 +1,26 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Differ; + +/** + * @author Dariusz Rumiński + */ +interface DifferInterface +{ + /** + * Create diff. + */ + public function diff(string $old, string $new, ?\SplFileInfo $file = null): string; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Differ/FullDiffer.php b/vendor/friendsofphp/php-cs-fixer/src/Differ/FullDiffer.php new file mode 100644 index 00000000..a927ede4 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Differ/FullDiffer.php @@ -0,0 +1,44 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Differ; + +use SebastianBergmann\Diff\Differ; +use SebastianBergmann\Diff\Output\StrictUnifiedDiffOutputBuilder; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class FullDiffer implements DifferInterface +{ + private Differ $differ; + + public function __construct() + { + $this->differ = new Differ(new StrictUnifiedDiffOutputBuilder([ + 'collapseRanges' => false, + 'commonLineThreshold' => 100, + 'contextLines' => 100, + 'fromFile' => 'Original', + 'toFile' => 'New', + ])); + } + + public function diff(string $old, string $new, ?\SplFileInfo $file = null): string + { + return $this->differ->diff($old, $new); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Differ/NullDiffer.php b/vendor/friendsofphp/php-cs-fixer/src/Differ/NullDiffer.php new file mode 100644 index 00000000..cf07f9e7 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Differ/NullDiffer.php @@ -0,0 +1,26 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Differ; + +/** + * @author Dariusz Rumiński + */ +final class NullDiffer implements DifferInterface +{ + public function diff(string $old, string $new, ?\SplFileInfo $file = null): string + { + return ''; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Differ/UnifiedDiffer.php b/vendor/friendsofphp/php-cs-fixer/src/Differ/UnifiedDiffer.php new file mode 100644 index 00000000..36663aa1 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Differ/UnifiedDiffer.php @@ -0,0 +1,47 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Differ; + +use PhpCsFixer\Preg; +use SebastianBergmann\Diff\Differ; +use SebastianBergmann\Diff\Output\StrictUnifiedDiffOutputBuilder; + +final class UnifiedDiffer implements DifferInterface +{ + public function diff(string $old, string $new, ?\SplFileInfo $file = null): string + { + if (null === $file) { + $options = [ + 'fromFile' => 'Original', + 'toFile' => 'New', + ]; + } else { + $filePath = $file->getRealPath(); + + if (Preg::match('/\s/', $filePath)) { + $filePath = '"'.$filePath.'"'; + } + + $options = [ + 'fromFile' => $filePath, + 'toFile' => $filePath, + ]; + } + + $differ = new Differ(new StrictUnifiedDiffOutputBuilder($options)); + + return $differ->diff($old, $new); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/DocBlock/Annotation.php b/vendor/friendsofphp/php-cs-fixer/src/DocBlock/Annotation.php new file mode 100644 index 00000000..f394b82c --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/DocBlock/Annotation.php @@ -0,0 +1,311 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\DocBlock; + +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceUseAnalysis; + +/** + * This represents an entire annotation from a docblock. + * + * @author Graham Campbell + * @author Dariusz Rumiński + */ +final class Annotation +{ + /** + * All the annotation tag names with types. + * + * @var list + */ + private static array $tags = [ + 'method', + 'param', + 'property', + 'property-read', + 'property-write', + 'return', + 'throws', + 'type', + 'var', + ]; + + /** + * The lines that make up the annotation. + * + * @var Line[] + */ + private array $lines; + + /** + * The position of the first line of the annotation in the docblock. + * + * @var int + */ + private $start; + + /** + * The position of the last line of the annotation in the docblock. + * + * @var int + */ + private $end; + + /** + * The associated tag. + * + * @var null|Tag + */ + private $tag; + + /** + * Lazy loaded, cached types content. + * + * @var null|string + */ + private $typesContent; + + /** + * The cached types. + * + * @var null|list + */ + private $types; + + /** + * @var null|NamespaceAnalysis + */ + private $namespace; + + /** + * @var NamespaceUseAnalysis[] + */ + private array $namespaceUses; + + /** + * Create a new line instance. + * + * @param Line[] $lines + * @param null|NamespaceAnalysis $namespace + * @param NamespaceUseAnalysis[] $namespaceUses + */ + public function __construct(array $lines, $namespace = null, array $namespaceUses = []) + { + $this->lines = array_values($lines); + $this->namespace = $namespace; + $this->namespaceUses = $namespaceUses; + + $keys = array_keys($lines); + + $this->start = $keys[0]; + $this->end = end($keys); + } + + /** + * Get the string representation of object. + */ + public function __toString(): string + { + return $this->getContent(); + } + + /** + * Get all the annotation tag names with types. + * + * @return list + */ + public static function getTagsWithTypes(): array + { + return self::$tags; + } + + /** + * Get the start position of this annotation. + */ + public function getStart(): int + { + return $this->start; + } + + /** + * Get the end position of this annotation. + */ + public function getEnd(): int + { + return $this->end; + } + + /** + * Get the associated tag. + */ + public function getTag(): Tag + { + if (null === $this->tag) { + $this->tag = new Tag($this->lines[0]); + } + + return $this->tag; + } + + /** + * @internal + */ + public function getTypeExpression(): ?TypeExpression + { + $typesContent = $this->getTypesContent(); + + return null === $typesContent + ? null + : new TypeExpression($typesContent, $this->namespace, $this->namespaceUses); + } + + /** + * @return null|string + * + * @internal + */ + public function getVariableName() + { + $type = preg_quote($this->getTypesContent() ?? '', '/'); + $regex = "/@{$this->tag->getName()}\\s+({$type}\\s*)?(&\\s*)?(\\.{3}\\s*)?(?\\$.+?)(?:[\\s*]|$)/"; + + if (Preg::match($regex, $this->lines[0]->getContent(), $matches)) { + return $matches['variable']; + } + + return null; + } + + /** + * Get the types associated with this annotation. + * + * @return list + */ + public function getTypes(): array + { + if (null === $this->types) { + $typeExpression = $this->getTypeExpression(); + $this->types = null === $typeExpression + ? [] + : $typeExpression->getTypes(); + } + + return $this->types; + } + + /** + * Set the types associated with this annotation. + * + * @param list $types + */ + public function setTypes(array $types): void + { + $pattern = '/'.preg_quote($this->getTypesContent(), '/').'/'; + + $this->lines[0]->setContent(Preg::replace($pattern, implode($this->getTypeExpression()->getTypesGlue(), $types), $this->lines[0]->getContent(), 1)); + + $this->clearCache(); + } + + /** + * Get the normalized types associated with this annotation, so they can easily be compared. + * + * @return list + */ + public function getNormalizedTypes(): array + { + $normalized = array_map(static fn (string $type): string => strtolower($type), $this->getTypes()); + + sort($normalized); + + return $normalized; + } + + /** + * Remove this annotation by removing all its lines. + */ + public function remove(): void + { + foreach ($this->lines as $line) { + if ($line->isTheStart() && $line->isTheEnd()) { + // Single line doc block, remove entirely + $line->remove(); + } elseif ($line->isTheStart()) { + // Multi line doc block, but start is on the same line as the first annotation, keep only the start + $content = Preg::replace('#(\s*/\*\*).*#', '$1', $line->getContent()); + + $line->setContent($content); + } elseif ($line->isTheEnd()) { + // Multi line doc block, but end is on the same line as the last annotation, keep only the end + $content = Preg::replace('#(\s*)\S.*(\*/.*)#', '$1$2', $line->getContent()); + + $line->setContent($content); + } else { + // Multi line doc block, neither start nor end on this line, can be removed safely + $line->remove(); + } + } + + $this->clearCache(); + } + + /** + * Get the annotation content. + */ + public function getContent(): string + { + return implode('', $this->lines); + } + + public function supportTypes(): bool + { + return \in_array($this->getTag()->getName(), self::$tags, true); + } + + /** + * Get the current types content. + * + * Be careful modifying the underlying line as that won't flush the cache. + */ + private function getTypesContent(): ?string + { + if (null === $this->typesContent) { + $name = $this->getTag()->getName(); + + if (!$this->supportTypes()) { + throw new \RuntimeException('This tag does not support types.'); + } + + $matchingResult = Preg::match( + '{^(?:\h*\*|/\*\*)[\h*]*@'.$name.'\h+'.TypeExpression::REGEX_TYPES.'(?:(?:[*\h\v]|\&?[\.\$]).*)?\r?$}is', + $this->lines[0]->getContent(), + $matches + ); + + $this->typesContent = $matchingResult + ? $matches['types'] + : null; + } + + return $this->typesContent; + } + + private function clearCache(): void + { + $this->types = null; + $this->typesContent = null; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/DocBlock/DocBlock.php b/vendor/friendsofphp/php-cs-fixer/src/DocBlock/DocBlock.php new file mode 100644 index 00000000..b4ae53e2 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/DocBlock/DocBlock.php @@ -0,0 +1,250 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\DocBlock; + +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceUseAnalysis; + +/** + * This class represents a docblock. + * + * It internally splits it up into "lines" that we can manipulate. + * + * @author Graham Campbell + */ +final class DocBlock +{ + /** + * @var list + */ + private array $lines = []; + + /** + * @var null|list + */ + private ?array $annotations = null; + + private ?NamespaceAnalysis $namespace; + + /** + * @var list + */ + private array $namespaceUses; + + /** + * @param list $namespaceUses + */ + public function __construct(string $content, ?NamespaceAnalysis $namespace = null, array $namespaceUses = []) + { + foreach (Preg::split('/([^\n\r]+\R*)/', $content, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) as $line) { + $this->lines[] = new Line($line); + } + + $this->namespace = $namespace; + $this->namespaceUses = $namespaceUses; + } + + public function __toString(): string + { + return $this->getContent(); + } + + /** + * Get this docblock's lines. + * + * @return list + */ + public function getLines(): array + { + return $this->lines; + } + + /** + * Get a single line. + */ + public function getLine(int $pos): ?Line + { + return $this->lines[$pos] ?? null; + } + + /** + * Get this docblock's annotations. + * + * @return list + */ + public function getAnnotations(): array + { + if (null !== $this->annotations) { + return $this->annotations; + } + + $this->annotations = []; + $total = \count($this->lines); + + for ($index = 0; $index < $total; ++$index) { + if ($this->lines[$index]->containsATag()) { + // get all the lines that make up the annotation + $lines = \array_slice($this->lines, $index, $this->findAnnotationLength($index), true); + $annotation = new Annotation($lines, $this->namespace, $this->namespaceUses); + // move the index to the end of the annotation to avoid + // checking it again because we know the lines inside the + // current annotation cannot be part of another annotation + $index = $annotation->getEnd(); + // add the current annotation to the list of annotations + $this->annotations[] = $annotation; + } + } + + return $this->annotations; + } + + public function isMultiLine(): bool + { + return 1 !== \count($this->lines); + } + + /** + * Take a one line doc block, and turn it into a multi line doc block. + */ + public function makeMultiLine(string $indent, string $lineEnd): void + { + if ($this->isMultiLine()) { + return; + } + + $lineContent = $this->getSingleLineDocBlockEntry($this->lines[0]); + + if ('' === $lineContent) { + $this->lines = [ + new Line('/**'.$lineEnd), + new Line($indent.' *'.$lineEnd), + new Line($indent.' */'), + ]; + + return; + } + + $this->lines = [ + new Line('/**'.$lineEnd), + new Line($indent.' * '.$lineContent.$lineEnd), + new Line($indent.' */'), + ]; + } + + public function makeSingleLine(): void + { + if (!$this->isMultiLine()) { + return; + } + + $usefulLines = array_filter( + $this->lines, + static fn (Line $line): bool => $line->containsUsefulContent() + ); + + if (1 < \count($usefulLines)) { + return; + } + + $lineContent = ''; + if (\count($usefulLines) > 0) { + $lineContent = $this->getSingleLineDocBlockEntry(array_shift($usefulLines)); + } + + $this->lines = [new Line('/** '.$lineContent.' */')]; + } + + public function getAnnotation(int $pos): ?Annotation + { + $annotations = $this->getAnnotations(); + + return $annotations[$pos] ?? null; + } + + /** + * Get specific types of annotations only. + * + * @param list|string $types + * + * @return list + */ + public function getAnnotationsOfType($types): array + { + $typesToSearchFor = (array) $types; + + $annotations = []; + + foreach ($this->getAnnotations() as $annotation) { + $tagName = $annotation->getTag()->getName(); + if (\in_array($tagName, $typesToSearchFor, true)) { + $annotations[] = $annotation; + } + } + + return $annotations; + } + + /** + * Get the actual content of this docblock. + */ + public function getContent(): string + { + return implode('', $this->lines); + } + + private function findAnnotationLength(int $start): int + { + $index = $start; + + while ($line = $this->getLine(++$index)) { + if ($line->containsATag()) { + // we've 100% reached the end of the description if we get here + break; + } + + if (!$line->containsUsefulContent()) { + // if next line is also non-useful, or contains a tag, then we're done here + $next = $this->getLine($index + 1); + if (null === $next || !$next->containsUsefulContent() || $next->containsATag()) { + break; + } + // otherwise, continue, the annotation must have contained a blank line in its description + } + } + + return $index - $start; + } + + private function getSingleLineDocBlockEntry(Line $line): string + { + $lineString = $line->getContent(); + + if ('' === $lineString) { + return $lineString; + } + + $lineString = str_replace('*/', '', $lineString); + $lineString = trim($lineString); + + if (str_starts_with($lineString, '/**')) { + $lineString = substr($lineString, 3); + } elseif (str_starts_with($lineString, '*')) { + $lineString = substr($lineString, 1); + } + + return trim($lineString); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/DocBlock/Line.php b/vendor/friendsofphp/php-cs-fixer/src/DocBlock/Line.php new file mode 100644 index 00000000..1e6fa5b9 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/DocBlock/Line.php @@ -0,0 +1,128 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\DocBlock; + +use PhpCsFixer\Preg; + +/** + * This represents a line of a docblock. + * + * @author Graham Campbell + */ +final class Line +{ + /** + * The content of this line. + */ + private string $content; + + /** + * Create a new line instance. + */ + public function __construct(string $content) + { + $this->content = $content; + } + + /** + * Get the string representation of object. + */ + public function __toString(): string + { + return $this->content; + } + + /** + * Get the content of this line. + */ + public function getContent(): string + { + return $this->content; + } + + /** + * Does this line contain useful content? + * + * If the line contains text or tags, then this is true. + */ + public function containsUsefulContent(): bool + { + return Preg::match('/\\*\s*\S+/', $this->content) && '' !== trim(str_replace(['/', '*'], ' ', $this->content)); + } + + /** + * Does the line contain a tag? + * + * If this is true, then it must be the first line of an annotation. + */ + public function containsATag(): bool + { + return Preg::match('/\\*\s*@/', $this->content); + } + + /** + * Is the line the start of a docblock? + */ + public function isTheStart(): bool + { + return str_contains($this->content, '/**'); + } + + /** + * Is the line the end of a docblock? + */ + public function isTheEnd(): bool + { + return str_contains($this->content, '*/'); + } + + /** + * Set the content of this line. + */ + public function setContent(string $content): void + { + $this->content = $content; + } + + /** + * Remove this line by clearing its contents. + * + * Note that this method technically brakes the internal state of the + * docblock, but is useful when we need to retain the indices of lines + * during the execution of an algorithm. + */ + public function remove(): void + { + $this->content = ''; + } + + /** + * Append a blank docblock line to this line's contents. + * + * Note that this method technically brakes the internal state of the + * docblock, but is useful when we need to retain the indices of lines + * during the execution of an algorithm. + */ + public function addBlank(): void + { + $matched = Preg::match('/^(\h*\*)[^\r\n]*(\r?\n)$/', $this->content, $matches); + + if (!$matched) { + return; + } + + $this->content .= $matches[1].$matches[2]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/DocBlock/ShortDescription.php b/vendor/friendsofphp/php-cs-fixer/src/DocBlock/ShortDescription.php new file mode 100644 index 00000000..dbd9e5da --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/DocBlock/ShortDescription.php @@ -0,0 +1,63 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\DocBlock; + +/** + * This class represents a short description (aka summary) of a docblock. + * + * @internal + */ +final class ShortDescription +{ + /** + * The docblock containing the short description. + */ + private DocBlock $doc; + + public function __construct(DocBlock $doc) + { + $this->doc = $doc; + } + + /** + * Get the line index of the line containing the end of the short + * description, if present. + */ + public function getEnd(): ?int + { + $reachedContent = false; + + foreach ($this->doc->getLines() as $index => $line) { + // we went past a description, then hit a tag or blank line, so + // the last line of the description must be the one before this one + if ($reachedContent && ($line->containsATag() || !$line->containsUsefulContent())) { + return $index - 1; + } + + // no short description was found + if ($line->containsATag()) { + return null; + } + + // we've reached content, but need to check the next lines too + // in case the short description is multi-line + if ($line->containsUsefulContent()) { + $reachedContent = true; + } + } + + return null; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/DocBlock/Tag.php b/vendor/friendsofphp/php-cs-fixer/src/DocBlock/Tag.php new file mode 100644 index 00000000..6206718d --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/DocBlock/Tag.php @@ -0,0 +1,102 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\DocBlock; + +use PhpCsFixer\Preg; + +/** + * This represents a tag, as defined by the proposed PSR PHPDoc standard. + * + * @author Graham Campbell + * @author Jakub Kwaśniewski + */ +final class Tag +{ + /** + * All the tags defined by the proposed PSR PHPDoc standard. + */ + public const PSR_STANDARD_TAGS = [ + 'api', 'author', 'category', 'copyright', 'deprecated', 'example', + 'global', 'internal', 'license', 'link', 'method', 'package', 'param', + 'property', 'property-read', 'property-write', 'return', 'see', + 'since', 'subpackage', 'throws', 'todo', 'uses', 'var', 'version', + ]; + + /** + * The line containing the tag. + */ + private Line $line; + + /** + * The cached tag name. + */ + private ?string $name = null; + + /** + * Create a new tag instance. + */ + public function __construct(Line $line) + { + $this->line = $line; + } + + /** + * Get the tag name. + * + * This may be "param", or "return", etc. + */ + public function getName(): string + { + if (null === $this->name) { + Preg::matchAll('/@[a-zA-Z0-9_-]+(?=\s|$)/', $this->line->getContent(), $matches); + + if (isset($matches[0][0])) { + $this->name = ltrim($matches[0][0], '@'); + } else { + $this->name = 'other'; + } + } + + return $this->name; + } + + /** + * Set the tag name. + * + * This will also be persisted to the upstream line and annotation. + */ + public function setName(string $name): void + { + $current = $this->getName(); + + if ('other' === $current) { + throw new \RuntimeException('Cannot set name on unknown tag.'); + } + + $this->line->setContent(Preg::replace("/@{$current}/", "@{$name}", $this->line->getContent(), 1)); + + $this->name = $name; + } + + /** + * Is the tag a known tag? + * + * This is defined by if it exists in the proposed PSR PHPDoc standard. + */ + public function valid(): bool + { + return \in_array($this->getName(), self::PSR_STANDARD_TAGS, true); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/DocBlock/TagComparator.php b/vendor/friendsofphp/php-cs-fixer/src/DocBlock/TagComparator.php new file mode 100644 index 00000000..578fd3a7 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/DocBlock/TagComparator.php @@ -0,0 +1,64 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\DocBlock; + +/** + * This class is responsible for comparing tags to see if they should be kept + * together, or kept apart. + * + * @author Graham Campbell + * @author Jakub Kwaśniewski + * + * @deprecated + */ +final class TagComparator +{ + /** + * Groups of tags that should be allowed to immediately follow each other. + * + * @internal + */ + public const DEFAULT_GROUPS = [ + ['deprecated', 'link', 'see', 'since'], + ['author', 'copyright', 'license'], + ['category', 'package', 'subpackage'], + ['property', 'property-read', 'property-write'], + ]; + + /** + * Should the given tags be kept together, or kept apart? + * + * @param string[][] $groups + */ + public static function shouldBeTogether(Tag $first, Tag $second, array $groups = self::DEFAULT_GROUPS): bool + { + @trigger_error('Method '.__METHOD__.' is deprecated and will be removed in version 4.0.', E_USER_DEPRECATED); + + $firstName = $first->getName(); + $secondName = $second->getName(); + + if ($firstName === $secondName) { + return true; + } + + foreach ($groups as $group) { + if (\in_array($firstName, $group, true) && \in_array($secondName, $group, true)) { + return true; + } + } + + return false; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/DocBlock/TypeExpression.php b/vendor/friendsofphp/php-cs-fixer/src/DocBlock/TypeExpression.php new file mode 100644 index 00000000..e98f816d --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/DocBlock/TypeExpression.php @@ -0,0 +1,614 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\DocBlock; + +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceUseAnalysis; +use PhpCsFixer\Utils; + +/** + * @author Michael Vorisek + * + * @internal + */ +final class TypeExpression +{ + /** + * Regex to match any PHP identifier. + * + * @internal + */ + public const REGEX_IDENTIFIER = '(?:(?!(?(?x) # one or several types separated by `|` or `&` +'.self::REGEX_TYPE.' + (?: + \h*(?[|&])\h* + (?&type) + )*+ + )'; + + private const REGEX_TYPE = '(?(?x) # single type + (?\??\h*) + (?: + (? + (?(?i)(?:array|list|object)(?-i)\h*\{\h*) + (? + (? + (?(?:(?&constant)|(?&identifier)|(?&name))\h*\??\h*:\h*|) + (?(?&types_inner)) + ) + (?: + \h*,\h* + (?&array_shape_inner) + )* + (?:\h*,\h*)? + |) + \h*\} + ) + | + (? # callable syntax, e.g. `callable(string, int...): bool` + (?(?&name)\h*\(\h*) + (? + (? + (?(?&types_inner)) + (?\h*&|) + (?\h*\.\.\.|) + (?\h*\$(?&identifier)|) + (?\h*=|) + ) + (?: + \h*,\h* + (?&callable_argument) + )* + (?:\h*,\h*)? + |) + \h*\) + (?: + \h*\:\h* + (?(?&type)) + )? + ) + | + (? # generic syntax, e.g.: `array` + (?(?&name)\h*<\h*) + (? + (?&types_inner) + (?: + \h*,\h* + (?&types_inner) + )* + (?:\h*,\h*)? + ) + \h*> + ) + | + (? # class constants with optional wildcard, e.g.: `Foo::*`, `Foo::CONST_A`, `FOO::CONST_*` + (?&name)::\*?(?:(?&identifier)\*?)* + ) + | + (? # single constant value (case insensitive), e.g.: 1, -1.8E+6, `\'a\'` + (?i) + # all sorts of numbers: with or without sign, supports literal separator and several numeric systems, + # e.g.: 1, +1.1, 1., .1, -1, 123E+8, 123_456_789, 0x7Fb4, 0b0110, 0o777 + [+-]?(?: + (?:0b[01]++(?:_[01]++)*+) + | (?:0o[0-7]++(?:_[0-7]++)*+) + | (?:0x[\da-f]++(?:_[\da-f]++)*+) + | (?:(?\d++(?:_\d++)*+)|(?=\.\d)) + (?:\.(?&constant_digits)|(?<=\d)\.)?+ + (?:e[+-]?(?&constant_digits))?+ + ) + | \'(?:[^\'\\\\]|\\\\.)*+\' + | "(?:[^"\\\\]|\\\\.)*+" + (?-i) + ) + | + (? # self reference, e.g.: $this, $self, @static + (?i) + [@$](?:this | self | static) + (?-i) + ) + | + (? # full name, e.g.: `int`, `\DateTime`, `\Foo\Bar`, `positive-int` + \\\\?+ + (?'.self::REGEX_IDENTIFIER.') + (?:[\\\\\-](?&identifier))*+ + ) + | + (? # parenthesized type, e.g.: `(int)`, `(int|\stdClass)` + (? + \(\h* + ) + (?: + (? + (?&types_inner) + ) + | + (? # conditional type, e.g.: `$foo is \Throwable ? false : $foo` + (? + (?:\$(?&identifier)) + | + (?(?&types_inner)) + ) + (? + \h+(?i)is(?:\h+not)?(?-i)\h+ + ) + (?(?&types_inner)) + (?\h*\?\h*) + (?(?&types_inner)) + (?\h*:\h*) + (?(?&types_inner)) + ) + ) + \h*\) + ) + ) + (? # array, e.g.: `string[]`, `array[][]` + (\h*\[\h*\])* + ) + (?:(?=1)0 + (? + (?&type) + (?: + \h*[|&]\h* + (?&type) + )*+ + ) + |) + )'; + + private string $value; + + private bool $isUnionType = false; + + private string $typesGlue = '|'; + + /** + * @var list + */ + private array $innerTypeExpressions = []; + + private ?NamespaceAnalysis $namespace; + + /** + * @var NamespaceUseAnalysis[] + */ + private array $namespaceUses; + + /** + * @param NamespaceUseAnalysis[] $namespaceUses + */ + public function __construct(string $value, ?NamespaceAnalysis $namespace, array $namespaceUses) + { + $this->value = $value; + $this->namespace = $namespace; + $this->namespaceUses = $namespaceUses; + + $this->parse(); + } + + public function toString(): string + { + return $this->value; + } + + /** + * @return list + */ + public function getTypes(): array + { + if ($this->isUnionType) { + return array_map( + static fn (array $type) => $type['expression']->toString(), + $this->innerTypeExpressions, + ); + } + + return [$this->value]; + } + + public function isUnionType(): bool + { + return $this->isUnionType; + } + + public function getTypesGlue(): string + { + return $this->typesGlue; + } + + /** + * @param \Closure(self): void $callback + */ + public function walkTypes(\Closure $callback): void + { + foreach (array_reverse($this->innerTypeExpressions) as [ + 'start_index' => $startIndex, + 'expression' => $inner, + ]) { + $initialValueLength = \strlen($inner->toString()); + + $inner->walkTypes($callback); + + $this->value = substr_replace( + $this->value, + $inner->toString(), + $startIndex, + $initialValueLength + ); + } + + $callback($this); + } + + /** + * @param \Closure(self, self): (-1|0|1) $compareCallback + */ + public function sortTypes(\Closure $compareCallback): void + { + $this->walkTypes(static function (self $type) use ($compareCallback): void { + if ($type->isUnionType) { + $type->innerTypeExpressions = Utils::stableSort( + $type->innerTypeExpressions, + static fn (array $type): self => $type['expression'], + $compareCallback, + ); + + $type->value = implode($type->getTypesGlue(), $type->getTypes()); + } + }); + } + + public function getCommonType(): ?string + { + $aliases = $this->getAliases(); + + $mainType = null; + + foreach ($this->getTypes() as $type) { + if ('null' === $type) { + continue; + } + + if (str_starts_with($type, '?')) { + $type = substr($type, 1); + } + + if (Preg::match('/\[\h*\]$/', $type)) { + $type = 'array'; + } elseif (Preg::match('/^(.+?)\h*[<{(]/', $type, $matches)) { + $type = $matches[1]; + } + + if (isset($aliases[$type])) { + $type = $aliases[$type]; + } + + if (null === $mainType || $type === $mainType) { + $mainType = $type; + + continue; + } + + $mainType = $this->getParentType($type, $mainType); + + if (null === $mainType) { + return null; + } + } + + return $mainType; + } + + public function allowsNull(): bool + { + foreach ($this->getTypes() as $type) { + if (\in_array($type, ['null', 'mixed'], true) || str_starts_with($type, '?')) { + return true; + } + } + + return false; + } + + private function parse(): void + { + $index = 0; + while (true) { + Preg::match( + '{\G'.self::REGEX_TYPE.'(?:\h*(?[|&])\h*|$)}', + $this->value, + $matches, + PREG_OFFSET_CAPTURE, + $index + ); + + if ([] === $matches) { + throw new \Exception('Unable to parse phpdoc type '.var_export($this->value, true)); + } + + if (!$this->isUnionType) { + if (($matches['glue'][0] ?? '') === '') { + break; + } + + $this->isUnionType = true; + $this->typesGlue = $matches['glue'][0]; + } + + $this->innerTypeExpressions[] = [ + 'start_index' => $index, + 'expression' => $this->inner($matches['type'][0]), + ]; + + $consumedValueLength = \strlen($matches[0][0]); + $index += $consumedValueLength; + + if (\strlen($this->value) === $index) { + return; + } + } + + $nullableLength = \strlen($matches['nullable'][0]); + $index = $nullableLength; + + if ('' !== ($matches['generic'][0] ?? '') && $matches['generic'][1] === $nullableLength) { + $this->parseCommaSeparatedInnerTypes( + $index + \strlen($matches['generic_start'][0]), + $matches['generic_types'][0] + ); + } elseif ('' !== ($matches['callable'][0] ?? '') && $matches['callable'][1] === $nullableLength) { + $this->parseCallableArgumentTypes( + $index + \strlen($matches['callable_start'][0]), + $matches['callable_arguments'][0] + ); + + if ('' !== ($matches['callable_return'][0] ?? '')) { + $this->innerTypeExpressions[] = [ + 'start_index' => \strlen($this->value) - \strlen($matches['callable_return'][0]), + 'expression' => $this->inner($matches['callable_return'][0]), + ]; + } + } elseif ('' !== ($matches['array_shape'][0] ?? '') && $matches['array_shape'][1] === $nullableLength) { + $this->parseArrayShapeInnerTypes( + $index + \strlen($matches['array_shape_start'][0]), + $matches['array_shape_inners'][0] + ); + } elseif ('' !== ($matches['parenthesized'][0] ?? '') && $matches['parenthesized'][1] === $nullableLength) { + $index += \strlen($matches['parenthesized_start'][0]); + + if ('' !== ($matches['conditional'][0] ?? '')) { + if ('' !== ($matches['conditional_cond_left_types'][0] ?? '')) { + $this->innerTypeExpressions[] = [ + 'start_index' => $index, + 'expression' => $this->inner($matches['conditional_cond_left_types'][0]), + ]; + } + + $index += \strlen($matches['conditional_cond_left'][0]) + \strlen($matches['conditional_cond_middle'][0]); + + $this->innerTypeExpressions[] = [ + 'start_index' => $index, + 'expression' => $this->inner($matches['conditional_cond_right_types'][0]), + ]; + + $index += \strlen($matches['conditional_cond_right_types'][0]) + \strlen($matches['conditional_true_start'][0]); + + $this->innerTypeExpressions[] = [ + 'start_index' => $index, + 'expression' => $this->inner($matches['conditional_true_types'][0]), + ]; + + $index += \strlen($matches['conditional_true_types'][0]) + \strlen($matches['conditional_false_start'][0]); + + $this->innerTypeExpressions[] = [ + 'start_index' => $index, + 'expression' => $this->inner($matches['conditional_false_types'][0]), + ]; + } else { + $this->innerTypeExpressions[] = [ + 'start_index' => $index, + 'expression' => $this->inner($matches['parenthesized_types'][0]), + ]; + } + } + } + + private function parseCommaSeparatedInnerTypes(int $startIndex, string $value): void + { + $index = 0; + while (\strlen($value) !== $index) { + Preg::match( + '{\G'.self::REGEX_TYPES.'(?:\h*,\h*|$)}', + $value, + $matches, + 0, + $index + ); + + $this->innerTypeExpressions[] = [ + 'start_index' => $startIndex + $index, + 'expression' => $this->inner($matches['types']), + ]; + + $index += \strlen($matches[0]); + } + } + + private function parseCallableArgumentTypes(int $startIndex, string $value): void + { + $index = 0; + while (\strlen($value) !== $index) { + Preg::match( + '{\G(?:(?=1)0'.self::REGEX_TYPES.'|(?<_callable_argument>(?&callable_argument))(?:\h*,\h*|$))}', + $value, + $prematches, + 0, + $index + ); + $consumedValue = $prematches['_callable_argument']; + $consumedValueLength = \strlen($consumedValue); + $consumedCommaLength = \strlen($prematches[0]) - $consumedValueLength; + + $addedPrefix = 'Closure('; + Preg::match( + '{^'.self::REGEX_TYPES.'$}', + $addedPrefix.$consumedValue.'): void', + $matches, + PREG_OFFSET_CAPTURE + ); + + $this->innerTypeExpressions[] = [ + 'start_index' => $startIndex + $index, + 'expression' => $this->inner($matches['callable_argument_type'][0]), + ]; + + $index += $consumedValueLength + $consumedCommaLength; + } + } + + private function parseArrayShapeInnerTypes(int $startIndex, string $value): void + { + $index = 0; + while (\strlen($value) !== $index) { + Preg::match( + '{\G(?:(?=1)0'.self::REGEX_TYPES.'|(?<_array_shape_inner>(?&array_shape_inner))(?:\h*,\h*|$))}', + $value, + $prematches, + 0, + $index + ); + $consumedValue = $prematches['_array_shape_inner']; + $consumedValueLength = \strlen($consumedValue); + $consumedCommaLength = \strlen($prematches[0]) - $consumedValueLength; + + $addedPrefix = 'array{'; + Preg::match( + '{^'.self::REGEX_TYPES.'$}', + $addedPrefix.$consumedValue.'}', + $matches, + PREG_OFFSET_CAPTURE + ); + + $this->innerTypeExpressions[] = [ + 'start_index' => $startIndex + $index + $matches['array_shape_inner_value'][1] - \strlen($addedPrefix), + 'expression' => $this->inner($matches['array_shape_inner_value'][0]), + ]; + + $index += $consumedValueLength + $consumedCommaLength; + } + } + + private function inner(string $value): self + { + return new self($value, $this->namespace, $this->namespaceUses); + } + + private function getParentType(string $type1, string $type2): ?string + { + $types = [ + $this->normalize($type1), + $this->normalize($type2), + ]; + natcasesort($types); + $types = implode('|', $types); + + $parents = [ + 'array|Traversable' => 'iterable', + 'array|iterable' => 'iterable', + 'iterable|Traversable' => 'iterable', + 'self|static' => 'self', + ]; + + return $parents[$types] ?? null; + } + + private function normalize(string $type): string + { + $aliases = $this->getAliases(); + + if (isset($aliases[$type])) { + return $aliases[$type]; + } + + if (\in_array($type, [ + 'array', + 'bool', + 'callable', + 'false', + 'float', + 'int', + 'iterable', + 'mixed', + 'never', + 'null', + 'object', + 'resource', + 'string', + 'true', + 'void', + ], true)) { + return $type; + } + + if (Preg::match('/\[\]$/', $type)) { + return 'array'; + } + + if (Preg::match('/^(.+?)namespaceUses as $namespaceUse) { + if ($namespaceUse->getShortName() === $type) { + return $namespaceUse->getFullName(); + } + } + + if (null === $this->namespace || $this->namespace->isGlobalNamespace()) { + return $type; + } + + return "{$this->namespace->getFullName()}\\{$type}"; + } + + /** + * @return array + */ + private function getAliases(): array + { + return [ + 'boolean' => 'bool', + 'callback' => 'callable', + 'double' => 'float', + 'false' => 'bool', + 'integer' => 'int', + 'list' => 'array', + 'real' => 'float', + 'true' => 'bool', + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Doctrine/Annotation/DocLexer.php b/vendor/friendsofphp/php-cs-fixer/src/Doctrine/Annotation/DocLexer.php new file mode 100644 index 00000000..ef1878ea --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Doctrine/Annotation/DocLexer.php @@ -0,0 +1,173 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Doctrine\Annotation; + +use PhpCsFixer\Preg; + +/** + * Copyright (c) 2006-2013 Doctrine Project. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * @internal + */ +final class DocLexer +{ + public const T_NONE = 1; + public const T_INTEGER = 2; + public const T_STRING = 3; + public const T_FLOAT = 4; + + // All tokens that are also identifiers should be >= 100 + public const T_IDENTIFIER = 100; + public const T_AT = 101; + public const T_CLOSE_CURLY_BRACES = 102; + public const T_CLOSE_PARENTHESIS = 103; + public const T_COMMA = 104; + public const T_EQUALS = 105; + public const T_FALSE = 106; + public const T_NAMESPACE_SEPARATOR = 107; + public const T_OPEN_CURLY_BRACES = 108; + public const T_OPEN_PARENTHESIS = 109; + public const T_TRUE = 110; + public const T_NULL = 111; + public const T_COLON = 112; + public const T_MINUS = 113; + + /** @var array */ + private array $noCase = [ + '@' => self::T_AT, + ',' => self::T_COMMA, + '(' => self::T_OPEN_PARENTHESIS, + ')' => self::T_CLOSE_PARENTHESIS, + '{' => self::T_OPEN_CURLY_BRACES, + '}' => self::T_CLOSE_CURLY_BRACES, + '=' => self::T_EQUALS, + ':' => self::T_COLON, + '-' => self::T_MINUS, + '\\' => self::T_NAMESPACE_SEPARATOR, + ]; + + /** @var list */ + private array $tokens = []; + + private int $position = 0; + + private int $peek = 0; + + private ?string $regex = null; + + public function setInput(string $input): void + { + $this->tokens = []; + $this->reset(); + $this->scan($input); + } + + public function reset(): void + { + $this->peek = 0; + $this->position = 0; + } + + public function peek(): ?Token + { + if (isset($this->tokens[$this->position + $this->peek])) { + return $this->tokens[$this->position + $this->peek++]; + } + + return null; + } + + /** + * @return list + */ + private function getCatchablePatterns(): array + { + return [ + '[a-z_\\\][a-z0-9_\:\\\]*[a-z_][a-z0-9_]*', + '(?:[+-]?[0-9]+(?:[\.][0-9]+)*)(?:[eE][+-]?[0-9]+)?', + '"(?:""|[^"])*+"', + ]; + } + + /** + * @return list + */ + private function getNonCatchablePatterns(): array + { + return ['\s+', '\*+', '(.)']; + } + + /** + * @return self::T_* + */ + private function getType(string &$value): int + { + $type = self::T_NONE; + + if ('"' === $value[0]) { + $value = str_replace('""', '"', substr($value, 1, \strlen($value) - 2)); + + return self::T_STRING; + } + + if (isset($this->noCase[$value])) { + return $this->noCase[$value]; + } + + if ('_' === $value[0] || '\\' === $value[0] || !Preg::match('/[^A-Za-z]/', $value[0])) { + return self::T_IDENTIFIER; + } + + if (is_numeric($value)) { + return str_contains($value, '.') || false !== stripos($value, 'e') + ? self::T_FLOAT : self::T_INTEGER; + } + + return $type; + } + + private function scan(string $input): void + { + if (!isset($this->regex)) { + $this->regex = sprintf( + '/(%s)|%s/%s', + implode(')|(', $this->getCatchablePatterns()), + implode('|', $this->getNonCatchablePatterns()), + 'iu' + ); + } + + $flags = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE; + $matches = Preg::split($this->regex, $input, -1, $flags); + + foreach ($matches as $match) { + // Must remain before 'value' assignment since it can change content + $firstMatch = $match[0]; + $type = $this->getType($firstMatch); + + $this->tokens[] = new Token($type, $firstMatch, (int) $match[1]); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Doctrine/Annotation/Token.php b/vendor/friendsofphp/php-cs-fixer/src/Doctrine/Annotation/Token.php new file mode 100644 index 00000000..314df6b2 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Doctrine/Annotation/Token.php @@ -0,0 +1,87 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Doctrine\Annotation; + +/** + * A Doctrine annotation token. + * + * @internal + */ +final class Token +{ + private int $type; + + private string $content; + + private int $position; + + /** + * @param int $type The type + * @param string $content The content + */ + public function __construct(int $type = DocLexer::T_NONE, string $content = '', int $position = 0) + { + $this->type = $type; + $this->content = $content; + $this->position = $position; + } + + public function getType(): int + { + return $this->type; + } + + public function setType(int $type): void + { + $this->type = $type; + } + + public function getContent(): string + { + return $this->content; + } + + public function setContent(string $content): void + { + $this->content = $content; + } + + public function getPosition(): int + { + return $this->position; + } + + /** + * Returns whether the token type is one of the given types. + * + * @param int|int[] $types + */ + public function isType($types): bool + { + if (!\is_array($types)) { + $types = [$types]; + } + + return \in_array($this->getType(), $types, true); + } + + /** + * Overrides the content with an empty string. + */ + public function clear(): void + { + $this->setContent(''); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Doctrine/Annotation/Tokens.php b/vendor/friendsofphp/php-cs-fixer/src/Doctrine/Annotation/Tokens.php new file mode 100644 index 00000000..1928d2ae --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Doctrine/Annotation/Tokens.php @@ -0,0 +1,303 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Doctrine\Annotation; + +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token as PhpToken; + +/** + * A list of Doctrine annotation tokens. + * + * @internal + * + * @extends \SplFixedArray + */ +final class Tokens extends \SplFixedArray +{ + /** + * @param string[] $ignoredTags + * + * @throws \InvalidArgumentException + */ + public static function createFromDocComment(PhpToken $input, array $ignoredTags = []): self + { + if (!$input->isGivenKind(T_DOC_COMMENT)) { + throw new \InvalidArgumentException('Input must be a T_DOC_COMMENT token.'); + } + + $tokens = []; + + $content = $input->getContent(); + $ignoredTextPosition = 0; + $currentPosition = 0; + $token = null; + while (false !== $nextAtPosition = strpos($content, '@', $currentPosition)) { + if (0 !== $nextAtPosition && !Preg::match('/\s/', $content[$nextAtPosition - 1])) { + $currentPosition = $nextAtPosition + 1; + + continue; + } + + $lexer = new DocLexer(); + $lexer->setInput(substr($content, $nextAtPosition)); + + $scannedTokens = []; + $index = 0; + $nbScannedTokensToUse = 0; + $nbScopes = 0; + while (null !== $token = $lexer->peek()) { + if (0 === $index && !$token->isType(DocLexer::T_AT)) { + break; + } + + if (1 === $index) { + if (!$token->isType(DocLexer::T_IDENTIFIER) || \in_array($token->getContent(), $ignoredTags, true)) { + break; + } + + $nbScannedTokensToUse = 2; + } + + if ($index >= 2 && 0 === $nbScopes && !$token->isType([DocLexer::T_NONE, DocLexer::T_OPEN_PARENTHESIS])) { + break; + } + + $scannedTokens[] = $token; + + if ($token->isType(DocLexer::T_OPEN_PARENTHESIS)) { + ++$nbScopes; + } elseif ($token->isType(DocLexer::T_CLOSE_PARENTHESIS)) { + if (0 === --$nbScopes) { + $nbScannedTokensToUse = \count($scannedTokens); + + break; + } + } + + ++$index; + } + + if (0 !== $nbScopes) { + break; + } + + if (0 !== $nbScannedTokensToUse) { + $ignoredTextLength = $nextAtPosition - $ignoredTextPosition; + if (0 !== $ignoredTextLength) { + $tokens[] = new Token(DocLexer::T_NONE, substr($content, $ignoredTextPosition, $ignoredTextLength)); + } + + $lastTokenEndIndex = 0; + foreach (\array_slice($scannedTokens, 0, $nbScannedTokensToUse) as $token) { + if ($token->isType(DocLexer::T_STRING)) { + $token = new Token( + $token->getType(), + '"'.str_replace('"', '""', $token->getContent()).'"', + $token->getPosition() + ); + } + + $missingTextLength = $token->getPosition() - $lastTokenEndIndex; + if ($missingTextLength > 0) { + $tokens[] = new Token(DocLexer::T_NONE, substr( + $content, + $nextAtPosition + $lastTokenEndIndex, + $missingTextLength + )); + } + + $tokens[] = new Token($token->getType(), $token->getContent()); + $lastTokenEndIndex = $token->getPosition() + \strlen($token->getContent()); + } + + $currentPosition = $ignoredTextPosition = $nextAtPosition + $token->getPosition() + \strlen($token->getContent()); + } else { + $currentPosition = $nextAtPosition + 1; + } + } + + if ($ignoredTextPosition < \strlen($content)) { + $tokens[] = new Token(DocLexer::T_NONE, substr($content, $ignoredTextPosition)); + } + + return self::fromArray($tokens); + } + + /** + * Create token collection from array. + * + * @param array $array the array to import + * @param ?bool $saveIndices save the numeric indices used in the original array, default is yes + */ + public static function fromArray($array, $saveIndices = null): self + { + $tokens = new self(\count($array)); + + if (null === $saveIndices || $saveIndices) { + foreach ($array as $key => $val) { + $tokens[$key] = $val; + } + } else { + $index = 0; + + foreach ($array as $val) { + $tokens[$index++] = $val; + } + } + + return $tokens; + } + + /** + * Returns the index of the closest next token that is neither a comment nor a whitespace token. + */ + public function getNextMeaningfulToken(int $index): ?int + { + return $this->getMeaningfulTokenSibling($index, 1); + } + + /** + * Returns the index of the closest previous token that is neither a comment nor a whitespace token. + */ + public function getPreviousMeaningfulToken(int $index): ?int + { + return $this->getMeaningfulTokenSibling($index, -1); + } + + /** + * Returns the index of the last token that is part of the annotation at the given index. + */ + public function getAnnotationEnd(int $index): ?int + { + $currentIndex = null; + + if (isset($this[$index + 2])) { + if ($this[$index + 2]->isType(DocLexer::T_OPEN_PARENTHESIS)) { + $currentIndex = $index + 2; + } elseif ( + isset($this[$index + 3]) + && $this[$index + 2]->isType(DocLexer::T_NONE) + && $this[$index + 3]->isType(DocLexer::T_OPEN_PARENTHESIS) + && Preg::match('/^(\R\s*\*\s*)*\s*$/', $this[$index + 2]->getContent()) + ) { + $currentIndex = $index + 3; + } + } + + if (null !== $currentIndex) { + $level = 0; + for ($max = \count($this); $currentIndex < $max; ++$currentIndex) { + if ($this[$currentIndex]->isType(DocLexer::T_OPEN_PARENTHESIS)) { + ++$level; + } elseif ($this[$currentIndex]->isType(DocLexer::T_CLOSE_PARENTHESIS)) { + --$level; + } + + if (0 === $level) { + return $currentIndex; + } + } + + return null; + } + + return $index + 1; + } + + /** + * Returns the code from the tokens. + */ + public function getCode(): string + { + $code = ''; + foreach ($this as $token) { + $code .= $token->getContent(); + } + + return $code; + } + + /** + * Inserts a token at the given index. + */ + public function insertAt(int $index, Token $token): void + { + $this->setSize($this->getSize() + 1); + + for ($i = $this->getSize() - 1; $i > $index; --$i) { + $this[$i] = $this[$i - 1] ?? new Token(); + } + + $this[$index] = $token; + } + + public function offsetSet($index, $token): void + { + if (null === $token) { + throw new \InvalidArgumentException('Token must be an instance of PhpCsFixer\\Doctrine\\Annotation\\Token, "null" given.'); + } + + if (!$token instanceof Token) { + $type = \gettype($token); + + if ('object' === $type) { + $type = \get_class($token); + } + + throw new \InvalidArgumentException(sprintf('Token must be an instance of PhpCsFixer\\Doctrine\\Annotation\\Token, "%s" given.', $type)); + } + + parent::offsetSet($index, $token); + } + + /** + * @param mixed $index + * + * @throws \OutOfBoundsException + */ + public function offsetUnset($index): void + { + if (!isset($this[$index])) { + throw new \OutOfBoundsException(sprintf('Index "%s" is invalid or does not exist.', $index)); + } + + $max = \count($this) - 1; + while ($index < $max) { + $this[$index] = $this[$index + 1]; + ++$index; + } + + parent::offsetUnset($index); + + $this->setSize($max); + } + + private function getMeaningfulTokenSibling(int $index, int $direction): ?int + { + while (true) { + $index += $direction; + + if (!$this->offsetExists($index)) { + break; + } + + if (!$this[$index]->isType(DocLexer::T_NONE)) { + return $index; + } + } + + return null; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Documentation/DocumentationLocator.php b/vendor/friendsofphp/php-cs-fixer/src/Documentation/DocumentationLocator.php new file mode 100644 index 00000000..fb35fe43 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Documentation/DocumentationLocator.php @@ -0,0 +1,80 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Documentation; + +use PhpCsFixer\Fixer\FixerInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Utils; + +/** + * @internal + */ +final class DocumentationLocator +{ + private string $path; + + public function __construct() + { + $this->path = \dirname(__DIR__, 2).'/doc'; + } + + public function getFixersDocumentationDirectoryPath(): string + { + return $this->path.'/rules'; + } + + public function getFixersDocumentationIndexFilePath(): string + { + return $this->getFixersDocumentationDirectoryPath().'/index.rst'; + } + + public function getFixerDocumentationFilePath(FixerInterface $fixer): string + { + return $this->getFixersDocumentationDirectoryPath().'/'.Preg::replaceCallback( + '/^.*\\\\(.+)\\\\(.+)Fixer$/', + static fn (array $matches): string => Utils::camelCaseToUnderscore($matches[1]).'/'.Utils::camelCaseToUnderscore($matches[2]), + \get_class($fixer) + ).'.rst'; + } + + public function getFixerDocumentationFileRelativePath(FixerInterface $fixer): string + { + return Preg::replace( + '#^'.preg_quote($this->getFixersDocumentationDirectoryPath(), '#').'/#', + '', + $this->getFixerDocumentationFilePath($fixer) + ); + } + + public function getRuleSetsDocumentationDirectoryPath(): string + { + return $this->path.'/ruleSets'; + } + + public function getRuleSetsDocumentationIndexFilePath(): string + { + return $this->getRuleSetsDocumentationDirectoryPath().'/index.rst'; + } + + public function getRuleSetsDocumentationFilePath(string $name): string + { + return $this->getRuleSetsDocumentationDirectoryPath().'/'.str_replace(':risky', 'Risky', ucfirst(substr($name, 1))).'.rst'; + } + + public function getUsageFilePath(): string + { + return $this->path.'/usage.rst'; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Documentation/FixerDocumentGenerator.php b/vendor/friendsofphp/php-cs-fixer/src/Documentation/FixerDocumentGenerator.php new file mode 100644 index 00000000..c2c5e8dd --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Documentation/FixerDocumentGenerator.php @@ -0,0 +1,410 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Documentation; + +use PhpCsFixer\Console\Command\HelpCommand; +use PhpCsFixer\Differ\FullDiffer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; +use PhpCsFixer\Fixer\ExperimentalFixerInterface; +use PhpCsFixer\Fixer\FixerInterface; +use PhpCsFixer\FixerConfiguration\AliasedFixerOption; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\DeprecatedFixerOptionInterface; +use PhpCsFixer\FixerDefinition\CodeSampleInterface; +use PhpCsFixer\FixerDefinition\FileSpecificCodeSampleInterface; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSampleInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\RuleSet\RuleSet; +use PhpCsFixer\RuleSet\RuleSets; +use PhpCsFixer\StdinFileInfo; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Utils; + +/** + * @internal + */ +final class FixerDocumentGenerator +{ + private DocumentationLocator $locator; + + private FullDiffer $differ; + + public function __construct(DocumentationLocator $locator) + { + $this->locator = $locator; + $this->differ = new FullDiffer(); + } + + public function generateFixerDocumentation(FixerInterface $fixer): string + { + $name = $fixer->getName(); + $title = "Rule ``{$name}``"; + $titleLine = str_repeat('=', \strlen($title)); + $doc = "{$titleLine}\n{$title}\n{$titleLine}"; + + $definition = $fixer->getDefinition(); + $doc .= "\n\n".RstUtils::toRst($definition->getSummary()); + + $description = $definition->getDescription(); + + if (null !== $description) { + $description = RstUtils::toRst($description); + $doc .= <<getSuccessorsNames(); + + if (0 !== \count($alternatives)) { + $deprecationDescription .= RstUtils::toRst(sprintf( + "\n\nYou should use %s instead.", + Utils::naturalLanguageJoinWithBackticks($alternatives) + ), 0); + } + } + + $experimentalDescription = ''; + + if ($fixer instanceof ExperimentalFixerInterface) { + $experimentalDescriptionRaw = RstUtils::toRst('Rule is not covered with backward compatibility promise, use it at your own risk. Rule\'s behaviour may be changed at any point, including rule\'s name; its options\' names, availability and allowed values; its default configuration. Rule may be even removed without prior notice. Feel free to provide feedback and help with determining final state of the rule.', 0); + $experimentalDescription = <<getRiskyDescription(); + + if (null !== $riskyDescriptionRaw) { + $riskyDescriptionRaw = RstUtils::toRst($riskyDescriptionRaw, 0); + $riskyDescription = <<getConfigurationDefinition(); + + foreach ($configurationDefinition->getOptions() as $option) { + $optionInfo = "``{$option->getName()}``"; + $optionInfo .= "\n".str_repeat('~', \strlen($optionInfo)); + + if ($option instanceof DeprecatedFixerOptionInterface) { + $deprecationMessage = RstUtils::toRst($option->getDeprecationMessage()); + $optionInfo .= "\n\n.. warning:: This option is deprecated and will be removed in the next major version. {$deprecationMessage}"; + } + + $optionInfo .= "\n\n".RstUtils::toRst($option->getDescription()); + + if ($option instanceof AliasedFixerOption) { + $optionInfo .= "\n\n.. note:: The previous name of this option was ``{$option->getAlias()}`` but it is now deprecated and will be removed in the next major version."; + } + + $allowed = HelpCommand::getDisplayableAllowedValues($option); + + if (null === $allowed) { + $allowedKind = 'Allowed types'; + $allowed = array_map( + static fn ($value): string => '``'.$value.'``', + $option->getAllowedTypes(), + ); + } else { + $allowedKind = 'Allowed values'; + $allowed = array_map(static fn ($value): string => $value instanceof AllowedValueSubset + ? 'a subset of ``'.Utils::toString($value->getAllowedValues()).'``' + : '``'.Utils::toString($value).'``', $allowed); + } + + $allowed = Utils::naturalLanguageJoin($allowed, ''); + $optionInfo .= "\n\n{$allowedKind}: {$allowed}"; + + if ($option->hasDefault()) { + $default = Utils::toString($option->getDefault()); + $optionInfo .= "\n\nDefault value: ``{$default}``"; + } else { + $optionInfo .= "\n\nThis option is required."; + } + + $doc .= "\n\n{$optionInfo}"; + } + } + + $samples = $definition->getCodeSamples(); + + if (0 !== \count($samples)) { + $doc .= <<<'RST' + + + Examples + -------- + RST; + + foreach ($samples as $index => $sample) { + $title = sprintf('Example #%d', $index + 1); + $titleLine = str_repeat('~', \strlen($title)); + $doc .= "\n\n{$title}\n{$titleLine}"; + + if ($fixer instanceof ConfigurableFixerInterface) { + if (null === $sample->getConfiguration()) { + $doc .= "\n\n*Default* configuration."; + } else { + $doc .= sprintf( + "\n\nWith configuration: ``%s``.", + Utils::toString($sample->getConfiguration()) + ); + } + } + + $doc .= "\n".$this->generateSampleDiff($fixer, $sample, $index + 1, $name); + } + } + + $ruleSetConfigs = self::getSetsOfRule($name); + + if ([] !== $ruleSetConfigs) { + $plural = 1 !== \count($ruleSetConfigs) ? 's' : ''; + $doc .= << $config) { + $ruleSetPath = $this->locator->getRuleSetsDocumentationFilePath($set); + $ruleSetPath = substr($ruleSetPath, strrpos($ruleSetPath, '/')); + + $configInfo = (null !== $config) + ? " with config:\n\n ``".Utils::toString($config)."``\n" + : ''; + + $doc .= <<`_{$configInfo}\n + RST; + } + } + + $reflectionObject = new \ReflectionObject($fixer); + $className = str_replace('\\', '\\\\', $reflectionObject->getName()); + $fileName = $reflectionObject->getFileName(); + $fileName = str_replace('\\', '/', $fileName); + $fileName = substr($fileName, strrpos($fileName, '/src/Fixer/') + 1); + $fileName = "`{$className} <./../../../{$fileName}>`_"; + + $testFileName = Preg::replace('~.*\K/src/(?=Fixer/)~', '/tests/', $fileName); + $testFileName = Preg::replace('~PhpCsFixer\\\\\\\\\K(?=Fixer\\\\\\\\)~', 'Tests\\\\\\\\', $testFileName); + $testFileName = Preg::replace('~(?= <|\.php>)~', 'Test', $testFileName); + + $doc .= <<', $doc); + + return "{$doc}\n"; + } + + /** + * @internal + * + * @return array> + */ + public static function getSetsOfRule(string $ruleName): array + { + $ruleSetConfigs = []; + + foreach (RuleSets::getSetDefinitionNames() as $set) { + $ruleSet = new RuleSet([$set => true]); + + if ($ruleSet->hasRule($ruleName)) { + $ruleSetConfigs[$set] = $ruleSet->getRuleConfiguration($ruleName); + } + } + + return $ruleSetConfigs; + } + + /** + * @param FixerInterface[] $fixers + */ + public function generateFixersDocumentationIndex(array $fixers): string + { + $overrideGroups = [ + 'PhpUnit' => 'PHPUnit', + 'PhpTag' => 'PHP Tag', + 'Phpdoc' => 'PHPDoc', + ]; + + usort($fixers, static fn (FixerInterface $a, FixerInterface $b): int => \get_class($a) <=> \get_class($b)); + + $documentation = <<<'RST' + ======================= + List of Available Rules + ======================= + RST; + + $currentGroup = null; + + foreach ($fixers as $fixer) { + $namespace = Preg::replace('/^.*\\\\(.+)\\\\.+Fixer$/', '$1', \get_class($fixer)); + $group = $overrideGroups[$namespace] ?? Preg::replace('/(?<=[[:lower:]])(?=[[:upper:]])/', ' ', $namespace); + + if ($group !== $currentGroup) { + $underline = str_repeat('-', \strlen($group)); + $documentation .= "\n\n{$group}\n{$underline}\n"; + + $currentGroup = $group; + } + + $path = './'.$this->locator->getFixerDocumentationFileRelativePath($fixer); + + $attributes = []; + + if ($fixer instanceof DeprecatedFixerInterface) { + $attributes[] = 'deprecated'; + } + + if ($fixer instanceof ExperimentalFixerInterface) { + $attributes[] = 'experimental'; + } + + if ($fixer->isRisky()) { + $attributes[] = 'risky'; + } + + $attributes = 0 === \count($attributes) + ? '' + : ' *('.implode(', ', $attributes).')*'; + + $summary = str_replace('`', '``', $fixer->getDefinition()->getSummary()); + + $documentation .= <<getName()} <{$path}>`_{$attributes} + + {$summary} + RST; + } + + return "{$documentation}\n"; + } + + private function generateSampleDiff(FixerInterface $fixer, CodeSampleInterface $sample, int $sampleNumber, string $ruleName): string + { + if ($sample instanceof VersionSpecificCodeSampleInterface && !$sample->isSuitableFor(\PHP_VERSION_ID)) { + $existingFile = @file_get_contents($this->locator->getFixerDocumentationFilePath($fixer)); + + if (false !== $existingFile) { + Preg::match("/\\RExample #{$sampleNumber}\\R.+?(?\\R\\.\\. code-block:: diff\\R\\R.*?)\\R(?:\\R\\S|$)/s", $existingFile, $matches); + + if (isset($matches['diff'])) { + return $matches['diff']; + } + } + + $error = <<getCode(); + + $tokens = Tokens::fromCode($old); + $file = $sample instanceof FileSpecificCodeSampleInterface + ? $sample->getSplFileInfo() + : new StdinFileInfo(); + + if ($fixer instanceof ConfigurableFixerInterface) { + $fixer->configure($sample->getConfiguration() ?? []); + } + + $fixer->fix($file, $tokens); + + $diff = $this->differ->diff($old, $tokens->generateCode()); + $diff = Preg::replace('/@@[ \+\-\d,]+@@\n/', '', $diff); + $diff = Preg::replace('/\r/', '^M', $diff); + $diff = Preg::replace('/^ $/m', '', $diff); + $diff = Preg::replace('/\n$/', '', $diff); + $diff = RstUtils::indent($diff, 3); + + return << + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Documentation; + +use PhpCsFixer\Preg; + +/** + * @internal + */ +final class RstUtils +{ + private function __construct() + { + // cannot create instance of util. class + } + + public static function toRst(string $string, int $indent = 0): string + { + $string = wordwrap(self::ensureProperInlineCode($string), 80 - $indent); + + return 0 === $indent ? $string : self::indent($string, $indent); + } + + public static function ensureProperInlineCode(string $string): string + { + return Preg::replace('/(? + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Documentation; + +use PhpCsFixer\Fixer\FixerInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\RuleSet\DeprecatedRuleSetDescriptionInterface; +use PhpCsFixer\RuleSet\RuleSetDescriptionInterface; +use PhpCsFixer\Utils; + +/** + * @internal + */ +final class RuleSetDocumentationGenerator +{ + private DocumentationLocator $locator; + + public function __construct(DocumentationLocator $locator) + { + $this->locator = $locator; + } + + /** + * @param FixerInterface[] $fixers + */ + public function generateRuleSetsDocumentation(RuleSetDescriptionInterface $definition, array $fixers): string + { + $fixerNames = []; + + foreach ($fixers as $fixer) { + $fixerNames[$fixer->getName()] = $fixer; + } + + $title = "Rule set ``{$definition->getName()}``"; + $titleLine = str_repeat('=', \strlen($title)); + $doc = "{$titleLine}\n{$title}\n{$titleLine}\n\n".$definition->getDescription(); + + $warnings = []; + if ($definition instanceof DeprecatedRuleSetDescriptionInterface) { + $deprecationDescription = <<<'RST' + + This rule set is deprecated and will be removed in the next major version + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + RST; + $alternatives = $definition->getSuccessorsNames(); + + if (0 !== \count($alternatives)) { + $deprecationDescription .= RstUtils::toRst( + sprintf( + "\n\nYou should use %s instead.", + Utils::naturalLanguageJoinWithBackticks($alternatives) + ), + 0 + ); + } else { + $deprecationDescription .= 'No replacement available.'; + } + + $warnings[] = $deprecationDescription; + } + + if ($definition->isRisky()) { + $warnings[] = <<<'RST' + + This set contains rules that are risky + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Using this rule set may lead to changes in your code's logic and behaviour. Use it with caution and review changes before incorporating them into your code base. + RST; + } + + if ([] !== $warnings) { + $warningsHeader = 1 === \count($warnings) ? 'Warning' : 'Warnings'; + + $warningsHeaderLine = str_repeat('-', \strlen($warningsHeader)); + $doc .= "\n\n".implode( + "\n", + array_filter([ + $warningsHeader, + $warningsHeaderLine, + ...$warnings, + ]) + ); + } + + $rules = $definition->getRules(); + + if ([] === $rules) { + $doc .= "\n\nThis is an empty set."; + } else { + $enabledRules = array_filter($rules, static fn ($config) => false !== $config); + $disabledRules = array_filter($rules, static fn ($config) => false === $config); + + $listRules = function (array $rules) use (&$doc, $fixerNames): void { + foreach ($rules as $rule => $config) { + if (str_starts_with($rule, '@')) { + $ruleSetPath = $this->locator->getRuleSetsDocumentationFilePath($rule); + $ruleSetPath = substr($ruleSetPath, strrpos($ruleSetPath, '/')); + + $doc .= "\n- `{$rule} <.{$ruleSetPath}>`_"; + } else { + $path = Preg::replace( + '#^'.preg_quote($this->locator->getFixersDocumentationDirectoryPath(), '#').'/#', + './../rules/', + $this->locator->getFixerDocumentationFilePath($fixerNames[$rule]) + ); + + $doc .= "\n- `{$rule} <{$path}>`_"; + } + + if (!\is_bool($config)) { + $doc .= " with config:\n\n ``".Utils::toString($config)."``\n"; + } + } + }; + + if ([] !== $enabledRules) { + $doc .= "\n\nRules\n-----\n"; + $listRules($enabledRules); + } + + if ([] !== $disabledRules) { + $doc .= "\n\nDisabled rules\n--------------\n"; + $listRules($disabledRules); + } + } + + return $doc."\n"; + } + + /** + * @param array $setDefinitions + */ + public function generateRuleSetsDocumentationIndex(array $setDefinitions): string + { + $documentation = <<<'RST' + =========================== + List of Available Rule sets + =========================== + RST; + + foreach ($setDefinitions as $path => $definition) { + $path = substr($path, strrpos($path, '/')); + + $attributes = []; + + if ($definition instanceof DeprecatedRuleSetDescriptionInterface) { + $attributes[] = 'deprecated'; + } + + $attributes = 0 === \count($attributes) + ? '' + : ' *('.implode(', ', $attributes).')*'; + + $documentation .= "\n- `{$definition->getName()} <.{$path}>`_{$attributes}"; + } + + return $documentation."\n"; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Error/Error.php b/vendor/friendsofphp/php-cs-fixer/src/Error/Error.php new file mode 100644 index 00000000..1f14ed9f --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Error/Error.php @@ -0,0 +1,93 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Error; + +/** + * An abstraction for errors that can occur before and during fixing. + * + * @author Andreas Möller + * + * @internal + */ +final class Error +{ + /** + * Error which has occurred in linting phase, before applying any fixers. + */ + public const TYPE_INVALID = 1; + + /** + * Error which has occurred during fixing phase. + */ + public const TYPE_EXCEPTION = 2; + + /** + * Error which has occurred in linting phase, after applying any fixers. + */ + public const TYPE_LINT = 3; + + private int $type; + + private string $filePath; + + private ?\Throwable $source; + + /** + * @var list + */ + private array $appliedFixers; + + private ?string $diff; + + /** + * @param list $appliedFixers + */ + public function __construct(int $type, string $filePath, ?\Throwable $source = null, array $appliedFixers = [], ?string $diff = null) + { + $this->type = $type; + $this->filePath = $filePath; + $this->source = $source; + $this->appliedFixers = $appliedFixers; + $this->diff = $diff; + } + + public function getFilePath(): string + { + return $this->filePath; + } + + public function getSource(): ?\Throwable + { + return $this->source; + } + + public function getType(): int + { + return $this->type; + } + + /** + * @return list + */ + public function getAppliedFixers(): array + { + return $this->appliedFixers; + } + + public function getDiff(): ?string + { + return $this->diff; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Error/ErrorsManager.php b/vendor/friendsofphp/php-cs-fixer/src/Error/ErrorsManager.php new file mode 100644 index 00000000..d1d9e0cf --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Error/ErrorsManager.php @@ -0,0 +1,73 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Error; + +/** + * Manager of errors that occur during fixing. + * + * @author Dariusz Rumiński + * + * @internal + */ +final class ErrorsManager +{ + /** + * @var list + */ + private array $errors = []; + + /** + * Returns errors reported during linting before fixing. + * + * @return list + */ + public function getInvalidErrors(): array + { + return array_filter($this->errors, static fn (Error $error): bool => Error::TYPE_INVALID === $error->getType()); + } + + /** + * Returns errors reported during fixing. + * + * @return list + */ + public function getExceptionErrors(): array + { + return array_filter($this->errors, static fn (Error $error): bool => Error::TYPE_EXCEPTION === $error->getType()); + } + + /** + * Returns errors reported during linting after fixing. + * + * @return list + */ + public function getLintErrors(): array + { + return array_filter($this->errors, static fn (Error $error): bool => Error::TYPE_LINT === $error->getType()); + } + + /** + * Returns true if no errors were reported. + */ + public function isEmpty(): bool + { + return [] === $this->errors; + } + + public function report(Error $error): void + { + $this->errors[] = $error; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/ExecutorWithoutErrorHandler.php b/vendor/friendsofphp/php-cs-fixer/src/ExecutorWithoutErrorHandler.php new file mode 100644 index 00000000..003398e4 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/ExecutorWithoutErrorHandler.php @@ -0,0 +1,58 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class ExecutorWithoutErrorHandler +{ + private function __construct() {} + + /** + * @template T + * + * @param callable(): T $callback + * + * @return T + * + * @throws ExecutorWithoutErrorHandlerException + */ + public static function execute(callable $callback) + { + /** @var ?string */ + $error = null; + + set_error_handler(static function (int $errorNumber, string $errorString, string $errorFile, int $errorLine) use (&$error): bool { + $error = $errorString; + + return true; + }); + + try { + $result = $callback(); + } finally { + restore_error_handler(); + } + + if (null !== $error) { + throw new ExecutorWithoutErrorHandlerException($error); + } + + return $result; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/ExecutorWithoutErrorHandlerException.php b/vendor/friendsofphp/php-cs-fixer/src/ExecutorWithoutErrorHandlerException.php new file mode 100644 index 00000000..750352a2 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/ExecutorWithoutErrorHandlerException.php @@ -0,0 +1,22 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class ExecutorWithoutErrorHandlerException extends \RuntimeException {} diff --git a/vendor/friendsofphp/php-cs-fixer/src/FileReader.php b/vendor/friendsofphp/php-cs-fixer/src/FileReader.php new file mode 100644 index 00000000..d4fb752c --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/FileReader.php @@ -0,0 +1,73 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer; + +/** + * File reader that unify access to regular file and stdin-alike file. + * + * Regular file could be read multiple times with `file_get_contents`, but file provided on stdin cannot. + * Consecutive try will provide empty content for stdin-alike file. + * This reader unifies access to them. + * + * @internal + */ +final class FileReader +{ + /** + * @var null|string + */ + private $stdinContent; + + public static function createSingleton(): self + { + static $instance = null; + + if (!$instance) { + $instance = new self(); + } + + return $instance; + } + + public function read(string $filePath): string + { + if ('php://stdin' === $filePath) { + if (null === $this->stdinContent) { + $this->stdinContent = $this->readRaw($filePath); + } + + return $this->stdinContent; + } + + return $this->readRaw($filePath); + } + + private function readRaw(string $realPath): string + { + $content = @file_get_contents($realPath); + + if (false === $content) { + $error = error_get_last(); + + throw new \RuntimeException(sprintf( + 'Failed to read content from "%s".%s', + $realPath, + null !== $error ? ' '.$error['message'] : '' + )); + } + + return $content; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/FileRemoval.php b/vendor/friendsofphp/php-cs-fixer/src/FileRemoval.php new file mode 100644 index 00000000..148a4c5e --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/FileRemoval.php @@ -0,0 +1,100 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer; + +/** + * Handles files removal with possibility to remove them on shutdown. + * + * @author Adam Klvač + * @author Dariusz Rumiński + * + * @internal + */ +final class FileRemoval +{ + /** + * List of observed files to be removed. + * + * @var array + */ + private array $files = []; + + public function __construct() + { + register_shutdown_function([$this, 'clean']); + } + + public function __destruct() + { + $this->clean(); + } + + /** + * This class is not intended to be serialized, + * and cannot be deserialized (see __wakeup method). + */ + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.self::class); + } + + /** + * Disable the deserialization of the class to prevent attacker executing + * code by leveraging the __destruct method. + * + * @see https://owasp.org/www-community/vulnerabilities/PHP_Object_Injection + */ + public function __wakeup(): void + { + throw new \BadMethodCallException('Cannot unserialize '.self::class); + } + + /** + * Adds a file to be removed. + */ + public function observe(string $path): void + { + $this->files[$path] = true; + } + + /** + * Removes a file from shutdown removal. + */ + public function delete(string $path): void + { + if (isset($this->files[$path])) { + unset($this->files[$path]); + } + + $this->unlink($path); + } + + /** + * Removes attached files. + */ + public function clean(): void + { + foreach ($this->files as $file => $value) { + $this->unlink($file); + } + + $this->files = []; + } + + private function unlink(string $path): void + { + @unlink($path); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Finder.php b/vendor/friendsofphp/php-cs-fixer/src/Finder.php new file mode 100644 index 00000000..419354ef --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Finder.php @@ -0,0 +1,35 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer; + +use Symfony\Component\Finder\Finder as BaseFinder; + +/** + * @author Fabien Potencier + * @author Dariusz Rumiński + */ +class Finder extends BaseFinder +{ + public function __construct() + { + parent::__construct(); + + $this + ->files() + ->name('/\.php$/') + ->exclude('vendor') + ; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/AbstractIncrementOperatorFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/AbstractIncrementOperatorFixer.php new file mode 100644 index 00000000..3a2db980 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/AbstractIncrementOperatorFixer.php @@ -0,0 +1,58 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Tokenizer\Tokens; + +abstract class AbstractIncrementOperatorFixer extends AbstractFixer +{ + final protected function findStart(Tokens $tokens, int $index): int + { + do { + $index = $tokens->getPrevMeaningfulToken($index); + $token = $tokens[$index]; + + $blockType = Tokens::detectBlockType($token); + if (null !== $blockType && !$blockType['isStart']) { + $index = $tokens->findBlockStart($blockType['type'], $index); + $token = $tokens[$index]; + } + } while (!$token->equalsAny(['$', [T_VARIABLE]])); + + $prevIndex = $tokens->getPrevMeaningfulToken($index); + $prevToken = $tokens[$prevIndex]; + + if ($prevToken->equals('$')) { + return $this->findStart($tokens, $index); + } + + if ($prevToken->isObjectOperator()) { + return $this->findStart($tokens, $prevIndex); + } + + if ($prevToken->isGivenKind(T_PAAMAYIM_NEKUDOTAYIM)) { + $prevPrevIndex = $tokens->getPrevMeaningfulToken($prevIndex); + if (!$tokens[$prevPrevIndex]->isGivenKind([T_STATIC, T_STRING])) { + return $this->findStart($tokens, $prevIndex); + } + + $index = $tokens->getTokenNotOfKindsSibling($prevIndex, -1, [T_NS_SEPARATOR, T_STATIC, T_STRING]); + $index = $tokens->getNextMeaningfulToken($index); + } + + return $index; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/AbstractPhpUnitFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/AbstractPhpUnitFixer.php new file mode 100644 index 00000000..9d90aba1 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/AbstractPhpUnitFixer.php @@ -0,0 +1,164 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\DocBlock\Line; +use PhpCsFixer\Indicator\PhpUnitTestCaseIndicator; +use PhpCsFixer\Tokenizer\Analyzer\WhitespacesAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @internal + */ +abstract class AbstractPhpUnitFixer extends AbstractFixer +{ + final public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAllTokenKindsFound([T_CLASS, T_STRING]); + } + + final protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator(); + + foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens) as $indices) { + $this->applyPhpUnitClassFix($tokens, $indices[0], $indices[1]); + } + } + + abstract protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void; + + final protected function getDocBlockIndex(Tokens $tokens, int $index): int + { + $modifiers = [T_PUBLIC, T_PROTECTED, T_PRIVATE, T_FINAL, T_ABSTRACT, T_COMMENT]; + + if (\defined('T_ATTRIBUTE')) { // @TODO: drop condition when PHP 8.0+ is required + $modifiers[] = T_ATTRIBUTE; + } + + if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.2+ is required + $modifiers[] = T_READONLY; + } + + do { + $index = $tokens->getPrevNonWhitespace($index); + + if ($tokens[$index]->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) { + $index = $tokens->getPrevTokenOfKind($index, [[T_ATTRIBUTE]]); + } + } while ($tokens[$index]->isGivenKind($modifiers)); + + return $index; + } + + /** + * @param array $preventingAnnotations + */ + final protected function ensureIsDockBlockWithAnnotation( + Tokens $tokens, + int $index, + string $annotation, + array $preventingAnnotations + ): void { + $docBlockIndex = $this->getDocBlockIndex($tokens, $index); + + if ($this->isPHPDoc($tokens, $docBlockIndex)) { + $this->updateDocBlockIfNeeded($tokens, $docBlockIndex, $annotation, $preventingAnnotations); + } else { + $this->createDocBlock($tokens, $docBlockIndex, $annotation); + } + } + + final protected function isPHPDoc(Tokens $tokens, int $index): bool + { + return $tokens[$index]->isGivenKind(T_DOC_COMMENT); + } + + private function createDocBlock(Tokens $tokens, int $docBlockIndex, string $annotation): void + { + $lineEnd = $this->whitespacesConfig->getLineEnding(); + $originalIndent = WhitespacesAnalyzer::detectIndent($tokens, $tokens->getNextNonWhitespace($docBlockIndex)); + $toInsert = [ + new Token([T_DOC_COMMENT, "/**{$lineEnd}{$originalIndent} * @{$annotation}{$lineEnd}{$originalIndent} */"]), + new Token([T_WHITESPACE, $lineEnd.$originalIndent]), + ]; + $index = $tokens->getNextMeaningfulToken($docBlockIndex); + $tokens->insertAt($index, $toInsert); + + if (!$tokens[$index - 1]->isGivenKind(T_WHITESPACE)) { + $extraNewLines = $this->whitespacesConfig->getLineEnding(); + + if (!$tokens[$index - 1]->isGivenKind(T_OPEN_TAG)) { + $extraNewLines .= $this->whitespacesConfig->getLineEnding(); + } + + $tokens->insertAt($index, [ + new Token([T_WHITESPACE, $extraNewLines.WhitespacesAnalyzer::detectIndent($tokens, $index)]), + ]); + } + } + + /** + * @param array $preventingAnnotations + */ + private function updateDocBlockIfNeeded( + Tokens $tokens, + int $docBlockIndex, + string $annotation, + array $preventingAnnotations + ): void { + $doc = new DocBlock($tokens[$docBlockIndex]->getContent()); + foreach ($preventingAnnotations as $preventingAnnotation) { + if ([] !== $doc->getAnnotationsOfType($preventingAnnotation)) { + return; + } + } + $doc = $this->makeDocBlockMultiLineIfNeeded($doc, $tokens, $docBlockIndex, $annotation); + $lines = $this->addInternalAnnotation($doc, $tokens, $docBlockIndex, $annotation); + $lines = implode('', $lines); + + $tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $lines]); + } + + /** + * @return array + */ + private function addInternalAnnotation(DocBlock $docBlock, Tokens $tokens, int $docBlockIndex, string $annotation): array + { + $lines = $docBlock->getLines(); + $originalIndent = WhitespacesAnalyzer::detectIndent($tokens, $docBlockIndex); + $lineEnd = $this->whitespacesConfig->getLineEnding(); + array_splice($lines, -1, 0, $originalIndent.' * @'.$annotation.$lineEnd); + + return $lines; + } + + private function makeDocBlockMultiLineIfNeeded(DocBlock $doc, Tokens $tokens, int $docBlockIndex, string $annotation): DocBlock + { + $lines = $doc->getLines(); + if (1 === \count($lines) && [] === $doc->getAnnotationsOfType($annotation)) { + $indent = WhitespacesAnalyzer::detectIndent($tokens, $tokens->getNextNonWhitespace($docBlockIndex)); + $doc->makeMultiLine($indent, $this->whitespacesConfig->getLineEnding()); + + return $doc; + } + + return $doc; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/AbstractShortOperatorFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/AbstractShortOperatorFixer.php new file mode 100644 index 00000000..0653e195 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/AbstractShortOperatorFixer.php @@ -0,0 +1,264 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Tokenizer\Analyzer\AlternativeSyntaxAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\RangeAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @internal + */ +abstract class AbstractShortOperatorFixer extends AbstractFixer +{ + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $alternativeSyntaxAnalyzer = new AlternativeSyntaxAnalyzer(); + + for ($index = \count($tokens) - 1; $index > 3; --$index) { + if (!$this->isOperatorTokenCandidate($tokens, $index)) { + continue; + } + + // get what is before the operator + + $beforeRange = $this->getBeforeOperatorRange($tokens, $index); + $equalsIndex = $tokens->getPrevMeaningfulToken($beforeRange['start']); + + // make sure that before that is '=' + + if (!$tokens[$equalsIndex]->equals('=')) { + continue; + } + + // get and check what is before '=' + + $assignRange = $this->getBeforeOperatorRange($tokens, $equalsIndex); + $beforeAssignmentIndex = $tokens->getPrevMeaningfulToken($assignRange['start']); + + if ($tokens[$beforeAssignmentIndex]->equals(':')) { + if (!$this->belongsToSwitchOrAlternativeSyntax($alternativeSyntaxAnalyzer, $tokens, $beforeAssignmentIndex)) { + continue; + } + } elseif (!$tokens[$beforeAssignmentIndex]->equalsAny([';', '{', '}', '(', ')', ',', [T_OPEN_TAG], [T_RETURN]])) { + continue; + } + + // check if "assign" and "before" the operator are (functionally) the same + + if (RangeAnalyzer::rangeEqualsRange($tokens, $assignRange, $beforeRange)) { + $this->shortenOperation($tokens, $equalsIndex, $index, $assignRange, $beforeRange); + + continue; + } + + if (!$this->isOperatorCommutative($tokens[$index])) { + continue; + } + + $afterRange = $this->getAfterOperatorRange($tokens, $index); + + // check if "assign" and "after" the operator are (functionally) the same + if (!RangeAnalyzer::rangeEqualsRange($tokens, $assignRange, $afterRange)) { + continue; + } + + $this->shortenOperation($tokens, $equalsIndex, $index, $assignRange, $afterRange); + } + } + + abstract protected function getReplacementToken(Token $token): Token; + + abstract protected function isOperatorTokenCandidate(Tokens $tokens, int $index): bool; + + /** + * @param array{start: int, end: int} $assignRange + * @param array{start: int, end: int} $operatorRange + */ + private function shortenOperation( + Tokens $tokens, + int $equalsIndex, + int $operatorIndex, + array $assignRange, + array $operatorRange + ): void { + $tokens[$equalsIndex] = $this->getReplacementToken($tokens[$operatorIndex]); + $tokens->clearTokenAndMergeSurroundingWhitespace($operatorIndex); + $this->clearMeaningfulFromRange($tokens, $operatorRange); + + foreach ([$equalsIndex, $assignRange['end']] as $i) { + $i = $tokens->getNonEmptySibling($i, 1); + + if ($tokens[$i]->isWhitespace(" \t")) { + $tokens[$i] = new Token([T_WHITESPACE, ' ']); + } elseif (!$tokens[$i]->isWhitespace()) { + $tokens->insertAt($i, new Token([T_WHITESPACE, ' '])); + } + } + } + + /** + * @return array{start: int, end: int} + */ + private function getAfterOperatorRange(Tokens $tokens, int $index): array + { + $index = $tokens->getNextMeaningfulToken($index); + $range = ['start' => $index]; + + while (true) { + $nextIndex = $tokens->getNextMeaningfulToken($index); + + if (null === $nextIndex || $tokens[$nextIndex]->equalsAny([';', ',', [T_CLOSE_TAG]])) { + break; + } + + $blockType = Tokens::detectBlockType($tokens[$nextIndex]); + + if (null === $blockType) { + $index = $nextIndex; + + continue; + } + + if (false === $blockType['isStart']) { + break; + } + + $index = $tokens->findBlockEnd($blockType['type'], $nextIndex); + } + + $range['end'] = $index; + + return $range; + } + + /** + * @return array{start: int, end: int} + */ + private function getBeforeOperatorRange(Tokens $tokens, int $index): array + { + static $blockOpenTypes; + + if (null === $blockOpenTypes) { + $blockOpenTypes = [',']; // not a true "block type", but speeds up things + + foreach (Tokens::getBlockEdgeDefinitions() as $definition) { + $blockOpenTypes[] = $definition['start']; + } + } + + $controlStructureWithoutBracesTypes = [T_IF, T_ELSE, T_ELSEIF, T_FOR, T_FOREACH, T_WHILE]; + + $previousIndex = $tokens->getPrevMeaningfulToken($index); + $previousToken = $tokens[$previousIndex]; + + if ($tokens[$previousIndex]->equalsAny($blockOpenTypes)) { + return ['start' => $index, 'end' => $index]; + } + + $range = ['end' => $previousIndex]; + $index = $previousIndex; + + while ($previousToken->equalsAny([ + '$', + ']', + ')', + [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE], + [CT::T_DYNAMIC_PROP_BRACE_CLOSE], + [CT::T_DYNAMIC_VAR_BRACE_CLOSE], + [T_NS_SEPARATOR], + [T_STRING], + [T_VARIABLE], + ])) { + $blockType = Tokens::detectBlockType($previousToken); + + if (null !== $blockType) { + $blockStart = $tokens->findBlockStart($blockType['type'], $previousIndex); + + if ($tokens[$previousIndex]->equals(')') && $tokens[$tokens->getPrevMeaningfulToken($blockStart)]->isGivenKind($controlStructureWithoutBracesTypes)) { + break; // we went too far back + } + + $previousIndex = $blockStart; + } + + $index = $previousIndex; + $previousIndex = $tokens->getPrevMeaningfulToken($previousIndex); + $previousToken = $tokens[$previousIndex]; + } + + if ($previousToken->isGivenKind(T_OBJECT_OPERATOR)) { + $index = $this->getBeforeOperatorRange($tokens, $previousIndex)['start']; + } elseif ($previousToken->isGivenKind(T_PAAMAYIM_NEKUDOTAYIM)) { + $index = $this->getBeforeOperatorRange($tokens, $tokens->getPrevMeaningfulToken($previousIndex))['start']; + } + + $range['start'] = $index; + + return $range; + } + + /** + * @param array{start: int, end: int} $range + */ + private function clearMeaningfulFromRange(Tokens $tokens, array $range): void + { + // $range['end'] must be meaningful! + for ($i = $range['end']; $i >= $range['start']; $i = $tokens->getPrevMeaningfulToken($i)) { + $tokens->clearTokenAndMergeSurroundingWhitespace($i); + } + } + + private function isOperatorCommutative(Token $operatorToken): bool + { + static $commutativeKinds = ['*', '|', '&', '^']; // note that for arrays in PHP `+` is not commutative + static $nonCommutativeKinds = ['-', '/', '.', '%', '+']; + + if ($operatorToken->isGivenKind(T_COALESCE)) { + return false; + } + + if ($operatorToken->equalsAny($commutativeKinds)) { + return true; + } + + if ($operatorToken->equalsAny($nonCommutativeKinds)) { + return false; + } + + throw new \InvalidArgumentException(sprintf('Not supported operator "%s".', $operatorToken->toJson())); + } + + private function belongsToSwitchOrAlternativeSyntax(AlternativeSyntaxAnalyzer $alternativeSyntaxAnalyzer, Tokens $tokens, int $index): bool + { + $candidate = $index; + $index = $tokens->getPrevMeaningfulToken($candidate); + + if ($tokens[$index]->isGivenKind(T_DEFAULT)) { + return true; + } + + $index = $tokens->getPrevMeaningfulToken($index); + + if ($tokens[$index]->isGivenKind(T_CASE)) { + return true; + } + + return $alternativeSyntaxAnalyzer->belongsToAlternativeSyntax($tokens, $candidate); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/ArrayPushFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/ArrayPushFixer.php new file mode 100644 index 00000000..9023bbd3 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/ArrayPushFixer.php @@ -0,0 +1,207 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Alias; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class ArrayPushFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Converts simple usages of `array_push($x, $y);` to `$x[] = $y;`.', + [new CodeSample("isTokenKindFound(T_STRING) && $tokens->count() > 7; + } + + public function isRisky(): bool + { + return true; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $functionsAnalyzer = new FunctionsAnalyzer(); + + for ($index = $tokens->count() - 7; $index > 0; --$index) { + if (!$tokens[$index]->equals([T_STRING, 'array_push'], false)) { + continue; + } + + if (!$functionsAnalyzer->isGlobalFunctionCall($tokens, $index)) { + continue; // redeclare/override + } + + // meaningful before must be `getPrevMeaningfulToken($index); + $namespaceSeparatorIndex = null; + + if ($tokens[$index]->isGivenKind(T_NS_SEPARATOR)) { + $namespaceSeparatorIndex = $index; + $index = $tokens->getPrevMeaningfulToken($index); + } + + if (!$tokens[$index]->equalsAny([';', '{', '}', ')', [T_OPEN_TAG]])) { + continue; + } + + // figure out where the arguments list opens + + $openBraceIndex = $tokens->getNextMeaningfulToken($callIndex); + $blockType = Tokens::detectBlockType($tokens[$openBraceIndex]); + + if (null === $blockType || Tokens::BLOCK_TYPE_PARENTHESIS_BRACE !== $blockType['type']) { + continue; + } + + // figure out where the arguments list closes + + $closeBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openBraceIndex); + + // meaningful after `)` must be `;`, `? >` or nothing + + $afterCloseBraceIndex = $tokens->getNextMeaningfulToken($closeBraceIndex); + + if (null !== $afterCloseBraceIndex && !$tokens[$afterCloseBraceIndex]->equalsAny([';', [T_CLOSE_TAG]])) { + continue; + } + + // must have 2 arguments + + // first argument must be a variable (with possibly array indexing etc.), + // after that nothing meaningful should be there till the next `,` or `)` + // if `)` than we cannot fix it (it is a single argument call) + + $firstArgumentStop = $this->getFirstArgumentEnd($tokens, $openBraceIndex); + $firstArgumentStop = $tokens->getNextMeaningfulToken($firstArgumentStop); + + if (!$tokens[$firstArgumentStop]->equals(',')) { + return; + } + + // second argument can be about anything but ellipsis, we must make sure there is not + // a third argument (or more) passed to `array_push` + + $secondArgumentStart = $tokens->getNextMeaningfulToken($firstArgumentStop); + $secondArgumentStop = $this->getSecondArgumentEnd($tokens, $secondArgumentStart, $closeBraceIndex); + + if (null === $secondArgumentStop) { + continue; + } + + // candidate is valid, replace tokens + + $tokens->clearTokenAndMergeSurroundingWhitespace($closeBraceIndex); + $tokens->clearTokenAndMergeSurroundingWhitespace($firstArgumentStop); + $tokens->insertAt( + $firstArgumentStop, + [ + new Token('['), + new Token(']'), + new Token([T_WHITESPACE, ' ']), + new Token('='), + ] + ); + $tokens->clearTokenAndMergeSurroundingWhitespace($openBraceIndex); + $tokens->clearTokenAndMergeSurroundingWhitespace($callIndex); + + if (null !== $namespaceSeparatorIndex) { + $tokens->clearTokenAndMergeSurroundingWhitespace($namespaceSeparatorIndex); + } + } + } + + private function getFirstArgumentEnd(Tokens $tokens, int $index): int + { + $nextIndex = $tokens->getNextMeaningfulToken($index); + $nextToken = $tokens[$nextIndex]; + + while ($nextToken->equalsAny([ + '$', + '[', + '(', + [CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN], + [CT::T_DYNAMIC_PROP_BRACE_OPEN], + [CT::T_DYNAMIC_VAR_BRACE_OPEN], + [CT::T_NAMESPACE_OPERATOR], + [T_NS_SEPARATOR], + [T_STATIC], + [T_STRING], + [T_VARIABLE], + ])) { + $blockType = Tokens::detectBlockType($nextToken); + + if (null !== $blockType) { + $nextIndex = $tokens->findBlockEnd($blockType['type'], $nextIndex); + } + + $index = $nextIndex; + $nextIndex = $tokens->getNextMeaningfulToken($nextIndex); + $nextToken = $tokens[$nextIndex]; + } + + if ($nextToken->isGivenKind(T_OBJECT_OPERATOR)) { + return $this->getFirstArgumentEnd($tokens, $nextIndex); + } + + if ($nextToken->isGivenKind(T_PAAMAYIM_NEKUDOTAYIM)) { + return $this->getFirstArgumentEnd($tokens, $tokens->getNextMeaningfulToken($nextIndex)); + } + + return $index; + } + + /** + * @param int $endIndex boundary, i.e. tokens index of `)` + */ + private function getSecondArgumentEnd(Tokens $tokens, int $index, int $endIndex): ?int + { + if ($tokens[$index]->isGivenKind(T_ELLIPSIS)) { + return null; + } + + for (; $index <= $endIndex; ++$index) { + $blockType = Tokens::detectBlockType($tokens[$index]); + + while (null !== $blockType && $blockType['isStart']) { + $index = $tokens->findBlockEnd($blockType['type'], $index); + $index = $tokens->getNextMeaningfulToken($index); + $blockType = Tokens::detectBlockType($tokens[$index]); + } + + if ($tokens[$index]->equals(',') || $tokens[$index]->isGivenKind([T_YIELD, T_YIELD_FROM, T_LOGICAL_AND, T_LOGICAL_OR, T_LOGICAL_XOR])) { + return null; + } + } + + return $endIndex; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/BacktickToShellExecFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/BacktickToShellExecFixer.php new file mode 100644 index 00000000..f2197705 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/BacktickToShellExecFixer.php @@ -0,0 +1,149 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Alias; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Filippo Tessarotto + */ +final class BacktickToShellExecFixer extends AbstractFixer +{ + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound('`'); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Converts backtick operators to `shell_exec` calls.', + [ + new CodeSample( + <<<'EOT' + call()}`; + + EOT + ), + ], + 'Conversion is done only when it is non risky, so when special chars like single-quotes, double-quotes and backticks are not used inside the command.' + ); + } + + /** + * {@inheritdoc} + * + * Must run before EscapeImplicitBackslashesFixer, ExplicitStringVariableFixer, NativeFunctionInvocationFixer, SingleQuoteFixer, StringImplicitBackslashesFixer. + */ + public function getPriority(): int + { + return 17; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $backtickStarted = false; + $backtickTokens = []; + for ($index = $tokens->count() - 1; $index > 0; --$index) { + $token = $tokens[$index]; + + if (!$token->equals('`')) { + if ($backtickStarted) { + $backtickTokens[$index] = $token; + } + + continue; + } + + $backtickTokens[$index] = $token; + + if ($backtickStarted) { + $this->fixBackticks($tokens, $backtickTokens); + $backtickTokens = []; + } + + $backtickStarted = !$backtickStarted; + } + } + + /** + * Override backtick code with corresponding double-quoted string. + * + * @param array $backtickTokens + */ + private function fixBackticks(Tokens $tokens, array $backtickTokens): void + { + // Track indices for final override + ksort($backtickTokens); + $openingBacktickIndex = array_key_first($backtickTokens); + $closingBacktickIndex = array_key_last($backtickTokens); + + // Strip enclosing backticks + array_shift($backtickTokens); + array_pop($backtickTokens); + + // Double-quoted strings are parsed differently if they contain + // variables or not, so we need to build the new token array accordingly + $count = \count($backtickTokens); + + $newTokens = [ + new Token([T_STRING, 'shell_exec']), + new Token('('), + ]; + + if (1 !== $count) { + $newTokens[] = new Token('"'); + } + + foreach ($backtickTokens as $token) { + if (!$token->isGivenKind(T_ENCAPSED_AND_WHITESPACE)) { + $newTokens[] = $token; + + continue; + } + + $content = $token->getContent(); + // Escaping special chars depends on the context: too tricky + if (Preg::match('/[`"\']/u', $content)) { + return; + } + + $kind = T_ENCAPSED_AND_WHITESPACE; + + if (1 === $count) { + $content = '"'.$content.'"'; + $kind = T_CONSTANT_ENCAPSED_STRING; + } + + $newTokens[] = new Token([$kind, $content]); + } + + if (1 !== $count) { + $newTokens[] = new Token('"'); + } + + $newTokens[] = new Token(')'); + + $tokens->overrideRange($openingBacktickIndex, $closingBacktickIndex, $newTokens); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/EregToPregFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/EregToPregFixer.php new file mode 100644 index 00000000..396658c3 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/EregToPregFixer.php @@ -0,0 +1,193 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Alias; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\PregException; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Matteo Beccati + */ +final class EregToPregFixer extends AbstractFixer +{ + /** + * @var list> the list of the ext/ereg function names, their preg equivalent and the preg modifier(s), if any + * all condensed in an array of arrays + */ + private static array $functions = [ + ['ereg', 'preg_match', ''], + ['eregi', 'preg_match', 'i'], + ['ereg_replace', 'preg_replace', ''], + ['eregi_replace', 'preg_replace', 'i'], + ['split', 'preg_split', ''], + ['spliti', 'preg_split', 'i'], + ]; + + /** + * @var list the list of preg delimiters, in order of preference + */ + private static array $delimiters = ['/', '#', '!']; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Replace deprecated `ereg` regular expression functions with `preg`.', + [new CodeSample("isTokenKindFound(T_STRING); + } + + public function isRisky(): bool + { + return true; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $end = $tokens->count() - 1; + $functionsAnalyzer = new FunctionsAnalyzer(); + + foreach (self::$functions as $map) { + // the sequence is the function name, followed by "(" and a quoted string + $seq = [[T_STRING, $map[0]], '(', [T_CONSTANT_ENCAPSED_STRING]]; + $currIndex = 0; + + while (true) { + $match = $tokens->findSequence($seq, $currIndex, $end, false); + + // did we find a match? + if (null === $match) { + break; + } + + // findSequence also returns the tokens, but we're only interested in the indices, i.e.: + // 0 => function name, + // 1 => parenthesis "(" + // 2 => quoted string passed as 1st parameter + $match = array_keys($match); + + // advance tokenizer cursor + $currIndex = $match[2]; + + if (!$functionsAnalyzer->isGlobalFunctionCall($tokens, $match[0])) { + continue; + } + + // ensure the first parameter is just a string (e.g. has nothing appended) + $next = $tokens->getNextMeaningfulToken($match[2]); + + if (null === $next || !$tokens[$next]->equalsAny([',', ')'])) { + continue; + } + + // convert to PCRE + $regexTokenContent = $tokens[$match[2]]->getContent(); + + if ('b' === $regexTokenContent[0] || 'B' === $regexTokenContent[0]) { + $quote = $regexTokenContent[1]; + $prefix = $regexTokenContent[0]; + $string = substr($regexTokenContent, 2, -1); + } else { + $quote = $regexTokenContent[0]; + $prefix = ''; + $string = substr($regexTokenContent, 1, -1); + } + + $delim = $this->getBestDelimiter($string); + $preg = $delim.addcslashes($string, $delim).$delim.'D'.$map[2]; + + // check if the preg is valid + if (!$this->checkPreg($preg)) { + continue; + } + + // modify function and argument + $tokens[$match[0]] = new Token([T_STRING, $map[1]]); + $tokens[$match[2]] = new Token([T_CONSTANT_ENCAPSED_STRING, $prefix.$quote.$preg.$quote]); + } + } + } + + /** + * Check the validity of a PCRE. + * + * @param string $pattern the regular expression + */ + private function checkPreg(string $pattern): bool + { + try { + Preg::match($pattern, ''); + + return true; + } catch (PregException $e) { + return false; + } + } + + /** + * Get the delimiter that would require the least escaping in a regular expression. + * + * @param string $pattern the regular expression + * + * @return string the preg delimiter + */ + private function getBestDelimiter(string $pattern): string + { + // try to find something that's not used + $delimiters = []; + + foreach (self::$delimiters as $k => $d) { + if (!str_contains($pattern, $d)) { + return $d; + } + + $delimiters[$d] = [substr_count($pattern, $d), $k]; + } + + // return the least used delimiter, using the position in the list as a tiebreaker + uasort($delimiters, static function (array $a, array $b): int { + if ($a[0] === $b[0]) { + return $a[1] <=> $b[1]; + } + + return $a[0] <=> $b[0]; + }); + + return array_key_first($delimiters); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/MbStrFunctionsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/MbStrFunctionsFixer.php new file mode 100644 index 00000000..1cbbd874 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/MbStrFunctionsFixer.php @@ -0,0 +1,141 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Alias; + +use PhpCsFixer\AbstractFunctionReferenceFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Filippo Tessarotto + */ +final class MbStrFunctionsFixer extends AbstractFunctionReferenceFixer +{ + /** + * list of the string-related function names and their mb_ equivalent. + * + * @var array< + * string, + * array{ + * alternativeName: string, + * argumentCount: list, + * }, + * > + */ + private static array $functionsMap = [ + 'str_split' => ['alternativeName' => 'mb_str_split', 'argumentCount' => [1, 2, 3]], + 'stripos' => ['alternativeName' => 'mb_stripos', 'argumentCount' => [2, 3]], + 'stristr' => ['alternativeName' => 'mb_stristr', 'argumentCount' => [2, 3]], + 'strlen' => ['alternativeName' => 'mb_strlen', 'argumentCount' => [1]], + 'strpos' => ['alternativeName' => 'mb_strpos', 'argumentCount' => [2, 3]], + 'strrchr' => ['alternativeName' => 'mb_strrchr', 'argumentCount' => [2]], + 'strripos' => ['alternativeName' => 'mb_strripos', 'argumentCount' => [2, 3]], + 'strrpos' => ['alternativeName' => 'mb_strrpos', 'argumentCount' => [2, 3]], + 'strstr' => ['alternativeName' => 'mb_strstr', 'argumentCount' => [2, 3]], + 'strtolower' => ['alternativeName' => 'mb_strtolower', 'argumentCount' => [1]], + 'strtoupper' => ['alternativeName' => 'mb_strtoupper', 'argumentCount' => [1]], + 'substr' => ['alternativeName' => 'mb_substr', 'argumentCount' => [2, 3]], + 'substr_count' => ['alternativeName' => 'mb_substr_count', 'argumentCount' => [2, 3, 4]], + ]; + + /** + * @var array< + * string, + * array{ + * alternativeName: string, + * argumentCount: list, + * }, + * > + */ + private array $functions; + + public function __construct() + { + parent::__construct(); + + if (\PHP_VERSION_ID >= 8_03_00) { + self::$functionsMap['str_pad'] = ['alternativeName' => 'mb_str_pad', 'argumentCount' => [1, 2, 3, 4]]; + } + + if (\PHP_VERSION_ID >= 8_04_00) { + self::$functionsMap['trim'] = ['alternativeName' => 'mb_trim', 'argumentCount' => [1, 2]]; + self::$functionsMap['ltrim'] = ['alternativeName' => 'mb_ltrim', 'argumentCount' => [1, 2]]; + self::$functionsMap['rtrim'] = ['alternativeName' => 'mb_rtrim', 'argumentCount' => [1, 2]]; + } + + $this->functions = array_filter( + self::$functionsMap, + static fn (array $mapping): bool => (new \ReflectionFunction($mapping['alternativeName']))->isInternal() + ); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Replace non multibyte-safe functions with corresponding mb function.', + [ + new CodeSample( + 'functions as $functionIdentity => $functionReplacement) { + $currIndex = 0; + do { + // try getting function reference and translate boundaries for humans + $boundaries = $this->find($functionIdentity, $tokens, $currIndex, $tokens->count() - 1); + if (null === $boundaries) { + // next function search, as current one not found + continue 2; + } + + [$functionName, $openParenthesis, $closeParenthesis] = $boundaries; + $count = $argumentsAnalyzer->countArguments($tokens, $openParenthesis, $closeParenthesis); + if (!\in_array($count, $functionReplacement['argumentCount'], true)) { + continue 2; + } + + // analysing cursor shift, so nested calls could be processed + $currIndex = $openParenthesis; + + $tokens[$functionName] = new Token([T_STRING, $functionReplacement['alternativeName']]); + } while (null !== $currIndex); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/ModernizeStrposFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/ModernizeStrposFixer.php new file mode 100644 index 00000000..b060b4e6 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/ModernizeStrposFixer.php @@ -0,0 +1,233 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Alias; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Alexander M. Turek + */ +final class ModernizeStrposFixer extends AbstractFixer +{ + private const REPLACEMENTS = [ + [ + 'operator' => [T_IS_IDENTICAL, '==='], + 'operand' => [T_LNUMBER, '0'], + 'replacement' => [T_STRING, 'str_starts_with'], + 'negate' => false, + ], + [ + 'operator' => [T_IS_NOT_IDENTICAL, '!=='], + 'operand' => [T_LNUMBER, '0'], + 'replacement' => [T_STRING, 'str_starts_with'], + 'negate' => true, + ], + [ + 'operator' => [T_IS_NOT_IDENTICAL, '!=='], + 'operand' => [T_STRING, 'false'], + 'replacement' => [T_STRING, 'str_contains'], + 'negate' => false, + ], + [ + 'operator' => [T_IS_IDENTICAL, '==='], + 'operand' => [T_STRING, 'false'], + 'replacement' => [T_STRING, 'str_contains'], + 'negate' => true, + ], + ]; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Replace `strpos()` calls with `str_starts_with()` or `str_contains()` if possible.', + [ + new CodeSample( + 'isTokenKindFound(T_STRING) && $tokens->isAnyTokenKindsFound([T_IS_IDENTICAL, T_IS_NOT_IDENTICAL]); + } + + public function isRisky(): bool + { + return true; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $functionsAnalyzer = new FunctionsAnalyzer(); + $argumentsAnalyzer = new ArgumentsAnalyzer(); + + for ($index = \count($tokens) - 1; $index > 0; --$index) { + // find candidate function call + if (!$tokens[$index]->equals([T_STRING, 'strpos'], false) || !$functionsAnalyzer->isGlobalFunctionCall($tokens, $index)) { + continue; + } + + // assert called with 2 arguments + $openIndex = $tokens->getNextMeaningfulToken($index); + $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex); + $arguments = $argumentsAnalyzer->getArguments($tokens, $openIndex, $closeIndex); + + if (2 !== \count($arguments)) { + continue; + } + + // check if part condition and fix if needed + $compareTokens = $this->getCompareTokens($tokens, $index, -1); // look behind + + if (null === $compareTokens) { + $compareTokens = $this->getCompareTokens($tokens, $closeIndex, 1); // look ahead + } + + if (null !== $compareTokens) { + $this->fixCall($tokens, $index, $compareTokens); + } + } + } + + /** + * @param array{operator_index: int, operand_index: int} $operatorIndices + */ + private function fixCall(Tokens $tokens, int $functionIndex, array $operatorIndices): void + { + foreach (self::REPLACEMENTS as $replacement) { + if (!$tokens[$operatorIndices['operator_index']]->equals($replacement['operator'])) { + continue; + } + + if (!$tokens[$operatorIndices['operand_index']]->equals($replacement['operand'], false)) { + continue; + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($operatorIndices['operator_index']); + $tokens->clearTokenAndMergeSurroundingWhitespace($operatorIndices['operand_index']); + $tokens->clearTokenAndMergeSurroundingWhitespace($functionIndex); + + if ($replacement['negate']) { + $negateInsertIndex = $functionIndex; + + $prevFunctionIndex = $tokens->getPrevMeaningfulToken($functionIndex); + if ($tokens[$prevFunctionIndex]->isGivenKind(T_NS_SEPARATOR)) { + $negateInsertIndex = $prevFunctionIndex; + } + + $tokens->insertAt($negateInsertIndex, new Token('!')); + ++$functionIndex; + } + + $tokens->insertAt($functionIndex, new Token($replacement['replacement'])); + + break; + } + } + + /** + * @param -1|1 $direction + * + * @return null|array{operator_index: int, operand_index: int} + */ + private function getCompareTokens(Tokens $tokens, int $offsetIndex, int $direction): ?array + { + $operatorIndex = $tokens->getMeaningfulTokenSibling($offsetIndex, $direction); + + if (null !== $operatorIndex && $tokens[$operatorIndex]->isGivenKind(T_NS_SEPARATOR)) { + $operatorIndex = $tokens->getMeaningfulTokenSibling($operatorIndex, $direction); + } + + if (null === $operatorIndex || !$tokens[$operatorIndex]->isGivenKind([T_IS_IDENTICAL, T_IS_NOT_IDENTICAL])) { + return null; + } + + $operandIndex = $tokens->getMeaningfulTokenSibling($operatorIndex, $direction); + + if (null === $operandIndex) { + return null; + } + + $operand = $tokens[$operandIndex]; + + if (!$operand->equals([T_LNUMBER, '0']) && !$operand->equals([T_STRING, 'false'], false)) { + return null; + } + + $precedenceTokenIndex = $tokens->getMeaningfulTokenSibling($operandIndex, $direction); + + if (null !== $precedenceTokenIndex && $this->isOfHigherPrecedence($tokens[$precedenceTokenIndex])) { + return null; + } + + return ['operator_index' => $operatorIndex, 'operand_index' => $operandIndex]; + } + + private function isOfHigherPrecedence(Token $token): bool + { + static $operatorsKinds = [ + T_DEC, // -- + T_INC, // ++ + T_INSTANCEOF, // instanceof + T_IS_GREATER_OR_EQUAL, // >= + T_IS_SMALLER_OR_EQUAL, // <= + T_POW, // ** + T_SL, // << + T_SR, // >> + ]; + + static $operatorsPerContent = [ + '!', + '%', + '*', + '+', + '-', + '.', + '/', + '<', + '>', + '~', + ]; + + return $token->isGivenKind($operatorsKinds) || $token->equalsAny($operatorsPerContent); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/NoAliasFunctionsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/NoAliasFunctionsFixer.php new file mode 100644 index 00000000..2a3d231d --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/NoAliasFunctionsFixer.php @@ -0,0 +1,322 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Alias; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Vladimir Reznichenko + * @author Dariusz Rumiński + */ +final class NoAliasFunctionsFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + private const SETS = [ + '@internal' => [ + 'diskfreespace' => 'disk_free_space', + + 'dns_check_record' => 'checkdnsrr', + 'dns_get_mx' => 'getmxrr', + + 'session_commit' => 'session_write_close', + + 'stream_register_wrapper' => 'stream_wrapper_register', + 'set_file_buffer' => 'stream_set_write_buffer', + 'socket_set_blocking' => 'stream_set_blocking', + 'socket_get_status' => 'stream_get_meta_data', + 'socket_set_timeout' => 'stream_set_timeout', + 'socket_getopt' => 'socket_get_option', + 'socket_setopt' => 'socket_set_option', + + 'chop' => 'rtrim', + 'close' => 'closedir', + 'doubleval' => 'floatval', + 'fputs' => 'fwrite', + 'get_required_files' => 'get_included_files', + 'ini_alter' => 'ini_set', + 'is_double' => 'is_float', + 'is_integer' => 'is_int', + 'is_long' => 'is_int', + 'is_real' => 'is_float', + 'is_writeable' => 'is_writable', + 'join' => 'implode', + 'key_exists' => 'array_key_exists', + 'magic_quotes_runtime' => 'set_magic_quotes_runtime', + 'pos' => 'current', + 'show_source' => 'highlight_file', + 'sizeof' => 'count', + 'strchr' => 'strstr', + 'user_error' => 'trigger_error', + ], + + '@IMAP' => [ + 'imap_create' => 'imap_createmailbox', + 'imap_fetchtext' => 'imap_body', + 'imap_header' => 'imap_headerinfo', + 'imap_listmailbox' => 'imap_list', + 'imap_listsubscribed' => 'imap_lsub', + 'imap_rename' => 'imap_renamemailbox', + 'imap_scan' => 'imap_listscan', + 'imap_scanmailbox' => 'imap_listscan', + ], + + '@ldap' => [ + 'ldap_close' => 'ldap_unbind', + 'ldap_modify' => 'ldap_mod_replace', + ], + + '@mysqli' => [ + 'mysqli_execute' => 'mysqli_stmt_execute', + 'mysqli_set_opt' => 'mysqli_options', + 'mysqli_escape_string' => 'mysqli_real_escape_string', + ], + + '@pg' => [ + 'pg_exec' => 'pg_query', + ], + + '@oci' => [ + 'oci_free_cursor' => 'oci_free_statement', + ], + + '@odbc' => [ + 'odbc_do' => 'odbc_exec', + 'odbc_field_precision' => 'odbc_field_len', + ], + + '@mbreg' => [ + 'mbereg' => 'mb_ereg', + 'mbereg_match' => 'mb_ereg_match', + 'mbereg_replace' => 'mb_ereg_replace', + 'mbereg_search' => 'mb_ereg_search', + 'mbereg_search_getpos' => 'mb_ereg_search_getpos', + 'mbereg_search_getregs' => 'mb_ereg_search_getregs', + 'mbereg_search_init' => 'mb_ereg_search_init', + 'mbereg_search_pos' => 'mb_ereg_search_pos', + 'mbereg_search_regs' => 'mb_ereg_search_regs', + 'mbereg_search_setpos' => 'mb_ereg_search_setpos', + 'mberegi' => 'mb_eregi', + 'mberegi_replace' => 'mb_eregi_replace', + 'mbregex_encoding' => 'mb_regex_encoding', + 'mbsplit' => 'mb_split', + ], + + '@openssl' => [ + 'openssl_get_publickey' => 'openssl_pkey_get_public', + 'openssl_get_privatekey' => 'openssl_pkey_get_private', + ], + + '@sodium' => [ + 'sodium_crypto_scalarmult_base' => 'sodium_crypto_box_publickey_from_secretkey', + ], + + '@exif' => [ + 'read_exif_data' => 'exif_read_data', + ], + + '@ftp' => [ + 'ftp_quit' => 'ftp_close', + ], + + '@posix' => [ + 'posix_errno' => 'posix_get_last_error', + ], + + '@pcntl' => [ + 'pcntl_errno' => 'pcntl_get_last_error', + ], + + '@time' => [ + 'mktime' => ['time', 0], + 'gmmktime' => ['time', 0], + ], + ]; + + /** + * @var array stores alias (key) - master (value) functions mapping + */ + private array $aliases = []; + + public function configure(array $configuration): void + { + parent::configure($configuration); + + $this->aliases = []; + + foreach ($this->configuration['sets'] as $set) { + if ('@all' === $set) { + $this->aliases = array_merge(...array_values(self::SETS)); + + break; + } + + $this->aliases = array_merge($this->aliases, self::SETS[$set]); + } + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Master functions shall be used instead of aliases.', + [ + new CodeSample( + ' ['@mbreg']] + ), + ], + null, + 'Risky when any of the alias functions are overridden.' + ); + } + + /** + * {@inheritdoc} + * + * Must run before ImplodeCallFixer, PhpUnitDedicateAssertFixer. + */ + public function getPriority(): int + { + return 40; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_STRING); + } + + public function isRisky(): bool + { + return true; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $functionsAnalyzer = new FunctionsAnalyzer(); + $argumentsAnalyzer = new ArgumentsAnalyzer(); + + /** @var Token $token */ + foreach ($tokens->findGivenKind(T_STRING) as $index => $token) { + // check mapping hit + $tokenContent = strtolower($token->getContent()); + + if (!isset($this->aliases[$tokenContent])) { + continue; + } + + // skip expressions without parameters list + $openParenthesis = $tokens->getNextMeaningfulToken($index); + + if (!$tokens[$openParenthesis]->equals('(')) { + continue; + } + + if (!$functionsAnalyzer->isGlobalFunctionCall($tokens, $index)) { + continue; + } + + if (\is_array($this->aliases[$tokenContent])) { + [$alias, $numberOfArguments] = $this->aliases[$tokenContent]; + + $count = $argumentsAnalyzer->countArguments($tokens, $openParenthesis, $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesis)); + + if ($numberOfArguments !== $count) { + continue; + } + } else { + $alias = $this->aliases[$tokenContent]; + } + + $tokens[$index] = new Token([T_STRING, $alias]); + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + $sets = [ + '@all' => 'all listed sets', + '@internal' => 'native functions', + '@exif' => 'EXIF functions', + '@ftp' => 'FTP functions', + '@IMAP' => 'IMAP functions', + '@ldap' => 'LDAP functions', + '@mbreg' => 'from `ext-mbstring`', + '@mysqli' => 'mysqli functions', + '@oci' => 'oci functions', + '@odbc' => 'odbc functions', + '@openssl' => 'openssl functions', + '@pcntl' => 'PCNTL functions', + '@pg' => 'pg functions', + '@posix' => 'POSIX functions', + '@snmp' => 'SNMP functions', // @TODO Remove on next major 4.0 as this set is now empty + '@sodium' => 'libsodium functions', + '@time' => 'time functions', + ]; + + $list = "List of sets to fix. Defined sets are:\n\n"; + + foreach ($sets as $set => $description) { + $list .= sprintf("* `%s` (%s);\n", $set, $description); + } + + $list = rtrim($list, ";\n").'.'; + + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('sets', $list)) + ->setAllowedTypes(['array']) + ->setAllowedValues([new AllowedValueSubset(array_keys($sets))]) + ->setDefault(['@internal', '@IMAP', '@pg']) + ->getOption(), + ]); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/NoAliasLanguageConstructCallFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/NoAliasLanguageConstructCallFixer.php new file mode 100644 index 00000000..4df764ab --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/NoAliasLanguageConstructCallFixer.php @@ -0,0 +1,59 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Alias; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class NoAliasLanguageConstructCallFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Master language constructs shall be used instead of aliases.', + [ + new CodeSample( + 'isTokenKindFound(T_EXIT); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_EXIT)) { + continue; + } + + if ('exit' === strtolower($token->getContent())) { + continue; + } + + $tokens[$index] = new Token([T_EXIT, 'exit']); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/NoMixedEchoPrintFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/NoMixedEchoPrintFixer.php new file mode 100644 index 00000000..ff5e666c --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/NoMixedEchoPrintFixer.php @@ -0,0 +1,139 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Alias; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Sullivan Senechal + */ +final class NoMixedEchoPrintFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** + * @var string + */ + private $callBack; + + /** + * @var int T_ECHO or T_PRINT + */ + private $candidateTokenType; + + public function configure(array $configuration): void + { + parent::configure($configuration); + + if ('echo' === $this->configuration['use']) { + $this->candidateTokenType = T_PRINT; + $this->callBack = 'fixPrintToEcho'; + } else { + $this->candidateTokenType = T_ECHO; + $this->callBack = 'fixEchoToPrint'; + } + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Either language construct `print` or `echo` should be used.', + [ + new CodeSample(" 'print']), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run after EchoTagSyntaxFixer. + */ + public function getPriority(): int + { + return -10; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound($this->candidateTokenType); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $callBack = $this->callBack; + foreach ($tokens as $index => $token) { + if ($token->isGivenKind($this->candidateTokenType)) { + $this->{$callBack}($tokens, $index); + } + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('use', 'The desired language construct.')) + ->setAllowedValues(['print', 'echo']) + ->setDefault('echo') + ->getOption(), + ]); + } + + private function fixEchoToPrint(Tokens $tokens, int $index): void + { + $nextTokenIndex = $tokens->getNextMeaningfulToken($index); + $endTokenIndex = $tokens->getNextTokenOfKind($index, [';', [T_CLOSE_TAG]]); + $canBeConverted = true; + + for ($i = $nextTokenIndex; $i < $endTokenIndex; ++$i) { + if ($tokens[$i]->equalsAny(['(', [CT::T_ARRAY_SQUARE_BRACE_OPEN]])) { + $blockType = Tokens::detectBlockType($tokens[$i]); + $i = $tokens->findBlockEnd($blockType['type'], $i); + } + + if ($tokens[$i]->equals(',')) { + $canBeConverted = false; + + break; + } + } + + if (false === $canBeConverted) { + return; + } + + $tokens[$index] = new Token([T_PRINT, 'print']); + } + + private function fixPrintToEcho(Tokens $tokens, int $index): void + { + $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; + + if (!$prevToken->equalsAny([';', '{', '}', ')', [T_OPEN_TAG], [T_ELSE]])) { + return; + } + + $tokens[$index] = new Token([T_ECHO, 'echo']); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/PowToExponentiationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/PowToExponentiationFixer.php new file mode 100644 index 00000000..d626a930 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/PowToExponentiationFixer.php @@ -0,0 +1,219 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Alias; + +use PhpCsFixer\AbstractFunctionReferenceFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class PowToExponentiationFixer extends AbstractFunctionReferenceFixer +{ + public function isCandidate(Tokens $tokens): bool + { + // minimal candidate to fix is seven tokens: pow(x,y); + return $tokens->count() > 7 && $tokens->isTokenKindFound(T_STRING); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Converts `pow` to the `**` operator.', + [ + new CodeSample( + "findPowCalls($tokens); + $argumentsAnalyzer = new ArgumentsAnalyzer(); + $numberOfTokensAdded = 0; + $previousCloseParenthesisIndex = \count($tokens); + + foreach (array_reverse($candidates) as $candidate) { + // if in the previous iteration(s) tokens were added to the collection and this is done within the tokens + // indices of the current candidate than the index of the close ')' of the candidate has moved and so + // the index needs to be updated + if ($previousCloseParenthesisIndex < $candidate[2]) { + $previousCloseParenthesisIndex = $candidate[2]; + $candidate[2] += $numberOfTokensAdded; + } else { + $previousCloseParenthesisIndex = $candidate[2]; + $numberOfTokensAdded = 0; + } + + $arguments = $argumentsAnalyzer->getArguments($tokens, $candidate[1], $candidate[2]); + + if (2 !== \count($arguments)) { + continue; + } + + for ($i = $candidate[1]; $i < $candidate[2]; ++$i) { + if ($tokens[$i]->isGivenKind(T_ELLIPSIS)) { + continue 2; + } + } + + $numberOfTokensAdded += $this->fixPowToExponentiation( + $tokens, + $candidate[0], // functionNameIndex, + $candidate[1], // openParenthesisIndex, + $candidate[2], // closeParenthesisIndex, + $arguments + ); + } + } + + /** + * @return array + */ + private function findPowCalls(Tokens $tokens): array + { + $candidates = []; + + // Minimal candidate to fix is seven tokens: pow(x,y); + $end = \count($tokens) - 6; + + // First possible location is after the open token: 1 + for ($i = 1; $i < $end; ++$i) { + $candidate = $this->find('pow', $tokens, $i, $end); + + if (null === $candidate) { + break; + } + + $i = $candidate[1]; // proceed to openParenthesisIndex + $candidates[] = $candidate; + } + + return $candidates; + } + + /** + * @param array $arguments + * + * @return int number of tokens added to the collection + */ + private function fixPowToExponentiation(Tokens $tokens, int $functionNameIndex, int $openParenthesisIndex, int $closeParenthesisIndex, array $arguments): int + { + // find the argument separator ',' directly after the last token of the first argument; + // replace it with T_POW '**' + $tokens[$tokens->getNextTokenOfKind(reset($arguments), [','])] = new Token([T_POW, '**']); + + // clean up the function call tokens prt. I + $tokens->clearAt($closeParenthesisIndex); + $previousIndex = $tokens->getPrevMeaningfulToken($closeParenthesisIndex); + + if ($tokens[$previousIndex]->equals(',')) { + $tokens->clearAt($previousIndex); // trailing ',' in function call (PHP 7.3) + } + + $added = 0; + + // check if the arguments need to be wrapped in parentheses + foreach (array_reverse($arguments, true) as $argumentStartIndex => $argumentEndIndex) { + if ($this->isParenthesisNeeded($tokens, $argumentStartIndex, $argumentEndIndex)) { + $tokens->insertAt($argumentEndIndex + 1, new Token(')')); + $tokens->insertAt($argumentStartIndex, new Token('(')); + $added += 2; + } + } + + // clean up the function call tokens prt. II + $tokens->clearAt($openParenthesisIndex); + $tokens->clearAt($functionNameIndex); + + $prevMeaningfulTokenIndex = $tokens->getPrevMeaningfulToken($functionNameIndex); + + if ($tokens[$prevMeaningfulTokenIndex]->isGivenKind(T_NS_SEPARATOR)) { + $tokens->clearAt($prevMeaningfulTokenIndex); + } + + return $added; + } + + private function isParenthesisNeeded(Tokens $tokens, int $argumentStartIndex, int $argumentEndIndex): bool + { + static $allowedKinds = null; + + if (null === $allowedKinds) { + $allowedKinds = $this->getAllowedKinds(); + } + + for ($i = $argumentStartIndex; $i <= $argumentEndIndex; ++$i) { + if ($tokens[$i]->isGivenKind($allowedKinds) || $tokens->isEmptyAt($i)) { + continue; + } + + $blockType = Tokens::detectBlockType($tokens[$i]); + + if (null !== $blockType) { + $i = $tokens->findBlockEnd($blockType['type'], $i); + + continue; + } + + if ($tokens[$i]->equals('$')) { + $i = $tokens->getNextMeaningfulToken($i); + if ($tokens[$i]->isGivenKind(CT::T_DYNAMIC_VAR_BRACE_OPEN)) { + $i = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_DYNAMIC_VAR_BRACE, $i); + + continue; + } + } + + if ($tokens[$i]->equals('+') && $tokens->getPrevMeaningfulToken($i) < $argumentStartIndex) { + continue; + } + + return true; + } + + return false; + } + + /** + * @return list + */ + private function getAllowedKinds(): array + { + return [ + T_DNUMBER, T_LNUMBER, T_VARIABLE, T_STRING, T_CONSTANT_ENCAPSED_STRING, T_DOUBLE_CAST, + T_INT_CAST, T_INC, T_DEC, T_NS_SEPARATOR, T_WHITESPACE, T_DOUBLE_COLON, T_LINE, T_COMMENT, T_DOC_COMMENT, + CT::T_NAMESPACE_OPERATOR, + ...Token::getObjectOperatorKinds(), + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/RandomApiMigrationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/RandomApiMigrationFixer.php new file mode 100644 index 00000000..c65f6932 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/RandomApiMigrationFixer.php @@ -0,0 +1,158 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Alias; + +use PhpCsFixer\AbstractFunctionReferenceFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; + +/** + * @author Vladimir Reznichenko + */ +final class RandomApiMigrationFixer extends AbstractFunctionReferenceFixer implements ConfigurableFixerInterface +{ + /** + * @var array> + */ + private static array $argumentCounts = [ + 'getrandmax' => [0], + 'mt_rand' => [1, 2], + 'rand' => [0, 2], + 'srand' => [0, 1], + 'random_int' => [0, 2], + ]; + + public function configure(array $configuration): void + { + parent::configure($configuration); + + foreach ($this->configuration['replacements'] as $functionName => $replacement) { + $this->configuration['replacements'][$functionName] = [ + 'alternativeName' => $replacement, + 'argumentCount' => self::$argumentCounts[$functionName], + ]; + } + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Replaces `rand`, `srand`, `getrandmax` functions calls with their `mt_*` analogs or `random_int`.', + [ + new CodeSample(" ['getrandmax' => 'mt_getrandmax']] + ), + new CodeSample( + " ['rand' => 'random_int']] + ), + ], + null, + 'Risky when the configured functions are overridden. Or when relying on the seed based generating of the numbers.' + ); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $argumentsAnalyzer = new ArgumentsAnalyzer(); + + foreach ($this->configuration['replacements'] as $functionIdentity => $functionReplacement) { + if ($functionIdentity === $functionReplacement['alternativeName']) { + continue; + } + + $currIndex = 0; + + do { + // try getting function reference and translate boundaries for humans + $boundaries = $this->find($functionIdentity, $tokens, $currIndex, $tokens->count() - 1); + + if (null === $boundaries) { + // next function search, as current one not found + continue 2; + } + + [$functionName, $openParenthesis, $closeParenthesis] = $boundaries; + $count = $argumentsAnalyzer->countArguments($tokens, $openParenthesis, $closeParenthesis); + + if (!\in_array($count, $functionReplacement['argumentCount'], true)) { + continue 2; + } + + // analysing cursor shift, so nested calls could be processed + $currIndex = $openParenthesis; + $tokens[$functionName] = new Token([T_STRING, $functionReplacement['alternativeName']]); + + if (0 === $count && 'random_int' === $functionReplacement['alternativeName']) { + $tokens->insertAt($currIndex + 1, [ + new Token([T_LNUMBER, '0']), + new Token(','), + new Token([T_WHITESPACE, ' ']), + new Token([T_STRING, 'getrandmax']), + new Token('('), + new Token(')'), + ]); + + $currIndex += 6; + } + } while (null !== $currIndex); + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('replacements', 'Mapping between replaced functions with the new ones.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([static function (array $value): bool { + foreach ($value as $functionName => $replacement) { + if (!\array_key_exists($functionName, self::$argumentCounts)) { + throw new InvalidOptionsException(sprintf( + 'Function "%s" is not handled by the fixer.', + $functionName + )); + } + + if (!\is_string($replacement)) { + throw new InvalidOptionsException(sprintf( + 'Replacement for function "%s" must be a string, "%s" given.', + $functionName, + get_debug_type($replacement) + )); + } + } + + return true; + }]) + ->setDefault([ + 'getrandmax' => 'mt_getrandmax', + 'rand' => 'mt_rand', // @TODO change to `random_int` as default on 4.0 + 'srand' => 'mt_srand', + ]) + ->getOption(), + ]); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/SetTypeToCastFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/SetTypeToCastFixer.php new file mode 100644 index 00000000..2c807eb5 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/SetTypeToCastFixer.php @@ -0,0 +1,240 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Alias; + +use PhpCsFixer\AbstractFunctionReferenceFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class SetTypeToCastFixer extends AbstractFunctionReferenceFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Cast shall be used, not `settype`.', + [ + new CodeSample( + 'isAllTokenKindsFound([T_CONSTANT_ENCAPSED_STRING, T_STRING, T_VARIABLE]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $map = [ + 'array' => [T_ARRAY_CAST, '(array)'], + 'bool' => [T_BOOL_CAST, '(bool)'], + 'boolean' => [T_BOOL_CAST, '(bool)'], + 'double' => [T_DOUBLE_CAST, '(float)'], + 'float' => [T_DOUBLE_CAST, '(float)'], + 'int' => [T_INT_CAST, '(int)'], + 'integer' => [T_INT_CAST, '(int)'], + 'object' => [T_OBJECT_CAST, '(object)'], + 'string' => [T_STRING_CAST, '(string)'], + // note: `'null' is dealt with later on + ]; + + $argumentsAnalyzer = new ArgumentsAnalyzer(); + + foreach (array_reverse($this->findSettypeCalls($tokens)) as $candidate) { + $functionNameIndex = $candidate[0]; + + $arguments = $argumentsAnalyzer->getArguments($tokens, $candidate[1], $candidate[2]); + if (2 !== \count($arguments)) { + continue; // function must be overridden or used incorrectly + } + + $prev = $tokens->getPrevMeaningfulToken($functionNameIndex); + + if (!$tokens[$prev]->equalsAny([';', '{', '}', [T_OPEN_TAG]])) { + continue; // return value of the function is used + } + + reset($arguments); + + // --- Test first argument -------------------- + + $firstArgumentStart = key($arguments); + if ($tokens[$firstArgumentStart]->isComment() || $tokens[$firstArgumentStart]->isWhitespace()) { + $firstArgumentStart = $tokens->getNextMeaningfulToken($firstArgumentStart); + } + + if (!$tokens[$firstArgumentStart]->isGivenKind(T_VARIABLE)) { + continue; // settype only works with variables pass by reference, function must be overridden + } + + $commaIndex = $tokens->getNextMeaningfulToken($firstArgumentStart); + + if (null === $commaIndex || !$tokens[$commaIndex]->equals(',')) { + continue; // first argument is complex statement; function must be overridden + } + + // --- Test second argument ------------------- + + next($arguments); + $secondArgumentStart = key($arguments); + $secondArgumentEnd = $arguments[$secondArgumentStart]; + + if ($tokens[$secondArgumentStart]->isComment() || $tokens[$secondArgumentStart]->isWhitespace()) { + $secondArgumentStart = $tokens->getNextMeaningfulToken($secondArgumentStart); + } + + if ( + !$tokens[$secondArgumentStart]->isGivenKind(T_CONSTANT_ENCAPSED_STRING) + || $tokens->getNextMeaningfulToken($secondArgumentStart) < $secondArgumentEnd + ) { + continue; // second argument is of the wrong type or is a (complex) statement of some sort (function is overridden) + } + + // --- Test type ------------------------------ + + $type = strtolower(trim($tokens[$secondArgumentStart]->getContent(), '"\'"')); + + if ('null' !== $type && !isset($map[$type])) { + continue; // we don't know how to map + } + + // --- Fixing --------------------------------- + + $argumentToken = $tokens[$firstArgumentStart]; + + $this->removeSettypeCall( + $tokens, + $functionNameIndex, + $candidate[1], + $firstArgumentStart, + $commaIndex, + $secondArgumentStart, + $candidate[2] + ); + + if ('null' === $type) { + $this->fixSettypeNullCall($tokens, $functionNameIndex, $argumentToken); + } else { + $this->fixSettypeCall($tokens, $functionNameIndex, $argumentToken, new Token($map[$type])); + } + } + } + + /** + * @return list> + */ + private function findSettypeCalls(Tokens $tokens): array + { + $candidates = []; + + $end = \count($tokens); + for ($i = 1; $i < $end; ++$i) { + $candidate = $this->find('settype', $tokens, $i, $end); + if (null === $candidate) { + break; + } + + $i = $candidate[1]; // proceed to openParenthesisIndex + $candidates[] = $candidate; + } + + return $candidates; + } + + private function removeSettypeCall( + Tokens $tokens, + int $functionNameIndex, + int $openParenthesisIndex, + int $firstArgumentStart, + int $commaIndex, + int $secondArgumentStart, + int $closeParenthesisIndex + ): void { + $tokens->clearTokenAndMergeSurroundingWhitespace($closeParenthesisIndex); + $prevIndex = $tokens->getPrevMeaningfulToken($closeParenthesisIndex); + if ($tokens[$prevIndex]->equals(',')) { + $tokens->clearTokenAndMergeSurroundingWhitespace($prevIndex); + } + $tokens->clearTokenAndMergeSurroundingWhitespace($secondArgumentStart); + $tokens->clearTokenAndMergeSurroundingWhitespace($commaIndex); + $tokens->clearTokenAndMergeSurroundingWhitespace($firstArgumentStart); + $tokens->clearTokenAndMergeSurroundingWhitespace($openParenthesisIndex); + $tokens->clearAt($functionNameIndex); // we'll be inserting here so no need to merge the space tokens + $tokens->clearEmptyTokens(); + } + + private function fixSettypeCall( + Tokens $tokens, + int $functionNameIndex, + Token $argumentToken, + Token $castToken + ): void { + $tokens->insertAt( + $functionNameIndex, + [ + clone $argumentToken, + new Token([T_WHITESPACE, ' ']), + new Token('='), + new Token([T_WHITESPACE, ' ']), + $castToken, + new Token([T_WHITESPACE, ' ']), + clone $argumentToken, + ] + ); + + $tokens->removeTrailingWhitespace($functionNameIndex + 6); // 6 = number of inserted tokens -1 for offset correction + } + + private function fixSettypeNullCall( + Tokens $tokens, + int $functionNameIndex, + Token $argumentToken + ): void { + $tokens->insertAt( + $functionNameIndex, + [ + clone $argumentToken, + new Token([T_WHITESPACE, ' ']), + new Token('='), + new Token([T_WHITESPACE, ' ']), + new Token([T_STRING, 'null']), + ] + ); + + $tokens->removeTrailingWhitespace($functionNameIndex + 4); // 4 = number of inserted tokens -1 for offset correction + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/ArraySyntaxFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/ArraySyntaxFixer.php new file mode 100644 index 00000000..a7b0b20f --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/ArraySyntaxFixer.php @@ -0,0 +1,136 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ArrayNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Gregor Harlan + * @author Sebastiaan Stok + * @author Dariusz Rumiński + */ +final class ArraySyntaxFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** + * @var null|int + */ + private $candidateTokenKind; + + /** + * @var null|string + */ + private $fixCallback; + + public function configure(array $configuration): void + { + parent::configure($configuration); + + $this->resolveCandidateTokenKind(); + $this->resolveFixCallback(); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'PHP arrays should be declared using the configured syntax.', + [ + new CodeSample( + " 'long'] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before BinaryOperatorSpacesFixer, SingleSpaceAfterConstructFixer, SingleSpaceAroundConstructFixer, TernaryOperatorSpacesFixer. + */ + public function getPriority(): int + { + return 37; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound($this->candidateTokenKind); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $callback = $this->fixCallback; + + for ($index = $tokens->count() - 1; 0 <= $index; --$index) { + if ($tokens[$index]->isGivenKind($this->candidateTokenKind)) { + $this->{$callback}($tokens, $index); + } + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('syntax', 'Whether to use the `long` or `short` array syntax.')) + ->setAllowedValues(['long', 'short']) + ->setDefault('short') + ->getOption(), + ]); + } + + private function fixToLongArraySyntax(Tokens $tokens, int $index): void + { + $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $index); + + $tokens[$index] = new Token('('); + $tokens[$closeIndex] = new Token(')'); + + $tokens->insertAt($index, new Token([T_ARRAY, 'array'])); + } + + private function fixToShortArraySyntax(Tokens $tokens, int $index): void + { + $openIndex = $tokens->getNextTokenOfKind($index, ['(']); + $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex); + + $tokens[$openIndex] = new Token([CT::T_ARRAY_SQUARE_BRACE_OPEN, '[']); + $tokens[$closeIndex] = new Token([CT::T_ARRAY_SQUARE_BRACE_CLOSE, ']']); + + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } + + private function resolveFixCallback(): void + { + $this->fixCallback = sprintf('fixTo%sArraySyntax', ucfirst($this->configuration['syntax'])); + } + + private function resolveCandidateTokenKind(): void + { + $this->candidateTokenKind = 'long' === $this->configuration['syntax'] ? CT::T_ARRAY_SQUARE_BRACE_OPEN : T_ARRAY; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NoMultilineWhitespaceAroundDoubleArrowFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NoMultilineWhitespaceAroundDoubleArrowFixer.php new file mode 100644 index 00000000..b7a888f0 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NoMultilineWhitespaceAroundDoubleArrowFixer.php @@ -0,0 +1,80 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ArrayNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Carlos Cirello + * @author Dariusz Rumiński + * @author Graham Campbell + */ +final class NoMultilineWhitespaceAroundDoubleArrowFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Operator `=>` should not be surrounded by multi-line whitespaces.', + [new CodeSample(" 2);\n")] + ); + } + + /** + * {@inheritdoc} + * + * Must run before BinaryOperatorSpacesFixer, MethodArgumentSpaceFixer. + */ + public function getPriority(): int + { + return 31; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_DOUBLE_ARROW); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_DOUBLE_ARROW)) { + continue; + } + + if (!$tokens[$index - 2]->isComment() || str_starts_with($tokens[$index - 2]->getContent(), '/*')) { + $this->fixWhitespace($tokens, $index - 1); + } + + // do not move anything about if there is a comment following the whitespace + if (!$tokens[$index + 2]->isComment()) { + $this->fixWhitespace($tokens, $index + 1); + } + } + } + + private function fixWhitespace(Tokens $tokens, int $index): void + { + $token = $tokens[$index]; + + if ($token->isWhitespace() && !$token->isWhitespace(" \t")) { + $tokens[$index] = new Token([T_WHITESPACE, rtrim($token->getContent()).' ']); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NoTrailingCommaInSinglelineArrayFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NoTrailingCommaInSinglelineArrayFixer.php new file mode 100644 index 00000000..6d133841 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NoTrailingCommaInSinglelineArrayFixer.php @@ -0,0 +1,52 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ArrayNotation; + +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\Fixer\Basic\NoTrailingCommaInSinglelineFixer; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; + +/** + * @deprecated + * + * @author Dariusz Rumiński + * @author Sebastiaan Stok + */ +final class NoTrailingCommaInSinglelineArrayFixer extends AbstractProxyFixer implements DeprecatedFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'PHP single-line arrays should not have trailing comma.', + [new CodeSample("proxyFixers); + } + + protected function createProxyFixers(): array + { + $fixer = new NoTrailingCommaInSinglelineFixer(); + $fixer->configure(['elements' => ['array']]); + + return [$fixer]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NoWhitespaceBeforeCommaInArrayFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NoWhitespaceBeforeCommaInArrayFixer.php new file mode 100644 index 00000000..f2ff389d --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NoWhitespaceBeforeCommaInArrayFixer.php @@ -0,0 +1,140 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ArrayNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Adam Marczuk + */ +final class NoWhitespaceBeforeCommaInArrayFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'In array declaration, there MUST NOT be a whitespace before each comma.', + [ + new CodeSample(" true] + ), + ] + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index > 0; --$index) { + if ($tokens[$index]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN])) { + $this->fixSpacing($index, $tokens); + } + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('after_heredoc', 'Whether the whitespace between heredoc end and comma should be removed.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + + /** + * Method to fix spacing in array declaration. + */ + private function fixSpacing(int $index, Tokens $tokens): void + { + if ($tokens[$index]->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) { + $startIndex = $index; + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $startIndex); + } else { + $startIndex = $tokens->getNextTokenOfKind($index, ['(']); + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startIndex); + } + + for ($i = $endIndex - 1; $i > $startIndex; --$i) { + $i = $this->skipNonArrayElements($i, $tokens); + $currentToken = $tokens[$i]; + $prevIndex = $tokens->getPrevNonWhitespace($i - 1); + + if ( + $currentToken->equals(',') && !$tokens[$prevIndex]->isComment() + && (true === $this->configuration['after_heredoc'] || !$tokens[$prevIndex]->isGivenKind(T_END_HEREDOC)) + ) { + $tokens->removeLeadingWhitespace($i); + } + } + } + + /** + * Method to move index over the non-array elements like function calls or function declarations. + */ + private function skipNonArrayElements(int $index, Tokens $tokens): int + { + if ($tokens[$index]->equals('}')) { + return $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); + } + + if ($tokens[$index]->equals(')')) { + $startIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + $startIndex = $tokens->getPrevMeaningfulToken($startIndex); + if (!$tokens[$startIndex]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN])) { + return $startIndex; + } + } + + if ($tokens[$index]->equals(',') && $this->commaIsPartOfImplementsList($index, $tokens)) { + --$index; + } + + return $index; + } + + private function commaIsPartOfImplementsList(int $index, Tokens $tokens): bool + { + do { + $index = $tokens->getPrevMeaningfulToken($index); + + $current = $tokens[$index]; + } while ($current->isGivenKind(T_STRING) || $current->equals(',')); + + return $current->isGivenKind(T_IMPLEMENTS); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NormalizeIndexBraceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NormalizeIndexBraceFixer.php new file mode 100644 index 00000000..f7e2b060 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NormalizeIndexBraceFixer.php @@ -0,0 +1,53 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ArrayNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class NormalizeIndexBraceFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Array index should always be written by using square braces.', + [new CodeSample("isTokenKindFound(CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if ($token->isGivenKind(CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN)) { + $tokens[$index] = new Token('['); + } elseif ($token->isGivenKind(CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE)) { + $tokens[$index] = new Token(']'); + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/ReturnToYieldFromFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/ReturnToYieldFromFixer.php new file mode 100644 index 00000000..4b130a27 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/ReturnToYieldFromFixer.php @@ -0,0 +1,105 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ArrayNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Kuba Werłos + */ +final class ReturnToYieldFromFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'If the function explicitly returns an array, and has the return type `iterable`, then `yield from` must be used instead of `return`.', + [new CodeSample('isAllTokenKindsFound([T_FUNCTION, T_RETURN]) && $tokens->isAnyTokenKindsFound([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN]); + } + + /** + * {@inheritdoc} + * + * Must run before YieldFromArrayToYieldsFixer. + * Must run after PhpUnitDataProviderReturnTypeFixer, PhpdocToReturnTypeFixer. + */ + public function getPriority(): int + { + return 1; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens->findGivenKind(T_RETURN) as $index => $token) { + if (!$this->shouldBeFixed($tokens, $index)) { + continue; + } + + $tokens[$index] = new Token([T_YIELD_FROM, 'yield from']); + } + } + + private function shouldBeFixed(Tokens $tokens, int $returnIndex): bool + { + $arrayStartIndex = $tokens->getNextMeaningfulToken($returnIndex); + if (!$tokens[$arrayStartIndex]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN])) { + return false; + } + + if ($tokens[$arrayStartIndex]->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) { + $arrayEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $arrayStartIndex); + } else { + $arrayOpenParenthesisIndex = $tokens->getNextTokenOfKind($arrayStartIndex, ['(']); + $arrayEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $arrayOpenParenthesisIndex); + } + + $functionEndIndex = $arrayEndIndex; + do { + $functionEndIndex = $tokens->getNextMeaningfulToken($functionEndIndex); + } while (null !== $functionEndIndex && $tokens[$functionEndIndex]->equals(';')); + if (null === $functionEndIndex || !$tokens[$functionEndIndex]->equals('}')) { + return false; + } + + $functionStartIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $functionEndIndex); + + $returnTypeIndex = $tokens->getPrevMeaningfulToken($functionStartIndex); + if (!$tokens[$returnTypeIndex]->isGivenKind(T_STRING)) { + return false; + } + + if ('iterable' !== strtolower($tokens[$returnTypeIndex]->getContent())) { + return false; + } + + $beforeReturnTypeIndex = $tokens->getPrevMeaningfulToken($returnTypeIndex); + + return $tokens[$beforeReturnTypeIndex]->isGivenKind(CT::T_TYPE_COLON); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/TrimArraySpacesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/TrimArraySpacesFixer.php new file mode 100644 index 00000000..775e9125 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/TrimArraySpacesFixer.php @@ -0,0 +1,97 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ArrayNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Jared Henderson + */ +final class TrimArraySpacesFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Arrays should be formatted like function/method arguments, without leading or trailing single line space.', + [new CodeSample("isAnyTokenKindsFound([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN, CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = 0, $c = $tokens->count(); $index < $c; ++$index) { + if ($tokens[$index]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN, CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN])) { + self::fixArray($tokens, $index); + } + } + } + + /** + * Method to trim leading/trailing whitespace within single line arrays. + */ + private static function fixArray(Tokens $tokens, int $index): void + { + $startIndex = $index; + + if ($tokens[$startIndex]->isGivenKind(T_ARRAY)) { + $startIndex = $tokens->getNextMeaningfulToken($startIndex); + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startIndex); + } elseif ($tokens[$startIndex]->isGivenKind(CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN)) { + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_DESTRUCTURING_SQUARE_BRACE, $startIndex); + } else { + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $startIndex); + } + + $nextIndex = $startIndex + 1; + $nextToken = $tokens[$nextIndex]; + $nextNonWhitespaceIndex = $tokens->getNextNonWhitespace($startIndex); + $nextNonWhitespaceToken = $tokens[$nextNonWhitespaceIndex]; + $tokenAfterNextNonWhitespaceToken = $tokens[$nextNonWhitespaceIndex + 1]; + + $prevIndex = $endIndex - 1; + $prevToken = $tokens[$prevIndex]; + $prevNonWhitespaceIndex = $tokens->getPrevNonWhitespace($endIndex); + $prevNonWhitespaceToken = $tokens[$prevNonWhitespaceIndex]; + + if ( + $nextToken->isWhitespace(" \t") + && ( + !$nextNonWhitespaceToken->isComment() + || $nextNonWhitespaceIndex === $prevNonWhitespaceIndex + || $tokenAfterNextNonWhitespaceToken->isWhitespace(" \t") + || str_starts_with($nextNonWhitespaceToken->getContent(), '/*') + ) + ) { + $tokens->clearAt($nextIndex); + } + + if ( + $prevToken->isWhitespace(" \t") + && !$prevNonWhitespaceToken->equals(',') + ) { + $tokens->clearAt($prevIndex); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/WhitespaceAfterCommaInArrayFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/WhitespaceAfterCommaInArrayFixer.php new file mode 100644 index 00000000..ed4ef90d --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/WhitespaceAfterCommaInArrayFixer.php @@ -0,0 +1,137 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ArrayNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Adam Marczuk + */ +final class WhitespaceAfterCommaInArrayFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'In array declaration, there MUST be a whitespace after each comma.', + [ + new CodeSample(" true]), + ] + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN]); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('ensure_single_space', 'If there are only horizontal whitespaces after the comma then ensure it is a single space.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $tokensToInsert = []; + + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + if (!$tokens[$index]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN])) { + continue; + } + + if ($tokens[$index]->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) { + $startIndex = $index; + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $startIndex); + } else { + $startIndex = $tokens->getNextTokenOfKind($index, ['(']); + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startIndex); + } + + for ($i = $endIndex - 1; $i > $startIndex; --$i) { + $i = $this->skipNonArrayElements($i, $tokens); + if (!$tokens[$i]->equals(',')) { + continue; + } + if (!$tokens[$i + 1]->isWhitespace()) { + $tokensToInsert[$i + 1] = new Token([T_WHITESPACE, ' ']); + } elseif ( + true === $this->configuration['ensure_single_space'] + && ' ' !== $tokens[$i + 1]->getContent() + && Preg::match('/^\h+$/', $tokens[$i + 1]->getContent()) + && (!$tokens[$i + 2]->isComment() || Preg::match('/^\h+$/', $tokens[$i + 3]->getContent())) + ) { + $tokens[$i + 1] = new Token([T_WHITESPACE, ' ']); + } + } + } + + if ([] !== $tokensToInsert) { + $tokens->insertSlices($tokensToInsert); + } + } + + /** + * Method to move index over the non-array elements like function calls or function declarations. + * + * @return int New index + */ + private function skipNonArrayElements(int $index, Tokens $tokens): int + { + if ($tokens[$index]->equals('}')) { + return $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); + } + + if ($tokens[$index]->equals(')')) { + $startIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + $startIndex = $tokens->getPrevMeaningfulToken($startIndex); + if (!$tokens[$startIndex]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN])) { + return $startIndex; + } + } + + if ($tokens[$index]->equals(',') && $this->commaIsPartOfImplementsList($index, $tokens)) { + --$index; + } + + return $index; + } + + private function commaIsPartOfImplementsList(int $index, Tokens $tokens): bool + { + do { + $index = $tokens->getPrevMeaningfulToken($index); + + $current = $tokens[$index]; + } while ($current->isGivenKind(T_STRING) || $current->equals(',')); + + return $current->isGivenKind(T_IMPLEMENTS); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/YieldFromArrayToYieldsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/YieldFromArrayToYieldsFixer.php new file mode 100644 index 00000000..7df614fc --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/YieldFromArrayToYieldsFixer.php @@ -0,0 +1,189 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ArrayNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Kuba Werłos + */ +final class YieldFromArrayToYieldsFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Yield from array must be unpacked to series of yields.', + [ + new CodeSample('isTokenKindFound(T_YIELD_FROM); + } + + /** + * {@inheritdoc} + * + * Must run before BlankLineBeforeStatementFixer, NoExtraBlankLinesFixer, NoMultipleStatementsPerLineFixer, NoWhitespaceInBlankLineFixer, StatementIndentationFixer. + * Must run after ReturnToYieldFromFixer. + */ + public function getPriority(): int + { + return 0; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + /** + * @var array $inserts + */ + $inserts = []; + + foreach ($this->getYieldsFromToUnpack($tokens) as $index => [$startIndex, $endIndex]) { + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + + if ($tokens[$startIndex]->equals('(')) { + $prevStartIndex = $tokens->getPrevMeaningfulToken($startIndex); + $tokens->clearTokenAndMergeSurroundingWhitespace($prevStartIndex); // clear `array` from `array(` + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($startIndex); + $tokens->clearTokenAndMergeSurroundingWhitespace($endIndex); + + $arrayHasTrailingComma = false; + + $startIndex = $tokens->getNextMeaningfulToken($startIndex); + + $inserts[$startIndex] = [new Token([T_YIELD, 'yield']), new Token([T_WHITESPACE, ' '])]; + + foreach ($this->findArrayItemCommaIndex( + $tokens, + $startIndex, + $tokens->getPrevMeaningfulToken($endIndex), + ) as $commaIndex) { + $nextItemIndex = $tokens->getNextMeaningfulToken($commaIndex); + + if ($nextItemIndex < $endIndex) { + $inserts[$nextItemIndex] = [new Token([T_YIELD, 'yield']), new Token([T_WHITESPACE, ' '])]; + $tokens[$commaIndex] = new Token(';'); + } else { + $arrayHasTrailingComma = true; + // array has trailing comma - we replace it with `;` (as it's best fit to put it) + $tokens[$commaIndex] = new Token(';'); + } + } + + // there was a trailing comma, so we do not need original `;` after initial array structure + if (true === $arrayHasTrailingComma) { + $tokens->clearTokenAndMergeSurroundingWhitespace($tokens->getNextMeaningfulToken($endIndex)); + } + } + + $tokens->insertSlices($inserts); + } + + /** + * @return iterable> + */ + private function getYieldsFromToUnpack(Tokens $tokens): iterable + { + $tokensCount = $tokens->count(); + $index = 0; + while (++$index < $tokensCount) { + if (!$tokens[$index]->isGivenKind(T_YIELD_FROM)) { + continue; + } + + $prevIndex = $tokens->getPrevMeaningfulToken($index); + if (!$tokens[$prevIndex]->equalsAny([';', '{', '}', [T_OPEN_TAG]])) { + continue; + } + + $arrayStartIndex = $tokens->getNextMeaningfulToken($index); + + if (!$tokens[$arrayStartIndex]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN])) { + continue; + } + + if ($tokens[$arrayStartIndex]->isGivenKind(T_ARRAY)) { + $startIndex = $tokens->getNextTokenOfKind($arrayStartIndex, ['(']); + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startIndex); + } else { + $startIndex = $arrayStartIndex; + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $startIndex); + } + + // is there empty "yield from []" ? + if ($endIndex === $tokens->getNextMeaningfulToken($startIndex)) { + continue; + } + + // is there any nested "yield from"? + if ([] !== $tokens->findGivenKind(T_YIELD_FROM, $startIndex, $endIndex)) { + continue; + } + + yield $index => [$startIndex, $endIndex]; + } + } + + /** + * @return iterable + */ + private function findArrayItemCommaIndex(Tokens $tokens, int $startIndex, int $endIndex): iterable + { + for ($index = $startIndex; $index <= $endIndex; ++$index) { + $token = $tokens[$index]; + + // skip nested (), [], {} constructs + $blockDefinitionProbe = Tokens::detectBlockType($token); + + if (null !== $blockDefinitionProbe && true === $blockDefinitionProbe['isStart']) { + $index = $tokens->findBlockEnd($blockDefinitionProbe['type'], $index); + + continue; + } + + if (!$tokens[$index]->equals(',')) { + continue; + } + + yield $index; + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/AttributeNotation/AttributeEmptyParenthesesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/AttributeNotation/AttributeEmptyParenthesesFixer.php new file mode 100644 index 00000000..cba06046 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/AttributeNotation/AttributeEmptyParenthesesFixer.php @@ -0,0 +1,123 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\AttributeNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author HypeMC + */ +final class AttributeEmptyParenthesesFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'PHP attributes declared without arguments must (not) be followed by empty parentheses.', + [ + new CodeSample(" true] + ), + ] + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return \defined('T_ATTRIBUTE') && $tokens->isTokenKindFound(T_ATTRIBUTE); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('use_parentheses', 'Whether attributes should be followed by parentheses or not.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $index = 0; + + while (null !== $index = $tokens->getNextTokenOfKind($index, [[T_ATTRIBUTE]])) { + $nextIndex = $index; + + do { + $parenthesesIndex = $tokens->getNextTokenOfKind($nextIndex, ['(', ',', [CT::T_ATTRIBUTE_CLOSE]]); + + if (true === $this->configuration['use_parentheses']) { + $this->ensureParenthesesAt($tokens, $parenthesesIndex); + } else { + $this->ensureNoParenthesesAt($tokens, $parenthesesIndex); + } + + $nextIndex = $tokens->getNextTokenOfKind($nextIndex, ['(', ',', [CT::T_ATTRIBUTE_CLOSE]]); + + // Find closing parentheses, we need to do this in case there's a comma inside the parentheses + if ($tokens[$nextIndex]->equals('(')) { + $nextIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $nextIndex); + $nextIndex = $tokens->getNextTokenOfKind($nextIndex, [',', [CT::T_ATTRIBUTE_CLOSE]]); + } + + // In case there's a comma right before T_ATTRIBUTE_CLOSE + if (!$tokens[$nextIndex]->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) { + $nextIndex = $tokens->getNextMeaningfulToken($nextIndex); + } + } while (!$tokens[$nextIndex]->isGivenKind(CT::T_ATTRIBUTE_CLOSE)); + } + } + + private function ensureParenthesesAt(Tokens $tokens, int $index): void + { + if ($tokens[$index]->equals('(')) { + return; + } + + $tokens->insertAt( + $tokens->getPrevMeaningfulToken($index) + 1, + [new Token('('), new Token(')')] + ); + } + + private function ensureNoParenthesesAt(Tokens $tokens, int $index): void + { + if (!$tokens[$index]->equals('(')) { + return; + } + + $closingIndex = $tokens->getNextMeaningfulToken($index); + + // attribute has arguments - parentheses can not be removed + if (!$tokens[$closingIndex]->equals(')')) { + return; + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($closingIndex); + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/BracesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/BracesFixer.php new file mode 100644 index 00000000..a5b3916b --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/BracesFixer.php @@ -0,0 +1,252 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Basic; + +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ControlStructure\ControlStructureBracesFixer; +use PhpCsFixer\Fixer\ControlStructure\ControlStructureContinuationPositionFixer; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; +use PhpCsFixer\Fixer\LanguageConstruct\DeclareParenthesesFixer; +use PhpCsFixer\Fixer\LanguageConstruct\SingleSpaceAroundConstructFixer; +use PhpCsFixer\Fixer\Whitespace\NoExtraBlankLinesFixer; +use PhpCsFixer\Fixer\Whitespace\StatementIndentationFixer; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; + +/** + * Fixer for rules defined in PSR2 ¶4.1, ¶4.4, ¶5. + * + * @author Dariusz Rumiński + * + * @deprecated + */ +final class BracesFixer extends AbstractProxyFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface, DeprecatedFixerInterface +{ + /** + * @internal + */ + public const LINE_NEXT = 'next'; + + /** + * @internal + */ + public const LINE_SAME = 'same'; + + /** + * @var null|CurlyBracesPositionFixer + */ + private $curlyBracesPositionFixer; + + /** + * @var null|ControlStructureContinuationPositionFixer + */ + private $controlStructureContinuationPositionFixer; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'The body of each structure MUST be enclosed by braces. Braces should be properly placed. Body of braces should be properly indented.', + [ + new CodeSample( + '= 0; }; +$negative = function ($item) { + return $item < 0; }; +', + ['allow_single_line_closure' => true] + ), + new CodeSample( + ' self::LINE_SAME] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before HeredocIndentationFixer. + * Must run after ClassAttributesSeparationFixer, ClassDefinitionFixer, EmptyLoopBodyFixer, NoAlternativeSyntaxFixer, NoEmptyStatementFixer, NoUselessElseFixer, SingleLineThrowFixer, SingleSpaceAfterConstructFixer, SingleSpaceAroundConstructFixer, SingleTraitInsertPerStatementFixer. + */ + public function getPriority(): int + { + return 35; + } + + public function getSuccessorsNames(): array + { + return array_keys($this->proxyFixers); + } + + public function configure(array $configuration = null): void + { + parent::configure($configuration); + + $this->getCurlyBracesPositionFixer()->configure([ + 'control_structures_opening_brace' => $this->translatePositionOption($this->configuration['position_after_control_structures']), + 'functions_opening_brace' => $this->translatePositionOption($this->configuration['position_after_functions_and_oop_constructs']), + 'anonymous_functions_opening_brace' => $this->translatePositionOption($this->configuration['position_after_anonymous_constructs']), + 'classes_opening_brace' => $this->translatePositionOption($this->configuration['position_after_functions_and_oop_constructs']), + 'anonymous_classes_opening_brace' => $this->translatePositionOption($this->configuration['position_after_anonymous_constructs']), + 'allow_single_line_empty_anonymous_classes' => $this->configuration['allow_single_line_anonymous_class_with_empty_body'], + 'allow_single_line_anonymous_functions' => $this->configuration['allow_single_line_closure'], + ]); + + $this->getControlStructureContinuationPositionFixer()->configure([ + 'position' => self::LINE_NEXT === $this->configuration['position_after_control_structures'] + ? ControlStructureContinuationPositionFixer::NEXT_LINE + : ControlStructureContinuationPositionFixer::SAME_LINE, + ]); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('allow_single_line_anonymous_class_with_empty_body', 'Whether single line anonymous class with empty body notation should be allowed.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + (new FixerOptionBuilder('allow_single_line_closure', 'Whether single line lambda notation should be allowed.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + (new FixerOptionBuilder('position_after_functions_and_oop_constructs', 'Whether the opening brace should be placed on "next" or "same" line after classy constructs (non-anonymous classes, interfaces, traits, methods and non-lambda functions).')) + ->setAllowedValues([self::LINE_NEXT, self::LINE_SAME]) + ->setDefault(self::LINE_NEXT) + ->getOption(), + (new FixerOptionBuilder('position_after_control_structures', 'Whether the opening brace should be placed on "next" or "same" line after control structures.')) + ->setAllowedValues([self::LINE_NEXT, self::LINE_SAME]) + ->setDefault(self::LINE_SAME) + ->getOption(), + (new FixerOptionBuilder('position_after_anonymous_constructs', 'Whether the opening brace should be placed on "next" or "same" line after anonymous constructs (anonymous classes and lambda functions).')) + ->setAllowedValues([self::LINE_NEXT, self::LINE_SAME]) + ->setDefault(self::LINE_SAME) + ->getOption(), + ]); + } + + protected function createProxyFixers(): array + { + $singleSpaceAroundConstructFixer = new SingleSpaceAroundConstructFixer(); + $singleSpaceAroundConstructFixer->configure([ + 'constructs_contain_a_single_space' => [], + 'constructs_followed_by_a_single_space' => ['elseif', 'for', 'foreach', 'if', 'match', 'while', 'use_lambda'], + 'constructs_preceded_by_a_single_space' => ['use_lambda'], + ]); + + $noExtraBlankLinesFixer = new NoExtraBlankLinesFixer(); + $noExtraBlankLinesFixer->configure([ + 'tokens' => ['curly_brace_block'], + ]); + + return [ + $singleSpaceAroundConstructFixer, + new ControlStructureBracesFixer(), + $noExtraBlankLinesFixer, + $this->getCurlyBracesPositionFixer(), + $this->getControlStructureContinuationPositionFixer(), + new DeclareParenthesesFixer(), + new NoMultipleStatementsPerLineFixer(), + new StatementIndentationFixer(true), + ]; + } + + private function getCurlyBracesPositionFixer(): CurlyBracesPositionFixer + { + if (null === $this->curlyBracesPositionFixer) { + $this->curlyBracesPositionFixer = new CurlyBracesPositionFixer(); + } + + return $this->curlyBracesPositionFixer; + } + + private function getControlStructureContinuationPositionFixer(): ControlStructureContinuationPositionFixer + { + if (null === $this->controlStructureContinuationPositionFixer) { + $this->controlStructureContinuationPositionFixer = new ControlStructureContinuationPositionFixer(); + } + + return $this->controlStructureContinuationPositionFixer; + } + + private function translatePositionOption(string $option): string + { + return self::LINE_NEXT === $option + ? CurlyBracesPositionFixer::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END + : CurlyBracesPositionFixer::SAME_LINE; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/BracesPositionFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/BracesPositionFixer.php new file mode 100644 index 00000000..710448a4 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/BracesPositionFixer.php @@ -0,0 +1,410 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Basic; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\Indentation; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +final class BracesPositionFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface +{ + use Indentation; + + /** + * @internal + */ + public const NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END = 'next_line_unless_newline_at_signature_end'; + + /** + * @internal + */ + public const SAME_LINE = 'same_line'; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Braces must be placed as configured.', + [ + new CodeSample( + ' self::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END] + ), + new CodeSample( + ' self::SAME_LINE] + ), + new CodeSample( + ' self::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END] + ), + new CodeSample( + ' self::SAME_LINE] + ), + new CodeSample( + ' self::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END] + ), + new CodeSample( + ' true] + ), + new CodeSample( + ' true] + ), + ] + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound('{'); + } + + /** + * {@inheritdoc} + * + * Must run before SingleLineEmptyBodyFixer, StatementIndentationFixer. + * Must run after ControlStructureBracesFixer, NoMultipleStatementsPerLineFixer. + */ + public function getPriority(): int + { + return -2; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $classyTokens = Token::getClassyTokenKinds(); + $controlStructureTokens = [T_DECLARE, T_DO, T_ELSE, T_ELSEIF, T_FINALLY, T_FOR, T_FOREACH, T_IF, T_WHILE, T_TRY, T_CATCH, T_SWITCH]; + // @TODO: drop condition when PHP 8.0+ is required + if (\defined('T_MATCH')) { + $controlStructureTokens[] = T_MATCH; + } + + $tokensAnalyzer = new TokensAnalyzer($tokens); + + $allowSingleLineUntil = null; + + foreach ($tokens as $index => $token) { + $allowSingleLine = false; + $allowSingleLineIfEmpty = false; + + if ($token->isGivenKind($classyTokens)) { + $openBraceIndex = $tokens->getNextTokenOfKind($index, ['{']); + + if ($tokensAnalyzer->isAnonymousClass($index)) { + $allowSingleLineIfEmpty = true === $this->configuration['allow_single_line_empty_anonymous_classes']; + $positionOption = 'anonymous_classes_opening_brace'; + } else { + $positionOption = 'classes_opening_brace'; + } + } elseif ($token->isGivenKind(T_FUNCTION)) { + $openBraceIndex = $tokens->getNextTokenOfKind($index, ['{', ';']); + + if ($tokens[$openBraceIndex]->equals(';')) { + continue; + } + + if ($tokensAnalyzer->isLambda($index)) { + $allowSingleLine = true === $this->configuration['allow_single_line_anonymous_functions']; + $positionOption = 'anonymous_functions_opening_brace'; + } else { + $positionOption = 'functions_opening_brace'; + } + } elseif ($token->isGivenKind($controlStructureTokens)) { + $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $index); + $openBraceIndex = $tokens->getNextMeaningfulToken($parenthesisEndIndex); + + if (!$tokens[$openBraceIndex]->equals('{')) { + continue; + } + + $positionOption = 'control_structures_opening_brace'; + } else { + continue; + } + + $closeBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $openBraceIndex); + + $addNewlinesInsideBraces = true; + if ($allowSingleLine || $allowSingleLineIfEmpty || $index < $allowSingleLineUntil) { + $addNewlinesInsideBraces = false; + + for ($indexInsideBraces = $openBraceIndex + 1; $indexInsideBraces < $closeBraceIndex; ++$indexInsideBraces) { + $tokenInsideBraces = $tokens[$indexInsideBraces]; + + if ( + ($allowSingleLineIfEmpty && !$tokenInsideBraces->isWhitespace() && !$tokenInsideBraces->isComment()) + || ($tokenInsideBraces->isWhitespace() && Preg::match('/\R/', $tokenInsideBraces->getContent())) + ) { + $addNewlinesInsideBraces = true; + + break; + } + } + + if (!$addNewlinesInsideBraces && null === $allowSingleLineUntil) { + $allowSingleLineUntil = $closeBraceIndex; + } + } + + if ( + $addNewlinesInsideBraces + && !$this->isFollowedByNewLine($tokens, $openBraceIndex) + && !$this->hasCommentOnSameLine($tokens, $openBraceIndex) + && !$tokens[$tokens->getNextMeaningfulToken($openBraceIndex)]->isGivenKind(T_CLOSE_TAG) + ) { + $whitespace = $this->whitespacesConfig->getLineEnding().$this->getLineIndentation($tokens, $openBraceIndex); + if ($tokens->ensureWhitespaceAtIndex($openBraceIndex + 1, 0, $whitespace)) { + ++$closeBraceIndex; + } + } + + $whitespace = ' '; + if (self::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END === $this->configuration[$positionOption]) { + $whitespace = $this->whitespacesConfig->getLineEnding().$this->getLineIndentation($tokens, $index); + + $previousTokenIndex = $openBraceIndex; + do { + $previousTokenIndex = $tokens->getPrevMeaningfulToken($previousTokenIndex); + } while ($tokens[$previousTokenIndex]->isGivenKind([CT::T_TYPE_COLON, CT::T_NULLABLE_TYPE, T_STRING, T_NS_SEPARATOR, CT::T_ARRAY_TYPEHINT, T_STATIC, CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION, T_CALLABLE, CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_OPEN, CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE])); + + if ($tokens[$previousTokenIndex]->equals(')')) { + if ($tokens[--$previousTokenIndex]->isComment()) { + --$previousTokenIndex; + } + if ( + $tokens[$previousTokenIndex]->isWhitespace() + && Preg::match('/\R/', $tokens[$previousTokenIndex]->getContent()) + ) { + $whitespace = ' '; + } + } + } + + $moveBraceToIndex = null; + + if (' ' === $whitespace) { + $previousMeaningfulIndex = $tokens->getPrevMeaningfulToken($openBraceIndex); + for ($indexBeforeOpenBrace = $openBraceIndex - 1; $indexBeforeOpenBrace > $previousMeaningfulIndex; --$indexBeforeOpenBrace) { + if (!$tokens[$indexBeforeOpenBrace]->isComment()) { + continue; + } + + $tokenBeforeOpenBrace = $tokens[--$indexBeforeOpenBrace]; + if ($tokenBeforeOpenBrace->isWhitespace()) { + $moveBraceToIndex = $indexBeforeOpenBrace; + } elseif ($indexBeforeOpenBrace === $previousMeaningfulIndex) { + $moveBraceToIndex = $previousMeaningfulIndex + 1; + } + } + } elseif (!$tokens[$openBraceIndex - 1]->isWhitespace() || !Preg::match('/\R/', $tokens[$openBraceIndex - 1]->getContent())) { + for ($indexAfterOpenBrace = $openBraceIndex + 1; $indexAfterOpenBrace < $closeBraceIndex; ++$indexAfterOpenBrace) { + if ($tokens[$indexAfterOpenBrace]->isWhitespace() && Preg::match('/\R/', $tokens[$indexAfterOpenBrace]->getContent())) { + break; + } + + if ($tokens[$indexAfterOpenBrace]->isComment() && !str_starts_with($tokens[$indexAfterOpenBrace]->getContent(), '/*')) { + $moveBraceToIndex = $indexAfterOpenBrace + 1; + } + } + } + + if (null !== $moveBraceToIndex) { + /** @var Token $movedToken */ + $movedToken = clone $tokens[$openBraceIndex]; + + $delta = $openBraceIndex < $moveBraceToIndex ? 1 : -1; + + if ($tokens[$openBraceIndex + $delta]->isWhitespace()) { + if (-1 === $delta && Preg::match('/\R/', $tokens[$openBraceIndex - 1]->getContent())) { + $content = Preg::replace('/^(\h*?\R)?\h*/', '', $tokens[$openBraceIndex + 1]->getContent()); + if ('' !== $content) { + $tokens[$openBraceIndex + 1] = new Token([T_WHITESPACE, $content]); + } else { + $tokens->clearAt($openBraceIndex + 1); + } + } elseif ($tokens[$openBraceIndex - 1]->isWhitespace()) { + $tokens->clearAt($openBraceIndex - 1); + } + } + + for (; $openBraceIndex !== $moveBraceToIndex; $openBraceIndex += $delta) { + /** @var Token $siblingToken */ + $siblingToken = $tokens[$openBraceIndex + $delta]; + $tokens[$openBraceIndex] = $siblingToken; + } + + $tokens[$openBraceIndex] = $movedToken; + + $openBraceIndex = $moveBraceToIndex; + } + + if ($tokens->ensureWhitespaceAtIndex($openBraceIndex - 1, 1, $whitespace)) { + ++$closeBraceIndex; + if (null !== $allowSingleLineUntil) { + ++$allowSingleLineUntil; + } + } + + if ( + !$addNewlinesInsideBraces + || $tokens[$tokens->getPrevMeaningfulToken($closeBraceIndex)]->isGivenKind(T_OPEN_TAG) + ) { + continue; + } + + for ($prevIndex = $closeBraceIndex - 1; $tokens->isEmptyAt($prevIndex); --$prevIndex); + + $prevToken = $tokens[$prevIndex]; + if ($prevToken->isWhitespace() && Preg::match('/\R/', $prevToken->getContent())) { + continue; + } + + $whitespace = $this->whitespacesConfig->getLineEnding().$this->getLineIndentation($tokens, $openBraceIndex); + $tokens->ensureWhitespaceAtIndex($prevIndex, 1, $whitespace); + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('control_structures_opening_brace', 'The position of the opening brace of control structures‘ body.')) + ->setAllowedValues([self::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END, self::SAME_LINE]) + ->setDefault(self::SAME_LINE) + ->getOption(), + (new FixerOptionBuilder('functions_opening_brace', 'The position of the opening brace of functions‘ body.')) + ->setAllowedValues([self::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END, self::SAME_LINE]) + ->setDefault(self::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END) + ->getOption(), + (new FixerOptionBuilder('anonymous_functions_opening_brace', 'The position of the opening brace of anonymous functions‘ body.')) + ->setAllowedValues([self::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END, self::SAME_LINE]) + ->setDefault(self::SAME_LINE) + ->getOption(), + (new FixerOptionBuilder('classes_opening_brace', 'The position of the opening brace of classes‘ body.')) + ->setAllowedValues([self::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END, self::SAME_LINE]) + ->setDefault(self::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END) + ->getOption(), + (new FixerOptionBuilder('anonymous_classes_opening_brace', 'The position of the opening brace of anonymous classes‘ body.')) + ->setAllowedValues([self::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END, self::SAME_LINE]) + ->setDefault(self::SAME_LINE) + ->getOption(), + (new FixerOptionBuilder('allow_single_line_empty_anonymous_classes', 'Allow anonymous classes to have opening and closing braces on the same line.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), + (new FixerOptionBuilder('allow_single_line_anonymous_functions', 'Allow anonymous functions to have opening and closing braces on the same line.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), + ]); + } + + private function findParenthesisEnd(Tokens $tokens, int $structureTokenIndex): int + { + $nextIndex = $tokens->getNextMeaningfulToken($structureTokenIndex); + $nextToken = $tokens[$nextIndex]; + + // return if next token is not opening parenthesis + if (!$nextToken->equals('(')) { + return $structureTokenIndex; + } + + return $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $nextIndex); + } + + private function isFollowedByNewLine(Tokens $tokens, int $index): bool + { + for (++$index, $max = \count($tokens) - 1; $index < $max; ++$index) { + $token = $tokens[$index]; + if (!$token->isComment()) { + return $token->isWhitespace() && Preg::match('/\R/', $token->getContent()); + } + } + + return false; + } + + private function hasCommentOnSameLine(Tokens $tokens, int $index): bool + { + $token = $tokens[$index + 1]; + + if ($token->isWhitespace() && !Preg::match('/\R/', $token->getContent())) { + $token = $tokens[$index + 2]; + } + + return $token->isComment(); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/CurlyBracesPositionFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/CurlyBracesPositionFixer.php new file mode 100644 index 00000000..324d0753 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/CurlyBracesPositionFixer.php @@ -0,0 +1,100 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Basic; + +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; +use PhpCsFixer\Fixer\Indentation; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; + +/** + * @deprecated + */ +final class CurlyBracesPositionFixer extends AbstractProxyFixer implements ConfigurableFixerInterface, DeprecatedFixerInterface, WhitespacesAwareFixerInterface +{ + use Indentation; + + /** + * @internal + */ + public const NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END = 'next_line_unless_newline_at_signature_end'; + + /** + * @internal + */ + public const SAME_LINE = 'same_line'; + + private BracesPositionFixer $bracesPositionFixer; + + public function __construct() + { + $this->bracesPositionFixer = new BracesPositionFixer(); + + parent::__construct(); + } + + public function getDefinition(): FixerDefinitionInterface + { + $fixerDefinition = $this->bracesPositionFixer->getDefinition(); + + return new FixerDefinition( + 'Curly braces must be placed as configured.', + $fixerDefinition->getCodeSamples(), + $fixerDefinition->getDescription(), + $fixerDefinition->getRiskyDescription() + ); + } + + public function configure(array $configuration): void + { + $this->bracesPositionFixer->configure($configuration); + + parent::configure($configuration); + } + + /** + * {@inheritdoc} + * + * Must run before SingleLineEmptyBodyFixer, StatementIndentationFixer. + * Must run after ControlStructureBracesFixer, NoMultipleStatementsPerLineFixer. + */ + public function getPriority(): int + { + return $this->bracesPositionFixer->getPriority(); + } + + public function getSuccessorsNames(): array + { + return [ + $this->bracesPositionFixer->getName(), + ]; + } + + protected function createProxyFixers(): array + { + return [ + $this->bracesPositionFixer, + ]; + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return $this->bracesPositionFixer->createConfigurationDefinition(); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/EncodingFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/EncodingFixer.php new file mode 100644 index 00000000..2dc98e47 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/EncodingFixer.php @@ -0,0 +1,80 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Basic; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Fixer for rules defined in PSR1 ¶2.2. + * + * @author Dariusz Rumiński + */ +final class EncodingFixer extends AbstractFixer +{ + private string $bom; + + public function __construct() + { + parent::__construct(); + + $this->bom = pack('CCC', 0xEF, 0xBB, 0xBF); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'PHP code MUST use only UTF-8 without BOM (remove BOM).', + [ + new CodeSample( + $this->bom.'getContent(); + + if (str_starts_with($content, $this->bom)) { + $newContent = substr($content, 3); + + if ('' === $newContent) { + $tokens->clearAt(0); + } else { + $tokens[0] = new Token([$tokens[0]->getId(), $newContent]); + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/NoMultipleStatementsPerLineFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/NoMultipleStatementsPerLineFixer.php new file mode 100644 index 00000000..fcd9d292 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/NoMultipleStatementsPerLineFixer.php @@ -0,0 +1,101 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Basic; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\Indentation; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Fixer for rules defined in PSR2 ¶2.3 Lines: There must not be more than one statement per line. + */ +final class NoMultipleStatementsPerLineFixer extends AbstractFixer implements WhitespacesAwareFixerInterface +{ + use Indentation; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'There must not be more than one statement per line.', + [new CodeSample("isTokenKindFound(';'); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = 1, $max = \count($tokens) - 1; $index < $max; ++$index) { + if ($tokens[$index]->isGivenKind(T_FOR)) { + $index = $tokens->findBlockEnd( + Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, + $tokens->getNextTokenOfKind($index, ['(']) + ); + + continue; + } + + if (!$tokens[$index]->equals(';')) { + continue; + } + + for ($nextIndex = $index + 1; $nextIndex < $max; ++$nextIndex) { + $token = $tokens[$nextIndex]; + + if ($token->isWhitespace() || $token->isComment()) { + if (Preg::match('/\R/', $token->getContent())) { + break; + } + + continue; + } + + if (!$token->equalsAny(['}', [T_CLOSE_TAG], [T_ENDIF], [T_ENDFOR], [T_ENDSWITCH], [T_ENDWHILE], [T_ENDFOREACH]])) { + $whitespaceIndex = $index; + do { + $token = $tokens[++$whitespaceIndex]; + } while ($token->isComment()); + + $newline = $this->whitespacesConfig->getLineEnding().$this->getLineIndentation($tokens, $index); + + if ($tokens->ensureWhitespaceAtIndex($whitespaceIndex, 0, $newline)) { + ++$max; + } + } + + break; + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/NoTrailingCommaInSinglelineFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/NoTrailingCommaInSinglelineFixer.php new file mode 100644 index 00000000..610299b3 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/NoTrailingCommaInSinglelineFixer.php @@ -0,0 +1,149 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Basic; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\AttributeAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Tokens; + +final class NoTrailingCommaInSinglelineFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'If a list of values separated by a comma is contained on a single line, then the last item MUST NOT have a trailing comma.', + [ + new CodeSample(" ['array_destructuring']]), + ] + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return + $tokens->isTokenKindFound(',') + && $tokens->isAnyTokenKindsFound([')', CT::T_ARRAY_SQUARE_BRACE_CLOSE, CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, CT::T_GROUP_IMPORT_BRACE_CLOSE]); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + $elements = ['arguments', 'array_destructuring', 'array', 'group_import']; + + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('elements', 'Which elements to fix.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([new AllowedValueSubset($elements)]) + ->setDefault($elements) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + if (!$tokens[$index]->equals(')') && !$tokens[$index]->isGivenKind([CT::T_ARRAY_SQUARE_BRACE_CLOSE, CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, CT::T_GROUP_IMPORT_BRACE_CLOSE])) { + continue; + } + + $commaIndex = $tokens->getPrevMeaningfulToken($index); + + if (!$tokens[$commaIndex]->equals(',')) { + continue; + } + + $block = Tokens::detectBlockType($tokens[$index]); + $blockOpenIndex = $tokens->findBlockStart($block['type'], $index); + + if ($tokens->isPartialCodeMultiline($blockOpenIndex, $index)) { + continue; + } + + if (!$this->shouldBeCleared($tokens, $blockOpenIndex)) { + continue; + } + + do { + $tokens->clearTokenAndMergeSurroundingWhitespace($commaIndex); + $commaIndex = $tokens->getPrevMeaningfulToken($commaIndex); + } while ($tokens[$commaIndex]->equals(',')); + + $tokens->removeTrailingWhitespace($commaIndex); + } + } + + private function shouldBeCleared(Tokens $tokens, int $openIndex): bool + { + /** @var string[] $elements */ + $elements = $this->configuration['elements']; + + if ($tokens[$openIndex]->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) { + return \in_array('array', $elements, true); + } + + if ($tokens[$openIndex]->isGivenKind(CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN)) { + return \in_array('array_destructuring', $elements, true); + } + + if ($tokens[$openIndex]->isGivenKind(CT::T_GROUP_IMPORT_BRACE_OPEN)) { + return \in_array('group_import', $elements, true); + } + + if (!$tokens[$openIndex]->equals('(')) { + return false; + } + + $beforeOpen = $tokens->getPrevMeaningfulToken($openIndex); + + if ($tokens[$beforeOpen]->isGivenKind(T_ARRAY)) { + return \in_array('array', $elements, true); + } + + if ($tokens[$beforeOpen]->isGivenKind(T_LIST)) { + return \in_array('array_destructuring', $elements, true); + } + + if ($tokens[$beforeOpen]->isGivenKind([T_UNSET, T_ISSET, T_VARIABLE, T_CLASS])) { + return \in_array('arguments', $elements, true); + } + + if ($tokens[$beforeOpen]->isGivenKind(T_STRING)) { + return !AttributeAnalyzer::isAttribute($tokens, $beforeOpen) && \in_array('arguments', $elements, true); + } + + if ($tokens[$beforeOpen]->equalsAny([')', ']', [CT::T_DYNAMIC_VAR_BRACE_CLOSE], [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE]])) { + $block = Tokens::detectBlockType($tokens[$beforeOpen]); + + return + ( + Tokens::BLOCK_TYPE_ARRAY_INDEX_CURLY_BRACE === $block['type'] + || Tokens::BLOCK_TYPE_DYNAMIC_VAR_BRACE === $block['type'] + || Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE === $block['type'] + || Tokens::BLOCK_TYPE_PARENTHESIS_BRACE === $block['type'] + ) && \in_array('arguments', $elements, true); + } + + return false; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/NonPrintableCharacterFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/NonPrintableCharacterFixer.php new file mode 100644 index 00000000..d150cb5f --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/NonPrintableCharacterFixer.php @@ -0,0 +1,178 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Basic; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Removes Zero-width space (ZWSP), Non-breaking space (NBSP) and other invisible unicode symbols. + * + * @author Ivan Boprzenkov + */ +final class NonPrintableCharacterFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** + * @var array + */ + private array $symbolsReplace; + + /** + * @var int[] + */ + private static array $tokens = [ + T_STRING_VARNAME, + T_INLINE_HTML, + T_VARIABLE, + T_COMMENT, + T_ENCAPSED_AND_WHITESPACE, + T_CONSTANT_ENCAPSED_STRING, + T_DOC_COMMENT, + ]; + + public function __construct() + { + parent::__construct(); + + $this->symbolsReplace = [ + pack('H*', 'e2808b') => ['', '200b'], // ZWSP U+200B + pack('H*', 'e28087') => [' ', '2007'], // FIGURE SPACE U+2007 + pack('H*', 'e280af') => [' ', '202f'], // NBSP U+202F + pack('H*', 'e281a0') => ['', '2060'], // WORD JOINER U+2060 + pack('H*', 'c2a0') => [' ', 'a0'], // NO-BREAK SPACE U+A0 + ]; + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Remove Zero-width space (ZWSP), Non-breaking space (NBSP) and other invisible unicode symbols.', + [ + new CodeSample( + ' false] + ), + ], + null, + 'Risky when strings contain intended invisible characters.' + ); + } + + public function isRisky(): bool + { + return true; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound(self::$tokens); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('use_escape_sequences_in_strings', 'Whether characters should be replaced with escape sequences in strings.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $replacements = []; + $escapeSequences = []; + + foreach ($this->symbolsReplace as $character => [$replacement, $codepoint]) { + $replacements[$character] = $replacement; + $escapeSequences[$character] = '\u{'.$codepoint.'}'; + } + + foreach ($tokens as $index => $token) { + $content = $token->getContent(); + + if ( + true === $this->configuration['use_escape_sequences_in_strings'] + && $token->isGivenKind([T_CONSTANT_ENCAPSED_STRING, T_ENCAPSED_AND_WHITESPACE]) + ) { + if (!Preg::match('/'.implode('|', array_keys($escapeSequences)).'/', $content)) { + continue; + } + + $previousToken = $tokens[$index - 1]; + $stringTypeChanged = false; + $swapQuotes = false; + + if ($previousToken->isGivenKind(T_START_HEREDOC)) { + $previousTokenContent = $previousToken->getContent(); + + if (str_contains($previousTokenContent, '\'')) { + $tokens[$index - 1] = new Token([T_START_HEREDOC, str_replace('\'', '', $previousTokenContent)]); + $stringTypeChanged = true; + } + } elseif (str_starts_with($content, "'")) { + $stringTypeChanged = true; + $swapQuotes = true; + } + + if ($swapQuotes) { + $content = str_replace("\\'", "'", $content); + } + + if ($stringTypeChanged) { + $content = Preg::replace('/(\\\\{1,2})/', '\\\\\\\\', $content); + $content = str_replace('$', '\$', $content); + } + + if ($swapQuotes) { + $content = str_replace('"', '\"', $content); + $content = Preg::replace('/^\'(.*)\'$/s', '"$1"', $content); + } + + $tokens[$index] = new Token([$token->getId(), strtr($content, $escapeSequences)]); + + continue; + } + + if ($token->isGivenKind(self::$tokens)) { + $newContent = strtr($content, $replacements); + + // variable name cannot contain space + if ($token->isGivenKind([T_STRING_VARNAME, T_VARIABLE]) && str_contains($newContent, ' ')) { + continue; + } + + // multiline comment must have "*/" only at the end + if ($token->isGivenKind([T_COMMENT, T_DOC_COMMENT]) && str_starts_with($newContent, '/*') && strpos($newContent, '*/') !== \strlen($newContent) - 2) { + continue; + } + + $tokens[$index] = new Token([$token->getId(), $newContent]); + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/NumericLiteralSeparatorFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/NumericLiteralSeparatorFixer.php new file mode 100644 index 00000000..79d52e62 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/NumericLiteralSeparatorFixer.php @@ -0,0 +1,217 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Basic; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Let's you add underscores to numeric literals. + * + * Inspired by: + * - {@link https://github.com/kubawerlos/php-cs-fixer-custom-fixers/blob/main/src/Fixer/NumericLiteralSeparatorFixer.php} + * - {@link https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/rules/numeric-separators-style.js} + * + * @author Marvin Heilemann + * @author Greg Korba + */ +final class NumericLiteralSeparatorFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + public const STRATEGY_USE_SEPARATOR = 'use_separator'; + public const STRATEGY_NO_SEPARATOR = 'no_separator'; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Adds separators to numeric literals of any kind.', + [ + new CodeSample( + <<<'PHP' + self::STRATEGY_NO_SEPARATOR], + ), + new CodeSample( + <<<'PHP' + self::STRATEGY_USE_SEPARATOR], + ), + new CodeSample( + " true] + ), + ] + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound([T_DNUMBER, T_LNUMBER]); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder( + 'override_existing', + 'Whether literals already containing underscores should be reformatted.' + )) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + (new FixerOptionBuilder( + 'strategy', + 'Whether numeric literal should be separated by underscores or not.' + )) + ->setAllowedValues([self::STRATEGY_USE_SEPARATOR, self::STRATEGY_NO_SEPARATOR]) + ->setDefault(self::STRATEGY_USE_SEPARATOR) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind([T_DNUMBER, T_LNUMBER])) { + continue; + } + + $content = $token->getContent(); + + $newContent = $this->formatValue($content); + + if ($content === $newContent) { + // Skip Token override if its the same content, like when it + // already got a valid literal separator structure. + continue; + } + + $tokens[$index] = new Token([$token->getId(), $newContent]); + } + } + + private function formatValue(string $value): string + { + if (self::STRATEGY_NO_SEPARATOR === $this->configuration['strategy']) { + return str_contains($value, '_') ? str_replace('_', '', $value) : $value; + } + + if (true === $this->configuration['override_existing']) { + $value = str_replace('_', '', $value); + } elseif (str_contains($value, '_')) { + // Keep already underscored literals untouched. + return $value; + } + + $lowerValue = strtolower($value); + + if (str_starts_with($lowerValue, '0b')) { + // Binary + return $this->insertEveryRight($value, 8, 2); + } + + if (str_starts_with($lowerValue, '0x')) { + // Hexadecimal + return $this->insertEveryRight($value, 2, 2); + } + + if (str_starts_with($lowerValue, '0o')) { + // Octal + return $this->insertEveryRight($value, 3, 2); + } + if (str_starts_with($lowerValue, '0') && !str_contains($lowerValue, '.')) { + // Octal notation prior PHP 8.1 but still valid + return $this->insertEveryRight($value, 3, 1); + } + + // All other types + + /** If its a negative value we need an offset */ + $negativeOffset = static fn ($v) => str_contains($v, '-') ? 1 : 0; + + Preg::matchAll('/([0-9-_]+)?((\.)([0-9_]*))?((e)([0-9-_]+))?/i', $value, $result); + + $integer = $result[1][0]; + $joinedValue = $this->insertEveryRight($integer, 3, $negativeOffset($integer)); + + $dot = $result[3][0]; + if ('' !== $dot) { + $integer = $result[4][0]; + $decimal = $this->insertEveryLeft($integer, 3, $negativeOffset($integer)); + $joinedValue = $joinedValue.$dot.$decimal; + } + + $tim = $result[6][0]; + if ('' !== $tim) { + $integer = $result[7][0]; + $times = $this->insertEveryRight($integer, 3, $negativeOffset($integer)); + $joinedValue = $joinedValue.$tim.$times; + } + + return $joinedValue; + } + + private function insertEveryRight(string $value, int $length, int $offset = 0): string + { + $position = $length * -1; + while ($position > -(\strlen($value) - $offset)) { + $value = substr_replace($value, '_', $position, 0); + $position -= $length + 1; + } + + return $value; + } + + private function insertEveryLeft(string $value, int $length, int $offset = 0): string + { + $position = $length; + while ($position < \strlen($value)) { + $value = substr_replace($value, '_', $position, $offset); + $position += $length + 1; + } + + return $value; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/OctalNotationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/OctalNotationFixer.php new file mode 100644 index 00000000..36f19e88 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/OctalNotationFixer.php @@ -0,0 +1,64 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Basic; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class OctalNotationFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Literal octal must be in `0o` notation.', + [ + new VersionSpecificCodeSample( + "= 8_01_00 && $tokens->isTokenKindFound(T_LNUMBER); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_LNUMBER)) { + continue; + } + + $content = $token->getContent(); + + $newContent = Preg::replace('#^0_*+([0-7_]+)$#', '0o$1', $content); + + if ($content === $newContent) { + continue; + } + + $tokens[$index] = new Token([T_LNUMBER, $newContent]); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/PsrAutoloadingFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/PsrAutoloadingFixer.php new file mode 100644 index 00000000..2f31f540 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/PsrAutoloadingFixer.php @@ -0,0 +1,280 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Basic; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\DocBlock\TypeExpression; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\FileSpecificCodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\StdinFileInfo; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Jordi Boggiano + * @author Dariusz Rumiński + * @author Bram Gotink + * @author Graham Campbell + * @author Kuba Werłos + */ +final class PsrAutoloadingFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Classes must be in a path that matches their namespace, be at least one namespace deep and the class name should match the file name.', + [ + new FileSpecificCodeSample( + ' './src'] + ), + ], + null, + 'This fixer may change your class name, which will break the code that depends on the old name.' + ); + } + + public function configure(array $configuration): void + { + parent::configure($configuration); + + if (null !== $this->configuration['dir']) { + $realpath = realpath($this->configuration['dir']); + + if (false === $realpath) { + throw new \InvalidArgumentException(sprintf('Failed to resolve configured directory "%s".', $this->configuration['dir'])); + } + + $this->configuration['dir'] = $realpath; + } + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds()); + } + + public function isRisky(): bool + { + return true; + } + + /** + * {@inheritdoc} + * + * Must run before SelfAccessorFixer. + */ + public function getPriority(): int + { + return -10; + } + + public function supports(\SplFileInfo $file): bool + { + if ($file instanceof StdinFileInfo) { + return false; + } + + if ( + // ignore file with extension other than php + ('php' !== $file->getExtension()) + // ignore file with name that cannot be a class name + || !Preg::match('/^'.TypeExpression::REGEX_IDENTIFIER.'$/', $file->getBasename('.php')) + ) { + return false; + } + + try { + $tokens = Tokens::fromCode(sprintf('getBasename('.php'))); + + if ($tokens[3]->isKeyword() || $tokens[3]->isMagicConstant()) { + // name cannot be a class name - detected by PHP 5.x + return false; + } + } catch (\ParseError $e) { + // name cannot be a class name - detected by PHP 7.x + return false; + } + + // ignore stubs/fixtures, since they typically contain invalid files for various reasons + return !Preg::match('{[/\\\\](stub|fixture)s?[/\\\\]}i', $file->getRealPath()); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('dir', 'If provided, the directory where the project code is placed.')) + ->setAllowedTypes(['null', 'string']) + ->setDefault(null) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $tokenAnalyzer = new TokensAnalyzer($tokens); + + if (null !== $this->configuration['dir'] && !str_starts_with($file->getRealPath(), $this->configuration['dir'])) { + return; + } + + $namespace = null; + $namespaceStartIndex = null; + $namespaceEndIndex = null; + + $classyName = null; + $classyIndex = null; + + foreach ($tokens as $index => $token) { + if ($token->isGivenKind(T_NAMESPACE)) { + if (null !== $namespace) { + return; + } + + $namespaceStartIndex = $tokens->getNextMeaningfulToken($index); + $namespaceEndIndex = $tokens->getNextTokenOfKind($namespaceStartIndex, [';']); + $namespace = trim($tokens->generatePartialCode($namespaceStartIndex, $namespaceEndIndex - 1)); + } elseif ($token->isClassy()) { + if ($tokenAnalyzer->isAnonymousClass($index)) { + continue; + } + + if (null !== $classyName) { + return; + } + + $classyIndex = $tokens->getNextMeaningfulToken($index); + $classyName = $tokens[$classyIndex]->getContent(); + } + } + + if (null === $classyName) { + return; + } + + $expectedClassyName = $this->calculateClassyName($file, $namespace, $classyName); + + if ($classyName !== $expectedClassyName) { + $tokens[$classyIndex] = new Token([T_STRING, $expectedClassyName]); + } + + if (null === $this->configuration['dir'] || null === $namespace) { + return; + } + + if (!is_dir($this->configuration['dir'])) { + return; + } + + $configuredDir = realpath($this->configuration['dir']); + $fileDir = \dirname($file->getRealPath()); + + if (\strlen($configuredDir) >= \strlen($fileDir)) { + return; + } + + $newNamespace = substr(str_replace('/', '\\', $fileDir), \strlen($configuredDir) + 1); + $originalNamespace = substr($namespace, -\strlen($newNamespace)); + + if ($originalNamespace !== $newNamespace && strtolower($originalNamespace) === strtolower($newNamespace)) { + $tokens->clearRange($namespaceStartIndex, $namespaceEndIndex); + $namespace = substr($namespace, 0, -\strlen($newNamespace)).$newNamespace; + + $newNamespace = Tokens::fromCode('clearRange(0, 2); + $newNamespace->clearEmptyTokens(); + + $tokens->insertAt($namespaceStartIndex, $newNamespace); + } + } + + private function calculateClassyName(\SplFileInfo $file, ?string $namespace, string $currentName): string + { + $name = $file->getBasename('.php'); + $maxNamespace = $this->calculateMaxNamespace($file, $namespace); + + if (null !== $this->configuration['dir']) { + return ('' !== $maxNamespace ? (str_replace('\\', '_', $maxNamespace).'_') : '').$name; + } + + $namespaceParts = array_reverse(explode('\\', $maxNamespace)); + + foreach ($namespaceParts as $namespacePart) { + $nameCandidate = sprintf('%s_%s', $namespacePart, $name); + + if (strtolower($nameCandidate) !== strtolower(substr($currentName, -\strlen($nameCandidate)))) { + break; + } + + $name = $nameCandidate; + } + + return $name; + } + + private function calculateMaxNamespace(\SplFileInfo $file, ?string $namespace): string + { + if (null === $this->configuration['dir']) { + $root = \dirname($file->getRealPath()); + + while ($root !== \dirname($root)) { + $root = \dirname($root); + } + } else { + $root = realpath($this->configuration['dir']); + } + + $namespaceAccordingToFileLocation = trim(str_replace(\DIRECTORY_SEPARATOR, '\\', substr(\dirname($file->getRealPath()), \strlen($root))), '\\'); + + if (null === $namespace) { + return $namespaceAccordingToFileLocation; + } + + $namespaceAccordingToFileLocationPartsReversed = array_reverse(explode('\\', $namespaceAccordingToFileLocation)); + $namespacePartsReversed = array_reverse(explode('\\', $namespace)); + + foreach ($namespacePartsReversed as $key => $namespaceParte) { + if (!isset($namespaceAccordingToFileLocationPartsReversed[$key])) { + break; + } + + if (strtolower($namespaceParte) !== strtolower($namespaceAccordingToFileLocationPartsReversed[$key])) { + break; + } + + unset($namespaceAccordingToFileLocationPartsReversed[$key]); + } + + return implode('\\', array_reverse($namespaceAccordingToFileLocationPartsReversed)); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/SingleLineEmptyBodyFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/SingleLineEmptyBodyFixer.php new file mode 100644 index 00000000..c3a81769 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/SingleLineEmptyBodyFixer.php @@ -0,0 +1,83 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Basic; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class SingleLineEmptyBodyFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Empty body of class, interface, trait, enum or function must be abbreviated as `{}` and placed on the same line as the previous symbol, separated by a single space.', + [new CodeSample('isTokenKindFound(T_ENUM)) { // @TODO: drop condition when PHP 8.1+ is required + return true; + } + + return $tokens->isAnyTokenKindsFound([T_INTERFACE, T_CLASS, T_FUNCTION, T_TRAIT]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index > 0; --$index) { + if (!$tokens[$index]->isGivenKind([...Token::getClassyTokenKinds(), T_FUNCTION])) { + continue; + } + + $openBraceIndex = $tokens->getNextTokenOfKind($index, ['{', ';']); + if (!$tokens[$openBraceIndex]->equals('{')) { + continue; + } + + $closeBraceIndex = $tokens->getNextNonWhitespace($openBraceIndex); + if (!$tokens[$closeBraceIndex]->equals('}')) { + continue; + } + + $tokens->ensureWhitespaceAtIndex($openBraceIndex + 1, 0, ''); + + $beforeOpenBraceIndex = $tokens->getPrevNonWhitespace($openBraceIndex); + if (!$tokens[$beforeOpenBraceIndex]->isGivenKind([T_COMMENT, T_DOC_COMMENT])) { + $tokens->ensureWhitespaceAtIndex($openBraceIndex - 1, 1, ' '); + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/ClassReferenceNameCasingFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/ClassReferenceNameCasingFixer.php new file mode 100644 index 00000000..20b951d1 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/ClassReferenceNameCasingFixer.php @@ -0,0 +1,168 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Casing; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class ClassReferenceNameCasingFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'When referencing an internal class it must be written using the correct casing.', + [ + new CodeSample("isTokenKindFound(T_STRING); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $namespaceUsesAnalyzer = new NamespaceUsesAnalyzer(); + $classNames = $this->getClassNames(); + + foreach ($tokens->getNamespaceDeclarations() as $namespace) { + $uses = []; + + foreach ($namespaceUsesAnalyzer->getDeclarationsInNamespace($tokens, $namespace) as $use) { + $uses[strtolower($use->getShortName())] = true; + } + + foreach ($this->getClassReference($tokens, $namespace) as $reference) { + $currentContent = $tokens[$reference]->getContent(); + $lowerCurrentContent = strtolower($currentContent); + + if (isset($classNames[$lowerCurrentContent]) && $currentContent !== $classNames[$lowerCurrentContent] && !isset($uses[$lowerCurrentContent])) { + $tokens[$reference] = new Token([T_STRING, $classNames[$lowerCurrentContent]]); + } + } + } + } + + private function getClassReference(Tokens $tokens, NamespaceAnalysis $namespace): \Generator + { + static $notBeforeKinds; + static $blockKinds; + + if (null === $notBeforeKinds) { + $notBeforeKinds = [ + CT::T_USE_TRAIT, + T_AS, + T_CASE, // PHP 8.1 trait enum-case + T_CLASS, + T_CONST, + T_DOUBLE_ARROW, + T_DOUBLE_COLON, + T_FUNCTION, + T_INTERFACE, + T_OBJECT_OPERATOR, + T_TRAIT, + ]; + + if (\defined('T_NULLSAFE_OBJECT_OPERATOR')) { // @TODO: drop condition when PHP 8.0+ is required + $notBeforeKinds[] = T_NULLSAFE_OBJECT_OPERATOR; + } + + if (\defined('T_ENUM')) { // @TODO: drop condition when PHP 8.1+ is required + $notBeforeKinds[] = T_ENUM; + } + } + + if (null === $blockKinds) { + $blockKinds = ['before' => [','], 'after' => [',']]; + + foreach (Tokens::getBlockEdgeDefinitions() as $definition) { + $blockKinds['before'][] = $definition['start']; + $blockKinds['after'][] = $definition['end']; + } + } + + $namespaceIsGlobal = $namespace->isGlobalNamespace(); + + for ($index = $namespace->getScopeStartIndex(); $index < $namespace->getScopeEndIndex(); ++$index) { + if (!$tokens[$index]->isGivenKind(T_STRING)) { + continue; + } + + $nextIndex = $tokens->getNextMeaningfulToken($index); + + if ($tokens[$nextIndex]->isGivenKind(T_NS_SEPARATOR)) { + continue; + } + + $prevIndex = $tokens->getPrevMeaningfulToken($index); + $nextIndex = $tokens->getNextMeaningfulToken($index); + + $isNamespaceSeparator = $tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR); + + if (!$isNamespaceSeparator && !$namespaceIsGlobal) { + continue; + } + + if ($isNamespaceSeparator) { + $prevIndex = $tokens->getPrevMeaningfulToken($prevIndex); + + if ($tokens[$prevIndex]->isGivenKind(T_STRING)) { + continue; + } + } elseif ($tokens[$prevIndex]->isGivenKind($notBeforeKinds)) { + continue; + } + + if ($tokens[$prevIndex]->equalsAny($blockKinds['before']) && $tokens[$nextIndex]->equalsAny($blockKinds['after'])) { + continue; + } + + if (!$tokens[$prevIndex]->isGivenKind(T_NEW) && $tokens[$nextIndex]->equalsAny(['(', ';', [T_CLOSE_TAG]])) { + continue; + } + + yield $index; + } + } + + /** + * @return array + */ + private function getClassNames(): array + { + static $classes = null; + + if (null === $classes) { + $classes = []; + + foreach (get_declared_classes() as $class) { + if ((new \ReflectionClass($class))->isInternal()) { + $classes[strtolower($class)] = $class; + } + } + } + + return $classes; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/ConstantCaseFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/ConstantCaseFixer.php new file mode 100644 index 00000000..14501aaf --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/ConstantCaseFixer.php @@ -0,0 +1,155 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Casing; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Fixer for constants case. + * + * @author Pol Dellaiera + */ +final class ConstantCaseFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** + * Hold the function that will be used to convert the constants. + * + * @var callable + */ + private $fixFunction; + + public function configure(array $configuration): void + { + parent::configure($configuration); + + if ('lower' === $this->configuration['case']) { + $this->fixFunction = static fn (string $content): string => strtolower($content); + } + + if ('upper' === $this->configuration['case']) { + $this->fixFunction = static fn (string $content): string => strtoupper($content); + } + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'The PHP constants `true`, `false`, and `null` MUST be written using the correct casing.', + [ + new CodeSample(" 'upper']), + ] + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_STRING); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('case', 'Whether to use the `upper` or `lower` case syntax.')) + ->setAllowedValues(['upper', 'lower']) + ->setDefault('lower') + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $fixFunction = $this->fixFunction; + + foreach ($tokens as $index => $token) { + if (!$token->isNativeConstant()) { + continue; + } + + if ( + $this->isNeighbourAccepted($tokens, $tokens->getPrevMeaningfulToken($index)) + && $this->isNeighbourAccepted($tokens, $tokens->getNextMeaningfulToken($index)) + && !$this->isEnumCaseName($tokens, $index) + ) { + $tokens[$index] = new Token([$token->getId(), $fixFunction($token->getContent())]); + } + } + } + + private function isNeighbourAccepted(Tokens $tokens, int $index): bool + { + static $forbiddenTokens = null; + + if (null === $forbiddenTokens) { + $forbiddenTokens = [ + T_AS, + T_CLASS, + T_CONST, + T_EXTENDS, + T_IMPLEMENTS, + T_INSTANCEOF, + T_INSTEADOF, + T_INTERFACE, + T_NEW, + T_NS_SEPARATOR, + T_PAAMAYIM_NEKUDOTAYIM, + T_TRAIT, + T_USE, + CT::T_USE_TRAIT, + CT::T_USE_LAMBDA, + ...Token::getObjectOperatorKinds(), + ]; + } + + $token = $tokens[$index]; + + if ($token->equalsAny(['{', '}'])) { + return false; + } + + return !$token->isGivenKind($forbiddenTokens); + } + + private function isEnumCaseName(Tokens $tokens, int $index): bool + { + if (!\defined('T_ENUM') || !$tokens->isTokenKindFound(T_ENUM)) { // @TODO: drop condition when PHP 8.1+ is required + return false; + } + + $prevIndex = $tokens->getPrevMeaningfulToken($index); + + if (null === $prevIndex || !$tokens[$prevIndex]->isGivenKind(T_CASE)) { + return false; + } + + if (!$tokens->isTokenKindFound(T_SWITCH)) { + return true; + } + + $prevIndex = $tokens->getPrevTokenOfKind($prevIndex, [[T_ENUM], [T_SWITCH]]); + + return null !== $prevIndex && $tokens[$prevIndex]->isGivenKind(T_ENUM); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/IntegerLiteralCaseFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/IntegerLiteralCaseFixer.php new file mode 100644 index 00000000..1d85abba --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/IntegerLiteralCaseFixer.php @@ -0,0 +1,62 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Casing; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class IntegerLiteralCaseFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Integer literals must be in correct case.', + [ + new CodeSample( + "isTokenKindFound(T_LNUMBER); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_LNUMBER)) { + continue; + } + + $content = $token->getContent(); + + $newContent = Preg::replaceCallback('#^0([boxBOX])([0-9a-fA-F_]+)$#', static fn ($matches) => '0'.strtolower($matches[1]).strtoupper($matches[2]), $content); + + if ($content === $newContent) { + continue; + } + + $tokens[$index] = new Token([T_LNUMBER, $newContent]); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/LowercaseKeywordsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/LowercaseKeywordsFixer.php new file mode 100644 index 00000000..120e4cde --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/LowercaseKeywordsFixer.php @@ -0,0 +1,72 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Casing; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Fixer for rules defined in PSR2 ¶2.5. + * + * @author Dariusz Rumiński + */ +final class LowercaseKeywordsFixer extends AbstractFixer +{ + /** + * @var int[] + */ + private static array $excludedTokens = [T_HALT_COMPILER]; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'PHP keywords MUST be in lower case.', + [ + new CodeSample( + 'isAnyTokenKindsFound(Token::getKeywords()); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if ($token->isKeyword() && !$token->isGivenKind(self::$excludedTokens)) { + $tokens[$index] = new Token([$token->getId(), strtolower($token->getContent())]); + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/LowercaseStaticReferenceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/LowercaseStaticReferenceFixer.php new file mode 100644 index 00000000..7615b296 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/LowercaseStaticReferenceFixer.php @@ -0,0 +1,107 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Casing; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Kuba Werłos + */ +final class LowercaseStaticReferenceFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Class static references `self`, `static` and `parent` MUST be in lower case.', + [ + new CodeSample('isAnyTokenKindsFound([T_STATIC, T_STRING]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if (!$token->equalsAny([[T_STRING, 'self'], [T_STATIC, 'static'], [T_STRING, 'parent']], false)) { + continue; + } + + $newContent = strtolower($token->getContent()); + if ($token->getContent() === $newContent) { + continue; // case is already correct + } + + $prevIndex = $tokens->getPrevMeaningfulToken($index); + if ($tokens[$prevIndex]->isGivenKind([T_CONST, T_DOUBLE_COLON, T_FUNCTION, T_NAMESPACE, T_NS_SEPARATOR, T_STATIC, T_STRING, CT::T_ARRAY_TYPEHINT, CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE]) || $tokens[$prevIndex]->isObjectOperator()) { + continue; + } + + $nextIndex = $tokens->getNextMeaningfulToken($index); + if ($tokens[$nextIndex]->isGivenKind([T_FUNCTION, T_NS_SEPARATOR, T_PRIVATE, T_PROTECTED, T_PUBLIC, T_STRING, CT::T_NULLABLE_TYPE])) { + continue; + } + + if ('static' === $newContent && $tokens[$nextIndex]->isGivenKind(T_VARIABLE)) { + continue; + } + + if ($tokens[$prevIndex]->isGivenKind(T_CASE) && !$tokens[$nextIndex]->isGivenKind(T_PAAMAYIM_NEKUDOTAYIM)) { + continue; + } + + $tokens[$index] = new Token([$token->getId(), $newContent]); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/MagicConstantCasingFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/MagicConstantCasingFixer.php new file mode 100644 index 00000000..f4902b77 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/MagicConstantCasingFixer.php @@ -0,0 +1,92 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Casing; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author ntzm + */ +final class MagicConstantCasingFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Magic constants should be referred to using the correct casing.', + [new CodeSample("isAnyTokenKindsFound($this->getMagicConstantTokens()); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $magicConstants = $this->getMagicConstants(); + $magicConstantTokens = $this->getMagicConstantTokens(); + + foreach ($tokens as $index => $token) { + if ($token->isGivenKind($magicConstantTokens)) { + $tokens[$index] = new Token([$token->getId(), $magicConstants[$token->getId()]]); + } + } + } + + /** + * @return array + */ + private function getMagicConstants(): array + { + static $magicConstants = null; + + if (null === $magicConstants) { + $magicConstants = [ + T_LINE => '__LINE__', + T_FILE => '__FILE__', + T_DIR => '__DIR__', + T_FUNC_C => '__FUNCTION__', + T_CLASS_C => '__CLASS__', + T_METHOD_C => '__METHOD__', + T_NS_C => '__NAMESPACE__', + CT::T_CLASS_CONSTANT => 'class', + T_TRAIT_C => '__TRAIT__', + ]; + } + + return $magicConstants; + } + + /** + * @return array + */ + private function getMagicConstantTokens(): array + { + static $magicConstantTokens = null; + + if (null === $magicConstantTokens) { + $magicConstantTokens = array_keys($this->getMagicConstants()); + } + + return $magicConstantTokens; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/MagicMethodCasingFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/MagicMethodCasingFixer.php new file mode 100644 index 00000000..544d2cbb --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/MagicMethodCasingFixer.php @@ -0,0 +1,197 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Casing; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class MagicMethodCasingFixer extends AbstractFixer +{ + /** + * @var array + */ + private static array $magicNames = [ + '__call' => '__call', + '__callstatic' => '__callStatic', + '__clone' => '__clone', + '__construct' => '__construct', + '__debuginfo' => '__debugInfo', + '__destruct' => '__destruct', + '__get' => '__get', + '__invoke' => '__invoke', + '__isset' => '__isset', + '__serialize' => '__serialize', + '__set' => '__set', + '__set_state' => '__set_state', + '__sleep' => '__sleep', + '__tostring' => '__toString', + '__unserialize' => '__unserialize', + '__unset' => '__unset', + '__wakeup' => '__wakeup', + ]; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Magic method definitions and calls must be using the correct casing.', + [ + new CodeSample( + '__INVOKE(1); +' + ), + ] + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_STRING) && $tokens->isAnyTokenKindsFound([T_FUNCTION, T_DOUBLE_COLON, ...Token::getObjectOperatorKinds()]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $inClass = 0; + $tokenCount = \count($tokens); + + for ($index = 1; $index < $tokenCount - 2; ++$index) { + if (0 === $inClass && $tokens[$index]->isClassy()) { + $inClass = 1; + $index = $tokens->getNextTokenOfKind($index, ['{']); + + continue; + } + + if (0 !== $inClass) { + if ($tokens[$index]->equals('{')) { + ++$inClass; + + continue; + } + + if ($tokens[$index]->equals('}')) { + --$inClass; + + continue; + } + } + + if (!$tokens[$index]->isGivenKind(T_STRING)) { + continue; // wrong type + } + + $content = $tokens[$index]->getContent(); + + if (!str_starts_with($content, '__')) { + continue; // cheap look ahead + } + + $name = strtolower($content); + + if (!$this->isMagicMethodName($name)) { + continue; // method name is not one of the magic ones we can fix + } + + $nameInCorrectCasing = $this->getMagicMethodNameInCorrectCasing($name); + if ($nameInCorrectCasing === $content) { + continue; // method name is already in the correct casing, no fix needed + } + + if ($this->isFunctionSignature($tokens, $index)) { + if (0 !== $inClass) { + // this is a method definition we want to fix + $this->setTokenToCorrectCasing($tokens, $index, $nameInCorrectCasing); + } + + continue; + } + + if ($this->isMethodCall($tokens, $index)) { + $this->setTokenToCorrectCasing($tokens, $index, $nameInCorrectCasing); + + continue; + } + + if ( + ('__callstatic' === $name || '__set_state' === $name) + && $this->isStaticMethodCall($tokens, $index) + ) { + $this->setTokenToCorrectCasing($tokens, $index, $nameInCorrectCasing); + } + } + } + + private function isFunctionSignature(Tokens $tokens, int $index): bool + { + $prevIndex = $tokens->getPrevMeaningfulToken($index); + if (!$tokens[$prevIndex]->isGivenKind(T_FUNCTION)) { + return false; // not a method signature + } + + return $tokens[$tokens->getNextMeaningfulToken($index)]->equals('('); + } + + private function isMethodCall(Tokens $tokens, int $index): bool + { + $prevIndex = $tokens->getPrevMeaningfulToken($index); + if (!$tokens[$prevIndex]->isObjectOperator()) { + return false; // not a "simple" method call + } + + return $tokens[$tokens->getNextMeaningfulToken($index)]->equals('('); + } + + private function isStaticMethodCall(Tokens $tokens, int $index): bool + { + $prevIndex = $tokens->getPrevMeaningfulToken($index); + if (!$tokens[$prevIndex]->isGivenKind(T_DOUBLE_COLON)) { + return false; // not a "simple" static method call + } + + return $tokens[$tokens->getNextMeaningfulToken($index)]->equals('('); + } + + private function isMagicMethodName(string $name): bool + { + return isset(self::$magicNames[$name]); + } + + /** + * @param string $name name of a magic method + */ + private function getMagicMethodNameInCorrectCasing(string $name): string + { + return self::$magicNames[$name]; + } + + private function setTokenToCorrectCasing(Tokens $tokens, int $index, string $nameInCorrectCasing): void + { + $tokens[$index] = new Token([T_STRING, $nameInCorrectCasing]); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/NativeFunctionCasingFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/NativeFunctionCasingFixer.php new file mode 100644 index 00000000..84c7c643 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/NativeFunctionCasingFixer.php @@ -0,0 +1,89 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Casing; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class NativeFunctionCasingFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Function defined by PHP should be called using the correct casing.', + [new CodeSample("isTokenKindFound(T_STRING); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $functionsAnalyzer = new FunctionsAnalyzer(); + + static $nativeFunctionNames = null; + + if (null === $nativeFunctionNames) { + $nativeFunctionNames = $this->getNativeFunctionNames(); + } + + for ($index = 0, $count = $tokens->count(); $index < $count; ++$index) { + // test if we are at a function all + if (!$functionsAnalyzer->isGlobalFunctionCall($tokens, $index)) { + continue; + } + + // test if the function call is to a native PHP function + $lower = strtolower($tokens[$index]->getContent()); + if (!\array_key_exists($lower, $nativeFunctionNames)) { + continue; + } + + $tokens[$index] = new Token([T_STRING, $nativeFunctionNames[$lower]]); + } + } + + /** + * @return array + */ + private function getNativeFunctionNames(): array + { + $allFunctions = get_defined_functions(); + $functions = []; + foreach ($allFunctions['internal'] as $function) { + $functions[strtolower($function)] = $function; + } + + return $functions; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/NativeFunctionTypeDeclarationCasingFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/NativeFunctionTypeDeclarationCasingFixer.php new file mode 100644 index 00000000..2b2d5094 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/NativeFunctionTypeDeclarationCasingFixer.php @@ -0,0 +1,58 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Casing; + +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; + +/** + * @deprecated in favor of NativeTypeDeclarationCasingFixer + */ +final class NativeFunctionTypeDeclarationCasingFixer extends AbstractProxyFixer implements DeprecatedFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Native type declarations for functions should use the correct case.', + [ + new CodeSample("proxyFixers); + } + + protected function createProxyFixers(): array + { + $fixer = new NativeTypeDeclarationCasingFixer(); + + return [$fixer]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/NativeTypeDeclarationCasingFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/NativeTypeDeclarationCasingFixer.php new file mode 100644 index 00000000..726fd398 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/NativeTypeDeclarationCasingFixer.php @@ -0,0 +1,360 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Casing; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\TypeAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +final class NativeTypeDeclarationCasingFixer extends AbstractFixer +{ + /* + * https://wiki.php.net/rfc/typed_class_constants + * Supported types + * Class constant type declarations support all type declarations supported by PHP, + * except `void`, `callable`, `never`. + * + * array + * bool + * callable + * float + * int + * iterable + * object + * mixed + * parent + * self + * string + * any class or interface name -> not native, so not applicable for this Fixer + * ?type -> not native, `?` has no casing, so not applicable for this Fixer + * + * Not in the list referenced but supported: + * null + * static + */ + private const CLASS_CONST_SUPPORTED_HINTS = [ + 'array' => true, + 'bool' => true, + 'float' => true, + 'int' => true, + 'iterable' => true, + 'mixed' => true, + 'null' => true, + 'object' => true, + 'parent' => true, + 'self' => true, + 'string' => true, + 'static' => true, + ]; + + private const CLASS_PROPERTY_SUPPORTED_HINTS = [ + 'array' => true, + 'bool' => true, + 'float' => true, + 'int' => true, + 'iterable' => true, + 'mixed' => true, + 'null' => true, + 'object' => true, + 'parent' => true, + 'self' => true, + 'static' => true, + 'string' => true, + ]; + + private const TYPE_SEPARATION_TYPES = [ + CT::T_TYPE_ALTERNATION, + CT::T_TYPE_INTERSECTION, + CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_OPEN, + CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE, + ]; + + /** + * https://secure.php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration. + * + * self PHP 5.0 + * array PHP 5.1 + * callable PHP 5.4 + * bool PHP 7.0 + * float PHP 7.0 + * int PHP 7.0 + * string PHP 7.0 + * iterable PHP 7.1 + * void PHP 7.1 + * object PHP 7.2 + * static PHP 8.0 (return type only) + * mixed PHP 8.0 + * false PHP 8.0 (union return type only) + * null PHP 8.0 (union return type only) + * never PHP 8.1 (return type only) + * true PHP 8.2 (standalone type: https://wiki.php.net/rfc/true-type) + * false PHP 8.2 (standalone type: https://wiki.php.net/rfc/null-false-standalone-types) + * null PHP 8.2 (standalone type: https://wiki.php.net/rfc/null-false-standalone-types) + * + * @var array + */ + private array $functionTypeHints; + + private FunctionsAnalyzer $functionsAnalyzer; + + /** + * @var list|string> + */ + private array $beforePropertyTypeTokens; + + public function __construct() + { + parent::__construct(); + + $this->beforePropertyTypeTokens = ['{', ';', [T_PRIVATE], [T_PROTECTED], [T_PUBLIC], [T_VAR]]; + + $this->functionTypeHints = [ + 'array' => true, + 'bool' => true, + 'callable' => true, + 'float' => true, + 'int' => true, + 'iterable' => true, + 'object' => true, + 'self' => true, + 'string' => true, + 'void' => true, + ]; + + if (\PHP_VERSION_ID >= 8_00_00) { + $this->functionTypeHints['false'] = true; + $this->functionTypeHints['mixed'] = true; + $this->functionTypeHints['null'] = true; + $this->functionTypeHints['static'] = true; + } + + if (\PHP_VERSION_ID >= 8_01_00) { + $this->functionTypeHints['never'] = true; + + $this->beforePropertyTypeTokens[] = [T_READONLY]; + } + + if (\PHP_VERSION_ID >= 8_02_00) { + $this->functionTypeHints['true'] = true; + } + + $this->functionsAnalyzer = new FunctionsAnalyzer(); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Native type declarations should be used in the correct case.', + [ + new CodeSample( + "isAnyTokenKindsFound(Token::getClassyTokenKinds()); + + return + $tokens->isAnyTokenKindsFound([T_FUNCTION, T_FN]) + || ($classyFound && $tokens->isTokenKindFound(T_STRING)) + || ( + \PHP_VERSION_ID >= 8_03_00 + && $tokens->isTokenKindFound(T_CONST) + && $classyFound + ); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $this->fixFunctions($tokens); + $this->fixClassConstantsAndProperties($tokens); + } + + private function fixFunctions(Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + if ($tokens[$index]->isGivenKind([T_FUNCTION, T_FN])) { + $this->fixFunctionReturnType($tokens, $index); + $this->fixFunctionArgumentTypes($tokens, $index); + } + } + } + + private function fixFunctionArgumentTypes(Tokens $tokens, int $index): void + { + foreach ($this->functionsAnalyzer->getFunctionArguments($tokens, $index) as $argument) { + $this->fixArgumentType($tokens, $argument->getTypeAnalysis()); + } + } + + private function fixFunctionReturnType(Tokens $tokens, int $index): void + { + $this->fixArgumentType($tokens, $this->functionsAnalyzer->getFunctionReturnType($tokens, $index)); + } + + private function fixArgumentType(Tokens $tokens, ?TypeAnalysis $type = null): void + { + if (null === $type) { + return; + } + + for ($index = $type->getStartIndex(); $index <= $type->getEndIndex(); ++$index) { + if ($tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind(T_NS_SEPARATOR)) { + continue; + } + + $this->fixCasing($this->functionTypeHints, $tokens, $index); + } + } + + private function fixClassConstantsAndProperties(Tokens $tokens): void + { + $analyzer = new TokensAnalyzer($tokens); + $elements = array_reverse($analyzer->getClassyElements(), true); + + foreach ($elements as $index => $element) { + if ('const' === $element['type']) { + if (\PHP_VERSION_ID >= 8_03_00 && !$this->isConstWithoutType($tokens, $index)) { + foreach ($this->getNativeTypeHintCandidatesForConstant($tokens, $index) as $nativeTypeHintIndex) { + $this->fixCasing($this::CLASS_CONST_SUPPORTED_HINTS, $tokens, $nativeTypeHintIndex); + } + } + + continue; + } + + if ('property' === $element['type']) { + foreach ($this->getNativeTypeHintCandidatesForProperty($tokens, $index) as $nativeTypeHintIndex) { + $this->fixCasing($this::CLASS_PROPERTY_SUPPORTED_HINTS, $tokens, $nativeTypeHintIndex); + } + } + } + } + + /** @return iterable */ + private function getNativeTypeHintCandidatesForConstant(Tokens $tokens, int $index): iterable + { + $constNameIndex = $this->getConstNameIndex($tokens, $index); + $index = $this->getFirstIndexOfType($tokens, $index); + + do { + $typeEnd = $this->getTypeEnd($tokens, $index, $constNameIndex); + + if ($typeEnd === $index) { + yield $index; + } + + do { + $index = $tokens->getNextMeaningfulToken($index); + } while ($tokens[$index]->isGivenKind(self::TYPE_SEPARATION_TYPES)); + } while ($index < $constNameIndex); + } + + private function isConstWithoutType(Tokens $tokens, int $index): bool + { + $index = $tokens->getNextMeaningfulToken($index); + + return $tokens[$index]->isGivenKind(T_STRING) && $tokens[$tokens->getNextMeaningfulToken($index)]->equals('='); + } + + private function getConstNameIndex(Tokens $tokens, int $index): int + { + return $tokens->getPrevMeaningfulToken( + $tokens->getNextTokenOfKind($index, ['=']), + ); + } + + /** @return iterable */ + private function getNativeTypeHintCandidatesForProperty(Tokens $tokens, int $index): iterable + { + $propertyNameIndex = $index; + $index = $tokens->getPrevTokenOfKind($index, $this->beforePropertyTypeTokens); + + $index = $this->getFirstIndexOfType($tokens, $index); + + do { + $typeEnd = $this->getTypeEnd($tokens, $index, $propertyNameIndex); + + if ($typeEnd === $index) { + yield $index; + } + + do { + $index = $tokens->getNextMeaningfulToken($index); + } while ($tokens[$index]->isGivenKind(self::TYPE_SEPARATION_TYPES)); + } while ($index < $propertyNameIndex); + + return []; + } + + private function getFirstIndexOfType(Tokens $tokens, int $index): int + { + $index = $tokens->getNextMeaningfulToken($index); + + if ($tokens[$index]->isGivenKind(CT::T_NULLABLE_TYPE)) { + $index = $tokens->getNextMeaningfulToken($index); + } + + if ($tokens[$index]->isGivenKind(CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_OPEN)) { + $index = $tokens->getNextMeaningfulToken($index); + } + + return $index; + } + + private function getTypeEnd(Tokens $tokens, int $index, int $upperLimit): int + { + if (!$tokens[$index]->isGivenKind([T_STRING, T_NS_SEPARATOR])) { + return $index; // callable, array, self, static, etc. + } + + $endIndex = $index; + while ($tokens[$index]->isGivenKind([T_STRING, T_NS_SEPARATOR]) && $index < $upperLimit) { + $endIndex = $index; + $index = $tokens->getNextMeaningfulToken($index); + } + + return $endIndex; + } + + /** + * @param array $supportedTypeHints + */ + private function fixCasing(array $supportedTypeHints, Tokens $tokens, int $index): void + { + $typeContent = $tokens[$index]->getContent(); + $typeContentLower = strtolower($typeContent); + + if (isset($supportedTypeHints[$typeContentLower]) && $typeContent !== $typeContentLower) { + $tokens[$index] = new Token([$tokens[$index]->getId(), $typeContentLower]); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/CastSpacesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/CastSpacesFixer.php new file mode 100644 index 00000000..bf8c53c3 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/CastSpacesFixer.php @@ -0,0 +1,118 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\CastNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class CastSpacesFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + private const INSIDE_CAST_SPACE_REPLACE_MAP = [ + ' ' => '', + "\t" => '', + "\n" => '', + "\r" => '', + "\0" => '', + "\x0B" => '', + ]; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'A single space or none should be between cast and variable.', + [ + new CodeSample( + " 'single'] + ), + new CodeSample( + " 'none'] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run after NoShortBoolCastFixer. + */ + public function getPriority(): int + { + return -10; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound(Token::getCastTokenKinds()); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if (!$token->isCast()) { + continue; + } + + $tokens[$index] = new Token([ + $token->getId(), + strtr($token->getContent(), self::INSIDE_CAST_SPACE_REPLACE_MAP), + ]); + + if ('single' === $this->configuration['space']) { + // force single whitespace after cast token: + if ($tokens[$index + 1]->isWhitespace(" \t")) { + // - if next token is whitespaces that contains only spaces and tabs - override next token with single space + $tokens[$index + 1] = new Token([T_WHITESPACE, ' ']); + } elseif (!$tokens[$index + 1]->isWhitespace()) { + // - if next token is not whitespaces that contains spaces, tabs and new lines - append single space to current token + $tokens->insertAt($index + 1, new Token([T_WHITESPACE, ' '])); + } + + continue; + } + + // force no whitespace after cast token: + if ($tokens[$index + 1]->isWhitespace()) { + $tokens->clearAt($index + 1); + } + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('space', 'Spacing to apply between cast and variable.')) + ->setAllowedValues(['none', 'single']) + ->setDefault('single') + ->getOption(), + ]); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/LowercaseCastFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/LowercaseCastFixer.php new file mode 100644 index 00000000..e50db282 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/LowercaseCastFixer.php @@ -0,0 +1,66 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\CastNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class LowercaseCastFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Cast should be written in lower case.', + [ + new CodeSample( + 'isAnyTokenKindsFound(Token::getCastTokenKinds()); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = 0, $count = $tokens->count(); $index < $count; ++$index) { + if (!$tokens[$index]->isCast()) { + continue; + } + + $tokens[$index] = new Token([$tokens[$index]->getId(), strtolower($tokens[$index]->getContent())]); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/ModernizeTypesCastingFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/ModernizeTypesCastingFixer.php new file mode 100644 index 00000000..75f2a437 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/ModernizeTypesCastingFixer.php @@ -0,0 +1,164 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\CastNotation; + +use PhpCsFixer\AbstractFunctionReferenceFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Vladimir Reznichenko + */ +final class ModernizeTypesCastingFixer extends AbstractFunctionReferenceFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Replaces `intval`, `floatval`, `doubleval`, `strval` and `boolval` function calls with according type casting operator.', + [ + new CodeSample( + ' [T_INT_CAST, '(int)'], + 'floatval' => [T_DOUBLE_CAST, '(float)'], + 'doubleval' => [T_DOUBLE_CAST, '(float)'], + 'strval' => [T_STRING_CAST, '(string)'], + 'boolval' => [T_BOOL_CAST, '(bool)'], + ]; + + $argumentsAnalyzer = new ArgumentsAnalyzer(); + + foreach ($replacement as $functionIdentity => $newToken) { + $currIndex = 0; + + do { + // try getting function reference and translate boundaries for humans + $boundaries = $this->find($functionIdentity, $tokens, $currIndex, $tokens->count() - 1); + + if (null === $boundaries) { + // next function search, as current one not found + continue 2; + } + + [$functionName, $openParenthesis, $closeParenthesis] = $boundaries; + + // analysing cursor shift + $currIndex = $openParenthesis; + + // indicator that the function is overridden + if (1 !== $argumentsAnalyzer->countArguments($tokens, $openParenthesis, $closeParenthesis)) { + continue; + } + + $paramContentEnd = $closeParenthesis; + $commaCandidate = $tokens->getPrevMeaningfulToken($paramContentEnd); + + if ($tokens[$commaCandidate]->equals(',')) { + $tokens->removeTrailingWhitespace($commaCandidate); + $tokens->clearAt($commaCandidate); + $paramContentEnd = $commaCandidate; + } + + // check if something complex passed as an argument and preserve parentheses then + $countParamTokens = 0; + + for ($paramContentIndex = $openParenthesis + 1; $paramContentIndex < $paramContentEnd; ++$paramContentIndex) { + // not a space, means some sensible token + if (!$tokens[$paramContentIndex]->isGivenKind(T_WHITESPACE)) { + ++$countParamTokens; + } + } + + $preserveParentheses = $countParamTokens > 1; + + $afterCloseParenthesisIndex = $tokens->getNextMeaningfulToken($closeParenthesis); + $afterCloseParenthesisToken = $tokens[$afterCloseParenthesisIndex]; + $wrapInParentheses = $afterCloseParenthesisToken->equalsAny(['[', '{']) || $afterCloseParenthesisToken->isGivenKind(T_POW); + + // analyse namespace specification (root one or none) and decide what to do + $prevTokenIndex = $tokens->getPrevMeaningfulToken($functionName); + + if ($tokens[$prevTokenIndex]->isGivenKind(T_NS_SEPARATOR)) { + // get rid of root namespace when it used + $tokens->removeTrailingWhitespace($prevTokenIndex); + $tokens->clearAt($prevTokenIndex); + } + + // perform transformation + $replacementSequence = [ + new Token($newToken), + new Token([T_WHITESPACE, ' ']), + ]; + + if ($wrapInParentheses) { + array_unshift($replacementSequence, new Token('(')); + } + + if (!$preserveParentheses) { + // closing parenthesis removed with leading spaces + $tokens->removeLeadingWhitespace($closeParenthesis); + $tokens->clearAt($closeParenthesis); + + // opening parenthesis removed with trailing spaces + $tokens->removeLeadingWhitespace($openParenthesis); + $tokens->removeTrailingWhitespace($openParenthesis); + $tokens->clearAt($openParenthesis); + } else { + // we'll need to provide a space after a casting operator + $tokens->removeTrailingWhitespace($functionName); + } + + if ($wrapInParentheses) { + $tokens->insertAt($closeParenthesis, new Token(')')); + } + + $tokens->overrideRange($functionName, $functionName, $replacementSequence); + + // nested transformations support + $currIndex = $functionName; + } while (null !== $currIndex); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/NoShortBoolCastFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/NoShortBoolCastFixer.php new file mode 100644 index 00000000..80439c17 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/NoShortBoolCastFixer.php @@ -0,0 +1,88 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\CastNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class NoShortBoolCastFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + * + * Must run before CastSpacesFixer. + */ + public function getPriority(): int + { + return -9; + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Short cast `bool` using double exclamation mark should not be used.', + [new CodeSample("isTokenKindFound('!'); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = \count($tokens) - 1; $index > 1; --$index) { + if ($tokens[$index]->equals('!')) { + $index = $this->fixShortCast($tokens, $index); + } + } + } + + private function fixShortCast(Tokens $tokens, int $index): int + { + for ($i = $index - 1; $i > 1; --$i) { + if ($tokens[$i]->equals('!')) { + $this->fixShortCastToBoolCast($tokens, $i, $index); + + break; + } + + if (!$tokens[$i]->isComment() && !$tokens[$i]->isWhitespace()) { + break; + } + } + + return $i; + } + + private function fixShortCastToBoolCast(Tokens $tokens, int $start, int $end): void + { + for (; $start <= $end; ++$start) { + if ( + !$tokens[$start]->isComment() + && !($tokens[$start]->isWhitespace() && $tokens[$start - 1]->isComment()) + ) { + $tokens->clearAt($start); + } + } + + $tokens->insertAt($start, new Token([T_BOOL_CAST, '(bool)'])); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/NoUnsetCastFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/NoUnsetCastFixer.php new file mode 100644 index 00000000..81c1567f --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/NoUnsetCastFixer.php @@ -0,0 +1,88 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\CastNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class NoUnsetCastFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Variables must be set `null` instead of using `(unset)` casting.', + [new CodeSample("isTokenKindFound(T_UNSET_CAST); + } + + /** + * {@inheritdoc} + * + * Must run before BinaryOperatorSpacesFixer. + */ + public function getPriority(): int + { + return 0; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = \count($tokens) - 1; $index > 0; --$index) { + if ($tokens[$index]->isGivenKind(T_UNSET_CAST)) { + $this->fixUnsetCast($tokens, $index); + } + } + } + + private function fixUnsetCast(Tokens $tokens, int $index): void + { + $assignmentIndex = $tokens->getPrevMeaningfulToken($index); + if (null === $assignmentIndex || !$tokens[$assignmentIndex]->equals('=')) { + return; + } + + $varIndex = $tokens->getNextMeaningfulToken($index); + if (null === $varIndex || !$tokens[$varIndex]->isGivenKind(T_VARIABLE)) { + return; + } + + $afterVar = $tokens->getNextMeaningfulToken($varIndex); + if (null === $afterVar || !$tokens[$afterVar]->equalsAny([';', [T_CLOSE_TAG]])) { + return; + } + + $nextIsWhiteSpace = $tokens[$assignmentIndex + 1]->isWhitespace(); + + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + $tokens->clearTokenAndMergeSurroundingWhitespace($varIndex); + + ++$assignmentIndex; + if (!$nextIsWhiteSpace) { + $tokens->insertAt($assignmentIndex, new Token([T_WHITESPACE, ' '])); + } + + ++$assignmentIndex; + $tokens->insertAt($assignmentIndex, new Token([T_STRING, 'null'])); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/ShortScalarCastFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/ShortScalarCastFixer.php new file mode 100644 index 00000000..934d8a89 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/ShortScalarCastFixer.php @@ -0,0 +1,71 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\CastNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class ShortScalarCastFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Cast `(boolean)` and `(integer)` should be written as `(bool)` and `(int)`, `(double)` and `(real)` as `(float)`, `(binary)` as `(string)`.', + [ + new CodeSample( + "isAnyTokenKindsFound(Token::getCastTokenKinds()); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + static $castMap = [ + 'boolean' => 'bool', + 'integer' => 'int', + 'double' => 'float', + 'real' => 'float', + 'binary' => 'string', + ]; + + for ($index = 0, $count = $tokens->count(); $index < $count; ++$index) { + if (!$tokens[$index]->isCast()) { + continue; + } + + $castFrom = trim(substr($tokens[$index]->getContent(), 1, -1)); + $castFromLowered = strtolower($castFrom); + + if (!\array_key_exists($castFromLowered, $castMap)) { + continue; + } + + $tokens[$index] = new Token([ + $tokens[$index]->getId(), + str_replace($castFrom, $castMap[$castFromLowered], $tokens[$index]->getContent()), + ]); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/ClassAttributesSeparationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/ClassAttributesSeparationFixer.php new file mode 100644 index 00000000..a0c4f720 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/ClassAttributesSeparationFixer.php @@ -0,0 +1,596 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ClassNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; +use PhpCsFixer\Utils; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; + +/** + * Make sure there is one blank line above and below class elements. + * + * The exception is when an element is the first or last item in a 'classy'. + * + * @phpstan-type _Class array{ + * index: int, + * open: int, + * close: int, + * elements: non-empty-list<_Element> + * } + * @phpstan-type _Element array{token: Token, type: string, index: int, start?: int, end?: int} + */ +final class ClassAttributesSeparationFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface +{ + /** + * @internal + */ + public const SPACING_NONE = 'none'; + + /** + * @internal + */ + public const SPACING_ONE = 'one'; + + private const SPACING_ONLY_IF_META = 'only_if_meta'; + + /** + * @var array + */ + private array $classElementTypes = []; + + public function configure(array $configuration): void + { + parent::configure($configuration); + + $this->classElementTypes = []; // reset previous configuration + + foreach ($this->configuration['elements'] as $elementType => $spacing) { + $this->classElementTypes[$elementType] = $spacing; + } + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Class, trait and interface elements must be separated with one or none blank line.', + [ + new CodeSample( + ' ['property' => self::SPACING_ONE]] + ), + new CodeSample( + ' ['const' => self::SPACING_ONE]] + ), + new CodeSample( + ' ['const' => self::SPACING_ONLY_IF_META]] + ), + new VersionSpecificCodeSample( + ' ['property' => self::SPACING_ONLY_IF_META]] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before BracesFixer, IndentationTypeFixer, NoExtraBlankLinesFixer, StatementIndentationFixer. + * Must run after OrderedClassElementsFixer, SingleClassElementPerStatementFixer, VisibilityRequiredFixer. + */ + public function getPriority(): int + { + return 55; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds()); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($this->getElementsByClass($tokens) as $class) { + $elements = $class['elements']; + $elementCount = \count($elements); + + if (0 === $elementCount) { + continue; + } + + if (isset($this->classElementTypes[$elements[0]['type']])) { + $this->fixSpaceBelowClassElement($tokens, $class); + $this->fixSpaceAboveClassElement($tokens, $class, 0); + } + + for ($index = 1; $index < $elementCount; ++$index) { + if (isset($this->classElementTypes[$elements[$index]['type']])) { + $this->fixSpaceAboveClassElement($tokens, $class, $index); + } + } + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('elements', 'Dictionary of `const|method|property|trait_import|case` => `none|one|only_if_meta` values.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([static function (array $option): bool { + foreach ($option as $type => $spacing) { + $supportedTypes = ['const', 'method', 'property', 'trait_import', 'case']; + + if (!\in_array($type, $supportedTypes, true)) { + throw new InvalidOptionsException( + sprintf( + 'Unexpected element type, expected any of %s, got "%s".', + Utils::naturalLanguageJoin($supportedTypes), + \gettype($type).'#'.$type + ) + ); + } + + $supportedSpacings = [self::SPACING_NONE, self::SPACING_ONE, self::SPACING_ONLY_IF_META]; + + if (!\in_array($spacing, $supportedSpacings, true)) { + throw new InvalidOptionsException( + sprintf( + 'Unexpected spacing for element type "%s", expected any of %s, got "%s".', + $spacing, + Utils::naturalLanguageJoin($supportedSpacings), + \is_object($spacing) ? \get_class($spacing) : (null === $spacing ? 'null' : \gettype($spacing).'#'.$spacing) + ) + ); + } + } + + return true; + }]) + ->setDefault([ + 'const' => self::SPACING_ONE, + 'method' => self::SPACING_ONE, + 'property' => self::SPACING_ONE, + 'trait_import' => self::SPACING_NONE, + 'case' => self::SPACING_NONE, + ]) + ->getOption(), + ]); + } + + /** + * Fix spacing above an element of a class, interface or trait. + * + * Deals with comments, PHPDocs and spaces above the element with respect to the position of the + * element within the class, interface or trait. + * + * @param _Class $class + */ + private function fixSpaceAboveClassElement(Tokens $tokens, array $class, int $elementIndex): void + { + $element = $class['elements'][$elementIndex]; + $elementAboveEnd = isset($class['elements'][$elementIndex + 1]) ? $class['elements'][$elementIndex + 1]['end'] : 0; + $nonWhiteAbove = $tokens->getPrevNonWhitespace($element['start']); + + // element is directly after class open brace + if ($nonWhiteAbove === $class['open']) { + $this->correctLineBreaks($tokens, $nonWhiteAbove, $element['start'], 1); + + return; + } + + // deal with comments above an element + if ($tokens[$nonWhiteAbove]->isGivenKind(T_COMMENT)) { + // check if the comment belongs to the previous element + if ($elementAboveEnd === $nonWhiteAbove) { + $this->correctLineBreaks($tokens, $nonWhiteAbove, $element['start'], $this->determineRequiredLineCount($tokens, $class, $elementIndex)); + + return; + } + + // more than one line break, always bring it back to 2 line breaks between the element start and what is above it + if ($tokens[$nonWhiteAbove + 1]->isWhitespace() && substr_count($tokens[$nonWhiteAbove + 1]->getContent(), "\n") > 1) { + $this->correctLineBreaks($tokens, $nonWhiteAbove, $element['start'], 2); + + return; + } + + // there are 2 cases: + if ( + 1 === $element['start'] - $nonWhiteAbove + || $tokens[$nonWhiteAbove - 1]->isWhitespace() && substr_count($tokens[$nonWhiteAbove - 1]->getContent(), "\n") > 0 + || $tokens[$nonWhiteAbove + 1]->isWhitespace() && substr_count($tokens[$nonWhiteAbove + 1]->getContent(), "\n") > 0 + ) { + // 1. The comment is meant for the element (although not a PHPDoc), + // make sure there is one line break between the element and the comment... + $this->correctLineBreaks($tokens, $nonWhiteAbove, $element['start'], 1); + // ... and make sure there is blank line above the comment (with the exception when it is directly after a class opening) + $nonWhiteAbove = $this->findCommentBlockStart($tokens, $nonWhiteAbove, $elementAboveEnd); + $nonWhiteAboveComment = $tokens->getPrevNonWhitespace($nonWhiteAbove); + + $this->correctLineBreaks($tokens, $nonWhiteAboveComment, $nonWhiteAbove, $nonWhiteAboveComment === $class['open'] ? 1 : 2); + } else { + // 2. The comment belongs to the code above the element, + // make sure there is a blank line above the element (i.e. 2 line breaks) + $this->correctLineBreaks($tokens, $nonWhiteAbove, $element['start'], 2); + } + + return; + } + + // deal with element with a PHPDoc/attribute above it + if ($tokens[$nonWhiteAbove]->isGivenKind([T_DOC_COMMENT, CT::T_ATTRIBUTE_CLOSE])) { + // there should be one linebreak between the element and the attribute above it + $this->correctLineBreaks($tokens, $nonWhiteAbove, $element['start'], 1); + + // make sure there is blank line above the comment (with the exception when it is directly after a class opening) + $nonWhiteAbove = $this->findCommentBlockStart($tokens, $nonWhiteAbove, $elementAboveEnd); + $nonWhiteAboveComment = $tokens->getPrevNonWhitespace($nonWhiteAbove); + + $this->correctLineBreaks($tokens, $nonWhiteAboveComment, $nonWhiteAbove, $nonWhiteAboveComment === $class['open'] ? 1 : 2); + + return; + } + + $this->correctLineBreaks($tokens, $nonWhiteAbove, $element['start'], $this->determineRequiredLineCount($tokens, $class, $elementIndex)); + } + + /** + * @param _Class $class + */ + private function determineRequiredLineCount(Tokens $tokens, array $class, int $elementIndex): int + { + $type = $class['elements'][$elementIndex]['type']; + $spacing = $this->classElementTypes[$type]; + + if (self::SPACING_ONE === $spacing) { + return 2; + } + + if (self::SPACING_NONE === $spacing) { + if (!isset($class['elements'][$elementIndex + 1])) { + return 1; + } + + $aboveElement = $class['elements'][$elementIndex + 1]; + + if ($aboveElement['type'] !== $type) { + return 2; + } + + $aboveElementDocCandidateIndex = $tokens->getPrevNonWhitespace($aboveElement['start']); + + return $tokens[$aboveElementDocCandidateIndex]->isGivenKind([T_DOC_COMMENT, CT::T_ATTRIBUTE_CLOSE]) ? 2 : 1; + } + + if (self::SPACING_ONLY_IF_META === $spacing) { + $aboveElementDocCandidateIndex = $tokens->getPrevNonWhitespace($class['elements'][$elementIndex]['start']); + + return $tokens[$aboveElementDocCandidateIndex]->isGivenKind([T_DOC_COMMENT, CT::T_ATTRIBUTE_CLOSE]) ? 2 : 1; + } + + throw new \RuntimeException(sprintf('Unknown spacing "%s".', $spacing)); + } + + /** + * @param _Class $class + */ + private function fixSpaceBelowClassElement(Tokens $tokens, array $class): void + { + $element = $class['elements'][0]; + + // if this is last element fix; fix to the class end `}` here if appropriate + if ($class['close'] === $tokens->getNextNonWhitespace($element['end'])) { + $this->correctLineBreaks($tokens, $element['end'], $class['close'], 1); + } + } + + private function correctLineBreaks(Tokens $tokens, int $startIndex, int $endIndex, int $reqLineCount): void + { + $lineEnding = $this->whitespacesConfig->getLineEnding(); + + ++$startIndex; + $numbOfWhiteTokens = $endIndex - $startIndex; + + if (0 === $numbOfWhiteTokens) { + $tokens->insertAt($startIndex, new Token([T_WHITESPACE, str_repeat($lineEnding, $reqLineCount)])); + + return; + } + + $lineBreakCount = $this->getLineBreakCount($tokens, $startIndex, $endIndex); + + if ($reqLineCount === $lineBreakCount) { + return; + } + + if ($lineBreakCount < $reqLineCount) { + $tokens[$startIndex] = new Token([ + T_WHITESPACE, + str_repeat($lineEnding, $reqLineCount - $lineBreakCount).$tokens[$startIndex]->getContent(), + ]); + + return; + } + + // $lineCount = > $reqLineCount : check the one Token case first since this one will be true most of the time + if (1 === $numbOfWhiteTokens) { + $tokens[$startIndex] = new Token([ + T_WHITESPACE, + Preg::replace('/\r\n|\n/', '', $tokens[$startIndex]->getContent(), $lineBreakCount - $reqLineCount), + ]); + + return; + } + + // $numbOfWhiteTokens = > 1 + $toReplaceCount = $lineBreakCount - $reqLineCount; + + for ($i = $startIndex; $i < $endIndex && $toReplaceCount > 0; ++$i) { + $tokenLineCount = substr_count($tokens[$i]->getContent(), "\n"); + + if ($tokenLineCount > 0) { + $tokens[$i] = new Token([ + T_WHITESPACE, + Preg::replace('/\r\n|\n/', '', $tokens[$i]->getContent(), min($toReplaceCount, $tokenLineCount)), + ]); + $toReplaceCount -= $tokenLineCount; + } + } + } + + private function getLineBreakCount(Tokens $tokens, int $startIndex, int $endIndex): int + { + $lineCount = 0; + + for ($i = $startIndex; $i < $endIndex; ++$i) { + $lineCount += substr_count($tokens[$i]->getContent(), "\n"); + } + + return $lineCount; + } + + private function findCommentBlockStart(Tokens $tokens, int $start, int $elementAboveEnd): int + { + for ($i = $start; $i > $elementAboveEnd; --$i) { + if ($tokens[$i]->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) { + $start = $i = $tokens->findBlockStart(Tokens::BLOCK_TYPE_ATTRIBUTE, $i); + + continue; + } + + if ($tokens[$i]->isComment()) { + $start = $i; + + continue; + } + + if (!$tokens[$i]->isWhitespace() || $this->getLineBreakCount($tokens, $i, $i + 1) > 1) { + break; + } + } + + return $start; + } + + /** + * @TODO Introduce proper DTO instead of an array + * + * @return \Generator<_Class> + */ + private function getElementsByClass(Tokens $tokens): \Generator + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + $class = $classIndex = false; + $elements = $tokensAnalyzer->getClassyElements(); + + for (end($elements);; prev($elements)) { + $index = key($elements); + + if (null === $index) { + break; + } + + $element = current($elements); + $element['index'] = $index; + + if ($element['classIndex'] !== $classIndex) { + if (false !== $class) { + yield $class; + } + + $classIndex = $element['classIndex']; + $classOpen = $tokens->getNextTokenOfKind($classIndex, ['{']); + $classEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classOpen); + $class = [ + 'index' => $classIndex, + 'open' => $classOpen, + 'close' => $classEnd, + 'elements' => [], + ]; + } + + unset($element['classIndex']); + $element['start'] = $this->getFirstTokenIndexOfClassElement($tokens, $class, $element); + $element['end'] = $this->getLastTokenIndexOfClassElement($tokens, $class, $element, $tokensAnalyzer); + + $class['elements'][] = $element; // reset the key by design + } + + if (false !== $class) { + yield $class; + } + } + + /** + * including trailing single line comments if belonging to the class element. + * + * @param _Class $class + * @param _Element $element + */ + private function getFirstTokenIndexOfClassElement(Tokens $tokens, array $class, array $element): int + { + $modifierTypes = [T_PRIVATE, T_PROTECTED, T_PUBLIC, T_ABSTRACT, T_FINAL, T_STATIC, T_STRING, T_NS_SEPARATOR, T_VAR, CT::T_NULLABLE_TYPE, CT::T_ARRAY_TYPEHINT, CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION, CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_OPEN, CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE]; + + if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required + $modifierTypes[] = T_READONLY; + } + + $firstElementAttributeIndex = $element['index']; + + do { + $nonWhiteAbove = $tokens->getPrevMeaningfulToken($firstElementAttributeIndex); + + if (null !== $nonWhiteAbove && $tokens[$nonWhiteAbove]->isGivenKind($modifierTypes)) { + $firstElementAttributeIndex = $nonWhiteAbove; + } else { + break; + } + } while ($firstElementAttributeIndex > $class['open']); + + return $firstElementAttributeIndex; + } + + /** + * including trailing single line comments if belonging to the class element. + * + * @param _Class $class + * @param _Element $element + */ + private function getLastTokenIndexOfClassElement(Tokens $tokens, array $class, array $element, TokensAnalyzer $tokensAnalyzer): int + { + // find last token of the element + if ('method' === $element['type'] && !$tokens[$class['index']]->isGivenKind(T_INTERFACE)) { + $attributes = $tokensAnalyzer->getMethodAttributes($element['index']); + + if (true === $attributes['abstract']) { + $elementEndIndex = $tokens->getNextTokenOfKind($element['index'], [';']); + } else { + $elementEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $tokens->getNextTokenOfKind($element['index'], ['{'])); + } + } elseif ('trait_import' === $element['type']) { + $elementEndIndex = $element['index']; + + do { + $elementEndIndex = $tokens->getNextMeaningfulToken($elementEndIndex); + } while ($tokens[$elementEndIndex]->isGivenKind([T_STRING, T_NS_SEPARATOR]) || $tokens[$elementEndIndex]->equals(',')); + + if (!$tokens[$elementEndIndex]->equals(';')) { + $elementEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $tokens->getNextTokenOfKind($element['index'], ['{'])); + } + } else { // 'const', 'property', enum-'case', or 'method' of an interface + $elementEndIndex = $tokens->getNextTokenOfKind($element['index'], [';']); + } + + $singleLineElement = true; + + for ($i = $element['index'] + 1; $i < $elementEndIndex; ++$i) { + if (str_contains($tokens[$i]->getContent(), "\n")) { + $singleLineElement = false; + + break; + } + } + + if ($singleLineElement) { + while (true) { + $nextToken = $tokens[$elementEndIndex + 1]; + + if (($nextToken->isComment() || $nextToken->isWhitespace()) && !str_contains($nextToken->getContent(), "\n")) { + ++$elementEndIndex; + } else { + break; + } + } + + if ($tokens[$elementEndIndex]->isWhitespace()) { + $elementEndIndex = $tokens->getPrevNonWhitespace($elementEndIndex); + } + } + + return $elementEndIndex; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/ClassDefinitionFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/ClassDefinitionFixer.php new file mode 100644 index 00000000..1ca054e5 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/ClassDefinitionFixer.php @@ -0,0 +1,551 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ClassNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * Fixer for part of the rules defined in PSR2 ¶4.1 Extends and Implements and PSR12 ¶8. Anonymous Classes. + * + * @phpstan-type _ClassExtendsInfo array{start: int, numberOfExtends: int, multiLine: bool} + * @phpstan-type _ClassImplementsInfo array{start: int, numberOfImplements: int, multiLine: bool} + */ +final class ClassDefinitionFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Whitespace around the keywords of a class, trait, enum or interfaces definition should be one space.', + [ + new CodeSample( + ' true] + ), + new CodeSample( + ' true] + ), + new CodeSample( + ' true] + ), + new CodeSample( + ' true] + ), + new CodeSample( + " true] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before BracesFixer, SingleLineEmptyBodyFixer. + * Must run after NewWithBracesFixer, NewWithParenthesesFixer. + */ + public function getPriority(): int + { + return 36; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds()); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + // -4, one for count to index, 3 because min. of tokens for a classy location. + for ($index = $tokens->getSize() - 4; $index > 0; --$index) { + if ($tokens[$index]->isClassy()) { + $this->fixClassyDefinition($tokens, $index); + } + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('multi_line_extends_each_single_line', 'Whether definitions should be multiline.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + (new FixerOptionBuilder('single_item_single_line', 'Whether definitions should be single line when including a single item.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + (new FixerOptionBuilder('single_line', 'Whether definitions should be single line.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + (new FixerOptionBuilder('space_before_parenthesis', 'Whether there should be a single space after the parenthesis of anonymous class (PSR12) or not.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + (new FixerOptionBuilder('inline_constructor_arguments', 'Whether constructor argument list in anonymous classes should be single line.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), + ]); + } + + /** + * @param int $classyIndex Class definition token start index + */ + private function fixClassyDefinition(Tokens $tokens, int $classyIndex): void + { + $classDefInfo = $this->getClassyDefinitionInfo($tokens, $classyIndex); + + // PSR2 4.1 Lists of implements MAY be split across multiple lines, where each subsequent line is indented once. + // When doing so, the first item in the list MUST be on the next line, and there MUST be only one interface per line. + + if (false !== $classDefInfo['implements']) { + $classDefInfo['implements'] = $this->fixClassyDefinitionImplements( + $tokens, + $classDefInfo['open'], + $classDefInfo['implements'] + ); + } + + if (false !== $classDefInfo['extends']) { + $classDefInfo['extends'] = $this->fixClassyDefinitionExtends( + $tokens, + false === $classDefInfo['implements'] ? $classDefInfo['open'] : $classDefInfo['implements']['start'], + $classDefInfo['extends'] + ); + } + + // PSR2: class definition open curly brace must go on a new line. + // PSR12: anonymous class curly brace on same line if not multi line implements. + + $classDefInfo['open'] = $this->fixClassyDefinitionOpenSpacing($tokens, $classDefInfo); + + if ($classDefInfo['implements']) { + $end = $classDefInfo['implements']['start']; + } elseif ($classDefInfo['extends']) { + $end = $classDefInfo['extends']['start']; + } else { + $end = $tokens->getPrevNonWhitespace($classDefInfo['open']); + } + + if ($classDefInfo['anonymousClass'] && false === $this->configuration['inline_constructor_arguments']) { + if (!$tokens[$end]->equals(')')) { // anonymous class with `extends` and/or `implements` + $start = $tokens->getPrevMeaningfulToken($end); + $this->makeClassyDefinitionSingleLine($tokens, $start, $end); + $end = $start; + } + + if ($tokens[$end]->equals(')')) { // skip constructor arguments of anonymous class + $end = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $end); + } + } + + // 4.1 The extends and implements keywords MUST be declared on the same line as the class name. + $this->makeClassyDefinitionSingleLine($tokens, $classDefInfo['start'], $end); + + $this->sortClassModifiers($tokens, $classDefInfo); + } + + /** + * @param _ClassExtendsInfo $classExtendsInfo + * + * @return _ClassExtendsInfo + */ + private function fixClassyDefinitionExtends(Tokens $tokens, int $classOpenIndex, array $classExtendsInfo): array + { + $endIndex = $tokens->getPrevNonWhitespace($classOpenIndex); + + if (true === $this->configuration['single_line'] || false === $classExtendsInfo['multiLine']) { + $this->makeClassyDefinitionSingleLine($tokens, $classExtendsInfo['start'], $endIndex); + $classExtendsInfo['multiLine'] = false; + } elseif (true === $this->configuration['single_item_single_line'] && 1 === $classExtendsInfo['numberOfExtends']) { + $this->makeClassyDefinitionSingleLine($tokens, $classExtendsInfo['start'], $endIndex); + $classExtendsInfo['multiLine'] = false; + } elseif (true === $this->configuration['multi_line_extends_each_single_line'] && $classExtendsInfo['multiLine']) { + $this->makeClassyInheritancePartMultiLine($tokens, $classExtendsInfo['start'], $endIndex); + $classExtendsInfo['multiLine'] = true; + } + + return $classExtendsInfo; + } + + /** + * @param _ClassImplementsInfo $classImplementsInfo + * + * @return _ClassImplementsInfo + */ + private function fixClassyDefinitionImplements(Tokens $tokens, int $classOpenIndex, array $classImplementsInfo): array + { + $endIndex = $tokens->getPrevNonWhitespace($classOpenIndex); + + if (true === $this->configuration['single_line'] || false === $classImplementsInfo['multiLine']) { + $this->makeClassyDefinitionSingleLine($tokens, $classImplementsInfo['start'], $endIndex); + $classImplementsInfo['multiLine'] = false; + } elseif (true === $this->configuration['single_item_single_line'] && 1 === $classImplementsInfo['numberOfImplements']) { + $this->makeClassyDefinitionSingleLine($tokens, $classImplementsInfo['start'], $endIndex); + $classImplementsInfo['multiLine'] = false; + } else { + $this->makeClassyInheritancePartMultiLine($tokens, $classImplementsInfo['start'], $endIndex); + $classImplementsInfo['multiLine'] = true; + } + + return $classImplementsInfo; + } + + /** + * @param array{ + * start: int, + * classy: int, + * open: int, + * extends: false|_ClassExtendsInfo, + * implements: false|_ClassImplementsInfo, + * anonymousClass: bool, + * final: false|int, + * abstract: false|int, + * readonly: false|int, + * } $classDefInfo + */ + private function fixClassyDefinitionOpenSpacing(Tokens $tokens, array $classDefInfo): int + { + if ($classDefInfo['anonymousClass']) { + if (false !== $classDefInfo['implements']) { + $spacing = $classDefInfo['implements']['multiLine'] ? $this->whitespacesConfig->getLineEnding() : ' '; + } elseif (false !== $classDefInfo['extends']) { + $spacing = $classDefInfo['extends']['multiLine'] ? $this->whitespacesConfig->getLineEnding() : ' '; + } else { + $spacing = ' '; + } + } else { + $spacing = $this->whitespacesConfig->getLineEnding(); + } + + $openIndex = $tokens->getNextTokenOfKind($classDefInfo['classy'], ['{']); + + if (' ' !== $spacing && str_contains($tokens[$openIndex - 1]->getContent(), "\n")) { + return $openIndex; + } + + if ($tokens[$openIndex - 1]->isWhitespace()) { + if (' ' !== $spacing || !$tokens[$tokens->getPrevNonWhitespace($openIndex - 1)]->isComment()) { + $tokens[$openIndex - 1] = new Token([T_WHITESPACE, $spacing]); + } + + return $openIndex; + } + + $tokens->insertAt($openIndex, new Token([T_WHITESPACE, $spacing])); + + return $openIndex + 1; + } + + /** + * @return array{ + * start: int, + * classy: int, + * open: int, + * extends: false|_ClassExtendsInfo, + * implements: false|_ClassImplementsInfo, + * anonymousClass: bool, + * final: false|int, + * abstract: false|int, + * readonly: false|int, + * } + */ + private function getClassyDefinitionInfo(Tokens $tokens, int $classyIndex): array + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + $openIndex = $tokens->getNextTokenOfKind($classyIndex, ['{']); + $def = [ + 'classy' => $classyIndex, + 'open' => $openIndex, + 'extends' => false, + 'implements' => false, + 'anonymousClass' => false, + 'final' => false, + 'abstract' => false, + 'readonly' => false, + ]; + + if (!$tokens[$classyIndex]->isGivenKind(T_TRAIT)) { + $extends = $tokens->findGivenKind(T_EXTENDS, $classyIndex, $openIndex); + $def['extends'] = [] !== $extends ? $this->getClassyInheritanceInfo($tokens, array_key_first($extends), 'numberOfExtends') : false; + + if (!$tokens[$classyIndex]->isGivenKind(T_INTERFACE)) { + $implements = $tokens->findGivenKind(T_IMPLEMENTS, $classyIndex, $openIndex); + $def['implements'] = [] !== $implements ? $this->getClassyInheritanceInfo($tokens, array_key_first($implements), 'numberOfImplements') : false; + $def['anonymousClass'] = $tokensAnalyzer->isAnonymousClass($classyIndex); + } + } + + if ($def['anonymousClass']) { + $startIndex = $tokens->getPrevTokenOfKind($classyIndex, [[T_NEW]]); // go to "new" for anonymous class + } else { + $modifiers = $tokensAnalyzer->getClassyModifiers($classyIndex); + $startIndex = $classyIndex; + + foreach (['final', 'abstract', 'readonly'] as $modifier) { + if (isset($modifiers[$modifier])) { + $def[$modifier] = $modifiers[$modifier]; + $startIndex = min($startIndex, $modifiers[$modifier]); + } else { + $def[$modifier] = false; + } + } + } + + $def['start'] = $startIndex; + + return $def; + } + + /** + * @return array|array{start: int, multiLine: bool} + */ + private function getClassyInheritanceInfo(Tokens $tokens, int $startIndex, string $label): array + { + $implementsInfo = ['start' => $startIndex, $label => 1, 'multiLine' => false]; + ++$startIndex; + $endIndex = $tokens->getNextTokenOfKind($startIndex, ['{', [T_IMPLEMENTS], [T_EXTENDS]]); + $endIndex = $tokens[$endIndex]->equals('{') ? $tokens->getPrevNonWhitespace($endIndex) : $endIndex; + + for ($i = $startIndex; $i < $endIndex; ++$i) { + if ($tokens[$i]->equals(',')) { + ++$implementsInfo[$label]; + + continue; + } + + if (!$implementsInfo['multiLine'] && str_contains($tokens[$i]->getContent(), "\n")) { + $implementsInfo['multiLine'] = true; + } + } + + return $implementsInfo; + } + + private function makeClassyDefinitionSingleLine(Tokens $tokens, int $startIndex, int $endIndex): void + { + for ($i = $endIndex; $i >= $startIndex; --$i) { + if ($tokens[$i]->isWhitespace()) { + if (str_contains($tokens[$i]->getContent(), "\n")) { + if (\defined('T_ATTRIBUTE')) { // @TODO: drop condition and else when PHP 8.0+ is required + if ($tokens[$i - 1]->isGivenKind(CT::T_ATTRIBUTE_CLOSE) || $tokens[$i + 1]->isGivenKind(T_ATTRIBUTE)) { + continue; + } + } else { + if (($tokens[$i - 1]->isComment() && str_ends_with($tokens[$i - 1]->getContent(), ']')) + || ($tokens[$i + 1]->isComment() && str_starts_with($tokens[$i + 1]->getContent(), '#[')) + ) { + continue; + } + } + + if ($tokens[$i - 1]->isGivenKind(T_DOC_COMMENT) || $tokens[$i + 1]->isGivenKind(T_DOC_COMMENT)) { + continue; + } + } + + if ($tokens[$i - 1]->isComment()) { + $content = $tokens[$i - 1]->getContent(); + if (!str_starts_with($content, '//') && !str_starts_with($content, '#')) { + $tokens[$i] = new Token([T_WHITESPACE, ' ']); + } + + continue; + } + + if ($tokens[$i + 1]->isComment()) { + $content = $tokens[$i + 1]->getContent(); + if (!str_starts_with($content, '//')) { + $tokens[$i] = new Token([T_WHITESPACE, ' ']); + } + + continue; + } + + if ($tokens[$i - 1]->isGivenKind(T_CLASS) && $tokens[$i + 1]->equals('(')) { + if (true === $this->configuration['space_before_parenthesis']) { + $tokens[$i] = new Token([T_WHITESPACE, ' ']); + } else { + $tokens->clearAt($i); + } + + continue; + } + + if (!$tokens[$i - 1]->equals(',') && $tokens[$i + 1]->equalsAny([',', ')']) || $tokens[$i - 1]->equals('(')) { + $tokens->clearAt($i); + + continue; + } + + $tokens[$i] = new Token([T_WHITESPACE, ' ']); + + continue; + } + + if ($tokens[$i]->equals(',') && !$tokens[$i + 1]->isWhitespace()) { + $tokens->insertAt($i + 1, new Token([T_WHITESPACE, ' '])); + + continue; + } + + if (true === $this->configuration['space_before_parenthesis'] && $tokens[$i]->isGivenKind(T_CLASS) && !$tokens[$i + 1]->isWhitespace()) { + $tokens->insertAt($i + 1, new Token([T_WHITESPACE, ' '])); + + continue; + } + + if (!$tokens[$i]->isComment()) { + continue; + } + + if (!$tokens[$i + 1]->isWhitespace() && !$tokens[$i + 1]->isComment() && !str_contains($tokens[$i]->getContent(), "\n")) { + $tokens->insertAt($i + 1, new Token([T_WHITESPACE, ' '])); + } + + if (!$tokens[$i - 1]->isWhitespace() && !$tokens[$i - 1]->isComment()) { + $tokens->insertAt($i, new Token([T_WHITESPACE, ' '])); + } + } + } + + private function makeClassyInheritancePartMultiLine(Tokens $tokens, int $startIndex, int $endIndex): void + { + for ($i = $endIndex; $i > $startIndex; --$i) { + $previousInterfaceImplementingIndex = $tokens->getPrevTokenOfKind($i, [',', [T_IMPLEMENTS], [T_EXTENDS]]); + $breakAtIndex = $tokens->getNextMeaningfulToken($previousInterfaceImplementingIndex); + + // make the part of a ',' or 'implements' single line + $this->makeClassyDefinitionSingleLine( + $tokens, + $breakAtIndex, + $i + ); + + // make sure the part is on its own line + $isOnOwnLine = false; + + for ($j = $breakAtIndex; $j > $previousInterfaceImplementingIndex; --$j) { + if (str_contains($tokens[$j]->getContent(), "\n")) { + $isOnOwnLine = true; + + break; + } + } + + if (!$isOnOwnLine) { + if ($tokens[$breakAtIndex - 1]->isWhitespace()) { + $tokens[$breakAtIndex - 1] = new Token([ + T_WHITESPACE, + $this->whitespacesConfig->getLineEnding().$this->whitespacesConfig->getIndent(), + ]); + } else { + $tokens->insertAt($breakAtIndex, new Token([T_WHITESPACE, $this->whitespacesConfig->getLineEnding().$this->whitespacesConfig->getIndent()])); + } + } + + $i = $previousInterfaceImplementingIndex + 1; + } + } + + /** + * @param array{ + * final: false|int, + * abstract: false|int, + * readonly: false|int, + * } $classDefInfo + */ + private function sortClassModifiers(Tokens $tokens, array $classDefInfo): void + { + if (false === $classDefInfo['readonly']) { + return; + } + + $readonlyIndex = $classDefInfo['readonly']; + + foreach (['final', 'abstract'] as $accessModifier) { + if (false === $classDefInfo[$accessModifier] || $classDefInfo[$accessModifier] < $readonlyIndex) { + continue; + } + + $accessModifierIndex = $classDefInfo[$accessModifier]; + + /** @var Token $readonlyToken */ + $readonlyToken = clone $tokens[$readonlyIndex]; + + /** @var Token $accessToken */ + $accessToken = clone $tokens[$accessModifierIndex]; + + $tokens[$readonlyIndex] = $accessToken; + $tokens[$accessModifierIndex] = $readonlyToken; + + break; + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalClassFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalClassFixer.php new file mode 100644 index 00000000..e13cc8f4 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalClassFixer.php @@ -0,0 +1,67 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ClassNotation; + +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; + +/** + * @author Filippo Tessarotto + */ +final class FinalClassFixer extends AbstractProxyFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'All classes must be final, except abstract ones and Doctrine entities.', + [ + new CodeSample( + 'configure([ + 'include' => [], + 'consider_absent_docblock_as_internal_class' => true, + ]); + + return [$fixer]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalInternalClassFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalInternalClassFixer.php new file mode 100644 index 00000000..cfd45f02 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalInternalClassFixer.php @@ -0,0 +1,356 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ClassNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; +use PhpCsFixer\Utils; +use Symfony\Component\OptionsResolver\Options; + +/** + * @author Dariusz Rumiński + */ +final class FinalInternalClassFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + private const DEFAULTS = [ + 'include' => [ + 'internal', + ], + 'exclude' => [ + 'final', + 'Entity', + 'ORM\Entity', + 'ORM\Mapping\Entity', + 'Mapping\Entity', + 'Document', + 'ODM\Document', + ], + ]; + + private bool $checkAttributes; + + public function __construct() + { + parent::__construct(); + + $this->checkAttributes = \PHP_VERSION_ID >= 8_00_00; + } + + public function configure(array $configuration): void + { + parent::configure($configuration); + + $this->assertConfigHasNoConflicts(); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Internal classes should be `final`.', + [ + new CodeSample(" ['@Custom'], + 'exclude' => ['@not-fix'], + ] + ), + ], + null, + 'Changing classes to `final` might cause code execution to break.' + ); + } + + /** + * {@inheritdoc} + * + * Must run before ProtectedToPrivateFixer, SelfStaticAccessorFixer. + * Must run after PhpUnitInternalClassFixer. + */ + public function getPriority(): int + { + return 67; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_CLASS); + } + + public function isRisky(): bool + { + return true; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + + for ($index = $tokens->count() - 1; 0 <= $index; --$index) { + if (!$tokens[$index]->isGivenKind(T_CLASS) || !$this->isClassCandidate($tokensAnalyzer, $tokens, $index)) { + continue; + } + + // make class 'final' + $tokens->insertSlices([ + $index => [ + new Token([T_FINAL, 'final']), + new Token([T_WHITESPACE, ' ']), + ], + ]); + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + $annotationsAsserts = [static function (array $values): bool { + foreach ($values as $value) { + if (!\is_string($value) || '' === $value) { + return false; + } + } + + return true; + }]; + + $annotationsNormalizer = static function (Options $options, array $value): array { + $newValue = []; + foreach ($value as $key) { + if (str_starts_with($key, '@')) { + $key = substr($key, 1); + } + + $newValue[strtolower($key)] = true; + } + + return $newValue; + }; + + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('annotation_include', 'Class level attribute or annotation tags that must be set in order to fix the class (case insensitive).')) + ->setAllowedTypes(['array']) + ->setAllowedValues($annotationsAsserts) + ->setDefault( + array_map( + static fn (string $string) => '@'.$string, + self::DEFAULTS['include'], + ), + ) + ->setNormalizer($annotationsNormalizer) + ->setDeprecationMessage('Use `include` to configure PHPDoc annotations tags and attributes.') + ->getOption(), + (new FixerOptionBuilder('annotation_exclude', 'Class level attribute or annotation tags that must be omitted to fix the class, even if all of the white list ones are used as well (case insensitive).')) + ->setAllowedTypes(['array']) + ->setAllowedValues($annotationsAsserts) + ->setDefault( + array_map( + static fn (string $string) => '@'.$string, + self::DEFAULTS['exclude'], + ), + ) + ->setNormalizer($annotationsNormalizer) + ->setDeprecationMessage('Use `exclude` to configure PHPDoc annotations tags and attributes.') + ->getOption(), + (new FixerOptionBuilder('include', 'Class level attribute or annotation tags that must be set in order to fix the class (case insensitive).')) + ->setAllowedTypes(['array']) + ->setAllowedValues($annotationsAsserts) + ->setDefault(self::DEFAULTS['include']) + ->setNormalizer($annotationsNormalizer) + ->getOption(), + (new FixerOptionBuilder('exclude', 'Class level attribute or annotation tags that must be omitted to fix the class, even if all of the white list ones are used as well (case insensitive).')) + ->setAllowedTypes(['array']) + ->setAllowedValues($annotationsAsserts) + ->setDefault(self::DEFAULTS['exclude']) + ->setNormalizer($annotationsNormalizer) + ->getOption(), + (new FixerOptionBuilder('consider_absent_docblock_as_internal_class', 'Whether classes without any DocBlock should be fixed to final.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + + /** + * @param int $index T_CLASS index + */ + private function isClassCandidate(TokensAnalyzer $tokensAnalyzer, Tokens $tokens, int $index): bool + { + if ($tokensAnalyzer->isAnonymousClass($index)) { + return false; + } + + $modifiers = $tokensAnalyzer->getClassyModifiers($index); + + if (isset($modifiers['final']) || isset($modifiers['abstract'])) { + return false; // ignore class; it is abstract or already final + } + + $decisions = []; + $currentIndex = $index; + + $acceptTypes = [ + CT::T_ATTRIBUTE_CLOSE, + T_DOC_COMMENT, + T_COMMENT, // Skip comments + ]; + + if (\defined('T_READONLY')) { + // Skip readonly classes for PHP 8.2+ + $acceptTypes[] = T_READONLY; + } + + while ($currentIndex) { + $currentIndex = $tokens->getPrevNonWhitespace($currentIndex); + + if (!$tokens[$currentIndex]->isGivenKind($acceptTypes)) { + break; + } + + if ($this->checkAttributes && $tokens[$currentIndex]->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) { + $attributeStartIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_ATTRIBUTE, $currentIndex); + $decisions[] = $this->isClassCandidateBasedOnAttribute($tokens, $attributeStartIndex, $currentIndex); + + $currentIndex = $attributeStartIndex; + } + + if ($tokens[$currentIndex]->isGivenKind([T_DOC_COMMENT])) { + $decisions[] = $this->isClassCandidateBasedOnPhpDoc($tokens, $currentIndex); + } + } + + if (\in_array(false, $decisions, true)) { + return false; + } + + return \in_array(true, $decisions, true) + || ([] === $decisions && true === $this->configuration['consider_absent_docblock_as_internal_class']); + } + + private function isClassCandidateBasedOnPhpDoc(Tokens $tokens, int $index): ?bool + { + $doc = new DocBlock($tokens[$index]->getContent()); + $tags = []; + + foreach ($doc->getAnnotations() as $annotation) { + if (!Preg::match('/@([^\(\s]+)/', $annotation->getContent(), $matches)) { + continue; + } + $tag = strtolower(substr(array_shift($matches), 1)); + + $tags[$tag] = true; + } + + if (\count(array_intersect_key($this->configuration['exclude'], $tags)) > 0) { + return false; + } + + if ($this->isConfiguredAsInclude($tags)) { + return true; + } + + return null; + } + + private function isClassCandidateBasedOnAttribute(Tokens $tokens, int $startIndex, int $endIndex): ?bool + { + $attributeCandidates = []; + $attributeString = ''; + $currentIndex = $startIndex; + + while ($currentIndex < $endIndex && null !== ($currentIndex = $tokens->getNextMeaningfulToken($currentIndex))) { + if (!$tokens[$currentIndex]->isGivenKind([T_STRING, T_NS_SEPARATOR])) { + if ('' !== $attributeString) { + $attributeCandidates[$attributeString] = true; + $attributeString = ''; + } + + continue; + } + + $attributeString .= strtolower($tokens[$currentIndex]->getContent()); + } + + if (\count(array_intersect_key($this->configuration['exclude'], $attributeCandidates)) > 0) { + return false; + } + + if ($this->isConfiguredAsInclude($attributeCandidates)) { + return true; + } + + return null; + } + + /** + * @param array $attributes + */ + private function isConfiguredAsInclude(array $attributes): bool + { + if (0 === \count($this->configuration['include'])) { + return true; + } + + return \count(array_intersect_key($this->configuration['include'], $attributes)) > 0; + } + + private function assertConfigHasNoConflicts(): void + { + foreach (['include', 'exclude'] as $newConfigKey) { + $oldConfigKey = 'annotation_'.$newConfigKey; + $defaults = []; + + foreach (self::DEFAULTS[$newConfigKey] as $foo) { + $defaults[strtolower($foo)] = true; + } + + $newConfigIsSet = $this->configuration[$newConfigKey] !== $defaults; + $oldConfigIsSet = $this->configuration[$oldConfigKey] !== $defaults; + + if ($newConfigIsSet && $oldConfigIsSet) { + throw new InvalidFixerConfigurationException($this->getName(), sprintf('Configuration cannot contain deprecated option "%s" and new option "%s".', $oldConfigKey, $newConfigKey)); + } + + if ($oldConfigIsSet) { + $this->configuration[$newConfigKey] = $this->configuration[$oldConfigKey]; + $this->checkAttributes = false; // run in old mode + } + + // if ($newConfigIsSet) - only new config is set, all good + // if (!$newConfigIsSet && !$oldConfigIsSet) - both are set as to default values, all good + + unset($this->configuration[$oldConfigKey]); + } + + $intersect = array_intersect_assoc($this->configuration['include'], $this->configuration['exclude']); + + if (\count($intersect) > 0) { + throw new InvalidFixerConfigurationException($this->getName(), sprintf('Annotation cannot be used in both "include" and "exclude" list, got duplicates: %s.', Utils::naturalLanguageJoin(array_keys($intersect)))); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalPublicMethodForAbstractClassFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalPublicMethodForAbstractClassFixer.php new file mode 100644 index 00000000..ed9a7ac7 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalPublicMethodForAbstractClassFixer.php @@ -0,0 +1,160 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ClassNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Filippo Tessarotto + */ +final class FinalPublicMethodForAbstractClassFixer extends AbstractFixer +{ + /** + * @var array + */ + private array $magicMethods = [ + '__construct' => true, + '__destruct' => true, + '__call' => true, + '__callstatic' => true, + '__get' => true, + '__set' => true, + '__isset' => true, + '__unset' => true, + '__sleep' => true, + '__wakeup' => true, + '__tostring' => true, + '__invoke' => true, + '__set_state' => true, + '__clone' => true, + '__debuginfo' => true, + ]; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'All `public` methods of `abstract` classes should be `final`.', + [ + new CodeSample( + 'isAllTokenKindsFound([T_ABSTRACT, T_PUBLIC, T_FUNCTION]); + } + + public function isRisky(): bool + { + return true; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $abstracts = array_keys($tokens->findGivenKind(T_ABSTRACT)); + + while ($abstractIndex = array_pop($abstracts)) { + $classIndex = $tokens->getNextTokenOfKind($abstractIndex, [[T_CLASS], [T_FUNCTION]]); + if (!$tokens[$classIndex]->isGivenKind(T_CLASS)) { + continue; + } + + $classOpen = $tokens->getNextTokenOfKind($classIndex, ['{']); + $classClose = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classOpen); + + $this->fixClass($tokens, $classOpen, $classClose); + } + } + + private function fixClass(Tokens $tokens, int $classOpenIndex, int $classCloseIndex): void + { + for ($index = $classCloseIndex - 1; $index > $classOpenIndex; --$index) { + // skip method contents + if ($tokens[$index]->equals('}')) { + $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); + + continue; + } + + // skip non public methods + if (!$tokens[$index]->isGivenKind(T_PUBLIC)) { + continue; + } + + $nextIndex = $tokens->getNextMeaningfulToken($index); + $nextToken = $tokens[$nextIndex]; + + if ($nextToken->isGivenKind(T_STATIC)) { + $nextIndex = $tokens->getNextMeaningfulToken($nextIndex); + $nextToken = $tokens[$nextIndex]; + } + + // skip uses, attributes, constants etc + if (!$nextToken->isGivenKind(T_FUNCTION)) { + continue; + } + + $nextIndex = $tokens->getNextMeaningfulToken($nextIndex); + $nextToken = $tokens[$nextIndex]; + + // skip magic methods + if (isset($this->magicMethods[strtolower($nextToken->getContent())])) { + continue; + } + + $prevIndex = $tokens->getPrevMeaningfulToken($index); + $prevToken = $tokens[$prevIndex]; + + if ($prevToken->isGivenKind(T_STATIC)) { + $index = $prevIndex; + $prevIndex = $tokens->getPrevMeaningfulToken($index); + $prevToken = $tokens[$prevIndex]; + } + + // skip abstract or already final methods + if ($prevToken->isGivenKind([T_ABSTRACT, T_FINAL])) { + $index = $prevIndex; + + continue; + } + + $tokens->insertAt( + $index, + [ + new Token([T_FINAL, 'final']), + new Token([T_WHITESPACE, ' ']), + ] + ); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoBlankLinesAfterClassOpeningFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoBlankLinesAfterClassOpeningFixer.php new file mode 100644 index 00000000..b09c2a2b --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoBlankLinesAfterClassOpeningFixer.php @@ -0,0 +1,93 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ClassNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Ceeram + */ +final class NoBlankLinesAfterClassOpeningFixer extends AbstractFixer implements WhitespacesAwareFixerInterface +{ + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds()); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'There should be no empty lines after class opening brace.', + [ + new CodeSample( + ' $token) { + if (!$token->isClassy()) { + continue; + } + + $startBraceIndex = $tokens->getNextTokenOfKind($index, ['{']); + if (!$tokens[$startBraceIndex + 1]->isWhitespace()) { + continue; + } + + $this->fixWhitespace($tokens, $startBraceIndex + 1); + } + } + + /** + * Cleanup a whitespace token. + */ + private function fixWhitespace(Tokens $tokens, int $index): void + { + $content = $tokens[$index]->getContent(); + // if there is more than one new line in the whitespace, then we need to fix it + if (substr_count($content, "\n") > 1) { + // the final bit of the whitespace must be the next statement's indentation + $tokens[$index] = new Token([T_WHITESPACE, $this->whitespacesConfig->getLineEnding().substr($content, strrpos($content, "\n") + 1)]); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoNullPropertyInitializationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoNullPropertyInitializationFixer.php new file mode 100644 index 00000000..b08a9231 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoNullPropertyInitializationFixer.php @@ -0,0 +1,141 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ClassNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author ntzm + */ +final class NoNullPropertyInitializationFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Properties MUST not be explicitly initialized with `null` except when they have a type declaration (PHP 7.4).', + [ + new CodeSample( + 'isAnyTokenKindsFound([T_CLASS, T_TRAIT]) && $tokens->isAnyTokenKindsFound([T_PUBLIC, T_PROTECTED, T_PRIVATE, T_VAR, T_STATIC]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $inClass = []; + $classLevel = 0; + + for ($index = 0, $count = $tokens->count(); $index < $count; ++$index) { + if ($tokens[$index]->isGivenKind([T_CLASS, T_TRAIT])) { // Enums and interfaces do not have properties + ++$classLevel; + $inClass[$classLevel] = 1; + + $index = $tokens->getNextTokenOfKind($index, ['{']); + + continue; + } + + if (0 === $classLevel) { + continue; + } + + if ($tokens[$index]->equals('{')) { + ++$inClass[$classLevel]; + + continue; + } + + if ($tokens[$index]->equals('}')) { + --$inClass[$classLevel]; + + if (0 === $inClass[$classLevel]) { + unset($inClass[$classLevel]); + --$classLevel; + } + + continue; + } + + // Ensure we are in a class but not in a method in case there are static variables defined + if (1 !== $inClass[$classLevel]) { + continue; + } + + if (!$tokens[$index]->isGivenKind([T_PUBLIC, T_PROTECTED, T_PRIVATE, T_VAR, T_STATIC])) { + continue; + } + + while (true) { + $varTokenIndex = $index = $tokens->getNextMeaningfulToken($index); + + if ($tokens[$index]->isGivenKind(T_STATIC)) { + $varTokenIndex = $index = $tokens->getNextMeaningfulToken($index); + } + + if (!$tokens[$index]->isGivenKind(T_VARIABLE)) { + break; + } + + $index = $tokens->getNextMeaningfulToken($index); + + if ($tokens[$index]->equals('=')) { + $index = $tokens->getNextMeaningfulToken($index); + + if ($tokens[$index]->isGivenKind(T_NS_SEPARATOR)) { + $index = $tokens->getNextMeaningfulToken($index); + } + + if ($tokens[$index]->equals([T_STRING, 'null'], false)) { + for ($i = $varTokenIndex + 1; $i <= $index; ++$i) { + if ( + !($tokens[$i]->isWhitespace() && str_contains($tokens[$i]->getContent(), "\n")) + && !$tokens[$i]->isComment() + ) { + $tokens->clearAt($i); + } + } + } + + ++$index; + } + + if (!$tokens[$index]->equals(',')) { + break; + } + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoPhp4ConstructorFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoPhp4ConstructorFixer.php new file mode 100644 index 00000000..3fe8e98c --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoPhp4ConstructorFixer.php @@ -0,0 +1,408 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ClassNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Matteo Beccati + */ +final class NoPhp4ConstructorFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Convert PHP4-style constructors to `__construct`.', + [ + new CodeSample('isTokenKindFound(T_CLASS); + } + + public function isRisky(): bool + { + return true; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + $classes = array_keys($tokens->findGivenKind(T_CLASS)); + $numClasses = \count($classes); + + for ($i = 0; $i < $numClasses; ++$i) { + $index = $classes[$i]; + + // is it an anonymous class definition? + if ($tokensAnalyzer->isAnonymousClass($index)) { + continue; + } + + // is it inside a namespace? + $nspIndex = $tokens->getPrevTokenOfKind($index, [[T_NAMESPACE, 'namespace']]); + + if (null !== $nspIndex) { + $nspIndex = $tokens->getNextMeaningfulToken($nspIndex); + + // make sure it's not the global namespace, as PHP4 constructors are allowed in there + if (!$tokens[$nspIndex]->equals('{')) { + // unless it's the global namespace, the index currently points to the name + $nspIndex = $tokens->getNextTokenOfKind($nspIndex, [';', '{']); + + if ($tokens[$nspIndex]->equals(';')) { + // the class is inside a (non-block) namespace, no PHP4-code should be in there + break; + } + + // the index points to the { of a block-namespace + $nspEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $nspIndex); + + if ($index < $nspEnd) { + // the class is inside a block namespace, skip other classes that might be in it + for ($j = $i + 1; $j < $numClasses; ++$j) { + if ($classes[$j] < $nspEnd) { + ++$i; + } + } + + // and continue checking the classes that might follow + continue; + } + } + } + + $classNameIndex = $tokens->getNextMeaningfulToken($index); + $className = $tokens[$classNameIndex]->getContent(); + $classStart = $tokens->getNextTokenOfKind($classNameIndex, ['{']); + $classEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classStart); + + $this->fixConstructor($tokens, $className, $classStart, $classEnd); + $this->fixParent($tokens, $classStart, $classEnd); + } + } + + /** + * Fix constructor within a class, if possible. + * + * @param Tokens $tokens the Tokens instance + * @param string $className the class name + * @param int $classStart the class start index + * @param int $classEnd the class end index + */ + private function fixConstructor(Tokens $tokens, string $className, int $classStart, int $classEnd): void + { + $php4 = $this->findFunction($tokens, $className, $classStart, $classEnd); + + if (null === $php4) { + return; // no PHP4-constructor! + } + + if (isset($php4['modifiers'][T_ABSTRACT]) || isset($php4['modifiers'][T_STATIC])) { + return; // PHP4 constructor can't be abstract or static + } + + $php5 = $this->findFunction($tokens, '__construct', $classStart, $classEnd); + + if (null === $php5) { + // no PHP5-constructor, we can rename the old one to __construct + $tokens[$php4['nameIndex']] = new Token([T_STRING, '__construct']); + + // in some (rare) cases we might have just created an infinite recursion issue + $this->fixInfiniteRecursion($tokens, $php4['bodyIndex'], $php4['endIndex']); + + return; + } + + // does the PHP4-constructor only call $this->__construct($args, ...)? + [$sequences, $case] = $this->getWrapperMethodSequence($tokens, '__construct', $php4['startIndex'], $php4['bodyIndex']); + + foreach ($sequences as $seq) { + if (null !== $tokens->findSequence($seq, $php4['bodyIndex'] - 1, $php4['endIndex'], $case)) { + // good, delete it! + for ($i = $php4['startIndex']; $i <= $php4['endIndex']; ++$i) { + $tokens->clearAt($i); + } + + return; + } + } + + // does __construct only call the PHP4-constructor (with the same args)? + [$sequences, $case] = $this->getWrapperMethodSequence($tokens, $className, $php4['startIndex'], $php4['bodyIndex']); + + foreach ($sequences as $seq) { + if (null !== $tokens->findSequence($seq, $php5['bodyIndex'] - 1, $php5['endIndex'], $case)) { + // that was a weird choice, but we can safely delete it and... + for ($i = $php5['startIndex']; $i <= $php5['endIndex']; ++$i) { + $tokens->clearAt($i); + } + + // rename the PHP4 one to __construct + $tokens[$php4['nameIndex']] = new Token([T_STRING, '__construct']); + + return; + } + } + } + + /** + * Fix calls to the parent constructor within a class. + * + * @param Tokens $tokens the Tokens instance + * @param int $classStart the class start index + * @param int $classEnd the class end index + */ + private function fixParent(Tokens $tokens, int $classStart, int $classEnd): void + { + // check calls to the parent constructor + foreach ($tokens->findGivenKind(T_EXTENDS) as $index => $token) { + $parentIndex = $tokens->getNextMeaningfulToken($index); + $parentClass = $tokens[$parentIndex]->getContent(); + + // using parent::ParentClassName() or ParentClassName::ParentClassName() + $parentSeq = $tokens->findSequence([ + [T_STRING], + [T_DOUBLE_COLON], + [T_STRING, $parentClass], + '(', + ], $classStart, $classEnd, [2 => false]); + + if (null !== $parentSeq) { + // we only need indices + $parentSeq = array_keys($parentSeq); + + // match either of the possibilities + if ($tokens[$parentSeq[0]]->equalsAny([[T_STRING, 'parent'], [T_STRING, $parentClass]], false)) { + // replace with parent::__construct + $tokens[$parentSeq[0]] = new Token([T_STRING, 'parent']); + $tokens[$parentSeq[2]] = new Token([T_STRING, '__construct']); + } + } + + foreach (Token::getObjectOperatorKinds() as $objectOperatorKind) { + // using $this->ParentClassName() + $parentSeq = $tokens->findSequence([ + [T_VARIABLE, '$this'], + [$objectOperatorKind], + [T_STRING, $parentClass], + '(', + ], $classStart, $classEnd, [2 => false]); + + if (null !== $parentSeq) { + // we only need indices + $parentSeq = array_keys($parentSeq); + + // replace call with parent::__construct() + $tokens[$parentSeq[0]] = new Token([ + T_STRING, + 'parent', + ]); + $tokens[$parentSeq[1]] = new Token([ + T_DOUBLE_COLON, + '::', + ]); + $tokens[$parentSeq[2]] = new Token([T_STRING, '__construct']); + } + } + } + } + + /** + * Fix a particular infinite recursion issue happening when the parent class has __construct and the child has only + * a PHP4 constructor that calls the parent constructor as $this->__construct(). + * + * @param Tokens $tokens the Tokens instance + * @param int $start the PHP4 constructor body start + * @param int $end the PHP4 constructor body end + */ + private function fixInfiniteRecursion(Tokens $tokens, int $start, int $end): void + { + foreach (Token::getObjectOperatorKinds() as $objectOperatorKind) { + $seq = [ + [T_VARIABLE, '$this'], + [$objectOperatorKind], + [T_STRING, '__construct'], + ]; + + while (true) { + $callSeq = $tokens->findSequence($seq, $start, $end, [2 => false]); + + if (null === $callSeq) { + return; + } + + $callSeq = array_keys($callSeq); + + $tokens[$callSeq[0]] = new Token([T_STRING, 'parent']); + $tokens[$callSeq[1]] = new Token([T_DOUBLE_COLON, '::']); + } + } + } + + /** + * Generate the sequence of tokens necessary for the body of a wrapper method that simply + * calls $this->{$method}( [args...] ) with the same arguments as its own signature. + * + * @param Tokens $tokens the Tokens instance + * @param string $method the wrapped method name + * @param int $startIndex function/method start index + * @param int $bodyIndex function/method body index + * + * @return array{array>, array{3: false}} + */ + private function getWrapperMethodSequence(Tokens $tokens, string $method, int $startIndex, int $bodyIndex): array + { + $sequences = []; + + foreach (Token::getObjectOperatorKinds() as $objectOperatorKind) { + // initialise sequence as { $this->{$method}( + $seq = [ + '{', + [T_VARIABLE, '$this'], + [$objectOperatorKind], + [T_STRING, $method], + '(', + ]; + + // parse method parameters, if any + $index = $startIndex; + + while (true) { + // find the next variable name + $index = $tokens->getNextTokenOfKind($index, [[T_VARIABLE]]); + + if (null === $index || $index >= $bodyIndex) { + // we've reached the body already + break; + } + + // append a comma if it's not the first variable + if (\count($seq) > 5) { + $seq[] = ','; + } + + // append variable name to the sequence + $seq[] = [T_VARIABLE, $tokens[$index]->getContent()]; + } + + // almost done, close the sequence with ); } + $seq[] = ')'; + $seq[] = ';'; + $seq[] = '}'; + + $sequences[] = $seq; + } + + return [$sequences, [3 => false]]; + } + + /** + * Find a function or method matching a given name within certain bounds. + * + * Returns: + * - nameIndex (int): The index of the function/method name. + * - startIndex (int): The index of the function/method start. + * - endIndex (int): The index of the function/method end. + * - bodyIndex (int): The index of the function/method body. + * - modifiers (array): The modifiers as array keys and their index as the values, e.g. array(T_PUBLIC => 10) + * + * @param Tokens $tokens the Tokens instance + * @param string $name the function/Method name + * @param int $startIndex the search start index + * @param int $endIndex the search end index + * + * @return null|array{ + * nameIndex: int, + * startIndex: int, + * endIndex: int, + * bodyIndex: int, + * modifiers: list, + * } + */ + private function findFunction(Tokens $tokens, string $name, int $startIndex, int $endIndex): ?array + { + $function = $tokens->findSequence([ + [T_FUNCTION], + [T_STRING, $name], + '(', + ], $startIndex, $endIndex, false); + + if (null === $function) { + return null; + } + + // keep only the indices + $function = array_keys($function); + + // find previous block, saving method modifiers for later use + $possibleModifiers = [T_PUBLIC, T_PROTECTED, T_PRIVATE, T_STATIC, T_ABSTRACT, T_FINAL]; + $modifiers = []; + + $prevBlock = $tokens->getPrevMeaningfulToken($function[0]); + + while (null !== $prevBlock && $tokens[$prevBlock]->isGivenKind($possibleModifiers)) { + $modifiers[$tokens[$prevBlock]->getId()] = $prevBlock; + $prevBlock = $tokens->getPrevMeaningfulToken($prevBlock); + } + + if (isset($modifiers[T_ABSTRACT])) { + // abstract methods have no body + $bodyStart = null; + $funcEnd = $tokens->getNextTokenOfKind($function[2], [';']); + } else { + // find method body start and the end of the function definition + $bodyStart = $tokens->getNextTokenOfKind($function[2], ['{']); + $funcEnd = null !== $bodyStart ? $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $bodyStart) : null; + } + + return [ + 'nameIndex' => $function[1], + 'startIndex' => $prevBlock + 1, + 'endIndex' => $funcEnd, + 'bodyIndex' => $bodyStart, + 'modifiers' => $modifiers, + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoUnneededFinalMethodFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoUnneededFinalMethodFixer.php new file mode 100644 index 00000000..276bdaba --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoUnneededFinalMethodFixer.php @@ -0,0 +1,211 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ClassNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Filippo Tessarotto + */ +final class NoUnneededFinalMethodFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Removes `final` from methods where possible.', + [ + new CodeSample( + ' false] + ), + ], + null, + 'Risky when child class overrides a `private` method.' + ); + } + + public function isCandidate(Tokens $tokens): bool + { + if (!$tokens->isAllTokenKindsFound([T_FINAL, T_FUNCTION])) { + return false; + } + + if (\defined('T_ENUM') && $tokens->isTokenKindFound(T_ENUM)) { // @TODO: drop condition when PHP 8.1+ is required + return true; + } + + return $tokens->isTokenKindFound(T_CLASS); + } + + public function isRisky(): bool + { + return true; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($this->getMethods($tokens) as $element) { + $index = $element['method_final_index']; + + if ($element['method_of_enum'] || $element['class_is_final']) { + $this->clearFinal($tokens, $index); + + continue; + } + + if (!$element['method_is_private'] || false === $this->configuration['private_methods'] || $element['method_is_constructor']) { + continue; + } + + $this->clearFinal($tokens, $index); + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('private_methods', 'Private methods of non-`final` classes must not be declared `final`.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), + ]); + } + + /** + * @return \Generator + */ + private function getMethods(Tokens $tokens): \Generator + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + $modifierKinds = [T_PUBLIC, T_PROTECTED, T_PRIVATE, T_FINAL, T_ABSTRACT, T_STATIC]; + + $enums = []; + $classesAreFinal = []; + $elements = $tokensAnalyzer->getClassyElements(); + + for (end($elements);; prev($elements)) { + $index = key($elements); + + if (null === $index) { + break; + } + + $element = current($elements); + + if ('method' !== $element['type']) { + continue; // not a method + } + + $classIndex = $element['classIndex']; + + if (!\array_key_exists($classIndex, $enums)) { + $enums[$classIndex] = \defined('T_ENUM') && $tokens[$classIndex]->isGivenKind(T_ENUM); // @TODO: drop condition when PHP 8.1+ is required + } + + $element['method_final_index'] = null; + $element['method_is_private'] = false; + + $previous = $index; + + do { + $previous = $tokens->getPrevMeaningfulToken($previous); + + if ($tokens[$previous]->isGivenKind(T_PRIVATE)) { + $element['method_is_private'] = true; + } elseif ($tokens[$previous]->isGivenKind(T_FINAL)) { + $element['method_final_index'] = $previous; + } + } while ($tokens[$previous]->isGivenKind($modifierKinds)); + + if ($enums[$classIndex]) { + $element['method_of_enum'] = true; + + yield $element; + + continue; + } + + if (!\array_key_exists($classIndex, $classesAreFinal)) { + $modifiers = $tokensAnalyzer->getClassyModifiers($classIndex); + $classesAreFinal[$classIndex] = isset($modifiers['final']); + } + + $element['method_of_enum'] = false; + $element['class_is_final'] = $classesAreFinal[$classIndex]; + $element['method_is_constructor'] = '__construct' === strtolower($tokens[$tokens->getNextMeaningfulToken($index)]->getContent()); + + yield $element; + } + } + + private function clearFinal(Tokens $tokens, ?int $index): void + { + if (null === $index) { + return; + } + + $tokens->clearAt($index); + + ++$index; + + if ($tokens[$index]->isWhitespace()) { + $tokens->clearAt($index); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedClassElementsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedClassElementsFixer.php new file mode 100644 index 00000000..40d39ea3 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedClassElementsFixer.php @@ -0,0 +1,586 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ClassNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Utils; + +/** + * @author Gregor Harlan + * + * @phpstan-type _ClassElement array{ + * start: int, + * visibility: string, + * abstract: bool, + * static: bool, + * readonly: bool, + * type: string, + * name: string, + * end: int, + * } + */ +final class OrderedClassElementsFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** @internal */ + public const SORT_ALPHA = 'alpha'; + + /** @internal */ + public const SORT_NONE = 'none'; + + private const SUPPORTED_SORT_ALGORITHMS = [ + self::SORT_NONE, + self::SORT_ALPHA, + ]; + + /** + * @var array> Array containing all class element base types (keys) and their parent types (values) + */ + private static array $typeHierarchy = [ + 'use_trait' => null, + 'public' => null, + 'protected' => null, + 'private' => null, + 'case' => ['public'], + 'constant' => null, + 'constant_public' => ['constant', 'public'], + 'constant_protected' => ['constant', 'protected'], + 'constant_private' => ['constant', 'private'], + 'property' => null, + 'property_static' => ['property'], + 'property_public' => ['property', 'public'], + 'property_protected' => ['property', 'protected'], + 'property_private' => ['property', 'private'], + 'property_public_readonly' => ['property_readonly', 'property_public'], + 'property_protected_readonly' => ['property_readonly', 'property_protected'], + 'property_private_readonly' => ['property_readonly', 'property_private'], + 'property_public_static' => ['property_static', 'property_public'], + 'property_protected_static' => ['property_static', 'property_protected'], + 'property_private_static' => ['property_static', 'property_private'], + 'method' => null, + 'method_abstract' => ['method'], + 'method_static' => ['method'], + 'method_public' => ['method', 'public'], + 'method_protected' => ['method', 'protected'], + 'method_private' => ['method', 'private'], + 'method_public_abstract' => ['method_abstract', 'method_public'], + 'method_protected_abstract' => ['method_abstract', 'method_protected'], + 'method_private_abstract' => ['method_abstract', 'method_private'], + 'method_public_abstract_static' => ['method_abstract', 'method_static', 'method_public'], + 'method_protected_abstract_static' => ['method_abstract', 'method_static', 'method_protected'], + 'method_private_abstract_static' => ['method_abstract', 'method_static', 'method_private'], + 'method_public_static' => ['method_static', 'method_public'], + 'method_protected_static' => ['method_static', 'method_protected'], + 'method_private_static' => ['method_static', 'method_private'], + ]; + + /** + * @var array Array containing special method types + */ + private static array $specialTypes = [ + 'construct' => null, + 'destruct' => null, + 'magic' => null, + 'phpunit' => null, + ]; + + /** + * @var array Resolved configuration array (type => position) + */ + private array $typePosition; + + public function configure(array $configuration): void + { + parent::configure($configuration); + + $this->typePosition = []; + $position = 0; + + foreach ($this->configuration['order'] as $type) { + $this->typePosition[$type] = $position++; + } + + foreach (self::$typeHierarchy as $type => $parents) { + if (isset($this->typePosition[$type])) { + continue; + } + + if (null === $parents) { + $this->typePosition[$type] = null; + + continue; + } + + foreach ($parents as $parent) { + if (isset($this->typePosition[$parent])) { + $this->typePosition[$type] = $this->typePosition[$parent]; + + continue 2; + } + } + + $this->typePosition[$type] = null; + } + + $lastPosition = \count($this->configuration['order']); + + foreach ($this->typePosition as &$pos) { + if (null === $pos) { + $pos = $lastPosition; + } + + $pos *= 10; // last digit is used by phpunit method ordering + } + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds()); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Orders the elements of classes/interfaces/traits/enums.', + [ + new CodeSample( + ' ['method_private', 'method_public']] + ), + new CodeSample( + ' ['method_public'], 'sort_algorithm' => self::SORT_ALPHA] + ), + new CodeSample( + ' ['method_public'], 'sort_algorithm' => self::SORT_ALPHA, 'case_sensitive' => true] + ), + ], + 'Accepts a subset of pre-defined element types, special element groups, and custom patterns. + +Element types: `[\''.implode('\', \'', array_keys(self::$typeHierarchy)).'\']` + +Special element types: `[\''.implode('\', \'', array_keys(self::$specialTypes)).'\']` + +Custom values: + +- `method:*`: specify a single method name (e.g. `method:__invoke`) to set the order of that specific method.' + ); + } + + /** + * {@inheritdoc} + * + * Must run before ClassAttributesSeparationFixer, NoBlankLinesAfterClassOpeningFixer, SpaceAfterSemicolonFixer. + * Must run after NoPhp4ConstructorFixer, ProtectedToPrivateFixer. + */ + public function getPriority(): int + { + return 65; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($i = 1, $count = $tokens->count(); $i < $count; ++$i) { + if (!$tokens[$i]->isClassy()) { + continue; + } + + $i = $tokens->getNextTokenOfKind($i, ['{']); + $elements = $this->getElements($tokens, $i); + + if (0 === \count($elements)) { + continue; + } + + $sorted = $this->sortElements($elements); + $endIndex = $elements[\count($elements) - 1]['end']; + + if ($sorted !== $elements) { + $this->sortTokens($tokens, $i, $endIndex, $sorted); + } + + $i = $endIndex; + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + $builtIns = array_keys(array_merge(self::$typeHierarchy, self::$specialTypes)); + + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('order', 'List of strings defining order of elements.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([ + static function (array $values) use ($builtIns): bool { + foreach ($values as $value) { + if (\in_array($value, $builtIns, true)) { + return true; + } + + if (\is_string($value) && 'method:' === substr($value, 0, 7)) { + return true; + } + } + + return false; + }, + ]) + ->setDefault([ + 'use_trait', + 'case', + 'constant_public', + 'constant_protected', + 'constant_private', + 'property_public', + 'property_protected', + 'property_private', + 'construct', + 'destruct', + 'magic', + 'phpunit', + 'method_public', + 'method_protected', + 'method_private', + ]) + ->getOption(), + (new FixerOptionBuilder('sort_algorithm', 'How multiple occurrences of same type statements should be sorted.')) + ->setAllowedValues(self::SUPPORTED_SORT_ALGORITHMS) + ->setDefault(self::SORT_NONE) + ->getOption(), + (new FixerOptionBuilder('case_sensitive', 'Whether the sorting should be case sensitive.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + + /** + * @return list<_ClassElement> + */ + private function getElements(Tokens $tokens, int $startIndex): array + { + static $elementTokenKinds = [CT::T_USE_TRAIT, T_CASE, T_CONST, T_VARIABLE, T_FUNCTION]; + + ++$startIndex; + $elements = []; + + while (true) { + $element = [ + 'start' => $startIndex, + 'visibility' => 'public', + 'abstract' => false, + 'static' => false, + 'readonly' => false, + ]; + + for ($i = $startIndex;; ++$i) { + $token = $tokens[$i]; + + // class end + if ($token->equals('}')) { + return $elements; + } + + if ($token->isGivenKind(T_ABSTRACT)) { + $element['abstract'] = true; + + continue; + } + + if ($token->isGivenKind(T_STATIC)) { + $element['static'] = true; + + continue; + } + + if (\defined('T_READONLY') && $token->isGivenKind(T_READONLY)) { // @TODO: drop condition when PHP 8.1+ is required + $element['readonly'] = true; + } + + if ($token->isGivenKind([T_PROTECTED, T_PRIVATE])) { + $element['visibility'] = strtolower($token->getContent()); + + continue; + } + + if (!$token->isGivenKind($elementTokenKinds)) { + continue; + } + + $type = $this->detectElementType($tokens, $i); + + if (\is_array($type)) { + $element['type'] = $type[0]; + $element['name'] = $type[1]; + } else { + $element['type'] = $type; + } + + if ('property' === $element['type']) { + $element['name'] = $tokens[$i]->getContent(); + } elseif (\in_array($element['type'], ['use_trait', 'case', 'constant', 'method', 'magic', 'construct', 'destruct'], true)) { + $element['name'] = $tokens[$tokens->getNextMeaningfulToken($i)]->getContent(); + } + + $element['end'] = $this->findElementEnd($tokens, $i); + + break; + } + + $elements[] = $element; + $startIndex = $element['end'] + 1; + } + } + + /** + * @return array|string type or array of type and name + */ + private function detectElementType(Tokens $tokens, int $index) + { + $token = $tokens[$index]; + + if ($token->isGivenKind(CT::T_USE_TRAIT)) { + return 'use_trait'; + } + + if ($token->isGivenKind(T_CASE)) { + return 'case'; + } + + if ($token->isGivenKind(T_CONST)) { + return 'constant'; + } + + if ($token->isGivenKind(T_VARIABLE)) { + return 'property'; + } + + $nameToken = $tokens[$tokens->getNextMeaningfulToken($index)]; + + if ($nameToken->equals([T_STRING, '__construct'], false)) { + return 'construct'; + } + + if ($nameToken->equals([T_STRING, '__destruct'], false)) { + return 'destruct'; + } + + if ( + $nameToken->equalsAny([ + [T_STRING, 'setUpBeforeClass'], + [T_STRING, 'doSetUpBeforeClass'], + [T_STRING, 'tearDownAfterClass'], + [T_STRING, 'doTearDownAfterClass'], + [T_STRING, 'setUp'], + [T_STRING, 'doSetUp'], + [T_STRING, 'assertPreConditions'], + [T_STRING, 'assertPostConditions'], + [T_STRING, 'tearDown'], + [T_STRING, 'doTearDown'], + ], false) + ) { + return ['phpunit', strtolower($nameToken->getContent())]; + } + + return str_starts_with($nameToken->getContent(), '__') ? 'magic' : 'method'; + } + + private function findElementEnd(Tokens $tokens, int $index): int + { + $index = $tokens->getNextTokenOfKind($index, ['{', ';']); + + if ($tokens[$index]->equals('{')) { + $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); + } + + for (++$index; $tokens[$index]->isWhitespace(" \t") || $tokens[$index]->isComment(); ++$index); + + --$index; + + return $tokens[$index]->isWhitespace() ? $index - 1 : $index; + } + + /** + * @param list<_ClassElement> $elements + * + * @return list<_ClassElement> + */ + private function sortElements(array $elements): array + { + static $phpunitPositions = [ + 'setupbeforeclass' => 1, + 'dosetupbeforeclass' => 2, + 'teardownafterclass' => 3, + 'doteardownafterclass' => 4, + 'setup' => 5, + 'dosetup' => 6, + 'assertpreconditions' => 7, + 'assertpostconditions' => 8, + 'teardown' => 9, + 'doteardown' => 10, + ]; + + $getPositionType = function (array $element) use ($phpunitPositions): int { + $type = $element['type']; + + if (\in_array($type, ['method', 'magic', 'phpunit'], true) && isset($this->typePosition["method:{$element['name']}"])) { + return $this->typePosition["method:{$element['name']}"]; + } + + if (\array_key_exists($type, self::$specialTypes)) { + if (isset($this->typePosition[$type])) { + $position = $this->typePosition[$type]; + + if ('phpunit' === $type) { + $position += $phpunitPositions[$element['name']]; + } + + return $position; + } + + $type = 'method'; + } + + if (\in_array($type, ['constant', 'property', 'method'], true)) { + $type .= '_'.$element['visibility']; + + if ($element['abstract']) { + $type .= '_abstract'; + } + + if ($element['static']) { + $type .= '_static'; + } + + if ($element['readonly']) { + $type .= '_readonly'; + } + } + + return $this->typePosition[$type]; + }; + + return Utils::stableSort( + $elements, + /** + * @return array{element: _ClassElement, position: int} + */ + static fn (array $element): array => ['element' => $element, 'position' => $getPositionType($element)], + /** + * @param array{element: _ClassElement, position: int} $a + * @param array{element: _ClassElement, position: int} $b + * + * @return -1|0|1 + */ + fn (array $a, array $b): int => ($a['position'] === $b['position']) ? $this->sortGroupElements($a['element'], $b['element']) : $a['position'] <=> $b['position'], + ); + } + + /** + * @param _ClassElement $a + * @param _ClassElement $b + */ + private function sortGroupElements(array $a, array $b): int + { + if (self::SORT_ALPHA === $this->configuration['sort_algorithm']) { + return true === $this->configuration['case_sensitive'] + ? $a['name'] <=> $b['name'] + : strcasecmp($a['name'], $b['name']); + } + + return $a['start'] <=> $b['start']; + } + + /** + * @param list<_ClassElement> $elements + */ + private function sortTokens(Tokens $tokens, int $startIndex, int $endIndex, array $elements): void + { + $replaceTokens = []; + + foreach ($elements as $element) { + for ($i = $element['start']; $i <= $element['end']; ++$i) { + $replaceTokens[] = clone $tokens[$i]; + } + } + + $tokens->overrideRange($startIndex + 1, $endIndex, $replaceTokens); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedInterfacesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedInterfacesFixer.php new file mode 100644 index 00000000..985c9e83 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedInterfacesFixer.php @@ -0,0 +1,255 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ClassNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dave van der Brugge + */ +final class OrderedInterfacesFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** @internal */ + public const OPTION_DIRECTION = 'direction'; + + /** @internal */ + public const OPTION_ORDER = 'order'; + + /** @internal */ + public const DIRECTION_ASCEND = 'ascend'; + + /** @internal */ + public const DIRECTION_DESCEND = 'descend'; + + /** @internal */ + public const ORDER_ALPHA = 'alpha'; + + /** @internal */ + public const ORDER_LENGTH = 'length'; + + /** + * Array of supported directions in configuration. + * + * @var string[] + */ + private const SUPPORTED_DIRECTION_OPTIONS = [ + self::DIRECTION_ASCEND, + self::DIRECTION_DESCEND, + ]; + + /** + * Array of supported orders in configuration. + * + * @var string[] + */ + private const SUPPORTED_ORDER_OPTIONS = [ + self::ORDER_ALPHA, + self::ORDER_LENGTH, + ]; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Orders the interfaces in an `implements` or `interface extends` clause.', + [ + new CodeSample( + " self::DIRECTION_DESCEND] + ), + new CodeSample( + " self::ORDER_LENGTH] + ), + new CodeSample( + " self::ORDER_LENGTH, + self::OPTION_DIRECTION => self::DIRECTION_DESCEND, + ] + ), + new CodeSample( + " self::ORDER_ALPHA, + ] + ), + new CodeSample( + " self::ORDER_ALPHA, + 'case_sensitive' => true, + ] + ), + ], + ); + } + + /** + * {@inheritdoc} + * + * Must run after FullyQualifiedStrictTypesFixer. + */ + public function getPriority(): int + { + return 0; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_IMPLEMENTS) + || $tokens->isAllTokenKindsFound([T_INTERFACE, T_EXTENDS]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_IMPLEMENTS)) { + if (!$token->isGivenKind(T_EXTENDS)) { + continue; + } + + $nameTokenIndex = $tokens->getPrevMeaningfulToken($index); + $interfaceTokenIndex = $tokens->getPrevMeaningfulToken($nameTokenIndex); + $interfaceToken = $tokens[$interfaceTokenIndex]; + + if (!$interfaceToken->isGivenKind(T_INTERFACE)) { + continue; + } + } + + $implementsStart = $index + 1; + $implementsEnd = $tokens->getPrevMeaningfulToken($tokens->getNextTokenOfKind($implementsStart, ['{'])); + + $interfaces = $this->getInterfaces($tokens, $implementsStart, $implementsEnd); + + if (1 === \count($interfaces)) { + continue; + } + + foreach ($interfaces as $interfaceIndex => $interface) { + $interfaceTokens = Tokens::fromArray($interface, false); + $normalized = ''; + $actualInterfaceIndex = $interfaceTokens->getNextMeaningfulToken(-1); + + while ($interfaceTokens->offsetExists($actualInterfaceIndex)) { + $token = $interfaceTokens[$actualInterfaceIndex]; + + if ($token->isComment() || $token->isWhitespace()) { + break; + } + + $normalized .= str_replace('\\', ' ', $token->getContent()); + ++$actualInterfaceIndex; + } + + $interfaces[$interfaceIndex] = [ + 'tokens' => $interface, + 'normalized' => $normalized, + 'originalIndex' => $interfaceIndex, + ]; + } + + usort($interfaces, function (array $first, array $second): int { + $score = self::ORDER_LENGTH === $this->configuration[self::OPTION_ORDER] + ? \strlen($first['normalized']) - \strlen($second['normalized']) + : ( + true === $this->configuration['case_sensitive'] + ? $first['normalized'] <=> $second['normalized'] + : strcasecmp($first['normalized'], $second['normalized']) + ); + + if (self::DIRECTION_DESCEND === $this->configuration[self::OPTION_DIRECTION]) { + $score *= -1; + } + + return $score; + }); + + $changed = false; + + foreach ($interfaces as $interfaceIndex => $interface) { + if ($interface['originalIndex'] !== $interfaceIndex) { + $changed = true; + + break; + } + } + + if (!$changed) { + continue; + } + + $newTokens = array_shift($interfaces)['tokens']; + + foreach ($interfaces as $interface) { + array_push($newTokens, new Token(','), ...$interface['tokens']); + } + + $tokens->overrideRange($implementsStart, $implementsEnd, $newTokens); + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder(self::OPTION_ORDER, 'How the interfaces should be ordered.')) + ->setAllowedValues(self::SUPPORTED_ORDER_OPTIONS) + ->setDefault(self::ORDER_ALPHA) + ->getOption(), + (new FixerOptionBuilder(self::OPTION_DIRECTION, 'Which direction the interfaces should be ordered.')) + ->setAllowedValues(self::SUPPORTED_DIRECTION_OPTIONS) + ->setDefault(self::DIRECTION_ASCEND) + ->getOption(), + (new FixerOptionBuilder('case_sensitive', 'Whether the sorting should be case sensitive.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + + /** + * @return array> + */ + private function getInterfaces(Tokens $tokens, int $implementsStart, int $implementsEnd): array + { + $interfaces = []; + $interfaceIndex = 0; + + for ($i = $implementsStart; $i <= $implementsEnd; ++$i) { + if ($tokens[$i]->equals(',')) { + ++$interfaceIndex; + $interfaces[$interfaceIndex] = []; + + continue; + } + + $interfaces[$interfaceIndex][] = $tokens[$i]; + } + + return $interfaces; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedTraitsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedTraitsFixer.php new file mode 100644 index 00000000..417eb126 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedTraitsFixer.php @@ -0,0 +1,212 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ClassNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Tokens; + +final class OrderedTraitsFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Trait `use` statements must be sorted alphabetically.', + [ + new CodeSample(" true, + ] + ), + ], + null, + 'Risky when depending on order of the imports.' + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(CT::T_USE_TRAIT); + } + + public function isRisky(): bool + { + return true; + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('case_sensitive', 'Whether the sorting should be case sensitive.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($this->findUseStatementsGroups($tokens) as $uses) { + $this->sortUseStatements($tokens, $uses); + } + } + + /** + * @return iterable> + */ + private function findUseStatementsGroups(Tokens $tokens): iterable + { + $uses = []; + + for ($index = 1, $max = \count($tokens); $index < $max; ++$index) { + $token = $tokens[$index]; + + if ($token->isWhitespace() || $token->isComment()) { + continue; + } + + if (!$token->isGivenKind(CT::T_USE_TRAIT)) { + if (\count($uses) > 0) { + yield $uses; + + $uses = []; + } + + continue; + } + + $startIndex = $tokens->getNextNonWhitespace($tokens->getPrevMeaningfulToken($index)); + $endIndex = $tokens->getNextTokenOfKind($index, [';', '{']); + + if ($tokens[$endIndex]->equals('{')) { + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $endIndex); + } + + $use = []; + + for ($i = $startIndex; $i <= $endIndex; ++$i) { + $use[] = $tokens[$i]; + } + + $uses[$startIndex] = Tokens::fromArray($use); + + $index = $endIndex; + } + } + + /** + * @param array $uses + */ + private function sortUseStatements(Tokens $tokens, array $uses): void + { + foreach ($uses as $use) { + $this->sortMultipleTraitsInStatement($use); + } + + $this->sort($tokens, $uses); + } + + private function sortMultipleTraitsInStatement(Tokens $use): void + { + $traits = []; + $indexOfName = null; + $name = []; + + for ($index = 0, $max = \count($use); $index < $max; ++$index) { + $token = $use[$index]; + + if ($token->isGivenKind([T_STRING, T_NS_SEPARATOR])) { + $name[] = $token; + + if (null === $indexOfName) { + $indexOfName = $index; + } + + continue; + } + + if ($token->equalsAny([',', ';', '{'])) { + $traits[$indexOfName] = Tokens::fromArray($name); + + $name = []; + $indexOfName = null; + } + + if ($token->equals('{')) { + $index = $use->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); + } + } + + $this->sort($use, $traits); + } + + /** + * @param array $elements + */ + private function sort(Tokens $tokens, array $elements): void + { + $toTraitName = static function (Tokens $use): string { + $string = ''; + + foreach ($use as $token) { + if ($token->equalsAny([';', '{'])) { + break; + } + + if ($token->isGivenKind([T_NS_SEPARATOR, T_STRING])) { + $string .= $token->getContent(); + } + } + + return ltrim($string, '\\'); + }; + + $sortedElements = $elements; + uasort( + $sortedElements, + fn (Tokens $useA, Tokens $useB): int => true === $this->configuration['case_sensitive'] + ? $toTraitName($useA) <=> $toTraitName($useB) + : strcasecmp($toTraitName($useA), $toTraitName($useB)) + ); + + $sortedElements = array_combine( + array_keys($elements), + array_values($sortedElements) + ); + + $beforeOverrideCount = $tokens->count(); + + foreach (array_reverse($sortedElements, true) as $index => $tokensToInsert) { + $tokens->overrideRange( + $index, + $index + \count($elements[$index]) - 1, + $tokensToInsert + ); + } + + if ($beforeOverrideCount < $tokens->count()) { + $tokens->clearEmptyTokens(); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedTypesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedTypesFixer.php new file mode 100644 index 00000000..10ef2f7f --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedTypesFixer.php @@ -0,0 +1,436 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ClassNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\TypeAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author John Paul E. Balandan, CPA + */ +final class OrderedTypesFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Sort union types and intersection types using configured order.', + [ + new CodeSample( + 'save($foo); +} catch (\RuntimeException|CacheException $e) { + logger($e); + + throw $e; +} +' + ), + new VersionSpecificCodeSample( + ' true, + ] + ), + new VersionSpecificCodeSample( + ' 'always_last'] + ), + new VersionSpecificCodeSample( + ' 'none', + 'null_adjustment' => 'always_last', + ] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before TypesSpacesFixer. + * Must run after NullableTypeDeclarationFixer, NullableTypeDeclarationForDefaultNullValueFixer. + */ + public function getPriority(): int + { + return 0; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound([CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION]); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('sort_algorithm', 'Whether the types should be sorted alphabetically, or not sorted.')) + ->setAllowedValues(['alpha', 'none']) + ->setDefault('alpha') + ->getOption(), + (new FixerOptionBuilder('null_adjustment', 'Forces the position of `null` (overrides `sort_algorithm`).')) + ->setAllowedValues(['always_first', 'always_last', 'none']) + ->setDefault('always_first') + ->getOption(), + (new FixerOptionBuilder('case_sensitive', 'Whether the sorting should be case sensitive.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $functionsAnalyzer = new FunctionsAnalyzer(); + + foreach ($this->getElements($tokens) as $index => $type) { + if ('catch' === $type) { + $this->fixCatchArgumentType($tokens, $index); + + continue; + } + + if ('property' === $type) { + $this->fixPropertyType($tokens, $index); + + continue; + } + + $this->fixMethodArgumentType($functionsAnalyzer, $tokens, $index); + $this->fixMethodReturnType($functionsAnalyzer, $tokens, $index); + } + } + + /** + * @return array + * + * @phpstan-return array + */ + private function getElements(Tokens $tokens): array + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + + $elements = array_map( + static fn (array $element): string => $element['type'], + array_filter( + $tokensAnalyzer->getClassyElements(), + static fn (array $element): bool => \in_array($element['type'], ['method', 'property'], true) + ) + ); + + foreach ($tokens as $index => $token) { + if ($token->isGivenKind(T_CATCH)) { + $elements[$index] = 'catch'; + + continue; + } + + if ( + $token->isGivenKind(T_FN) + || ($token->isGivenKind(T_FUNCTION) && !isset($elements[$index])) + ) { + $elements[$index] = 'method'; + } + } + + return $elements; + } + + private function collectTypeAnalysis(Tokens $tokens, int $startIndex, int $endIndex): ?TypeAnalysis + { + $type = ''; + $typeStartIndex = $tokens->getNextMeaningfulToken($startIndex); + $typeEndIndex = $typeStartIndex; + + for ($i = $typeStartIndex; $i < $endIndex; ++$i) { + if ($tokens[$i]->isWhitespace() || $tokens[$i]->isComment()) { + continue; + } + + $type .= $tokens[$i]->getContent(); + $typeEndIndex = $i; + } + + return '' !== $type ? new TypeAnalysis($type, $typeStartIndex, $typeEndIndex) : null; + } + + private function fixCatchArgumentType(Tokens $tokens, int $index): void + { + $catchStart = $tokens->getNextTokenOfKind($index, ['(']); + $catchEnd = $tokens->getNextTokenOfKind($catchStart, [')', [T_VARIABLE]]); + + $catchArgumentType = $this->collectTypeAnalysis($tokens, $catchStart, $catchEnd); + + if (null === $catchArgumentType || !$this->isTypeSortable($catchArgumentType)) { + return; // nothing to fix + } + + $this->sortTypes($catchArgumentType, $tokens); + } + + private function fixPropertyType(Tokens $tokens, int $index): void + { + $propertyIndex = $index; + $propertyModifiers = [T_PRIVATE, T_PROTECTED, T_PUBLIC, T_STATIC, T_VAR]; + + if (\defined('T_READONLY')) { + $propertyModifiers[] = T_READONLY; // @TODO drop condition when PHP 8.1 is supported + } + + do { + $index = $tokens->getPrevMeaningfulToken($index); + } while (!$tokens[$index]->isGivenKind($propertyModifiers)); + + $propertyType = $this->collectTypeAnalysis($tokens, $index, $propertyIndex); + + if (null === $propertyType || !$this->isTypeSortable($propertyType)) { + return; // nothing to fix + } + + $this->sortTypes($propertyType, $tokens); + } + + private function fixMethodArgumentType(FunctionsAnalyzer $functionsAnalyzer, Tokens $tokens, int $index): void + { + foreach ($functionsAnalyzer->getFunctionArguments($tokens, $index) as $argumentInfo) { + $argumentType = $argumentInfo->getTypeAnalysis(); + + if (null === $argumentType || !$this->isTypeSortable($argumentType)) { + continue; // nothing to fix + } + + $this->sortTypes($argumentType, $tokens); + } + } + + private function fixMethodReturnType(FunctionsAnalyzer $functionsAnalyzer, Tokens $tokens, int $index): void + { + $returnType = $functionsAnalyzer->getFunctionReturnType($tokens, $index); + + if (null === $returnType || !$this->isTypeSortable($returnType)) { + return; // nothing to fix + } + + $this->sortTypes($returnType, $tokens); + } + + private function sortTypes(TypeAnalysis $typeAnalysis, Tokens $tokens): void + { + $type = $typeAnalysis->getName(); + + if (str_contains($type, '|') && str_contains($type, '&')) { + // a DNF type of the form (A&B)|C, available as of PHP 8.2 + [$originalTypes, $glue] = $this->collectDisjunctiveNormalFormTypes($type); + } else { + [$originalTypes, $glue] = $this->collectUnionOrIntersectionTypes($type); + } + + // If the $types array is coming from a DNF type, then we have parts + // which are also array. If so, we sort those sub-types first before + // running the sorting algorithm to the entire $types array. + $sortedTypes = array_map(function ($subType) { + if (\is_array($subType)) { + return $this->runTypesThroughSortingAlgorithm($subType); + } + + return $subType; + }, $originalTypes); + + $sortedTypes = $this->runTypesThroughSortingAlgorithm($sortedTypes); + + if ($sortedTypes === $originalTypes) { + return; + } + + $tokens->overrideRange( + $typeAnalysis->getStartIndex(), + $typeAnalysis->getEndIndex(), + $this->createTypeDeclarationTokens($sortedTypes, $glue) + ); + } + + private function isTypeSortable(TypeAnalysis $type): bool + { + return str_contains($type->getName(), '|') || str_contains($type->getName(), '&'); + } + + /** + * @return array{0: array, 1: string} + */ + private function collectDisjunctiveNormalFormTypes(string $type): array + { + $types = array_map(static function (string $subType) { + if (str_starts_with($subType, '(')) { + return explode('&', trim($subType, '()')); + } + + return $subType; + }, explode('|', $type)); + + return [$types, '|']; + } + + /** + * @return array{0: string[], 1: string} + */ + private function collectUnionOrIntersectionTypes(string $type): array + { + $types = explode('|', $type); + $glue = '|'; + + if (1 === \count($types)) { + $types = explode('&', $type); + $glue = '&'; + } + + return [$types, $glue]; + } + + /** + * @param array $types + * + * @return array + */ + private function runTypesThroughSortingAlgorithm(array $types): array + { + $normalizeType = static fn (string $type): string => Preg::replace('/^\\\\?/', '', $type); + + usort($types, function ($a, $b) use ($normalizeType): int { + if (\is_array($a)) { + $a = implode('&', $a); + } + + if (\is_array($b)) { + $b = implode('&', $b); + } + + $a = $normalizeType($a); + $b = $normalizeType($b); + $lowerCaseA = strtolower($a); + $lowerCaseB = strtolower($b); + + if ('none' !== $this->configuration['null_adjustment']) { + if ('null' === $lowerCaseA && 'null' !== $lowerCaseB) { + return 'always_last' === $this->configuration['null_adjustment'] ? 1 : -1; + } + + if ('null' !== $lowerCaseA && 'null' === $lowerCaseB) { + return 'always_last' === $this->configuration['null_adjustment'] ? -1 : 1; + } + } + + if ('alpha' === $this->configuration['sort_algorithm']) { + return true === $this->configuration['case_sensitive'] ? $a <=> $b : strcasecmp($a, $b); + } + + return 0; + }); + + return $types; + } + + /** + * @param array $types + * + * @return array + */ + private function createTypeDeclarationTokens(array $types, string $glue, bool $isDisjunctive = false): array + { + static $specialTypes = [ + 'array' => [CT::T_ARRAY_TYPEHINT, 'array'], + 'callable' => [T_CALLABLE, 'callable'], + 'static' => [T_STATIC, 'static'], + ]; + + static $glues = [ + '|' => [CT::T_TYPE_ALTERNATION, '|'], + '&' => [CT::T_TYPE_INTERSECTION, '&'], + ]; + + $count = \count($types); + $newTokens = []; + + foreach ($types as $i => $type) { + if (\is_array($type)) { + $newTokens = [ + ...$newTokens, + ...$this->createTypeDeclarationTokens($type, '&', true), + ]; + } elseif (isset($specialTypes[$type])) { + $newTokens[] = new Token($specialTypes[$type]); + } else { + foreach (explode('\\', $type) as $nsIndex => $value) { + if (0 === $nsIndex && '' === $value) { + continue; + } + + if ($nsIndex > 0) { + $newTokens[] = new Token([T_NS_SEPARATOR, '\\']); + } + + $newTokens[] = new Token([T_STRING, $value]); + } + } + + if ($i <= $count - 2) { + $newTokens[] = new Token($glues[$glue]); + } + } + + if ($isDisjunctive) { + array_unshift($newTokens, new Token([CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_OPEN, '('])); + $newTokens[] = new Token([CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE, ')']); + } + + return $newTokens; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/PhpdocReadonlyClassCommentToKeywordFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/PhpdocReadonlyClassCommentToKeywordFixer.php new file mode 100644 index 00000000..78916047 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/PhpdocReadonlyClassCommentToKeywordFixer.php @@ -0,0 +1,129 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ClassNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Marcel Behrmann + */ +final class PhpdocReadonlyClassCommentToKeywordFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + * + * Must run before NoEmptyPhpdocFixer, NoExtraBlankLinesFixer, PhpdocAlignFixer. + * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. + */ + public function getPriority(): int + { + return 4; + } + + public function isCandidate(Tokens $tokens): bool + { + return \PHP_VERSION_ID >= 8_02_00 && $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + public function isRisky(): bool + { + return true; + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Converts readonly comment on classes to the readonly keyword.', + [ + new VersionSpecificCodeSample( + << $token) { + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + $doc = new DocBlock($token->getContent()); + + $annotations = $doc->getAnnotationsOfType('readonly'); + + if (0 === \count($annotations)) { + continue; + } + + foreach ($annotations as $annotation) { + $annotation->remove(); + } + + $mainIndex = $index; + $index = $tokens->getNextMeaningfulToken($index); + $addReadonly = true; + + while ($tokens[$index]->isGivenKind([ + T_ABSTRACT, + T_FINAL, + T_PRIVATE, + T_PUBLIC, + T_PROTECTED, + T_READONLY, + ])) { + if ($tokens[$index]->isGivenKind([T_READONLY])) { + $addReadonly = false; + } + + $index = $tokens->getNextMeaningfulToken($index); + } + + if (!$tokens[$index]->isGivenKind(T_CLASS)) { + continue; + } + + if ($addReadonly) { + $tokens->insertAt($index, [new Token([T_READONLY, 'readonly']), new Token([T_WHITESPACE, ' '])]); + } + + $newContent = $doc->getContent(); + + if ('' === $newContent) { + $tokens->clearTokenAndMergeSurroundingWhitespace($mainIndex); + + continue; + } + + $tokens[$mainIndex] = new Token([T_DOC_COMMENT, $doc->getContent()]); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/ProtectedToPrivateFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/ProtectedToPrivateFixer.php new file mode 100644 index 00000000..168bbb3b --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/ProtectedToPrivateFixer.php @@ -0,0 +1,170 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ClassNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Filippo Tessarotto + */ +final class ProtectedToPrivateFixer extends AbstractFixer +{ + private TokensAnalyzer $tokensAnalyzer; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Converts `protected` variables and methods to `private` where possible.', + [ + new CodeSample( + 'isAllTokenKindsFound([T_ENUM, T_PROTECTED])) { // @TODO: drop condition when PHP 8.1+ is required + return true; + } + + return $tokens->isAllTokenKindsFound([T_CLASS, T_FINAL, T_PROTECTED]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $this->tokensAnalyzer = new TokensAnalyzer($tokens); + $modifierKinds = [T_PUBLIC, T_PROTECTED, T_PRIVATE, T_FINAL, T_ABSTRACT, T_NS_SEPARATOR, T_STRING, CT::T_NULLABLE_TYPE, CT::T_ARRAY_TYPEHINT, T_STATIC, CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION]; + + if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required + $modifierKinds[] = T_READONLY; + } + + $classesCandidate = []; + $classElementTypes = ['method' => true, 'property' => true, 'const' => true]; + + foreach ($this->tokensAnalyzer->getClassyElements() as $index => $element) { + $classIndex = $element['classIndex']; + + if (!\array_key_exists($classIndex, $classesCandidate)) { + $classesCandidate[$classIndex] = $this->isClassCandidate($tokens, $classIndex); + } + + if (false === $classesCandidate[$classIndex]) { + continue; + } + + if (!isset($classElementTypes[$element['type']])) { + continue; + } + + $previous = $index; + $isProtected = false; + $isFinal = false; + + do { + $previous = $tokens->getPrevMeaningfulToken($previous); + + if ($tokens[$previous]->isGivenKind(T_PROTECTED)) { + $isProtected = $previous; + } elseif ($tokens[$previous]->isGivenKind(T_FINAL)) { + $isFinal = $previous; + } + } while ($tokens[$previous]->isGivenKind($modifierKinds)); + + if (false === $isProtected) { + continue; + } + + if ($isFinal && 'const' === $element['type']) { + continue; // Final constants cannot be private + } + + $element['protected_index'] = $isProtected; + $tokens[$element['protected_index']] = new Token([T_PRIVATE, 'private']); + } + } + + /** + * Consider symbol as candidate for fixing if it's: + * - an Enum (PHP8.1+) + * - a class, which: + * - is not anonymous + * - is not final + * - does not use traits + * - does not extend other class. + */ + private function isClassCandidate(Tokens $tokens, int $classIndex): bool + { + if (\defined('T_ENUM') && $tokens[$classIndex]->isGivenKind(T_ENUM)) { // @TODO: drop condition when PHP 8.1+ is required + return true; + } + + if (!$tokens[$classIndex]->isGivenKind(T_CLASS) || $this->tokensAnalyzer->isAnonymousClass($classIndex)) { + return false; + } + + $modifiers = $this->tokensAnalyzer->getClassyModifiers($classIndex); + + if (!isset($modifiers['final'])) { + return false; + } + + $classNameIndex = $tokens->getNextMeaningfulToken($classIndex); // move to class name as anonymous class is never "final" + $classExtendsIndex = $tokens->getNextMeaningfulToken($classNameIndex); // move to possible "extends" + + if ($tokens[$classExtendsIndex]->isGivenKind(T_EXTENDS)) { + return false; + } + + if (!$tokens->isTokenKindFound(CT::T_USE_TRAIT)) { + return true; // cheap test + } + + $classOpenIndex = $tokens->getNextTokenOfKind($classNameIndex, ['{']); + $classCloseIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classOpenIndex); + $useIndex = $tokens->getNextTokenOfKind($classOpenIndex, [[CT::T_USE_TRAIT]]); + + return null === $useIndex || $useIndex > $classCloseIndex; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SelfAccessorFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SelfAccessorFixer.php new file mode 100644 index 00000000..1ad573c9 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SelfAccessorFixer.php @@ -0,0 +1,200 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ClassNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Gregor Harlan + */ +final class SelfAccessorFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Inside class or interface element `self` should be preferred to the class name itself.', + [ + new CodeSample( + 'isAnyTokenKindsFound([T_CLASS, T_INTERFACE]); + } + + /** + * {@inheritdoc} + * + * Must run after PsrAutoloadingFixer. + */ + public function getPriority(): int + { + return -11; + } + + public function isRisky(): bool + { + return true; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + + foreach ($tokens->getNamespaceDeclarations() as $namespace) { + for ($index = $namespace->getScopeStartIndex(); $index < $namespace->getScopeEndIndex(); ++$index) { + if (!$tokens[$index]->isGivenKind([T_CLASS, T_INTERFACE]) || $tokensAnalyzer->isAnonymousClass($index)) { + continue; + } + + $nameIndex = $tokens->getNextTokenOfKind($index, [[T_STRING]]); + $startIndex = $tokens->getNextTokenOfKind($nameIndex, ['{']); + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $startIndex); + + $name = $tokens[$nameIndex]->getContent(); + + $this->replaceNameOccurrences($tokens, $namespace->getFullName(), $name, $startIndex, $endIndex); + + $index = $endIndex; + } + } + } + + /** + * Replace occurrences of the name of the classy element by "self" (if possible). + */ + private function replaceNameOccurrences(Tokens $tokens, string $namespace, string $name, int $startIndex, int $endIndex): void + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + $insideMethodSignatureUntil = null; + + for ($i = $startIndex; $i < $endIndex; ++$i) { + if ($i === $insideMethodSignatureUntil) { + $insideMethodSignatureUntil = null; + } + + $token = $tokens[$i]; + + // skip anonymous classes + if ($token->isGivenKind(T_CLASS) && $tokensAnalyzer->isAnonymousClass($i)) { + $i = $tokens->getNextTokenOfKind($i, ['{']); + $i = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $i); + + continue; + } + + if ($token->isGivenKind(T_FN)) { + $i = $tokensAnalyzer->getLastTokenIndexOfArrowFunction($i); + $i = $tokens->getNextMeaningfulToken($i); + + continue; + } + + if ($token->isGivenKind(T_FUNCTION)) { + if ($tokensAnalyzer->isLambda($i)) { + $i = $tokens->getNextTokenOfKind($i, ['{']); + $i = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $i); + + continue; + } + + $i = $tokens->getNextTokenOfKind($i, ['(']); + $insideMethodSignatureUntil = $tokens->getNextTokenOfKind($i, ['{', ';']); + + continue; + } + + if (!$token->equals([T_STRING, $name], false)) { + continue; + } + + $nextToken = $tokens[$tokens->getNextMeaningfulToken($i)]; + if ($nextToken->isGivenKind(T_NS_SEPARATOR)) { + continue; + } + + $classStartIndex = $i; + $prevToken = $tokens[$tokens->getPrevMeaningfulToken($i)]; + if ($prevToken->isGivenKind(T_NS_SEPARATOR)) { + $classStartIndex = $this->getClassStart($tokens, $i, $namespace); + if (null === $classStartIndex) { + continue; + } + $prevToken = $tokens[$tokens->getPrevMeaningfulToken($classStartIndex)]; + } + if ($prevToken->isGivenKind(T_STRING) || $prevToken->isObjectOperator()) { + continue; + } + + if ( + $prevToken->isGivenKind([T_INSTANCEOF, T_NEW]) + || $nextToken->isGivenKind(T_PAAMAYIM_NEKUDOTAYIM) + || ( + null !== $insideMethodSignatureUntil + && $i < $insideMethodSignatureUntil + && $prevToken->equalsAny(['(', ',', [CT::T_NULLABLE_TYPE], [CT::T_TYPE_ALTERNATION], [CT::T_TYPE_COLON]]) + ) + ) { + for ($j = $classStartIndex; $j < $i; ++$j) { + $tokens->clearTokenAndMergeSurroundingWhitespace($j); + } + $tokens[$i] = new Token([T_STRING, 'self']); + } + } + } + + private function getClassStart(Tokens $tokens, int $index, string $namespace): ?int + { + $namespace = ('' !== $namespace ? '\\'.$namespace : '').'\\'; + + foreach (array_reverse(Preg::split('/(\\\\)/', $namespace, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE)) as $piece) { + $index = $tokens->getPrevMeaningfulToken($index); + if ('\\' === $piece) { + if (!$tokens[$index]->isGivenKind(T_NS_SEPARATOR)) { + return null; + } + } elseif (!$tokens[$index]->equals([T_STRING, $piece], false)) { + return null; + } + } + + return $index; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SelfStaticAccessorFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SelfStaticAccessorFixer.php new file mode 100644 index 00000000..0bf56ab0 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SelfStaticAccessorFixer.php @@ -0,0 +1,224 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ClassNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +final class SelfStaticAccessorFixer extends AbstractFixer +{ + private TokensAnalyzer $tokensAnalyzer; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Inside an enum or `final`/anonymous class, `self` should be preferred over `static`.', + [ + new CodeSample( + 'isTokenKindFound(T_STATIC) + && $tokens->isAnyTokenKindsFound($classyTypes) + && $tokens->isAnyTokenKindsFound([T_DOUBLE_COLON, T_NEW, T_INSTANCEOF]); + } + + /** + * {@inheritdoc} + * + * Must run after FinalClassFixer, FinalInternalClassFixer, FunctionToConstantFixer, PhpUnitTestCaseStaticMethodCallsFixer. + */ + public function getPriority(): int + { + return -10; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $classyTokensOfInterest = [[T_CLASS]]; + + if (\defined('T_ENUM')) { + $classyTokensOfInterest[] = [T_ENUM]; // @TODO drop condition when PHP 8.1+ is required + } + + $this->tokensAnalyzer = new TokensAnalyzer($tokens); + $classyIndex = $tokens->getNextTokenOfKind(0, $classyTokensOfInterest); + + while (null !== $classyIndex) { + if ($tokens[$classyIndex]->isGivenKind(T_CLASS)) { + $modifiers = $this->tokensAnalyzer->getClassyModifiers($classyIndex); + + if ( + isset($modifiers['final']) + || $this->tokensAnalyzer->isAnonymousClass($classyIndex) + ) { + $classyIndex = $this->fixClassy($tokens, $classyIndex); + } + } else { + $classyIndex = $this->fixClassy($tokens, $classyIndex); + } + + $classyIndex = $tokens->getNextTokenOfKind($classyIndex, $classyTokensOfInterest); + } + } + + private function fixClassy(Tokens $tokens, int $index): int + { + $index = $tokens->getNextTokenOfKind($index, ['{']); + $classOpenCount = 1; + + while ($classOpenCount > 0) { + ++$index; + + if ($tokens[$index]->equals('{')) { + ++$classOpenCount; + + continue; + } + + if ($tokens[$index]->equals('}')) { + --$classOpenCount; + + continue; + } + + if ($tokens[$index]->isGivenKind(T_FUNCTION)) { + // do not fix inside lambda + if ($this->tokensAnalyzer->isLambda($index)) { + // figure out where the lambda starts + $index = $tokens->getNextTokenOfKind($index, ['{']); + $openCount = 1; + + do { + $index = $tokens->getNextTokenOfKind($index, ['}', '{', [T_CLASS]]); + if ($tokens[$index]->equals('}')) { + --$openCount; + } elseif ($tokens[$index]->equals('{')) { + ++$openCount; + } else { + $index = $this->fixClassy($tokens, $index); + } + } while ($openCount > 0); + } + + continue; + } + + if ($tokens[$index]->isGivenKind([T_NEW, T_INSTANCEOF])) { + $index = $tokens->getNextMeaningfulToken($index); + + if ($tokens[$index]->isGivenKind(T_STATIC)) { + $tokens[$index] = new Token([T_STRING, 'self']); + } + + continue; + } + + if (!$tokens[$index]->isGivenKind(T_STATIC)) { + continue; + } + + $staticIndex = $index; + $index = $tokens->getNextMeaningfulToken($index); + + if (!$tokens[$index]->isGivenKind(T_DOUBLE_COLON)) { + continue; + } + + $tokens[$staticIndex] = new Token([T_STRING, 'self']); + } + + return $index; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SingleClassElementPerStatementFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SingleClassElementPerStatementFixer.php new file mode 100644 index 00000000..2eed2464 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SingleClassElementPerStatementFixer.php @@ -0,0 +1,228 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ClassNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * Fixer for rules defined in PSR2 ¶4.2. + * + * @author Javier Spagnoletti + * @author Dariusz Rumiński + */ +final class SingleClassElementPerStatementFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface +{ + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds()); + } + + /** + * {@inheritdoc} + * + * Must run before ClassAttributesSeparationFixer. + */ + public function getPriority(): int + { + return 56; + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'There MUST NOT be more than one property or constant declared per statement.', + [ + new CodeSample( + ' ['property']] + ), + ] + ); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $analyzer = new TokensAnalyzer($tokens); + $elements = array_reverse($analyzer->getClassyElements(), true); + + foreach ($elements as $index => $element) { + if (!\in_array($element['type'], $this->configuration['elements'], true)) { + continue; // not in configuration + } + + $this->fixElement($tokens, $element['type'], $index); + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + $values = ['const', 'property']; + + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('elements', 'List of strings which element should be modified.')) + ->setDefault($values) + ->setAllowedTypes(['array']) + ->setAllowedValues([new AllowedValueSubset($values)]) + ->getOption(), + ]); + } + + private function fixElement(Tokens $tokens, string $type, int $index): void + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + $repeatIndex = $index; + + while (true) { + $repeatIndex = $tokens->getNextMeaningfulToken($repeatIndex); + $repeatToken = $tokens[$repeatIndex]; + + if ($tokensAnalyzer->isArray($repeatIndex)) { + if ($repeatToken->isGivenKind(T_ARRAY)) { + $repeatIndex = $tokens->getNextTokenOfKind($repeatIndex, ['(']); + $repeatIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $repeatIndex); + } else { + $repeatIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $repeatIndex); + } + + continue; + } + + if ($repeatToken->equals(';')) { + return; // no repeating found, no fixing needed + } + + if ($repeatToken->equals(',')) { + break; + } + } + + $start = $tokens->getPrevTokenOfKind($index, [';', '{', '}']); + $this->expandElement( + $tokens, + $type, + $tokens->getNextMeaningfulToken($start), + $tokens->getNextTokenOfKind($index, [';']) + ); + } + + private function expandElement(Tokens $tokens, string $type, int $startIndex, int $endIndex): void + { + $divisionContent = null; + + if ($tokens[$startIndex - 1]->isWhitespace()) { + $divisionContent = $tokens[$startIndex - 1]->getContent(); + + if (Preg::match('#(\n|\r\n)#', $divisionContent, $matches)) { + $divisionContent = $matches[0].trim($divisionContent, "\r\n"); + } + } + + // iterate variables to split up + for ($i = $endIndex - 1; $i > $startIndex; --$i) { + $token = $tokens[$i]; + + if ($token->equals(')')) { + $i = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $i); + + continue; + } + + if ($token->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_CLOSE)) { + $i = $tokens->findBlockStart(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $i); + + continue; + } + + if (!$tokens[$i]->equals(',')) { + continue; + } + + $tokens[$i] = new Token(';'); + + if ($tokens[$i + 1]->isWhitespace()) { + $tokens->clearAt($i + 1); + } + + if (null !== $divisionContent && '' !== $divisionContent) { + $tokens->insertAt($i + 1, new Token([T_WHITESPACE, $divisionContent])); + } + + // collect modifiers + $sequence = $this->getModifiersSequences($tokens, $type, $startIndex, $endIndex); + $tokens->insertAt($i + 2, $sequence); + } + } + + /** + * @return Token[] + */ + private function getModifiersSequences(Tokens $tokens, string $type, int $startIndex, int $endIndex): array + { + if ('property' === $type) { + $tokenKinds = [T_PUBLIC, T_PROTECTED, T_PRIVATE, T_STATIC, T_VAR, T_STRING, T_NS_SEPARATOR, CT::T_NULLABLE_TYPE, CT::T_ARRAY_TYPEHINT, CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION]; + + if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required + $tokenKinds[] = T_READONLY; + } + } else { + $tokenKinds = [T_PUBLIC, T_PROTECTED, T_PRIVATE, T_CONST]; + } + + $sequence = []; + + for ($i = $startIndex; $i < $endIndex - 1; ++$i) { + if ($tokens[$i]->isComment()) { + continue; + } + + if (!$tokens[$i]->isWhitespace() && !$tokens[$i]->isGivenKind($tokenKinds)) { + break; + } + + $sequence[] = clone $tokens[$i]; + } + + return $sequence; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SingleTraitInsertPerStatementFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SingleTraitInsertPerStatementFixer.php new file mode 100644 index 00000000..e656cfc9 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SingleTraitInsertPerStatementFixer.php @@ -0,0 +1,116 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ClassNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class SingleTraitInsertPerStatementFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Each trait `use` must be done as single statement.', + [ + new CodeSample( + 'isTokenKindFound(CT::T_USE_TRAIT); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = \count($tokens) - 1; 1 < $index; --$index) { + if ($tokens[$index]->isGivenKind(CT::T_USE_TRAIT)) { + $candidates = $this->getCandidates($tokens, $index); + if (\count($candidates) > 0) { + $this->fixTraitUse($tokens, $index, $candidates); + } + } + } + } + + /** + * @param int[] $candidates ',' indices to fix + */ + private function fixTraitUse(Tokens $tokens, int $useTraitIndex, array $candidates): void + { + foreach ($candidates as $commaIndex) { + $inserts = [ + new Token([CT::T_USE_TRAIT, 'use']), + new Token([T_WHITESPACE, ' ']), + ]; + + $nextImportStartIndex = $tokens->getNextMeaningfulToken($commaIndex); + + if ($tokens[$nextImportStartIndex - 1]->isWhitespace()) { + if (Preg::match('/\R/', $tokens[$nextImportStartIndex - 1]->getContent())) { + array_unshift($inserts, clone $tokens[$useTraitIndex - 1]); + } + $tokens->clearAt($nextImportStartIndex - 1); + } + + $tokens[$commaIndex] = new Token(';'); + $tokens->insertAt($nextImportStartIndex, $inserts); + } + } + + /** + * @return list + */ + private function getCandidates(Tokens $tokens, int $index): array + { + $indices = []; + $index = $tokens->getNextTokenOfKind($index, [',', ';', '{']); + + while (!$tokens[$index]->equals(';')) { + if ($tokens[$index]->equals('{')) { + return []; // do not fix use cases with grouping + } + + $indices[] = $index; + $index = $tokens->getNextTokenOfKind($index, [',', ';', '{']); + } + + return array_reverse($indices); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/VisibilityRequiredFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/VisibilityRequiredFixer.php new file mode 100644 index 00000000..d98168c8 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/VisibilityRequiredFixer.php @@ -0,0 +1,199 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ClassNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * Fixer for rules defined in PSR2 ¶4.3, ¶4.5. + * + * @author Dariusz Rumiński + */ +final class VisibilityRequiredFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Visibility MUST be declared on all properties and methods; `abstract` and `final` MUST be declared before the visibility; `static` MUST be declared after the visibility.', + [ + new CodeSample( + ' ['const']] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before ClassAttributesSeparationFixer. + */ + public function getPriority(): int + { + return 56; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds()); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('elements', 'The structural elements to fix (PHP >= 7.1 required for `const`).')) + ->setAllowedTypes(['array']) + ->setAllowedValues([new AllowedValueSubset(['property', 'method', 'const'])]) + ->setDefault(['property', 'method', 'const']) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + + $propertyTypeDeclarationKinds = [T_STRING, T_NS_SEPARATOR, CT::T_NULLABLE_TYPE, CT::T_ARRAY_TYPEHINT, CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION, CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_OPEN, CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE]; + + if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required + $propertyReadOnlyType = T_READONLY; + $propertyTypeDeclarationKinds[] = T_READONLY; + } else { + $propertyReadOnlyType = -999; + } + + $expectedKindsGeneric = [T_ABSTRACT, T_FINAL, T_PRIVATE, T_PROTECTED, T_PUBLIC, T_STATIC, T_VAR]; + $expectedKindsPropertyKinds = [...$expectedKindsGeneric, ...$propertyTypeDeclarationKinds]; + + foreach (array_reverse($tokensAnalyzer->getClassyElements(), true) as $index => $element) { + if (!\in_array($element['type'], $this->configuration['elements'], true)) { + continue; + } + + $abstractFinalIndex = null; + $visibilityIndex = null; + $staticIndex = null; + $typeIndex = null; + $readOnlyIndex = null; + $prevIndex = $tokens->getPrevMeaningfulToken($index); + $expectedKinds = 'property' === $element['type'] + ? $expectedKindsPropertyKinds + : $expectedKindsGeneric; + + while ($tokens[$prevIndex]->isGivenKind($expectedKinds)) { + if ($tokens[$prevIndex]->isGivenKind([T_ABSTRACT, T_FINAL])) { + $abstractFinalIndex = $prevIndex; + } elseif ($tokens[$prevIndex]->isGivenKind(T_STATIC)) { + $staticIndex = $prevIndex; + } elseif ($tokens[$prevIndex]->isGivenKind($propertyReadOnlyType)) { + $readOnlyIndex = $prevIndex; + } elseif ($tokens[$prevIndex]->isGivenKind($propertyTypeDeclarationKinds)) { + $typeIndex = $prevIndex; + } else { + $visibilityIndex = $prevIndex; + } + + $prevIndex = $tokens->getPrevMeaningfulToken($prevIndex); + } + + if (null !== $typeIndex) { + $index = $typeIndex; + } + + if ($tokens[$prevIndex]->equals(',')) { + continue; + } + + $swapIndex = $staticIndex ?? $readOnlyIndex; // "static" property cannot be "readonly", so there can always be at most one swap + + if (null !== $swapIndex) { + if ($this->isKeywordPlacedProperly($tokens, $swapIndex, $index)) { + $index = $swapIndex; + } else { + $this->moveTokenAndEnsureSingleSpaceFollows($tokens, $swapIndex, $index); + } + } + + if (null === $visibilityIndex) { + $tokens->insertAt($index, [new Token([T_PUBLIC, 'public']), new Token([T_WHITESPACE, ' '])]); + } else { + if ($tokens[$visibilityIndex]->isGivenKind(T_VAR)) { + $tokens[$visibilityIndex] = new Token([T_PUBLIC, 'public']); + } + if ($this->isKeywordPlacedProperly($tokens, $visibilityIndex, $index)) { + $index = $visibilityIndex; + } else { + $this->moveTokenAndEnsureSingleSpaceFollows($tokens, $visibilityIndex, $index); + } + } + + if (null === $abstractFinalIndex) { + continue; + } + + if ($this->isKeywordPlacedProperly($tokens, $abstractFinalIndex, $index)) { + continue; + } + + $this->moveTokenAndEnsureSingleSpaceFollows($tokens, $abstractFinalIndex, $index); + } + } + + private function isKeywordPlacedProperly(Tokens $tokens, int $keywordIndex, int $comparedIndex): bool + { + return $keywordIndex + 2 === $comparedIndex && ' ' === $tokens[$keywordIndex + 1]->getContent(); + } + + private function moveTokenAndEnsureSingleSpaceFollows(Tokens $tokens, int $fromIndex, int $toIndex): void + { + $tokens->insertAt($toIndex, [$tokens[$fromIndex], new Token([T_WHITESPACE, ' '])]); + $tokens->clearAt($fromIndex); + + if ($tokens[$fromIndex + 1]->isWhitespace()) { + $tokens->clearAt($fromIndex + 1); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassUsage/DateTimeImmutableFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassUsage/DateTimeImmutableFixer.php new file mode 100644 index 00000000..2add0308 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassUsage/DateTimeImmutableFixer.php @@ -0,0 +1,146 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ClassUsage; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Kuba Werłos + */ +final class DateTimeImmutableFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Class `DateTimeImmutable` should be used instead of `DateTime`.', + [new CodeSample("isTokenKindFound(T_STRING); + } + + public function isRisky(): bool + { + return true; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $functionsAnalyzer = new FunctionsAnalyzer(); + $functionMap = [ + 'date_create' => 'date_create_immutable', + 'date_create_from_format' => 'date_create_immutable_from_format', + ]; + + $isInNamespace = false; + $isImported = false; // e.g. use DateTime; + + for ($index = 0, $limit = $tokens->count(); $index < $limit; ++$index) { + $token = $tokens[$index]; + + if ($token->isGivenKind(T_NAMESPACE)) { + $isInNamespace = true; + + continue; + } + + if ($isInNamespace && $token->isGivenKind(T_USE)) { + $nextIndex = $tokens->getNextMeaningfulToken($index); + + if ('datetime' !== strtolower($tokens[$nextIndex]->getContent())) { + continue; + } + + $nextNextIndex = $tokens->getNextMeaningfulToken($nextIndex); + + if ($tokens[$nextNextIndex]->equals(';')) { + $isImported = true; + } + + $index = $nextNextIndex; + + continue; + } + + if (!$token->isGivenKind(T_STRING)) { + continue; + } + + $prevIndex = $tokens->getPrevMeaningfulToken($index); + + if ($tokens[$prevIndex]->isGivenKind(T_FUNCTION)) { + continue; + } + + $lowercaseContent = strtolower($token->getContent()); + + if ('datetime' === $lowercaseContent) { + $this->fixClassUsage($tokens, $index, $isInNamespace, $isImported); + $limit = $tokens->count(); // update limit, as fixing class usage may insert new token + + continue; + } + + if (isset($functionMap[$lowercaseContent]) && $functionsAnalyzer->isGlobalFunctionCall($tokens, $index)) { + $tokens[$index] = new Token([T_STRING, $functionMap[$lowercaseContent]]); + } + } + } + + private function fixClassUsage(Tokens $tokens, int $index, bool $isInNamespace, bool $isImported): void + { + $nextIndex = $tokens->getNextMeaningfulToken($index); + if ($tokens[$nextIndex]->isGivenKind(T_DOUBLE_COLON)) { + $nextNextIndex = $tokens->getNextMeaningfulToken($nextIndex); + if ($tokens[$nextNextIndex]->isGivenKind(T_STRING)) { + $nextNextNextIndex = $tokens->getNextMeaningfulToken($nextNextIndex); + if (!$tokens[$nextNextNextIndex]->equals('(')) { + return; + } + } + } + + $isUsedAlone = false; // e.g. new DateTime(); + $isUsedWithLeadingBackslash = false; // e.g. new \DateTime(); + + $prevIndex = $tokens->getPrevMeaningfulToken($index); + if ($tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) { + $prevPrevIndex = $tokens->getPrevMeaningfulToken($prevIndex); + if (!$tokens[$prevPrevIndex]->isGivenKind(T_STRING)) { + $isUsedWithLeadingBackslash = true; + } + } elseif (!$tokens[$prevIndex]->isGivenKind(T_DOUBLE_COLON) && !$tokens[$prevIndex]->isObjectOperator()) { + $isUsedAlone = true; + } + + if ($isUsedWithLeadingBackslash || $isUsedAlone && ($isInNamespace && $isImported || !$isInNamespace)) { + $tokens[$index] = new Token([T_STRING, \DateTimeImmutable::class]); + if ($isInNamespace && $isUsedAlone) { + $tokens->insertAt($index, new Token([T_NS_SEPARATOR, '\\'])); + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/CommentToPhpdocFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/CommentToPhpdocFixer.php new file mode 100644 index 00000000..aedaa12b --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/CommentToPhpdocFixer.php @@ -0,0 +1,219 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Comment; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\CommentsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Utils; + +/** + * @author Kuba Werłos + */ +final class CommentToPhpdocFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface +{ + /** + * @var string[] + */ + private array $ignoredTags = []; + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_COMMENT); + } + + public function isRisky(): bool + { + return true; + } + + /** + * {@inheritdoc} + * + * Must run before GeneralPhpdocAnnotationRemoveFixer, GeneralPhpdocTagRenameFixer, NoBlankLinesAfterPhpdocFixer, NoEmptyPhpdocFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocAlignFixer, PhpdocAnnotationWithoutDotFixer, PhpdocArrayTypeFixer, PhpdocInlineTagNormalizerFixer, PhpdocLineSpanFixer, PhpdocListTypeFixer, PhpdocNoAccessFixer, PhpdocNoAliasTagFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocNoUselessInheritdocFixer, PhpdocOrderByValueFixer, PhpdocOrderFixer, PhpdocParamOrderFixer, PhpdocReadonlyClassCommentToKeywordFixer, PhpdocReturnSelfReferenceFixer, PhpdocSeparationFixer, PhpdocSingleLineVarSpacingFixer, PhpdocSummaryFixer, PhpdocTagCasingFixer, PhpdocTagTypeFixer, PhpdocToCommentFixer, PhpdocToParamTypeFixer, PhpdocToPropertyTypeFixer, PhpdocToReturnTypeFixer, PhpdocTrimConsecutiveBlankLineSeparationFixer, PhpdocTrimFixer, PhpdocTypesOrderFixer, PhpdocVarAnnotationCorrectOrderFixer, PhpdocVarWithoutNameFixer. + * Must run after AlignMultilineCommentFixer. + */ + public function getPriority(): int + { + // Should be run before all other PHPDoc fixers + return 26; + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Comments with annotation should be docblock when used on structural elements.', + [ + new CodeSample(" ['todo']]), + ], + null, + 'Risky as new docblocks might mean more, e.g. a Doctrine entity might have a new column in database.' + ); + } + + public function configure(array $configuration): void + { + parent::configure($configuration); + + $this->ignoredTags = array_map( + static fn (string $tag): string => strtolower($tag), + $this->configuration['ignored_tags'] + ); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('ignored_tags', 'List of ignored tags.')) + ->setAllowedTypes(['array']) + ->setDefault([]) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $commentsAnalyzer = new CommentsAnalyzer(); + + for ($index = 0, $limit = \count($tokens); $index < $limit; ++$index) { + $token = $tokens[$index]; + + if (!$token->isGivenKind(T_COMMENT)) { + continue; + } + + if ($commentsAnalyzer->isHeaderComment($tokens, $index)) { + continue; + } + + if (!$commentsAnalyzer->isBeforeStructuralElement($tokens, $index)) { + continue; + } + + $commentIndices = $commentsAnalyzer->getCommentBlockIndices($tokens, $index); + + if ($this->isCommentCandidate($tokens, $commentIndices)) { + $this->fixComment($tokens, $commentIndices); + } + + $index = max($commentIndices); + } + } + + /** + * @param int[] $indices + */ + private function isCommentCandidate(Tokens $tokens, array $indices): bool + { + return array_reduce( + $indices, + function (bool $carry, int $index) use ($tokens): bool { + if ($carry) { + return true; + } + if (!Preg::match('~(?:#|//|/\*+|\R(?:\s*\*)?)\s*\@([a-zA-Z0-9_\\\\-]+)(?=\s|\(|$)~', $tokens[$index]->getContent(), $matches)) { + return false; + } + + return !\in_array(strtolower($matches[1]), $this->ignoredTags, true); + }, + false + ); + } + + /** + * @param int[] $indices + */ + private function fixComment(Tokens $tokens, array $indices): void + { + if (1 === \count($indices)) { + $this->fixCommentSingleLine($tokens, reset($indices)); + } else { + $this->fixCommentMultiLine($tokens, $indices); + } + } + + private function fixCommentSingleLine(Tokens $tokens, int $index): void + { + $message = $this->getMessage($tokens[$index]->getContent()); + + if ('' !== trim(substr($message, 0, 1))) { + $message = ' '.$message; + } + + if ('' !== trim(substr($message, -1))) { + $message .= ' '; + } + + $tokens[$index] = new Token([T_DOC_COMMENT, '/**'.$message.'*/']); + } + + /** + * @param int[] $indices + */ + private function fixCommentMultiLine(Tokens $tokens, array $indices): void + { + $startIndex = reset($indices); + $indent = Utils::calculateTrailingWhitespaceIndent($tokens[$startIndex - 1]); + + $newContent = '/**'.$this->whitespacesConfig->getLineEnding(); + $count = max($indices); + + for ($index = $startIndex; $index <= $count; ++$index) { + if (!$tokens[$index]->isComment()) { + continue; + } + if (str_contains($tokens[$index]->getContent(), '*/')) { + return; + } + $message = $this->getMessage($tokens[$index]->getContent()); + if ('' !== trim(substr($message, 0, 1))) { + $message = ' '.$message; + } + $newContent .= $indent.' *'.$message.$this->whitespacesConfig->getLineEnding(); + } + + for ($index = $startIndex; $index <= $count; ++$index) { + $tokens->clearAt($index); + } + + $newContent .= $indent.' */'; + + $tokens->insertAt($startIndex, new Token([T_DOC_COMMENT, $newContent])); + } + + private function getMessage(string $content): string + { + if (str_starts_with($content, '#')) { + return substr($content, 1); + } + if (str_starts_with($content, '//')) { + return substr($content, 2); + } + + return rtrim(ltrim($content, '/*'), '*/'); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/HeaderCommentFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/HeaderCommentFixer.php new file mode 100644 index 00000000..1915cf23 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/HeaderCommentFixer.php @@ -0,0 +1,438 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Comment; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use Symfony\Component\OptionsResolver\Options; + +/** + * @author Antonio J. García Lagar + */ +final class HeaderCommentFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface +{ + /** + * @internal + */ + public const HEADER_PHPDOC = 'PHPDoc'; + + /** + * @internal + */ + public const HEADER_COMMENT = 'comment'; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Add, replace or remove header comment.', + [ + new CodeSample( + ' 'Made with love.', + ] + ), + new CodeSample( + ' 'Made with love.', + 'comment_type' => 'PHPDoc', + 'location' => 'after_open', + 'separate' => 'bottom', + ] + ), + new CodeSample( + ' 'Made with love.', + 'comment_type' => 'comment', + 'location' => 'after_declare_strict', + ] + ), + new CodeSample( + ' '', + ] + ), + ] + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isMonolithicPhp() && !$tokens->isTokenKindFound(T_OPEN_TAG_WITH_ECHO); + } + + /** + * {@inheritdoc} + * + * Must run before BlankLinesBeforeNamespaceFixer, SingleBlankLineBeforeNamespaceFixer, SingleLineCommentStyleFixer. + * Must run after DeclareStrictTypesFixer, NoBlankLinesAfterPhpdocFixer. + */ + public function getPriority(): int + { + // When this fixer is configured with ["separate" => "bottom", "comment_type" => "PHPDoc"] + // and the target file has no namespace or declare() construct, + // the fixed header comment gets trimmed by NoBlankLinesAfterPhpdocFixer if we run before it. + return -30; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $location = $this->configuration['location']; + $locationIndices = []; + + foreach (['after_open', 'after_declare_strict'] as $possibleLocation) { + $locationIndex = $this->findHeaderCommentInsertionIndex($tokens, $possibleLocation); + + if (!isset($locationIndices[$locationIndex]) || $possibleLocation === $location) { + $locationIndices[$locationIndex] = $possibleLocation; + } + } + + foreach ($locationIndices as $possibleLocation) { + // figure out where the comment should be placed + $headerNewIndex = $this->findHeaderCommentInsertionIndex($tokens, $possibleLocation); + + // check if there is already a comment + $headerCurrentIndex = $this->findHeaderCommentCurrentIndex($tokens, $headerNewIndex - 1); + + if (null === $headerCurrentIndex) { + if ('' === $this->configuration['header'] || $possibleLocation !== $location) { + continue; + } + + $this->insertHeader($tokens, $headerNewIndex); + + continue; + } + + $sameComment = $this->getHeaderAsComment() === $tokens[$headerCurrentIndex]->getContent(); + $expectedLocation = $possibleLocation === $location; + + if (!$sameComment || !$expectedLocation) { + if ($expectedLocation xor $sameComment) { + $this->removeHeader($tokens, $headerCurrentIndex); + } + + if ('' === $this->configuration['header']) { + continue; + } + + if ($possibleLocation === $location) { + $this->insertHeader($tokens, $headerNewIndex); + } + + continue; + } + + $this->fixWhiteSpaceAroundHeader($tokens, $headerCurrentIndex); + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + $fixerName = $this->getName(); + + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('header', 'Proper header content.')) + ->setAllowedTypes(['string']) + ->setNormalizer(static function (Options $options, string $value) use ($fixerName): string { + if ('' === trim($value)) { + return ''; + } + + if (str_contains($value, '*/')) { + throw new InvalidFixerConfigurationException($fixerName, 'Cannot use \'*/\' in header.'); + } + + return $value; + }) + ->getOption(), + (new FixerOptionBuilder('comment_type', 'Comment syntax type.')) + ->setAllowedValues([self::HEADER_PHPDOC, self::HEADER_COMMENT]) + ->setDefault(self::HEADER_COMMENT) + ->getOption(), + (new FixerOptionBuilder('location', 'The location of the inserted header.')) + ->setAllowedValues(['after_open', 'after_declare_strict']) + ->setDefault('after_declare_strict') + ->getOption(), + (new FixerOptionBuilder('separate', 'Whether the header should be separated from the file content with a new line.')) + ->setAllowedValues(['both', 'top', 'bottom', 'none']) + ->setDefault('both') + ->getOption(), + ]); + } + + /** + * Enclose the given text in a comment block. + */ + private function getHeaderAsComment(): string + { + $lineEnding = $this->whitespacesConfig->getLineEnding(); + $comment = (self::HEADER_COMMENT === $this->configuration['comment_type'] ? '/*' : '/**').$lineEnding; + $lines = explode("\n", str_replace("\r", '', $this->configuration['header'])); + + foreach ($lines as $line) { + $comment .= rtrim(' * '.$line).$lineEnding; + } + + return $comment.' */'; + } + + private function findHeaderCommentCurrentIndex(Tokens $tokens, int $headerNewIndex): ?int + { + $index = $tokens->getNextNonWhitespace($headerNewIndex); + + if (null === $index || !$tokens[$index]->isComment()) { + return null; + } + + $next = $index + 1; + + if (!isset($tokens[$next]) || \in_array($this->configuration['separate'], ['top', 'none'], true) || !$tokens[$index]->isGivenKind(T_DOC_COMMENT)) { + return $index; + } + + if ($tokens[$next]->isWhitespace()) { + if (!Preg::match('/^\h*\R\h*$/D', $tokens[$next]->getContent())) { + return $index; + } + + ++$next; + } + + if (!isset($tokens[$next]) || !$tokens[$next]->isClassy() && !$tokens[$next]->isGivenKind(T_FUNCTION)) { + return $index; + } + + return $this->getHeaderAsComment() === $tokens[$index]->getContent() ? $index : null; + } + + /** + * Find the index where the header comment must be inserted. + */ + private function findHeaderCommentInsertionIndex(Tokens $tokens, string $location): int + { + $openTagIndex = $tokens[0]->isGivenKind(T_INLINE_HTML) ? 1 : 0; + + if ('after_open' === $location) { + return $openTagIndex + 1; + } + + $index = $tokens->getNextMeaningfulToken($openTagIndex); + + if (null === $index) { + return $openTagIndex + 1; // file without meaningful tokens but an open tag, comment should always be placed directly after the open tag + } + + if (!$tokens[$index]->isGivenKind(T_DECLARE)) { + return $openTagIndex + 1; + } + + $next = $tokens->getNextMeaningfulToken($index); + + if (null === $next || !$tokens[$next]->equals('(')) { + return $openTagIndex + 1; + } + + $next = $tokens->getNextMeaningfulToken($next); + + if (null === $next || !$tokens[$next]->equals([T_STRING, 'strict_types'], false)) { + return $openTagIndex + 1; + } + + $next = $tokens->getNextMeaningfulToken($next); + + if (null === $next || !$tokens[$next]->equals('=')) { + return $openTagIndex + 1; + } + + $next = $tokens->getNextMeaningfulToken($next); + + if (null === $next || !$tokens[$next]->isGivenKind(T_LNUMBER)) { + return $openTagIndex + 1; + } + + $next = $tokens->getNextMeaningfulToken($next); + + if (null === $next || !$tokens[$next]->equals(')')) { + return $openTagIndex + 1; + } + + $next = $tokens->getNextMeaningfulToken($next); + + if (null === $next || !$tokens[$next]->equals(';')) { // don't insert after close tag + return $openTagIndex + 1; + } + + return $next + 1; + } + + private function fixWhiteSpaceAroundHeader(Tokens $tokens, int $headerIndex): void + { + $lineEnding = $this->whitespacesConfig->getLineEnding(); + + // fix lines after header comment + if ( + ('both' === $this->configuration['separate'] || 'bottom' === $this->configuration['separate']) + && null !== $tokens->getNextMeaningfulToken($headerIndex) + ) { + $expectedLineCount = 2; + } else { + $expectedLineCount = 1; + } + + if ($headerIndex === \count($tokens) - 1) { + $tokens->insertAt($headerIndex + 1, new Token([T_WHITESPACE, str_repeat($lineEnding, $expectedLineCount)])); + } else { + $lineBreakCount = $this->getLineBreakCount($tokens, $headerIndex, 1); + + if ($lineBreakCount < $expectedLineCount) { + $missing = str_repeat($lineEnding, $expectedLineCount - $lineBreakCount); + + if ($tokens[$headerIndex + 1]->isWhitespace()) { + $tokens[$headerIndex + 1] = new Token([T_WHITESPACE, $missing.$tokens[$headerIndex + 1]->getContent()]); + } else { + $tokens->insertAt($headerIndex + 1, new Token([T_WHITESPACE, $missing])); + } + } elseif ($lineBreakCount > $expectedLineCount && $tokens[$headerIndex + 1]->isWhitespace()) { + $newLinesToRemove = $lineBreakCount - $expectedLineCount; + $tokens[$headerIndex + 1] = new Token([ + T_WHITESPACE, + Preg::replace("/^\\R{{$newLinesToRemove}}/", '', $tokens[$headerIndex + 1]->getContent()), + ]); + } + } + + // fix lines before header comment + $expectedLineCount = 'both' === $this->configuration['separate'] || 'top' === $this->configuration['separate'] ? 2 : 1; + $prev = $tokens->getPrevNonWhitespace($headerIndex); + + $regex = '/\h$/'; + + if ($tokens[$prev]->isGivenKind(T_OPEN_TAG) && Preg::match($regex, $tokens[$prev]->getContent())) { + $tokens[$prev] = new Token([T_OPEN_TAG, Preg::replace($regex, $lineEnding, $tokens[$prev]->getContent())]); + } + + $lineBreakCount = $this->getLineBreakCount($tokens, $headerIndex, -1); + + if ($lineBreakCount < $expectedLineCount) { + // because of the way the insert index was determined for header comment there cannot be an empty token here + $tokens->insertAt($headerIndex, new Token([T_WHITESPACE, str_repeat($lineEnding, $expectedLineCount - $lineBreakCount)])); + } + } + + private function getLineBreakCount(Tokens $tokens, int $index, int $direction): int + { + $whitespace = ''; + + for ($index += $direction; isset($tokens[$index]); $index += $direction) { + $token = $tokens[$index]; + + if ($token->isWhitespace()) { + $whitespace .= $token->getContent(); + + continue; + } + + if (-1 === $direction && $token->isGivenKind(T_OPEN_TAG)) { + $whitespace .= $token->getContent(); + } + + if ('' !== $token->getContent()) { + break; + } + } + + return substr_count($whitespace, "\n"); + } + + private function removeHeader(Tokens $tokens, int $index): void + { + $prevIndex = $index - 1; + $prevToken = $tokens[$prevIndex]; + $newlineRemoved = false; + + if ($prevToken->isWhitespace()) { + $content = $prevToken->getContent(); + + if (Preg::match('/\R/', $content)) { + $newlineRemoved = true; + } + + $content = Preg::replace('/\R?\h*$/', '', $content); + + $tokens->ensureWhitespaceAtIndex($prevIndex, 0, $content); + } + + $nextIndex = $index + 1; + $nextToken = $tokens[$nextIndex] ?? null; + + if (!$newlineRemoved && null !== $nextToken && $nextToken->isWhitespace()) { + $content = Preg::replace('/^\R/', '', $nextToken->getContent()); + + $tokens->ensureWhitespaceAtIndex($nextIndex, 0, $content); + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } + + private function insertHeader(Tokens $tokens, int $index): void + { + $tokens->insertAt($index, new Token([self::HEADER_COMMENT === $this->configuration['comment_type'] ? T_COMMENT : T_DOC_COMMENT, $this->getHeaderAsComment()])); + $this->fixWhiteSpaceAroundHeader($tokens, $index); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/MultilineCommentOpeningClosingFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/MultilineCommentOpeningClosingFixer.php new file mode 100644 index 00000000..dc4b7dc3 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/MultilineCommentOpeningClosingFixer.php @@ -0,0 +1,89 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Comment; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Filippo Tessarotto + */ +final class MultilineCommentOpeningClosingFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'DocBlocks must start with two asterisks, multiline comments must start with a single asterisk, after the opening slash. Both must end with a single asterisk before the closing slash.', + [ + new CodeSample( + <<<'EOT' + isAnyTokenKindsFound([T_COMMENT, T_DOC_COMMENT]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + $originalContent = $token->getContent(); + + if ( + !$token->isGivenKind(T_DOC_COMMENT) + && !($token->isGivenKind(T_COMMENT) && str_starts_with($originalContent, '/*')) + ) { + continue; + } + + $newContent = $originalContent; + + // Fix opening + if ($token->isGivenKind(T_COMMENT)) { + $newContent = Preg::replace('/^\\/\\*{2,}(?!\\/)/', '/*', $newContent); + } + + // Fix closing + $newContent = Preg::replace('/(?getId(), $newContent]); + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/NoEmptyCommentFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/NoEmptyCommentFixer.php new file mode 100644 index 00000000..23f0b1a3 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/NoEmptyCommentFixer.php @@ -0,0 +1,162 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Comment; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Tokens; + +final class NoEmptyCommentFixer extends AbstractFixer +{ + private const TYPE_HASH = 1; + + private const TYPE_DOUBLE_SLASH = 2; + + private const TYPE_SLASH_ASTERISK = 3; + + /** + * {@inheritdoc} + * + * Must run before NoExtraBlankLinesFixer, NoTrailingWhitespaceFixer, NoWhitespaceInBlankLineFixer. + * Must run after PhpdocToCommentFixer. + */ + public function getPriority(): int + { + return 2; + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'There should not be any empty comments.', + [new CodeSample("isTokenKindFound(T_COMMENT); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = 1, $count = \count($tokens); $index < $count; ++$index) { + if (!$tokens[$index]->isGivenKind(T_COMMENT)) { + continue; + } + + $blockInfo = $this->getCommentBlock($tokens, $index); + $blockStart = $blockInfo['blockStart']; + $index = $blockInfo['blockEnd']; + $isEmpty = $blockInfo['isEmpty']; + + if (false === $isEmpty) { + continue; + } + + for ($i = $blockStart; $i <= $index; ++$i) { + $tokens->clearTokenAndMergeSurroundingWhitespace($i); + } + } + } + + /** + * Return the start index, end index and a flag stating if the comment block is empty. + * + * @param int $index T_COMMENT index + * + * @return array{blockStart: int, blockEnd: int, isEmpty: bool} + */ + private function getCommentBlock(Tokens $tokens, int $index): array + { + $commentType = $this->getCommentType($tokens[$index]->getContent()); + $empty = $this->isEmptyComment($tokens[$index]->getContent()); + + if (self::TYPE_SLASH_ASTERISK === $commentType) { + return [ + 'blockStart' => $index, + 'blockEnd' => $index, + 'isEmpty' => $empty, + ]; + } + + $start = $index; + $count = \count($tokens); + ++$index; + + for (; $index < $count; ++$index) { + if ($tokens[$index]->isComment()) { + if ($commentType !== $this->getCommentType($tokens[$index]->getContent())) { + break; + } + + if ($empty) { // don't retest if already known the block not being empty + $empty = $this->isEmptyComment($tokens[$index]->getContent()); + } + + continue; + } + + if (!$tokens[$index]->isWhitespace() || $this->getLineBreakCount($tokens, $index, $index + 1) > 1) { + break; + } + } + + return [ + 'blockStart' => $start, + 'blockEnd' => $index - 1, + 'isEmpty' => $empty, + ]; + } + + private function getCommentType(string $content): int + { + if (str_starts_with($content, '#')) { + return self::TYPE_HASH; + } + + if ('*' === $content[1]) { + return self::TYPE_SLASH_ASTERISK; + } + + return self::TYPE_DOUBLE_SLASH; + } + + private function getLineBreakCount(Tokens $tokens, int $whiteStart, int $whiteEnd): int + { + $lineCount = 0; + for ($i = $whiteStart; $i < $whiteEnd; ++$i) { + $lineCount += Preg::matchAll('/\R/u', $tokens[$i]->getContent(), $matches); + } + + return $lineCount; + } + + private function isEmptyComment(string $content): bool + { + static $mapper = [ + self::TYPE_HASH => '|^#\s*$|', // single line comment starting with '#' + self::TYPE_SLASH_ASTERISK => '|^/\*[\s\*]*\*+/$|', // comment starting with '/*' and ending with '*/' (but not a PHPDoc) + self::TYPE_DOUBLE_SLASH => '|^//\s*$|', // single line comment starting with '//' + ]; + + $type = $this->getCommentType($content); + + return Preg::match($mapper[$type], $content); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/NoTrailingWhitespaceInCommentFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/NoTrailingWhitespaceInCommentFixer.php new file mode 100644 index 00000000..d17a4495 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/NoTrailingWhitespaceInCommentFixer.php @@ -0,0 +1,75 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Comment; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class NoTrailingWhitespaceInCommentFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'There MUST be no trailing spaces inside comment or PHPDoc.', + [new CodeSample('isAnyTokenKindsFound([T_COMMENT, T_DOC_COMMENT]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if ($token->isGivenKind(T_DOC_COMMENT)) { + $tokens[$index] = new Token([T_DOC_COMMENT, Preg::replace('/(*ANY)[\h]+$/m', '', $token->getContent())]); + + continue; + } + + if ($token->isGivenKind(T_COMMENT)) { + if (str_starts_with($token->getContent(), '/*')) { + $tokens[$index] = new Token([T_COMMENT, Preg::replace('/(*ANY)[\h]+$/m', '', $token->getContent())]); + } elseif (isset($tokens[$index + 1]) && $tokens[$index + 1]->isWhitespace()) { + $trimmedContent = ltrim($tokens[$index + 1]->getContent(), " \t"); + $tokens->ensureWhitespaceAtIndex($index + 1, 0, $trimmedContent); + } + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/SingleLineCommentSpacingFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/SingleLineCommentSpacingFixer.php new file mode 100644 index 00000000..ecf09eca --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/SingleLineCommentSpacingFixer.php @@ -0,0 +1,110 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Comment; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class SingleLineCommentSpacingFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Single-line comments must have proper spacing.', + [ + new CodeSample( + 'isTokenKindFound(T_COMMENT); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = \count($tokens) - 1; 0 <= $index; --$index) { + $token = $tokens[$index]; + + if (!$token->isGivenKind(T_COMMENT)) { + continue; + } + + $content = $token->getContent(); + $contentLength = \strlen($content); + + if ('/' === $content[0]) { + if ($contentLength < 3) { + continue; // cheap check for "//" + } + + if ('*' === $content[1]) { // slash asterisk comment + if ($contentLength < 5 || '*' === $content[2] || str_contains($content, "\n")) { + continue; // cheap check for "/**/", comment that looks like a PHPDoc, or multi line comment + } + + $newContent = rtrim(substr($content, 0, -2)).' '.substr($content, -2); + $newContent = $this->fixCommentLeadingSpace($newContent, '/*'); + } else { // double slash comment + $newContent = $this->fixCommentLeadingSpace($content, '//'); + } + } else { // hash comment + if ($contentLength < 2 || '[' === $content[1]) { // cheap check for "#" or annotation (like) comment + continue; + } + + $newContent = $this->fixCommentLeadingSpace($content, '#'); + } + + if ($newContent !== $content) { + $tokens[$index] = new Token([T_COMMENT, $newContent]); + } + } + } + + // fix space between comment open and leading text + private function fixCommentLeadingSpace(string $content, string $prefix): string + { + if (Preg::match(sprintf('@^%s\h+.*$@', preg_quote($prefix, '@')), $content)) { + return $content; + } + + $position = \strlen($prefix); + + return substr($content, 0, $position).' '.substr($content, $position); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/SingleLineCommentStyleFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/SingleLineCommentStyleFixer.php new file mode 100644 index 00000000..f0b38658 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/SingleLineCommentStyleFixer.php @@ -0,0 +1,173 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Comment; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Filippo Tessarotto + */ +final class SingleLineCommentStyleFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** + * @var bool + */ + private $asteriskEnabled; + + /** + * @var bool + */ + private $hashEnabled; + + public function configure(array $configuration): void + { + parent::configure($configuration); + + $this->asteriskEnabled = \in_array('asterisk', $this->configuration['comment_types'], true); + $this->hashEnabled = \in_array('hash', $this->configuration['comment_types'], true); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Single-line comments and multi-line comments with only one line of actual content should use the `//` syntax.', + [ + new CodeSample( + ' ['asterisk']] + ), + new CodeSample( + " ['hash']] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run after HeaderCommentFixer, NoUselessReturnFixer, PhpdocToCommentFixer. + */ + public function getPriority(): int + { + return -31; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_COMMENT); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_COMMENT)) { + continue; + } + + $content = $token->getContent(); + + /** @TODO PHP 8.0 - no more need for `?: ''` */ + $commentContent = substr($content, 2, -2) ?: ''; // @phpstan-ignore-line + + if ($this->hashEnabled && str_starts_with($content, '#')) { + if (isset($content[1]) && '[' === $content[1]) { + continue; // This might be an attribute on PHP8, do not change + } + + $tokens[$index] = new Token([$token->getId(), '//'.substr($content, 1)]); + + continue; + } + + if ( + !$this->asteriskEnabled + || str_contains($commentContent, '?>') + || !str_starts_with($content, '/*') + || Preg::match('/[^\s\*].*\R.*[^\s\*]/s', $commentContent) + ) { + continue; + } + + $nextTokenIndex = $index + 1; + if (isset($tokens[$nextTokenIndex])) { + $nextToken = $tokens[$nextTokenIndex]; + if (!$nextToken->isWhitespace() || !Preg::match('/\R/', $nextToken->getContent())) { + continue; + } + + $tokens[$nextTokenIndex] = new Token([$nextToken->getId(), ltrim($nextToken->getContent(), " \t")]); + } + + $content = '//'; + if (Preg::match('/[^\s\*]/', $commentContent)) { + $content = '// '.Preg::replace('/[\s\*]*([^\s\*](?:.+[^\s\*])?)[\s\*]*/', '\1', $commentContent); + } + $tokens[$index] = new Token([$token->getId(), $content]); + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('comment_types', 'List of comment types to fix.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([new AllowedValueSubset(['asterisk', 'hash'])]) + ->setDefault(['asterisk', 'hash']) + ->getOption(), + ]); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ConfigurableFixerInterface.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ConfigurableFixerInterface.php new file mode 100644 index 00000000..24ce993b --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ConfigurableFixerInterface.php @@ -0,0 +1,47 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer; + +use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; + +/** + * @author Dariusz Rumiński + */ +interface ConfigurableFixerInterface extends FixerInterface +{ + /** + * Set configuration. + * + * New configuration must override current one, not patch it. + * Using empty array makes fixer to use default configuration + * (or reset configuration from previously configured back to default one). + * + * Some fixers may have no configuration, then - simply don't implement this interface. + * Other ones may have configuration that will change behavior of fixer, + * eg `php_unit_strict` fixer allows to configure which methods should be fixed. + * Finally, some fixers need configuration to work, eg `header_comment`. + * + * @param array $configuration configuration depends on Fixer + * + * @throws InvalidFixerConfigurationException + */ + public function configure(array $configuration): void; + + /** + * Defines the available configuration options of the fixer. + */ + public function getConfigurationDefinition(): FixerConfigurationResolverInterface; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ConstantNotation/NativeConstantInvocationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ConstantNotation/NativeConstantInvocationFixer.php new file mode 100644 index 00000000..ed767cf0 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ConstantNotation/NativeConstantInvocationFixer.php @@ -0,0 +1,287 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ConstantNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; + +/** + * @author Filippo Tessarotto + */ +final class NativeConstantInvocationFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** + * @var array + */ + private array $constantsToEscape = []; + + /** + * @var array + */ + private array $caseInsensitiveConstantsToEscape = []; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Add leading `\` before constant invocation of internal constant to speed up resolving. Constant name match is case-sensitive, except for `null`, `false` and `true`.', + [ + new CodeSample(" 'namespaced'] + ), + new CodeSample( + " [ + 'MY_CUSTOM_PI', + ], + ] + ), + new CodeSample( + " false, + 'include' => [ + 'MY_CUSTOM_PI', + ], + ] + ), + new CodeSample( + " [ + 'M_PI', + ], + ] + ), + ], + null, + 'Risky when any of the constants are namespaced or overridden.' + ); + } + + /** + * {@inheritdoc} + * + * Must run before GlobalNamespaceImportFixer. + * Must run after FunctionToConstantFixer. + */ + public function getPriority(): int + { + return 1; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_STRING); + } + + public function isRisky(): bool + { + return true; + } + + public function configure(array $configuration): void + { + parent::configure($configuration); + + $uniqueConfiguredExclude = array_unique($this->configuration['exclude']); + + // Case-sensitive constants handling + $constantsToEscape = array_values($this->configuration['include']); + + if (true === $this->configuration['fix_built_in']) { + $getDefinedConstants = get_defined_constants(true); + unset($getDefinedConstants['user']); + foreach ($getDefinedConstants as $constants) { + $constantsToEscape = [...$constantsToEscape, ...array_keys($constants)]; + } + } + + $constantsToEscape = array_diff( + array_unique($constantsToEscape), + $uniqueConfiguredExclude + ); + + // Case-insensitive constants handling + static $caseInsensitiveConstants = ['null', 'false', 'true']; + $caseInsensitiveConstantsToEscape = []; + + foreach ($constantsToEscape as $constantIndex => $constant) { + $loweredConstant = strtolower($constant); + if (\in_array($loweredConstant, $caseInsensitiveConstants, true)) { + $caseInsensitiveConstantsToEscape[] = $loweredConstant; + unset($constantsToEscape[$constantIndex]); + } + } + + $caseInsensitiveConstantsToEscape = array_diff( + array_unique($caseInsensitiveConstantsToEscape), + array_map( + static fn (string $function): string => strtolower($function), + $uniqueConfiguredExclude, + ), + ); + + // Store the cache + $this->constantsToEscape = array_fill_keys($constantsToEscape, true); + ksort($this->constantsToEscape); + + $this->caseInsensitiveConstantsToEscape = array_fill_keys($caseInsensitiveConstantsToEscape, true); + ksort($this->caseInsensitiveConstantsToEscape); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + if ('all' === $this->configuration['scope']) { + $this->fixConstantInvocations($tokens, 0, \count($tokens) - 1); + + return; + } + + $namespaces = $tokens->getNamespaceDeclarations(); + + // 'scope' is 'namespaced' here + /** @var NamespaceAnalysis $namespace */ + foreach (array_reverse($namespaces) as $namespace) { + if ($namespace->isGlobalNamespace()) { + continue; + } + + $this->fixConstantInvocations($tokens, $namespace->getScopeStartIndex(), $namespace->getScopeEndIndex()); + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + $constantChecker = static function (array $value): bool { + foreach ($value as $constantName) { + if (!\is_string($constantName) || '' === trim($constantName) || trim($constantName) !== $constantName) { + throw new InvalidOptionsException(sprintf( + 'Each element must be a non-empty, trimmed string, got "%s" instead.', + get_debug_type($constantName) + )); + } + } + + return true; + }; + + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('fix_built_in', 'Whether to fix constants returned by `get_defined_constants`. User constants are not accounted in this list and must be specified in the include one.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), + (new FixerOptionBuilder('include', 'List of additional constants to fix.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([$constantChecker]) + ->setDefault([]) + ->getOption(), + (new FixerOptionBuilder('exclude', 'List of constants to ignore.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([$constantChecker]) + ->setDefault(['null', 'false', 'true']) + ->getOption(), + (new FixerOptionBuilder('scope', 'Only fix constant invocations that are made within a namespace or fix all.')) + ->setAllowedValues(['all', 'namespaced']) + ->setDefault('all') + ->getOption(), + (new FixerOptionBuilder('strict', 'Whether leading `\` of constant invocation not meant to have it should be removed.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), + ]); + } + + private function fixConstantInvocations(Tokens $tokens, int $startIndex, int $endIndex): void + { + $useDeclarations = (new NamespaceUsesAnalyzer())->getDeclarationsFromTokens($tokens); + $useConstantDeclarations = []; + + foreach ($useDeclarations as $use) { + if ($use->isConstant()) { + $useConstantDeclarations[$use->getShortName()] = true; + } + } + + $tokenAnalyzer = new TokensAnalyzer($tokens); + + for ($index = $endIndex; $index > $startIndex; --$index) { + $token = $tokens[$index]; + + // test if we are at a constant call + if (!$token->isGivenKind(T_STRING)) { + continue; + } + + if (!$tokenAnalyzer->isConstantInvocation($index)) { + continue; + } + + $tokenContent = $token->getContent(); + $prevIndex = $tokens->getPrevMeaningfulToken($index); + + if (!isset($this->constantsToEscape[$tokenContent]) && !isset($this->caseInsensitiveConstantsToEscape[strtolower($tokenContent)])) { + if (false === $this->configuration['strict']) { + continue; + } + + if (!$tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) { + continue; + } + + $prevPrevIndex = $tokens->getPrevMeaningfulToken($prevIndex); + + if ($tokens[$prevPrevIndex]->isGivenKind(T_STRING)) { + continue; + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($prevIndex); + + continue; + } + + if (isset($useConstantDeclarations[$tokenContent])) { + continue; + } + + if ($tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) { + continue; + } + + $tokens->insertAt($index, new Token([T_NS_SEPARATOR, '\\'])); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/ControlStructureBracesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/ControlStructureBracesFixer.php new file mode 100644 index 00000000..f89d959a --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/ControlStructureBracesFixer.php @@ -0,0 +1,256 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ControlStructure; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\AlternativeSyntaxAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class ControlStructureBracesFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'The body of each control structure MUST be enclosed within braces.', + [new CodeSample("getControlTokens(); + + for ($index = $tokens->count() - 1; 0 <= $index; --$index) { + $token = $tokens[$index]; + + if (!$token->isGivenKind($controlTokens)) { + continue; + } + + if ( + $token->isGivenKind(T_ELSE) + && $tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind(T_IF) + ) { + continue; + } + + $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $index); + $nextAfterParenthesisEndIndex = $tokens->getNextMeaningfulToken($parenthesisEndIndex); + $tokenAfterParenthesis = $tokens[$nextAfterParenthesisEndIndex]; + + if ($tokenAfterParenthesis->equalsAny([';', '{', ':', [T_CLOSE_TAG]])) { + continue; + } + + $statementEndIndex = null; + + if ($tokenAfterParenthesis->isGivenKind([T_IF, T_FOR, T_FOREACH, T_SWITCH, T_WHILE])) { + $tokenAfterParenthesisBlockEnd = $tokens->findBlockEnd( // go to ')' + Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, + $tokens->getNextMeaningfulToken($nextAfterParenthesisEndIndex) + ); + + if ($tokens[$tokens->getNextMeaningfulToken($tokenAfterParenthesisBlockEnd)]->equals(':')) { + $statementEndIndex = $alternativeSyntaxAnalyzer->findAlternativeSyntaxBlockEnd($tokens, $nextAfterParenthesisEndIndex); + + $tokenAfterStatementEndIndex = $tokens->getNextMeaningfulToken($statementEndIndex); + if ($tokens[$tokenAfterStatementEndIndex]->equals(';')) { + $statementEndIndex = $tokenAfterStatementEndIndex; + } + } + } + + if (null === $statementEndIndex) { + $statementEndIndex = $this->findStatementEnd($tokens, $parenthesisEndIndex); + } + + $tokensToInsertAfterStatement = [ + new Token([T_WHITESPACE, ' ']), + new Token('}'), + ]; + + if (!$tokens[$statementEndIndex]->equalsAny([';', '}'])) { + array_unshift($tokensToInsertAfterStatement, new Token(';')); + } + + $tokens->insertSlices([$statementEndIndex + 1 => $tokensToInsertAfterStatement]); + + // insert opening brace + $tokens->insertSlices([$parenthesisEndIndex + 1 => [ + new Token([T_WHITESPACE, ' ']), + new Token('{'), + ]]); + } + } + + private function findParenthesisEnd(Tokens $tokens, int $structureTokenIndex): int + { + $nextIndex = $tokens->getNextMeaningfulToken($structureTokenIndex); + $nextToken = $tokens[$nextIndex]; + + if (!$nextToken->equals('(')) { + return $structureTokenIndex; + } + + return $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $nextIndex); + } + + private function findStatementEnd(Tokens $tokens, int $parenthesisEndIndex): int + { + $nextIndex = $tokens->getNextMeaningfulToken($parenthesisEndIndex); + if (null === $nextIndex) { + return $parenthesisEndIndex; + } + + $nextToken = $tokens[$nextIndex]; + + if ($nextToken->equals('{')) { + return $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $nextIndex); + } + + if ($nextToken->isGivenKind($this->getControlTokens())) { + $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $nextIndex); + + $endIndex = $this->findStatementEnd($tokens, $parenthesisEndIndex); + + if ($nextToken->isGivenKind([T_IF, T_TRY, T_DO])) { + $openingTokenKind = $nextToken->getId(); + + while (true) { + $nextIndex = $tokens->getNextMeaningfulToken($endIndex); + if (null !== $nextIndex && $tokens[$nextIndex]->isGivenKind($this->getControlContinuationTokensForOpeningToken($openingTokenKind))) { + $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $nextIndex); + + $endIndex = $this->findStatementEnd($tokens, $parenthesisEndIndex); + + if ($tokens[$nextIndex]->isGivenKind($this->getFinalControlContinuationTokensForOpeningToken($openingTokenKind))) { + return $endIndex; + } + } else { + break; + } + } + } + + return $endIndex; + } + + $index = $parenthesisEndIndex; + + while (true) { + $token = $tokens[++$index]; + + // if there is some block in statement (eg lambda function) we need to skip it + if ($token->equals('{')) { + $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); + + continue; + } + + if ($token->equals(';')) { + return $index; + } + + if ($token->isGivenKind(T_CLOSE_TAG)) { + return $tokens->getPrevNonWhitespace($index); + } + } + } + + /** + * @return list + */ + private function getControlTokens(): array + { + static $tokens = [ + T_DECLARE, + T_DO, + T_ELSE, + T_ELSEIF, + T_FINALLY, + T_FOR, + T_FOREACH, + T_IF, + T_WHILE, + T_TRY, + T_CATCH, + T_SWITCH, + ]; + + return $tokens; + } + + /** + * @return list + */ + private function getControlContinuationTokensForOpeningToken(int $openingTokenKind): array + { + if (T_IF === $openingTokenKind) { + return [ + T_ELSE, + T_ELSEIF, + ]; + } + + if (T_DO === $openingTokenKind) { + return [T_WHILE]; + } + + if (T_TRY === $openingTokenKind) { + return [ + T_CATCH, + T_FINALLY, + ]; + } + + return []; + } + + /** + * @return list + */ + private function getFinalControlContinuationTokensForOpeningToken(int $openingTokenKind): array + { + if (T_IF === $openingTokenKind) { + return [T_ELSE]; + } + + if (T_TRY === $openingTokenKind) { + return [T_FINALLY]; + } + + return []; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/ControlStructureContinuationPositionFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/ControlStructureContinuationPositionFixer.php new file mode 100644 index 00000000..a51fa779 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/ControlStructureContinuationPositionFixer.php @@ -0,0 +1,143 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ControlStructure; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\WhitespacesAnalyzer; +use PhpCsFixer\Tokenizer\Tokens; + +final class ControlStructureContinuationPositionFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface +{ + /** + * @internal + */ + public const NEXT_LINE = 'next_line'; + + /** + * @internal + */ + public const SAME_LINE = 'same_line'; + + private const CONTROL_CONTINUATION_TOKENS = [ + T_CATCH, + T_ELSE, + T_ELSEIF, + T_FINALLY, + T_WHILE, + ]; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Control structure continuation keyword must be on the configured line.', + [ + new CodeSample( + ' self::NEXT_LINE] + ), + ] + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound(self::CONTROL_CONTINUATION_TOKENS); + } + + /** + * {@inheritdoc} + * + * Must run after ControlStructureBracesFixer. + */ + public function getPriority(): int + { + return parent::getPriority(); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('position', 'The position of the keyword that continues the control structure.')) + ->setAllowedValues([self::NEXT_LINE, self::SAME_LINE]) + ->setDefault(self::SAME_LINE) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $this->fixControlContinuationBraces($tokens); + } + + private function fixControlContinuationBraces(Tokens $tokens): void + { + for ($index = \count($tokens) - 1; 0 < $index; --$index) { + $token = $tokens[$index]; + + if (!$token->isGivenKind(self::CONTROL_CONTINUATION_TOKENS)) { + continue; + } + + $prevIndex = $tokens->getPrevNonWhitespace($index); + $prevToken = $tokens[$prevIndex]; + + if (!$prevToken->equals('}')) { + continue; + } + + if ($token->isGivenKind(T_WHILE)) { + $prevIndex = $tokens->getPrevMeaningfulToken( + $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $prevIndex) + ); + + if (!$tokens[$prevIndex]->isGivenKind(T_DO)) { + continue; + } + } + + $tokens->ensureWhitespaceAtIndex( + $index - 1, + 1, + self::NEXT_LINE === $this->configuration['position'] ? + $this->whitespacesConfig->getLineEnding().WhitespacesAnalyzer::detectIndent($tokens, $index) + : ' ' + ); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/ElseifFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/ElseifFixer.php new file mode 100644 index 00000000..19c810ec --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/ElseifFixer.php @@ -0,0 +1,98 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ControlStructure; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Fixer for rules defined in PSR2 ¶5.1. + * + * @author Dariusz Rumiński + */ +final class ElseifFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'The keyword `elseif` should be used instead of `else if` so that all control keywords look like single words.', + [new CodeSample("isAllTokenKindsFound([T_IF, T_ELSE]); + } + + /** + * Replace all `else if` (T_ELSE T_IF) with `elseif` (T_ELSEIF). + * + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_ELSE)) { + continue; + } + + $ifTokenIndex = $tokens->getNextMeaningfulToken($index); + + // if next meaningful token is not T_IF - continue searching, this is not the case for fixing + if (!$tokens[$ifTokenIndex]->isGivenKind(T_IF)) { + continue; + } + + // if next meaningful token is T_IF, but uses an alternative syntax - this is not the case for fixing neither + $conditionEndBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $tokens->getNextMeaningfulToken($ifTokenIndex)); + $afterConditionIndex = $tokens->getNextMeaningfulToken($conditionEndBraceIndex); + if ($tokens[$afterConditionIndex]->equals(':')) { + continue; + } + + // now we have T_ELSE following by T_IF with no alternative syntax so we could fix this + // 1. clear whitespaces between T_ELSE and T_IF + $tokens->clearAt($index + 1); + + // 2. change token from T_ELSE into T_ELSEIF + $tokens[$index] = new Token([T_ELSEIF, 'elseif']); + + // 3. clear succeeding T_IF + $tokens->clearAt($ifTokenIndex); + + $beforeIfTokenIndex = $tokens->getPrevNonWhitespace($ifTokenIndex); + + // 4. clear extra whitespace after T_IF in T_COMMENT,T_WHITESPACE?,T_IF,T_WHITESPACE sequence + if ($tokens[$beforeIfTokenIndex]->isComment() && $tokens[$ifTokenIndex + 1]->isWhitespace()) { + $tokens->clearAt($ifTokenIndex + 1); + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/EmptyLoopBodyFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/EmptyLoopBodyFixer.php new file mode 100644 index 00000000..54f804fe --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/EmptyLoopBodyFixer.php @@ -0,0 +1,125 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ControlStructure; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +final class EmptyLoopBodyFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + private const STYLE_BRACES = 'braces'; + + private const STYLE_SEMICOLON = 'semicolon'; + + private const TOKEN_LOOP_KINDS = [T_FOR, T_FOREACH, T_WHILE]; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Empty loop-body must be in configured style.', + [ + new CodeSample(" 'braces', + ] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before BracesFixer, NoExtraBlankLinesFixer, NoTrailingWhitespaceFixer. + * Must run after NoEmptyStatementFixer. + */ + public function getPriority(): int + { + return 39; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound(self::TOKEN_LOOP_KINDS); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + if (self::STYLE_BRACES === $this->configuration['style']) { + $analyzer = new TokensAnalyzer($tokens); + $fixLoop = static function (int $index, int $endIndex) use ($tokens, $analyzer): void { + if ($tokens[$index]->isGivenKind(T_WHILE) && $analyzer->isWhilePartOfDoWhile($index)) { + return; + } + + $semiColonIndex = $tokens->getNextMeaningfulToken($endIndex); + + if (!$tokens[$semiColonIndex]->equals(';')) { + return; + } + + $tokens[$semiColonIndex] = new Token('{'); + $tokens->insertAt($semiColonIndex + 1, new Token('}')); + }; + } else { + $fixLoop = static function (int $index, int $endIndex) use ($tokens): void { + $braceOpenIndex = $tokens->getNextMeaningfulToken($endIndex); + + if (!$tokens[$braceOpenIndex]->equals('{')) { + return; + } + + $braceCloseIndex = $tokens->getNextNonWhitespace($braceOpenIndex); + + if (!$tokens[$braceCloseIndex]->equals('}')) { + return; + } + + $tokens[$braceOpenIndex] = new Token(';'); + $tokens->clearTokenAndMergeSurroundingWhitespace($braceCloseIndex); + }; + } + + for ($index = $tokens->count() - 1; $index > 0; --$index) { + if ($tokens[$index]->isGivenKind(self::TOKEN_LOOP_KINDS)) { + $endIndex = $tokens->getNextTokenOfKind($index, ['(']); // proceed to open '(' + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $endIndex); // proceed to close ')' + $fixLoop($index, $endIndex); // fix loop if needs fixing + } + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('style', 'Style of empty loop-bodies.')) + ->setAllowedTypes(['string']) + ->setAllowedValues([self::STYLE_BRACES, self::STYLE_SEMICOLON]) + ->setDefault(self::STYLE_SEMICOLON) + ->getOption(), + ]); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/EmptyLoopConditionFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/EmptyLoopConditionFixer.php new file mode 100644 index 00000000..6e3c81fc --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/EmptyLoopConditionFixer.php @@ -0,0 +1,188 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ControlStructure; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class EmptyLoopConditionFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + private const STYLE_FOR = 'for'; + + private const STYLE_WHILE = 'while'; + + private const TOKEN_LOOP_KINDS = [T_FOR, T_WHILE]; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Empty loop-condition must be in configured style.', + [ + new CodeSample(" 'for']), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before NoExtraBlankLinesFixer, NoTrailingWhitespaceFixer. + */ + public function getPriority(): int + { + return 1; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound(self::TOKEN_LOOP_KINDS); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + if (self::STYLE_WHILE === $this->configuration['style']) { + $candidateLoopKinds = [T_FOR, T_WHILE]; + $replacement = [new Token([T_WHILE, 'while']), new Token([T_WHITESPACE, ' ']), new Token('('), new Token([T_STRING, 'true']), new Token(')')]; + + $fixLoop = static function (int $index, int $openIndex, int $endIndex) use ($tokens, $replacement): void { + if (self::isForLoopWithEmptyCondition($tokens, $index, $openIndex, $endIndex)) { + self::clearNotCommentsInRange($tokens, $index, $endIndex); + self::cloneAndInsert($tokens, $index, $replacement); + } elseif (self::isWhileLoopWithEmptyCondition($tokens, $index, $openIndex, $endIndex)) { + $doIndex = self::getDoIndex($tokens, $index); + + if (null !== $doIndex) { + self::clearNotCommentsInRange($tokens, $index, $tokens->getNextMeaningfulToken($endIndex)); // clear including `;` + $tokens->clearAt($doIndex); + self::cloneAndInsert($tokens, $doIndex, $replacement); + } + } + }; + } else { // self::STYLE_FOR + $candidateLoopKinds = [T_WHILE]; + $replacement = [new Token([T_FOR, 'for']), new Token('('), new Token(';'), new Token(';'), new Token(')')]; + + $fixLoop = static function (int $index, int $openIndex, int $endIndex) use ($tokens, $replacement): void { + if (!self::isWhileLoopWithEmptyCondition($tokens, $index, $openIndex, $endIndex)) { + return; + } + + $doIndex = self::getDoIndex($tokens, $index); + + if (null === $doIndex) { + self::clearNotCommentsInRange($tokens, $index, $endIndex); + self::cloneAndInsert($tokens, $index, $replacement); + } else { + self::clearNotCommentsInRange($tokens, $index, $tokens->getNextMeaningfulToken($endIndex)); // clear including `;` + $tokens->clearAt($doIndex); + self::cloneAndInsert($tokens, $doIndex, $replacement); + } + }; + } + + for ($index = $tokens->count() - 1; $index > 0; --$index) { + if ($tokens[$index]->isGivenKind($candidateLoopKinds)) { + $openIndex = $tokens->getNextTokenOfKind($index, ['(']); // proceed to open '(' + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex); // proceed to close ')' + $fixLoop($index, $openIndex, $endIndex); // fix loop if needed + } + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('style', 'Style of empty loop-condition.')) + ->setAllowedTypes(['string']) + ->setAllowedValues([self::STYLE_WHILE, self::STYLE_FOR]) + ->setDefault(self::STYLE_WHILE) + ->getOption(), + ]); + } + + private static function clearNotCommentsInRange(Tokens $tokens, int $indexStart, int $indexEnd): void + { + for ($i = $indexStart; $i <= $indexEnd; ++$i) { + if (!$tokens[$i]->isComment()) { + $tokens->clearTokenAndMergeSurroundingWhitespace($i); + } + } + } + + /** + * @param Token[] $replacement + */ + private static function cloneAndInsert(Tokens $tokens, int $index, array $replacement): void + { + $replacementClones = []; + + foreach ($replacement as $token) { + $replacementClones[] = clone $token; + } + + $tokens->insertAt($index, $replacementClones); + } + + private static function getDoIndex(Tokens $tokens, int $index): ?int + { + $endIndex = $tokens->getPrevMeaningfulToken($index); + + if (!$tokens[$endIndex]->equals('}')) { + return null; + } + + $startIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $endIndex); + $index = $tokens->getPrevMeaningfulToken($startIndex); + + return null === $index || !$tokens[$index]->isGivenKind(T_DO) ? null : $index; + } + + private static function isForLoopWithEmptyCondition(Tokens $tokens, int $index, int $openIndex, int $endIndex): bool + { + if (!$tokens[$index]->isGivenKind(T_FOR)) { + return false; + } + + $index = $tokens->getNextMeaningfulToken($openIndex); + + if (null === $index || !$tokens[$index]->equals(';')) { + return false; + } + + $index = $tokens->getNextMeaningfulToken($index); + + return null !== $index && $tokens[$index]->equals(';') && $endIndex === $tokens->getNextMeaningfulToken($index); + } + + private static function isWhileLoopWithEmptyCondition(Tokens $tokens, int $index, int $openIndex, int $endIndex): bool + { + if (!$tokens[$index]->isGivenKind(T_WHILE)) { + return false; + } + + $index = $tokens->getNextMeaningfulToken($openIndex); + + return null !== $index && $tokens[$index]->equals([T_STRING, 'true']) && $endIndex === $tokens->getNextMeaningfulToken($index); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/IncludeFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/IncludeFixer.php new file mode 100644 index 00000000..a0bd5acc --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/IncludeFixer.php @@ -0,0 +1,153 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ControlStructure; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\BlocksAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Sebastiaan Stok + * @author Dariusz Rumiński + * @author Kuba Werłos + */ +final class IncludeFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Include/Require and file path should be divided with a single space. File path should not be placed within parentheses.', + [ + new CodeSample( + 'isAnyTokenKindsFound([T_REQUIRE, T_REQUIRE_ONCE, T_INCLUDE, T_INCLUDE_ONCE]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $this->clearIncludies($tokens, $this->findIncludies($tokens)); + } + + /** + * @param array $includies + */ + private function clearIncludies(Tokens $tokens, array $includies): void + { + $blocksAnalyzer = new BlocksAnalyzer(); + + foreach ($includies as $includy) { + if (!$tokens[$includy['end']]->isGivenKind(T_CLOSE_TAG)) { + $afterEndIndex = $tokens->getNextNonWhitespace($includy['end']); + + if (null === $afterEndIndex || !$tokens[$afterEndIndex]->isComment()) { + $tokens->removeLeadingWhitespace($includy['end']); + } + } + + $braces = $includy['braces']; + + if (null !== $braces) { + $prevIndex = $tokens->getPrevMeaningfulToken($includy['begin']); + $nextIndex = $tokens->getNextMeaningfulToken($braces['close']); + + // Include is also legal as function parameter or condition statement but requires being wrapped then. + if (!$tokens[$nextIndex]->equalsAny([';', [T_CLOSE_TAG]]) && !$blocksAnalyzer->isBlock($tokens, $prevIndex, $nextIndex)) { + continue; + } + + $this->removeWhitespaceAroundIfPossible($tokens, $braces['open']); + $this->removeWhitespaceAroundIfPossible($tokens, $braces['close']); + $tokens->clearTokenAndMergeSurroundingWhitespace($braces['open']); + $tokens->clearTokenAndMergeSurroundingWhitespace($braces['close']); + } + + $nextIndex = $tokens->getNonEmptySibling($includy['begin'], 1); + + if ($tokens[$nextIndex]->isWhitespace()) { + $tokens[$nextIndex] = new Token([T_WHITESPACE, ' ']); + } elseif (null !== $braces || $tokens[$nextIndex]->isGivenKind([T_VARIABLE, T_CONSTANT_ENCAPSED_STRING, T_COMMENT])) { + $tokens->insertAt($includy['begin'] + 1, new Token([T_WHITESPACE, ' '])); + } + } + } + + /** + * @return array + */ + private function findIncludies(Tokens $tokens): array + { + static $includyTokenKinds = [T_REQUIRE, T_REQUIRE_ONCE, T_INCLUDE, T_INCLUDE_ONCE]; + + $includies = []; + + foreach ($tokens->findGivenKind($includyTokenKinds) as $includyTokens) { + foreach ($includyTokens as $index => $token) { + $includy = [ + 'begin' => $index, + 'braces' => null, + 'end' => $tokens->getNextTokenOfKind($index, [';', [T_CLOSE_TAG]]), + ]; + + $braceOpenIndex = $tokens->getNextMeaningfulToken($index); + + if ($tokens[$braceOpenIndex]->equals('(')) { + $braceCloseIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $braceOpenIndex); + + $includy['braces'] = [ + 'open' => $braceOpenIndex, + 'close' => $braceCloseIndex, + ]; + } + + $includies[$index] = $includy; + } + } + + krsort($includies); + + return $includies; + } + + private function removeWhitespaceAroundIfPossible(Tokens $tokens, int $index): void + { + $nextIndex = $tokens->getNextNonWhitespace($index); + + if (null === $nextIndex || !$tokens[$nextIndex]->isComment()) { + $tokens->removeLeadingWhitespace($index); + } + + $prevIndex = $tokens->getPrevNonWhitespace($index); + + if (null === $prevIndex || !$tokens[$prevIndex]->isComment()) { + $tokens->removeTrailingWhitespace($index); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoAlternativeSyntaxFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoAlternativeSyntaxFixer.php new file mode 100644 index 00000000..e994ad92 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoAlternativeSyntaxFixer.php @@ -0,0 +1,231 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ControlStructure; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Eddilbert Macharia + */ +final class NoAlternativeSyntaxFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Replace control structure alternative syntax to use braces.', + [ + new CodeSample( + "\nLorem ipsum.\n\n", + ['fix_non_monolithic_code' => true] + ), + ] + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->hasAlternativeSyntax() && (true === $this->configuration['fix_non_monolithic_code'] || $tokens->isMonolithicPhp()); + } + + /** + * {@inheritdoc} + * + * Must run before BracesFixer, ElseifFixer, NoSuperfluousElseifFixer, NoUnneededControlParenthesesFixer, NoUselessElseFixer, SwitchContinueToBreakFixer. + */ + public function getPriority(): int + { + return 42; + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('fix_non_monolithic_code', 'Whether to also fix code with inline HTML.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) // @TODO change to "false" on next major 4.0 + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = \count($tokens) - 1; 0 <= $index; --$index) { + $token = $tokens[$index]; + $this->fixElseif($index, $token, $tokens); + $this->fixElse($index, $token, $tokens); + $this->fixOpenCloseControls($index, $token, $tokens); + } + } + + private function findParenthesisEnd(Tokens $tokens, int $structureTokenIndex): int + { + $nextIndex = $tokens->getNextMeaningfulToken($structureTokenIndex); + $nextToken = $tokens[$nextIndex]; + + return $nextToken->equals('(') + ? $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $nextIndex) + : $structureTokenIndex; // return if next token is not opening parenthesis + } + + /** + * Handle both extremes of the control structures. + * e.g. if(): or endif;. + * + * @param int $index the index of the token being processed + * @param Token $token the token being processed + * @param Tokens $tokens the collection of tokens + */ + private function fixOpenCloseControls(int $index, Token $token, Tokens $tokens): void + { + if ($token->isGivenKind([T_IF, T_FOREACH, T_WHILE, T_FOR, T_SWITCH, T_DECLARE])) { + $openIndex = $tokens->getNextTokenOfKind($index, ['(']); + $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex); + $afterParenthesisIndex = $tokens->getNextMeaningfulToken($closeIndex); + $afterParenthesis = $tokens[$afterParenthesisIndex]; + + if (!$afterParenthesis->equals(':')) { + return; + } + + $items = []; + + if (!$tokens[$afterParenthesisIndex - 1]->isWhitespace()) { + $items[] = new Token([T_WHITESPACE, ' ']); + } + + $items[] = new Token('{'); + + if (!$tokens[$afterParenthesisIndex + 1]->isWhitespace()) { + $items[] = new Token([T_WHITESPACE, ' ']); + } + + $tokens->clearAt($afterParenthesisIndex); + $tokens->insertAt($afterParenthesisIndex, $items); + } + + if (!$token->isGivenKind([T_ENDIF, T_ENDFOREACH, T_ENDWHILE, T_ENDFOR, T_ENDSWITCH, T_ENDDECLARE])) { + return; + } + + $nextTokenIndex = $tokens->getNextMeaningfulToken($index); + $nextToken = $tokens[$nextTokenIndex]; + $tokens[$index] = new Token('}'); + + if ($nextToken->equals(';')) { + $tokens->clearAt($nextTokenIndex); + } + } + + /** + * Handle the else: cases. + * + * @param int $index the index of the token being processed + * @param Token $token the token being processed + * @param Tokens $tokens the collection of tokens + */ + private function fixElse(int $index, Token $token, Tokens $tokens): void + { + if (!$token->isGivenKind(T_ELSE)) { + return; + } + + $tokenAfterElseIndex = $tokens->getNextMeaningfulToken($index); + $tokenAfterElse = $tokens[$tokenAfterElseIndex]; + + if (!$tokenAfterElse->equals(':')) { + return; + } + + $this->addBraces($tokens, new Token([T_ELSE, 'else']), $index, $tokenAfterElseIndex); + } + + /** + * Handle the elsif(): cases. + * + * @param int $index the index of the token being processed + * @param Token $token the token being processed + * @param Tokens $tokens the collection of tokens + */ + private function fixElseif(int $index, Token $token, Tokens $tokens): void + { + if (!$token->isGivenKind(T_ELSEIF)) { + return; + } + + $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $index); + $tokenAfterParenthesisIndex = $tokens->getNextMeaningfulToken($parenthesisEndIndex); + $tokenAfterParenthesis = $tokens[$tokenAfterParenthesisIndex]; + + if (!$tokenAfterParenthesis->equals(':')) { + return; + } + + $this->addBraces($tokens, new Token([T_ELSEIF, 'elseif']), $index, $tokenAfterParenthesisIndex); + } + + /** + * Add opening and closing braces to the else: and elseif: cases. + * + * @param Tokens $tokens the tokens collection + * @param Token $token the current token + * @param int $index the current token index + * @param int $colonIndex the index of the colon + */ + private function addBraces(Tokens $tokens, Token $token, int $index, int $colonIndex): void + { + $items = [ + new Token('}'), + new Token([T_WHITESPACE, ' ']), + $token, + ]; + + if (!$tokens[$index + 1]->isWhitespace()) { + $items[] = new Token([T_WHITESPACE, ' ']); + } + + $tokens->clearAt($index); + $tokens->insertAt( + $index, + $items + ); + + // increment the position of the colon by number of items inserted + $colonIndex += \count($items); + + $items = [new Token('{')]; + + if (!$tokens[$colonIndex + 1]->isWhitespace()) { + $items[] = new Token([T_WHITESPACE, ' ']); + } + + $tokens->clearAt($colonIndex); + $tokens->insertAt( + $colonIndex, + $items + ); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoBreakCommentFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoBreakCommentFixer.php new file mode 100644 index 00000000..b50a6c85 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoBreakCommentFixer.php @@ -0,0 +1,353 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ControlStructure; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\WhitespacesAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; +use Symfony\Component\OptionsResolver\Options; + +/** + * Fixer for rule defined in PSR2 ¶5.2. + */ +final class NoBreakCommentFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'There must be a comment when fall-through is intentional in a non-empty case body.', + [ + new CodeSample( + ' 'some comment'] + ), + ], + 'Adds a "no break" comment before fall-through cases, and removes it if there is no fall-through.' + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_SWITCH); + } + + /** + * {@inheritdoc} + * + * Must run after NoUselessElseFixer. + */ + public function getPriority(): int + { + return 0; + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('comment_text', 'The text to use in the added comment and to detect it.')) + ->setAllowedTypes(['string']) + ->setAllowedValues([ + static function (string $value): bool { + if (Preg::match('/\R/', $value)) { + throw new InvalidOptionsException('The comment text must not contain new lines.'); + } + + return true; + }, + ]) + ->setNormalizer(static fn (Options $options, string $value): string => rtrim($value)) + ->setDefault('no break') + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = \count($tokens) - 1; $index >= 0; --$index) { + if ($tokens[$index]->isGivenKind(T_DEFAULT)) { + if ($tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind(T_DOUBLE_ARROW)) { + continue; // this is "default" from "match" + } + } elseif (!$tokens[$index]->isGivenKind(T_CASE)) { + continue; + } + + $this->fixCase($tokens, $tokens->getNextTokenOfKind($index, [':', ';'])); + } + } + + private function fixCase(Tokens $tokens, int $casePosition): void + { + $empty = true; + $fallThrough = true; + $commentPosition = null; + + for ($i = $casePosition + 1, $max = \count($tokens); $i < $max; ++$i) { + if ($tokens[$i]->isGivenKind([...self::getParenthesisedStructureKinds(), T_ELSE, T_DO, T_CLASS])) { + $empty = false; + $i = $this->getStructureEnd($tokens, $i); + + continue; + } + + if ($tokens[$i]->isGivenKind([T_BREAK, T_CONTINUE, T_RETURN, T_EXIT, T_GOTO])) { + $fallThrough = false; + + continue; + } + + if ($tokens[$i]->isGivenKind(T_THROW)) { + $previousIndex = $tokens->getPrevMeaningfulToken($i); + + if ($previousIndex === $casePosition || $tokens[$previousIndex]->equalsAny(['{', ';', '}', [T_OPEN_TAG]])) { + $fallThrough = false; + } + + continue; + } + + if ($tokens[$i]->equals('}') || $tokens[$i]->isGivenKind(T_ENDSWITCH)) { + if (null !== $commentPosition) { + $this->removeComment($tokens, $commentPosition); + } + + break; + } + + if ($this->isNoBreakComment($tokens[$i])) { + $commentPosition = $i; + + continue; + } + + if ($tokens[$i]->isGivenKind([T_CASE, T_DEFAULT])) { + if (!$empty && $fallThrough) { + if (null !== $commentPosition && $tokens->getPrevNonWhitespace($i) !== $commentPosition) { + $this->removeComment($tokens, $commentPosition); + $commentPosition = null; + } + + if (null === $commentPosition) { + $this->insertCommentAt($tokens, $i); + } else { + $text = $this->configuration['comment_text']; + $tokens[$commentPosition] = new Token([ + $tokens[$commentPosition]->getId(), + str_ireplace($text, $text, $tokens[$commentPosition]->getContent()), + ]); + + $this->ensureNewLineAt($tokens, $commentPosition); + } + } elseif (null !== $commentPosition) { + $this->removeComment($tokens, $commentPosition); + } + + break; + } + + if (!$tokens[$i]->isGivenKind([T_COMMENT, T_WHITESPACE])) { + $empty = false; + } + } + } + + private function isNoBreakComment(Token $token): bool + { + if (!$token->isComment()) { + return false; + } + + $text = preg_quote($this->configuration['comment_text'], '~'); + + return Preg::match("~^((//|#)\\s*{$text}\\s*)|(/\\*\\*?\\s*{$text}(\\s+.*)*\\*/)$~i", $token->getContent()); + } + + private function insertCommentAt(Tokens $tokens, int $casePosition): void + { + $lineEnding = $this->whitespacesConfig->getLineEnding(); + $newlinePosition = $this->ensureNewLineAt($tokens, $casePosition); + $newlineToken = $tokens[$newlinePosition]; + $nbNewlines = substr_count($newlineToken->getContent(), $lineEnding); + + if ($newlineToken->isGivenKind(T_OPEN_TAG) && Preg::match('/\R/', $newlineToken->getContent())) { + ++$nbNewlines; + } elseif ($tokens[$newlinePosition - 1]->isGivenKind(T_OPEN_TAG) && Preg::match('/\R/', $tokens[$newlinePosition - 1]->getContent())) { + ++$nbNewlines; + + if (!Preg::match('/\R/', $newlineToken->getContent())) { + $tokens[$newlinePosition] = new Token([$newlineToken->getId(), $lineEnding.$newlineToken->getContent()]); + } + } + + if ($nbNewlines > 1) { + Preg::match('/^(.*?)(\R\h*)$/s', $newlineToken->getContent(), $matches); + + $indent = WhitespacesAnalyzer::detectIndent($tokens, $newlinePosition - 1); + $tokens[$newlinePosition] = new Token([$newlineToken->getId(), $matches[1].$lineEnding.$indent]); + $tokens->insertAt(++$newlinePosition, new Token([T_WHITESPACE, $matches[2]])); + } + + $tokens->insertAt($newlinePosition, new Token([T_COMMENT, '// '.$this->configuration['comment_text']])); + $this->ensureNewLineAt($tokens, $newlinePosition); + } + + /** + * @return int The newline token position + */ + private function ensureNewLineAt(Tokens $tokens, int $position): int + { + $lineEnding = $this->whitespacesConfig->getLineEnding(); + $content = $lineEnding.WhitespacesAnalyzer::detectIndent($tokens, $position); + $whitespaceToken = $tokens[$position - 1]; + + if (!$whitespaceToken->isGivenKind(T_WHITESPACE)) { + if ($whitespaceToken->isGivenKind(T_OPEN_TAG)) { + $content = Preg::replace('/\R/', '', $content); + + if (!Preg::match('/\R/', $whitespaceToken->getContent())) { + $tokens[$position - 1] = new Token([T_OPEN_TAG, Preg::replace('/\s+$/', $lineEnding, $whitespaceToken->getContent())]); + } + } + + if ('' !== $content) { + $tokens->insertAt($position, new Token([T_WHITESPACE, $content])); + + return $position; + } + + return $position - 1; + } + + if ($tokens[$position - 2]->isGivenKind(T_OPEN_TAG) && Preg::match('/\R/', $tokens[$position - 2]->getContent())) { + $content = Preg::replace('/^\R/', '', $content); + } + + if (!Preg::match('/\R/', $whitespaceToken->getContent())) { + $tokens[$position - 1] = new Token([T_WHITESPACE, $content]); + } + + return $position - 1; + } + + private function removeComment(Tokens $tokens, int $commentPosition): void + { + if ($tokens[$tokens->getPrevNonWhitespace($commentPosition)]->isGivenKind(T_OPEN_TAG)) { + $whitespacePosition = $commentPosition + 1; + $regex = '/^\R\h*/'; + } else { + $whitespacePosition = $commentPosition - 1; + $regex = '/\R\h*$/'; + } + + $whitespaceToken = $tokens[$whitespacePosition]; + + if ($whitespaceToken->isGivenKind(T_WHITESPACE)) { + $content = Preg::replace($regex, '', $whitespaceToken->getContent()); + + $tokens->ensureWhitespaceAtIndex($whitespacePosition, 0, $content); + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($commentPosition); + } + + private function getStructureEnd(Tokens $tokens, int $position): int + { + $initialToken = $tokens[$position]; + + if ($initialToken->isGivenKind(self::getParenthesisedStructureKinds())) { + $position = $tokens->findBlockEnd( + Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, + $tokens->getNextTokenOfKind($position, ['(']) + ); + } elseif ($initialToken->isGivenKind(T_CLASS)) { + $openParenthesisPosition = $tokens->getNextMeaningfulToken($position); + + if ('(' === $tokens[$openParenthesisPosition]->getContent()) { + $position = $tokens->findBlockEnd( + Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, + $openParenthesisPosition + ); + } + } + + $position = $tokens->getNextMeaningfulToken($position); + + if ('{' !== $tokens[$position]->getContent()) { + return $tokens->getNextTokenOfKind($position, [';']); + } + + $position = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $position); + + if ($initialToken->isGivenKind(T_DO)) { + $position = $tokens->findBlockEnd( + Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, + $tokens->getNextTokenOfKind($position, ['(']) + ); + + return $tokens->getNextTokenOfKind($position, [';']); + } + + return $position; + } + + /** + * @return array + */ + private static function getParenthesisedStructureKinds(): array + { + static $structureKinds = null; + + if (null === $structureKinds) { + $structureKinds = [T_FOR, T_FOREACH, T_WHILE, T_IF, T_ELSEIF, T_SWITCH, T_FUNCTION]; + if (\defined('T_MATCH')) { // @TODO: drop condition when PHP 8.0+ is required + $structureKinds[] = T_MATCH; + } + } + + return $structureKinds; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoSuperfluousElseifFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoSuperfluousElseifFixer.php new file mode 100644 index 00000000..93dbb083 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoSuperfluousElseifFixer.php @@ -0,0 +1,100 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ControlStructure; + +use PhpCsFixer\AbstractNoUselessElseFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class NoSuperfluousElseifFixer extends AbstractNoUselessElseFixer +{ + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound([T_ELSE, T_ELSEIF]); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Replaces superfluous `elseif` with `if`.', + [ + new CodeSample(" $token) { + if ($this->isElseif($tokens, $index) && $this->isSuperfluousElse($tokens, $index)) { + $this->convertElseifToIf($tokens, $index); + } + } + } + + private function isElseif(Tokens $tokens, int $index): bool + { + return + $tokens[$index]->isGivenKind(T_ELSEIF) + || ($tokens[$index]->isGivenKind(T_ELSE) && $tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind(T_IF)); + } + + private function convertElseifToIf(Tokens $tokens, int $index): void + { + if ($tokens[$index]->isGivenKind(T_ELSE)) { + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } else { + $tokens[$index] = new Token([T_IF, 'if']); + } + + $whitespace = ''; + + for ($previous = $index - 1; $previous > 0; --$previous) { + $token = $tokens[$previous]; + if ($token->isWhitespace() && Preg::match('/(\R\N*)$/', $token->getContent(), $matches)) { + $whitespace = $matches[1]; + + break; + } + } + + if ('' === $whitespace) { + return; + } + + $previousToken = $tokens[$index - 1]; + + if (!$previousToken->isWhitespace()) { + $tokens->insertAt($index, new Token([T_WHITESPACE, $whitespace])); + } elseif (!Preg::match('/\R/', $previousToken->getContent())) { + $tokens[$index - 1] = new Token([T_WHITESPACE, $whitespace]); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoTrailingCommaInListCallFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoTrailingCommaInListCallFixer.php new file mode 100644 index 00000000..4b710058 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoTrailingCommaInListCallFixer.php @@ -0,0 +1,51 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ControlStructure; + +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\Fixer\Basic\NoTrailingCommaInSinglelineFixer; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; + +/** + * @deprecated + * + * @author Dariusz Rumiński + */ +final class NoTrailingCommaInListCallFixer extends AbstractProxyFixer implements DeprecatedFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Remove trailing commas in list function calls.', + [new CodeSample("proxyFixers); + } + + protected function createProxyFixers(): array + { + $fixer = new NoTrailingCommaInSinglelineFixer(); + $fixer->configure(['elements' => ['array_destructuring']]); + + return [$fixer]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUnneededBracesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUnneededBracesFixer.php new file mode 100644 index 00000000..3d1e72bc --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUnneededBracesFixer.php @@ -0,0 +1,168 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ControlStructure; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class NoUnneededBracesFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Removes unneeded braces that are superfluous and aren\'t part of a control structure\'s body.', + [ + new CodeSample( + ' true] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before NoUselessElseFixer, NoUselessReturnFixer, ReturnAssignmentFixer, SimplifiedIfReturnFixer. + */ + public function getPriority(): int + { + return 40; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound('}'); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($this->findBraceOpen($tokens) as $index) { + if ($this->isOverComplete($tokens, $index)) { + $this->clearOverCompleteBraces($tokens, $index, $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index)); + } + } + + if (true === $this->configuration['namespaces']) { + $this->clearIfIsOverCompleteNamespaceBlock($tokens); + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('namespaces', 'Remove unneeded braces from bracketed namespaces.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + + /** + * @param int $openIndex index of `{` token + * @param int $closeIndex index of `}` token + */ + private function clearOverCompleteBraces(Tokens $tokens, int $openIndex, int $closeIndex): void + { + $tokens->clearTokenAndMergeSurroundingWhitespace($closeIndex); + $tokens->clearTokenAndMergeSurroundingWhitespace($openIndex); + } + + /** + * @return iterable + */ + private function findBraceOpen(Tokens $tokens): iterable + { + for ($i = \count($tokens) - 1; $i > 0; --$i) { + if ($tokens[$i]->equals('{')) { + yield $i; + } + } + } + + /** + * @param int $index index of `{` token + */ + private function isOverComplete(Tokens $tokens, int $index): bool + { + static $include = ['{', '}', [T_OPEN_TAG], ':', ';']; + + return $tokens[$tokens->getPrevMeaningfulToken($index)]->equalsAny($include); + } + + private function clearIfIsOverCompleteNamespaceBlock(Tokens $tokens): void + { + if (1 !== $tokens->countTokenKind(T_NAMESPACE)) { + return; // fast check, we never fix if multiple namespaces are defined + } + + $index = $tokens->getNextTokenOfKind(0, [[T_NAMESPACE]]); + + $namespaceIsNamed = false; + + $index = $tokens->getNextMeaningfulToken($index); + while ($tokens[$index]->isGivenKind([T_STRING, T_NS_SEPARATOR])) { + $index = $tokens->getNextMeaningfulToken($index); + $namespaceIsNamed = true; + } + + if (!$namespaceIsNamed) { + return; + } + + if (!$tokens[$index]->equals('{')) { + return; // `;` + } + + $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); + $afterCloseIndex = $tokens->getNextMeaningfulToken($closeIndex); + + if (null !== $afterCloseIndex && (!$tokens[$afterCloseIndex]->isGivenKind(T_CLOSE_TAG) || null !== $tokens->getNextMeaningfulToken($afterCloseIndex))) { + return; + } + + // clear up + $tokens->clearTokenAndMergeSurroundingWhitespace($closeIndex); + $tokens[$index] = new Token(';'); + + if ($tokens[$index - 1]->isWhitespace(" \t") && !$tokens[$index - 2]->isComment()) { + $tokens->clearTokenAndMergeSurroundingWhitespace($index - 1); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUnneededControlParenthesesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUnneededControlParenthesesFixer.php new file mode 100644 index 00000000..b0156c27 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUnneededControlParenthesesFixer.php @@ -0,0 +1,747 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ControlStructure; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Sullivan Senechal + * @author Dariusz Rumiński + * @author Gregor Harlan + */ +final class NoUnneededControlParenthesesFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** + * @var int[] + */ + private const BLOCK_TYPES = [ + Tokens::BLOCK_TYPE_ARRAY_INDEX_CURLY_BRACE, + Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, + Tokens::BLOCK_TYPE_CURLY_BRACE, + Tokens::BLOCK_TYPE_DESTRUCTURING_SQUARE_BRACE, + Tokens::BLOCK_TYPE_DYNAMIC_PROP_BRACE, + Tokens::BLOCK_TYPE_DYNAMIC_VAR_BRACE, + Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE, + Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, + ]; + + private const BEFORE_TYPES = [ + ';', + '{', + [T_OPEN_TAG], + [T_OPEN_TAG_WITH_ECHO], + [T_ECHO], + [T_PRINT], + [T_RETURN], + [T_THROW], + [T_YIELD], + [T_YIELD_FROM], + [T_BREAK], + [T_CONTINUE], + // won't be fixed, but true in concept, helpful for fast check + [T_REQUIRE], + [T_REQUIRE_ONCE], + [T_INCLUDE], + [T_INCLUDE_ONCE], + ]; + + private const CONFIG_OPTIONS = [ + 'break', + 'clone', + 'continue', + 'echo_print', + 'negative_instanceof', + 'others', + 'return', + 'switch_case', + 'yield', + 'yield_from', + ]; + + private const TOKEN_TYPE_CONFIG_MAP = [ + T_BREAK => 'break', + T_CASE => 'switch_case', + T_CONTINUE => 'continue', + T_ECHO => 'echo_print', + T_PRINT => 'echo_print', + T_RETURN => 'return', + T_YIELD => 'yield', + T_YIELD_FROM => 'yield_from', + ]; + + // handled by the `include` rule + private const TOKEN_TYPE_NO_CONFIG = [ + T_REQUIRE, + T_REQUIRE_ONCE, + T_INCLUDE, + T_INCLUDE_ONCE, + ]; + + /** + * @var list|string> + */ + private array $noopTypes; + + private TokensAnalyzer $tokensAnalyzer; + + public function __construct() + { + parent::__construct(); + + $this->noopTypes = [ + '$', + [T_CONSTANT_ENCAPSED_STRING], + [T_DNUMBER], + [T_DOUBLE_COLON], + [T_LNUMBER], + [T_NS_SEPARATOR], + [T_STRING], + [T_VARIABLE], + [T_STATIC], + // magic constants + [T_CLASS_C], + [T_DIR], + [T_FILE], + [T_FUNC_C], + [T_LINE], + [T_METHOD_C], + [T_NS_C], + [T_TRAIT_C], + ]; + + foreach (Token::getObjectOperatorKinds() as $kind) { + $this->noopTypes[] = [$kind]; + } + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Removes unneeded parentheses around control statements.', + [ + new CodeSample( + ' ['break', 'continue']] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before ConcatSpaceFixer, NoTrailingWhitespaceFixer. + * Must run after ModernizeTypesCastingFixer, NoAlternativeSyntaxFixer. + */ + public function getPriority(): int + { + return 30; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound(['(', CT::T_BRACE_CLASS_INSTANTIATION_OPEN]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $this->tokensAnalyzer = new TokensAnalyzer($tokens); + + foreach ($tokens as $openIndex => $token) { + if ($token->equals('(')) { + $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex); + } elseif ($token->isGivenKind(CT::T_BRACE_CLASS_INSTANTIATION_OPEN)) { + $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_BRACE_CLASS_INSTANTIATION, $openIndex); + } else { + continue; + } + + $beforeOpenIndex = $tokens->getPrevMeaningfulToken($openIndex); + $afterCloseIndex = $tokens->getNextMeaningfulToken($closeIndex); + + // do a cheap check for negative case: `X()` + + if ($tokens->getNextMeaningfulToken($openIndex) === $closeIndex) { + if ($this->isExitStatement($tokens, $beforeOpenIndex)) { + $this->removeUselessParenthesisPair($tokens, $beforeOpenIndex, $afterCloseIndex, $openIndex, $closeIndex, 'others'); + } + + continue; + } + + // do a cheap check for negative case: `foo(1,2)` + + if ($this->isKnownNegativePre($tokens[$beforeOpenIndex])) { + continue; + } + + // check for the simple useless wrapped cases + + if ($this->isUselessWrapped($tokens, $beforeOpenIndex, $afterCloseIndex)) { + $this->removeUselessParenthesisPair($tokens, $beforeOpenIndex, $afterCloseIndex, $openIndex, $closeIndex, $this->getConfigType($tokens, $beforeOpenIndex)); + + continue; + } + + // handle `clone` statements + + if ($this->isCloneStatement($tokens, $beforeOpenIndex)) { + if ($this->isWrappedCloneArgument($tokens, $beforeOpenIndex, $openIndex, $closeIndex, $afterCloseIndex)) { + $this->removeUselessParenthesisPair($tokens, $beforeOpenIndex, $afterCloseIndex, $openIndex, $closeIndex, 'clone'); + } + + continue; + } + + // handle `instance of` statements + + $instanceOfIndex = $this->getIndexOfInstanceOfStatement($tokens, $openIndex, $closeIndex); + + if (null !== $instanceOfIndex) { + if ($this->isWrappedInstanceOf($tokens, $instanceOfIndex, $beforeOpenIndex, $openIndex, $closeIndex, $afterCloseIndex)) { + $this->removeUselessParenthesisPair( + $tokens, + $beforeOpenIndex, + $afterCloseIndex, + $openIndex, + $closeIndex, + $tokens[$beforeOpenIndex]->equals('!') ? 'negative_instanceof' : 'others' + ); + } + + continue; + } + + // last checks deal with operators, do not swap around + + if ($this->isWrappedPartOfOperation($tokens, $beforeOpenIndex, $openIndex, $closeIndex, $afterCloseIndex)) { + $this->removeUselessParenthesisPair($tokens, $beforeOpenIndex, $afterCloseIndex, $openIndex, $closeIndex, $this->getConfigType($tokens, $beforeOpenIndex)); + } + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + $defaults = array_filter( + self::CONFIG_OPTIONS, + static fn (string $option): bool => 'negative_instanceof' !== $option && 'others' !== $option && 'yield_from' !== $option + ); + + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('statements', 'List of control statements to fix.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([new AllowedValueSubset(self::CONFIG_OPTIONS)]) + ->setDefault(array_values($defaults)) + ->getOption(), + ]); + } + + private function isUselessWrapped(Tokens $tokens, int $beforeOpenIndex, int $afterCloseIndex): bool + { + return + $this->isSingleStatement($tokens, $beforeOpenIndex, $afterCloseIndex) + || $this->isWrappedFnBody($tokens, $beforeOpenIndex, $afterCloseIndex) + || $this->isWrappedForElement($tokens, $beforeOpenIndex, $afterCloseIndex) + || $this->isWrappedLanguageConstructArgument($tokens, $beforeOpenIndex, $afterCloseIndex) + || $this->isWrappedSequenceElement($tokens, $beforeOpenIndex, $afterCloseIndex); + } + + private function isExitStatement(Tokens $tokens, int $beforeOpenIndex): bool + { + return $tokens[$beforeOpenIndex]->isGivenKind(T_EXIT); + } + + private function isCloneStatement(Tokens $tokens, int $beforeOpenIndex): bool + { + return $tokens[$beforeOpenIndex]->isGivenKind(T_CLONE); + } + + private function isWrappedCloneArgument(Tokens $tokens, int $beforeOpenIndex, int $openIndex, int $closeIndex, int $afterCloseIndex): bool + { + $beforeOpenIndex = $tokens->getPrevMeaningfulToken($beforeOpenIndex); + + if ( + !( + $tokens[$beforeOpenIndex]->equals('?') // For BC reasons + || $this->isSimpleAssignment($tokens, $beforeOpenIndex, $afterCloseIndex) + || $this->isSingleStatement($tokens, $beforeOpenIndex, $afterCloseIndex) + || $this->isWrappedFnBody($tokens, $beforeOpenIndex, $afterCloseIndex) + || $this->isWrappedForElement($tokens, $beforeOpenIndex, $afterCloseIndex) + || $this->isWrappedSequenceElement($tokens, $beforeOpenIndex, $afterCloseIndex) + ) + ) { + return false; + } + + $newCandidateIndex = $tokens->getNextMeaningfulToken($openIndex); + + if ($tokens[$newCandidateIndex]->isGivenKind(T_NEW)) { + $openIndex = $newCandidateIndex; // `clone (new X)`, `clone (new X())`, clone (new X(Y))` + } + + return !$this->containsOperation($tokens, $openIndex, $closeIndex); + } + + private function getIndexOfInstanceOfStatement(Tokens $tokens, int $openIndex, int $closeIndex): ?int + { + $instanceOfIndex = $tokens->findGivenKind(T_INSTANCEOF, $openIndex, $closeIndex); + + return 1 === \count($instanceOfIndex) ? array_key_first($instanceOfIndex) : null; + } + + private function isWrappedInstanceOf(Tokens $tokens, int $instanceOfIndex, int $beforeOpenIndex, int $openIndex, int $closeIndex, int $afterCloseIndex): bool + { + if ( + $this->containsOperation($tokens, $openIndex, $instanceOfIndex) + || $this->containsOperation($tokens, $instanceOfIndex, $closeIndex) + ) { + return false; + } + + if ($tokens[$beforeOpenIndex]->equals('!')) { + $beforeOpenIndex = $tokens->getPrevMeaningfulToken($beforeOpenIndex); + } + + return + $this->isSimpleAssignment($tokens, $beforeOpenIndex, $afterCloseIndex) + || $this->isSingleStatement($tokens, $beforeOpenIndex, $afterCloseIndex) + || $this->isWrappedFnBody($tokens, $beforeOpenIndex, $afterCloseIndex) + || $this->isWrappedForElement($tokens, $beforeOpenIndex, $afterCloseIndex) + || $this->isWrappedSequenceElement($tokens, $beforeOpenIndex, $afterCloseIndex); + } + + private function isWrappedPartOfOperation(Tokens $tokens, int $beforeOpenIndex, int $openIndex, int $closeIndex, int $afterCloseIndex): bool + { + if ($this->containsOperation($tokens, $openIndex, $closeIndex)) { + return false; + } + + $boundariesMoved = false; + + if ($this->isPreUnaryOperation($tokens, $beforeOpenIndex)) { + $beforeOpenIndex = $this->getBeforePreUnaryOperation($tokens, $beforeOpenIndex); + $boundariesMoved = true; + } + + if ($this->isAccess($tokens, $afterCloseIndex)) { + $afterCloseIndex = $this->getAfterAccess($tokens, $afterCloseIndex); + $boundariesMoved = true; + + if ($this->tokensAnalyzer->isUnarySuccessorOperator($afterCloseIndex)) { // post unary operation are only valid here + $afterCloseIndex = $tokens->getNextMeaningfulToken($afterCloseIndex); + } + } + + if ($boundariesMoved) { + if ($this->isKnownNegativePre($tokens[$beforeOpenIndex])) { + return false; + } + + if ($this->isUselessWrapped($tokens, $beforeOpenIndex, $afterCloseIndex)) { + return true; + } + } + + // check if part of some operation sequence + + $beforeIsBinaryOperation = $this->tokensAnalyzer->isBinaryOperator($beforeOpenIndex); + $afterIsBinaryOperation = $this->tokensAnalyzer->isBinaryOperator($afterCloseIndex); + + if ($beforeIsBinaryOperation && $afterIsBinaryOperation) { + return true; // `+ (x) +` + } + + $beforeToken = $tokens[$beforeOpenIndex]; + $afterToken = $tokens[$afterCloseIndex]; + + $beforeIsBlockOpenOrComma = $beforeToken->equals(',') || null !== $this->getBlock($tokens, $beforeOpenIndex, true); + $afterIsBlockEndOrComma = $afterToken->equals(',') || null !== $this->getBlock($tokens, $afterCloseIndex, false); + + if (($beforeIsBlockOpenOrComma && $afterIsBinaryOperation) || ($beforeIsBinaryOperation && $afterIsBlockEndOrComma)) { + // $beforeIsBlockOpenOrComma && $afterIsBlockEndOrComma is covered by `isWrappedSequenceElement` + // `[ (x) +` or `+ (X) ]` or `, (X) +` or `+ (X) ,` + + return true; + } + + if ($tokens[$beforeOpenIndex]->equals('}')) { + $beforeIsStatementOpen = !$this->closeCurlyBelongsToDynamicElement($tokens, $beforeOpenIndex); + } else { + $beforeIsStatementOpen = $beforeToken->equalsAny(self::BEFORE_TYPES) || $beforeToken->isGivenKind(T_CASE); + } + + $afterIsStatementEnd = $afterToken->equalsAny([';', [T_CLOSE_TAG]]); + + return + ($beforeIsStatementOpen && $afterIsBinaryOperation) // `isGivenKind([T_PRINT, T_YIELD, T_YIELD_FROM, T_REQUIRE, T_REQUIRE_ONCE, T_INCLUDE, T_INCLUDE_ONCE])) { + return false; + } + + $beforeOpenIndex = $tokens->getPrevMeaningfulToken($beforeOpenIndex); + + return $this->isWrappedSequenceElement($tokens, $beforeOpenIndex, $afterCloseIndex); + } + + // any of `isGivenKind(T_CASE)) { + return $tokens[$afterCloseIndex]->equalsAny([':', ';']); // `switch case` + } + + if (!$tokens[$afterCloseIndex]->equalsAny([';', [T_CLOSE_TAG]])) { + return false; + } + + if ($tokens[$beforeOpenIndex]->equals('}')) { + return !$this->closeCurlyBelongsToDynamicElement($tokens, $beforeOpenIndex); + } + + return $tokens[$beforeOpenIndex]->equalsAny(self::BEFORE_TYPES); + } + + private function isSimpleAssignment(Tokens $tokens, int $beforeOpenIndex, int $afterCloseIndex): bool + { + return $tokens[$beforeOpenIndex]->equals('=') && $tokens[$afterCloseIndex]->equalsAny([';', [T_CLOSE_TAG]]); // `= (X) ;` + } + + private function isWrappedSequenceElement(Tokens $tokens, int $startIndex, int $endIndex): bool + { + $startIsComma = $tokens[$startIndex]->equals(','); + $endIsComma = $tokens[$endIndex]->equals(','); + + if ($startIsComma && $endIsComma) { + return true; // `,(X),` + } + + $blockTypeStart = $this->getBlock($tokens, $startIndex, true); + $blockTypeEnd = $this->getBlock($tokens, $endIndex, false); + + return + ($startIsComma && null !== $blockTypeEnd) // `,(X)]` + || ($endIsComma && null !== $blockTypeStart) // `[(X),` + || (null !== $blockTypeEnd && null !== $blockTypeStart); // any type of `{(X)}`, `[(X)]` and `((X))` + } + + // any of `for( (X); ;(X)) ;` note that the middle element is covered as 'single statement' as it is `; (X) ;` + private function isWrappedForElement(Tokens $tokens, int $beforeOpenIndex, int $afterCloseIndex): bool + { + $forCandidateIndex = null; + + if ($tokens[$beforeOpenIndex]->equals('(') && $tokens[$afterCloseIndex]->equals(';')) { + $forCandidateIndex = $tokens->getPrevMeaningfulToken($beforeOpenIndex); + } elseif ($tokens[$afterCloseIndex]->equals(')') && $tokens[$beforeOpenIndex]->equals(';')) { + $forCandidateIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $afterCloseIndex); + $forCandidateIndex = $tokens->getPrevMeaningfulToken($forCandidateIndex); + } + + return null !== $forCandidateIndex && $tokens[$forCandidateIndex]->isGivenKind(T_FOR); + } + + // `fn() => (X);` + private function isWrappedFnBody(Tokens $tokens, int $beforeOpenIndex, int $afterCloseIndex): bool + { + if (!$tokens[$beforeOpenIndex]->isGivenKind(T_DOUBLE_ARROW)) { + return false; + } + + $beforeOpenIndex = $tokens->getPrevMeaningfulToken($beforeOpenIndex); + + if ($tokens[$beforeOpenIndex]->isGivenKind(T_STRING)) { + while (true) { + $beforeOpenIndex = $tokens->getPrevMeaningfulToken($beforeOpenIndex); + + if (!$tokens[$beforeOpenIndex]->isGivenKind([T_STRING, CT::T_TYPE_INTERSECTION, CT::T_TYPE_ALTERNATION])) { + break; + } + } + + if (!$tokens[$beforeOpenIndex]->isGivenKind(CT::T_TYPE_COLON)) { + return false; + } + + $beforeOpenIndex = $tokens->getPrevMeaningfulToken($beforeOpenIndex); + } + + if (!$tokens[$beforeOpenIndex]->equals(')')) { + return false; + } + + $beforeOpenIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $beforeOpenIndex); + $beforeOpenIndex = $tokens->getPrevMeaningfulToken($beforeOpenIndex); + + if ($tokens[$beforeOpenIndex]->isGivenKind(CT::T_RETURN_REF)) { + $beforeOpenIndex = $tokens->getPrevMeaningfulToken($beforeOpenIndex); + } + + if (!$tokens[$beforeOpenIndex]->isGivenKind(T_FN)) { + return false; + } + + return $tokens[$afterCloseIndex]->equalsAny([';', ',', [T_CLOSE_TAG]]); + } + + private function isPreUnaryOperation(Tokens $tokens, int $index): bool + { + return $this->tokensAnalyzer->isUnaryPredecessorOperator($index) || $tokens[$index]->isCast(); + } + + private function getBeforePreUnaryOperation(Tokens $tokens, int $index): int + { + do { + $index = $tokens->getPrevMeaningfulToken($index); + } while ($this->isPreUnaryOperation($tokens, $index)); + + return $index; + } + + // array access `(X)[` or `(X){` or object access `(X)->` or `(X)?->` + private function isAccess(Tokens $tokens, int $index): bool + { + $token = $tokens[$index]; + + return $token->isObjectOperator() || $token->equals('[') || $token->isGivenKind([CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN]); + } + + private function getAfterAccess(Tokens $tokens, int $index): int + { + while (true) { + $block = $this->getBlock($tokens, $index, true); + + if (null !== $block) { + $index = $tokens->findBlockEnd($block['type'], $index); + $index = $tokens->getNextMeaningfulToken($index); + + continue; + } + + if ( + $tokens[$index]->isObjectOperator() + || $tokens[$index]->equalsAny(['$', [T_PAAMAYIM_NEKUDOTAYIM], [T_STRING], [T_VARIABLE]]) + ) { + $index = $tokens->getNextMeaningfulToken($index); + + continue; + } + + break; + } + + return $index; + } + + /** + * @return null|array{type: Tokens::BLOCK_TYPE_*, isStart: bool} + */ + private function getBlock(Tokens $tokens, int $index, bool $isStart): ?array + { + $block = Tokens::detectBlockType($tokens[$index]); + + return null !== $block && $isStart === $block['isStart'] && \in_array($block['type'], self::BLOCK_TYPES, true) ? $block : null; + } + + // cheap check on a tokens type before `(` of which we know the `(` will never be superfluous + private function isKnownNegativePre(Token $token): bool + { + static $knownNegativeTypes; + + if (null === $knownNegativeTypes) { + $knownNegativeTypes = [ + [CT::T_CLASS_CONSTANT], + [CT::T_DYNAMIC_VAR_BRACE_CLOSE], + [CT::T_RETURN_REF], + [CT::T_USE_LAMBDA], + [T_ARRAY], + [T_CATCH], + [T_CLASS], + [T_DECLARE], + [T_ELSEIF], + [T_EMPTY], + [T_EXIT], + [T_EVAL], + [T_FN], + [T_FOREACH], + [T_FOR], + [T_FUNCTION], + [T_HALT_COMPILER], + [T_IF], + [T_ISSET], + [T_LIST], + [T_STRING], + [T_SWITCH], + [T_STATIC], + [T_UNSET], + [T_VARIABLE], + [T_WHILE], + // handled by the `include` rule + [T_REQUIRE], + [T_REQUIRE_ONCE], + [T_INCLUDE], + [T_INCLUDE_ONCE], + ]; + + if (\defined('T_MATCH')) { // @TODO: drop condition and add directly in `$knownNegativeTypes` above when PHP 8.0+ is required + $knownNegativeTypes[] = T_MATCH; + } + } + + return $token->equalsAny($knownNegativeTypes); + } + + private function containsOperation(Tokens $tokens, int $startIndex, int $endIndex): bool + { + while (true) { + $startIndex = $tokens->getNextMeaningfulToken($startIndex); + + if ($startIndex === $endIndex) { + break; + } + + $block = Tokens::detectBlockType($tokens[$startIndex]); + + if (null !== $block && $block['isStart']) { + $startIndex = $tokens->findBlockEnd($block['type'], $startIndex); + + continue; + } + + if (!$tokens[$startIndex]->equalsAny($this->noopTypes)) { + return true; + } + } + + return false; + } + + private function getConfigType(Tokens $tokens, int $beforeOpenIndex): ?string + { + if ($tokens[$beforeOpenIndex]->isGivenKind(self::TOKEN_TYPE_NO_CONFIG)) { + return null; + } + + foreach (self::TOKEN_TYPE_CONFIG_MAP as $type => $configItem) { + if ($tokens[$beforeOpenIndex]->isGivenKind($type)) { + return $configItem; + } + } + + return 'others'; + } + + private function removeUselessParenthesisPair( + Tokens $tokens, + int $beforeOpenIndex, + int $afterCloseIndex, + int $openIndex, + int $closeIndex, + ?string $configType + ): void { + $statements = $this->configuration['statements']; + + if (null === $configType || !\in_array($configType, $statements, true)) { + return; + } + + $needsSpaceAfter = + !$this->isAccess($tokens, $afterCloseIndex) + && !$tokens[$afterCloseIndex]->equalsAny([';', ',', [T_CLOSE_TAG]]) + && null === $this->getBlock($tokens, $afterCloseIndex, false) + && !($tokens[$afterCloseIndex]->equalsAny([':', ';']) && $tokens[$beforeOpenIndex]->isGivenKind(T_CASE)); + + $needsSpaceBefore = + !$this->isPreUnaryOperation($tokens, $beforeOpenIndex) + && !$tokens[$beforeOpenIndex]->equalsAny(['}', [T_EXIT], [T_OPEN_TAG]]) + && null === $this->getBlock($tokens, $beforeOpenIndex, true); + + $this->removeBrace($tokens, $closeIndex, $needsSpaceAfter); + $this->removeBrace($tokens, $openIndex, $needsSpaceBefore); + } + + private function removeBrace(Tokens $tokens, int $index, bool $needsSpace): void + { + if ($needsSpace) { + foreach ([-1, 1] as $direction) { + $siblingIndex = $tokens->getNonEmptySibling($index, $direction); + + if ($tokens[$siblingIndex]->isWhitespace() || $tokens[$siblingIndex]->isComment()) { + $needsSpace = false; + + break; + } + } + } + + if ($needsSpace) { + $tokens[$index] = new Token([T_WHITESPACE, ' ']); + } else { + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } + } + + private function closeCurlyBelongsToDynamicElement(Tokens $tokens, int $beforeOpenIndex): bool + { + $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $beforeOpenIndex); + $index = $tokens->getPrevMeaningfulToken($index); + + if ($tokens[$index]->isGivenKind(T_DOUBLE_COLON)) { + return true; + } + + if ($tokens[$index]->equals(':')) { + $index = $tokens->getPrevTokenOfKind($index, [[T_CASE], '?']); + + return !$tokens[$index]->isGivenKind(T_CASE); + } + + return false; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUnneededCurlyBracesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUnneededCurlyBracesFixer.php new file mode 100644 index 00000000..b71c524b --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUnneededCurlyBracesFixer.php @@ -0,0 +1,92 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ControlStructure; + +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; + +/** + * @deprecated + */ +final class NoUnneededCurlyBracesFixer extends AbstractProxyFixer implements ConfigurableFixerInterface, DeprecatedFixerInterface +{ + private NoUnneededBracesFixer $noUnneededBracesFixer; + + public function __construct() + { + $this->noUnneededBracesFixer = new NoUnneededBracesFixer(); + + parent::__construct(); + } + + public function getDefinition(): FixerDefinitionInterface + { + $fixerDefinition = $this->noUnneededBracesFixer->getDefinition(); + + return new FixerDefinition( + 'Removes unneeded curly braces that are superfluous and aren\'t part of a control structure\'s body.', + $fixerDefinition->getCodeSamples(), + $fixerDefinition->getDescription(), + $fixerDefinition->getRiskyDescription() + ); + } + + public function configure(array $configuration): void + { + $this->noUnneededBracesFixer->configure($configuration); + + parent::configure($configuration); + } + + /** + * {@inheritdoc} + * + * Must run before NoUselessElseFixer, NoUselessReturnFixer, ReturnAssignmentFixer, SimplifiedIfReturnFixer. + */ + public function getPriority(): int + { + return $this->noUnneededBracesFixer->getPriority(); + } + + public function getSuccessorsNames(): array + { + return [ + $this->noUnneededBracesFixer->getName(), + ]; + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('namespaces', 'Remove unneeded curly braces from bracketed namespaces.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + + protected function createProxyFixers(): array + { + return [ + $this->noUnneededBracesFixer, + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUselessElseFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUselessElseFixer.php new file mode 100644 index 00000000..9b2d1cce --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUselessElseFixer.php @@ -0,0 +1,120 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ControlStructure; + +use PhpCsFixer\AbstractNoUselessElseFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Tokens; + +final class NoUselessElseFixer extends AbstractNoUselessElseFixer +{ + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_ELSE); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'There should not be useless `else` cases.', + [ + new CodeSample(" $token) { + if (!$token->isGivenKind(T_ELSE)) { + continue; + } + + // `else if` vs. `else` and alternative syntax `else:` checks + if ($tokens[$tokens->getNextMeaningfulToken($index)]->equalsAny([':', [T_IF]])) { + continue; + } + + // clean up `else` if it is an empty statement + $this->fixEmptyElse($tokens, $index); + if ($tokens->isEmptyAt($index)) { + continue; + } + + // clean up `else` if possible + if ($this->isSuperfluousElse($tokens, $index)) { + $this->clearElse($tokens, $index); + } + } + } + + /** + * Remove tokens part of an `else` statement if not empty (i.e. no meaningful tokens inside). + * + * @param int $index T_ELSE index + */ + private function fixEmptyElse(Tokens $tokens, int $index): void + { + $next = $tokens->getNextMeaningfulToken($index); + + if ($tokens[$next]->equals('{')) { + $close = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $next); + if (1 === $close - $next) { // '{}' + $this->clearElse($tokens, $index); + } elseif ($tokens->getNextMeaningfulToken($next) === $close) { // '{/**/}' + $this->clearElse($tokens, $index); + } + + return; + } + + // short `else` + $end = $tokens->getNextTokenOfKind($index, [';', [T_CLOSE_TAG]]); + if ($next === $end) { + $this->clearElse($tokens, $index); + } + } + + /** + * @param int $index index of T_ELSE + */ + private function clearElse(Tokens $tokens, int $index): void + { + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + + // clear T_ELSE and the '{' '}' if there are any + $next = $tokens->getNextMeaningfulToken($index); + + if (!$tokens[$next]->equals('{')) { + return; + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $next)); + $tokens->clearTokenAndMergeSurroundingWhitespace($next); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SimplifiedIfReturnFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SimplifiedIfReturnFixer.php new file mode 100644 index 00000000..6af35387 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SimplifiedIfReturnFixer.php @@ -0,0 +1,138 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ControlStructure; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Filippo Tessarotto + */ +final class SimplifiedIfReturnFixer extends AbstractFixer +{ + /** + * @var list|string>}> + */ + private array $sequences = [ + [ + 'isNegative' => false, + 'sequence' => [ + '{', [T_RETURN], [T_STRING, 'true'], ';', '}', + [T_RETURN], [T_STRING, 'false'], ';', + ], + ], + [ + 'isNegative' => true, + 'sequence' => [ + '{', [T_RETURN], [T_STRING, 'false'], ';', '}', + [T_RETURN], [T_STRING, 'true'], ';', + ], + ], + [ + 'isNegative' => false, + 'sequence' => [ + [T_RETURN], [T_STRING, 'true'], ';', + [T_RETURN], [T_STRING, 'false'], ';', + ], + ], + [ + 'isNegative' => true, + 'sequence' => [ + [T_RETURN], [T_STRING, 'false'], ';', + [T_RETURN], [T_STRING, 'true'], ';', + ], + ], + ]; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Simplify `if` control structures that return the boolean result of their condition.', + [new CodeSample("isAllTokenKindsFound([T_IF, T_RETURN, T_STRING]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($ifIndex = $tokens->count() - 1; 0 <= $ifIndex; --$ifIndex) { + if (!$tokens[$ifIndex]->isGivenKind([T_IF, T_ELSEIF])) { + continue; + } + + if ($tokens[$tokens->getPrevMeaningfulToken($ifIndex)]->equals(')')) { + continue; // in a loop without braces + } + + $startParenthesisIndex = $tokens->getNextTokenOfKind($ifIndex, ['(']); + $endParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startParenthesisIndex); + $firstCandidateIndex = $tokens->getNextMeaningfulToken($endParenthesisIndex); + + foreach ($this->sequences as $sequenceSpec) { + $sequenceFound = $tokens->findSequence($sequenceSpec['sequence'], $firstCandidateIndex); + + if (null === $sequenceFound) { + continue; + } + + $firstSequenceIndex = array_key_first($sequenceFound); + + if ($firstSequenceIndex !== $firstCandidateIndex) { + continue; + } + + $indicesToClear = array_keys($sequenceFound); + array_pop($indicesToClear); // Preserve last semicolon + rsort($indicesToClear); + + foreach ($indicesToClear as $index) { + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } + + $newTokens = [ + new Token([T_RETURN, 'return']), + new Token([T_WHITESPACE, ' ']), + ]; + + if ($sequenceSpec['isNegative']) { + $newTokens[] = new Token('!'); + } else { + $newTokens[] = new Token([T_BOOL_CAST, '(bool)']); + } + + $tokens->overrideRange($ifIndex, $ifIndex, $newTokens); + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SwitchCaseSemicolonToColonFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SwitchCaseSemicolonToColonFixer.php new file mode 100644 index 00000000..f5e2210f --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SwitchCaseSemicolonToColonFixer.php @@ -0,0 +1,87 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ControlStructure; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\SwitchAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\ControlCaseStructuresAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Fixer for rules defined in PSR2 ¶5.2. + */ +final class SwitchCaseSemicolonToColonFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'A case should be followed by a colon and not a semicolon.', + [ + new CodeSample( + 'isTokenKindFound(T_SWITCH); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + /** @var SwitchAnalysis $analysis */ + foreach (ControlCaseStructuresAnalyzer::findControlStructures($tokens, [T_SWITCH]) as $analysis) { + $default = $analysis->getDefaultAnalysis(); + + if (null !== $default) { + $this->fixTokenIfNeeded($tokens, $default->getColonIndex()); + } + + foreach ($analysis->getCases() as $caseAnalysis) { + $this->fixTokenIfNeeded($tokens, $caseAnalysis->getColonIndex()); + } + } + } + + private function fixTokenIfNeeded(Tokens $tokens, int $index): void + { + if ($tokens[$index]->equals(';')) { + $tokens[$index] = new Token(':'); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SwitchCaseSpaceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SwitchCaseSpaceFixer.php new file mode 100644 index 00000000..5616e1f3 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SwitchCaseSpaceFixer.php @@ -0,0 +1,85 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ControlStructure; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\SwitchAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\ControlCaseStructuresAnalyzer; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Fixer for rules defined in PSR2 ¶5.2. + * + * @author Sullivan Senechal + */ +final class SwitchCaseSpaceFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Removes extra spaces between colon and case value.', + [ + new CodeSample( + 'isTokenKindFound(T_SWITCH); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + /** @var SwitchAnalysis $analysis */ + foreach (ControlCaseStructuresAnalyzer::findControlStructures($tokens, [T_SWITCH]) as $analysis) { + $default = $analysis->getDefaultAnalysis(); + + if (null !== $default) { + $index = $default->getIndex(); + + if (!$tokens[$index + 1]->isWhitespace() || !$tokens[$index + 2]->equalsAny([':', ';'])) { + continue; + } + + $tokens->clearAt($index + 1); + } + + foreach ($analysis->getCases() as $caseAnalysis) { + $colonIndex = $caseAnalysis->getColonIndex(); + $valueIndex = $tokens->getPrevNonWhitespace($colonIndex); + + // skip if there is no space between the colon and previous token or is space after comment + if ($valueIndex === $colonIndex - 1 || $tokens[$valueIndex]->isComment()) { + continue; + } + + $tokens->clearAt($valueIndex + 1); + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SwitchContinueToBreakFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SwitchContinueToBreakFixer.php new file mode 100644 index 00000000..23b523a8 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SwitchContinueToBreakFixer.php @@ -0,0 +1,240 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ControlStructure; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class SwitchContinueToBreakFixer extends AbstractFixer +{ + /** + * @var int[] + */ + private array $switchLevels = []; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Switch case must not be ended with `continue` but with `break`.', + [ + new CodeSample( + ' 3) { + continue; + } + + continue 2; + } +} +' + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run after NoAlternativeSyntaxFixer. + */ + public function getPriority(): int + { + return 0; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAllTokenKindsFound([T_SWITCH, T_CONTINUE]) && !$tokens->hasAlternativeSyntax(); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $count = \count($tokens); + + for ($index = 1; $index < $count - 1; ++$index) { + $index = $this->doFix($tokens, $index, 0, false); + } + } + + /** + * @param int $depth >= 0 + */ + private function doFix(Tokens $tokens, int $index, int $depth, bool $isInSwitch): int + { + $token = $tokens[$index]; + + if ($token->isGivenKind([T_FOREACH, T_FOR, T_WHILE])) { + // go to first `(`, go to its close ')', go to first of '{', ';', '? >' + $index = $tokens->getNextTokenOfKind($index, ['(']); + $index = $tokens->getNextTokenOfKind($index, [')']); + $index = $tokens->getNextTokenOfKind($index, ['{', ';', [T_CLOSE_TAG]]); + + if (!$tokens[$index]->equals('{')) { + return $index; + } + + return $this->fixInLoop($tokens, $index, $depth + 1); + } + + if ($token->isGivenKind(T_DO)) { + return $this->fixInLoop($tokens, $tokens->getNextTokenOfKind($index, ['{']), $depth + 1); + } + + if ($token->isGivenKind(T_SWITCH)) { + return $this->fixInSwitch($tokens, $index, $depth + 1); + } + + if ($token->isGivenKind(T_CONTINUE)) { + return $this->fixContinueWhenActsAsBreak($tokens, $index, $isInSwitch, $depth); + } + + return $index; + } + + private function fixInSwitch(Tokens $tokens, int $switchIndex, int $depth): int + { + $this->switchLevels[] = $depth; + + // figure out where the switch starts + $openIndex = $tokens->getNextTokenOfKind($switchIndex, ['{']); + + // figure out where the switch ends + $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $openIndex); + + for ($index = $openIndex + 1; $index < $closeIndex; ++$index) { + $index = $this->doFix($tokens, $index, $depth, true); + } + + array_pop($this->switchLevels); + + return $closeIndex; + } + + private function fixInLoop(Tokens $tokens, int $openIndex, int $depth): int + { + $openCount = 1; + + while (true) { + ++$openIndex; + $token = $tokens[$openIndex]; + + if ($token->equals('{')) { + ++$openCount; + + continue; + } + + if ($token->equals('}')) { + --$openCount; + + if (0 === $openCount) { + break; + } + + continue; + } + + $openIndex = $this->doFix($tokens, $openIndex, $depth, false); + } + + return $openIndex; + } + + private function fixContinueWhenActsAsBreak(Tokens $tokens, int $continueIndex, bool $isInSwitch, int $depth): int + { + $followingContinueIndex = $tokens->getNextMeaningfulToken($continueIndex); + $followingContinueToken = $tokens[$followingContinueIndex]; + + if ($isInSwitch && $followingContinueToken->equals(';')) { + $this->replaceContinueWithBreakToken($tokens, $continueIndex); // short continue 1 notation + + return $followingContinueIndex; + } + + if (!$followingContinueToken->isGivenKind(T_LNUMBER)) { + return $followingContinueIndex; + } + + $afterFollowingContinueIndex = $tokens->getNextMeaningfulToken($followingContinueIndex); + + if (!$tokens[$afterFollowingContinueIndex]->equals(';')) { + return $afterFollowingContinueIndex; // if next not is `;` return without fixing, for example `continue 1 ? >getContent(); + $jump = str_replace('_', '', $jump); // support for numeric_literal_separator + + if (\strlen($jump) > 2 && 'x' === $jump[1]) { + $jump = hexdec($jump); // hexadecimal - 0x1 + } elseif (\strlen($jump) > 2 && 'b' === $jump[1]) { + $jump = bindec($jump); // binary - 0b1 + } elseif (\strlen($jump) > 1 && '0' === $jump[0]) { + $jump = octdec($jump); // octal 01 + } elseif (Preg::match('#^\d+$#', $jump)) { // positive int + $jump = (float) $jump; // cast to float, might be a number bigger than PHP max. int value + } else { + return $afterFollowingContinueIndex; // cannot process value, ignore + } + + if ($jump > PHP_INT_MAX) { + return $afterFollowingContinueIndex; // cannot process value, ignore + } + + $jump = (int) $jump; + + if ($isInSwitch && (1 === $jump || 0 === $jump)) { + $this->replaceContinueWithBreakToken($tokens, $continueIndex); // long continue 0/1 notation + + return $afterFollowingContinueIndex; + } + + $jumpDestination = $depth - $jump + 1; + + if (\in_array($jumpDestination, $this->switchLevels, true)) { + $this->replaceContinueWithBreakToken($tokens, $continueIndex); + + return $afterFollowingContinueIndex; + } + + return $afterFollowingContinueIndex; + } + + private function replaceContinueWithBreakToken(Tokens $tokens, int $index): void + { + $tokens[$index] = new Token([T_BREAK, 'break']); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/TrailingCommaInMultilineFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/TrailingCommaInMultilineFixer.php new file mode 100644 index 00000000..d97f61cb --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/TrailingCommaInMultilineFixer.php @@ -0,0 +1,233 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ControlStructure; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerConfiguration\InvalidOptionsForEnvException; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; +use Symfony\Component\OptionsResolver\Options; + +/** + * @author Sebastiaan Stok + * @author Dariusz Rumiński + * @author Kuba Werłos + */ +final class TrailingCommaInMultilineFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** + * @internal + */ + public const ELEMENTS_ARRAYS = 'arrays'; + + /** + * @internal + */ + public const ELEMENTS_ARGUMENTS = 'arguments'; + + /** + * @internal + */ + public const ELEMENTS_PARAMETERS = 'parameters'; + + private const MATCH_EXPRESSIONS = 'match'; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Multi-line arrays, arguments list, parameters list and `match` expressions must have a trailing comma.', + [ + new CodeSample(" true] + ), + new CodeSample(" [self::ELEMENTS_ARGUMENTS]]), + new VersionSpecificCodeSample(" [self::ELEMENTS_PARAMETERS]]), + ] + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN, '(']); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('after_heredoc', 'Whether a trailing comma should also be placed after heredoc end.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + (new FixerOptionBuilder('elements', sprintf('Where to fix multiline trailing comma (PHP >= 8.0 for `%s` and `%s`).', self::ELEMENTS_PARAMETERS, self::MATCH_EXPRESSIONS))) // @TODO: remove text when PHP 8.0+ is required + ->setAllowedTypes(['array']) + ->setAllowedValues([new AllowedValueSubset([self::ELEMENTS_ARRAYS, self::ELEMENTS_ARGUMENTS, self::ELEMENTS_PARAMETERS, self::MATCH_EXPRESSIONS])]) + ->setDefault([self::ELEMENTS_ARRAYS]) + ->setNormalizer(static function (Options $options, array $value) { + if (\PHP_VERSION_ID < 8_00_00) { // @TODO: drop condition when PHP 8.0+ is required + foreach ([self::ELEMENTS_PARAMETERS, self::MATCH_EXPRESSIONS] as $option) { + if (\in_array($option, $value, true)) { + throw new InvalidOptionsForEnvException(sprintf('"%s" option can only be enabled with PHP 8.0+.', $option)); + } + } + } + + return $value; + }) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $fixArrays = \in_array(self::ELEMENTS_ARRAYS, $this->configuration['elements'], true); + $fixArguments = \in_array(self::ELEMENTS_ARGUMENTS, $this->configuration['elements'], true); + $fixParameters = \PHP_VERSION_ID >= 8_00_00 && \in_array(self::ELEMENTS_PARAMETERS, $this->configuration['elements'], true); // @TODO: drop condition when PHP 8.0+ is required + $fixMatch = \PHP_VERSION_ID >= 8_00_00 && \in_array(self::MATCH_EXPRESSIONS, $this->configuration['elements'], true); // @TODO: drop condition when PHP 8.0+ is required + + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + $prevIndex = $tokens->getPrevMeaningfulToken($index); + + if ( + $fixArrays + && ( + $tokens[$index]->equals('(') && $tokens[$prevIndex]->isGivenKind(T_ARRAY) // long syntax + || $tokens[$index]->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN) // short syntax + ) + ) { + $this->fixBlock($tokens, $index); + + continue; + } + + if (!$tokens[$index]->equals('(')) { + continue; + } + + $prevPrevIndex = $tokens->getPrevMeaningfulToken($prevIndex); + + if ($fixArguments + && $tokens[$prevIndex]->equalsAny([']', [T_CLASS], [T_STRING], [T_VARIABLE], [T_STATIC]]) + && !$tokens[$prevPrevIndex]->isGivenKind(T_FUNCTION) + ) { + $this->fixBlock($tokens, $index); + + continue; + } + + if ( + $fixParameters + && ( + $tokens[$prevIndex]->isGivenKind(T_STRING) && $tokens[$prevPrevIndex]->isGivenKind(T_FUNCTION) + || $tokens[$prevIndex]->isGivenKind([T_FN, T_FUNCTION]) + ) + ) { + $this->fixBlock($tokens, $index); + } + + if ($fixMatch && $tokens[$prevIndex]->isGivenKind(T_MATCH)) { + $this->fixMatch($tokens, $index); + } + } + } + + private function fixBlock(Tokens $tokens, int $startIndex): void + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + + if (!$tokensAnalyzer->isBlockMultiline($tokens, $startIndex)) { + return; + } + + $blockType = Tokens::detectBlockType($tokens[$startIndex]); + $endIndex = $tokens->findBlockEnd($blockType['type'], $startIndex); + + $beforeEndIndex = $tokens->getPrevMeaningfulToken($endIndex); + if (!$tokens->isPartialCodeMultiline($beforeEndIndex, $endIndex)) { + return; + } + $beforeEndToken = $tokens[$beforeEndIndex]; + + // if there is some item between braces then add `,` after it + if ( + $startIndex !== $beforeEndIndex && !$beforeEndToken->equals(',') + && (true === $this->configuration['after_heredoc'] || !$beforeEndToken->isGivenKind(T_END_HEREDOC)) + ) { + $tokens->insertAt($beforeEndIndex + 1, new Token(',')); + + $endToken = $tokens[$endIndex]; + + if (!$endToken->isComment() && !$endToken->isWhitespace()) { + $tokens->ensureWhitespaceAtIndex($endIndex, 1, ' '); + } + } + } + + private function fixMatch(Tokens $tokens, int $index): void + { + $index = $tokens->getNextTokenOfKind($index, ['{']); + $closeIndex = $index; + $isMultiline = false; + $depth = 1; + + do { + ++$closeIndex; + + if ($tokens[$closeIndex]->equals('{')) { + ++$depth; + } elseif ($tokens[$closeIndex]->equals('}')) { + --$depth; + } elseif (!$isMultiline && str_contains($tokens[$closeIndex]->getContent(), "\n")) { + $isMultiline = true; + } + } while ($depth > 0); + + if (!$isMultiline) { + return; + } + + $previousIndex = $tokens->getPrevMeaningfulToken($closeIndex); + if (!$tokens->isPartialCodeMultiline($previousIndex, $closeIndex)) { + return; + } + + if (!$tokens[$previousIndex]->equals(',')) { + $tokens->insertAt($previousIndex + 1, new Token(',')); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/YodaStyleFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/YodaStyleFixer.php new file mode 100644 index 00000000..c8005f1b --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/YodaStyleFixer.php @@ -0,0 +1,741 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ControlStructure; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Bram Gotink + * @author Dariusz Rumiński + */ +final class YodaStyleFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** + * @var array + */ + private $candidatesMap; + + /** + * @var array + */ + private $candidateTypesConfiguration; + + /** + * @var list + */ + private $candidateTypes; + + public function configure(array $configuration): void + { + parent::configure($configuration); + + $this->resolveConfiguration(); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Write conditions in Yoda style (`true`), non-Yoda style (`[\'equal\' => false, \'identical\' => false, \'less_and_greater\' => false]`) or ignore those conditions (`null`) based on configuration.', + [ + new CodeSample( + ' 3; // less than +', + [ + 'equal' => true, + 'identical' => false, + 'less_and_greater' => null, + ] + ), + new CodeSample( + ' true, + ] + ), + new CodeSample( + ' false, + 'identical' => false, + 'less_and_greater' => false, + ] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run after IsNullFixer. + */ + public function getPriority(): int + { + return 0; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound($this->candidateTypes); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $this->fixTokens($tokens); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('equal', 'Style for equal (`==`, `!=`) statements.')) + ->setAllowedTypes(['bool', 'null']) + ->setDefault(true) + ->getOption(), + (new FixerOptionBuilder('identical', 'Style for identical (`===`, `!==`) statements.')) + ->setAllowedTypes(['bool', 'null']) + ->setDefault(true) + ->getOption(), + (new FixerOptionBuilder('less_and_greater', 'Style for less and greater than (`<`, `<=`, `>`, `>=`) statements.')) + ->setAllowedTypes(['bool', 'null']) + ->setDefault(null) + ->getOption(), + (new FixerOptionBuilder('always_move_variable', 'Whether variables should always be on non assignable side when applying Yoda style.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + + /** + * Finds the end of the right-hand side of the comparison at the given + * index. + * + * The right-hand side ends when an operator with a lower precedence is + * encountered or when the block level for `()`, `{}` or `[]` goes below + * zero. + * + * @param Tokens $tokens The token list + * @param int $index The index of the comparison + * + * @return int The last index of the right-hand side of the comparison + */ + private function findComparisonEnd(Tokens $tokens, int $index): int + { + ++$index; + $count = \count($tokens); + + while ($index < $count) { + $token = $tokens[$index]; + + if ($token->isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) { + ++$index; + + continue; + } + + if ($this->isOfLowerPrecedence($token)) { + break; + } + + $block = Tokens::detectBlockType($token); + + if (null === $block) { + ++$index; + + continue; + } + + if (!$block['isStart']) { + break; + } + + $index = $tokens->findBlockEnd($block['type'], $index) + 1; + } + + $prev = $tokens->getPrevMeaningfulToken($index); + + return $tokens[$prev]->isGivenKind(T_CLOSE_TAG) ? $tokens->getPrevMeaningfulToken($prev) : $prev; + } + + /** + * Finds the start of the left-hand side of the comparison at the given + * index. + * + * The left-hand side ends when an operator with a lower precedence is + * encountered or when the block level for `()`, `{}` or `[]` goes below + * zero. + * + * @param Tokens $tokens The token list + * @param int $index The index of the comparison + * + * @return int The first index of the left-hand side of the comparison + */ + private function findComparisonStart(Tokens $tokens, int $index): int + { + --$index; + $nonBlockFound = false; + + while (0 <= $index) { + $token = $tokens[$index]; + + if ($token->isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) { + --$index; + + continue; + } + + if ($token->isGivenKind([CT::T_NAMED_ARGUMENT_COLON])) { + break; + } + + if ($this->isOfLowerPrecedence($token)) { + break; + } + + $block = Tokens::detectBlockType($token); + + if (null === $block) { + --$index; + $nonBlockFound = true; + + continue; + } + + if ( + $block['isStart'] + || ($nonBlockFound && Tokens::BLOCK_TYPE_CURLY_BRACE === $block['type']) // closing of structure not related to the comparison + ) { + break; + } + + $index = $tokens->findBlockStart($block['type'], $index) - 1; + } + + return $tokens->getNextMeaningfulToken($index); + } + + private function fixTokens(Tokens $tokens): Tokens + { + for ($i = \count($tokens) - 1; $i > 1; --$i) { + if ($tokens[$i]->isGivenKind($this->candidateTypes)) { + $yoda = $this->candidateTypesConfiguration[$tokens[$i]->getId()]; + } elseif ( + ($tokens[$i]->equals('<') && \in_array('<', $this->candidateTypes, true)) + || ($tokens[$i]->equals('>') && \in_array('>', $this->candidateTypes, true)) + ) { + $yoda = $this->candidateTypesConfiguration[$tokens[$i]->getContent()]; + } else { + continue; + } + + $fixableCompareInfo = $this->getCompareFixableInfo($tokens, $i, $yoda); + + if (null === $fixableCompareInfo) { + continue; + } + + $i = $this->fixTokensCompare( + $tokens, + $fixableCompareInfo['left']['start'], + $fixableCompareInfo['left']['end'], + $i, + $fixableCompareInfo['right']['start'], + $fixableCompareInfo['right']['end'] + ); + } + + return $tokens; + } + + /** + * Fixes the comparison at the given index. + * + * A comparison is considered fixed when + * - both sides are a variable (e.g. $a === $b) + * - neither side is a variable (e.g. self::CONST === 3) + * - only the right-hand side is a variable (e.g. 3 === self::$var) + * + * If the left-hand side and right-hand side of the given comparison are + * swapped, this function runs recursively on the previous left-hand-side. + * + * @return int an upper bound for all non-fixed comparisons + */ + private function fixTokensCompare( + Tokens $tokens, + int $startLeft, + int $endLeft, + int $compareOperatorIndex, + int $startRight, + int $endRight + ): int { + $type = $tokens[$compareOperatorIndex]->getId(); + $content = $tokens[$compareOperatorIndex]->getContent(); + + if (\array_key_exists($type, $this->candidatesMap)) { + $tokens[$compareOperatorIndex] = clone $this->candidatesMap[$type]; + } elseif (\array_key_exists($content, $this->candidatesMap)) { + $tokens[$compareOperatorIndex] = clone $this->candidatesMap[$content]; + } + + $right = $this->fixTokensComparePart($tokens, $startRight, $endRight); + $left = $this->fixTokensComparePart($tokens, $startLeft, $endLeft); + + for ($i = $startRight; $i <= $endRight; ++$i) { + $tokens->clearAt($i); + } + + for ($i = $startLeft; $i <= $endLeft; ++$i) { + $tokens->clearAt($i); + } + + $tokens->insertAt($startRight, $left); + $tokens->insertAt($startLeft, $right); + + return $startLeft; + } + + private function fixTokensComparePart(Tokens $tokens, int $start, int $end): Tokens + { + $newTokens = $tokens->generatePartialCode($start, $end); + $newTokens = $this->fixTokens(Tokens::fromCode(sprintf('clearAt(\count($newTokens) - 1); + $newTokens->clearAt(0); + $newTokens->clearEmptyTokens(); + + return $newTokens; + } + + /** + * @return null|array{left: array{start: int, end: int}, right: array{start: int, end: int}} + */ + private function getCompareFixableInfo(Tokens $tokens, int $index, bool $yoda): ?array + { + $right = $this->getRightSideCompareFixableInfo($tokens, $index); + + if (!$yoda && $this->isOfLowerPrecedenceAssignment($tokens[$tokens->getNextMeaningfulToken($right['end'])])) { + return null; + } + + $left = $this->getLeftSideCompareFixableInfo($tokens, $index); + + if ($this->isListStatement($tokens, $left['start'], $left['end']) || $this->isListStatement($tokens, $right['start'], $right['end'])) { + return null; // do not fix lists assignment inside statements + } + + /** @var bool $strict */ + $strict = $this->configuration['always_move_variable']; + $leftSideIsVariable = $this->isVariable($tokens, $left['start'], $left['end'], $strict); + $rightSideIsVariable = $this->isVariable($tokens, $right['start'], $right['end'], $strict); + + if (!($leftSideIsVariable xor $rightSideIsVariable)) { + return null; // both are (not) variables, do not touch + } + + if (!$strict) { // special handling for braces with not "always_move_variable" + $leftSideIsVariable = $leftSideIsVariable && !$tokens[$left['start']]->equals('('); + $rightSideIsVariable = $rightSideIsVariable && !$tokens[$right['start']]->equals('('); + } + + return ($yoda && !$leftSideIsVariable) || (!$yoda && !$rightSideIsVariable) + ? null + : ['left' => $left, 'right' => $right]; + } + + /** + * @return array{start: int, end: int} + */ + private function getLeftSideCompareFixableInfo(Tokens $tokens, int $index): array + { + return [ + 'start' => $this->findComparisonStart($tokens, $index), + 'end' => $tokens->getPrevMeaningfulToken($index), + ]; + } + + /** + * @return array{start: int, end: int} + */ + private function getRightSideCompareFixableInfo(Tokens $tokens, int $index): array + { + return [ + 'start' => $tokens->getNextMeaningfulToken($index), + 'end' => $this->findComparisonEnd($tokens, $index), + ]; + } + + private function isListStatement(Tokens $tokens, int $index, int $end): bool + { + for ($i = $index; $i <= $end; ++$i) { + if ($tokens[$i]->isGivenKind([T_LIST, CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE])) { + return true; + } + } + + return false; + } + + /** + * Checks whether the given token has a lower precedence than `T_IS_EQUAL` + * or `T_IS_IDENTICAL`. + * + * @param Token $token The token to check + * + * @return bool Whether the token has a lower precedence + */ + private function isOfLowerPrecedence(Token $token): bool + { + static $tokens; + + if (null === $tokens) { + $tokens = [ + T_BOOLEAN_AND, // && + T_BOOLEAN_OR, // || + T_CASE, // case + T_DOUBLE_ARROW, // => + T_ECHO, // echo + T_GOTO, // goto + T_LOGICAL_AND, // and + T_LOGICAL_OR, // or + T_LOGICAL_XOR, // xor + T_OPEN_TAG, // isOfLowerPrecedenceAssignment($token) || $token->isGivenKind($tokens) || $token->equalsAny($otherTokens); + } + + /** + * Checks whether the given assignment token has a lower precedence than `T_IS_EQUAL` + * or `T_IS_IDENTICAL`. + */ + private function isOfLowerPrecedenceAssignment(Token $token): bool + { + static $tokens; + + if (null === $tokens) { + $tokens = [ + T_AND_EQUAL, // &= + T_CONCAT_EQUAL, // .= + T_DIV_EQUAL, // /= + T_MINUS_EQUAL, // -= + T_MOD_EQUAL, // %= + T_MUL_EQUAL, // *= + T_OR_EQUAL, // |= + T_PLUS_EQUAL, // += + T_POW_EQUAL, // **= + T_SL_EQUAL, // <<= + T_SR_EQUAL, // >>= + T_XOR_EQUAL, // ^= + T_COALESCE_EQUAL, // ??= + ]; + } + + return $token->equals('=') || $token->isGivenKind($tokens); + } + + /** + * Checks whether the tokens between the given start and end describe a + * variable. + * + * @param Tokens $tokens The token list + * @param int $start The first index of the possible variable + * @param int $end The last index of the possible variable + * @param bool $strict Enable strict variable detection + * + * @return bool Whether the tokens describe a variable + */ + private function isVariable(Tokens $tokens, int $start, int $end, bool $strict): bool + { + $tokenAnalyzer = new TokensAnalyzer($tokens); + + if ($start === $end) { + return $tokens[$start]->isGivenKind(T_VARIABLE); + } + + if ($tokens[$start]->equals('(')) { + return true; + } + + if ($strict) { + for ($index = $start; $index <= $end; ++$index) { + if ( + $tokens[$index]->isCast() + || $tokens[$index]->isGivenKind(T_INSTANCEOF) + || $tokens[$index]->equals('!') + || $tokenAnalyzer->isBinaryOperator($index) + ) { + return false; + } + } + } + + $index = $start; + + // handle multiple braces around statement ((($a === 1))) + while ( + $tokens[$index]->equals('(') + && $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index) === $end + ) { + $index = $tokens->getNextMeaningfulToken($index); + $end = $tokens->getPrevMeaningfulToken($end); + } + + $expectString = false; + + while ($index <= $end) { + $current = $tokens[$index]; + if ($current->isComment() || $current->isWhitespace() || $tokens->isEmptyAt($index)) { + ++$index; + + continue; + } + + // check if this is the last token + if ($index === $end) { + return $current->isGivenKind($expectString ? T_STRING : T_VARIABLE); + } + + if ($current->isGivenKind([T_LIST, CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE])) { + return false; + } + + $nextIndex = $tokens->getNextMeaningfulToken($index); + $next = $tokens[$nextIndex]; + + // self:: or ClassName:: + if ($current->isGivenKind(T_STRING) && $next->isGivenKind(T_DOUBLE_COLON)) { + $index = $tokens->getNextMeaningfulToken($nextIndex); + + continue; + } + + // \ClassName + if ($current->isGivenKind(T_NS_SEPARATOR) && $next->isGivenKind(T_STRING)) { + $index = $nextIndex; + + continue; + } + + // ClassName\ + if ($current->isGivenKind(T_STRING) && $next->isGivenKind(T_NS_SEPARATOR)) { + $index = $nextIndex; + + continue; + } + + // $a-> or a-> (as in $b->a->c) + if ($current->isGivenKind([T_STRING, T_VARIABLE]) && $next->isObjectOperator()) { + $index = $tokens->getNextMeaningfulToken($nextIndex); + $expectString = true; + + continue; + } + + // $a[...], a[...] (as in $c->a[$b]), $a{...} or a{...} (as in $c->a{$b}) + if ( + $current->isGivenKind($expectString ? T_STRING : T_VARIABLE) + && $next->equalsAny(['[', [CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN, '{']]) + ) { + $index = $tokens->findBlockEnd( + $next->equals('[') ? Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE : Tokens::BLOCK_TYPE_ARRAY_INDEX_CURLY_BRACE, + $nextIndex + ); + + if ($index === $end) { + return true; + } + + $index = $tokens->getNextMeaningfulToken($index); + + if (!$tokens[$index]->equalsAny(['[', [CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN, '{']]) && !$tokens[$index]->isObjectOperator()) { + return false; + } + + $index = $tokens->getNextMeaningfulToken($index); + $expectString = true; + + continue; + } + + // $a(...) or $a->b(...) + if ($strict && $current->isGivenKind([T_STRING, T_VARIABLE]) && $next->equals('(')) { + return false; + } + + // {...} (as in $a->{$b}) + if ($expectString && $current->isGivenKind(CT::T_DYNAMIC_PROP_BRACE_OPEN)) { + $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_DYNAMIC_PROP_BRACE, $index); + if ($index === $end) { + return true; + } + + $index = $tokens->getNextMeaningfulToken($index); + + if (!$tokens[$index]->isObjectOperator()) { + return false; + } + + $index = $tokens->getNextMeaningfulToken($index); + $expectString = true; + + continue; + } + + break; + } + + return !$this->isConstant($tokens, $start, $end); + } + + private function isConstant(Tokens $tokens, int $index, int $end): bool + { + $expectArrayOnly = false; + $expectNumberOnly = false; + $expectNothing = false; + + for (; $index <= $end; ++$index) { + $token = $tokens[$index]; + + if ($token->isComment() || $token->isWhitespace()) { + continue; + } + + if ($expectNothing) { + return false; + } + + if ($expectArrayOnly) { + if ($token->equalsAny(['(', ')', [CT::T_ARRAY_SQUARE_BRACE_CLOSE]])) { + continue; + } + + return false; + } + + if ($token->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN])) { + $expectArrayOnly = true; + + continue; + } + + if ($expectNumberOnly && !$token->isGivenKind([T_LNUMBER, T_DNUMBER])) { + return false; + } + + if ($token->equals('-')) { + $expectNumberOnly = true; + + continue; + } + + if ( + $token->isGivenKind([T_LNUMBER, T_DNUMBER, T_CONSTANT_ENCAPSED_STRING]) + || $token->equalsAny([[T_STRING, 'true'], [T_STRING, 'false'], [T_STRING, 'null']]) + ) { + $expectNothing = true; + + continue; + } + + return false; + } + + return true; + } + + private function resolveConfiguration(): void + { + $candidateTypes = []; + $this->candidatesMap = []; + + if (null !== $this->configuration['equal']) { + // `==`, `!=` and `<>` + $candidateTypes[T_IS_EQUAL] = $this->configuration['equal']; + $candidateTypes[T_IS_NOT_EQUAL] = $this->configuration['equal']; + } + + if (null !== $this->configuration['identical']) { + // `===` and `!==` + $candidateTypes[T_IS_IDENTICAL] = $this->configuration['identical']; + $candidateTypes[T_IS_NOT_IDENTICAL] = $this->configuration['identical']; + } + + if (null !== $this->configuration['less_and_greater']) { + // `<`, `<=`, `>` and `>=` + $candidateTypes[T_IS_SMALLER_OR_EQUAL] = $this->configuration['less_and_greater']; + $this->candidatesMap[T_IS_SMALLER_OR_EQUAL] = new Token([T_IS_GREATER_OR_EQUAL, '>=']); + + $candidateTypes[T_IS_GREATER_OR_EQUAL] = $this->configuration['less_and_greater']; + $this->candidatesMap[T_IS_GREATER_OR_EQUAL] = new Token([T_IS_SMALLER_OR_EQUAL, '<=']); + + $candidateTypes['<'] = $this->configuration['less_and_greater']; + $this->candidatesMap['<'] = new Token('>'); + + $candidateTypes['>'] = $this->configuration['less_and_greater']; + $this->candidatesMap['>'] = new Token('<'); + } + + $this->candidateTypesConfiguration = $candidateTypes; + $this->candidateTypes = array_keys($candidateTypes); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/DeprecatedFixerInterface.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/DeprecatedFixerInterface.php new file mode 100644 index 00000000..eded1614 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/DeprecatedFixerInterface.php @@ -0,0 +1,28 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer; + +/** + * @author Kuba Werłos + */ +interface DeprecatedFixerInterface extends FixerInterface +{ + /** + * Returns names of fixers to use instead, if any. + * + * @return list + */ + public function getSuccessorsNames(): array; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationArrayAssignmentFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationArrayAssignmentFixer.php new file mode 100644 index 00000000..d4801c45 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationArrayAssignmentFixer.php @@ -0,0 +1,99 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\DoctrineAnnotation; + +use PhpCsFixer\AbstractDoctrineAnnotationFixer; +use PhpCsFixer\Doctrine\Annotation\DocLexer; +use PhpCsFixer\Doctrine\Annotation\Tokens; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; + +/** + * Forces the configured operator for assignment in arrays in Doctrine Annotations. + */ +final class DoctrineAnnotationArrayAssignmentFixer extends AbstractDoctrineAnnotationFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Doctrine annotations must use configured operator for assignment in arrays.', + [ + new CodeSample( + " ':'] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before DoctrineAnnotationSpacesFixer. + */ + public function getPriority(): int + { + return 1; + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + $options = parent::createConfigurationDefinition()->getOptions(); + + $operator = new FixerOptionBuilder('operator', 'The operator to use.'); + $options[] = $operator + ->setAllowedValues(['=', ':']) + ->setDefault('=') + ->getOption() + ; + + return new FixerConfigurationResolver($options); + } + + protected function fixAnnotations(Tokens $doctrineAnnotationTokens): void + { + $scopes = []; + foreach ($doctrineAnnotationTokens as $token) { + if ($token->isType(DocLexer::T_OPEN_PARENTHESIS)) { + $scopes[] = 'annotation'; + + continue; + } + + if ($token->isType(DocLexer::T_OPEN_CURLY_BRACES)) { + $scopes[] = 'array'; + + continue; + } + + if ($token->isType([DocLexer::T_CLOSE_PARENTHESIS, DocLexer::T_CLOSE_CURLY_BRACES])) { + array_pop($scopes); + + continue; + } + + if ('array' === end($scopes) && $token->isType([DocLexer::T_EQUALS, DocLexer::T_COLON])) { + $token->setContent($this->configuration['operator']); + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationBracesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationBracesFixer.php new file mode 100644 index 00000000..ead0fb69 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationBracesFixer.php @@ -0,0 +1,116 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\DoctrineAnnotation; + +use PhpCsFixer\AbstractDoctrineAnnotationFixer; +use PhpCsFixer\Doctrine\Annotation\DocLexer; +use PhpCsFixer\Doctrine\Annotation\Token; +use PhpCsFixer\Doctrine\Annotation\Tokens; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; + +/** + * Adds braces to Doctrine annotations when missing. + */ +final class DoctrineAnnotationBracesFixer extends AbstractDoctrineAnnotationFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Doctrine annotations without arguments must use the configured syntax.', + [ + new CodeSample( + " 'with_braces'] + ), + ] + ); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + ...parent::createConfigurationDefinition()->getOptions(), + (new FixerOptionBuilder('syntax', 'Whether to add or remove braces.')) + ->setAllowedValues(['with_braces', 'without_braces']) + ->setDefault('without_braces') + ->getOption(), + ]); + } + + protected function fixAnnotations(Tokens $doctrineAnnotationTokens): void + { + if ('without_braces' === $this->configuration['syntax']) { + $this->removesBracesFromAnnotations($doctrineAnnotationTokens); + } else { + $this->addBracesToAnnotations($doctrineAnnotationTokens); + } + } + + private function addBracesToAnnotations(Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if (!$tokens[$index]->isType(DocLexer::T_AT)) { + continue; + } + + $braceIndex = $tokens->getNextMeaningfulToken($index + 1); + if (null !== $braceIndex && $tokens[$braceIndex]->isType(DocLexer::T_OPEN_PARENTHESIS)) { + continue; + } + + $tokens->insertAt($index + 2, new Token(DocLexer::T_OPEN_PARENTHESIS, '(')); + $tokens->insertAt($index + 3, new Token(DocLexer::T_CLOSE_PARENTHESIS, ')')); + } + } + + private function removesBracesFromAnnotations(Tokens $tokens): void + { + for ($index = 0, $max = \count($tokens); $index < $max; ++$index) { + if (!$tokens[$index]->isType(DocLexer::T_AT)) { + continue; + } + + $openBraceIndex = $tokens->getNextMeaningfulToken($index + 1); + if (null === $openBraceIndex) { + continue; + } + + if (!$tokens[$openBraceIndex]->isType(DocLexer::T_OPEN_PARENTHESIS)) { + continue; + } + + $closeBraceIndex = $tokens->getNextMeaningfulToken($openBraceIndex); + if (null === $closeBraceIndex) { + continue; + } + + if (!$tokens[$closeBraceIndex]->isType(DocLexer::T_CLOSE_PARENTHESIS)) { + continue; + } + + for ($currentIndex = $index + 2; $currentIndex <= $closeBraceIndex; ++$currentIndex) { + $tokens[$currentIndex]->clear(); + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationIndentationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationIndentationFixer.php new file mode 100644 index 00000000..1359aef0 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationIndentationFixer.php @@ -0,0 +1,182 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\DoctrineAnnotation; + +use PhpCsFixer\AbstractDoctrineAnnotationFixer; +use PhpCsFixer\Doctrine\Annotation\DocLexer; +use PhpCsFixer\Doctrine\Annotation\Tokens; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; + +final class DoctrineAnnotationIndentationFixer extends AbstractDoctrineAnnotationFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Doctrine annotations must be indented with four spaces.', + [ + new CodeSample(" true] + ), + ] + ); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + ...parent::createConfigurationDefinition()->getOptions(), + (new FixerOptionBuilder('indent_mixed_lines', 'Whether to indent lines that have content before closing parenthesis.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + + protected function fixAnnotations(Tokens $doctrineAnnotationTokens): void + { + $annotationPositions = []; + for ($index = 0, $max = \count($doctrineAnnotationTokens); $index < $max; ++$index) { + if (!$doctrineAnnotationTokens[$index]->isType(DocLexer::T_AT)) { + continue; + } + + $annotationEndIndex = $doctrineAnnotationTokens->getAnnotationEnd($index); + if (null === $annotationEndIndex) { + return; + } + + $annotationPositions[] = [$index, $annotationEndIndex]; + $index = $annotationEndIndex; + } + + $indentLevel = 0; + foreach ($doctrineAnnotationTokens as $index => $token) { + if (!$token->isType(DocLexer::T_NONE) || !str_contains($token->getContent(), "\n")) { + continue; + } + + if (!$this->indentationCanBeFixed($doctrineAnnotationTokens, $index, $annotationPositions)) { + continue; + } + + $braces = $this->getLineBracesCount($doctrineAnnotationTokens, $index); + $delta = $braces[0] - $braces[1]; + $mixedBraces = 0 === $delta && $braces[0] > 0; + $extraIndentLevel = 0; + + if ($indentLevel > 0 && ($delta < 0 || $mixedBraces)) { + --$indentLevel; + + if (true === $this->configuration['indent_mixed_lines'] && $this->isClosingLineWithMeaningfulContent($doctrineAnnotationTokens, $index)) { + $extraIndentLevel = 1; + } + } + + $token->setContent(Preg::replace( + '/(\n( +\*)?) *$/', + '$1'.str_repeat(' ', 4 * ($indentLevel + $extraIndentLevel) + 1), + $token->getContent() + )); + + if ($delta > 0 || $mixedBraces) { + ++$indentLevel; + } + } + } + + /** + * @return int[] + */ + private function getLineBracesCount(Tokens $tokens, int $index): array + { + $opening = 0; + $closing = 0; + + while (isset($tokens[++$index])) { + $token = $tokens[$index]; + if ($token->isType(DocLexer::T_NONE) && str_contains($token->getContent(), "\n")) { + break; + } + + if ($token->isType([DocLexer::T_OPEN_PARENTHESIS, DocLexer::T_OPEN_CURLY_BRACES])) { + ++$opening; + + continue; + } + + if (!$token->isType([DocLexer::T_CLOSE_PARENTHESIS, DocLexer::T_CLOSE_CURLY_BRACES])) { + continue; + } + + if ($opening > 0) { + --$opening; + } else { + ++$closing; + } + } + + return [$opening, $closing]; + } + + private function isClosingLineWithMeaningfulContent(Tokens $tokens, int $index): bool + { + while (isset($tokens[++$index])) { + $token = $tokens[$index]; + if ($token->isType(DocLexer::T_NONE)) { + if (str_contains($token->getContent(), "\n")) { + return false; + } + + continue; + } + + return !$token->isType([DocLexer::T_CLOSE_PARENTHESIS, DocLexer::T_CLOSE_CURLY_BRACES]); + } + + return false; + } + + /** + * @param array> $annotationPositions Pairs of begin and end indices of main annotations + */ + private function indentationCanBeFixed(Tokens $tokens, int $newLineTokenIndex, array $annotationPositions): bool + { + foreach ($annotationPositions as $position) { + if ($newLineTokenIndex >= $position[0] && $newLineTokenIndex <= $position[1]) { + return true; + } + } + + for ($index = $newLineTokenIndex + 1, $max = \count($tokens); $index < $max; ++$index) { + $token = $tokens[$index]; + + if (str_contains($token->getContent(), "\n")) { + return false; + } + + return $tokens[$index]->isType(DocLexer::T_AT); + } + + return false; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationSpacesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationSpacesFixer.php new file mode 100644 index 00000000..0ee08482 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationSpacesFixer.php @@ -0,0 +1,290 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\DoctrineAnnotation; + +use PhpCsFixer\AbstractDoctrineAnnotationFixer; +use PhpCsFixer\Doctrine\Annotation\DocLexer; +use PhpCsFixer\Doctrine\Annotation\Token; +use PhpCsFixer\Doctrine\Annotation\Tokens; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; + +/** + * Fixes spaces around commas and assignment operators in Doctrine annotations. + */ +final class DoctrineAnnotationSpacesFixer extends AbstractDoctrineAnnotationFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Fixes spaces in Doctrine annotations.', + [ + new CodeSample( + " false, 'before_array_assignments_equals' => false] + ), + ], + 'There must not be any space around parentheses; commas must be preceded by no space and followed by one space; there must be no space around named arguments assignment operator; there must be one space around array assignment operator.' + ); + } + + /** + * {@inheritdoc} + * + * Must run after DoctrineAnnotationArrayAssignmentFixer. + */ + public function getPriority(): int + { + return 0; + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + ...parent::createConfigurationDefinition()->getOptions(), + (new FixerOptionBuilder('around_parentheses', 'Whether to fix spaces around parentheses.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), + (new FixerOptionBuilder('around_commas', 'Whether to fix spaces around commas.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), + (new FixerOptionBuilder('before_argument_assignments', 'Whether to add, remove or ignore spaces before argument assignment operator.')) + ->setAllowedTypes(['null', 'bool']) + ->setDefault(false) + ->getOption(), + (new FixerOptionBuilder('after_argument_assignments', 'Whether to add, remove or ignore spaces after argument assignment operator.')) + ->setAllowedTypes(['null', 'bool']) + ->setDefault(false) + ->getOption(), + (new FixerOptionBuilder('before_array_assignments_equals', 'Whether to add, remove or ignore spaces before array `=` assignment operator.')) + ->setAllowedTypes(['null', 'bool']) + ->setDefault(true) + ->getOption(), + (new FixerOptionBuilder('after_array_assignments_equals', 'Whether to add, remove or ignore spaces after array assignment `=` operator.')) + ->setAllowedTypes(['null', 'bool']) + ->setDefault(true) + ->getOption(), + (new FixerOptionBuilder('before_array_assignments_colon', 'Whether to add, remove or ignore spaces before array `:` assignment operator.')) + ->setAllowedTypes(['null', 'bool']) + ->setDefault(true) + ->getOption(), + (new FixerOptionBuilder('after_array_assignments_colon', 'Whether to add, remove or ignore spaces after array assignment `:` operator.')) + ->setAllowedTypes(['null', 'bool']) + ->setDefault(true) + ->getOption(), + ]); + } + + protected function fixAnnotations(Tokens $doctrineAnnotationTokens): void + { + if (true === $this->configuration['around_parentheses']) { + $this->fixSpacesAroundParentheses($doctrineAnnotationTokens); + } + + if (true === $this->configuration['around_commas']) { + $this->fixSpacesAroundCommas($doctrineAnnotationTokens); + } + + if ( + null !== $this->configuration['before_argument_assignments'] + || null !== $this->configuration['after_argument_assignments'] + || null !== $this->configuration['before_array_assignments_equals'] + || null !== $this->configuration['after_array_assignments_equals'] + || null !== $this->configuration['before_array_assignments_colon'] + || null !== $this->configuration['after_array_assignments_colon'] + ) { + $this->fixAroundAssignments($doctrineAnnotationTokens); + } + } + + private function fixSpacesAroundParentheses(Tokens $tokens): void + { + $inAnnotationUntilIndex = null; + + foreach ($tokens as $index => $token) { + if (null !== $inAnnotationUntilIndex) { + if ($index === $inAnnotationUntilIndex) { + $inAnnotationUntilIndex = null; + + continue; + } + } elseif ($tokens[$index]->isType(DocLexer::T_AT)) { + $endIndex = $tokens->getAnnotationEnd($index); + if (null !== $endIndex) { + $inAnnotationUntilIndex = $endIndex + 1; + } + + continue; + } + + if (null === $inAnnotationUntilIndex) { + continue; + } + + if (!$token->isType([DocLexer::T_OPEN_PARENTHESIS, DocLexer::T_CLOSE_PARENTHESIS])) { + continue; + } + + if ($token->isType(DocLexer::T_OPEN_PARENTHESIS)) { + $token = $tokens[$index - 1]; + if ($token->isType(DocLexer::T_NONE)) { + $token->clear(); + } + + $token = $tokens[$index + 1]; + } else { + $token = $tokens[$index - 1]; + } + + if ($token->isType(DocLexer::T_NONE)) { + if (str_contains($token->getContent(), "\n")) { + continue; + } + + $token->clear(); + } + } + } + + private function fixSpacesAroundCommas(Tokens $tokens): void + { + $inAnnotationUntilIndex = null; + + foreach ($tokens as $index => $token) { + if (null !== $inAnnotationUntilIndex) { + if ($index === $inAnnotationUntilIndex) { + $inAnnotationUntilIndex = null; + + continue; + } + } elseif ($tokens[$index]->isType(DocLexer::T_AT)) { + $endIndex = $tokens->getAnnotationEnd($index); + if (null !== $endIndex) { + $inAnnotationUntilIndex = $endIndex; + } + + continue; + } + + if (null === $inAnnotationUntilIndex) { + continue; + } + + if (!$token->isType(DocLexer::T_COMMA)) { + continue; + } + + $token = $tokens[$index - 1]; + if ($token->isType(DocLexer::T_NONE)) { + $token->clear(); + } + + if ($index < \count($tokens) - 1 && !Preg::match('/^\s/', $tokens[$index + 1]->getContent())) { + $tokens->insertAt($index + 1, new Token(DocLexer::T_NONE, ' ')); + } + } + } + + private function fixAroundAssignments(Tokens $tokens): void + { + $beforeArguments = $this->configuration['before_argument_assignments']; + $afterArguments = $this->configuration['after_argument_assignments']; + $beforeArraysEquals = $this->configuration['before_array_assignments_equals']; + $afterArraysEquals = $this->configuration['after_array_assignments_equals']; + $beforeArraysColon = $this->configuration['before_array_assignments_colon']; + $afterArraysColon = $this->configuration['after_array_assignments_colon']; + + $scopes = []; + foreach ($tokens as $index => $token) { + $endScopeType = end($scopes); + if (false !== $endScopeType && $token->isType($endScopeType)) { + array_pop($scopes); + + continue; + } + + if ($tokens[$index]->isType(DocLexer::T_AT)) { + $scopes[] = DocLexer::T_CLOSE_PARENTHESIS; + + continue; + } + + if ($tokens[$index]->isType(DocLexer::T_OPEN_CURLY_BRACES)) { + $scopes[] = DocLexer::T_CLOSE_CURLY_BRACES; + + continue; + } + + if (DocLexer::T_CLOSE_PARENTHESIS === $endScopeType && $token->isType(DocLexer::T_EQUALS)) { + $this->updateSpacesAfter($tokens, $index, $afterArguments); + $this->updateSpacesBefore($tokens, $index, $beforeArguments); + + continue; + } + + if (DocLexer::T_CLOSE_CURLY_BRACES === $endScopeType) { + if ($token->isType(DocLexer::T_EQUALS)) { + $this->updateSpacesAfter($tokens, $index, $afterArraysEquals); + $this->updateSpacesBefore($tokens, $index, $beforeArraysEquals); + + continue; + } + + if ($token->isType(DocLexer::T_COLON)) { + $this->updateSpacesAfter($tokens, $index, $afterArraysColon); + $this->updateSpacesBefore($tokens, $index, $beforeArraysColon); + } + } + } + } + + private function updateSpacesAfter(Tokens $tokens, int $index, ?bool $insert): void + { + $this->updateSpacesAt($tokens, $index + 1, $index + 1, $insert); + } + + private function updateSpacesBefore(Tokens $tokens, int $index, ?bool $insert): void + { + $this->updateSpacesAt($tokens, $index - 1, $index, $insert); + } + + private function updateSpacesAt(Tokens $tokens, int $index, int $insertIndex, ?bool $insert): void + { + if (null === $insert) { + return; + } + + $token = $tokens[$index]; + if ($insert) { + if (!$token->isType(DocLexer::T_NONE)) { + $tokens->insertAt($insertIndex, $token = new Token()); + } + + $token->setContent(' '); + } elseif ($token->isType(DocLexer::T_NONE)) { + $token->clear(); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ExperimentalFixerInterface.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ExperimentalFixerInterface.php new file mode 100644 index 00000000..9ade1875 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ExperimentalFixerInterface.php @@ -0,0 +1,20 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer; + +/** + * @internal + */ +interface ExperimentalFixerInterface extends FixerInterface {} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FixerInterface.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FixerInterface.php new file mode 100644 index 00000000..6e79741b --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FixerInterface.php @@ -0,0 +1,79 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer; + +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + * @author Fabien Potencier + */ +interface FixerInterface +{ + /** + * Check if the fixer is a candidate for given Tokens collection. + * + * Fixer is a candidate when the collection contains tokens that may be fixed + * during fixer work. This could be considered as some kind of bloom filter. + * When this method returns true then to the Tokens collection may or may not + * need a fixing, but when this method returns false then the Tokens collection + * need no fixing for sure. + */ + public function isCandidate(Tokens $tokens): bool; + + /** + * Check if fixer is risky or not. + * + * Risky fixer could change code behavior! + */ + public function isRisky(): bool; + + /** + * Fixes a file. + * + * @param \SplFileInfo $file A \SplFileInfo instance + * @param Tokens $tokens Tokens collection + */ + public function fix(\SplFileInfo $file, Tokens $tokens): void; + + /** + * Returns the definition of the fixer. + */ + public function getDefinition(): FixerDefinitionInterface; + + /** + * Returns the name of the fixer. + * + * The name must be all lowercase and without any spaces. + * + * @return string The name of the fixer + */ + public function getName(): string; + + /** + * Returns the priority of the fixer. + * + * The default priority is 0 and higher priorities are executed first. + */ + public function getPriority(): int; + + /** + * Returns true if the file is supported by this fixer. + * + * @return bool true if the file is supported by this fixer, false otherwise + */ + public function supports(\SplFileInfo $file): bool; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/CombineNestedDirnameFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/CombineNestedDirnameFixer.php new file mode 100644 index 00000000..7a180998 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/CombineNestedDirnameFixer.php @@ -0,0 +1,224 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\FunctionNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Gregor Harlan + */ +final class CombineNestedDirnameFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Replace multiple nested calls of `dirname` by only one call with second `$level` parameter. Requires PHP >= 7.0.', + [ + new CodeSample( + "isTokenKindFound(T_STRING); + } + + public function isRisky(): bool + { + return true; + } + + /** + * {@inheritdoc} + * + * Must run before MethodArgumentSpaceFixer, NoSpacesInsideParenthesisFixer, SpacesInsideParenthesesFixer. + * Must run after DirConstantFixer. + */ + public function getPriority(): int + { + return 35; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; 0 <= $index; --$index) { + $dirnameInfo = $this->getDirnameInfo($tokens, $index); + + if (!$dirnameInfo) { + continue; + } + + $prev = $tokens->getPrevMeaningfulToken($dirnameInfo['indices'][0]); + + if (!$tokens[$prev]->equals('(')) { + continue; + } + + $prev = $tokens->getPrevMeaningfulToken($prev); + $firstArgumentEnd = $dirnameInfo['end']; + $dirnameInfoArray = [$dirnameInfo]; + + while ($dirnameInfo = $this->getDirnameInfo($tokens, $prev, $firstArgumentEnd)) { + $dirnameInfoArray[] = $dirnameInfo; + $prev = $tokens->getPrevMeaningfulToken($dirnameInfo['indices'][0]); + + if (!$tokens[$prev]->equals('(')) { + break; + } + + $prev = $tokens->getPrevMeaningfulToken($prev); + $firstArgumentEnd = $dirnameInfo['end']; + } + + if (\count($dirnameInfoArray) > 1) { + $this->combineDirnames($tokens, $dirnameInfoArray); + } + + $index = $prev; + } + } + + /** + * @param int $index Index of `dirname` + * @param null|int $firstArgumentEndIndex Index of last token of first argument of `dirname` call + * + * @return array{indices: list, secondArgument?: int, levels: int, end: int}|bool `false` when it is not a (supported) `dirname` call, an array with info about the dirname call otherwise + */ + private function getDirnameInfo(Tokens $tokens, int $index, ?int $firstArgumentEndIndex = null) + { + if (!$tokens[$index]->equals([T_STRING, 'dirname'], false)) { + return false; + } + + if (!(new FunctionsAnalyzer())->isGlobalFunctionCall($tokens, $index)) { + return false; + } + + $info = ['indices' => []]; + $prev = $tokens->getPrevMeaningfulToken($index); + + if ($tokens[$prev]->isGivenKind(T_NS_SEPARATOR)) { + $info['indices'][] = $prev; + } + + $info['indices'][] = $index; + + // opening parenthesis "(" + $next = $tokens->getNextMeaningfulToken($index); + $info['indices'][] = $next; + + if (null !== $firstArgumentEndIndex) { + $next = $tokens->getNextMeaningfulToken($firstArgumentEndIndex); + } else { + $next = $tokens->getNextMeaningfulToken($next); + + if ($tokens[$next]->equals(')')) { + return false; + } + + while (!$tokens[$next]->equalsAny([',', ')'])) { + $blockType = Tokens::detectBlockType($tokens[$next]); + + if (null !== $blockType) { + $next = $tokens->findBlockEnd($blockType['type'], $next); + } + + $next = $tokens->getNextMeaningfulToken($next); + } + } + + $info['indices'][] = $next; + + if ($tokens[$next]->equals(',')) { + $next = $tokens->getNextMeaningfulToken($next); + $info['indices'][] = $next; + } + + if ($tokens[$next]->equals(')')) { + $info['levels'] = 1; + $info['end'] = $next; + + return $info; + } + + if (!$tokens[$next]->isGivenKind(T_LNUMBER)) { + return false; + } + + $info['secondArgument'] = $next; + $info['levels'] = (int) $tokens[$next]->getContent(); + + $next = $tokens->getNextMeaningfulToken($next); + + if ($tokens[$next]->equals(',')) { + $info['indices'][] = $next; + $next = $tokens->getNextMeaningfulToken($next); + } + + if (!$tokens[$next]->equals(')')) { + return false; + } + + $info['indices'][] = $next; + $info['end'] = $next; + + return $info; + } + + /** + * @param array, secondArgument?: int, levels: int, end: int}> $dirnameInfoArray + */ + private function combineDirnames(Tokens $tokens, array $dirnameInfoArray): void + { + $outerDirnameInfo = array_pop($dirnameInfoArray); + $levels = $outerDirnameInfo['levels']; + + foreach ($dirnameInfoArray as $dirnameInfo) { + $levels += $dirnameInfo['levels']; + + foreach ($dirnameInfo['indices'] as $index) { + $tokens->removeLeadingWhitespace($index); + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } + } + + $levelsToken = new Token([T_LNUMBER, (string) $levels]); + + if (isset($outerDirnameInfo['secondArgument'])) { + $tokens[$outerDirnameInfo['secondArgument']] = $levelsToken; + } else { + $prev = $tokens->getPrevMeaningfulToken($outerDirnameInfo['end']); + $items = []; + + if (!$tokens[$prev]->equals(',')) { + $items = [new Token(','), new Token([T_WHITESPACE, ' '])]; + } + + $items[] = $levelsToken; + $tokens->insertAt($outerDirnameInfo['end'], $items); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/DateTimeCreateFromFormatCallFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/DateTimeCreateFromFormatCallFixer.php new file mode 100644 index 00000000..55ff455c --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/DateTimeCreateFromFormatCallFixer.php @@ -0,0 +1,163 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\FunctionNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class DateTimeCreateFromFormatCallFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'The first argument of `DateTime::createFromFormat` method must start with `!`.', + [ + new CodeSample("isTokenKindFound(T_DOUBLE_COLON); + } + + public function isRisky(): bool + { + return true; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $argumentsAnalyzer = new ArgumentsAnalyzer(); + $namespaceUsesAnalyzer = new NamespaceUsesAnalyzer(); + + foreach ($tokens->getNamespaceDeclarations() as $namespace) { + $scopeStartIndex = $namespace->getScopeStartIndex(); + $useDeclarations = $namespaceUsesAnalyzer->getDeclarationsInNamespace($tokens, $namespace); + + for ($index = $namespace->getScopeEndIndex(); $index > $scopeStartIndex; --$index) { + if (!$tokens[$index]->isGivenKind(T_DOUBLE_COLON)) { + continue; + } + + $functionNameIndex = $tokens->getNextMeaningfulToken($index); + + if (!$tokens[$functionNameIndex]->equals([T_STRING, 'createFromFormat'], false)) { + continue; + } + + if (!$tokens[$tokens->getNextMeaningfulToken($functionNameIndex)]->equals('(')) { + continue; + } + + $classNameIndex = $tokens->getPrevMeaningfulToken($index); + + if (!$tokens[$classNameIndex]->equalsAny([[T_STRING, \DateTime::class], [T_STRING, \DateTimeImmutable::class]], false)) { + continue; + } + + $preClassNameIndex = $tokens->getPrevMeaningfulToken($classNameIndex); + + if ($tokens[$preClassNameIndex]->isGivenKind(T_NS_SEPARATOR)) { + if ($tokens[$tokens->getPrevMeaningfulToken($preClassNameIndex)]->isGivenKind(T_STRING)) { + continue; + } + } elseif (!$namespace->isGlobalNamespace()) { + continue; + } else { + foreach ($useDeclarations as $useDeclaration) { + foreach (['datetime', 'datetimeimmutable'] as $name) { + if ($name === strtolower($useDeclaration->getShortName()) && $name !== strtolower($useDeclaration->getFullName())) { + continue 3; + } + } + } + } + + $openIndex = $tokens->getNextTokenOfKind($functionNameIndex, ['(']); + $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex); + + $argumentIndex = $this->getFirstArgumentTokenIndex($tokens, $argumentsAnalyzer->getArguments($tokens, $openIndex, $closeIndex)); + + if (null === $argumentIndex) { + continue; + } + + $format = $tokens[$argumentIndex]->getContent(); + + if (\strlen($format) < 3) { + continue; + } + + $offset = 'b' === $format[0] || 'B' === $format[0] ? 2 : 1; + + if ('!' === $format[$offset]) { + continue; + } + + $tokens->clearAt($argumentIndex); + $tokens->insertAt($argumentIndex, new Token([T_CONSTANT_ENCAPSED_STRING, substr_replace($format, '!', $offset, 0)])); + } + } + } + + /** + * @param array $arguments + */ + private function getFirstArgumentTokenIndex(Tokens $tokens, array $arguments): ?int + { + if (2 !== \count($arguments)) { + return null; + } + + $argumentStartIndex = array_key_first($arguments); + $argumentEndIndex = $arguments[$argumentStartIndex]; + $argumentStartIndex = $tokens->getNextMeaningfulToken($argumentStartIndex - 1); + + if ( + $argumentStartIndex !== $argumentEndIndex + && $tokens->getNextMeaningfulToken($argumentStartIndex) <= $argumentEndIndex + ) { + return null; // argument is not a simple single string + } + + return !$tokens[$argumentStartIndex]->isGivenKind(T_CONSTANT_ENCAPSED_STRING) + ? null // first argument is not a string + : $argumentStartIndex; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FopenFlagOrderFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FopenFlagOrderFixer.php new file mode 100644 index 00000000..3bf72e22 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FopenFlagOrderFixer.php @@ -0,0 +1,123 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\FunctionNotation; + +use PhpCsFixer\AbstractFopenFlagFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class FopenFlagOrderFixer extends AbstractFopenFlagFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Order the flags in `fopen` calls, `b` and `t` must be last.', + [new CodeSample("isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) { + continue; + } + + if (null !== $argumentFlagIndex) { + return; // multiple meaningful tokens found, no candidate for fixing + } + + $argumentFlagIndex = $i; + } + + // check if second argument is candidate + if (null === $argumentFlagIndex || !$tokens[$argumentFlagIndex]->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) { + return; + } + + $content = $tokens[$argumentFlagIndex]->getContent(); + $contentQuote = $content[0]; // `'`, `"`, `b` or `B` + + if ('b' === $contentQuote || 'B' === $contentQuote) { + $binPrefix = $contentQuote; + $contentQuote = $content[1]; // `'` or `"` + $mode = substr($content, 2, -1); + } else { + $binPrefix = ''; + $mode = substr($content, 1, -1); + } + + $modeLength = \strlen($mode); + if ($modeLength < 2) { + return; // nothing to sort + } + + if (false === $this->isValidModeString($mode)) { + return; + } + + $split = $this->sortFlags(Preg::split('#([^\+]\+?)#', $mode, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE)); + $newContent = $binPrefix.$contentQuote.implode('', $split).$contentQuote; + + if ($content !== $newContent) { + $tokens[$argumentFlagIndex] = new Token([T_CONSTANT_ENCAPSED_STRING, $newContent]); + } + } + + /** + * @param list $flags + * + * @return list + */ + private function sortFlags(array $flags): array + { + usort( + $flags, + static function (string $flag1, string $flag2): int { + if ($flag1 === $flag2) { + return 0; + } + + if ('b' === $flag1) { + return 1; + } + + if ('b' === $flag2) { + return -1; + } + + if ('t' === $flag1) { + return 1; + } + + if ('t' === $flag2) { + return -1; + } + + return $flag1 < $flag2 ? -1 : 1; + } + ); + + return $flags; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FopenFlagsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FopenFlagsFixer.php new file mode 100644 index 00000000..a136c7b3 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FopenFlagsFixer.php @@ -0,0 +1,106 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\FunctionNotation; + +use PhpCsFixer\AbstractFopenFlagFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class FopenFlagsFixer extends AbstractFopenFlagFixer implements ConfigurableFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'The flags in `fopen` calls must omit `t`, and `b` must be omitted or included consistently.', + [ + new CodeSample(" false]), + ], + null, + 'Risky when the function `fopen` is overridden.' + ); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('b_mode', 'The `b` flag must be used (`true`) or omitted (`false`).')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), + ]); + } + + protected function fixFopenFlagToken(Tokens $tokens, int $argumentStartIndex, int $argumentEndIndex): void + { + $argumentFlagIndex = null; + + for ($i = $argumentStartIndex; $i <= $argumentEndIndex; ++$i) { + if ($tokens[$i]->isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) { + continue; + } + + if (null !== $argumentFlagIndex) { + return; // multiple meaningful tokens found, no candidate for fixing + } + + $argumentFlagIndex = $i; + } + + // check if second argument is candidate + if (null === $argumentFlagIndex || !$tokens[$argumentFlagIndex]->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) { + return; + } + + $content = $tokens[$argumentFlagIndex]->getContent(); + $contentQuote = $content[0]; // `'`, `"`, `b` or `B` + + if ('b' === $contentQuote || 'B' === $contentQuote) { + $binPrefix = $contentQuote; + $contentQuote = $content[1]; // `'` or `"` + $mode = substr($content, 2, -1); + } else { + $binPrefix = ''; + $mode = substr($content, 1, -1); + } + + if (false === $this->isValidModeString($mode)) { + return; + } + + $mode = str_replace('t', '', $mode); + + if (true === $this->configuration['b_mode']) { + if (!str_contains($mode, 'b')) { + $mode .= 'b'; + } + } else { + $mode = str_replace('b', '', $mode); + } + + $newContent = $binPrefix.$contentQuote.$mode.$contentQuote; + + if ($content !== $newContent) { + $tokens[$argumentFlagIndex] = new Token([T_CONSTANT_ENCAPSED_STRING, $newContent]); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FunctionDeclarationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FunctionDeclarationFixer.php new file mode 100644 index 00000000..8e63c816 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FunctionDeclarationFixer.php @@ -0,0 +1,246 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\FunctionNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * Fixer for rules defined in PSR2 generally (¶1 and ¶6). + * + * @author Dariusz Rumiński + */ +final class FunctionDeclarationFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** + * @internal + */ + public const SPACING_NONE = 'none'; + + /** + * @internal + */ + public const SPACING_ONE = 'one'; + + private const SUPPORTED_SPACINGS = [self::SPACING_NONE, self::SPACING_ONE]; + + private string $singleLineWhitespaceOptions = " \t"; + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound([T_FUNCTION, T_FN]); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Spaces should be properly placed in a function declaration.', + [ + new CodeSample( + ' self::SPACING_NONE] + ), + new CodeSample( + ' null; +', + ['closure_fn_spacing' => self::SPACING_NONE] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before MethodArgumentSpaceFixer. + * Must run after SingleSpaceAfterConstructFixer, SingleSpaceAroundConstructFixer, UseArrowFunctionsFixer. + */ + public function getPriority(): int + { + return 31; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + $token = $tokens[$index]; + + if (!$token->isGivenKind([T_FUNCTION, T_FN])) { + continue; + } + + $startParenthesisIndex = $tokens->getNextTokenOfKind($index, ['(', ';', [T_CLOSE_TAG]]); + + if (!$tokens[$startParenthesisIndex]->equals('(')) { + continue; + } + + $endParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startParenthesisIndex); + + if (false === $this->configuration['trailing_comma_single_line'] + && !$tokens->isPartialCodeMultiline($index, $endParenthesisIndex) + ) { + $commaIndex = $tokens->getPrevMeaningfulToken($endParenthesisIndex); + + if ($tokens[$commaIndex]->equals(',')) { + $tokens->clearTokenAndMergeSurroundingWhitespace($commaIndex); + } + } + + $startBraceIndex = $tokens->getNextTokenOfKind($endParenthesisIndex, [';', '{', [T_DOUBLE_ARROW]]); + + // fix single-line whitespace before { or => + // eg: `function foo(){}` => `function foo() {}` + // eg: `function foo() {}` => `function foo() {}` + // eg: `fn() =>` => `fn() =>` + if ( + $tokens[$startBraceIndex]->equalsAny(['{', [T_DOUBLE_ARROW]]) + && ( + !$tokens[$startBraceIndex - 1]->isWhitespace() + || $tokens[$startBraceIndex - 1]->isWhitespace($this->singleLineWhitespaceOptions) + ) + ) { + $tokens->ensureWhitespaceAtIndex($startBraceIndex - 1, 1, ' '); + } + + $afterParenthesisIndex = $tokens->getNextNonWhitespace($endParenthesisIndex); + $afterParenthesisToken = $tokens[$afterParenthesisIndex]; + + if ($afterParenthesisToken->isGivenKind(CT::T_USE_LAMBDA)) { + // fix whitespace after CT:T_USE_LAMBDA (we might add a token, so do this before determining start and end parenthesis) + $tokens->ensureWhitespaceAtIndex($afterParenthesisIndex + 1, 0, ' '); + + $useStartParenthesisIndex = $tokens->getNextTokenOfKind($afterParenthesisIndex, ['(']); + $useEndParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $useStartParenthesisIndex); + + if (false === $this->configuration['trailing_comma_single_line'] + && !$tokens->isPartialCodeMultiline($index, $useEndParenthesisIndex) + ) { + $commaIndex = $tokens->getPrevMeaningfulToken($useEndParenthesisIndex); + + if ($tokens[$commaIndex]->equals(',')) { + $tokens->clearTokenAndMergeSurroundingWhitespace($commaIndex); + } + } + + // remove single-line edge whitespaces inside use parentheses + $this->fixParenthesisInnerEdge($tokens, $useStartParenthesisIndex, $useEndParenthesisIndex); + + // fix whitespace before CT::T_USE_LAMBDA + $tokens->ensureWhitespaceAtIndex($afterParenthesisIndex - 1, 1, ' '); + } + + // remove single-line edge whitespaces inside parameters list parentheses + $this->fixParenthesisInnerEdge($tokens, $startParenthesisIndex, $endParenthesisIndex); + $isLambda = $tokensAnalyzer->isLambda($index); + + // remove whitespace before ( + // eg: `function foo () {}` => `function foo() {}` + if (!$isLambda && $tokens[$startParenthesisIndex - 1]->isWhitespace() && !$tokens[$tokens->getPrevNonWhitespace($startParenthesisIndex - 1)]->isComment()) { + $tokens->clearAt($startParenthesisIndex - 1); + } + + $option = $token->isGivenKind(T_FN) ? 'closure_fn_spacing' : 'closure_function_spacing'; + + if ($isLambda && self::SPACING_NONE === $this->configuration[$option]) { + // optionally remove whitespace after T_FUNCTION of a closure + // eg: `function () {}` => `function() {}` + if ($tokens[$index + 1]->isWhitespace()) { + $tokens->clearAt($index + 1); + } + } else { + // otherwise, enforce whitespace after T_FUNCTION + // eg: `function foo() {}` => `function foo() {}` + $tokens->ensureWhitespaceAtIndex($index + 1, 0, ' '); + } + + if ($isLambda) { + $prev = $tokens->getPrevMeaningfulToken($index); + + if ($tokens[$prev]->isGivenKind(T_STATIC)) { + // fix whitespace after T_STATIC + // eg: `$a = static function(){};` => `$a = static function(){};` + $tokens->ensureWhitespaceAtIndex($prev + 1, 0, ' '); + } + } + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('closure_function_spacing', 'Spacing to use before open parenthesis for closures.')) + ->setDefault(self::SPACING_ONE) + ->setAllowedValues(self::SUPPORTED_SPACINGS) + ->getOption(), + (new FixerOptionBuilder('closure_fn_spacing', 'Spacing to use before open parenthesis for short arrow functions.')) + ->setDefault(self::SPACING_ONE) // @TODO change to SPACING_NONE on next major 4.0 + ->setAllowedValues(self::SUPPORTED_SPACINGS) + ->getOption(), + (new FixerOptionBuilder('trailing_comma_single_line', 'Whether trailing commas are allowed in single line signatures.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + + private function fixParenthesisInnerEdge(Tokens $tokens, int $start, int $end): void + { + do { + --$end; + } while ($tokens->isEmptyAt($end)); + + // remove single-line whitespace before `)` + if ($tokens[$end]->isWhitespace($this->singleLineWhitespaceOptions)) { + $tokens->clearAt($end); + } + + // remove single-line whitespace after `(` + if ($tokens[$start + 1]->isWhitespace($this->singleLineWhitespaceOptions)) { + $tokens->clearAt($start + 1); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FunctionTypehintSpaceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FunctionTypehintSpaceFixer.php new file mode 100644 index 00000000..132f7c13 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FunctionTypehintSpaceFixer.php @@ -0,0 +1,60 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\FunctionNotation; + +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; +use PhpCsFixer\Fixer\Whitespace\TypeDeclarationSpacesFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + * + * @deprecated + */ +final class FunctionTypehintSpaceFixer extends AbstractProxyFixer implements DeprecatedFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Ensure single space between function\'s argument and its typehint.', + [ + new CodeSample("isAnyTokenKindsFound([T_FUNCTION, T_FN]); + } + + public function getSuccessorsNames(): array + { + return array_keys($this->proxyFixers); + } + + protected function createProxyFixers(): array + { + $fixer = new TypeDeclarationSpacesFixer(); + $fixer->configure(['elements' => ['function']]); + + return [$fixer]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/ImplodeCallFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/ImplodeCallFixer.php new file mode 100644 index 00000000..4561c393 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/ImplodeCallFixer.php @@ -0,0 +1,139 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\FunctionNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Kuba Werłos + */ +final class ImplodeCallFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Function `implode` must be called with 2 arguments in the documented order.', + [ + new CodeSample("isTokenKindFound(T_STRING); + } + + /** + * {@inheritdoc} + * + * Must run before MethodArgumentSpaceFixer. + * Must run after NoAliasFunctionsFixer. + */ + public function getPriority(): int + { + return 37; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $functionsAnalyzer = new FunctionsAnalyzer(); + + for ($index = \count($tokens) - 1; $index > 0; --$index) { + if (!$tokens[$index]->equals([T_STRING, 'implode'], false)) { + continue; + } + + if (!$functionsAnalyzer->isGlobalFunctionCall($tokens, $index)) { + continue; + } + + $argumentsIndices = $this->getArgumentIndices($tokens, $index); + + if (1 === \count($argumentsIndices)) { + $firstArgumentIndex = array_key_first($argumentsIndices); + $tokens->insertAt($firstArgumentIndex, [ + new Token([T_CONSTANT_ENCAPSED_STRING, "''"]), + new Token(','), + new Token([T_WHITESPACE, ' ']), + ]); + + continue; + } + + if (2 === \count($argumentsIndices)) { + [$firstArgumentIndex, $secondArgumentIndex] = array_keys($argumentsIndices); + + // If the first argument is string we have nothing to do + if ($tokens[$firstArgumentIndex]->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) { + continue; + } + // If the second argument is not string we cannot make a swap + if (!$tokens[$secondArgumentIndex]->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) { + continue; + } + + // collect tokens from first argument + $firstArgumentEndIndex = $argumentsIndices[key($argumentsIndices)]; + $newSecondArgumentTokens = []; + for ($i = array_key_first($argumentsIndices); $i <= $firstArgumentEndIndex; ++$i) { + $newSecondArgumentTokens[] = clone $tokens[$i]; + $tokens->clearAt($i); + } + + $tokens->insertAt($firstArgumentIndex, clone $tokens[$secondArgumentIndex]); + + // insert above increased the second argument index + ++$secondArgumentIndex; + $tokens->clearAt($secondArgumentIndex); + $tokens->insertAt($secondArgumentIndex, $newSecondArgumentTokens); + } + } + } + + /** + * @return array In the format: startIndex => endIndex + */ + private function getArgumentIndices(Tokens $tokens, int $functionNameIndex): array + { + $argumentsAnalyzer = new ArgumentsAnalyzer(); + + $openParenthesis = $tokens->getNextTokenOfKind($functionNameIndex, ['(']); + $closeParenthesis = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesis); + + $indices = []; + + foreach ($argumentsAnalyzer->getArguments($tokens, $openParenthesis, $closeParenthesis) as $startIndexCandidate => $endIndex) { + $indices[$tokens->getNextMeaningfulToken($startIndexCandidate - 1)] = $tokens->getPrevMeaningfulToken($endIndex + 1); + } + + return $indices; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/LambdaNotUsedImportFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/LambdaNotUsedImportFixer.php new file mode 100644 index 00000000..e026637c --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/LambdaNotUsedImportFixer.php @@ -0,0 +1,359 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\FunctionNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +final class LambdaNotUsedImportFixer extends AbstractFixer +{ + /** + * @var ArgumentsAnalyzer + */ + private $argumentsAnalyzer; + + /** + * @var FunctionsAnalyzer + */ + private $functionAnalyzer; + + /** + * @var TokensAnalyzer + */ + private $tokensAnalyzer; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Lambda must not import variables it doesn\'t use.', + [new CodeSample("isAllTokenKindsFound([T_FUNCTION, CT::T_USE_LAMBDA]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $this->argumentsAnalyzer = new ArgumentsAnalyzer(); + $this->functionAnalyzer = new FunctionsAnalyzer(); + $this->tokensAnalyzer = new TokensAnalyzer($tokens); + + for ($index = $tokens->count() - 4; $index > 0; --$index) { + $lambdaUseIndex = $this->getLambdaUseIndex($tokens, $index); + + if (false !== $lambdaUseIndex) { + $this->fixLambda($tokens, $lambdaUseIndex); + } + } + } + + private function fixLambda(Tokens $tokens, int $lambdaUseIndex): void + { + $lambdaUseOpenBraceIndex = $tokens->getNextTokenOfKind($lambdaUseIndex, ['(']); + $lambdaUseCloseBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $lambdaUseOpenBraceIndex); + $arguments = $this->argumentsAnalyzer->getArguments($tokens, $lambdaUseOpenBraceIndex, $lambdaUseCloseBraceIndex); + + $imports = $this->filterArguments($tokens, $arguments); + + if (0 === \count($imports)) { + return; // no imports to remove + } + + $notUsedImports = $this->findNotUsedLambdaImports($tokens, $imports, $lambdaUseCloseBraceIndex); + $notUsedImportsCount = \count($notUsedImports); + + if (0 === $notUsedImportsCount) { + return; // no not used imports found + } + + if ($notUsedImportsCount === \count($arguments)) { + $this->clearImportsAndUse($tokens, $lambdaUseIndex, $lambdaUseCloseBraceIndex); // all imports are not used + + return; + } + + $this->clearImports($tokens, array_reverse($notUsedImports)); + } + + /** + * @param array $imports + * + * @return array + */ + private function findNotUsedLambdaImports(Tokens $tokens, array $imports, int $lambdaUseCloseBraceIndex): array + { + static $riskyKinds = [ + CT::T_DYNAMIC_VAR_BRACE_OPEN, + T_EVAL, + T_INCLUDE, + T_INCLUDE_ONCE, + T_REQUIRE, + T_REQUIRE_ONCE, + ]; + + // figure out where the lambda starts ... + $lambdaOpenIndex = $tokens->getNextTokenOfKind($lambdaUseCloseBraceIndex, ['{']); + $curlyBracesLevel = 0; + + for ($index = $lambdaOpenIndex;; ++$index) { // go through the body of the lambda and keep count of the (possible) usages of the imported variables + $token = $tokens[$index]; + + if ($token->equals('{')) { + ++$curlyBracesLevel; + + continue; + } + + if ($token->equals('}')) { + --$curlyBracesLevel; + + if (0 === $curlyBracesLevel) { + break; + } + + continue; + } + + if ($token->isGivenKind(T_STRING) && 'compact' === strtolower($token->getContent()) && $this->functionAnalyzer->isGlobalFunctionCall($tokens, $index)) { + return []; // wouldn't touch it with a ten-foot pole + } + + if ($token->isGivenKind($riskyKinds)) { + return []; + } + + if ($token->equals('$')) { + $nextIndex = $tokens->getNextMeaningfulToken($index); + + if ($tokens[$nextIndex]->isGivenKind(T_VARIABLE)) { + return []; // "$$a" case + } + } + + if ($token->isGivenKind(T_VARIABLE)) { + $content = $token->getContent(); + + if (isset($imports[$content])) { + unset($imports[$content]); + + if (0 === \count($imports)) { + return $imports; + } + } + } + + if ($token->isGivenKind(T_STRING_VARNAME)) { + $content = '$'.$token->getContent(); + + if (isset($imports[$content])) { + unset($imports[$content]); + + if (0 === \count($imports)) { + return $imports; + } + } + } + + if ($token->isClassy()) { // is anonymous class + // check if used as argument in the constructor of the anonymous class + $index = $tokens->getNextTokenOfKind($index, ['(', '{']); + + if ($tokens[$index]->equals('(')) { + $closeBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + $arguments = $this->argumentsAnalyzer->getArguments($tokens, $index, $closeBraceIndex); + + $imports = $this->countImportsUsedAsArgument($tokens, $imports, $arguments); + + $index = $tokens->getNextTokenOfKind($closeBraceIndex, ['{']); + } + + // skip body + $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); + + continue; + } + + if ($token->isGivenKind(T_FUNCTION)) { + // check if used as argument + $lambdaUseOpenBraceIndex = $tokens->getNextTokenOfKind($index, ['(']); + $lambdaUseCloseBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $lambdaUseOpenBraceIndex); + $arguments = $this->argumentsAnalyzer->getArguments($tokens, $lambdaUseOpenBraceIndex, $lambdaUseCloseBraceIndex); + + $imports = $this->countImportsUsedAsArgument($tokens, $imports, $arguments); + + // check if used as import + $index = $tokens->getNextTokenOfKind($index, [[CT::T_USE_LAMBDA], '{']); + + if ($tokens[$index]->isGivenKind(CT::T_USE_LAMBDA)) { + $lambdaUseOpenBraceIndex = $tokens->getNextTokenOfKind($index, ['(']); + $lambdaUseCloseBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $lambdaUseOpenBraceIndex); + $arguments = $this->argumentsAnalyzer->getArguments($tokens, $lambdaUseOpenBraceIndex, $lambdaUseCloseBraceIndex); + + $imports = $this->countImportsUsedAsArgument($tokens, $imports, $arguments); + + $index = $tokens->getNextTokenOfKind($lambdaUseCloseBraceIndex, ['{']); + } + + // skip body + $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); + + continue; + } + } + + return $imports; + } + + /** + * @param array $imports + * @param array $arguments + * + * @return array + */ + private function countImportsUsedAsArgument(Tokens $tokens, array $imports, array $arguments): array + { + foreach ($arguments as $start => $end) { + $info = $this->argumentsAnalyzer->getArgumentInfo($tokens, $start, $end); + $content = $info->getName(); + + if (isset($imports[$content])) { + unset($imports[$content]); + + if (0 === \count($imports)) { + return $imports; + } + } + } + + return $imports; + } + + /** + * @return false|int + */ + private function getLambdaUseIndex(Tokens $tokens, int $index) + { + if (!$tokens[$index]->isGivenKind(T_FUNCTION) || !$this->tokensAnalyzer->isLambda($index)) { + return false; + } + + $lambdaUseIndex = $tokens->getNextMeaningfulToken($index); // we are @ '(' or '&' after this + + if ($tokens[$lambdaUseIndex]->isGivenKind(CT::T_RETURN_REF)) { + $lambdaUseIndex = $tokens->getNextMeaningfulToken($lambdaUseIndex); + } + + $lambdaUseIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $lambdaUseIndex); // we are @ ')' after this + $lambdaUseIndex = $tokens->getNextMeaningfulToken($lambdaUseIndex); + + if (!$tokens[$lambdaUseIndex]->isGivenKind(CT::T_USE_LAMBDA)) { + return false; + } + + return $lambdaUseIndex; + } + + /** + * @param array $arguments + * + * @return array + */ + private function filterArguments(Tokens $tokens, array $arguments): array + { + $imports = []; + + foreach ($arguments as $start => $end) { + $info = $this->argumentsAnalyzer->getArgumentInfo($tokens, $start, $end); + $argument = $info->getNameIndex(); + + if ($tokens[$tokens->getPrevMeaningfulToken($argument)]->equals('&')) { + continue; + } + + $argumentCandidate = $tokens[$argument]; + + if ('$this' === $argumentCandidate->getContent()) { + continue; + } + + if ($this->tokensAnalyzer->isSuperGlobal($argument)) { + continue; + } + + $imports[$argumentCandidate->getContent()] = $argument; + } + + return $imports; + } + + /** + * @param array $imports + */ + private function clearImports(Tokens $tokens, array $imports): void + { + foreach ($imports as $removeIndex) { + $tokens->clearTokenAndMergeSurroundingWhitespace($removeIndex); + $previousRemoveIndex = $tokens->getPrevMeaningfulToken($removeIndex); + + if ($tokens[$previousRemoveIndex]->equals(',')) { + $tokens->clearTokenAndMergeSurroundingWhitespace($previousRemoveIndex); + } elseif ($tokens[$previousRemoveIndex]->equals('(')) { + $tokens->clearTokenAndMergeSurroundingWhitespace($tokens->getNextMeaningfulToken($removeIndex)); // next is always ',' here + } + } + } + + /** + * Remove `use` and all imported variables. + */ + private function clearImportsAndUse(Tokens $tokens, int $lambdaUseIndex, int $lambdaUseCloseBraceIndex): void + { + for ($i = $lambdaUseCloseBraceIndex; $i >= $lambdaUseIndex; --$i) { + if ($tokens[$i]->isComment()) { + continue; + } + + if ($tokens[$i]->isWhitespace()) { + $previousIndex = $tokens->getPrevNonWhitespace($i); + + if ($tokens[$previousIndex]->isComment()) { + continue; + } + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($i); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/MethodArgumentSpaceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/MethodArgumentSpaceFixer.php new file mode 100644 index 00000000..2b6e3f2c --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/MethodArgumentSpaceFixer.php @@ -0,0 +1,490 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\FunctionNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Kuanhung Chen + */ +final class MethodArgumentSpaceFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'In method arguments and method call, there MUST NOT be a space before each comma and there MUST be one space after each comma. Argument lists MAY be split across multiple lines, where each subsequent line is indented once. When doing so, the first item in the list MUST be on the next line, and there MUST be only one argument per line.', + [ + new CodeSample( + " false] + ), + new CodeSample( + " true] + ), + new CodeSample( + " 'ensure_fully_multiline'] + ), + new CodeSample( + " 'ensure_single_line'] + ), + new CodeSample( + " 'ensure_fully_multiline', + 'keep_multiple_spaces_after_comma' => true, + ] + ), + new CodeSample( + " 'ensure_fully_multiline', + 'keep_multiple_spaces_after_comma' => false, + ] + ), + new CodeSample( + " 'ensure_fully_multiline', + 'attribute_placement' => 'ignore', + ] + ), + new CodeSample( + " 'ensure_fully_multiline', + 'attribute_placement' => 'same_line', + ] + ), + new CodeSample( + " 'ensure_fully_multiline', + 'attribute_placement' => 'standalone', + ] + ), + new CodeSample( + <<<'SAMPLE' + true] + ), + ], + 'This fixer covers rules defined in PSR2 ¶4.4, ¶4.6.' + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound('('); + } + + /** + * {@inheritdoc} + * + * Must run before ArrayIndentationFixer, StatementIndentationFixer. + * Must run after CombineNestedDirnameFixer, FunctionDeclarationFixer, ImplodeCallFixer, LambdaNotUsedImportFixer, NoMultilineWhitespaceAroundDoubleArrowFixer, NoUselessSprintfFixer, PowToExponentiationFixer, StrictParamFixer. + */ + public function getPriority(): int + { + return 30; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $expectedTokens = [T_LIST, T_FUNCTION, CT::T_USE_LAMBDA, T_FN, T_CLASS]; + + for ($index = $tokens->count() - 1; $index > 0; --$index) { + $token = $tokens[$index]; + + if (!$token->equals('(')) { + continue; + } + + $meaningfulTokenBeforeParenthesis = $tokens[$tokens->getPrevMeaningfulToken($index)]; + + if ( + $meaningfulTokenBeforeParenthesis->isKeyword() + && !$meaningfulTokenBeforeParenthesis->isGivenKind($expectedTokens) + ) { + continue; + } + + $isMultiline = $this->fixFunction($tokens, $index); + + if ( + $isMultiline + && 'ensure_fully_multiline' === $this->configuration['on_multiline'] + && !$meaningfulTokenBeforeParenthesis->isGivenKind(T_LIST) + ) { + $this->ensureFunctionFullyMultiline($tokens, $index); + } + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('keep_multiple_spaces_after_comma', 'Whether keep multiple spaces after comma.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + (new FixerOptionBuilder( + 'on_multiline', + 'Defines how to handle function arguments lists that contain newlines.' + )) + ->setAllowedValues(['ignore', 'ensure_single_line', 'ensure_fully_multiline']) + ->setDefault('ensure_fully_multiline') + ->getOption(), + (new FixerOptionBuilder('after_heredoc', 'Whether the whitespace between heredoc end and comma should be removed.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + (new FixerOptionBuilder( + 'attribute_placement', + 'Defines how to handle argument attributes when function definition is multiline.' + )) + ->setAllowedValues(['ignore', 'same_line', 'standalone']) + ->setDefault('standalone') + ->getOption(), + ]); + } + + /** + * Fix arguments spacing for given function. + * + * @param Tokens $tokens Tokens to handle + * @param int $startFunctionIndex Start parenthesis position + * + * @return bool whether the function is multiline + */ + private function fixFunction(Tokens $tokens, int $startFunctionIndex): bool + { + $isMultiline = false; + + $endFunctionIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startFunctionIndex); + $firstWhitespaceIndex = $this->findWhitespaceIndexAfterParenthesis($tokens, $startFunctionIndex, $endFunctionIndex); + $lastWhitespaceIndex = $this->findWhitespaceIndexAfterParenthesis($tokens, $endFunctionIndex, $startFunctionIndex); + + foreach ([$firstWhitespaceIndex, $lastWhitespaceIndex] as $index) { + if (null === $index || !Preg::match('/\R/', $tokens[$index]->getContent())) { + continue; + } + + if ('ensure_single_line' !== $this->configuration['on_multiline']) { + $isMultiline = true; + + continue; + } + + $newLinesRemoved = $this->ensureSingleLine($tokens, $index); + + if (!$newLinesRemoved) { + $isMultiline = true; + } + } + + for ($index = $endFunctionIndex - 1; $index > $startFunctionIndex; --$index) { + $token = $tokens[$index]; + + if ($token->equals(')')) { + $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + + continue; + } + + if ($token->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_CLOSE)) { + $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $index); + + continue; + } + + if ($token->equals('}')) { + $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); + + continue; + } + + if ($token->equals(',')) { + $this->fixSpace($tokens, $index); + if (!$isMultiline && $this->isNewline($tokens[$index + 1])) { + $isMultiline = true; + } + } + } + + return $isMultiline; + } + + private function findWhitespaceIndexAfterParenthesis(Tokens $tokens, int $startParenthesisIndex, int $endParenthesisIndex): ?int + { + $direction = $endParenthesisIndex > $startParenthesisIndex ? 1 : -1; + $startIndex = $startParenthesisIndex + $direction; + $endIndex = $endParenthesisIndex - $direction; + + for ($index = $startIndex; $index !== $endIndex; $index += $direction) { + $token = $tokens[$index]; + + if ($token->isWhitespace()) { + return $index; + } + + if (!$token->isComment()) { + break; + } + } + + return null; + } + + /** + * @return bool Whether newlines were removed from the whitespace token + */ + private function ensureSingleLine(Tokens $tokens, int $index): bool + { + $previousToken = $tokens[$index - 1]; + + if ($previousToken->isComment() && !str_starts_with($previousToken->getContent(), '/*')) { + return false; + } + + $content = Preg::replace('/\R\h*/', '', $tokens[$index]->getContent()); + + $tokens->ensureWhitespaceAtIndex($index, 0, $content); + + return true; + } + + private function ensureFunctionFullyMultiline(Tokens $tokens, int $startFunctionIndex): void + { + // find out what the indentation is + $searchIndex = $startFunctionIndex; + do { + $prevWhitespaceTokenIndex = $tokens->getPrevTokenOfKind( + $searchIndex, + [[T_ENCAPSED_AND_WHITESPACE], [T_WHITESPACE]], + ); + + $searchIndex = $prevWhitespaceTokenIndex; + } while (null !== $prevWhitespaceTokenIndex + && !str_contains($tokens[$prevWhitespaceTokenIndex]->getContent(), "\n") + ); + + if (null === $prevWhitespaceTokenIndex) { + $existingIndentation = ''; + } elseif (!$tokens[$prevWhitespaceTokenIndex]->isGivenKind(T_WHITESPACE)) { + return; + } else { + $existingIndentation = $tokens[$prevWhitespaceTokenIndex]->getContent(); + $lastLineIndex = strrpos($existingIndentation, "\n"); + $existingIndentation = false === $lastLineIndex + ? $existingIndentation + : substr($existingIndentation, $lastLineIndex + 1); + } + + $indentation = $existingIndentation.$this->whitespacesConfig->getIndent(); + $endFunctionIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startFunctionIndex); + + $wasWhitespaceBeforeEndFunctionAddedAsNewToken = $tokens->ensureWhitespaceAtIndex( + $tokens[$endFunctionIndex - 1]->isWhitespace() ? $endFunctionIndex - 1 : $endFunctionIndex, + 0, + $this->whitespacesConfig->getLineEnding().$existingIndentation + ); + + if ($wasWhitespaceBeforeEndFunctionAddedAsNewToken) { + ++$endFunctionIndex; + } + + for ($index = $endFunctionIndex - 1; $index > $startFunctionIndex; --$index) { + $token = $tokens[$index]; + + // skip nested method calls and arrays + if ($token->equals(')')) { + $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + + continue; + } + + // skip nested arrays + if ($token->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_CLOSE)) { + $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $index); + + continue; + } + + if ($token->equals('}')) { + $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); + + continue; + } + + if ($tokens[$tokens->getNextMeaningfulToken($index)]->equals(')')) { + continue; + } + + if ($token->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) { + if ('standalone' === $this->configuration['attribute_placement']) { + $this->fixNewline($tokens, $index, $indentation); + } elseif ('same_line' === $this->configuration['attribute_placement']) { + $this->ensureSingleLine($tokens, $index + 1); + $tokens->ensureWhitespaceAtIndex($index + 1, 0, ' '); + } + $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_ATTRIBUTE, $index); + + continue; + } + + if ($token->equals(',')) { + $this->fixNewline($tokens, $index, $indentation); + } + } + + $this->fixNewline($tokens, $startFunctionIndex, $indentation, false); + } + + /** + * Method to insert newline after comma, attribute or opening parenthesis. + * + * @param int $index index of a comma + * @param string $indentation the indentation that should be used + * @param bool $override whether to override the existing character or not + */ + private function fixNewline(Tokens $tokens, int $index, string $indentation, bool $override = true): void + { + if ($tokens[$index + 1]->isComment()) { + return; + } + + if ($tokens[$index + 2]->isComment()) { + $nextMeaningfulTokenIndex = $tokens->getNextMeaningfulToken($index + 2); + if (!$this->isNewline($tokens[$nextMeaningfulTokenIndex - 1])) { + if ($tokens[$nextMeaningfulTokenIndex - 1]->isWhitespace()) { + $tokens->clearAt($nextMeaningfulTokenIndex - 1); + } + + $tokens->ensureWhitespaceAtIndex($nextMeaningfulTokenIndex, 0, $this->whitespacesConfig->getLineEnding().$indentation); + } + + return; + } + + $nextMeaningfulTokenIndex = $tokens->getNextMeaningfulToken($index); + + if ($tokens[$nextMeaningfulTokenIndex]->equals(')')) { + return; + } + + $tokens->ensureWhitespaceAtIndex($index + 1, 0, $this->whitespacesConfig->getLineEnding().$indentation); + } + + /** + * Method to insert space after comma and remove space before comma. + */ + private function fixSpace(Tokens $tokens, int $index): void + { + // remove space before comma if exist + if ($tokens[$index - 1]->isWhitespace()) { + $prevIndex = $tokens->getPrevNonWhitespace($index - 1); + + if ( + !$tokens[$prevIndex]->equals(',') && !$tokens[$prevIndex]->isComment() + && (true === $this->configuration['after_heredoc'] || !$tokens[$prevIndex]->isGivenKind(T_END_HEREDOC)) + ) { + $tokens->clearAt($index - 1); + } + } + + $nextIndex = $index + 1; + $nextToken = $tokens[$nextIndex]; + + // Two cases for fix space after comma (exclude multiline comments) + // 1) multiple spaces after comma + // 2) no space after comma + if ($nextToken->isWhitespace()) { + $newContent = $nextToken->getContent(); + + if ('ensure_single_line' === $this->configuration['on_multiline']) { + $newContent = Preg::replace('/\R/', '', $newContent); + } + + if ( + (false === $this->configuration['keep_multiple_spaces_after_comma'] || Preg::match('/\R/', $newContent)) + && !$this->isCommentLastLineToken($tokens, $index + 2) + ) { + $newContent = ltrim($newContent, " \t"); + } + + $tokens[$nextIndex] = new Token([T_WHITESPACE, '' === $newContent ? ' ' : $newContent]); + + return; + } + + if (!$this->isCommentLastLineToken($tokens, $index + 1)) { + $tokens->insertAt($index + 1, new Token([T_WHITESPACE, ' '])); + } + } + + /** + * Check if last item of current line is a comment. + * + * @param Tokens $tokens tokens to handle + * @param int $index index of token + */ + private function isCommentLastLineToken(Tokens $tokens, int $index): bool + { + if (!$tokens[$index]->isComment() || !$tokens[$index + 1]->isWhitespace()) { + return false; + } + + $content = $tokens[$index + 1]->getContent(); + + return $content !== ltrim($content, "\r\n"); + } + + /** + * Checks if token is new line. + */ + private function isNewline(Token $token): bool + { + return $token->isWhitespace() && str_contains($token->getContent(), "\n"); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NativeFunctionInvocationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NativeFunctionInvocationFixer.php new file mode 100644 index 00000000..06f9b660 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NativeFunctionInvocationFixer.php @@ -0,0 +1,401 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\FunctionNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Utils; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; + +/** + * @author Andreas Möller + */ +final class NativeFunctionInvocationFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** + * @internal + */ + public const SET_ALL = '@all'; + + /** + * Subset of SET_INTERNAL. + * + * Change function call to functions known to be optimized by the Zend engine. + * For details: + * - @see https://github.com/php/php-src/blob/php-7.2.6/Zend/zend_compile.c "zend_try_compile_special_func" + * - @see https://github.com/php/php-src/blob/php-7.2.6/ext/opcache/Optimizer/pass1_5.c + * + * @internal + */ + public const SET_COMPILER_OPTIMIZED = '@compiler_optimized'; + + /** + * @internal + */ + public const SET_INTERNAL = '@internal'; + + /** + * @var callable + */ + private $functionFilter; + + public function configure(array $configuration): void + { + parent::configure($configuration); + + $this->functionFilter = $this->getFunctionFilter(); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Add leading `\` before function invocation to speed up resolving.', + [ + new CodeSample( + ' [ + 'json_encode', + ], + ] + ), + new CodeSample( + ' 'all'] + ), + new CodeSample( + ' 'namespaced'] + ), + new CodeSample( + ' ['myGlobalFunction']] + ), + new CodeSample( + ' ['@all']] + ), + new CodeSample( + ' ['@internal']] + ), + new CodeSample( + ' ['@compiler_optimized']] + ), + ], + null, + 'Risky when any of the functions are overridden.' + ); + } + + /** + * {@inheritdoc} + * + * Must run before GlobalNamespaceImportFixer. + * Must run after BacktickToShellExecFixer, RegularCallableCallFixer, StrictParamFixer. + */ + public function getPriority(): int + { + return 1; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_STRING); + } + + public function isRisky(): bool + { + return true; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + if ('all' === $this->configuration['scope']) { + $this->fixFunctionCalls($tokens, $this->functionFilter, 0, \count($tokens) - 1, false); + + return; + } + + $namespaces = $tokens->getNamespaceDeclarations(); + + // 'scope' is 'namespaced' here + /** @var NamespaceAnalysis $namespace */ + foreach (array_reverse($namespaces) as $namespace) { + $this->fixFunctionCalls($tokens, $this->functionFilter, $namespace->getScopeStartIndex(), $namespace->getScopeEndIndex(), $namespace->isGlobalNamespace()); + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('exclude', 'List of functions to ignore.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([static function (array $value): bool { + foreach ($value as $functionName) { + if (!\is_string($functionName) || '' === trim($functionName) || trim($functionName) !== $functionName) { + throw new InvalidOptionsException(sprintf( + 'Each element must be a non-empty, trimmed string, got "%s" instead.', + get_debug_type($functionName) + )); + } + } + + return true; + }]) + ->setDefault([]) + ->getOption(), + (new FixerOptionBuilder('include', 'List of function names or sets to fix. Defined sets are `@internal` (all native functions), `@all` (all global functions) and `@compiler_optimized` (functions that are specially optimized by Zend).')) + ->setAllowedTypes(['array']) + ->setAllowedValues([static function (array $value): bool { + foreach ($value as $functionName) { + if (!\is_string($functionName) || '' === trim($functionName) || trim($functionName) !== $functionName) { + throw new InvalidOptionsException(sprintf( + 'Each element must be a non-empty, trimmed string, got "%s" instead.', + get_debug_type($functionName) + )); + } + + $sets = [ + self::SET_ALL, + self::SET_INTERNAL, + self::SET_COMPILER_OPTIMIZED, + ]; + + if (str_starts_with($functionName, '@') && !\in_array($functionName, $sets, true)) { + throw new InvalidOptionsException(sprintf('Unknown set "%s", known sets are %s.', $functionName, Utils::naturalLanguageJoin($sets))); + } + } + + return true; + }]) + ->setDefault([self::SET_COMPILER_OPTIMIZED]) + ->getOption(), + (new FixerOptionBuilder('scope', 'Only fix function calls that are made within a namespace or fix all.')) + ->setAllowedValues(['all', 'namespaced']) + ->setDefault('all') + ->getOption(), + (new FixerOptionBuilder('strict', 'Whether leading `\` of function call not meant to have it should be removed.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), + ]); + } + + private function fixFunctionCalls(Tokens $tokens, callable $functionFilter, int $start, int $end, bool $tryToRemove): void + { + $functionsAnalyzer = new FunctionsAnalyzer(); + + $tokensToInsert = []; + for ($index = $start; $index < $end; ++$index) { + if (!$functionsAnalyzer->isGlobalFunctionCall($tokens, $index)) { + continue; + } + + $prevIndex = $tokens->getPrevMeaningfulToken($index); + + if (!$functionFilter($tokens[$index]->getContent()) || $tryToRemove) { + if (false === $this->configuration['strict']) { + continue; + } + + if ($tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) { + $tokens->clearTokenAndMergeSurroundingWhitespace($prevIndex); + } + + continue; + } + + if ($tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) { + continue; // do not bother if previous token is already namespace separator + } + + $tokensToInsert[$index] = new Token([T_NS_SEPARATOR, '\\']); + } + + $tokens->insertSlices($tokensToInsert); + } + + private function getFunctionFilter(): callable + { + $exclude = $this->normalizeFunctionNames($this->configuration['exclude']); + + if (\in_array(self::SET_ALL, $this->configuration['include'], true)) { + if (\count($exclude) > 0) { + return static fn (string $functionName): bool => !isset($exclude[strtolower($functionName)]); + } + + return static fn (): bool => true; + } + + $include = []; + + if (\in_array(self::SET_INTERNAL, $this->configuration['include'], true)) { + $include = $this->getAllInternalFunctionsNormalized(); + } elseif (\in_array(self::SET_COMPILER_OPTIMIZED, $this->configuration['include'], true)) { + $include = $this->getAllCompilerOptimizedFunctionsNormalized(); // if `@internal` is set all compiler optimized function are already loaded + } + + foreach ($this->configuration['include'] as $additional) { + if (!str_starts_with($additional, '@')) { + $include[strtolower($additional)] = true; + } + } + + if (\count($exclude) > 0) { + return static fn (string $functionName): bool => isset($include[strtolower($functionName)]) && !isset($exclude[strtolower($functionName)]); + } + + return static fn (string $functionName): bool => isset($include[strtolower($functionName)]); + } + + /** + * @return array normalized function names of which the PHP compiler optimizes + */ + private function getAllCompilerOptimizedFunctionsNormalized(): array + { + return $this->normalizeFunctionNames([ + // @see https://github.com/php/php-src/blob/PHP-7.4/Zend/zend_compile.c "zend_try_compile_special_func" + 'array_key_exists', + 'array_slice', + 'assert', + 'boolval', + 'call_user_func', + 'call_user_func_array', + 'chr', + 'count', + 'defined', + 'doubleval', + 'floatval', + 'func_get_args', + 'func_num_args', + 'get_called_class', + 'get_class', + 'gettype', + 'in_array', + 'intval', + 'is_array', + 'is_bool', + 'is_double', + 'is_float', + 'is_int', + 'is_integer', + 'is_long', + 'is_null', + 'is_object', + 'is_real', + 'is_resource', + 'is_scalar', + 'is_string', + 'ord', + 'sizeof', + 'strlen', + 'strval', + // @see https://github.com/php/php-src/blob/php-7.2.6/ext/opcache/Optimizer/pass1_5.c + // @see https://github.com/php/php-src/blob/PHP-8.1.2/Zend/Optimizer/block_pass.c + // @see https://github.com/php/php-src/blob/php-8.1.3/Zend/Optimizer/zend_optimizer.c + 'constant', + 'define', + 'dirname', + 'extension_loaded', + 'function_exists', + 'is_callable', + 'ini_get', + ]); + } + + /** + * @return array normalized function names of all internal defined functions + */ + private function getAllInternalFunctionsNormalized(): array + { + return $this->normalizeFunctionNames(get_defined_functions()['internal']); + } + + /** + * @param string[] $functionNames + * + * @return array all function names lower cased + */ + private function normalizeFunctionNames(array $functionNames): array + { + foreach ($functionNames as $index => $functionName) { + $functionNames[strtolower($functionName)] = true; + unset($functionNames[$index]); + } + + return $functionNames; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoSpacesAfterFunctionNameFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoSpacesAfterFunctionNameFixer.php new file mode 100644 index 00000000..d852339e --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoSpacesAfterFunctionNameFixer.php @@ -0,0 +1,178 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\FunctionNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Fixer for rules defined in PSR2 ¶4.6. + * + * @author Varga Bence + * @author Dariusz Rumiński + */ +final class NoSpacesAfterFunctionNameFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'When making a method or function call, there MUST NOT be a space between the method or function name and the opening parenthesis.', + [new CodeSample("isAnyTokenKindsFound([T_STRING, ...$this->getFunctionyTokenKinds()]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $functionyTokens = $this->getFunctionyTokenKinds(); + $languageConstructionTokens = $this->getLanguageConstructionTokenKinds(); + $braceTypes = $this->getBraceAfterVariableKinds(); + + foreach ($tokens as $index => $token) { + // looking for start brace + if (!$token->equals('(')) { + continue; + } + + // last non-whitespace token, can never be `null` always at least PHP open tag before it + $lastTokenIndex = $tokens->getPrevNonWhitespace($index); + + // check for ternary operator + $endParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + $nextNonWhiteSpace = $tokens->getNextMeaningfulToken($endParenthesisIndex); + if ( + null !== $nextNonWhiteSpace + && !$tokens[$nextNonWhiteSpace]->equals(';') + && $tokens[$lastTokenIndex]->isGivenKind($languageConstructionTokens) + ) { + continue; + } + + // check if it is a function call + if ($tokens[$lastTokenIndex]->isGivenKind($functionyTokens)) { + $this->fixFunctionCall($tokens, $index); + } elseif ($tokens[$lastTokenIndex]->isGivenKind(T_STRING)) { // for real function calls or definitions + $possibleDefinitionIndex = $tokens->getPrevMeaningfulToken($lastTokenIndex); + if (!$tokens[$possibleDefinitionIndex]->isGivenKind(T_FUNCTION)) { + $this->fixFunctionCall($tokens, $index); + } + } elseif ($tokens[$lastTokenIndex]->equalsAny($braceTypes)) { + $block = Tokens::detectBlockType($tokens[$lastTokenIndex]); + if ( + Tokens::BLOCK_TYPE_ARRAY_INDEX_CURLY_BRACE === $block['type'] + || Tokens::BLOCK_TYPE_DYNAMIC_VAR_BRACE === $block['type'] + || Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE === $block['type'] + || Tokens::BLOCK_TYPE_PARENTHESIS_BRACE === $block['type'] + ) { + $this->fixFunctionCall($tokens, $index); + } + } + } + } + + /** + * Fixes whitespaces around braces of a function(y) call. + * + * @param Tokens $tokens tokens to handle + * @param int $index index of token + */ + private function fixFunctionCall(Tokens $tokens, int $index): void + { + // remove space before opening brace + if ($tokens[$index - 1]->isWhitespace()) { + $tokens->clearAt($index - 1); + } + } + + /** + * @return array|string> + */ + private function getBraceAfterVariableKinds(): array + { + static $tokens = [ + ')', + ']', + [CT::T_DYNAMIC_VAR_BRACE_CLOSE], + [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE], + ]; + + return $tokens; + } + + /** + * Gets the token kinds which can work as function calls. + * + * @return list Token names + */ + private function getFunctionyTokenKinds(): array + { + static $tokens = [ + T_ARRAY, + T_ECHO, + T_EMPTY, + T_EVAL, + T_EXIT, + T_INCLUDE, + T_INCLUDE_ONCE, + T_ISSET, + T_LIST, + T_PRINT, + T_REQUIRE, + T_REQUIRE_ONCE, + T_UNSET, + T_VARIABLE, + ]; + + return $tokens; + } + + /** + * Gets the token kinds of actually language construction. + * + * @return int[] + */ + private function getLanguageConstructionTokenKinds(): array + { + static $languageConstructionTokens = [ + T_ECHO, + T_PRINT, + T_INCLUDE, + T_INCLUDE_ONCE, + T_REQUIRE, + T_REQUIRE_ONCE, + ]; + + return $languageConstructionTokens; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoTrailingCommaInSinglelineFunctionCallFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoTrailingCommaInSinglelineFunctionCallFixer.php new file mode 100644 index 00000000..b0be871d --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoTrailingCommaInSinglelineFunctionCallFixer.php @@ -0,0 +1,59 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\FunctionNotation; + +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\Fixer\Basic\NoTrailingCommaInSinglelineFixer; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; + +/** + * @deprecated + */ +final class NoTrailingCommaInSinglelineFunctionCallFixer extends AbstractProxyFixer implements DeprecatedFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'When making a method or function call on a single line there MUST NOT be a trailing comma after the last argument.', + [new CodeSample("proxyFixers); + } + + protected function createProxyFixers(): array + { + $fixer = new NoTrailingCommaInSinglelineFixer(); + $fixer->configure(['elements' => ['arguments', 'array_destructuring']]); + + return [$fixer]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoUnreachableDefaultArgumentValueFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoUnreachableDefaultArgumentValueFixer.php new file mode 100644 index 00000000..108901da --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoUnreachableDefaultArgumentValueFixer.php @@ -0,0 +1,191 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\FunctionNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Mark Scherer + * @author Lucas Manzke + * @author Gregor Harlan + */ +final class NoUnreachableDefaultArgumentValueFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'In function arguments there must not be arguments with default values before non-default ones.', + [ + new CodeSample( + 'isAnyTokenKindsFound([T_FUNCTION, T_FN]); + } + + public function isRisky(): bool + { + return true; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $functionKinds = [T_FUNCTION, T_FN]; + + for ($i = 0, $l = $tokens->count(); $i < $l; ++$i) { + if (!$tokens[$i]->isGivenKind($functionKinds)) { + continue; + } + + $startIndex = $tokens->getNextTokenOfKind($i, ['(']); + $i = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startIndex); + + $this->fixFunctionDefinition($tokens, $startIndex, $i); + } + } + + private function fixFunctionDefinition(Tokens $tokens, int $startIndex, int $endIndex): void + { + $lastArgumentIndex = $this->getLastNonDefaultArgumentIndex($tokens, $startIndex, $endIndex); + + if (null === $lastArgumentIndex) { + return; + } + + for ($i = $lastArgumentIndex; $i > $startIndex; --$i) { + $token = $tokens[$i]; + + if ($token->isGivenKind(T_VARIABLE)) { + $lastArgumentIndex = $i; + + continue; + } + + if (!$token->equals('=') || $this->isNonNullableTypehintedNullableVariable($tokens, $i)) { + continue; + } + + $this->removeDefaultValue($tokens, $i, $this->getDefaultValueEndIndex($tokens, $lastArgumentIndex)); + } + } + + private function getLastNonDefaultArgumentIndex(Tokens $tokens, int $startIndex, int $endIndex): ?int + { + for ($i = $endIndex - 1; $i > $startIndex; --$i) { + $token = $tokens[$i]; + + if ($token->equals('=')) { + $i = $tokens->getPrevMeaningfulToken($i); + + continue; + } + + if ($token->isGivenKind(T_VARIABLE) && !$this->isEllipsis($tokens, $i)) { + return $i; + } + } + + return null; + } + + private function isEllipsis(Tokens $tokens, int $variableIndex): bool + { + return $tokens[$tokens->getPrevMeaningfulToken($variableIndex)]->isGivenKind(T_ELLIPSIS); + } + + private function getDefaultValueEndIndex(Tokens $tokens, int $index): int + { + do { + $index = $tokens->getPrevMeaningfulToken($index); + + if ($tokens[$index]->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) { + $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_ATTRIBUTE, $index); + } + } while (!$tokens[$index]->equals(',')); + + return $tokens->getPrevMeaningfulToken($index); + } + + private function removeDefaultValue(Tokens $tokens, int $startIndex, int $endIndex): void + { + for ($i = $startIndex; $i <= $endIndex;) { + $tokens->clearTokenAndMergeSurroundingWhitespace($i); + $this->clearWhitespacesBeforeIndex($tokens, $i); + $i = $tokens->getNextMeaningfulToken($i); + } + } + + /** + * @param int $index Index of "=" + */ + private function isNonNullableTypehintedNullableVariable(Tokens $tokens, int $index): bool + { + $nextToken = $tokens[$tokens->getNextMeaningfulToken($index)]; + + if (!$nextToken->equals([T_STRING, 'null'], false)) { + return false; + } + + $variableIndex = $tokens->getPrevMeaningfulToken($index); + + $searchTokens = [',', '(', [T_STRING], [CT::T_ARRAY_TYPEHINT], [T_CALLABLE]]; + $typehintKinds = [T_STRING, CT::T_ARRAY_TYPEHINT, T_CALLABLE]; + + $prevIndex = $tokens->getPrevTokenOfKind($variableIndex, $searchTokens); + + if (!$tokens[$prevIndex]->isGivenKind($typehintKinds)) { + return false; + } + + return !$tokens[$tokens->getPrevMeaningfulToken($prevIndex)]->isGivenKind(CT::T_NULLABLE_TYPE); + } + + private function clearWhitespacesBeforeIndex(Tokens $tokens, int $index): void + { + $prevIndex = $tokens->getNonEmptySibling($index, -1); + if (!$tokens[$prevIndex]->isWhitespace()) { + return; + } + + $prevNonWhiteIndex = $tokens->getPrevNonWhitespace($prevIndex); + if (null === $prevNonWhiteIndex || !$tokens[$prevNonWhiteIndex]->isComment()) { + $tokens->clearTokenAndMergeSurroundingWhitespace($prevIndex); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoUselessSprintfFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoUselessSprintfFixer.php new file mode 100644 index 00000000..125bbab6 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoUselessSprintfFixer.php @@ -0,0 +1,109 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\FunctionNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\Tokens; + +final class NoUselessSprintfFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'There must be no `sprintf` calls with only the first argument.', + [ + new CodeSample( + "isTokenKindFound(T_STRING); + } + + public function isRisky(): bool + { + return true; + } + + /** + * {@inheritdoc} + * + * Must run before MethodArgumentSpaceFixer, NativeFunctionCasingFixer, NoEmptyStatementFixer, NoExtraBlankLinesFixer, NoSpacesInsideParenthesisFixer, SpacesInsideParenthesesFixer. + */ + public function getPriority(): int + { + return 42; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $functionAnalyzer = new FunctionsAnalyzer(); + $argumentsAnalyzer = new ArgumentsAnalyzer(); + + for ($index = \count($tokens) - 1; $index > 0; --$index) { + if (!$tokens[$index]->isGivenKind(T_STRING)) { + continue; + } + + if ('sprintf' !== strtolower($tokens[$index]->getContent())) { + continue; + } + + if (!$functionAnalyzer->isGlobalFunctionCall($tokens, $index)) { + continue; + } + + $openParenthesisIndex = $tokens->getNextTokenOfKind($index, ['(']); + + if ($tokens[$tokens->getNextMeaningfulToken($openParenthesisIndex)]->isGivenKind(T_ELLIPSIS)) { + continue; + } + + $closeParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesisIndex); + + if (1 !== $argumentsAnalyzer->countArguments($tokens, $openParenthesisIndex, $closeParenthesisIndex)) { + continue; + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($closeParenthesisIndex); + + $prevMeaningfulTokenIndex = $tokens->getPrevMeaningfulToken($closeParenthesisIndex); + + if ($tokens[$prevMeaningfulTokenIndex]->equals(',')) { + $tokens->clearTokenAndMergeSurroundingWhitespace($prevMeaningfulTokenIndex); + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($openParenthesisIndex); + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + + $prevMeaningfulTokenIndex = $tokens->getPrevMeaningfulToken($index); + + if ($tokens[$prevMeaningfulTokenIndex]->isGivenKind(T_NS_SEPARATOR)) { + $tokens->clearTokenAndMergeSurroundingWhitespace($prevMeaningfulTokenIndex); + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NullableTypeDeclarationForDefaultNullValueFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NullableTypeDeclarationForDefaultNullValueFixer.php new file mode 100644 index 00000000..f93294e9 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NullableTypeDeclarationForDefaultNullValueFixer.php @@ -0,0 +1,233 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\FunctionNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\ArgumentAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\TypeAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author HypeMC + */ +final class NullableTypeDeclarationForDefaultNullValueFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Adds or removes `?` before single type declarations or `|null` at the end of union types when parameters have a default `null` value.', + [ + new CodeSample( + " false] + ), + new VersionSpecificCodeSample( + " false] + ), + new VersionSpecificCodeSample( + " false] + ), + ], + 'Rule is applied only in a PHP 7.1+ environment.' + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_VARIABLE) && $tokens->isAnyTokenKindsFound([T_FUNCTION, T_FN]); + } + + /** + * {@inheritdoc} + * + * Must run before NoUnreachableDefaultArgumentValueFixer, NullableTypeDeclarationFixer, OrderedTypesFixer. + */ + public function getPriority(): int + { + return 3; + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('use_nullable_type_declaration', 'Whether to add or remove `?` or `|null` to parameters with a default `null` value.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $functionsAnalyzer = new FunctionsAnalyzer(); + $tokenKinds = [T_FUNCTION, T_FN]; + + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + $token = $tokens[$index]; + + if (!$token->isGivenKind($tokenKinds)) { + continue; + } + + $arguments = $functionsAnalyzer->getFunctionArguments($tokens, $index); + $this->fixFunctionParameters($tokens, $arguments); + } + } + + /** + * @param array $arguments + */ + private function fixFunctionParameters(Tokens $tokens, array $arguments): void + { + $constructorPropertyModifiers = [ + CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC, + CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED, + CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE, + ]; + + if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required + $constructorPropertyModifiers[] = T_READONLY; + } + + foreach (array_reverse($arguments) as $argumentInfo) { + if ( + // Skip, if the parameter + // - doesn't have a type declaration + !$argumentInfo->hasTypeAnalysis() + // - has a mixed or standalone null type + || \in_array(strtolower($argumentInfo->getTypeAnalysis()->getName()), ['mixed', 'null'], true) + // - a default value is not null we can continue + || !$argumentInfo->hasDefault() || 'null' !== strtolower($argumentInfo->getDefault()) + ) { + continue; + } + + $argumentTypeInfo = $argumentInfo->getTypeAnalysis(); + + if (\PHP_VERSION_ID >= 8_00_00 && false === $this->configuration['use_nullable_type_declaration']) { + $visibility = $tokens[$tokens->getPrevMeaningfulToken($argumentTypeInfo->getStartIndex())]; + + if ($visibility->isGivenKind($constructorPropertyModifiers)) { + continue; + } + } + + $typeAnalysisName = $argumentTypeInfo->getName(); + if (str_contains($typeAnalysisName, '|') || str_contains($typeAnalysisName, '&')) { + $this->fixUnionTypeParameter($tokens, $argumentTypeInfo); + } else { + $this->fixSingleTypeParameter($tokens, $argumentTypeInfo); + } + } + } + + private function fixSingleTypeParameter(Tokens $tokens, TypeAnalysis $argumentTypeInfo): void + { + if (true === $this->configuration['use_nullable_type_declaration']) { + if (!$argumentTypeInfo->isNullable()) { + $tokens->insertAt($argumentTypeInfo->getStartIndex(), new Token([CT::T_NULLABLE_TYPE, '?'])); + } + } elseif ($argumentTypeInfo->isNullable()) { + $tokens->removeTrailingWhitespace($startIndex = $argumentTypeInfo->getStartIndex()); + $tokens->clearTokenAndMergeSurroundingWhitespace($startIndex); + } + } + + private function fixUnionTypeParameter(Tokens $tokens, TypeAnalysis $argumentTypeInfo): void + { + if (true === $this->configuration['use_nullable_type_declaration']) { + if ($argumentTypeInfo->isNullable()) { + return; + } + + $typeAnalysisName = $argumentTypeInfo->getName(); + $endIndex = $argumentTypeInfo->getEndIndex(); + + if (str_contains($typeAnalysisName, '&') && !str_contains($typeAnalysisName, '|')) { + $endIndex += 2; + $tokens->insertAt($argumentTypeInfo->getStartIndex(), new Token([CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_OPEN, '('])); + $tokens->insertAt($endIndex, new Token([CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE, ')'])); + } + + $tokens->insertAt($endIndex + 1, [ + new Token([CT::T_TYPE_ALTERNATION, '|']), + new Token([T_STRING, 'null']), + ]); + } elseif ($argumentTypeInfo->isNullable()) { + $startIndex = $argumentTypeInfo->getStartIndex(); + + $index = $tokens->getNextTokenOfKind($startIndex - 1, [[T_STRING, 'null']], false); + + if ($index === $startIndex) { + $tokens->removeTrailingWhitespace($index); + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + + $index = $tokens->getNextMeaningfulToken($index); + if ($tokens[$index]->equals([CT::T_TYPE_ALTERNATION, '|'])) { + $tokens->removeTrailingWhitespace($index); + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } + } else { + $tokens->removeLeadingWhitespace($index); + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + + $index = $tokens->getPrevMeaningfulToken($index); + if ($tokens[$index]->equals([CT::T_TYPE_ALTERNATION, '|'])) { + $tokens->removeLeadingWhitespace($index); + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } + } + + $typeAnalysisName = $argumentTypeInfo->getName(); + + if (str_contains($typeAnalysisName, '&') && 1 === substr_count($typeAnalysisName, '|')) { + $index = $tokens->getNextTokenOfKind($startIndex - 1, [[CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_OPEN]]); + $tokens->removeTrailingWhitespace($index); + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + + $index = $tokens->getPrevTokenOfKind($argumentTypeInfo->getEndIndex() + 1, [[CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE]]); + $tokens->removeLeadingWhitespace($index); + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/PhpdocToParamTypeFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/PhpdocToParamTypeFixer.php new file mode 100644 index 00000000..4b615ce3 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/PhpdocToParamTypeFixer.php @@ -0,0 +1,230 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\FunctionNotation; + +use PhpCsFixer\AbstractPhpdocToTypeDeclarationFixer; +use PhpCsFixer\DocBlock\Annotation; +use PhpCsFixer\Fixer\ExperimentalFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Jan Gantzert + */ +final class PhpdocToParamTypeFixer extends AbstractPhpdocToTypeDeclarationFixer implements ExperimentalFixerInterface +{ + private const TYPE_CHECK_TEMPLATE = ' + */ + private const SKIPPED_TYPES = [ + 'resource' => true, + 'static' => true, + 'void' => true, + ]; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Takes `@param` annotations of non-mixed types and adjusts accordingly the function signature. Requires PHP >= 7.0.', + [ + new CodeSample( + ' false] + ), + new CodeSample( + ' false] + ), + ], + null, + 'The `@param` annotation is mandatory for the fixer to make changes, signatures of methods without it (no docblock, inheritdocs) will not be fixed. Manual actions are required if inherited signatures are not properly documented.' + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound([T_FUNCTION, T_FN]); + } + + /** + * {@inheritdoc} + * + * Must run before NoSuperfluousPhpdocTagsFixer, PhpdocAlignFixer. + * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. + */ + public function getPriority(): int + { + return 8; + } + + protected function isSkippedType(string $type): bool + { + return isset(self::SKIPPED_TYPES[$type]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; 0 < $index; --$index) { + if (!$tokens[$index]->isGivenKind([T_FUNCTION, T_FN])) { + continue; + } + + $funcName = $tokens->getNextMeaningfulToken($index); + if ($tokens[$funcName]->equalsAny(self::EXCLUDE_FUNC_NAMES, false)) { + continue; + } + + $docCommentIndex = $this->findFunctionDocComment($tokens, $index); + + if (null === $docCommentIndex) { + continue; + } + + foreach ($this->getAnnotationsFromDocComment('param', $tokens, $docCommentIndex) as $paramTypeAnnotation) { + $typesExpression = $paramTypeAnnotation->getTypeExpression(); + + if (null === $typesExpression) { + continue; + } + + $typeInfo = $this->getCommonTypeInfo($typesExpression, false); + $unionTypes = null; + + if (null === $typeInfo) { + $unionTypes = $this->getUnionTypes($typesExpression, false); + } + + if (null === $typeInfo && null === $unionTypes) { + continue; + } + + if (null !== $typeInfo) { + $paramType = $typeInfo['commonType']; + $isNullable = $typeInfo['isNullable']; + } elseif (null !== $unionTypes) { + $paramType = $unionTypes; + $isNullable = false; + } + + if (!isset($paramType, $isNullable)) { + continue; + } + + $startIndex = $tokens->getNextTokenOfKind($index, ['(']); + $variableIndex = $this->findCorrectVariable($tokens, $startIndex, $paramTypeAnnotation); + + if (null === $variableIndex) { + continue; + } + + $byRefIndex = $tokens->getPrevMeaningfulToken($variableIndex); + + if ($tokens[$byRefIndex]->equals('&')) { + $variableIndex = $byRefIndex; + } + + if ($this->hasParamTypeHint($tokens, $variableIndex)) { + continue; + } + + if (!$this->isValidSyntax(sprintf(self::TYPE_CHECK_TEMPLATE, $paramType))) { + continue; + } + + $tokens->insertAt($variableIndex, array_merge( + $this->createTypeDeclarationTokens($paramType, $isNullable), + [new Token([T_WHITESPACE, ' '])] + )); + } + } + } + + protected function createTokensFromRawType(string $type): Tokens + { + $typeTokens = Tokens::fromCode(sprintf(self::TYPE_CHECK_TEMPLATE, $type)); + $typeTokens->clearRange(0, 4); + $typeTokens->clearRange(\count($typeTokens) - 6, \count($typeTokens) - 1); + $typeTokens->clearEmptyTokens(); + + return $typeTokens; + } + + private function findCorrectVariable(Tokens $tokens, int $startIndex, Annotation $paramTypeAnnotation): ?int + { + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startIndex); + + for ($index = $startIndex + 1; $index < $endIndex; ++$index) { + if (!$tokens[$index]->isGivenKind(T_VARIABLE)) { + continue; + } + + $variableName = $tokens[$index]->getContent(); + + if ($paramTypeAnnotation->getVariableName() === $variableName) { + return $index; + } + } + + return null; + } + + /** + * Determine whether the function already has a param type hint. + * + * @param int $index The index of the end of the function definition line, EG at { or ; + */ + private function hasParamTypeHint(Tokens $tokens, int $index): bool + { + $prevIndex = $tokens->getPrevMeaningfulToken($index); + + return !$tokens[$prevIndex]->equalsAny([',', '(']); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/PhpdocToPropertyTypeFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/PhpdocToPropertyTypeFixer.php new file mode 100644 index 00000000..22fcd11c --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/PhpdocToPropertyTypeFixer.php @@ -0,0 +1,282 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\FunctionNotation; + +use PhpCsFixer\AbstractPhpdocToTypeDeclarationFixer; +use PhpCsFixer\DocBlock\Annotation; +use PhpCsFixer\Fixer\ExperimentalFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @phpstan-import-type _CommonTypeInfo from AbstractPhpdocToTypeDeclarationFixer + */ +final class PhpdocToPropertyTypeFixer extends AbstractPhpdocToTypeDeclarationFixer implements ExperimentalFixerInterface +{ + private const TYPE_CHECK_TEMPLATE = ' + */ + private array $skippedTypes = [ + 'resource' => true, + 'null' => true, + ]; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Takes `@var` annotation of non-mixed types and adjusts accordingly the property signature. Requires PHP >= 7.4.', + [ + new CodeSample( + ' false] + ), + new CodeSample( + ' false] + ), + ], + null, + 'The `@var` annotation is mandatory for the fixer to make changes, signatures of properties without it (no docblock) will not be fixed. Manual actions might be required for newly typed properties that are read before initialization.' + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + /** + * {@inheritdoc} + * + * Must run before NoSuperfluousPhpdocTagsFixer, PhpdocAlignFixer. + * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. + */ + public function getPriority(): int + { + return 7; + } + + protected function isSkippedType(string $type): bool + { + return isset($this->skippedTypes[$type]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; 0 < $index; --$index) { + if ($tokens[$index]->isGivenKind([T_CLASS, T_TRAIT])) { + $this->fixClass($tokens, $index); + } + } + } + + protected function createTokensFromRawType(string $type): Tokens + { + $typeTokens = Tokens::fromCode(sprintf(self::TYPE_CHECK_TEMPLATE, $type)); + $typeTokens->clearRange(0, 8); + $typeTokens->clearRange(\count($typeTokens) - 5, \count($typeTokens) - 1); + $typeTokens->clearEmptyTokens(); + + return $typeTokens; + } + + private function fixClass(Tokens $tokens, int $index): void + { + $index = $tokens->getNextTokenOfKind($index, ['{']); + $classEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); + + for (; $index < $classEndIndex; ++$index) { + if ($tokens[$index]->isGivenKind(T_FUNCTION)) { + $index = $tokens->getNextTokenOfKind($index, ['{', ';']); + + if ($tokens[$index]->equals('{')) { + $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); + } + + continue; + } + + if (!$tokens[$index]->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + $docCommentIndex = $index; + $propertyIndices = $this->findNextUntypedPropertiesDeclaration($tokens, $docCommentIndex); + + if ([] === $propertyIndices) { + continue; + } + + $typeInfo = $this->resolveApplicableType( + $propertyIndices, + $this->getAnnotationsFromDocComment('var', $tokens, $docCommentIndex) + ); + + if (null === $typeInfo) { + continue; + } + + $propertyType = $typeInfo['commonType']; + $isNullable = $typeInfo['isNullable']; + + if (\in_array($propertyType, ['callable', 'never', 'void'], true)) { + continue; + } + + if (!$this->isValidSyntax(sprintf(self::TYPE_CHECK_TEMPLATE, $propertyType))) { + continue; + } + + $newTokens = array_merge( + $this->createTypeDeclarationTokens($propertyType, $isNullable), + [new Token([T_WHITESPACE, ' '])] + ); + + $tokens->insertAt(current($propertyIndices), $newTokens); + + $index = max($propertyIndices) + \count($newTokens) + 1; + $classEndIndex += \count($newTokens); + } + } + + /** + * @return array + */ + private function findNextUntypedPropertiesDeclaration(Tokens $tokens, int $index): array + { + do { + $index = $tokens->getNextMeaningfulToken($index); + } while ($tokens[$index]->isGivenKind([ + T_PRIVATE, + T_PROTECTED, + T_PUBLIC, + T_STATIC, + T_VAR, + ])); + + if (!$tokens[$index]->isGivenKind(T_VARIABLE)) { + return []; + } + + $properties = []; + + while (!$tokens[$index]->equals(';')) { + if ($tokens[$index]->isGivenKind(T_VARIABLE)) { + $properties[$tokens[$index]->getContent()] = $index; + } + + $index = $tokens->getNextMeaningfulToken($index); + } + + return $properties; + } + + /** + * @param array $propertyIndices + * @param Annotation[] $annotations + * + * @return ?_CommonTypeInfo + */ + private function resolveApplicableType(array $propertyIndices, array $annotations): ?array + { + $propertyTypes = []; + + foreach ($annotations as $annotation) { + $propertyName = $annotation->getVariableName(); + + if (null === $propertyName) { + if (1 !== \count($propertyIndices)) { + continue; + } + + $propertyName = array_key_first($propertyIndices); + } + + if (!isset($propertyIndices[$propertyName])) { + continue; + } + + $typesExpression = $annotation->getTypeExpression(); + + if (null === $typesExpression) { + continue; + } + + $typeInfo = $this->getCommonTypeInfo($typesExpression, false); + $unionTypes = null; + + if (null === $typeInfo) { + $unionTypes = $this->getUnionTypes($typesExpression, false); + } + + if (null === $typeInfo && null === $unionTypes) { + continue; + } + + if (null !== $unionTypes) { + $typeInfo = ['commonType' => $unionTypes, 'isNullable' => false]; + } + + if (\array_key_exists($propertyName, $propertyTypes) && $typeInfo !== $propertyTypes[$propertyName]) { + return null; + } + + $propertyTypes[$propertyName] = $typeInfo; + } + + if (\count($propertyTypes) !== \count($propertyIndices)) { + return null; + } + + $type = array_shift($propertyTypes); + + foreach ($propertyTypes as $propertyType) { + if ($propertyType !== $type) { + return null; + } + } + + return $type; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/PhpdocToReturnTypeFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/PhpdocToReturnTypeFixer.php new file mode 100644 index 00000000..7b029da2 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/PhpdocToReturnTypeFixer.php @@ -0,0 +1,235 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\FunctionNotation; + +use PhpCsFixer\AbstractPhpdocToTypeDeclarationFixer; +use PhpCsFixer\Fixer\ExperimentalFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Filippo Tessarotto + */ +final class PhpdocToReturnTypeFixer extends AbstractPhpdocToTypeDeclarationFixer implements ExperimentalFixerInterface +{ + private const TYPE_CHECK_TEMPLATE = '> + */ + private array $excludeFuncNames = [ + [T_STRING, '__construct'], + [T_STRING, '__destruct'], + [T_STRING, '__clone'], + ]; + + /** + * @var array + */ + private array $skippedTypes = [ + 'resource' => true, + 'null' => true, + ]; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Takes `@return` annotation of non-mixed types and adjusts accordingly the function signature.', + [ + new CodeSample( + ' false] + ), + new CodeSample( + ' false] + ), + new VersionSpecificCodeSample( + 'isAnyTokenKindsFound([T_FUNCTION, T_FN]); + } + + /** + * {@inheritdoc} + * + * Must run before FullyQualifiedStrictTypesFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAlignFixer, ReturnToYieldFromFixer, ReturnTypeDeclarationFixer. + * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. + */ + public function getPriority(): int + { + return 13; + } + + protected function isSkippedType(string $type): bool + { + return isset($this->skippedTypes[$type]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; 0 < $index; --$index) { + if (!$tokens[$index]->isGivenKind([T_FUNCTION, T_FN])) { + continue; + } + + $funcName = $tokens->getNextMeaningfulToken($index); + + if ($tokens[$funcName]->equalsAny($this->excludeFuncNames, false)) { + continue; + } + + $docCommentIndex = $this->findFunctionDocComment($tokens, $index); + if (null === $docCommentIndex) { + continue; + } + + $returnTypeAnnotations = $this->getAnnotationsFromDocComment('return', $tokens, $docCommentIndex); + if (1 !== \count($returnTypeAnnotations)) { + continue; + } + + $returnTypeAnnotation = $returnTypeAnnotations[0]; + + $typesExpression = $returnTypeAnnotation->getTypeExpression(); + + if (null === $typesExpression) { + continue; + } + + $typeInfo = $this->getCommonTypeInfo($typesExpression, true); + $unionTypes = null; + + if (null === $typeInfo) { + $unionTypes = $this->getUnionTypes($typesExpression, true); + } + + if (null === $typeInfo && null === $unionTypes) { + continue; + } + + if (null !== $typeInfo) { + $returnType = $typeInfo['commonType']; + $isNullable = $typeInfo['isNullable']; + } elseif (null !== $unionTypes) { + $returnType = $unionTypes; + $isNullable = false; + } + + if (!isset($returnType, $isNullable)) { + continue; + } + + $paramsStartIndex = $tokens->getNextTokenOfKind($index, ['(']); + $paramsEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $paramsStartIndex); + + $bodyStartIndex = $tokens->getNextTokenOfKind($paramsEndIndex, ['{', ';', [T_DOUBLE_ARROW]]); + + if ($this->hasReturnTypeHint($tokens, $bodyStartIndex)) { + continue; + } + + if (!$this->isValidSyntax(sprintf(self::TYPE_CHECK_TEMPLATE, $returnType))) { + continue; + } + + $tokens->insertAt( + $paramsEndIndex + 1, + array_merge( + [ + new Token([CT::T_TYPE_COLON, ':']), + new Token([T_WHITESPACE, ' ']), + ], + $this->createTypeDeclarationTokens($returnType, $isNullable) + ) + ); + } + } + + protected function createTokensFromRawType(string $type): Tokens + { + $typeTokens = Tokens::fromCode(sprintf(self::TYPE_CHECK_TEMPLATE, $type)); + $typeTokens->clearRange(0, 7); + $typeTokens->clearRange(\count($typeTokens) - 3, \count($typeTokens) - 1); + $typeTokens->clearEmptyTokens(); + + return $typeTokens; + } + + /** + * Determine whether the function already has a return type hint. + * + * @param int $index The index of the end of the function definition line, EG at { or ; + */ + private function hasReturnTypeHint(Tokens $tokens, int $index): bool + { + $endFuncIndex = $tokens->getPrevTokenOfKind($index, [')']); + $nextIndex = $tokens->getNextMeaningfulToken($endFuncIndex); + + return $tokens[$nextIndex]->isGivenKind(CT::T_TYPE_COLON); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/RegularCallableCallFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/RegularCallableCallFixer.php new file mode 100644 index 00000000..2bb372c3 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/RegularCallableCallFixer.php @@ -0,0 +1,256 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\FunctionNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class RegularCallableCallFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Callables must be called without using `call_user_func*` when possible.', + [ + new CodeSample( + ' \'baz\'])` or `call_user_func($foo, $foo = \'bar\')`.' + ); + } + + /** + * {@inheritdoc} + * + * Must run before NativeFunctionInvocationFixer. + * Must run after NoBinaryStringFixer, NoUselessConcatOperatorFixer. + */ + public function getPriority(): int + { + return 2; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_STRING); + } + + public function isRisky(): bool + { + return true; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $functionsAnalyzer = new FunctionsAnalyzer(); + $argumentsAnalyzer = new ArgumentsAnalyzer(); + + for ($index = $tokens->count() - 1; $index > 0; --$index) { + if (!$tokens[$index]->equalsAny([[T_STRING, 'call_user_func'], [T_STRING, 'call_user_func_array']], false)) { + continue; + } + + if (!$functionsAnalyzer->isGlobalFunctionCall($tokens, $index)) { + continue; // redeclare/override + } + + $openParenthesis = $tokens->getNextMeaningfulToken($index); + $closeParenthesis = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesis); + $arguments = $argumentsAnalyzer->getArguments($tokens, $openParenthesis, $closeParenthesis); + + if (1 > \count($arguments)) { + return; // no arguments! + } + + $this->processCall($tokens, $index, $arguments); + } + } + + /** + * @param array $arguments + */ + private function processCall(Tokens $tokens, int $index, array $arguments): void + { + $firstArgIndex = $tokens->getNextMeaningfulToken( + $tokens->getNextMeaningfulToken($index) + ); + + /** @var Token $firstArgToken */ + $firstArgToken = $tokens[$firstArgIndex]; + + if ($firstArgToken->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) { + $afterFirstArgIndex = $tokens->getNextMeaningfulToken($firstArgIndex); + + if (!$tokens[$afterFirstArgIndex]->equalsAny([',', ')'])) { + return; // first argument is an expression like `call_user_func("foo"."bar", ...)`, not supported! + } + + $firstArgTokenContent = $firstArgToken->getContent(); + + if (!$this->isValidFunctionInvoke($firstArgTokenContent)) { + return; + } + + $newCallTokens = Tokens::fromCode('getContent()), 1, -1).'();'); + $newCallTokensSize = $newCallTokens->count(); + $newCallTokens->clearAt(0); + $newCallTokens->clearRange($newCallTokensSize - 3, $newCallTokensSize - 1); + $newCallTokens->clearEmptyTokens(); + + $this->replaceCallUserFuncWithCallback($tokens, $index, $newCallTokens, $firstArgIndex, $firstArgIndex); + } elseif ( + $firstArgToken->isGivenKind(T_FUNCTION) + || ( + $firstArgToken->isGivenKind(T_STATIC) + && $tokens[$tokens->getNextMeaningfulToken($firstArgIndex)]->isGivenKind(T_FUNCTION) + ) + ) { + $firstArgEndIndex = $tokens->findBlockEnd( + Tokens::BLOCK_TYPE_CURLY_BRACE, + $tokens->getNextTokenOfKind($firstArgIndex, ['{']) + ); + + $newCallTokens = $this->getTokensSubcollection($tokens, $firstArgIndex, $firstArgEndIndex); + $newCallTokens->insertAt($newCallTokens->count(), new Token(')')); + $newCallTokens->insertAt(0, new Token('(')); + $this->replaceCallUserFuncWithCallback($tokens, $index, $newCallTokens, $firstArgIndex, $firstArgEndIndex); + } elseif ($firstArgToken->isGivenKind(T_VARIABLE)) { + $firstArgEndIndex = reset($arguments); + + // check if the same variable is used multiple times and if so do not fix + + foreach ($arguments as $argumentStart => $argumentEnd) { + if ($firstArgEndIndex === $argumentEnd) { + continue; + } + + for ($i = $argumentStart; $i <= $argumentEnd; ++$i) { + if ($tokens[$i]->equals($firstArgToken)) { + return; + } + } + } + + // check if complex statement and if so wrap the call in () if on PHP 7 or up, else do not fix + + $newCallTokens = $this->getTokensSubcollection($tokens, $firstArgIndex, $firstArgEndIndex); + $complex = false; + + for ($newCallIndex = \count($newCallTokens) - 1; $newCallIndex >= 0; --$newCallIndex) { + if ($newCallTokens[$newCallIndex]->isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT, T_VARIABLE])) { + continue; + } + + $blockType = Tokens::detectBlockType($newCallTokens[$newCallIndex]); + + if (null !== $blockType && (Tokens::BLOCK_TYPE_ARRAY_INDEX_CURLY_BRACE === $blockType['type'] || Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE === $blockType['type'])) { + $newCallIndex = $newCallTokens->findBlockStart($blockType['type'], $newCallIndex); + + continue; + } + + $complex = true; + + break; + } + + if ($complex) { + $newCallTokens->insertAt($newCallTokens->count(), new Token(')')); + $newCallTokens->insertAt(0, new Token('(')); + } + $this->replaceCallUserFuncWithCallback($tokens, $index, $newCallTokens, $firstArgIndex, $firstArgEndIndex); + } + } + + private function replaceCallUserFuncWithCallback(Tokens $tokens, int $callIndex, Tokens $newCallTokens, int $firstArgStartIndex, int $firstArgEndIndex): void + { + $tokens->clearRange($firstArgStartIndex, $firstArgEndIndex); + + $afterFirstArgIndex = $tokens->getNextMeaningfulToken($firstArgEndIndex); + $afterFirstArgToken = $tokens[$afterFirstArgIndex]; + + if ($afterFirstArgToken->equals(',')) { + $useEllipsis = $tokens[$callIndex]->equals([T_STRING, 'call_user_func_array'], false); + + if ($useEllipsis) { + $secondArgIndex = $tokens->getNextMeaningfulToken($afterFirstArgIndex); + $tokens->insertAt($secondArgIndex, new Token([T_ELLIPSIS, '...'])); + } + + $tokens->clearAt($afterFirstArgIndex); + $tokens->removeTrailingWhitespace($afterFirstArgIndex); + } + + $tokens->overrideRange($callIndex, $callIndex, $newCallTokens); + $prevIndex = $tokens->getPrevMeaningfulToken($callIndex); + + if ($tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) { + $tokens->clearTokenAndMergeSurroundingWhitespace($prevIndex); + } + } + + private function getTokensSubcollection(Tokens $tokens, int $indexStart, int $indexEnd): Tokens + { + $size = $indexEnd - $indexStart + 1; + $subCollection = new Tokens($size); + + for ($i = 0; $i < $size; ++$i) { + /** @var Token $toClone */ + $toClone = $tokens[$i + $indexStart]; + $subCollection[$i] = clone $toClone; + } + + return $subCollection; + } + + private function isValidFunctionInvoke(string $name): bool + { + if (\strlen($name) < 3 || 'b' === $name[0] || 'B' === $name[0]) { + return false; + } + + $name = substr($name, 1, -1); + + if ($name !== trim($name)) { + return false; + } + + return true; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/ReturnTypeDeclarationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/ReturnTypeDeclarationFixer.php new file mode 100644 index 00000000..356bf45c --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/ReturnTypeDeclarationFixer.php @@ -0,0 +1,119 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\FunctionNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class ReturnTypeDeclarationFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Adjust spacing around colon in return type declarations and backed enum types.', + [ + new CodeSample( + " 'none'] + ), + new CodeSample( + " 'one'] + ), + ], + 'Rule is applied only in a PHP 7+ environment.' + ); + } + + /** + * {@inheritdoc} + * + * Must run after PhpUnitDataProviderReturnTypeFixer, PhpdocToReturnTypeFixer, VoidReturnFixer. + */ + public function getPriority(): int + { + return -17; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(CT::T_TYPE_COLON); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $oneSpaceBefore = 'one' === $this->configuration['space_before']; + + for ($index = 0, $limit = $tokens->count(); $index < $limit; ++$index) { + if (!$tokens[$index]->isGivenKind(CT::T_TYPE_COLON)) { + continue; + } + + $previousIndex = $index - 1; + $previousToken = $tokens[$previousIndex]; + + if ($previousToken->isWhitespace()) { + if (!$tokens[$tokens->getPrevNonWhitespace($index - 1)]->isComment()) { + if ($oneSpaceBefore) { + $tokens[$previousIndex] = new Token([T_WHITESPACE, ' ']); + } else { + $tokens->clearAt($previousIndex); + } + } + } elseif ($oneSpaceBefore) { + $tokenWasAdded = $tokens->ensureWhitespaceAtIndex($index, 0, ' '); + + if ($tokenWasAdded) { + ++$limit; + } + + ++$index; + } + + ++$index; + + $tokenWasAdded = $tokens->ensureWhitespaceAtIndex($index, 0, ' '); + + if ($tokenWasAdded) { + ++$limit; + } + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('space_before', 'Spacing to apply before colon.')) + ->setAllowedValues(['one', 'none']) + ->setDefault('none') + ->getOption(), + ]); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/SingleLineThrowFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/SingleLineThrowFixer.php new file mode 100644 index 00000000..3ee0d51a --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/SingleLineThrowFixer.php @@ -0,0 +1,159 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\FunctionNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Kuba Werłos + */ +final class SingleLineThrowFixer extends AbstractFixer +{ + private const REMOVE_WHITESPACE_AFTER_TOKENS = ['[']; + private const REMOVE_WHITESPACE_AROUND_TOKENS = ['(', [T_DOUBLE_COLON]]; + private const REMOVE_WHITESPACE_BEFORE_TOKENS = [')', ']', ',', ';']; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Throwing exception must be done in single line.', + [ + new CodeSample("isTokenKindFound(T_THROW); + } + + /** + * {@inheritdoc} + * + * Must run before BracesFixer, ConcatSpaceFixer. + */ + public function getPriority(): int + { + return 36; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = 0, $count = $tokens->count(); $index < $count; ++$index) { + if (!$tokens[$index]->isGivenKind(T_THROW)) { + continue; + } + + $endCandidateIndex = $tokens->getNextMeaningfulToken($index); + + while (!$tokens[$endCandidateIndex]->equalsAny([')', ']', ',', ';', [T_CLOSE_TAG]])) { + $blockType = Tokens::detectBlockType($tokens[$endCandidateIndex]); + + if (null !== $blockType) { + if (Tokens::BLOCK_TYPE_CURLY_BRACE === $blockType['type'] || !$blockType['isStart']) { + break; + } + + $endCandidateIndex = $tokens->findBlockEnd($blockType['type'], $endCandidateIndex); + } + + $endCandidateIndex = $tokens->getNextMeaningfulToken($endCandidateIndex); + } + + $this->trimNewLines($tokens, $index, $tokens->getPrevMeaningfulToken($endCandidateIndex)); + } + } + + private function trimNewLines(Tokens $tokens, int $startIndex, int $endIndex): void + { + for ($index = $startIndex; $index < $endIndex; ++$index) { + $content = $tokens[$index]->getContent(); + + if ($tokens[$index]->isGivenKind(T_COMMENT)) { + if (str_starts_with($content, '//')) { + $content = '/*'.substr($content, 2).' */'; + $tokens->clearAt($index + 1); + } elseif (str_starts_with($content, '#')) { + $content = '/*'.substr($content, 1).' */'; + $tokens->clearAt($index + 1); + } elseif (Preg::match('/\R/', $content)) { + $content = Preg::replace('/\R/', ' ', $content); + } + + $tokens[$index] = new Token([T_COMMENT, $content]); + + continue; + } + + if (!$tokens[$index]->isGivenKind(T_WHITESPACE)) { + continue; + } + + if (!Preg::match('/\R/', $content)) { + continue; + } + + $prevIndex = $tokens->getNonEmptySibling($index, -1); + + if ($this->isPreviousTokenToClear($tokens[$prevIndex])) { + $tokens->clearAt($index); + + continue; + } + + $nextIndex = $tokens->getNonEmptySibling($index, 1); + + if ( + $this->isNextTokenToClear($tokens[$nextIndex]) + && !$tokens[$prevIndex]->isGivenKind(T_FUNCTION) + ) { + $tokens->clearAt($index); + + continue; + } + + $tokens[$index] = new Token([T_WHITESPACE, ' ']); + } + } + + private function isPreviousTokenToClear(Token $token): bool + { + static $tokens = null; + + if (null === $tokens) { + $tokens = [...self::REMOVE_WHITESPACE_AFTER_TOKENS, ...self::REMOVE_WHITESPACE_AROUND_TOKENS]; + } + + return $token->equalsAny($tokens) || $token->isObjectOperator(); + } + + private function isNextTokenToClear(Token $token): bool + { + static $tokens = null; + + if (null === $tokens) { + $tokens = [...self::REMOVE_WHITESPACE_AROUND_TOKENS, ...self::REMOVE_WHITESPACE_BEFORE_TOKENS]; + } + + return $token->equalsAny($tokens) || $token->isObjectOperator(); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/StaticLambdaFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/StaticLambdaFixer.php new file mode 100644 index 00000000..81763f7f --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/StaticLambdaFixer.php @@ -0,0 +1,130 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\FunctionNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +final class StaticLambdaFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Lambdas not (indirectly) referencing `$this` must be declared `static`.', + [new CodeSample("bindTo` on lambdas without referencing to `$this`.' + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound([T_FUNCTION, T_FN]); + } + + public function isRisky(): bool + { + return true; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $analyzer = new TokensAnalyzer($tokens); + $expectedFunctionKinds = [T_FUNCTION, T_FN]; + + for ($index = $tokens->count() - 4; $index > 0; --$index) { + if (!$tokens[$index]->isGivenKind($expectedFunctionKinds) || !$analyzer->isLambda($index)) { + continue; + } + + $prev = $tokens->getPrevMeaningfulToken($index); + + if ($tokens[$prev]->isGivenKind(T_STATIC)) { + continue; // lambda is already 'static' + } + + $argumentsStartIndex = $tokens->getNextTokenOfKind($index, ['(']); + $argumentsEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $argumentsStartIndex); + + // figure out where the lambda starts and ends + + if ($tokens[$index]->isGivenKind(T_FUNCTION)) { + $lambdaOpenIndex = $tokens->getNextTokenOfKind($argumentsEndIndex, ['{']); + $lambdaEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $lambdaOpenIndex); + } else { // T_FN + $lambdaOpenIndex = $tokens->getNextTokenOfKind($argumentsEndIndex, [[T_DOUBLE_ARROW]]); + $lambdaEndIndex = $analyzer->getLastTokenIndexOfArrowFunction($index); + } + + if ($this->hasPossibleReferenceToThis($tokens, $lambdaOpenIndex, $lambdaEndIndex)) { + continue; + } + + // make the lambda static + $tokens->insertAt( + $index, + [ + new Token([T_STATIC, 'static']), + new Token([T_WHITESPACE, ' ']), + ] + ); + + $index -= 4; // fixed after a lambda, closes candidate is at least 4 tokens before that + } + } + + /** + * Returns 'true' if there is a possible reference to '$this' within the given tokens index range. + */ + private function hasPossibleReferenceToThis(Tokens $tokens, int $startIndex, int $endIndex): bool + { + for ($i = $startIndex; $i <= $endIndex; ++$i) { + if ($tokens[$i]->isGivenKind(T_VARIABLE) && '$this' === strtolower($tokens[$i]->getContent())) { + return true; // directly accessing '$this' + } + + if ($tokens[$i]->isGivenKind([ + T_INCLUDE, // loading additional symbols we cannot analyze here + T_INCLUDE_ONCE, // " + T_REQUIRE, // " + T_REQUIRE_ONCE, // " + CT::T_DYNAMIC_VAR_BRACE_OPEN, // "$h = ${$g};" case + T_EVAL, // "$c = eval('return $this;');" case + ])) { + return true; + } + + if ($tokens[$i]->equals('$')) { + $nextIndex = $tokens->getNextMeaningfulToken($i); + + if ($tokens[$nextIndex]->isGivenKind(T_VARIABLE)) { + return true; // "$$a" case + } + } + + if ($tokens[$i]->equals([T_STRING, 'parent'], false)) { + return true; // parent:: case + } + } + + return false; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/UseArrowFunctionsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/UseArrowFunctionsFixer.php new file mode 100644 index 00000000..5cafd0a3 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/UseArrowFunctionsFixer.php @@ -0,0 +1,203 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\FunctionNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Gregor Harlan + */ +final class UseArrowFunctionsFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Anonymous functions with one-liner return statement must use arrow functions.', + [ + new CodeSample( + <<<'SAMPLE' + isAllTokenKindsFound([T_FUNCTION, T_RETURN]); + } + + public function isRisky(): bool + { + return true; + } + + /** + * {@inheritdoc} + * + * Must run before FunctionDeclarationFixer. + */ + public function getPriority(): int + { + return 32; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $analyzer = new TokensAnalyzer($tokens); + + for ($index = $tokens->count() - 1; $index > 0; --$index) { + if (!$tokens[$index]->isGivenKind(T_FUNCTION) || !$analyzer->isLambda($index)) { + continue; + } + + // Find parameters end + // Abort if they are multilined + + $parametersStart = $tokens->getNextMeaningfulToken($index); + + if ($tokens[$parametersStart]->isGivenKind(CT::T_RETURN_REF)) { + $parametersStart = $tokens->getNextMeaningfulToken($parametersStart); + } + + $parametersEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $parametersStart); + + if ($this->isMultilined($tokens, $parametersStart, $parametersEnd)) { + continue; + } + + // Find `use ()` start and end + // Abort if it contains reference variables + + $next = $tokens->getNextMeaningfulToken($parametersEnd); + + $useStart = null; + $useEnd = null; + + if ($tokens[$next]->isGivenKind(CT::T_USE_LAMBDA)) { + $useStart = $next; + + if ($tokens[$useStart - 1]->isGivenKind(T_WHITESPACE)) { + --$useStart; + } + + $next = $tokens->getNextMeaningfulToken($next); + + while (!$tokens[$next]->equals(')')) { + if ($tokens[$next]->equals('&')) { + // variables used by reference are not supported by arrow functions + continue 2; + } + + $next = $tokens->getNextMeaningfulToken($next); + } + + $useEnd = $next; + $next = $tokens->getNextMeaningfulToken($next); + } + + // Find opening brace and following `return` + // Abort if there is more than whitespace between them (like comments) + + $braceOpen = $tokens[$next]->equals('{') ? $next : $tokens->getNextTokenOfKind($next, ['{']); + $return = $braceOpen + 1; + + if ($tokens[$return]->isGivenKind(T_WHITESPACE)) { + ++$return; + } + + if (!$tokens[$return]->isGivenKind(T_RETURN)) { + continue; + } + + // Find semicolon of `return` statement + + $semicolon = $tokens->getNextTokenOfKind($return, ['{', ';']); + + if (!$tokens[$semicolon]->equals(';')) { + continue; + } + + // Find closing brace + // Abort if there is more than whitespace between semicolon and closing brace + + $braceClose = $semicolon + 1; + + if ($tokens[$braceClose]->isGivenKind(T_WHITESPACE)) { + ++$braceClose; + } + + if (!$tokens[$braceClose]->equals('}')) { + continue; + } + + // Abort if the `return` statement is multilined + + if ($this->isMultilined($tokens, $return, $semicolon)) { + continue; + } + + // Transform the function to an arrow function + + $this->transform($tokens, $index, $useStart, $useEnd, $braceOpen, $return, $semicolon, $braceClose); + } + } + + private function isMultilined(Tokens $tokens, int $start, int $end): bool + { + for ($i = $start; $i < $end; ++$i) { + if (str_contains($tokens[$i]->getContent(), "\n")) { + return true; + } + } + + return false; + } + + private function transform(Tokens $tokens, int $index, ?int $useStart, ?int $useEnd, int $braceOpen, int $return, int $semicolon, int $braceClose): void + { + $tokensToInsert = [new Token([T_DOUBLE_ARROW, '=>'])]; + + if ($tokens->getNextMeaningfulToken($return) === $semicolon) { + $tokensToInsert[] = new Token([T_WHITESPACE, ' ']); + $tokensToInsert[] = new Token([T_STRING, 'null']); + } + + $tokens->clearRange($semicolon, $braceClose); + $tokens->clearRange($braceOpen + 1, $return); + $tokens->overrideRange($braceOpen, $braceOpen, $tokensToInsert); + + if (null !== $useStart) { + $tokens->clearRange($useStart, $useEnd); + } + + $tokens[$index] = new Token([T_FN, 'fn']); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/VoidReturnFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/VoidReturnFixer.php new file mode 100644 index 00000000..150931dd --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/VoidReturnFixer.php @@ -0,0 +1,256 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\FunctionNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\DocBlock\Annotation; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Mark Nielsen + */ +final class VoidReturnFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Add `void` return type to functions with missing or empty return statements, but priority is given to `@return` annotations. Requires PHP >= 7.1.', + [ + new CodeSample( + "isTokenKindFound(T_FUNCTION); + } + + public function isRisky(): bool + { + return true; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + // These cause syntax errors. + static $excludedFunctions = [ + [T_STRING, '__clone'], + [T_STRING, '__construct'], + [T_STRING, '__debugInfo'], + [T_STRING, '__destruct'], + [T_STRING, '__isset'], + [T_STRING, '__serialize'], + [T_STRING, '__set_state'], + [T_STRING, '__sleep'], + [T_STRING, '__toString'], + ]; + + for ($index = $tokens->count() - 1; 0 <= $index; --$index) { + if (!$tokens[$index]->isGivenKind(T_FUNCTION)) { + continue; + } + + $functionName = $tokens->getNextMeaningfulToken($index); + if ($tokens[$functionName]->equalsAny($excludedFunctions, false)) { + continue; + } + + $startIndex = $tokens->getNextTokenOfKind($index, ['{', ';']); + + if ($this->hasReturnTypeHint($tokens, $startIndex)) { + continue; + } + + if ($tokens[$startIndex]->equals(';')) { + // No function body defined, fallback to PHPDoc. + if ($this->hasVoidReturnAnnotation($tokens, $index)) { + $this->fixFunctionDefinition($tokens, $startIndex); + } + + continue; + } + + if ($this->hasReturnAnnotation($tokens, $index)) { + continue; + } + + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $startIndex); + + if ($this->hasVoidReturn($tokens, $startIndex, $endIndex)) { + $this->fixFunctionDefinition($tokens, $startIndex); + } + } + } + + /** + * Determine whether there is a non-void return annotation in the function's PHPDoc comment. + * + * @param int $index The index of the function token + */ + private function hasReturnAnnotation(Tokens $tokens, int $index): bool + { + foreach ($this->findReturnAnnotations($tokens, $index) as $return) { + if (['void'] !== $return->getTypes()) { + return true; + } + } + + return false; + } + + /** + * Determine whether there is a void return annotation in the function's PHPDoc comment. + * + * @param int $index The index of the function token + */ + private function hasVoidReturnAnnotation(Tokens $tokens, int $index): bool + { + foreach ($this->findReturnAnnotations($tokens, $index) as $return) { + if (['void'] === $return->getTypes()) { + return true; + } + } + + return false; + } + + /** + * Determine whether the function already has a return type hint. + * + * @param int $index The index of the end of the function definition line, EG at { or ; + */ + private function hasReturnTypeHint(Tokens $tokens, int $index): bool + { + $endFuncIndex = $tokens->getPrevTokenOfKind($index, [')']); + $nextIndex = $tokens->getNextMeaningfulToken($endFuncIndex); + + return $tokens[$nextIndex]->isGivenKind(CT::T_TYPE_COLON); + } + + /** + * Determine whether the function has a void return. + * + * @param int $startIndex Start of function body + * @param int $endIndex End of function body + */ + private function hasVoidReturn(Tokens $tokens, int $startIndex, int $endIndex): bool + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + + for ($i = $startIndex; $i < $endIndex; ++$i) { + if ( + // skip anonymous classes + ($tokens[$i]->isGivenKind(T_CLASS) && $tokensAnalyzer->isAnonymousClass($i)) + // skip lambda functions + || ($tokens[$i]->isGivenKind(T_FUNCTION) && $tokensAnalyzer->isLambda($i)) + ) { + $i = $tokens->getNextTokenOfKind($i, ['{']); + $i = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $i); + + continue; + } + + if ($tokens[$i]->isGivenKind([T_YIELD, T_YIELD_FROM])) { + return false; // Generators cannot return void. + } + + if (!$tokens[$i]->isGivenKind(T_RETURN)) { + continue; + } + + $i = $tokens->getNextMeaningfulToken($i); + if (!$tokens[$i]->equals(';')) { + return false; + } + } + + return true; + } + + /** + * @param int $index The index of the end of the function definition line, EG at { or ; + */ + private function fixFunctionDefinition(Tokens $tokens, int $index): void + { + $endFuncIndex = $tokens->getPrevTokenOfKind($index, [')']); + $tokens->insertAt($endFuncIndex + 1, [ + new Token([CT::T_TYPE_COLON, ':']), + new Token([T_WHITESPACE, ' ']), + new Token([T_STRING, 'void']), + ]); + } + + /** + * Find all the return annotations in the function's PHPDoc comment. + * + * @param int $index The index of the function token + * + * @return list + */ + private function findReturnAnnotations(Tokens $tokens, int $index): array + { + $previousTokens = [ + T_ABSTRACT, + T_FINAL, + T_PRIVATE, + T_PROTECTED, + T_PUBLIC, + T_STATIC, + ]; + + if (\defined('T_ATTRIBUTE')) { // @TODO: drop condition when PHP 8.0+ is required + $previousTokens[] = T_ATTRIBUTE; + } + + do { + $index = $tokens->getPrevNonWhitespace($index); + + if ($tokens[$index]->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) { + $index = $tokens->getPrevTokenOfKind($index, [[T_ATTRIBUTE]]); + } + } while ($tokens[$index]->isGivenKind($previousTokens)); + + if (!$tokens[$index]->isGivenKind(T_DOC_COMMENT)) { + return []; + } + + $doc = new DocBlock($tokens[$index]->getContent()); + + return $doc->getAnnotationsOfType('return'); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/FullyQualifiedStrictTypesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/FullyQualifiedStrictTypesFixer.php new file mode 100644 index 00000000..ca5c14c0 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/FullyQualifiedStrictTypesFixer.php @@ -0,0 +1,894 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Import; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\DocBlock\TypeExpression; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\TypeAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Processor\ImportProcessor; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author VeeWee + * @author Tomas Jadrny + * @author Greg Korba + * @author SpacePossum + * @author Michael Vorisek + */ +final class FullyQualifiedStrictTypesFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface +{ + private const REGEX_CLASS = '(?:\\\\?+'.TypeExpression::REGEX_IDENTIFIER + .'(\\\\'.TypeExpression::REGEX_IDENTIFIER.')*+)'; + + /** + * @var array{ + * const?: list, + * class?: list, + * function?: list + * }|null + */ + private ?array $discoveredSymbols; + + /** + * @var array{ + * const?: array, + * class?: array, + * function?: array + * } + */ + private array $symbolsForImport = []; + + /** + * @var array, array> + */ + private array $reservedIdentifiersByLevel; + + /** @var array */ + private array $cacheUsesLast = []; + + /** @var array */ + private array $cacheUseNameByShortNameLower; + + /** @var array */ + private array $cacheUseShortNameByNameLower; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Removes the leading part of fully qualified symbol references if a given symbol is imported or belongs to the current namespace.', + [ + new CodeSample( + 'baz = $baz; + } + + /** + * @return \Foo\Bar\Baz + */ + public function getBaz() { + return $this->baz; + } + + public function doX(\Foo\Bar $foo, \Exception $e): \Foo\Bar\Baz + { + try {} + catch (\Foo\SomeException $e) {} + } +} +' + ), + new CodeSample( + ' true] + ), + new CodeSample( + ' true] + ), + new CodeSample( + ' true] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before NoSuperfluousPhpdocTagsFixer, OrderedImportsFixer, OrderedInterfacesFixer, StatementIndentationFixer. + * Must run after ClassKeywordFixer, PhpdocToReturnTypeFixer. + */ + public function getPriority(): int + { + return 7; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound([ + CT::T_USE_TRAIT, + ...(\defined('T_ATTRIBUTE') ? [T_ATTRIBUTE] : []), // @TODO: drop condition when PHP 8.0+ is required + T_CATCH, + T_DOUBLE_COLON, + T_DOC_COMMENT, + T_EXTENDS, + T_FUNCTION, + T_IMPLEMENTS, + T_INSTANCEOF, + T_NEW, + T_VARIABLE, + ]); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder( + 'leading_backslash_in_global_namespace', + 'Whether FQCN is prefixed with backslash when that FQCN is used in global namespace context.' + )) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + (new FixerOptionBuilder( + 'import_symbols', + 'Whether FQCNs should be automatically imported.' + )) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + (new FixerOptionBuilder( + 'phpdoc_tags', + 'Collection of PHPDoc annotation tags where FQCNs should be processed. As of now only simple tags with `@tag \F\Q\C\N` format are supported (no complex types).' + )) + ->setAllowedTypes(['array']) + ->setDefault([ + 'param', + 'phpstan-param', + 'phpstan-property', + 'phpstan-property-read', + 'phpstan-property-write', + 'phpstan-return', + 'phpstan-var', + 'property', + 'property-read', + 'property-write', + 'psalm-param', + 'psalm-property', + 'psalm-property-read', + 'psalm-property-write', + 'psalm-return', + 'psalm-var', + 'return', + 'see', + 'throws', + 'var', + ]) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $namespaceUsesAnalyzer = new NamespaceUsesAnalyzer(); + $functionsAnalyzer = new FunctionsAnalyzer(); + + $this->symbolsForImport = []; + + foreach ($tokens->getNamespaceDeclarations() as $namespaceIndex => $namespace) { + $namespace = $tokens->getNamespaceDeclarations()[$namespaceIndex]; + + $namespaceName = $namespace->getFullName(); + $uses = []; + $lastUse = null; + + foreach ($namespaceUsesAnalyzer->getDeclarationsInNamespace($tokens, $namespace, true) as $use) { + if (!$use->isClass()) { + continue; + } + $uses[ltrim($use->getFullName(), '\\')] = $use->getShortName(); + $lastUse = $use; + } + + $indexDiff = 0; + foreach (true === $this->configuration['import_symbols'] ? [true, false] : [false] as $discoverSymbolsPhase) { + $this->discoveredSymbols = $discoverSymbolsPhase ? [] : null; + + $classyKinds = [T_CLASS, T_INTERFACE, T_TRAIT]; + if (\defined('T_ENUM')) { // @TODO: drop condition when PHP 8.1+ is required + $classyKinds[] = T_ENUM; + } + + $openedCurlyBrackets = 0; + $this->reservedIdentifiersByLevel = []; + + for ($index = $namespace->getScopeStartIndex(); $index < $namespace->getScopeEndIndex() + $indexDiff; ++$index) { + $origSize = \count($tokens); + + if ($tokens[$index]->equals('{')) { + ++$openedCurlyBrackets; + } if ($tokens[$index]->equals('}')) { + unset($this->reservedIdentifiersByLevel[$openedCurlyBrackets]); + --$openedCurlyBrackets; + } elseif ($discoverSymbolsPhase && $tokens[$index]->isGivenKind($classyKinds)) { + $this->fixNextName($tokens, $index, $uses, $namespaceName); + } elseif ($tokens[$index]->isGivenKind(T_FUNCTION)) { + $this->fixFunction($functionsAnalyzer, $tokens, $index, $uses, $namespaceName); + } elseif ($tokens[$index]->isGivenKind([T_EXTENDS, T_IMPLEMENTS])) { + $this->fixExtendsImplements($tokens, $index, $uses, $namespaceName); + } elseif ($tokens[$index]->isGivenKind(T_CATCH)) { + $this->fixCatch($tokens, $index, $uses, $namespaceName); + } elseif ($tokens[$index]->isGivenKind(T_DOUBLE_COLON)) { + $this->fixPrevName($tokens, $index, $uses, $namespaceName); + } elseif ($tokens[$index]->isGivenKind([T_INSTANCEOF, T_NEW, CT::T_USE_TRAIT])) { + $this->fixNextName($tokens, $index, $uses, $namespaceName); + } elseif ($tokens[$index]->isGivenKind(T_VARIABLE)) { + $prevIndex = $tokens->getPrevMeaningfulToken($index); + if (null !== $prevIndex && $tokens[$prevIndex]->isGivenKind(T_STRING)) { + $this->fixPrevName($tokens, $index, $uses, $namespaceName); + } + } elseif (\defined('T_ATTRIBUTE') && $tokens[$index]->isGivenKind(T_ATTRIBUTE)) { // @TODO: drop const check when PHP 8.0+ is required + $this->fixNextName($tokens, $index, $uses, $namespaceName); + } elseif ($discoverSymbolsPhase && !\defined('T_ATTRIBUTE') && $tokens[$index]->isComment() && Preg::match('/#\[\s*('.self::REGEX_CLASS.')/', $tokens[$index]->getContent(), $matches)) { // @TODO: drop when PHP 8.0+ is required + $this->determineShortType($matches[1], $uses, $namespaceName); + } elseif ($tokens[$index]->isGivenKind(T_DOC_COMMENT)) { + Preg::matchAll('/\*\h*@(?:psalm-|phpstan-)?(?:template(?:-covariant|-contravariant)?|(?:import-)?type)\h+('.TypeExpression::REGEX_IDENTIFIER.')(?!\S)/i', $tokens[$index]->getContent(), $matches); + foreach ($matches[1] as $reservedIdentifier) { + $this->reservedIdentifiersByLevel[$openedCurlyBrackets + 1][$reservedIdentifier] = true; + } + + $this->fixPhpDoc($tokens, $index, $uses, $namespaceName); + } + + $indexDiff += \count($tokens) - $origSize; + } + + $this->reservedIdentifiersByLevel = []; + + if ($discoverSymbolsPhase) { + $this->setupUsesFromDiscoveredSymbols($uses, $namespaceName); + } + } + + if ([] !== $this->symbolsForImport) { + if (null !== $lastUse) { + $atIndex = $lastUse->getEndIndex() + 1; + } elseif (0 !== $namespace->getEndIndex()) { + $atIndex = $namespace->getEndIndex() + 1; + } else { + $firstTokenIndex = $tokens->getNextMeaningfulToken($namespace->getScopeStartIndex()); + if (null !== $firstTokenIndex && $tokens[$firstTokenIndex]->isGivenKind(T_DECLARE)) { + $atIndex = $tokens->getNextTokenOfKind($firstTokenIndex, [';']) + 1; + } else { + $atIndex = $namespace->getScopeStartIndex() + 1; + } + } + + // Insert all registered FQCNs + $this->createImportProcessor()->insertImports($tokens, $this->symbolsForImport, $atIndex); + + $this->symbolsForImport = []; + } + } + } + + /** + * @param array $uses + */ + private function refreshUsesCache(array $uses): void + { + if ($this->cacheUsesLast === $uses) { + return; + } + + $this->cacheUsesLast = $uses; + $this->cacheUseNameByShortNameLower = []; + $this->cacheUseShortNameByNameLower = []; + foreach ($uses as $useLongName => $useShortName) { + $this->cacheUseNameByShortNameLower[strtolower($useShortName)] = $useLongName; + $this->cacheUseShortNameByNameLower[strtolower($useLongName)] = $useShortName; + } + } + + private function isReservedIdentifier(string $symbol): bool + { + if (str_contains($symbol, '\\')) { // optimization only + return false; + } + + if ((new TypeAnalysis($symbol))->isReservedType()) { + return true; + } + + foreach ($this->reservedIdentifiersByLevel as $reservedIdentifiers) { + if (isset($reservedIdentifiers[$symbol])) { + return true; + } + } + + return false; + } + + /** + * Resolve absolute or relative symbol to normalized FQCN. + * + * @param array $uses + */ + private function resolveSymbol(string $symbol, array $uses, string $namespaceName): string + { + if (str_starts_with($symbol, '\\')) { + return substr($symbol, 1); + } + + if ($this->isReservedIdentifier($symbol)) { + return $symbol; + } + + $this->refreshUsesCache($uses); + + $symbolArr = explode('\\', $symbol, 2); + $shortStartNameLower = strtolower($symbolArr[0]); + if (isset($this->cacheUseNameByShortNameLower[$shortStartNameLower])) { + return $this->cacheUseNameByShortNameLower[$shortStartNameLower].(isset($symbolArr[1]) ? '\\'.$symbolArr[1] : ''); + } + + return ('' !== $namespaceName ? $namespaceName.'\\' : '').$symbol; + } + + /** + * Shorten normalized FQCN as much as possible. + * + * @param array $uses + */ + private function shortenSymbol(string $fqcn, array $uses, string $namespaceName): string + { + if ($this->isReservedIdentifier($fqcn)) { + return $fqcn; + } + + $this->refreshUsesCache($uses); + + $res = null; + + // try to shorten the name using namespace + $iMin = 0; + if (str_starts_with($fqcn, $namespaceName.'\\')) { + $tmpRes = substr($fqcn, \strlen($namespaceName) + 1); + if (!isset($this->cacheUseNameByShortNameLower[strtolower(explode('\\', $tmpRes, 2)[0])]) && !$this->isReservedIdentifier($tmpRes)) { + $res = $tmpRes; + $iMin = substr_count($namespaceName, '\\') + 1; + } + } + + // try to shorten the name using uses + $tmp = $fqcn; + for ($i = substr_count($fqcn, '\\'); $i >= $iMin; --$i) { + if (isset($this->cacheUseShortNameByNameLower[strtolower($tmp)])) { + $tmpRes = $this->cacheUseShortNameByNameLower[strtolower($tmp)].substr($fqcn, \strlen($tmp)); + if (!$this->isReservedIdentifier($tmpRes)) { + $res = $tmpRes; + + break; + } + } + + if ($i > 0) { + $tmp = substr($tmp, 0, strrpos($tmp, '\\')); + } + } + + // shortening is not possible, add leading backslash if needed + if (null === $res) { + $res = $fqcn; + if ('' !== $namespaceName + || true === $this->configuration['leading_backslash_in_global_namespace'] + || isset($this->cacheUseNameByShortNameLower[strtolower(explode('\\', $res, 2)[0])]) + ) { + $res = '\\'.$res; + } + } + + return $res; + } + + /** + * @param array $uses + */ + private function setupUsesFromDiscoveredSymbols(array &$uses, string $namespaceName): void + { + foreach ($this->discoveredSymbols as $kind => $discoveredSymbols) { + $discoveredFqcnByShortNameLower = []; + + if ('' === $namespaceName) { + foreach ($discoveredSymbols as $symbol) { + if (!str_starts_with($symbol, '\\')) { + $shortStartName = explode('\\', ltrim($symbol, '\\'), 2)[0]; + $shortStartNameLower = strtolower($shortStartName); + $discoveredFqcnByShortNameLower[$shortStartNameLower] = $this->resolveSymbol($shortStartName, $uses, $namespaceName); + } + } + } + + foreach ($uses as $useLongName => $useShortName) { + $discoveredFqcnByShortNameLower[strtolower($useShortName)] = $useLongName; + } + + $useByShortNameLower = []; + foreach ($uses as $useShortName) { + $useByShortNameLower[strtolower($useShortName)] = true; + } + + uasort($discoveredSymbols, static function ($a, $b) { + $res = str_starts_with($a, '\\') <=> str_starts_with($b, '\\'); + if (0 !== $res) { + return $res; + } + + return substr_count($a, '\\') <=> substr_count($b, '\\'); + }); + foreach ($discoveredSymbols as $symbol) { + while (true) { + $shortEndNameLower = strtolower(str_contains($symbol, '\\') ? substr($symbol, strrpos($symbol, '\\') + 1) : $symbol); + if (!isset($discoveredFqcnByShortNameLower[$shortEndNameLower])) { + $shortStartNameLower = strtolower(explode('\\', ltrim($symbol, '\\'), 2)[0]); + if (str_starts_with($symbol, '\\') || ('' === $namespaceName && !isset($useByShortNameLower[$shortStartNameLower])) + || !str_contains($symbol, '\\') + ) { + $discoveredFqcnByShortNameLower[$shortEndNameLower] = $this->resolveSymbol($symbol, $uses, $namespaceName); + + break; + } + } + // else short name collision - keep unimported + + if (str_starts_with($symbol, '\\') || '' === $namespaceName || !str_contains($symbol, '\\')) { + break; + } + + $symbol = substr($symbol, 0, strrpos($symbol, '\\')); + } + } + + foreach ($uses as $useLongName => $useShortName) { + $discoveredLongName = $discoveredFqcnByShortNameLower[strtolower($useShortName)] ?? null; + if (strtolower($discoveredLongName) === strtolower($useLongName)) { + unset($discoveredFqcnByShortNameLower[strtolower($useShortName)]); + } + } + + foreach ($discoveredFqcnByShortNameLower as $fqcn) { + $shortenedName = ltrim($this->shortenSymbol($fqcn, [], $namespaceName), '\\'); + if (str_contains($shortenedName, '\\')) { // prevent importing non-namespaced names in global namespace + $shortEndName = str_contains($fqcn, '\\') ? substr($fqcn, strrpos($fqcn, '\\') + 1) : $fqcn; + $uses[$fqcn] = $shortEndName; + $this->symbolsForImport[$kind][$shortEndName] = $fqcn; + } + } + + if (isset($this->symbolsForImport[$kind])) { + ksort($this->symbolsForImport[$kind], SORT_NATURAL); + } + } + } + + /** + * @param array $uses + */ + private function fixFunction(FunctionsAnalyzer $functionsAnalyzer, Tokens $tokens, int $index, array $uses, string $namespaceName): void + { + $arguments = $functionsAnalyzer->getFunctionArguments($tokens, $index); + + foreach ($arguments as $i => $argument) { + $argument = $functionsAnalyzer->getFunctionArguments($tokens, $index)[$i]; + + if ($argument->hasTypeAnalysis()) { + $this->replaceByShortType($tokens, $argument->getTypeAnalysis(), $uses, $namespaceName); + } + } + + $returnTypeAnalysis = $functionsAnalyzer->getFunctionReturnType($tokens, $index); + + if (null !== $returnTypeAnalysis) { + $this->replaceByShortType($tokens, $returnTypeAnalysis, $uses, $namespaceName); + } + } + + /** + * @param array $uses + */ + private function fixPhpDoc(Tokens $tokens, int $index, array $uses, string $namespaceName): void + { + $allowedTags = $this->configuration['phpdoc_tags']; + + if ([] === $allowedTags) { + return; + } + + $phpDoc = $tokens[$index]; + $phpDocContent = $phpDoc->getContent(); + $phpDocContentNew = Preg::replaceCallback('/([*{]\h*@)(\S+)(\h+)('.TypeExpression::REGEX_TYPES.')(?!(?!\})\S)/', function ($matches) use ($allowedTags, $uses, $namespaceName) { + if (!\in_array($matches[2], $allowedTags, true)) { + return $matches[0]; + } + + /** @TODO parse the complex type using TypeExpression and fix all names inside (like `list<\Foo\Bar|'a|b|c'|string>` or `\Foo\Bar[]`) */ + $unsupported = false; + + return $matches[1].$matches[2].$matches[3].implode('|', array_map(function ($v) use ($uses, $namespaceName, &$unsupported) { + if ($unsupported || !Preg::match('/^'.self::REGEX_CLASS.'$/', $v)) { + $unsupported = true; + + return $v; + } + + $shortTokens = $this->determineShortType($v, $uses, $namespaceName); + if (null === $shortTokens) { + return $v; + } + + return implode('', array_map( + static fn (Token $token) => $token->getContent(), + $shortTokens + )); + }, explode('|', $matches[4]))); + }, $phpDocContent); + + if ($phpDocContentNew !== $phpDocContent) { + $tokens[$index] = new Token([T_DOC_COMMENT, $phpDocContentNew]); + } + } + + /** + * @param array $uses + */ + private function fixExtendsImplements(Tokens $tokens, int $index, array $uses, string $namespaceName): void + { + // We handle `extends` and `implements` with similar logic, but we need to exit the loop under different conditions. + $isExtends = $tokens[$index]->equals([T_EXTENDS]); + $index = $tokens->getNextMeaningfulToken($index); + + $typeStartIndex = null; + $typeEndIndex = null; + + while (true) { + if ($tokens[$index]->equalsAny([',', '{', [T_IMPLEMENTS]])) { + if (null !== $typeStartIndex) { + $index += $this->shortenClassIfPossible($tokens, $typeStartIndex, $typeEndIndex, $uses, $namespaceName); + } + $typeStartIndex = null; + + if ($tokens[$index]->equalsAny($isExtends ? [[T_IMPLEMENTS], '{'] : ['{'])) { + break; + } + } else { + if (null === $typeStartIndex) { + $typeStartIndex = $index; + } + $typeEndIndex = $index; + } + + $index = $tokens->getNextMeaningfulToken($index); + } + } + + /** + * @param array $uses + */ + private function fixCatch(Tokens $tokens, int $index, array $uses, string $namespaceName): void + { + $index = $tokens->getNextMeaningfulToken($index); // '(' + $index = $tokens->getNextMeaningfulToken($index); // first part of first exception class to be caught + + $typeStartIndex = null; + $typeEndIndex = null; + + while (true) { + if ($tokens[$index]->equalsAny([')', [T_VARIABLE], [CT::T_TYPE_ALTERNATION]])) { + if (null === $typeStartIndex) { + break; + } + + $index += $this->shortenClassIfPossible($tokens, $typeStartIndex, $typeEndIndex, $uses, $namespaceName); + $typeStartIndex = null; + + if ($tokens[$index]->equals(')')) { + break; + } + } else { + if (null === $typeStartIndex) { + $typeStartIndex = $index; + } + $typeEndIndex = $index; + } + + $index = $tokens->getNextMeaningfulToken($index); + } + } + + /** + * @param array $uses + */ + private function fixPrevName(Tokens $tokens, int $index, array $uses, string $namespaceName): void + { + $typeStartIndex = null; + $typeEndIndex = null; + + while (true) { + $index = $tokens->getPrevMeaningfulToken($index); + if ($tokens[$index]->isObjectOperator()) { + break; + } + + if ($tokens[$index]->equalsAny([[T_STRING], [T_NS_SEPARATOR]])) { + $typeStartIndex = $index; + if (null === $typeEndIndex) { + $typeEndIndex = $index; + } + } else { + if (null !== $typeEndIndex) { + $index += $this->shortenClassIfPossible($tokens, $typeStartIndex, $typeEndIndex, $uses, $namespaceName); + } + + break; + } + } + } + + /** + * @param array $uses + */ + private function fixNextName(Tokens $tokens, int $index, array $uses, string $namespaceName): void + { + $typeStartIndex = null; + $typeEndIndex = null; + + while (true) { + $index = $tokens->getNextMeaningfulToken($index); + + if ($tokens[$index]->equalsAny([[T_STRING], [T_NS_SEPARATOR]])) { + if (null === $typeStartIndex) { + $typeStartIndex = $index; + } + $typeEndIndex = $index; + } else { + if (null !== $typeStartIndex) { + $index += $this->shortenClassIfPossible($tokens, $typeStartIndex, $typeEndIndex, $uses, $namespaceName); + } + + break; + } + } + } + + /** + * @param array $uses + */ + private function shortenClassIfPossible(Tokens $tokens, int $typeStartIndex, int $typeEndIndex, array $uses, string $namespaceName): int + { + $content = $tokens->generatePartialCode($typeStartIndex, $typeEndIndex); + $newTokens = $this->determineShortType($content, $uses, $namespaceName); + if (null === $newTokens) { + return 0; + } + + $tokens->overrideRange($typeStartIndex, $typeEndIndex, $newTokens); + + return \count($newTokens) - ($typeEndIndex - $typeStartIndex) - 1; + } + + /** + * @param array $uses + */ + private function replaceByShortType(Tokens $tokens, TypeAnalysis $type, array $uses, string $namespaceName): void + { + $typeStartIndex = $type->getStartIndex(); + + if ($tokens[$typeStartIndex]->isGivenKind(CT::T_NULLABLE_TYPE)) { + $typeStartIndex = $tokens->getNextMeaningfulToken($typeStartIndex); + } + + $types = $this->getTypes($tokens, $typeStartIndex, $type->getEndIndex()); + + foreach ($types as [$startIndex, $endIndex]) { + $content = $tokens->generatePartialCode($startIndex, $endIndex); + $newTokens = $this->determineShortType($content, $uses, $namespaceName); + if (null !== $newTokens) { + $tokens->overrideRange($startIndex, $endIndex, $newTokens); + } + } + } + + /** + * Determines short type based on FQCN, current namespace and imports (`use` declarations). + * + * @param array $uses + * + * @return null|Token[] + */ + private function determineShortType(string $typeName, array $uses, string $namespaceName): ?array + { + if (null !== $this->discoveredSymbols) { + if (!$this->isReservedIdentifier($typeName)) { + $this->discoveredSymbols['class'][] = $typeName; + } + + return null; + } + + $fqcn = $this->resolveSymbol($typeName, $uses, $namespaceName); + $shortenedType = $this->shortenSymbol($fqcn, $uses, $namespaceName); + if ($shortenedType === $typeName) { + return null; + } + + return $this->namespacedStringToTokens($shortenedType); + } + + /** + * @return iterable + */ + private function getTypes(Tokens $tokens, int $index, int $endIndex): iterable + { + $skipNextYield = false; + $typeStartIndex = $typeEndIndex = null; + while (true) { + if ($tokens[$index]->isGivenKind(CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_OPEN)) { + $index = $tokens->getNextMeaningfulToken($index); + $typeStartIndex = $typeEndIndex = null; + + continue; + } + + if ( + $tokens[$index]->isGivenKind([CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION, CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE]) + || $index > $endIndex + ) { + if (!$skipNextYield && null !== $typeStartIndex) { + $origCount = \count($tokens); + + yield [$typeStartIndex, $typeEndIndex]; + + $endIndex += \count($tokens) - $origCount; + + // type tokens were possibly updated, restart type match + $skipNextYield = true; + $index = $typeEndIndex = $typeStartIndex; + } else { + $skipNextYield = false; + $index = $tokens->getNextMeaningfulToken($index); + $typeStartIndex = $typeEndIndex = null; + } + + if ($index > $endIndex) { + break; + } + + continue; + } + + if (null === $typeStartIndex) { + $typeStartIndex = $index; + } + $typeEndIndex = $index; + + $index = $tokens->getNextMeaningfulToken($index); + } + } + + /** + * @return list + */ + private function namespacedStringToTokens(string $input): array + { + $tokens = []; + + if (str_starts_with($input, '\\')) { + $tokens[] = new Token([T_NS_SEPARATOR, '\\']); + $input = substr($input, 1); + } + + $parts = explode('\\', $input); + foreach ($parts as $index => $part) { + $tokens[] = new Token([T_STRING, $part]); + + if ($index !== \count($parts) - 1) { + $tokens[] = new Token([T_NS_SEPARATOR, '\\']); + } + } + + return $tokens; + } + + /** + * We need to create import processor dynamically (not in costructor), because actual whitespace configuration + * is set later, not when fixer's instance is created. + */ + private function createImportProcessor(): ImportProcessor + { + return new ImportProcessor($this->whitespacesConfig); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/GlobalNamespaceImportFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/GlobalNamespaceImportFixer.php new file mode 100644 index 00000000..9d2e528f --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/GlobalNamespaceImportFixer.php @@ -0,0 +1,708 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Import; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\DocBlock\Annotation; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceUseAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\ClassyAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Processor\ImportProcessor; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Gregor Harlan + */ +final class GlobalNamespaceImportFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface +{ + private ImportProcessor $importProcessor; + + public function __construct() + { + parent::__construct(); + + $this->importProcessor = new ImportProcessor($this->whitespacesConfig); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Imports or fully qualifies global classes/functions/constants.', + [ + new CodeSample( + ' true, 'import_constants' => true, 'import_functions' => true] + ), + new CodeSample( + ' false, 'import_constants' => false, 'import_functions' => false] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before NoUnusedImportsFixer, OrderedImportsFixer, StatementIndentationFixer. + * Must run after NativeConstantInvocationFixer, NativeFunctionInvocationFixer. + */ + public function getPriority(): int + { + return 0; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound([T_DOC_COMMENT, T_NS_SEPARATOR, T_USE]) + && $tokens->isTokenKindFound(T_NAMESPACE) + && 1 === $tokens->countTokenKind(T_NAMESPACE) + && $tokens->isMonolithicPhp(); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $namespaceAnalyses = $tokens->getNamespaceDeclarations(); + + if (1 !== \count($namespaceAnalyses) || $namespaceAnalyses[0]->isGlobalNamespace()) { + return; + } + + $useDeclarations = (new NamespaceUsesAnalyzer())->getDeclarationsFromTokens($tokens); + + $newImports = []; + + if (true === $this->configuration['import_constants']) { + $newImports['const'] = $this->importConstants($tokens, $useDeclarations); + } elseif (false === $this->configuration['import_constants']) { + $this->fullyQualifyConstants($tokens, $useDeclarations); + } + + if (true === $this->configuration['import_functions']) { + $newImports['function'] = $this->importFunctions($tokens, $useDeclarations); + } elseif (false === $this->configuration['import_functions']) { + $this->fullyQualifyFunctions($tokens, $useDeclarations); + } + + if (true === $this->configuration['import_classes']) { + $newImports['class'] = $this->importClasses($tokens, $useDeclarations); + } elseif (false === $this->configuration['import_classes']) { + $this->fullyQualifyClasses($tokens, $useDeclarations); + } + + $newImports = array_filter($newImports); + + if (\count($newImports) > 0) { + if (\count($useDeclarations) > 0) { + $useDeclaration = end($useDeclarations); + $atIndex = $useDeclaration->getEndIndex() + 1; + } else { + $namespace = $tokens->getNamespaceDeclarations()[0]; + $atIndex = $namespace->getEndIndex() + 1; + } + + $this->importProcessor->insertImports($tokens, $newImports, $atIndex); + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('import_constants', 'Whether to import, not import or ignore global constants.')) + ->setDefault(null) + ->setAllowedValues([true, false, null]) + ->getOption(), + (new FixerOptionBuilder('import_functions', 'Whether to import, not import or ignore global functions.')) + ->setDefault(null) + ->setAllowedValues([true, false, null]) + ->getOption(), + (new FixerOptionBuilder('import_classes', 'Whether to import, not import or ignore global classes.')) + ->setDefault(true) + ->setAllowedValues([true, false, null]) + ->getOption(), + ]); + } + + /** + * @param NamespaceUseAnalysis[] $useDeclarations + * + * @return array + */ + private function importConstants(Tokens $tokens, array $useDeclarations): array + { + [$global, $other] = $this->filterUseDeclarations($useDeclarations, static fn (NamespaceUseAnalysis $declaration): bool => $declaration->isConstant(), true); + + // find namespaced const declarations (`const FOO = 1`) + // and add them to the not importable names (already used) + for ($index = 0, $count = $tokens->count(); $index < $count; ++$index) { + $token = $tokens[$index]; + + if ($token->isClassy()) { + $index = $tokens->getNextTokenOfKind($index, ['{']); + $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); + + continue; + } + + if (!$token->isGivenKind(T_CONST)) { + continue; + } + + $index = $tokens->getNextMeaningfulToken($index); + $other[$tokens[$index]->getContent()] = true; + } + + $analyzer = new TokensAnalyzer($tokens); + $indices = []; + + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + $token = $tokens[$index]; + + if (!$token->isGivenKind(T_STRING)) { + continue; + } + + $name = $token->getContent(); + + if (isset($other[$name])) { + continue; + } + + if (!$analyzer->isConstantInvocation($index)) { + continue; + } + + $nsSeparatorIndex = $tokens->getPrevMeaningfulToken($index); + if (!$tokens[$nsSeparatorIndex]->isGivenKind(T_NS_SEPARATOR)) { + if (!isset($global[$name])) { + // found an unqualified constant invocation + // add it to the not importable names (already used) + $other[$name] = true; + } + + continue; + } + + $prevIndex = $tokens->getPrevMeaningfulToken($nsSeparatorIndex); + if ($tokens[$prevIndex]->isGivenKind([CT::T_NAMESPACE_OPERATOR, T_STRING])) { + continue; + } + + $indices[] = $index; + } + + return $this->prepareImports($tokens, $indices, $global, $other, true); + } + + /** + * @param NamespaceUseAnalysis[] $useDeclarations + * + * @return array + */ + private function importFunctions(Tokens $tokens, array $useDeclarations): array + { + [$global, $other] = $this->filterUseDeclarations($useDeclarations, static fn (NamespaceUseAnalysis $declaration): bool => $declaration->isFunction(), false); + + // find function declarations + // and add them to the not importable names (already used) + foreach ($this->findFunctionDeclarations($tokens, 0, $tokens->count() - 1) as $name) { + $other[strtolower($name)] = true; + } + + $analyzer = new FunctionsAnalyzer(); + $indices = []; + + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + $token = $tokens[$index]; + + if (!$token->isGivenKind(T_STRING)) { + continue; + } + + $name = strtolower($token->getContent()); + + if (isset($other[$name])) { + continue; + } + + if (!$analyzer->isGlobalFunctionCall($tokens, $index)) { + continue; + } + + $nsSeparatorIndex = $tokens->getPrevMeaningfulToken($index); + if (!$tokens[$nsSeparatorIndex]->isGivenKind(T_NS_SEPARATOR)) { + if (!isset($global[$name])) { + $other[$name] = true; + } + + continue; + } + + $indices[] = $index; + } + + return $this->prepareImports($tokens, $indices, $global, $other, false); + } + + /** + * @param NamespaceUseAnalysis[] $useDeclarations + * + * @return array + */ + private function importClasses(Tokens $tokens, array $useDeclarations): array + { + [$global, $other] = $this->filterUseDeclarations($useDeclarations, static fn (NamespaceUseAnalysis $declaration): bool => $declaration->isClass(), false); + + /** @var DocBlock[] $docBlocks */ + $docBlocks = []; + + // find class declarations and class usages in docblocks + // and add them to the not importable names (already used) + for ($index = 0, $count = $tokens->count(); $index < $count; ++$index) { + $token = $tokens[$index]; + + if ($token->isGivenKind(T_DOC_COMMENT)) { + $docBlocks[$index] = new DocBlock($token->getContent()); + + $this->traverseDocBlockTypes($docBlocks[$index], static function (string $type) use ($global, &$other): void { + if (str_contains($type, '\\')) { + return; + } + + $name = strtolower($type); + + if (!isset($global[$name])) { + $other[$name] = true; + } + }); + } + + if (!$token->isClassy()) { + continue; + } + + $index = $tokens->getNextMeaningfulToken($index); + + if ($tokens[$index]->isGivenKind(T_STRING)) { + $other[strtolower($tokens[$index]->getContent())] = true; + } + } + + $analyzer = new ClassyAnalyzer(); + $indices = []; + + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + $token = $tokens[$index]; + + if (!$token->isGivenKind(T_STRING)) { + continue; + } + + $name = strtolower($token->getContent()); + + if (isset($other[$name])) { + continue; + } + + if (!$analyzer->isClassyInvocation($tokens, $index)) { + continue; + } + + $nsSeparatorIndex = $tokens->getPrevMeaningfulToken($index); + if (!$tokens[$nsSeparatorIndex]->isGivenKind(T_NS_SEPARATOR)) { + if (!isset($global[$name])) { + $other[$name] = true; + } + + continue; + } + + if ($tokens[$tokens->getPrevMeaningfulToken($nsSeparatorIndex)]->isGivenKind([CT::T_NAMESPACE_OPERATOR, T_STRING])) { + continue; + } + + $indices[] = $index; + } + + $imports = []; + + foreach ($docBlocks as $index => $docBlock) { + $changed = $this->traverseDocBlockTypes($docBlock, static function (string $type) use ($global, $other, &$imports): string { + if ('\\' !== $type[0]) { + return $type; + } + + $name = substr($type, 1); + $checkName = strtolower($name); + + if (str_contains($checkName, '\\') || isset($other[$checkName])) { + return $type; + } + + if (isset($global[$checkName])) { + return \is_string($global[$checkName]) ? $global[$checkName] : $name; + } + + $imports[$checkName] = $name; + + return $name; + }); + + if ($changed) { + $tokens[$index] = new Token([T_DOC_COMMENT, $docBlock->getContent()]); + } + } + + return array_merge($imports, $this->prepareImports($tokens, $indices, $global, $other, false)); + } + + /** + * Removes the leading slash at the given indices (when the name is not already used). + * + * @param int[] $indices + * @param array $global + * @param array $other + * + * @return array array keys contain the names that must be imported + */ + private function prepareImports(Tokens $tokens, array $indices, array $global, array $other, bool $caseSensitive): array + { + $imports = []; + + foreach ($indices as $index) { + $name = $tokens[$index]->getContent(); + $checkName = $caseSensitive ? $name : strtolower($name); + + if (isset($other[$checkName])) { + continue; + } + + if (!isset($global[$checkName])) { + $imports[$checkName] = $name; + } elseif (\is_string($global[$checkName])) { + $tokens[$index] = new Token([T_STRING, $global[$checkName]]); + } + + $tokens->clearAt($tokens->getPrevMeaningfulToken($index)); + } + + return $imports; + } + + /** + * @param NamespaceUseAnalysis[] $useDeclarations + */ + private function fullyQualifyConstants(Tokens $tokens, array $useDeclarations): void + { + if (!$tokens->isTokenKindFound(CT::T_CONST_IMPORT)) { + return; + } + + [$global] = $this->filterUseDeclarations($useDeclarations, static fn (NamespaceUseAnalysis $declaration): bool => $declaration->isConstant() && !$declaration->isAliased(), true); + + if ([] === $global) { + return; + } + + $analyzer = new TokensAnalyzer($tokens); + + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + $token = $tokens[$index]; + + if (!$token->isGivenKind(T_STRING)) { + continue; + } + + if (!isset($global[$token->getContent()])) { + continue; + } + + if ($tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_NS_SEPARATOR)) { + continue; + } + + if (!$analyzer->isConstantInvocation($index)) { + continue; + } + + $tokens->insertAt($index, new Token([T_NS_SEPARATOR, '\\'])); + } + } + + /** + * @param NamespaceUseAnalysis[] $useDeclarations + */ + private function fullyQualifyFunctions(Tokens $tokens, array $useDeclarations): void + { + if (!$tokens->isTokenKindFound(CT::T_FUNCTION_IMPORT)) { + return; + } + + [$global] = $this->filterUseDeclarations($useDeclarations, static fn (NamespaceUseAnalysis $declaration): bool => $declaration->isFunction() && !$declaration->isAliased(), false); + + if ([] === $global) { + return; + } + + $analyzer = new FunctionsAnalyzer(); + + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + $token = $tokens[$index]; + + if (!$token->isGivenKind(T_STRING)) { + continue; + } + + if (!isset($global[strtolower($token->getContent())])) { + continue; + } + + if ($tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_NS_SEPARATOR)) { + continue; + } + + if (!$analyzer->isGlobalFunctionCall($tokens, $index)) { + continue; + } + + $tokens->insertAt($index, new Token([T_NS_SEPARATOR, '\\'])); + } + } + + /** + * @param NamespaceUseAnalysis[] $useDeclarations + */ + private function fullyQualifyClasses(Tokens $tokens, array $useDeclarations): void + { + if (!$tokens->isTokenKindFound(T_USE)) { + return; + } + + [$global] = $this->filterUseDeclarations($useDeclarations, static fn (NamespaceUseAnalysis $declaration): bool => $declaration->isClass() && !$declaration->isAliased(), false); + + if ([] === $global) { + return; + } + + $analyzer = new ClassyAnalyzer(); + + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + $token = $tokens[$index]; + + if ($token->isGivenKind(T_DOC_COMMENT)) { + $doc = new DocBlock($token->getContent()); + + $changed = $this->traverseDocBlockTypes($doc, static function (string $type) use ($global): string { + if (!isset($global[strtolower($type)])) { + return $type; + } + + return '\\'.$type; + }); + + if ($changed) { + $tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]); + } + + continue; + } + + if (!$token->isGivenKind(T_STRING)) { + continue; + } + + if (!isset($global[strtolower($token->getContent())])) { + continue; + } + + if ($tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_NS_SEPARATOR)) { + continue; + } + + if (!$analyzer->isClassyInvocation($tokens, $index)) { + continue; + } + + $tokens->insertAt($index, new Token([T_NS_SEPARATOR, '\\'])); + } + } + + /** + * @param NamespaceUseAnalysis[] $declarations + * + * @return array{0: array, 1: array} + */ + private function filterUseDeclarations(array $declarations, callable $callback, bool $caseSensitive): array + { + $global = []; + $other = []; + + foreach ($declarations as $declaration) { + if (!$callback($declaration)) { + continue; + } + + $fullName = ltrim($declaration->getFullName(), '\\'); + + if (str_contains($fullName, '\\')) { + $name = $caseSensitive ? $declaration->getShortName() : strtolower($declaration->getShortName()); + $other[$name] = true; + + continue; + } + + $checkName = $caseSensitive ? $fullName : strtolower($fullName); + $alias = $declaration->getShortName(); + $global[$checkName] = $alias === $fullName ? true : $alias; + } + + return [$global, $other]; + } + + /** + * @return iterable + */ + private function findFunctionDeclarations(Tokens $tokens, int $start, int $end): iterable + { + for ($index = $start; $index <= $end; ++$index) { + $token = $tokens[$index]; + + if ($token->isClassy()) { + $classStart = $tokens->getNextTokenOfKind($index, ['{']); + $classEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classStart); + + for ($index = $classStart; $index <= $classEnd; ++$index) { + if (!$tokens[$index]->isGivenKind(T_FUNCTION)) { + continue; + } + + $methodStart = $tokens->getNextTokenOfKind($index, ['{', ';']); + + if ($tokens[$methodStart]->equals(';')) { + $index = $methodStart; + + continue; + } + + $methodEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $methodStart); + + foreach ($this->findFunctionDeclarations($tokens, $methodStart, $methodEnd) as $function) { + yield $function; + } + + $index = $methodEnd; + } + + continue; + } + + if (!$token->isGivenKind(T_FUNCTION)) { + continue; + } + + $index = $tokens->getNextMeaningfulToken($index); + + if ($tokens[$index]->isGivenKind(CT::T_RETURN_REF)) { + $index = $tokens->getNextMeaningfulToken($index); + } + + if ($tokens[$index]->isGivenKind(T_STRING)) { + yield $tokens[$index]->getContent(); + } + } + } + + private function traverseDocBlockTypes(DocBlock $doc, callable $callback): bool + { + $annotations = $doc->getAnnotationsOfType(Annotation::getTagsWithTypes()); + + if (0 === \count($annotations)) { + return false; + } + + $changed = false; + + foreach ($annotations as $annotation) { + $types = $new = $annotation->getTypes(); + + foreach ($types as $i => $fullType) { + $newFullType = $fullType; + + Preg::matchAll('/[\\\\\w]+(?![\\\\\w:])/', $fullType, $matches, PREG_OFFSET_CAPTURE); + + foreach (array_reverse($matches[0]) as [$type, $offset]) { + $newType = $callback($type); + + if (null !== $newType && $type !== $newType) { + $newFullType = substr_replace($newFullType, $newType, $offset, \strlen($type)); + } + } + + $new[$i] = $newFullType; + } + + if ($types !== $new) { + $annotation->setTypes($new); + $changed = true; + } + } + + return $changed; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/GroupImportFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/GroupImportFixer.php new file mode 100644 index 00000000..bc506ea7 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/GroupImportFixer.php @@ -0,0 +1,268 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Import; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceUseAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Volodymyr Kupriienko + */ +final class GroupImportFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'There MUST be group use for the same namespaces.', + [ + new CodeSample( + "isTokenKindFound(T_USE); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $useWithSameNamespaces = $this->getSameNamespaces($tokens); + + if ([] === $useWithSameNamespaces) { + return; + } + + $this->removeSingleUseStatements($useWithSameNamespaces, $tokens); + $this->addGroupUseStatements($useWithSameNamespaces, $tokens); + } + + /** + * Gets namespace use analyzers with same namespaces. + * + * @return NamespaceUseAnalysis[] + */ + private function getSameNamespaces(Tokens $tokens): array + { + $useDeclarations = (new NamespaceUsesAnalyzer())->getDeclarationsFromTokens($tokens); + + if (0 === \count($useDeclarations)) { + return []; + } + + $allNamespaceAndType = array_map( + fn (NamespaceUseAnalysis $useDeclaration): string => $this->getNamespaceNameWithSlash($useDeclaration).$useDeclaration->getType(), + $useDeclarations + ); + + $sameNamespaces = array_filter(array_count_values($allNamespaceAndType), static fn (int $count): bool => $count > 1); + $sameNamespaces = array_keys($sameNamespaces); + + $sameNamespaceAnalysis = array_filter($useDeclarations, function (NamespaceUseAnalysis $useDeclaration) use ($sameNamespaces): bool { + $namespaceNameAndType = $this->getNamespaceNameWithSlash($useDeclaration).$useDeclaration->getType(); + + return \in_array($namespaceNameAndType, $sameNamespaces, true); + }); + + usort($sameNamespaceAnalysis, function (NamespaceUseAnalysis $a, NamespaceUseAnalysis $b): int { + $namespaceA = $this->getNamespaceNameWithSlash($a); + $namespaceB = $this->getNamespaceNameWithSlash($b); + + $namespaceDifference = \strlen($namespaceA) <=> \strlen($namespaceB); + + return 0 !== $namespaceDifference ? $namespaceDifference : $a->getFullName() <=> $b->getFullName(); + }); + + return $sameNamespaceAnalysis; + } + + /** + * @param NamespaceUseAnalysis[] $statements + */ + private function removeSingleUseStatements(array $statements, Tokens $tokens): void + { + foreach ($statements as $useDeclaration) { + $index = $useDeclaration->getStartIndex(); + $endIndex = $useDeclaration->getEndIndex(); + + $useStatementTokens = [T_USE, T_WHITESPACE, T_STRING, T_NS_SEPARATOR, T_AS, CT::T_CONST_IMPORT, CT::T_FUNCTION_IMPORT]; + + while ($index !== $endIndex) { + if ($tokens[$index]->isGivenKind($useStatementTokens)) { + $tokens->clearAt($index); + } + + ++$index; + } + + if (isset($tokens[$index]) && $tokens[$index]->equals(';')) { + $tokens->clearAt($index); + } + + ++$index; + + if (isset($tokens[$index]) && $tokens[$index]->isGivenKind(T_WHITESPACE)) { + $tokens->clearAt($index); + } + } + } + + /** + * @param NamespaceUseAnalysis[] $statements + */ + private function addGroupUseStatements(array $statements, Tokens $tokens): void + { + $currentUseDeclaration = null; + $insertIndex = \array_slice($statements, -1)[0]->getEndIndex() + 1; + + foreach ($statements as $index => $useDeclaration) { + if ($this->areDeclarationsDifferent($currentUseDeclaration, $useDeclaration)) { + $currentUseDeclaration = $useDeclaration; + $insertIndex += $this->createNewGroup( + $tokens, + $insertIndex, + $useDeclaration, + $this->getNamespaceNameWithSlash($currentUseDeclaration) + ); + } else { + $newTokens = [ + new Token(','), + new Token([T_WHITESPACE, ' ']), + ]; + + if ($useDeclaration->isAliased()) { + $tokens->insertAt($insertIndex, $newTokens); + $insertIndex += \count($newTokens); + $newTokens = []; + + $insertIndex += $this->insertToGroupUseWithAlias($tokens, $insertIndex, $useDeclaration); + } + + $newTokens[] = new Token([T_STRING, $useDeclaration->getShortName()]); + + if (!isset($statements[$index + 1]) || $this->areDeclarationsDifferent($currentUseDeclaration, $statements[$index + 1])) { + $newTokens[] = new Token([CT::T_GROUP_IMPORT_BRACE_CLOSE, '}']); + $newTokens[] = new Token(';'); + $newTokens[] = new Token([T_WHITESPACE, "\n"]); + } + + $tokens->insertAt($insertIndex, $newTokens); + $insertIndex += \count($newTokens); + } + } + } + + private function getNamespaceNameWithSlash(NamespaceUseAnalysis $useDeclaration): string + { + $position = strrpos($useDeclaration->getFullName(), '\\'); + if (false === $position || 0 === $position) { + return $useDeclaration->getFullName(); + } + + return substr($useDeclaration->getFullName(), 0, $position + 1); + } + + /** + * Insert use with alias to the group. + */ + private function insertToGroupUseWithAlias(Tokens $tokens, int $insertIndex, NamespaceUseAnalysis $useDeclaration): int + { + $newTokens = [ + new Token([T_STRING, substr($useDeclaration->getFullName(), strripos($useDeclaration->getFullName(), '\\') + 1)]), + new Token([T_WHITESPACE, ' ']), + new Token([T_AS, 'as']), + new Token([T_WHITESPACE, ' ']), + ]; + + $tokens->insertAt($insertIndex, $newTokens); + + return \count($newTokens); + } + + /** + * Creates new use statement group. + */ + private function createNewGroup(Tokens $tokens, int $insertIndex, NamespaceUseAnalysis $useDeclaration, string $currentNamespace): int + { + $insertedTokens = 0; + + if (\count($tokens) === $insertIndex) { + $tokens->setSize($insertIndex + 1); + } + + $newTokens = [ + new Token([T_USE, 'use']), + new Token([T_WHITESPACE, ' ']), + ]; + + if ($useDeclaration->isFunction() || $useDeclaration->isConstant()) { + $importStatementParams = $useDeclaration->isFunction() + ? [CT::T_FUNCTION_IMPORT, 'function'] + : [CT::T_CONST_IMPORT, 'const']; + + $newTokens[] = new Token($importStatementParams); + $newTokens[] = new Token([T_WHITESPACE, ' ']); + } + + $namespaceParts = array_filter(explode('\\', $currentNamespace)); + + foreach ($namespaceParts as $part) { + $newTokens[] = new Token([T_STRING, $part]); + $newTokens[] = new Token([T_NS_SEPARATOR, '\\']); + } + + $newTokens[] = new Token([CT::T_GROUP_IMPORT_BRACE_OPEN, '{']); + + $newTokensCount = \count($newTokens); + $tokens->insertAt($insertIndex, $newTokens); + $insertedTokens += $newTokensCount; + + $insertIndex += $newTokensCount; + + if ($useDeclaration->isAliased()) { + $inserted = $this->insertToGroupUseWithAlias($tokens, $insertIndex + 1, $useDeclaration) + 1; + $insertedTokens += $inserted; + $insertIndex += $inserted; + } + + $tokens->insertAt($insertIndex, new Token([T_STRING, $useDeclaration->getShortName()])); + + return ++$insertedTokens; + } + + /** + * Check if namespace use analyses are different. + */ + private function areDeclarationsDifferent(?NamespaceUseAnalysis $analysis1, ?NamespaceUseAnalysis $analysis2): bool + { + if (null === $analysis1 || null === $analysis2) { + return true; + } + + $namespaceName1 = $this->getNamespaceNameWithSlash($analysis1); + $namespaceName2 = $this->getNamespaceNameWithSlash($analysis2); + + return $namespaceName1 !== $namespaceName2 || $analysis1->getType() !== $analysis2->getType(); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/NoLeadingImportSlashFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/NoLeadingImportSlashFixer.php new file mode 100644 index 00000000..b9120518 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/NoLeadingImportSlashFixer.php @@ -0,0 +1,90 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Import; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Carlos Cirello + */ +final class NoLeadingImportSlashFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Remove leading slashes in `use` clauses.', + [new CodeSample("isTokenKindFound(T_USE); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + $usesIndices = $tokensAnalyzer->getImportUseIndexes(); + + foreach ($usesIndices as $idx) { + $nextTokenIdx = $tokens->getNextMeaningfulToken($idx); + $nextToken = $tokens[$nextTokenIdx]; + + if ($nextToken->isGivenKind(T_NS_SEPARATOR)) { + $this->removeLeadingImportSlash($tokens, $nextTokenIdx); + } elseif ($nextToken->isGivenKind([CT::T_FUNCTION_IMPORT, CT::T_CONST_IMPORT])) { + $nextTokenIdx = $tokens->getNextMeaningfulToken($nextTokenIdx); + if ($tokens[$nextTokenIdx]->isGivenKind(T_NS_SEPARATOR)) { + $this->removeLeadingImportSlash($tokens, $nextTokenIdx); + } + } + } + } + + private function removeLeadingImportSlash(Tokens $tokens, int $index): void + { + $previousIndex = $tokens->getPrevNonWhitespace($index); + + if ( + $previousIndex < $index - 1 + || $tokens[$previousIndex]->isComment() + ) { + $tokens->clearAt($index); + + return; + } + + $tokens[$index] = new Token([T_WHITESPACE, ' ']); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/NoUnneededImportAliasFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/NoUnneededImportAliasFixer.php new file mode 100644 index 00000000..2d1532c9 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/NoUnneededImportAliasFixer.php @@ -0,0 +1,88 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Import; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Tokens; + +final class NoUnneededImportAliasFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Imports should not be aliased as the same name.', + [new CodeSample("isAllTokenKindsFound([T_USE, T_AS]); + } + + /** + * {@inheritdoc} + * + * Must run before NoSinglelineWhitespaceBeforeSemicolonsFixer. + */ + public function getPriority(): int + { + return 1; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = \count($tokens) - 1; 0 <= $index; --$index) { + if (!$tokens[$index]->isGivenKind(T_AS)) { + continue; + } + + $aliasIndex = $tokens->getNextMeaningfulToken($index); + + if (!$tokens[$aliasIndex]->isGivenKind(T_STRING)) { + continue; + } + + $importIndex = $tokens->getPrevMeaningfulToken($index); + + if (!$tokens[$importIndex]->isGivenKind(T_STRING)) { + continue; + } + + if ($tokens[$importIndex]->getContent() !== $tokens[$aliasIndex]->getContent()) { + continue; + } + + do { + $importIndex = $tokens->getPrevMeaningfulToken($importIndex); + } while ($tokens[$importIndex]->isGivenKind([T_NS_SEPARATOR, T_STRING, T_AS]) || $tokens[$importIndex]->equals(',')); + + if ($tokens[$importIndex]->isGivenKind([CT::T_FUNCTION_IMPORT, CT::T_CONST_IMPORT])) { + $importIndex = $tokens->getPrevMeaningfulToken($importIndex); + } + + if (!$tokens[$importIndex]->isGivenKind([T_USE, CT::T_GROUP_IMPORT_BRACE_OPEN])) { + continue; + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($aliasIndex); + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/NoUnusedImportsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/NoUnusedImportsFixer.php new file mode 100644 index 00000000..689bbc56 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/NoUnusedImportsFixer.php @@ -0,0 +1,455 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Import; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceUseAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\GotoLabelAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Dariusz Rumiński + */ +final class NoUnusedImportsFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Unused `use` statements must be removed.', + [new CodeSample("isTokenKindFound(T_USE); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $useDeclarations = (new NamespaceUsesAnalyzer())->getDeclarationsFromTokens($tokens, true); + + if (0 === \count($useDeclarations)) { + return; + } + + foreach ($tokens->getNamespaceDeclarations() as $namespace) { + $currentNamespaceUseDeclarations = []; + $currentNamespaceUseDeclarationIndices = []; + + foreach ($useDeclarations as $useDeclaration) { + if ($useDeclaration->getStartIndex() >= $namespace->getScopeStartIndex() && $useDeclaration->getEndIndex() <= $namespace->getScopeEndIndex()) { + $currentNamespaceUseDeclarations[] = $useDeclaration; + $currentNamespaceUseDeclarationIndices[$useDeclaration->getStartIndex()] = $useDeclaration->getEndIndex(); + } + } + + foreach ($currentNamespaceUseDeclarations as $useDeclaration) { + if (!$this->isImportUsed($tokens, $namespace, $useDeclaration, $currentNamespaceUseDeclarationIndices)) { + $this->removeUseDeclaration($tokens, $useDeclaration); + } + } + + $this->removeUsesInSameNamespace($tokens, $currentNamespaceUseDeclarations, $namespace); + } + } + + /** + * @param array $ignoredIndices indices of the use statements themselves that should not be checked as being "used" + */ + private function isImportUsed(Tokens $tokens, NamespaceAnalysis $namespace, NamespaceUseAnalysis $import, array $ignoredIndices): bool + { + $analyzer = new TokensAnalyzer($tokens); + $gotoLabelAnalyzer = new GotoLabelAnalyzer(); + + $tokensNotBeforeFunctionCall = [T_NEW]; + + $attributeIsDefined = \defined('T_ATTRIBUTE'); + + if ($attributeIsDefined) { // @TODO: drop condition when PHP 8.0+ is required + $tokensNotBeforeFunctionCall[] = T_ATTRIBUTE; + } + + $namespaceEndIndex = $namespace->getScopeEndIndex(); + $inAttribute = false; + + for ($index = $namespace->getScopeStartIndex(); $index <= $namespaceEndIndex; ++$index) { + $token = $tokens[$index]; + + if ($attributeIsDefined && $token->isGivenKind(T_ATTRIBUTE)) { + $inAttribute = true; + + continue; + } + + if ($attributeIsDefined && $token->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) { + $inAttribute = false; + + continue; + } + + if (isset($ignoredIndices[$index])) { + $index = $ignoredIndices[$index]; + + continue; + } + + if ($token->isGivenKind(T_STRING)) { + if (0 !== strcasecmp($import->getShortName(), $token->getContent())) { + continue; + } + + $prevMeaningfulToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; + + if ($prevMeaningfulToken->isGivenKind(T_NAMESPACE)) { + $index = $tokens->getNextTokenOfKind($index, [';', '{', [T_CLOSE_TAG]]); + + continue; + } + + if ( + $prevMeaningfulToken->isGivenKind([T_NS_SEPARATOR, T_FUNCTION, T_CONST, T_DOUBLE_COLON]) + || $prevMeaningfulToken->isObjectOperator() + ) { + continue; + } + + if ($inAttribute) { + return true; + } + + $nextMeaningfulIndex = $tokens->getNextMeaningfulToken($index); + + if ($gotoLabelAnalyzer->belongsToGoToLabel($tokens, $nextMeaningfulIndex)) { + continue; + } + + $nextMeaningfulToken = $tokens[$nextMeaningfulIndex]; + + if ($analyzer->isConstantInvocation($index)) { + $type = NamespaceUseAnalysis::TYPE_CONSTANT; + } elseif ($nextMeaningfulToken->equals('(') && !$prevMeaningfulToken->isGivenKind($tokensNotBeforeFunctionCall)) { + $type = NamespaceUseAnalysis::TYPE_FUNCTION; + } else { + $type = NamespaceUseAnalysis::TYPE_CLASS; + } + + if ($import->getType() === $type) { + return true; + } + + continue; + } + + if ($token->isComment() + && Preg::match( + '/(?getShortName().'(?![[:alnum:]])/i', + $token->getContent() + ) + ) { + return true; + } + } + + return false; + } + + private function removeUseDeclaration( + Tokens $tokens, + NamespaceUseAnalysis $useDeclaration, + bool $forceCompleteRemoval = false + ): void { + [$start, $end] = ($useDeclaration->isInMulti() && !$forceCompleteRemoval) + ? [$useDeclaration->getChunkStartIndex(), $useDeclaration->getChunkEndIndex()] + : [$useDeclaration->getStartIndex(), $useDeclaration->getEndIndex()]; + $loopStartIndex = $useDeclaration->isInMulti() || $forceCompleteRemoval ? $end : $end - 1; + + for ($index = $loopStartIndex; $index >= $start; --$index) { + if ($tokens[$index]->isComment()) { + continue; + } + + if (!$tokens[$index]->isWhitespace() || !str_contains($tokens[$index]->getContent(), "\n")) { + $tokens->clearAt($index); + + continue; + } + + // when multi line white space keep the line feed if the previous token is a comment + $prevIndex = $tokens->getPrevNonWhitespace($index); + + if ($tokens[$prevIndex]->isComment()) { + $content = $tokens[$index]->getContent(); + $tokens[$index] = new Token([T_WHITESPACE, substr($content, strrpos($content, "\n"))]); // preserve indent only + } else { + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } + } + + // For multi-use import statements the tokens containing FQN were already removed in the loop above. + // We need to clean up tokens around the ex-chunk to keep the correct syntax and achieve proper formatting. + if (!$forceCompleteRemoval && $useDeclaration->isInMulti()) { + $this->cleanUpAfterImportChunkRemoval($tokens, $useDeclaration); + + return; + } + + if ($tokens[$useDeclaration->getEndIndex()]->equals(';')) { // do not remove `? >` + $tokens->clearAt($useDeclaration->getEndIndex()); + } + + $this->cleanUpSurroundingNewLines($tokens, $useDeclaration); + } + + /** + * @param list $useDeclarations + */ + private function removeUsesInSameNamespace(Tokens $tokens, array $useDeclarations, NamespaceAnalysis $namespaceDeclaration): void + { + $namespace = $namespaceDeclaration->getFullName(); + $nsLength = \strlen($namespace.'\\'); + + foreach ($useDeclarations as $useDeclaration) { + if ($useDeclaration->isAliased()) { + continue; + } + + $useDeclarationFullName = ltrim($useDeclaration->getFullName(), '\\'); + + if (!str_starts_with($useDeclarationFullName, $namespace.'\\')) { + continue; + } + + $partName = substr($useDeclarationFullName, $nsLength); + + if (!str_contains($partName, '\\')) { + $this->removeUseDeclaration($tokens, $useDeclaration); + } + } + } + + private function cleanUpAfterImportChunkRemoval(Tokens $tokens, NamespaceUseAnalysis $useDeclaration): void + { + $beforeChunkIndex = $tokens->getPrevMeaningfulToken($useDeclaration->getChunkStartIndex()); + $afterChunkIndex = $tokens->getNextMeaningfulToken($useDeclaration->getChunkEndIndex()); + $hasNonEmptyTokenBefore = $this->scanForNonEmptyTokensUntilNewLineFound( + $tokens, + $afterChunkIndex, + -1 + ); + $hasNonEmptyTokenAfter = $this->scanForNonEmptyTokensUntilNewLineFound( + $tokens, + $afterChunkIndex, + 1 + ); + + // We don't want to merge consequent new lines with indentation (leading to e.g. `\n \n `), + // so it's safe to merge whitespace only if there is any non-empty token before or after the chunk. + $mergingSurroundingWhitespaceIsSafe = $hasNonEmptyTokenBefore[1] || $hasNonEmptyTokenAfter[1]; + $clearToken = static function (int $index) use ($tokens, $mergingSurroundingWhitespaceIsSafe): void { + if ($mergingSurroundingWhitespaceIsSafe) { + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } else { + $tokens->clearAt($index); + } + }; + + if ($tokens[$afterChunkIndex]->equals(',')) { + $clearToken($afterChunkIndex); + } elseif ($tokens[$beforeChunkIndex]->equals(',')) { + $clearToken($beforeChunkIndex); + } + + // Ensure there's a single space where applicable, otherwise no space (before comma, before closing brace) + for ($index = $beforeChunkIndex; $index <= $afterChunkIndex; ++$index) { + if (null === $tokens[$index]->getId() || !$tokens[$index]->isWhitespace(' ')) { + continue; + } + + $nextTokenIndex = $tokens->getNextMeaningfulToken($index); + if ( + $tokens[$nextTokenIndex]->equals(',') + || $tokens[$nextTokenIndex]->equals(';') + || $tokens[$nextTokenIndex]->isGivenKind([CT::T_GROUP_IMPORT_BRACE_CLOSE]) + ) { + $tokens->clearAt($index); + } else { + $tokens[$index] = new Token([T_WHITESPACE, ' ']); + } + + $prevTokenIndex = $tokens->getPrevMeaningfulToken($index); + if ($tokens[$prevTokenIndex]->isGivenKind([CT::T_GROUP_IMPORT_BRACE_OPEN])) { + $tokens->clearAt($index); + } + } + + $this->removeLineIfEmpty($tokens, $useDeclaration); + $this->removeImportStatementIfEmpty($tokens, $useDeclaration); + } + + private function cleanUpSurroundingNewLines(Tokens $tokens, NamespaceUseAnalysis $useDeclaration): void + { + $prevIndex = $useDeclaration->getStartIndex() - 1; + $prevToken = $tokens[$prevIndex]; + + if ($prevToken->isWhitespace()) { + $content = rtrim($prevToken->getContent(), " \t"); + + $tokens->ensureWhitespaceAtIndex($prevIndex, 0, $content); + + $prevToken = $tokens[$prevIndex]; + } + + if (!isset($tokens[$useDeclaration->getEndIndex() + 1])) { + return; + } + + $nextIndex = $tokens->getNonEmptySibling($useDeclaration->getEndIndex(), 1); + + if (null === $nextIndex) { + return; + } + + $nextToken = $tokens[$nextIndex]; + + if ($nextToken->isWhitespace()) { + $content = Preg::replace( + "#^\r\n|^\n#", + '', + ltrim($nextToken->getContent(), " \t"), + 1 + ); + + $tokens->ensureWhitespaceAtIndex($nextIndex, 0, $content); + + $nextToken = $tokens[$nextIndex]; + } + + if ($prevToken->isWhitespace() && $nextToken->isWhitespace()) { + $content = $prevToken->getContent().$nextToken->getContent(); + + $tokens->ensureWhitespaceAtIndex($nextIndex, 0, $content); + + $tokens->clearAt($prevIndex); + } + } + + private function removeImportStatementIfEmpty(Tokens $tokens, NamespaceUseAnalysis $useDeclaration): void + { + // First we look for empty groups where all chunks were removed (`use Foo\{};`). + // We're only interested in ending brace if its index is between start and end of the import statement. + $endingBraceIndex = $tokens->getPrevTokenOfKind( + $useDeclaration->getEndIndex(), + [[CT::T_GROUP_IMPORT_BRACE_CLOSE]] + ); + + if ($endingBraceIndex > $useDeclaration->getStartIndex()) { + $openingBraceIndex = $tokens->getPrevMeaningfulToken($endingBraceIndex); + + if ($tokens[$openingBraceIndex]->isGivenKind(CT::T_GROUP_IMPORT_BRACE_OPEN)) { + $this->removeUseDeclaration($tokens, $useDeclaration, true); + } + } + + // Second we look for empty groups where all comma-separated chunks were removed (`use;`). + $beforeSemicolonIndex = $tokens->getPrevMeaningfulToken($useDeclaration->getEndIndex()); + if ( + $tokens[$beforeSemicolonIndex]->isGivenKind([T_USE]) + || \in_array($tokens[$beforeSemicolonIndex]->getContent(), ['function', 'const'], true) + ) { + $this->removeUseDeclaration($tokens, $useDeclaration, true); + } + } + + private function removeLineIfEmpty(Tokens $tokens, NamespaceUseAnalysis $useAnalysis): void + { + if (!$useAnalysis->isInMulti()) { + return; + } + + $hasNonEmptyTokenBefore = $this->scanForNonEmptyTokensUntilNewLineFound( + $tokens, + $useAnalysis->getChunkStartIndex(), + -1 + ); + $hasNonEmptyTokenAfter = $this->scanForNonEmptyTokensUntilNewLineFound( + $tokens, + $useAnalysis->getChunkEndIndex(), + 1 + ); + + if ( + \is_int($hasNonEmptyTokenBefore[0]) + && !$hasNonEmptyTokenBefore[1] + && \is_int($hasNonEmptyTokenAfter[0]) + && !$hasNonEmptyTokenAfter[1] + ) { + $tokens->clearRange($hasNonEmptyTokenBefore[0], $hasNonEmptyTokenAfter[0] - 1); + } + } + + /** + * Returns tuple with the index of first token with whitespace containing new line char + * and a flag if any non-empty token was found along the way. + * + * @param -1|1 $direction + * + * @return array{0: null|int, 1: bool} + */ + private function scanForNonEmptyTokensUntilNewLineFound(Tokens $tokens, int $index, int $direction): array + { + $hasNonEmptyToken = false; + $newLineTokenIndex = null; + + // Iterate until we find new line OR we get out of $tokens bounds (next sibling index is `null`). + while (\is_int($index)) { + $index = $tokens->getNonEmptySibling($index, $direction); + + if (null === $index || null === $tokens[$index]->getId()) { + continue; + } + + if (!$tokens[$index]->isWhitespace()) { + $hasNonEmptyToken = true; + } elseif (str_starts_with($tokens[$index]->getContent(), "\n")) { + $newLineTokenIndex = $index; + + break; + } + } + + return [$newLineTokenIndex, $hasNonEmptyToken]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/OrderedImportsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/OrderedImportsFixer.php new file mode 100644 index 00000000..ce71246b --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/OrderedImportsFixer.php @@ -0,0 +1,583 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Import; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; +use PhpCsFixer\Utils; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; + +/** + * @author Sebastiaan Stok + * @author Dariusz Rumiński + * @author Darius Matulionis + * @author Adriano Pilger + * + * @phpstan-type _UseImportInfo array{ + * namespace: non-empty-string, + * startIndex: int, + * endIndex: int, + * importType: self::IMPORT_TYPE_*, + * group: bool, + * } + */ +final class OrderedImportsFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface +{ + /** + * @internal + */ + public const IMPORT_TYPE_CLASS = 'class'; + + /** + * @internal + */ + public const IMPORT_TYPE_CONST = 'const'; + + /** + * @internal + */ + public const IMPORT_TYPE_FUNCTION = 'function'; + + /** + * @internal + */ + public const SORT_ALPHA = 'alpha'; + + /** + * @internal + */ + public const SORT_LENGTH = 'length'; + + /** + * @internal + */ + public const SORT_NONE = 'none'; + + /** + * Array of supported sort types in configuration. + * + * @var string[] + */ + private const SUPPORTED_SORT_TYPES = [self::IMPORT_TYPE_CLASS, self::IMPORT_TYPE_CONST, self::IMPORT_TYPE_FUNCTION]; + + /** + * Array of supported sort algorithms in configuration. + * + * @var string[] + */ + private const SUPPORTED_SORT_ALGORITHMS = [self::SORT_ALPHA, self::SORT_LENGTH, self::SORT_NONE]; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Ordering `use` statements.', + [ + new CodeSample( + " true] + ), + new CodeSample( + ' self::SORT_LENGTH] + ), + new CodeSample( + ' self::SORT_LENGTH, + 'imports_order' => [ + self::IMPORT_TYPE_CONST, + self::IMPORT_TYPE_CLASS, + self::IMPORT_TYPE_FUNCTION, + ], + ] + ), + new CodeSample( + ' self::SORT_ALPHA, + 'imports_order' => [ + self::IMPORT_TYPE_CONST, + self::IMPORT_TYPE_CLASS, + self::IMPORT_TYPE_FUNCTION, + ], + ] + ), + new CodeSample( + ' self::SORT_NONE, + 'imports_order' => [ + self::IMPORT_TYPE_CONST, + self::IMPORT_TYPE_CLASS, + self::IMPORT_TYPE_FUNCTION, + ], + ] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before BlankLineBetweenImportGroupsFixer. + * Must run after FullyQualifiedStrictTypesFixer, GlobalNamespaceImportFixer, NoLeadingImportSlashFixer. + */ + public function getPriority(): int + { + return -30; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_USE); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + $namespacesImports = $tokensAnalyzer->getImportUseIndexes(true); + + foreach (array_reverse($namespacesImports) as $usesPerNamespaceIndices) { + $count = \count($usesPerNamespaceIndices); + + if (0 === $count) { + continue; // nothing to sort + } + + if (1 === $count) { + $this->setNewOrder($tokens, $this->getNewOrder($usesPerNamespaceIndices, $tokens)); + + continue; + } + + $groupUsesOffset = 0; + $groupUses = [$groupUsesOffset => [$usesPerNamespaceIndices[0]]]; + + // if there's some logic between two `use` statements, sort only imports grouped before that logic + for ($index = 0; $index < $count - 1; ++$index) { + $nextGroupUse = $tokens->getNextTokenOfKind($usesPerNamespaceIndices[$index], [';', [T_CLOSE_TAG]]); + + if ($tokens[$nextGroupUse]->isGivenKind(T_CLOSE_TAG)) { + $nextGroupUse = $tokens->getNextTokenOfKind($usesPerNamespaceIndices[$index], [[T_OPEN_TAG]]); + } + + $nextGroupUse = $tokens->getNextMeaningfulToken($nextGroupUse); + + if ($nextGroupUse !== $usesPerNamespaceIndices[$index + 1]) { + $groupUses[++$groupUsesOffset] = []; + } + + $groupUses[$groupUsesOffset][] = $usesPerNamespaceIndices[$index + 1]; + } + + for ($index = $groupUsesOffset; $index >= 0; --$index) { + $this->setNewOrder($tokens, $this->getNewOrder($groupUses[$index], $tokens)); + } + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + $supportedSortTypes = self::SUPPORTED_SORT_TYPES; + + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('sort_algorithm', 'Whether the statements should be sorted alphabetically or by length, or not sorted.')) + ->setAllowedValues(self::SUPPORTED_SORT_ALGORITHMS) + ->setDefault(self::SORT_ALPHA) + ->getOption(), + (new FixerOptionBuilder('imports_order', 'Defines the order of import types.')) + ->setAllowedTypes(['array', 'null']) + ->setAllowedValues([static function (?array $value) use ($supportedSortTypes): bool { + if (null !== $value) { + $missing = array_diff($supportedSortTypes, $value); + if (\count($missing) > 0) { + throw new InvalidOptionsException(sprintf( + 'Missing sort %s %s.', + 1 === \count($missing) ? 'type' : 'types', + Utils::naturalLanguageJoin($missing) + )); + } + + $unknown = array_diff($value, $supportedSortTypes); + if (\count($unknown) > 0) { + throw new InvalidOptionsException(sprintf( + 'Unknown sort %s %s.', + 1 === \count($unknown) ? 'type' : 'types', + Utils::naturalLanguageJoin($unknown) + )); + } + } + + return true; + }]) + ->setDefault(null) // @TODO set to ['class', 'function', 'const'] on 4.0 + ->getOption(), + (new FixerOptionBuilder('case_sensitive', 'Whether the sorting should be case sensitive.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + + /** + * This method is used for sorting the uses in a namespace. + * + * @param _UseImportInfo $first + * @param _UseImportInfo $second + */ + private function sortAlphabetically(array $first, array $second): int + { + // Replace backslashes by spaces before sorting for correct sort order + $firstNamespace = str_replace('\\', ' ', $this->prepareNamespace($first['namespace'])); + $secondNamespace = str_replace('\\', ' ', $this->prepareNamespace($second['namespace'])); + + return true === $this->configuration['case_sensitive'] + ? $firstNamespace <=> $secondNamespace + : strcasecmp($firstNamespace, $secondNamespace); + } + + /** + * This method is used for sorting the uses statements in a namespace by length. + * + * @param _UseImportInfo $first + * @param _UseImportInfo $second + */ + private function sortByLength(array $first, array $second): int + { + $firstNamespace = (self::IMPORT_TYPE_CLASS === $first['importType'] ? '' : $first['importType'].' ').$this->prepareNamespace($first['namespace']); + $secondNamespace = (self::IMPORT_TYPE_CLASS === $second['importType'] ? '' : $second['importType'].' ').$this->prepareNamespace($second['namespace']); + + $firstNamespaceLength = \strlen($firstNamespace); + $secondNamespaceLength = \strlen($secondNamespace); + + if ($firstNamespaceLength === $secondNamespaceLength) { + $sortResult = true === $this->configuration['case_sensitive'] + ? $firstNamespace <=> $secondNamespace + : strcasecmp($firstNamespace, $secondNamespace); + } else { + $sortResult = $firstNamespaceLength > $secondNamespaceLength ? 1 : -1; + } + + return $sortResult; + } + + private function prepareNamespace(string $namespace): string + { + return trim(Preg::replace('%/\*(.*)\*/%s', '', $namespace)); + } + + /** + * @param list $uses + * + * @return array + */ + private function getNewOrder(array $uses, Tokens $tokens): array + { + $indices = []; + $originalIndices = []; + $lineEnding = $this->whitespacesConfig->getLineEnding(); + $usesCount = \count($uses); + + for ($i = 0; $i < $usesCount; ++$i) { + $index = $uses[$i]; + + $startIndex = $tokens->getTokenNotOfKindsSibling($index + 1, 1, [T_WHITESPACE]); + $endIndex = $tokens->getNextTokenOfKind($startIndex, [';', [T_CLOSE_TAG]]); + $previous = $tokens->getPrevMeaningfulToken($endIndex); + + $group = $tokens[$previous]->isGivenKind(CT::T_GROUP_IMPORT_BRACE_CLOSE); + if ($tokens[$startIndex]->isGivenKind(CT::T_CONST_IMPORT)) { + $type = self::IMPORT_TYPE_CONST; + $index = $tokens->getNextNonWhitespace($startIndex); + } elseif ($tokens[$startIndex]->isGivenKind(CT::T_FUNCTION_IMPORT)) { + $type = self::IMPORT_TYPE_FUNCTION; + $index = $tokens->getNextNonWhitespace($startIndex); + } else { + $type = self::IMPORT_TYPE_CLASS; + $index = $startIndex; + } + + $namespaceTokens = []; + + while ($index <= $endIndex) { + $token = $tokens[$index]; + + if ($index === $endIndex || (!$group && $token->equals(','))) { + if ($group && self::SORT_NONE !== $this->configuration['sort_algorithm']) { + // if group import, sort the items within the group definition + + // figure out where the list of namespace parts within the group def. starts + $namespaceTokensCount = \count($namespaceTokens) - 1; + $namespace = ''; + for ($k = 0; $k < $namespaceTokensCount; ++$k) { + if ($namespaceTokens[$k]->isGivenKind(CT::T_GROUP_IMPORT_BRACE_OPEN)) { + $namespace .= '{'; + + break; + } + + $namespace .= $namespaceTokens[$k]->getContent(); + } + + // fetch all parts, split up in an array of strings, move comments to the end + $parts = []; + $firstIndent = ''; + $separator = ', '; + $lastIndent = ''; + $hasGroupTrailingComma = false; + + for ($k1 = $k + 1; $k1 < $namespaceTokensCount; ++$k1) { + $comment = ''; + $namespacePart = ''; + for ($k2 = $k1;; ++$k2) { + if ($namespaceTokens[$k2]->equalsAny([',', [CT::T_GROUP_IMPORT_BRACE_CLOSE]])) { + break; + } + + if ($namespaceTokens[$k2]->isComment()) { + $comment .= $namespaceTokens[$k2]->getContent(); + + continue; + } + + // if there is any line ending inside the group import, it should be indented properly + if ( + '' === $firstIndent + && $namespaceTokens[$k2]->isWhitespace() + && str_contains($namespaceTokens[$k2]->getContent(), $lineEnding) + ) { + $lastIndent = $lineEnding; + $firstIndent = $lineEnding.$this->whitespacesConfig->getIndent(); + $separator = ','.$firstIndent; + } + + $namespacePart .= $namespaceTokens[$k2]->getContent(); + } + + $namespacePart = trim($namespacePart); + if ('' === $namespacePart) { + $hasGroupTrailingComma = true; + + continue; + } + + $comment = trim($comment); + if ('' !== $comment) { + $namespacePart .= ' '.$comment; + } + + $parts[] = $namespacePart; + + $k1 = $k2; + } + + $sortedParts = $parts; + sort($parts); + + // check if the order needs to be updated, otherwise don't touch as we might change valid CS (to other valid CS). + if ($sortedParts === $parts) { + $namespace = Tokens::fromArray($namespaceTokens)->generateCode(); + } else { + $namespace .= $firstIndent.implode($separator, $parts).($hasGroupTrailingComma ? ',' : '').$lastIndent.'}'; + } + } else { + $namespace = Tokens::fromArray($namespaceTokens)->generateCode(); + } + + $indices[$startIndex] = [ + 'namespace' => $namespace, + 'startIndex' => $startIndex, + 'endIndex' => $index - 1, + 'importType' => $type, + 'group' => $group, + ]; + + $originalIndices[] = $startIndex; + + if ($index === $endIndex) { + break; + } + + $namespaceTokens = []; + $nextPartIndex = $tokens->getTokenNotOfKindSibling($index, 1, [',', [T_WHITESPACE]]); + $startIndex = $nextPartIndex; + $index = $nextPartIndex; + + continue; + } + + $namespaceTokens[] = $token; + ++$index; + } + } + + // Is sort types provided, sorting by groups and each group by algorithm + if (null !== $this->configuration['imports_order']) { + // Grouping indices by import type. + $groupedByTypes = []; + + foreach ($indices as $startIndex => $item) { + $groupedByTypes[$item['importType']][$startIndex] = $item; + } + + // Sorting each group by algorithm. + foreach ($groupedByTypes as $type => $groupIndices) { + $groupedByTypes[$type] = $this->sortByAlgorithm($groupIndices); + } + + // Ordering groups + $sortedGroups = []; + + foreach ($this->configuration['imports_order'] as $type) { + if (isset($groupedByTypes[$type]) && [] !== $groupedByTypes[$type]) { + foreach ($groupedByTypes[$type] as $startIndex => $item) { + $sortedGroups[$startIndex] = $item; + } + } + } + + $indices = $sortedGroups; + } else { + // Sorting only by algorithm + $indices = $this->sortByAlgorithm($indices); + } + + $index = -1; + $usesOrder = []; + + // Loop through the index but use original index order + foreach ($indices as $v) { + $usesOrder[$originalIndices[++$index]] = $v; + } + + return $usesOrder; + } + + /** + * @param array $indices + * + * @return array + */ + private function sortByAlgorithm(array $indices): array + { + if (self::SORT_ALPHA === $this->configuration['sort_algorithm']) { + uasort($indices, [$this, 'sortAlphabetically']); + } elseif (self::SORT_LENGTH === $this->configuration['sort_algorithm']) { + uasort($indices, [$this, 'sortByLength']); + } + + return $indices; + } + + /** + * @param array $usesOrder + */ + private function setNewOrder(Tokens $tokens, array $usesOrder): void + { + $mapStartToEnd = []; + + foreach ($usesOrder as $use) { + $mapStartToEnd[$use['startIndex']] = $use['endIndex']; + } + + // Now insert the new tokens, starting from the end + foreach (array_reverse($usesOrder, true) as $index => $use) { + $code = sprintf( + 'getPrevMeaningfulToken($index); + if ($tokens[$prevIndex]->equals(',')) { + $numberOfInitialTokensToClear = 5; // clear `clearRange(0, $numberOfInitialTokensToClear - 1); + $declarationTokens->clearAt(\count($declarationTokens) - 1); // clear `;` + $declarationTokens->clearEmptyTokens(); + + $tokens->overrideRange($index, $mapStartToEnd[$index], $declarationTokens); + + if ($use['group']) { + // a group import must start with `use` and cannot be part of comma separated import list + $prev = $tokens->getPrevMeaningfulToken($index); + if ($tokens[$prev]->equals(',')) { + $tokens[$prev] = new Token(';'); + $tokens->insertAt($prev + 1, new Token([T_USE, 'use'])); + + if (!$tokens[$prev + 2]->isWhitespace()) { + $tokens->insertAt($prev + 2, new Token([T_WHITESPACE, ' '])); + } + } + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/SingleImportPerStatementFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/SingleImportPerStatementFixer.php new file mode 100644 index 00000000..f10e93db --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/SingleImportPerStatementFixer.php @@ -0,0 +1,265 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Import; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\WhitespacesAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * Fixer for rules defined in PSR2 ¶3. + * + * @author Dariusz Rumiński + */ +final class SingleImportPerStatementFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'There MUST be one use keyword per declaration.', + [ + new CodeSample( + ' true] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before MultilineWhitespaceBeforeSemicolonsFixer, NoLeadingImportSlashFixer, NoSinglelineWhitespaceBeforeSemicolonsFixer, NoUnusedImportsFixer, SpaceAfterSemicolonFixer. + */ + public function getPriority(): int + { + return 1; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_USE); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + + foreach (array_reverse($tokensAnalyzer->getImportUseIndexes()) as $index) { + $endIndex = $tokens->getNextTokenOfKind($index, [';', [T_CLOSE_TAG]]); + $groupClose = $tokens->getPrevMeaningfulToken($endIndex); + + if ($tokens[$groupClose]->isGivenKind(CT::T_GROUP_IMPORT_BRACE_CLOSE)) { + if (true === $this->configuration['group_to_single_imports']) { + $this->fixGroupUse($tokens, $index, $endIndex); + } + } else { + $this->fixMultipleUse($tokens, $index, $endIndex); + } + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('group_to_single_imports', 'Whether to change group imports into single imports.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), + ]); + } + + /** + * @return array{string, ?int, int, string} + */ + private function getGroupDeclaration(Tokens $tokens, int $index): array + { + $groupPrefix = ''; + $comment = ''; + $groupOpenIndex = null; + + for ($i = $index + 1;; ++$i) { + if ($tokens[$i]->isGivenKind(CT::T_GROUP_IMPORT_BRACE_OPEN)) { + $groupOpenIndex = $i; + + break; + } + + if ($tokens[$i]->isComment()) { + $comment .= $tokens[$i]->getContent(); + if (!$tokens[$i - 1]->isWhitespace() && !$tokens[$i + 1]->isWhitespace()) { + $groupPrefix .= ' '; + } + + continue; + } + + if ($tokens[$i]->isWhitespace()) { + $groupPrefix .= ' '; + + continue; + } + + $groupPrefix .= $tokens[$i]->getContent(); + } + + return [ + rtrim($groupPrefix), + $groupOpenIndex, + $tokens->findBlockEnd(Tokens::BLOCK_TYPE_GROUP_IMPORT_BRACE, $groupOpenIndex), + $comment, + ]; + } + + /** + * @return list + */ + private function getGroupStatements(Tokens $tokens, string $groupPrefix, int $groupOpenIndex, int $groupCloseIndex, string $comment): array + { + $statements = []; + $statement = $groupPrefix; + + for ($i = $groupOpenIndex + 1; $i <= $groupCloseIndex; ++$i) { + $token = $tokens[$i]; + + if ($token->equals(',') && $tokens[$tokens->getNextMeaningfulToken($i)]->isGivenKind(CT::T_GROUP_IMPORT_BRACE_CLOSE)) { + continue; + } + + if ($token->equalsAny([',', [CT::T_GROUP_IMPORT_BRACE_CLOSE]])) { + $statements[] = 'use'.$statement.';'; + $statement = $groupPrefix; + + continue; + } + + if ($token->isWhitespace()) { + $j = $tokens->getNextMeaningfulToken($i); + + if ($tokens[$j]->isGivenKind(T_AS)) { + $statement .= ' as '; + $i += 2; + } elseif ($tokens[$j]->isGivenKind(CT::T_FUNCTION_IMPORT)) { + $statement = ' function'.$statement; + $i += 2; + } elseif ($tokens[$j]->isGivenKind(CT::T_CONST_IMPORT)) { + $statement = ' const'.$statement; + $i += 2; + } + + if ($token->isWhitespace(" \t") || !str_starts_with($tokens[$i - 1]->getContent(), '//')) { + continue; + } + } + + $statement .= $token->getContent(); + } + + if ('' !== $comment) { + $statements[0] .= ' '.$comment; + } + + return $statements; + } + + private function fixGroupUse(Tokens $tokens, int $index, int $endIndex): void + { + [$groupPrefix, $groupOpenIndex, $groupCloseIndex, $comment] = $this->getGroupDeclaration($tokens, $index); + $statements = $this->getGroupStatements($tokens, $groupPrefix, $groupOpenIndex, $groupCloseIndex, $comment); + + if (\count($statements) < 2) { + return; + } + + $tokens->clearRange($index, $groupCloseIndex); + if ($tokens[$endIndex]->equals(';')) { + $tokens->clearAt($endIndex); + } + + $ending = $this->whitespacesConfig->getLineEnding(); + $importTokens = Tokens::fromCode('clearAt(0); + $importTokens->clearEmptyTokens(); + + $tokens->insertAt($index, $importTokens); + } + + private function fixMultipleUse(Tokens $tokens, int $index, int $endIndex): void + { + $nextTokenIndex = $tokens->getNextMeaningfulToken($index); + + if ($tokens[$nextTokenIndex]->isGivenKind(CT::T_FUNCTION_IMPORT)) { + $leadingTokens = [ + new Token([CT::T_FUNCTION_IMPORT, 'function']), + new Token([T_WHITESPACE, ' ']), + ]; + } elseif ($tokens[$nextTokenIndex]->isGivenKind(CT::T_CONST_IMPORT)) { + $leadingTokens = [ + new Token([CT::T_CONST_IMPORT, 'const']), + new Token([T_WHITESPACE, ' ']), + ]; + } else { + $leadingTokens = []; + } + + $ending = $this->whitespacesConfig->getLineEnding(); + + for ($i = $endIndex - 1; $i > $index; --$i) { + if (!$tokens[$i]->equals(',')) { + continue; + } + + $tokens[$i] = new Token(';'); + $i = $tokens->getNextMeaningfulToken($i); + + $tokens->insertAt($i, new Token([T_USE, 'use'])); + $tokens->insertAt($i + 1, new Token([T_WHITESPACE, ' '])); + + foreach ($leadingTokens as $offset => $leadingToken) { + $tokens->insertAt($i + 2 + $offset, clone $leadingTokens[$offset]); + } + + $indent = WhitespacesAnalyzer::detectIndent($tokens, $index); + + if ($tokens[$i - 1]->isWhitespace()) { + $tokens[$i - 1] = new Token([T_WHITESPACE, $ending.$indent]); + } elseif (!str_contains($tokens[$i - 1]->getContent(), "\n")) { + $tokens->insertAt($i, new Token([T_WHITESPACE, $ending.$indent])); + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/SingleLineAfterImportsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/SingleLineAfterImportsFixer.php new file mode 100644 index 00000000..b8b97797 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/SingleLineAfterImportsFixer.php @@ -0,0 +1,152 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Import; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; +use PhpCsFixer\Utils; + +/** + * Fixer for rules defined in PSR2 ¶3. + * + * @author Ceeram + * @author Graham Campbell + */ +final class SingleLineAfterImportsFixer extends AbstractFixer implements WhitespacesAwareFixerInterface +{ + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_USE); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Each namespace use MUST go on its own line and there MUST be one blank line after the use statements block.', + [ + new CodeSample( + 'whitespacesConfig->getLineEnding(); + $tokensAnalyzer = new TokensAnalyzer($tokens); + + $added = 0; + foreach ($tokensAnalyzer->getImportUseIndexes() as $index) { + $index += $added; + $indent = ''; + + // if previous line ends with comment and current line starts with whitespace, use current indent + if ($tokens[$index - 1]->isWhitespace(" \t") && $tokens[$index - 2]->isGivenKind(T_COMMENT)) { + $indent = $tokens[$index - 1]->getContent(); + } elseif ($tokens[$index - 1]->isWhitespace()) { + $indent = Utils::calculateTrailingWhitespaceIndent($tokens[$index - 1]); + } + + $semicolonIndex = $tokens->getNextTokenOfKind($index, [';', [T_CLOSE_TAG]]); // Handle insert index for inline T_COMMENT with whitespace after semicolon + $insertIndex = $semicolonIndex; + + if ($tokens[$semicolonIndex]->isGivenKind(T_CLOSE_TAG)) { + if ($tokens[$insertIndex - 1]->isWhitespace()) { + --$insertIndex; + } + + $tokens->insertAt($insertIndex, new Token(';')); + ++$added; + } + + if ($semicolonIndex === \count($tokens) - 1) { + $tokens->insertAt($insertIndex + 1, new Token([T_WHITESPACE, $ending.$ending.$indent])); + ++$added; + } else { + $newline = $ending; + $tokens[$semicolonIndex]->isGivenKind(T_CLOSE_TAG) ? --$insertIndex : ++$insertIndex; + if ($tokens[$insertIndex]->isWhitespace(" \t") && $tokens[$insertIndex + 1]->isComment()) { + ++$insertIndex; + } + + // Increment insert index for inline T_COMMENT or T_DOC_COMMENT + if ($tokens[$insertIndex]->isComment()) { + ++$insertIndex; + } + + $afterSemicolon = $tokens->getNextMeaningfulToken($semicolonIndex); + if (null === $afterSemicolon || !$tokens[$afterSemicolon]->isGivenKind(T_USE)) { + $newline .= $ending; + } + + if ($tokens[$insertIndex]->isWhitespace()) { + $nextToken = $tokens[$insertIndex]; + if (2 === substr_count($nextToken->getContent(), "\n")) { + continue; + } + $nextMeaningfulAfterUseIndex = $tokens->getNextMeaningfulToken($insertIndex); + if (null !== $nextMeaningfulAfterUseIndex && $tokens[$nextMeaningfulAfterUseIndex]->isGivenKind(T_USE)) { + if (substr_count($nextToken->getContent(), "\n") < 1) { + $tokens[$insertIndex] = new Token([T_WHITESPACE, $newline.$indent.ltrim($nextToken->getContent())]); + } + } else { + $tokens[$insertIndex] = new Token([T_WHITESPACE, $newline.$indent.ltrim($nextToken->getContent())]); + } + } else { + $tokens->insertAt($insertIndex, new Token([T_WHITESPACE, $newline.$indent])); + ++$added; + } + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Indentation.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Indentation.php new file mode 100644 index 00000000..0e370f20 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Indentation.php @@ -0,0 +1,92 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer; + +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @internal + */ +trait Indentation +{ + private function getLineIndentation(Tokens $tokens, int $index): string + { + $newlineTokenIndex = $this->getPreviousNewlineTokenIndex($tokens, $index); + + if (null === $newlineTokenIndex) { + return ''; + } + + return $this->extractIndent($this->computeNewLineContent($tokens, $newlineTokenIndex)); + } + + private function extractIndent(string $content): string + { + if (Preg::match('/\R(\h*)[^\r\n]*$/D', $content, $matches)) { + return $matches[1]; + } + + return ''; + } + + private function getPreviousNewlineTokenIndex(Tokens $tokens, int $index): ?int + { + while ($index > 0) { + $index = $tokens->getPrevTokenOfKind($index, [[T_WHITESPACE], [T_INLINE_HTML]]); + + if (null === $index) { + break; + } + + if ($this->isNewLineToken($tokens, $index)) { + return $index; + } + } + + return null; + } + + private function computeNewLineContent(Tokens $tokens, int $index): string + { + $content = $tokens[$index]->getContent(); + + if (0 !== $index && $tokens[$index - 1]->equalsAny([[T_OPEN_TAG], [T_CLOSE_TAG]])) { + $content = Preg::replace('/\S/', '', $tokens[$index - 1]->getContent()).$content; + } + + return $content; + } + + private function isNewLineToken(Tokens $tokens, int $index): bool + { + $token = $tokens[$index]; + + if ( + $token->isGivenKind(T_OPEN_TAG) + && isset($tokens[$index + 1]) + && !$tokens[$index + 1]->isWhitespace() + && Preg::match('/\R/', $token->getContent()) + ) { + return true; + } + + if (!$tokens[$index]->isGivenKind([T_WHITESPACE, T_INLINE_HTML])) { + return false; + } + + return Preg::match('/\R/', $this->computeNewLineContent($tokens, $index)); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ClassKeywordFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ClassKeywordFixer.php new file mode 100644 index 00000000..b171e56b --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ClassKeywordFixer.php @@ -0,0 +1,100 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\LanguageConstruct; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ExperimentalFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class ClassKeywordFixer extends AbstractFixer implements ExperimentalFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Converts FQCN strings to `*::class` keywords.', + [ + new CodeSample( + 'count() - 1; $index >= 0; --$index) { + $token = $tokens[$index]; + + if ($token->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) { + $name = substr($token->getContent(), 1, -1); + $name = ltrim($name, '\\'); + $name = str_replace('\\\\', '\\', $name); + + if ($this->exists($name)) { + $substitution = Tokens::fromCode("clearRange(0, 2); + $substitution->clearAt($substitution->getSize() - 1); + $substitution->clearEmptyTokens(); + + $tokens->clearAt($index); + $tokens->insertAt($index, $substitution); + } + } + } + } + + private function exists(string $name): bool + { + if (class_exists($name) || interface_exists($name) || trait_exists($name)) { + $rc = new \ReflectionClass($name); + + return $rc->getName() === $name; + } + + return false; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ClassKeywordRemoveFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ClassKeywordRemoveFixer.php new file mode 100644 index 00000000..2c976703 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ClassKeywordRemoveFixer.php @@ -0,0 +1,234 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\LanguageConstruct; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @deprecated + * + * @author Sullivan Senechal + */ +final class ClassKeywordRemoveFixer extends AbstractFixer implements DeprecatedFixerInterface +{ + /** + * @var string[] + */ + private array $imports = []; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Converts `::class` keywords to FQCN strings.', + [ + new CodeSample( + 'isTokenKindFound(CT::T_CLASS_CONSTANT); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $previousNamespaceScopeEndIndex = 0; + foreach ($tokens->getNamespaceDeclarations() as $declaration) { + $this->replaceClassKeywordsSection($tokens, '', $previousNamespaceScopeEndIndex, $declaration->getStartIndex()); + $this->replaceClassKeywordsSection($tokens, $declaration->getFullName(), $declaration->getStartIndex(), $declaration->getScopeEndIndex()); + $previousNamespaceScopeEndIndex = $declaration->getScopeEndIndex(); + } + + $this->replaceClassKeywordsSection($tokens, '', $previousNamespaceScopeEndIndex, $tokens->count() - 1); + } + + private function storeImports(Tokens $tokens, int $startIndex, int $endIndex): void + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + $this->imports = []; + + /** @var int $index */ + foreach ($tokensAnalyzer->getImportUseIndexes() as $index) { + if ($index < $startIndex || $index > $endIndex) { + continue; + } + + $import = ''; + while ($index = $tokens->getNextMeaningfulToken($index)) { + if ($tokens[$index]->equalsAny([';', [CT::T_GROUP_IMPORT_BRACE_OPEN]]) || $tokens[$index]->isGivenKind(T_AS)) { + break; + } + + $import .= $tokens[$index]->getContent(); + } + + // Imports group (PHP 7 spec) + if ($tokens[$index]->isGivenKind(CT::T_GROUP_IMPORT_BRACE_OPEN)) { + $groupEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_GROUP_IMPORT_BRACE, $index); + $groupImports = array_map( + static fn (string $import): string => trim($import), + explode(',', $tokens->generatePartialCode($index + 1, $groupEndIndex - 1)) + ); + foreach ($groupImports as $groupImport) { + $groupImportParts = array_map(static fn (string $import): string => trim($import), explode(' as ', $groupImport)); + if (2 === \count($groupImportParts)) { + $this->imports[$groupImportParts[1]] = $import.$groupImportParts[0]; + } else { + $this->imports[] = $import.$groupImport; + } + } + } elseif ($tokens[$index]->isGivenKind(T_AS)) { + $aliasIndex = $tokens->getNextMeaningfulToken($index); + $alias = $tokens[$aliasIndex]->getContent(); + $this->imports[$alias] = $import; + } else { + $this->imports[] = $import; + } + } + } + + private function replaceClassKeywordsSection(Tokens $tokens, string $namespace, int $startIndex, int $endIndex): void + { + if ($endIndex - $startIndex < 3) { + return; + } + + $this->storeImports($tokens, $startIndex, $endIndex); + + $ctClassTokens = $tokens->findGivenKind(CT::T_CLASS_CONSTANT, $startIndex, $endIndex); + foreach (array_reverse(array_keys($ctClassTokens)) as $classIndex) { + $this->replaceClassKeyword($tokens, $namespace, $classIndex); + } + } + + private function replaceClassKeyword(Tokens $tokens, string $namespacePrefix, int $classIndex): void + { + $classEndIndex = $tokens->getPrevMeaningfulToken($classIndex); + $classEndIndex = $tokens->getPrevMeaningfulToken($classEndIndex); + + if (!$tokens[$classEndIndex]->isGivenKind(T_STRING)) { + return; + } + + if ($tokens[$classEndIndex]->equalsAny([[T_STRING, 'self'], [T_STATIC, 'static'], [T_STRING, 'parent']], false)) { + return; + } + + $classBeginIndex = $classEndIndex; + while (true) { + $prev = $tokens->getPrevMeaningfulToken($classBeginIndex); + if (!$tokens[$prev]->isGivenKind([T_NS_SEPARATOR, T_STRING])) { + break; + } + + $classBeginIndex = $prev; + } + + $classString = $tokens->generatePartialCode( + $tokens[$classBeginIndex]->isGivenKind(T_NS_SEPARATOR) + ? $tokens->getNextMeaningfulToken($classBeginIndex) + : $classBeginIndex, + $classEndIndex + ); + + $classImport = false; + if ($tokens[$classBeginIndex]->isGivenKind(T_NS_SEPARATOR)) { + $namespacePrefix = ''; + } else { + foreach ($this->imports as $alias => $import) { + if ($classString === $alias) { + $classImport = $import; + + break; + } + + $classStringArray = explode('\\', $classString); + $namespaceToTest = $classStringArray[0]; + + if (0 === ($namespaceToTest <=> substr($import, -\strlen($namespaceToTest)))) { + $classImport = $import; + + break; + } + } + } + + for ($i = $classBeginIndex; $i <= $classIndex; ++$i) { + if (!$tokens[$i]->isComment() && !($tokens[$i]->isWhitespace() && str_contains($tokens[$i]->getContent(), "\n"))) { + $tokens->clearAt($i); + } + } + + $tokens->insertAt($classBeginIndex, new Token([ + T_CONSTANT_ENCAPSED_STRING, + "'".$this->makeClassFQN($namespacePrefix, $classImport, $classString)."'", + ])); + } + + /** + * @param false|string $classImport + */ + private function makeClassFQN(string $namespacePrefix, $classImport, string $classString): string + { + if (false === $classImport) { + return ('' !== $namespacePrefix ? ($namespacePrefix.'\\') : '').$classString; + } + + $classStringArray = explode('\\', $classString); + $classStringLength = \count($classStringArray); + $classImportArray = explode('\\', $classImport); + $classImportLength = \count($classImportArray); + + if (1 === $classStringLength) { + return $classImport; + } + + return implode('\\', array_merge( + \array_slice($classImportArray, 0, $classImportLength - $classStringLength + 1), + $classStringArray + )); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/CombineConsecutiveIssetsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/CombineConsecutiveIssetsFixer.php new file mode 100644 index 00000000..5a15c9e6 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/CombineConsecutiveIssetsFixer.php @@ -0,0 +1,163 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\LanguageConstruct; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class CombineConsecutiveIssetsFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Using `isset($var) &&` multiple times should be done in one call.', + [new CodeSample("isAllTokenKindsFound([T_ISSET, T_BOOLEAN_AND]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $tokenCount = $tokens->count(); + + for ($index = 1; $index < $tokenCount; ++$index) { + if (!$tokens[$index]->isGivenKind(T_ISSET) + || !$tokens[$tokens->getPrevMeaningfulToken($index)]->equalsAny(['(', '{', ';', '=', [T_OPEN_TAG], [T_BOOLEAN_AND], [T_BOOLEAN_OR]])) { + continue; + } + + $issetInfo = $this->getIssetInfo($tokens, $index); + $issetCloseBraceIndex = end($issetInfo); // ')' token + $insertLocation = prev($issetInfo) + 1; // one index after the previous meaningful of ')' + + $booleanAndTokenIndex = $tokens->getNextMeaningfulToken($issetCloseBraceIndex); + + while ($tokens[$booleanAndTokenIndex]->isGivenKind(T_BOOLEAN_AND)) { + $issetIndex = $tokens->getNextMeaningfulToken($booleanAndTokenIndex); + if (!$tokens[$issetIndex]->isGivenKind(T_ISSET)) { + $index = $issetIndex; + + break; + } + + // fetch info about the 'isset' statement that we're merging + $nextIssetInfo = $this->getIssetInfo($tokens, $issetIndex); + + $nextMeaningfulTokenIndex = $tokens->getNextMeaningfulToken(end($nextIssetInfo)); + $nextMeaningfulToken = $tokens[$nextMeaningfulTokenIndex]; + + if (!$nextMeaningfulToken->equalsAny([')', '}', ';', [T_CLOSE_TAG], [T_BOOLEAN_AND], [T_BOOLEAN_OR]])) { + $index = $nextMeaningfulTokenIndex; + + break; + } + + // clone what we want to move, do not clone '(' and ')' of the 'isset' statement we're merging + $clones = $this->getTokenClones($tokens, \array_slice($nextIssetInfo, 1, -1)); + + // clean up now the tokens of the 'isset' statement we're merging + $this->clearTokens($tokens, array_merge($nextIssetInfo, [$issetIndex, $booleanAndTokenIndex])); + + // insert the tokens to create the new statement + array_unshift($clones, new Token(','), new Token([T_WHITESPACE, ' '])); + $tokens->insertAt($insertLocation, $clones); + + // correct some counts and offset based on # of tokens inserted + $numberOfTokensInserted = \count($clones); + $tokenCount += $numberOfTokensInserted; + $issetCloseBraceIndex += $numberOfTokensInserted; + $insertLocation += $numberOfTokensInserted; + + $booleanAndTokenIndex = $tokens->getNextMeaningfulToken($issetCloseBraceIndex); + } + } + } + + /** + * @param list $indices + */ + private function clearTokens(Tokens $tokens, array $indices): void + { + foreach ($indices as $index) { + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } + } + + /** + * @param int $index of T_ISSET + * + * @return int[] indices of meaningful tokens belonging to the isset statement + */ + private function getIssetInfo(Tokens $tokens, int $index): array + { + $openIndex = $tokens->getNextMeaningfulToken($index); + + $braceOpenCount = 1; + $meaningfulTokenIndices = [$openIndex]; + + for ($i = $openIndex + 1;; ++$i) { + if ($tokens[$i]->isWhitespace() || $tokens[$i]->isComment()) { + continue; + } + + $meaningfulTokenIndices[] = $i; + + if ($tokens[$i]->equals(')')) { + --$braceOpenCount; + if (0 === $braceOpenCount) { + break; + } + } elseif ($tokens[$i]->equals('(')) { + ++$braceOpenCount; + } + } + + return $meaningfulTokenIndices; + } + + /** + * @param list $indices + * + * @return list + */ + private function getTokenClones(Tokens $tokens, array $indices): array + { + $clones = []; + + foreach ($indices as $i) { + $clones[] = clone $tokens[$i]; + } + + return $clones; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/CombineConsecutiveUnsetsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/CombineConsecutiveUnsetsFixer.php new file mode 100644 index 00000000..6ec6b753 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/CombineConsecutiveUnsetsFixer.php @@ -0,0 +1,179 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\LanguageConstruct; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class CombineConsecutiveUnsetsFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Calling `unset` on multiple items should be done in one call.', + [new CodeSample("isTokenKindFound(T_UNSET); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + if (!$tokens[$index]->isGivenKind(T_UNSET)) { + continue; + } + + $previousUnsetCall = $this->getPreviousUnsetCall($tokens, $index); + if (\is_int($previousUnsetCall)) { + $index = $previousUnsetCall; + + continue; + } + + [$previousUnset, , $previousUnsetBraceEnd] = $previousUnsetCall; + + // Merge the tokens inside the 'unset' call into the previous one 'unset' call. + $tokensAddCount = $this->moveTokens( + $tokens, + $nextUnsetContentStart = $tokens->getNextTokenOfKind($index, ['(']), + $nextUnsetContentEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $nextUnsetContentStart), + $previousUnsetBraceEnd - 1 + ); + + if (!$tokens[$previousUnsetBraceEnd]->isWhitespace()) { + $tokens->insertAt($previousUnsetBraceEnd, new Token([T_WHITESPACE, ' '])); + ++$tokensAddCount; + } + + $tokens->insertAt($previousUnsetBraceEnd, new Token(',')); + ++$tokensAddCount; + + // Remove 'unset', '(', ')' and (possibly) ';' from the merged 'unset' call. + $this->clearOffsetTokens($tokens, $tokensAddCount, [$index, $nextUnsetContentStart, $nextUnsetContentEnd]); + + $nextUnsetSemicolon = $tokens->getNextMeaningfulToken($nextUnsetContentEnd); + if (null !== $nextUnsetSemicolon && $tokens[$nextUnsetSemicolon]->equals(';')) { + $tokens->clearTokenAndMergeSurroundingWhitespace($nextUnsetSemicolon); + } + + $index = $previousUnset + 1; + } + } + + /** + * @param int[] $indices + */ + private function clearOffsetTokens(Tokens $tokens, int $offset, array $indices): void + { + foreach ($indices as $index) { + $tokens->clearTokenAndMergeSurroundingWhitespace($index + $offset); + } + } + + /** + * Find a previous call to unset directly before the index. + * + * Returns an array with + * * unset index + * * opening brace index + * * closing brace index + * * end semicolon index + * + * Or the index to where the method looked for a call. + * + * @return int|int[] + */ + private function getPreviousUnsetCall(Tokens $tokens, int $index) + { + $previousUnsetSemicolon = $tokens->getPrevMeaningfulToken($index); + if (null === $previousUnsetSemicolon) { + return $index; + } + + if (!$tokens[$previousUnsetSemicolon]->equals(';')) { + return $previousUnsetSemicolon; + } + + $previousUnsetBraceEnd = $tokens->getPrevMeaningfulToken($previousUnsetSemicolon); + if (null === $previousUnsetBraceEnd) { + return $index; + } + + if (!$tokens[$previousUnsetBraceEnd]->equals(')')) { + return $previousUnsetBraceEnd; + } + + $previousUnsetBraceStart = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $previousUnsetBraceEnd); + $previousUnset = $tokens->getPrevMeaningfulToken($previousUnsetBraceStart); + if (null === $previousUnset) { + return $index; + } + + if (!$tokens[$previousUnset]->isGivenKind(T_UNSET)) { + return $previousUnset; + } + + return [ + $previousUnset, + $previousUnsetBraceStart, + $previousUnsetBraceEnd, + $previousUnsetSemicolon, + ]; + } + + /** + * @param int $start Index previous of the first token to move + * @param int $end Index of the last token to move + * @param int $to Upper boundary index + * + * @return int Number of tokens inserted + */ + private function moveTokens(Tokens $tokens, int $start, int $end, int $to): int + { + $added = 0; + for ($i = $start + 1; $i < $end; $i += 2) { + if ($tokens[$i]->isWhitespace() && $tokens[$to + 1]->isWhitespace()) { + $tokens[$to + 1] = new Token([T_WHITESPACE, $tokens[$to + 1]->getContent().$tokens[$i]->getContent()]); + } else { + $tokens->insertAt(++$to, clone $tokens[$i]); + ++$end; + ++$added; + } + + $tokens->clearAt($i + 1); + } + + return $added; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/DeclareEqualNormalizeFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/DeclareEqualNormalizeFixer.php new file mode 100644 index 00000000..76b70a20 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/DeclareEqualNormalizeFixer.php @@ -0,0 +1,133 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\LanguageConstruct; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class DeclareEqualNormalizeFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** + * @var string + */ + private $callback; + + public function configure(array $configuration): void + { + parent::configure($configuration); + + $this->callback = 'none' === $this->configuration['space'] ? 'removeWhitespaceAroundToken' : 'ensureWhitespaceAroundToken'; + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Equal sign in declare statement should be surrounded by spaces or not following configuration.', + [ + new CodeSample(" 'single']), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run after DeclareStrictTypesFixer. + */ + public function getPriority(): int + { + return 0; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_DECLARE); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $callback = $this->callback; + for ($index = 0, $count = $tokens->count(); $index < $count - 6; ++$index) { + if (!$tokens[$index]->isGivenKind(T_DECLARE)) { + continue; + } + + $openParenthesisIndex = $tokens->getNextMeaningfulToken($index); + $closeParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesisIndex); + + for ($i = $closeParenthesisIndex; $i > $openParenthesisIndex; --$i) { + if ($tokens[$i]->equals('=')) { + $this->{$callback}($tokens, $i); + } + } + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('space', 'Spacing to apply around the equal sign.')) + ->setAllowedValues(['single', 'none']) + ->setDefault('none') + ->getOption(), + ]); + } + + /** + * @param int $index of `=` token + */ + private function ensureWhitespaceAroundToken(Tokens $tokens, int $index): void + { + if ($tokens[$index + 1]->isWhitespace()) { + if (' ' !== $tokens[$index + 1]->getContent()) { + $tokens[$index + 1] = new Token([T_WHITESPACE, ' ']); + } + } else { + $tokens->insertAt($index + 1, new Token([T_WHITESPACE, ' '])); + } + + if ($tokens[$index - 1]->isWhitespace()) { + if (' ' !== $tokens[$index - 1]->getContent() && !$tokens[$tokens->getPrevNonWhitespace($index - 1)]->isComment()) { + $tokens[$index - 1] = new Token([T_WHITESPACE, ' ']); + } + } else { + $tokens->insertAt($index, new Token([T_WHITESPACE, ' '])); + } + } + + /** + * @param int $index of `=` token + */ + private function removeWhitespaceAroundToken(Tokens $tokens, int $index): void + { + if (!$tokens[$tokens->getPrevNonWhitespace($index)]->isComment()) { + $tokens->removeLeadingWhitespace($index); + } + + $tokens->removeTrailingWhitespace($index); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/DeclareParenthesesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/DeclareParenthesesFixer.php new file mode 100644 index 00000000..e3879731 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/DeclareParenthesesFixer.php @@ -0,0 +1,56 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\LanguageConstruct; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Tokens; + +final class DeclareParenthesesFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'There must not be spaces around `declare` statement parentheses.', + [new CodeSample("isTokenKindFound(T_DECLARE); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; 0 <= $index; --$index) { + $token = $tokens[$index]; + + if (!$token->isGivenKind(T_DECLARE)) { + continue; + } + + $tokens->removeTrailingWhitespace($index); + + $startParenthesisIndex = $tokens->getNextTokenOfKind($index, ['(']); + $tokens->removeTrailingWhitespace($startParenthesisIndex); + + $endParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startParenthesisIndex); + $tokens->removeLeadingWhitespace($endParenthesisIndex); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/DirConstantFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/DirConstantFixer.php new file mode 100644 index 00000000..a19368e0 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/DirConstantFixer.php @@ -0,0 +1,129 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\LanguageConstruct; + +use PhpCsFixer\AbstractFunctionReferenceFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Vladimir Reznichenko + */ +final class DirConstantFixer extends AbstractFunctionReferenceFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Replaces `dirname(__FILE__)` expression with equivalent `__DIR__` constant.', + [new CodeSample("isAllTokenKindsFound([T_STRING, T_FILE]); + } + + /** + * {@inheritdoc} + * + * Must run before CombineNestedDirnameFixer. + */ + public function getPriority(): int + { + return 40; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $currIndex = 0; + + do { + $boundaries = $this->find('dirname', $tokens, $currIndex, $tokens->count() - 1); + if (null === $boundaries) { + return; + } + + [$functionNameIndex, $openParenthesis, $closeParenthesis] = $boundaries; + + // analysing cursor shift, so nested expressions kept processed + $currIndex = $openParenthesis; + + // ensure __FILE__ is in between (...) + + $fileCandidateRightIndex = $tokens->getPrevMeaningfulToken($closeParenthesis); + $trailingCommaIndex = null; + + if ($tokens[$fileCandidateRightIndex]->equals(',')) { + $trailingCommaIndex = $fileCandidateRightIndex; + $fileCandidateRightIndex = $tokens->getPrevMeaningfulToken($fileCandidateRightIndex); + } + + $fileCandidateRight = $tokens[$fileCandidateRightIndex]; + + if (!$fileCandidateRight->isGivenKind(T_FILE)) { + continue; + } + + $fileCandidateLeftIndex = $tokens->getNextMeaningfulToken($openParenthesis); + $fileCandidateLeft = $tokens[$fileCandidateLeftIndex]; + + if (!$fileCandidateLeft->isGivenKind(T_FILE)) { + continue; + } + + // get rid of root namespace when it used + $namespaceCandidateIndex = $tokens->getPrevMeaningfulToken($functionNameIndex); + $namespaceCandidate = $tokens[$namespaceCandidateIndex]; + + if ($namespaceCandidate->isGivenKind(T_NS_SEPARATOR)) { + $tokens->removeTrailingWhitespace($namespaceCandidateIndex); + $tokens->clearAt($namespaceCandidateIndex); + } + + if (null !== $trailingCommaIndex) { + if (!$tokens[$tokens->getNextNonWhitespace($trailingCommaIndex)]->isComment()) { + $tokens->removeTrailingWhitespace($trailingCommaIndex); + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($trailingCommaIndex); + } + + // closing parenthesis removed with leading spaces + if (!$tokens[$tokens->getNextNonWhitespace($closeParenthesis)]->isComment()) { + $tokens->removeLeadingWhitespace($closeParenthesis); + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($closeParenthesis); + + // opening parenthesis removed with trailing and leading spaces + if (!$tokens[$tokens->getNextNonWhitespace($openParenthesis)]->isComment()) { + $tokens->removeLeadingWhitespace($openParenthesis); + } + + $tokens->removeTrailingWhitespace($openParenthesis); + $tokens->clearTokenAndMergeSurroundingWhitespace($openParenthesis); + + // replace constant and remove function name + $tokens[$fileCandidateLeftIndex] = new Token([T_DIR, '__DIR__']); + $tokens->clearTokenAndMergeSurroundingWhitespace($functionNameIndex); + } while (null !== $currIndex); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ErrorSuppressionFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ErrorSuppressionFixer.php new file mode 100644 index 00000000..5cf4b852 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ErrorSuppressionFixer.php @@ -0,0 +1,169 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\LanguageConstruct; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Jules Pietri + * @author Kuba Werłos + */ +final class ErrorSuppressionFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** + * @internal + */ + public const OPTION_MUTE_DEPRECATION_ERROR = 'mute_deprecation_error'; + + /** + * @internal + */ + public const OPTION_NOISE_REMAINING_USAGES = 'noise_remaining_usages'; + + /** + * @internal + */ + public const OPTION_NOISE_REMAINING_USAGES_EXCLUDE = 'noise_remaining_usages_exclude'; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Error control operator should be added to deprecation notices and/or removed from other cases.', + [ + new CodeSample(" true] + ), + new CodeSample( + " true, + self::OPTION_NOISE_REMAINING_USAGES_EXCLUDE => ['unlink'], + ] + ), + ], + null, + 'Risky because adding/removing `@` might cause changes to code behaviour or if `trigger_error` function is overridden.' + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_STRING); + } + + public function isRisky(): bool + { + return true; + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder(self::OPTION_MUTE_DEPRECATION_ERROR, 'Whether to add `@` in deprecation notices.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), + (new FixerOptionBuilder(self::OPTION_NOISE_REMAINING_USAGES, 'Whether to remove `@` in remaining usages.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + (new FixerOptionBuilder(self::OPTION_NOISE_REMAINING_USAGES_EXCLUDE, 'List of global functions to exclude from removing `@`.')) + ->setAllowedTypes(['array']) + ->setDefault([]) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $functionsAnalyzer = new FunctionsAnalyzer(); + $excludedFunctions = array_map(static fn (string $function): string => strtolower($function), $this->configuration[self::OPTION_NOISE_REMAINING_USAGES_EXCLUDE]); + + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + $token = $tokens[$index]; + + if (true === $this->configuration[self::OPTION_NOISE_REMAINING_USAGES] && $token->equals('@')) { + $tokens->clearAt($index); + + continue; + } + + if (!$functionsAnalyzer->isGlobalFunctionCall($tokens, $index)) { + continue; + } + + $functionIndex = $index; + $startIndex = $index; + $prevIndex = $tokens->getPrevMeaningfulToken($index); + + if ($tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) { + $startIndex = $prevIndex; + $prevIndex = $tokens->getPrevMeaningfulToken($startIndex); + } + + $index = $prevIndex; + + if ($this->isDeprecationErrorCall($tokens, $functionIndex)) { + if (false === $this->configuration[self::OPTION_MUTE_DEPRECATION_ERROR]) { + continue; + } + + if ($tokens[$prevIndex]->equals('@')) { + continue; + } + + $tokens->insertAt($startIndex, new Token('@')); + + continue; + } + + if (!$tokens[$prevIndex]->equals('@')) { + continue; + } + + if (true === $this->configuration[self::OPTION_NOISE_REMAINING_USAGES] && !\in_array($tokens[$functionIndex]->getContent(), $excludedFunctions, true)) { + $tokens->clearAt($index); + } + } + } + + private function isDeprecationErrorCall(Tokens $tokens, int $index): bool + { + if ('trigger_error' !== strtolower($tokens[$index]->getContent())) { + return false; + } + + $endBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $tokens->getNextTokenOfKind($index, [[T_STRING], '('])); + $prevIndex = $tokens->getPrevMeaningfulToken($endBraceIndex); + + if ($tokens[$prevIndex]->equals(',')) { + $prevIndex = $tokens->getPrevMeaningfulToken($prevIndex); + } + + return $tokens[$prevIndex]->equals([T_STRING, 'E_USER_DEPRECATED']); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ExplicitIndirectVariableFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ExplicitIndirectVariableFixer.php new file mode 100644 index 00000000..38ef5fe9 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ExplicitIndirectVariableFixer.php @@ -0,0 +1,82 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\LanguageConstruct; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Filippo Tessarotto + */ +final class ExplicitIndirectVariableFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Add curly braces to indirect variables to make them clear to understand. Requires PHP >= 7.0.', + [ + new CodeSample( + <<<'EOT' + $bar['baz']; + echo $foo->$callback($baz); + + EOT + ), + ] + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_VARIABLE); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index > 1; --$index) { + $token = $tokens[$index]; + if (!$token->isGivenKind(T_VARIABLE)) { + continue; + } + + $prevIndex = $tokens->getPrevMeaningfulToken($index); + $prevToken = $tokens[$prevIndex]; + if (!$prevToken->equals('$') && !$prevToken->isObjectOperator()) { + continue; + } + + $openingBrace = CT::T_DYNAMIC_VAR_BRACE_OPEN; + $closingBrace = CT::T_DYNAMIC_VAR_BRACE_CLOSE; + if ($prevToken->isObjectOperator()) { + $openingBrace = CT::T_DYNAMIC_PROP_BRACE_OPEN; + $closingBrace = CT::T_DYNAMIC_PROP_BRACE_CLOSE; + } + + $tokens->overrideRange($index, $index, [ + new Token([$openingBrace, '{']), + new Token([T_VARIABLE, $token->getContent()]), + new Token([$closingBrace, '}']), + ]); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/FunctionToConstantFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/FunctionToConstantFixer.php new file mode 100644 index 00000000..3648f681 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/FunctionToConstantFixer.php @@ -0,0 +1,299 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\LanguageConstruct; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class FunctionToConstantFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** + * @var array + */ + private static $availableFunctions; + + /** + * @var array + */ + private array $functionsFixMap; + + public function __construct() + { + if (null === self::$availableFunctions) { + self::$availableFunctions = [ + 'get_called_class' => [ + new Token([T_STATIC, 'static']), + new Token([T_DOUBLE_COLON, '::']), + new Token([CT::T_CLASS_CONSTANT, 'class']), + ], + 'get_class' => [ + new Token([T_STRING, 'self']), + new Token([T_DOUBLE_COLON, '::']), + new Token([CT::T_CLASS_CONSTANT, 'class']), + ], + 'get_class_this' => [ + new Token([T_STATIC, 'static']), + new Token([T_DOUBLE_COLON, '::']), + new Token([CT::T_CLASS_CONSTANT, 'class']), + ], + 'php_sapi_name' => [new Token([T_STRING, 'PHP_SAPI'])], + 'phpversion' => [new Token([T_STRING, 'PHP_VERSION'])], + 'pi' => [new Token([T_STRING, 'M_PI'])], + ]; + } + + parent::__construct(); + } + + public function configure(array $configuration): void + { + parent::configure($configuration); + + $this->functionsFixMap = []; + + foreach ($this->configuration['functions'] as $key) { + $this->functionsFixMap[$key] = self::$availableFunctions[$key]; + } + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Replace core functions calls returning constants with the constants.', + [ + new CodeSample( + " ['get_called_class', 'get_class_this', 'phpversion']] + ), + ], + null, + 'Risky when any of the configured functions to replace are overridden.' + ); + } + + /** + * {@inheritdoc} + * + * Must run before NativeConstantInvocationFixer, NativeFunctionCasingFixer, NoExtraBlankLinesFixer, NoSinglelineWhitespaceBeforeSemicolonsFixer, NoTrailingWhitespaceFixer, NoWhitespaceInBlankLineFixer, SelfStaticAccessorFixer. + * Must run after NoSpacesAfterFunctionNameFixer, NoSpacesInsideParenthesisFixer, SpacesInsideParenthesesFixer. + */ + public function getPriority(): int + { + return 2; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_STRING); + } + + public function isRisky(): bool + { + return true; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $functionAnalyzer = new FunctionsAnalyzer(); + + for ($index = $tokens->count() - 4; $index > 0; --$index) { + $candidate = $this->getReplaceCandidate($tokens, $functionAnalyzer, $index); + if (null === $candidate) { + continue; + } + + $this->fixFunctionCallToConstant( + $tokens, + $index, + $candidate[0], // brace open + $candidate[1], // brace close + $candidate[2] // replacement + ); + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + $functionNames = array_keys(self::$availableFunctions); + + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('functions', 'List of function names to fix.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([new AllowedValueSubset($functionNames)]) + ->setDefault([ + 'get_called_class', + 'get_class', + 'get_class_this', + 'php_sapi_name', + 'phpversion', + 'pi', + ]) + ->getOption(), + ]); + } + + /** + * @param Token[] $replacements + */ + private function fixFunctionCallToConstant(Tokens $tokens, int $index, int $braceOpenIndex, int $braceCloseIndex, array $replacements): void + { + for ($i = $braceCloseIndex; $i >= $braceOpenIndex; --$i) { + if ($tokens[$i]->isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) { + continue; + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($i); + } + + if ( + $replacements[0]->isGivenKind([T_CLASS_C, T_STATIC]) + || ($replacements[0]->isGivenKind(T_STRING) && 'self' === $replacements[0]->getContent()) + ) { + $prevIndex = $tokens->getPrevMeaningfulToken($index); + $prevToken = $tokens[$prevIndex]; + if ($prevToken->isGivenKind(T_NS_SEPARATOR)) { + $tokens->clearAt($prevIndex); + } + } + + $tokens->clearAt($index); + $tokens->insertAt($index, $replacements); + } + + /** + * @return ?array{int, int, list} + */ + private function getReplaceCandidate( + Tokens $tokens, + FunctionsAnalyzer $functionAnalyzer, + int $index + ): ?array { + if (!$tokens[$index]->isGivenKind(T_STRING)) { + return null; + } + + $lowerContent = strtolower($tokens[$index]->getContent()); + + if ('get_class' === $lowerContent) { + return $this->fixGetClassCall($tokens, $functionAnalyzer, $index); + } + + if (!isset($this->functionsFixMap[$lowerContent])) { + return null; + } + + if (!$functionAnalyzer->isGlobalFunctionCall($tokens, $index)) { + return null; + } + + // test if function call without parameters + $braceOpenIndex = $tokens->getNextMeaningfulToken($index); + if (!$tokens[$braceOpenIndex]->equals('(')) { + return null; + } + + $braceCloseIndex = $tokens->getNextMeaningfulToken($braceOpenIndex); + if (!$tokens[$braceCloseIndex]->equals(')')) { + return null; + } + + return $this->getReplacementTokenClones($lowerContent, $braceOpenIndex, $braceCloseIndex); + } + + /** + * @return ?array{int, int, list} + */ + private function fixGetClassCall( + Tokens $tokens, + FunctionsAnalyzer $functionAnalyzer, + int $index + ): ?array { + if (!isset($this->functionsFixMap['get_class']) && !isset($this->functionsFixMap['get_class_this'])) { + return null; + } + + if (!$functionAnalyzer->isGlobalFunctionCall($tokens, $index)) { + return null; + } + + $braceOpenIndex = $tokens->getNextMeaningfulToken($index); + $braceCloseIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $braceOpenIndex); + + if ($braceCloseIndex === $tokens->getNextMeaningfulToken($braceOpenIndex)) { // no arguments passed + if (isset($this->functionsFixMap['get_class'])) { + return $this->getReplacementTokenClones('get_class', $braceOpenIndex, $braceCloseIndex); + } + } elseif (isset($this->functionsFixMap['get_class_this'])) { + $isThis = false; + + for ($i = $braceOpenIndex + 1; $i < $braceCloseIndex; ++$i) { + if ($tokens[$i]->equalsAny([[T_WHITESPACE], [T_COMMENT], [T_DOC_COMMENT], ')'])) { + continue; + } + + if ($tokens[$i]->isGivenKind(T_VARIABLE) && '$this' === strtolower($tokens[$i]->getContent())) { + $isThis = true; + + continue; + } + + if (false === $isThis && $tokens[$i]->equals('(')) { + continue; + } + + $isThis = false; + + break; + } + + if ($isThis) { + return $this->getReplacementTokenClones('get_class_this', $braceOpenIndex, $braceCloseIndex); + } + } + + return null; + } + + /** + * @return array{int, int, list} + */ + private function getReplacementTokenClones(string $lowerContent, int $braceOpenIndex, int $braceCloseIndex): array + { + $clones = array_map( + static fn (Token $token): Token => clone $token, + $this->functionsFixMap[$lowerContent], + ); + + return [ + $braceOpenIndex, + $braceCloseIndex, + $clones, + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/GetClassToClassKeywordFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/GetClassToClassKeywordFixer.php new file mode 100644 index 00000000..d5ceb526 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/GetClassToClassKeywordFixer.php @@ -0,0 +1,158 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\LanguageConstruct; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author John Paul E. Balandan, CPA + */ +final class GetClassToClassKeywordFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Replace `get_class` calls on object variables with class keyword syntax.', + [ + new VersionSpecificCodeSample( + "= 8_00_00 && $tokens->isAllTokenKindsFound([T_STRING, T_VARIABLE]); + } + + public function isRisky(): bool + { + return true; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $functionsAnalyzer = new FunctionsAnalyzer(); + $indicesToClear = []; + $tokenSlices = []; + + for ($index = $tokens->count() - 1; $index > 0; --$index) { + if (!$tokens[$index]->equals([T_STRING, 'get_class'], false)) { + continue; + } + + if (!$functionsAnalyzer->isGlobalFunctionCall($tokens, $index)) { + continue; + } + + $braceOpenIndex = $tokens->getNextMeaningfulToken($index); + $braceCloseIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $braceOpenIndex); + + if ($braceCloseIndex === $tokens->getNextMeaningfulToken($braceOpenIndex)) { + continue; // get_class with no arguments + } + + $meaningfulTokensCount = 0; + $variableTokensIndices = []; + + for ($i = $braceOpenIndex + 1; $i < $braceCloseIndex; ++$i) { + if (!$tokens[$i]->equalsAny([[T_WHITESPACE], [T_COMMENT], [T_DOC_COMMENT], '(', ')'])) { + ++$meaningfulTokensCount; + } + + if (!$tokens[$i]->isGivenKind(T_VARIABLE)) { + continue; + } + + if ('$this' === strtolower($tokens[$i]->getContent())) { + continue 2; // get_class($this) + } + + $variableTokensIndices[] = $i; + } + + if ($meaningfulTokensCount > 1 || 1 !== \count($variableTokensIndices)) { + continue; // argument contains more logic, or more arguments, or no variable argument + } + + $indicesToClear[$index] = [$braceOpenIndex, current($variableTokensIndices), $braceCloseIndex]; + } + + foreach ($indicesToClear as $index => $items) { + $tokenSlices[$index] = $this->getReplacementTokenSlices($tokens, $items[1]); + $this->clearGetClassCall($tokens, $index, $items[0], $items[2]); + } + + $tokens->insertSlices($tokenSlices); + } + + /** + * @return list + */ + private function getReplacementTokenSlices(Tokens $tokens, int $variableIndex): array + { + return [ + new Token([T_VARIABLE, $tokens[$variableIndex]->getContent()]), + new Token([T_DOUBLE_COLON, '::']), + new Token([CT::T_CLASS_CONSTANT, 'class']), + ]; + } + + private function clearGetClassCall(Tokens $tokens, int $index, int $braceOpenIndex, int $braceCloseIndex): void + { + for ($i = $braceOpenIndex; $i <= $braceCloseIndex; ++$i) { + if ($tokens[$i]->isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) { + continue; + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($i); + } + + $prevIndex = $tokens->getPrevMeaningfulToken($index); + + if ($tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) { + $tokens->clearAt($prevIndex); + } + + $tokens->clearAt($index); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/IsNullFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/IsNullFixer.php new file mode 100644 index 00000000..a99695d4 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/IsNullFixer.php @@ -0,0 +1,169 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\LanguageConstruct; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Vladimir Reznichenko + */ +final class IsNullFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Replaces `is_null($var)` expression with `null === $var`.', + [ + new CodeSample("isTokenKindFound(T_STRING); + } + + public function isRisky(): bool + { + return true; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + static $sequenceNeeded = [[T_STRING, 'is_null'], '(']; + $functionsAnalyzer = new FunctionsAnalyzer(); + $currIndex = 0; + + while (true) { + // recalculate "end" because we might have added tokens in previous iteration + $matches = $tokens->findSequence($sequenceNeeded, $currIndex, $tokens->count() - 1, false); + + // stop looping if didn't find any new matches + if (null === $matches) { + break; + } + + // 0 and 1 accordingly are "is_null", "(" tokens + $matches = array_keys($matches); + + // move the cursor just after the sequence + [$isNullIndex, $currIndex] = $matches; + + if (!$functionsAnalyzer->isGlobalFunctionCall($tokens, $matches[0])) { + continue; + } + + $next = $tokens->getNextMeaningfulToken($currIndex); + + if ($tokens[$next]->equals(')')) { + continue; + } + + $prevTokenIndex = $tokens->getPrevMeaningfulToken($matches[0]); + + // handle function references with namespaces + if ($tokens[$prevTokenIndex]->isGivenKind(T_NS_SEPARATOR)) { + $tokens->removeTrailingWhitespace($prevTokenIndex); + $tokens->clearAt($prevTokenIndex); + + $prevTokenIndex = $tokens->getPrevMeaningfulToken($prevTokenIndex); + } + + // check if inversion being used, text comparison is due to not existing constant + $isInvertedNullCheck = false; + + if ($tokens[$prevTokenIndex]->equals('!')) { + $isInvertedNullCheck = true; + + // get rid of inverting for proper transformations + $tokens->removeTrailingWhitespace($prevTokenIndex); + $tokens->clearAt($prevTokenIndex); + } + + // before getting rind of `()` around a parameter, ensure it's not assignment/ternary invariant + $referenceEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $matches[1]); + $isContainingDangerousConstructs = false; + + for ($paramTokenIndex = $matches[1]; $paramTokenIndex <= $referenceEnd; ++$paramTokenIndex) { + if (\in_array($tokens[$paramTokenIndex]->getContent(), ['?', '?:', '=', '??'], true)) { + $isContainingDangerousConstructs = true; + + break; + } + } + + // edge cases: is_null() followed/preceded by ==, ===, !=, !==, <>, (int-or-other-casting) + $parentLeftToken = $tokens[$tokens->getPrevMeaningfulToken($isNullIndex)]; + $parentRightToken = $tokens[$tokens->getNextMeaningfulToken($referenceEnd)]; + $parentOperations = [T_IS_EQUAL, T_IS_NOT_EQUAL, T_IS_IDENTICAL, T_IS_NOT_IDENTICAL]; + $wrapIntoParentheses = $parentLeftToken->isCast() || $parentLeftToken->isGivenKind($parentOperations) || $parentRightToken->isGivenKind($parentOperations); + + // possible trailing comma removed + $prevIndex = $tokens->getPrevMeaningfulToken($referenceEnd); + + if ($tokens[$prevIndex]->equals(',')) { + $tokens->clearTokenAndMergeSurroundingWhitespace($prevIndex); + } + + if (!$isContainingDangerousConstructs) { + // closing parenthesis removed with leading spaces + $tokens->removeLeadingWhitespace($referenceEnd); + $tokens->clearAt($referenceEnd); + + // opening parenthesis removed with trailing spaces + $tokens->removeLeadingWhitespace($matches[1]); + $tokens->removeTrailingWhitespace($matches[1]); + $tokens->clearAt($matches[1]); + } + + // sequence which we'll use as a replacement + $replacement = [ + new Token([T_STRING, 'null']), + new Token([T_WHITESPACE, ' ']), + new Token($isInvertedNullCheck ? [T_IS_NOT_IDENTICAL, '!=='] : [T_IS_IDENTICAL, '===']), + new Token([T_WHITESPACE, ' ']), + ]; + + if ($wrapIntoParentheses) { + array_unshift($replacement, new Token('(')); + $tokens->insertAt($referenceEnd + 1, new Token(')')); + } + + $tokens->overrideRange($isNullIndex, $isNullIndex, $replacement); + + // nested is_null calls support + $currIndex = $isNullIndex; + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/NoUnsetOnPropertyFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/NoUnsetOnPropertyFixer.php new file mode 100644 index 00000000..15a151a1 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/NoUnsetOnPropertyFixer.php @@ -0,0 +1,220 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\LanguageConstruct; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Gert de Pagter + */ +final class NoUnsetOnPropertyFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Properties should be set to `null` instead of using `unset`.', + [new CodeSample("a);\n")], + null, + 'Risky when relying on attributes to be removed using `unset` rather than be set to `null`.'. + ' Changing variables to `null` instead of unsetting means these still show up when looping over class variables'. + ' and reference properties remain unbroken.'. + ' With PHP 7.4, this rule might introduce `null` assignments to properties whose type declaration does not allow it.' + ); + } + + public function isRisky(): bool + { + return true; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_UNSET) + && $tokens->isAnyTokenKindsFound([T_OBJECT_OPERATOR, T_PAAMAYIM_NEKUDOTAYIM]); + } + + /** + * {@inheritdoc} + * + * Must run before CombineConsecutiveUnsetsFixer. + */ + public function getPriority(): int + { + return 25; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + if (!$tokens[$index]->isGivenKind(T_UNSET)) { + continue; + } + + $unsetsInfo = $this->getUnsetsInfo($tokens, $index); + + if (!$this->isAnyUnsetToTransform($unsetsInfo)) { + continue; + } + + $isLastUnset = true; // "last" as we reverse the array below + + foreach (array_reverse($unsetsInfo) as $unsetInfo) { + $this->updateTokens($tokens, $unsetInfo, $isLastUnset); + $isLastUnset = false; + } + } + } + + /** + * @return array> + */ + private function getUnsetsInfo(Tokens $tokens, int $index): array + { + $argumentsAnalyzer = new ArgumentsAnalyzer(); + + $unsetStart = $tokens->getNextTokenOfKind($index, ['(']); + $unsetEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $unsetStart); + $isFirst = true; + $unsets = []; + + foreach ($argumentsAnalyzer->getArguments($tokens, $unsetStart, $unsetEnd) as $startIndex => $endIndex) { + $startIndex = $tokens->getNextMeaningfulToken($startIndex - 1); + $endIndex = $tokens->getPrevMeaningfulToken($endIndex + 1); + $unsets[] = [ + 'startIndex' => $startIndex, + 'endIndex' => $endIndex, + 'isToTransform' => $this->isProperty($tokens, $startIndex, $endIndex), + 'isFirst' => $isFirst, + ]; + $isFirst = false; + } + + return $unsets; + } + + private function isProperty(Tokens $tokens, int $index, int $endIndex): bool + { + if ($tokens[$index]->isGivenKind(T_VARIABLE)) { + $nextIndex = $tokens->getNextMeaningfulToken($index); + + if (null === $nextIndex || !$tokens[$nextIndex]->isGivenKind(T_OBJECT_OPERATOR)) { + return false; + } + + $nextIndex = $tokens->getNextMeaningfulToken($nextIndex); + $nextNextIndex = $tokens->getNextMeaningfulToken($nextIndex); + + if (null !== $nextNextIndex && $nextNextIndex < $endIndex) { + return false; + } + + return null !== $nextIndex && $tokens[$nextIndex]->isGivenKind(T_STRING); + } + + if ($tokens[$index]->isGivenKind([T_NS_SEPARATOR, T_STRING])) { + $nextIndex = $tokens->getTokenNotOfKindsSibling($index, 1, [T_DOUBLE_COLON, T_NS_SEPARATOR, T_STRING]); + $nextNextIndex = $tokens->getNextMeaningfulToken($nextIndex); + + if (null !== $nextNextIndex && $nextNextIndex < $endIndex) { + return false; + } + + return null !== $nextIndex && $tokens[$nextIndex]->isGivenKind(T_VARIABLE); + } + + return false; + } + + /** + * @param array> $unsetsInfo + */ + private function isAnyUnsetToTransform(array $unsetsInfo): bool + { + foreach ($unsetsInfo as $unsetInfo) { + if ($unsetInfo['isToTransform']) { + return true; + } + } + + return false; + } + + /** + * @param array $unsetInfo + */ + private function updateTokens(Tokens $tokens, array $unsetInfo, bool $isLastUnset): void + { + // if entry is first and to be transformed we remove leading "unset(" + if ($unsetInfo['isFirst'] && $unsetInfo['isToTransform']) { + $braceIndex = $tokens->getPrevTokenOfKind($unsetInfo['startIndex'], ['(']); + $unsetIndex = $tokens->getPrevTokenOfKind($braceIndex, [[T_UNSET]]); + $tokens->clearTokenAndMergeSurroundingWhitespace($braceIndex); + $tokens->clearTokenAndMergeSurroundingWhitespace($unsetIndex); + } + + // if entry is last and to be transformed we remove trailing ")" + if ($isLastUnset && $unsetInfo['isToTransform']) { + $braceIndex = $tokens->getNextTokenOfKind($unsetInfo['endIndex'], [')']); + $previousIndex = $tokens->getPrevMeaningfulToken($braceIndex); + if ($tokens[$previousIndex]->equals(',')) { + $tokens->clearTokenAndMergeSurroundingWhitespace($previousIndex); // trailing ',' in function call (PHP 7.3) + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($braceIndex); + } + + // if entry is not last we replace comma with semicolon (last entry already has semicolon - from original unset) + if (!$isLastUnset) { + $commaIndex = $tokens->getNextTokenOfKind($unsetInfo['endIndex'], [',']); + $tokens[$commaIndex] = new Token(';'); + } + + // if entry is to be unset and is not last we add trailing ")" + if (!$unsetInfo['isToTransform'] && !$isLastUnset) { + $tokens->insertAt($unsetInfo['endIndex'] + 1, new Token(')')); + } + + // if entry is to be unset and is not first we add leading "unset(" + if (!$unsetInfo['isToTransform'] && !$unsetInfo['isFirst']) { + $tokens->insertAt( + $unsetInfo['startIndex'], + [ + new Token([T_UNSET, 'unset']), + new Token('('), + ] + ); + } + + // and finally + // if entry is to be transformed we add trailing " = null" + if ($unsetInfo['isToTransform']) { + $tokens->insertAt( + $unsetInfo['endIndex'] + 1, + [ + new Token([T_WHITESPACE, ' ']), + new Token('='), + new Token([T_WHITESPACE, ' ']), + new Token([T_STRING, 'null']), + ] + ); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/NullableTypeDeclarationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/NullableTypeDeclarationFixer.php new file mode 100644 index 00000000..a7b65432 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/NullableTypeDeclarationFixer.php @@ -0,0 +1,332 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\LanguageConstruct; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\TypeAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author John Paul E. Balandan, CPA + */ +final class NullableTypeDeclarationFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + private const OPTION_SYNTAX_UNION = 'union'; + private const OPTION_SYNTAX_QUESTION_MARK = 'question_mark'; + + private int $candidateTokenKind; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Nullable single type declaration should be standardised using configured syntax.', + [ + new VersionSpecificCodeSample( + " self::OPTION_SYNTAX_UNION] + ), + new VersionSpecificCodeSample( + ' self::OPTION_SYNTAX_QUESTION_MARK] + ), + ] + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return \PHP_VERSION_ID >= 8_00_00 && $tokens->isTokenKindFound($this->candidateTokenKind); + } + + /** + * {@inheritdoc} + * + * Must run before OrderedTypesFixer, TypesSpacesFixer. + * Must run after NullableTypeDeclarationForDefaultNullValueFixer, SingleSpaceAroundConstructFixer. + */ + public function getPriority(): int + { + return 2; + } + + public function configure(array $configuration): void + { + parent::configure($configuration); + + $this->candidateTokenKind = self::OPTION_SYNTAX_QUESTION_MARK === $this->configuration['syntax'] + ? CT::T_TYPE_ALTERNATION // `|` -> `?` + : CT::T_NULLABLE_TYPE; // `?` -> `|` + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('syntax', 'Whether to use question mark (`?`) or explicit `null` union for nullable type.')) + ->setAllowedValues([self::OPTION_SYNTAX_UNION, self::OPTION_SYNTAX_QUESTION_MARK]) + ->setDefault(self::OPTION_SYNTAX_QUESTION_MARK) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $functionsAnalyzer = new FunctionsAnalyzer(); + + foreach (array_reverse($this->getElements($tokens), true) as $index => $type) { + if ('property' === $type) { + $this->normalizePropertyType($tokens, $index); + + continue; + } + + $this->normalizeMethodReturnType($functionsAnalyzer, $tokens, $index); + $this->normalizeMethodArgumentType($functionsAnalyzer, $tokens, $index); + } + } + + /** + * @return array + * + * @phpstan-return array + */ + private function getElements(Tokens $tokens): array + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + + $elements = array_map( + static fn (array $element): string => 'method' === $element['type'] ? 'function' : $element['type'], + array_filter( + $tokensAnalyzer->getClassyElements(), + static fn (array $element): bool => \in_array($element['type'], ['method', 'property'], true) + ) + ); + + foreach ($tokens as $index => $token) { + if ( + $token->isGivenKind(T_FN) + || ($token->isGivenKind(T_FUNCTION) && !isset($elements[$index])) + ) { + $elements[$index] = 'function'; + } + } + + return $elements; + } + + private function collectTypeAnalysis(Tokens $tokens, int $startIndex, int $endIndex): ?TypeAnalysis + { + $type = ''; + $typeStartIndex = $tokens->getNextMeaningfulToken($startIndex); + $typeEndIndex = $typeStartIndex; + + for ($i = $typeStartIndex; $i < $endIndex; ++$i) { + if ($tokens[$i]->isWhitespace() || $tokens[$i]->isComment()) { + continue; + } + + $type .= $tokens[$i]->getContent(); + $typeEndIndex = $i; + } + + return '' !== $type ? new TypeAnalysis($type, $typeStartIndex, $typeEndIndex) : null; + } + + private function isTypeNormalizable(TypeAnalysis $typeAnalysis): bool + { + if (!$typeAnalysis->isNullable()) { + return false; + } + + $type = $typeAnalysis->getName(); + + if (str_contains($type, '&')) { + return false; // skip DNF types + } + + if (!str_contains($type, '|')) { + return true; + } + + return 1 === substr_count($type, '|') && Preg::match('/(?:\|null$|^null\|)/i', $type); + } + + private function normalizePropertyType(Tokens $tokens, int $index): void + { + $propertyEndIndex = $index; + $propertyModifiers = [T_PRIVATE, T_PROTECTED, T_PUBLIC, T_STATIC, T_VAR]; + + if (\defined('T_READONLY')) { + $propertyModifiers[] = T_READONLY; // @TODO: Drop condition when PHP 8.1+ is required + } + + do { + $index = $tokens->getPrevMeaningfulToken($index); + } while (!$tokens[$index]->isGivenKind($propertyModifiers)); + + $propertyType = $this->collectTypeAnalysis($tokens, $index, $propertyEndIndex); + + if (null === $propertyType || !$this->isTypeNormalizable($propertyType)) { + return; + } + + $this->normalizeNullableType($tokens, $propertyType); + } + + private function normalizeMethodArgumentType(FunctionsAnalyzer $functionsAnalyzer, Tokens $tokens, int $index): void + { + foreach (array_reverse($functionsAnalyzer->getFunctionArguments($tokens, $index), true) as $argumentInfo) { + $argumentType = $argumentInfo->getTypeAnalysis(); + + if (null === $argumentType || !$this->isTypeNormalizable($argumentType)) { + continue; + } + + $this->normalizeNullableType($tokens, $argumentType); + } + } + + private function normalizeMethodReturnType(FunctionsAnalyzer $functionsAnalyzer, Tokens $tokens, int $index): void + { + $returnType = $functionsAnalyzer->getFunctionReturnType($tokens, $index); + + if (null === $returnType || !$this->isTypeNormalizable($returnType)) { + return; + } + + $this->normalizeNullableType($tokens, $returnType); + } + + private function normalizeNullableType(Tokens $tokens, TypeAnalysis $typeAnalysis): void + { + $type = $typeAnalysis->getName(); + + if (!str_contains($type, '|') && !str_contains($type, '&')) { + $type = ($typeAnalysis->isNullable() ? '?' : '').$type; + } + + $isQuestionMarkSyntax = self::OPTION_SYNTAX_QUESTION_MARK === $this->configuration['syntax']; + + if ($isQuestionMarkSyntax) { + $normalizedType = $this->convertToNullableType($type); + $normalizedTypeAsString = implode('', $normalizedType); + } else { + $normalizedType = $this->convertToExplicitUnionType($type); + $normalizedTypeAsString = implode('|', $normalizedType); + } + + if ($normalizedTypeAsString === $type) { + return; // nothing to fix + } + + $tokens->overrideRange( + $typeAnalysis->getStartIndex(), + $typeAnalysis->getEndIndex(), + $this->createTypeDeclarationTokens($normalizedType, $isQuestionMarkSyntax) + ); + } + + /** + * @return list + */ + private function convertToNullableType(string $type): array + { + if (str_starts_with($type, '?')) { + return [$type]; // no need to convert; already fixed + } + + return ['?', Preg::replace('/(?:\|null$|^null\|)/i', '', $type)]; + } + + /** + * @return list + */ + private function convertToExplicitUnionType(string $type): array + { + if (str_contains($type, '|')) { + return [$type]; // no need to convert; already fixed + } + + return ['null', substr($type, 1)]; + } + + /** + * @param list $types + * + * @return list + */ + private function createTypeDeclarationTokens(array $types, bool $isQuestionMarkSyntax): array + { + static $specialTypes = [ + '?' => [CT::T_NULLABLE_TYPE, '?'], + 'array' => [CT::T_ARRAY_TYPEHINT, 'array'], + 'callable' => [T_CALLABLE, 'callable'], + 'static' => [T_STATIC, 'static'], + ]; + + $count = \count($types); + $newTokens = []; + + foreach ($types as $index => $type) { + if (isset($specialTypes[$type])) { + $newTokens[] = new Token($specialTypes[$type]); + } else { + foreach (explode('\\', $type) as $nsIndex => $value) { + if (0 === $nsIndex && '' === $value) { + continue; + } + + if ($nsIndex > 0) { + $newTokens[] = new Token([T_NS_SEPARATOR, '\\']); + } + + $newTokens[] = new Token([T_STRING, $value]); + } + } + + if ($index <= $count - 2 && !$isQuestionMarkSyntax) { + $newTokens[] = new Token([CT::T_TYPE_ALTERNATION, '|']); + } + } + + return $newTokens; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/SingleSpaceAfterConstructFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/SingleSpaceAfterConstructFixer.php new file mode 100644 index 00000000..229049a3 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/SingleSpaceAfterConstructFixer.php @@ -0,0 +1,198 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\LanguageConstruct; + +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; + +/** + * @author Andreas Möller + * + * @deprecated + */ +final class SingleSpaceAfterConstructFixer extends AbstractProxyFixer implements ConfigurableFixerInterface, DeprecatedFixerInterface +{ + /** + * @var array + */ + private static array $tokenMap = [ + 'abstract' => T_ABSTRACT, + 'as' => T_AS, + 'attribute' => CT::T_ATTRIBUTE_CLOSE, + 'break' => T_BREAK, + 'case' => T_CASE, + 'catch' => T_CATCH, + 'class' => T_CLASS, + 'clone' => T_CLONE, + 'comment' => T_COMMENT, + 'const' => T_CONST, + 'const_import' => CT::T_CONST_IMPORT, + 'continue' => T_CONTINUE, + 'do' => T_DO, + 'echo' => T_ECHO, + 'else' => T_ELSE, + 'elseif' => T_ELSEIF, + 'enum' => null, + 'extends' => T_EXTENDS, + 'final' => T_FINAL, + 'finally' => T_FINALLY, + 'for' => T_FOR, + 'foreach' => T_FOREACH, + 'function' => T_FUNCTION, + 'function_import' => CT::T_FUNCTION_IMPORT, + 'global' => T_GLOBAL, + 'goto' => T_GOTO, + 'if' => T_IF, + 'implements' => T_IMPLEMENTS, + 'include' => T_INCLUDE, + 'include_once' => T_INCLUDE_ONCE, + 'instanceof' => T_INSTANCEOF, + 'insteadof' => T_INSTEADOF, + 'interface' => T_INTERFACE, + 'match' => null, + 'named_argument' => CT::T_NAMED_ARGUMENT_COLON, + 'namespace' => T_NAMESPACE, + 'new' => T_NEW, + 'open_tag_with_echo' => T_OPEN_TAG_WITH_ECHO, + 'php_doc' => T_DOC_COMMENT, + 'php_open' => T_OPEN_TAG, + 'print' => T_PRINT, + 'private' => T_PRIVATE, + 'protected' => T_PROTECTED, + 'public' => T_PUBLIC, + 'readonly' => null, + 'require' => T_REQUIRE, + 'require_once' => T_REQUIRE_ONCE, + 'return' => T_RETURN, + 'static' => T_STATIC, + 'switch' => T_SWITCH, + 'throw' => T_THROW, + 'trait' => T_TRAIT, + 'try' => T_TRY, + 'type_colon' => CT::T_TYPE_COLON, + 'use' => T_USE, + 'use_lambda' => CT::T_USE_LAMBDA, + 'use_trait' => CT::T_USE_TRAIT, + 'var' => T_VAR, + 'while' => T_WHILE, + 'yield' => T_YIELD, + 'yield_from' => T_YIELD_FROM, + ]; + + private SingleSpaceAroundConstructFixer $singleSpaceAroundConstructFixer; + + public function __construct() + { + $this->singleSpaceAroundConstructFixer = new SingleSpaceAroundConstructFixer(); + + parent::__construct(); + } + + public function getSuccessorsNames(): array + { + return array_keys($this->proxyFixers); + } + + public function configure(array $configuration): void + { + parent::configure($configuration); + + $this->singleSpaceAroundConstructFixer->configure([ + 'constructs_contain_a_single_space' => [ + 'yield_from', + ], + 'constructs_preceded_by_a_single_space' => [], + 'constructs_followed_by_a_single_space' => $this->configuration['constructs'], + ]); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Ensures a single space after language constructs.', + [ + new CodeSample( + ' [ + 'echo', + ], + ] + ), + new CodeSample( + ' [ + 'yield_from', + ], + ] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before BracesFixer, FunctionDeclarationFixer. + * Must run after ArraySyntaxFixer, ModernizeStrposFixer. + */ + public function getPriority(): int + { + return parent::getPriority(); + } + + protected function createProxyFixers(): array + { + return [$this->singleSpaceAroundConstructFixer]; + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + $defaults = self::$tokenMap; + $tokens = array_keys($defaults); + + unset($defaults['type_colon']); + + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('constructs', 'List of constructs which must be followed by a single space.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([new AllowedValueSubset($tokens)]) + ->setDefault(array_keys($defaults)) + ->getOption(), + ]); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/SingleSpaceAroundConstructFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/SingleSpaceAroundConstructFixer.php new file mode 100644 index 00000000..67da8784 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/SingleSpaceAroundConstructFixer.php @@ -0,0 +1,483 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\LanguageConstruct; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Andreas Möller + * @author Dariusz Rumiński + */ +final class SingleSpaceAroundConstructFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** + * @var array + */ + private static array $tokenMapContainASingleSpace = [ + // for now, only one case - but we are ready to extend it, when we learn about new cases to cover + 'yield_from' => T_YIELD_FROM, + ]; + + /** + * @var array + */ + private static array $tokenMapPrecededByASingleSpace = [ + 'as' => T_AS, + 'use_lambda' => CT::T_USE_LAMBDA, + ]; + + /** + * @var array + */ + private static array $tokenMapFollowedByASingleSpace = [ + 'abstract' => T_ABSTRACT, + 'as' => T_AS, + 'attribute' => CT::T_ATTRIBUTE_CLOSE, + 'break' => T_BREAK, + 'case' => T_CASE, + 'catch' => T_CATCH, + 'class' => T_CLASS, + 'clone' => T_CLONE, + 'comment' => T_COMMENT, + 'const' => T_CONST, + 'const_import' => CT::T_CONST_IMPORT, + 'continue' => T_CONTINUE, + 'do' => T_DO, + 'echo' => T_ECHO, + 'else' => T_ELSE, + 'elseif' => T_ELSEIF, + 'enum' => null, + 'extends' => T_EXTENDS, + 'final' => T_FINAL, + 'finally' => T_FINALLY, + 'for' => T_FOR, + 'foreach' => T_FOREACH, + 'function' => T_FUNCTION, + 'function_import' => CT::T_FUNCTION_IMPORT, + 'global' => T_GLOBAL, + 'goto' => T_GOTO, + 'if' => T_IF, + 'implements' => T_IMPLEMENTS, + 'include' => T_INCLUDE, + 'include_once' => T_INCLUDE_ONCE, + 'instanceof' => T_INSTANCEOF, + 'insteadof' => T_INSTEADOF, + 'interface' => T_INTERFACE, + 'match' => null, + 'named_argument' => CT::T_NAMED_ARGUMENT_COLON, + 'namespace' => T_NAMESPACE, + 'new' => T_NEW, + 'open_tag_with_echo' => T_OPEN_TAG_WITH_ECHO, + 'php_doc' => T_DOC_COMMENT, + 'php_open' => T_OPEN_TAG, + 'print' => T_PRINT, + 'private' => T_PRIVATE, + 'protected' => T_PROTECTED, + 'public' => T_PUBLIC, + 'readonly' => null, + 'require' => T_REQUIRE, + 'require_once' => T_REQUIRE_ONCE, + 'return' => T_RETURN, + 'static' => T_STATIC, + 'switch' => T_SWITCH, + 'throw' => T_THROW, + 'trait' => T_TRAIT, + 'try' => T_TRY, + 'type_colon' => CT::T_TYPE_COLON, + 'use' => T_USE, + 'use_lambda' => CT::T_USE_LAMBDA, + 'use_trait' => CT::T_USE_TRAIT, + 'var' => T_VAR, + 'while' => T_WHILE, + 'yield' => T_YIELD, + 'yield_from' => T_YIELD_FROM, + ]; + + /** + * @var array + */ + private array $fixTokenMapFollowedByASingleSpace = []; + + /** + * @var array + */ + private array $fixTokenMapContainASingleSpace = []; + + /** + * @var array + */ + private array $fixTokenMapPrecededByASingleSpace = []; + + public function configure(array $configuration): void + { + parent::configure($configuration); + + if (\defined('T_MATCH')) { // @TODO: drop condition when PHP 8.0+ is required + self::$tokenMapFollowedByASingleSpace['match'] = T_MATCH; + } + + if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required + self::$tokenMapFollowedByASingleSpace['readonly'] = T_READONLY; + } + + if (\defined('T_ENUM')) { // @TODO: drop condition when PHP 8.1+ is required + self::$tokenMapFollowedByASingleSpace['enum'] = T_ENUM; + } + + $this->fixTokenMapContainASingleSpace = []; + + foreach ($this->configuration['constructs_contain_a_single_space'] as $key) { + if (null !== self::$tokenMapContainASingleSpace[$key]) { + $this->fixTokenMapContainASingleSpace[$key] = self::$tokenMapContainASingleSpace[$key]; + } + } + + $this->fixTokenMapPrecededByASingleSpace = []; + + foreach ($this->configuration['constructs_preceded_by_a_single_space'] as $key) { + if (null !== self::$tokenMapPrecededByASingleSpace[$key]) { + $this->fixTokenMapPrecededByASingleSpace[$key] = self::$tokenMapPrecededByASingleSpace[$key]; + } + } + + $this->fixTokenMapFollowedByASingleSpace = []; + + foreach ($this->configuration['constructs_followed_by_a_single_space'] as $key) { + if (null !== self::$tokenMapFollowedByASingleSpace[$key]) { + $this->fixTokenMapFollowedByASingleSpace[$key] = self::$tokenMapFollowedByASingleSpace[$key]; + } + } + + if (isset($this->fixTokenMapFollowedByASingleSpace['public'])) { + $this->fixTokenMapFollowedByASingleSpace['constructor_public'] = CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC; + } + + if (isset($this->fixTokenMapFollowedByASingleSpace['protected'])) { + $this->fixTokenMapFollowedByASingleSpace['constructor_protected'] = CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED; + } + + if (isset($this->fixTokenMapFollowedByASingleSpace['private'])) { + $this->fixTokenMapFollowedByASingleSpace['constructor_private'] = CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE; + } + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Ensures a single space after language constructs.', + [ + new CodeSample( + ' [ + 'yield_from', + ], + 'constructs_followed_by_a_single_space' => [ + 'yield_from', + ], + ] + ), + + new CodeSample( + ' [ + 'use_lambda', + ], + 'constructs_followed_by_a_single_space' => [ + 'use_lambda', + ], + ] + ), + new CodeSample( + ' [ + 'echo', + ], + ] + ), + new CodeSample( + ' [ + 'yield_from', + ], + ] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before BracesFixer, FunctionDeclarationFixer, NullableTypeDeclarationFixer. + * Must run after ArraySyntaxFixer, ModernizeStrposFixer. + */ + public function getPriority(): int + { + return 36; + } + + public function isCandidate(Tokens $tokens): bool + { + $tokenKinds = [ + ...array_values($this->fixTokenMapContainASingleSpace), + ...array_values($this->fixTokenMapPrecededByASingleSpace), + ...array_values($this->fixTokenMapFollowedByASingleSpace), + ]; + + return $tokens->isAnyTokenKindsFound($tokenKinds) && !$tokens->hasAlternativeSyntax(); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $tokenKindsContainASingleSpace = array_values($this->fixTokenMapContainASingleSpace); + + for ($index = $tokens->count() - 1; $index > 0; --$index) { + if ($tokens[$index]->isGivenKind($tokenKindsContainASingleSpace)) { + $token = $tokens[$index]; + + if ( + $token->isGivenKind(T_YIELD_FROM) + && 'yield from' !== strtolower($token->getContent()) + ) { + $tokens[$index] = new Token([T_YIELD_FROM, Preg::replace( + '/\s+/', + ' ', + $token->getContent() + )]); + } + } + } + + $tokenKindsPrecededByASingleSpace = array_values($this->fixTokenMapPrecededByASingleSpace); + + for ($index = $tokens->count() - 1; $index > 0; --$index) { + if ($tokens[$index]->isGivenKind($tokenKindsPrecededByASingleSpace)) { + $tokens->ensureWhitespaceAtIndex($index - 1, 1, ' '); + } + } + + $tokenKindsFollowedByASingleSpace = array_values($this->fixTokenMapFollowedByASingleSpace); + + for ($index = $tokens->count() - 2; $index >= 0; --$index) { + $token = $tokens[$index]; + + if (!$token->isGivenKind($tokenKindsFollowedByASingleSpace)) { + continue; + } + + $whitespaceTokenIndex = $index + 1; + + if ($tokens[$whitespaceTokenIndex]->equalsAny([',', ';', ')', [CT::T_ARRAY_SQUARE_BRACE_CLOSE], [CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE]])) { + continue; + } + + if ( + $token->isGivenKind(T_STATIC) + && !$tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind([T_FN, T_FUNCTION, T_NS_SEPARATOR, T_STRING, T_VARIABLE, CT::T_ARRAY_TYPEHINT, CT::T_NULLABLE_TYPE]) + ) { + continue; + } + + if ($token->isGivenKind(T_OPEN_TAG)) { + if ($tokens[$whitespaceTokenIndex]->equals([T_WHITESPACE]) && !str_contains($tokens[$whitespaceTokenIndex]->getContent(), "\n") && !str_contains($token->getContent(), "\n")) { + $tokens->clearAt($whitespaceTokenIndex); + } + + continue; + } + + if ($token->isGivenKind(T_CLASS) && $tokens[$tokens->getNextMeaningfulToken($index)]->equals('(')) { + continue; + } + + if ($token->isGivenKind([T_EXTENDS, T_IMPLEMENTS]) && $this->isMultilineExtendsOrImplementsWithMoreThanOneAncestor($tokens, $index)) { + continue; + } + + if ($token->isGivenKind(T_RETURN) && $this->isMultiLineReturn($tokens, $index)) { + continue; + } + + if ($token->isGivenKind(T_CONST) && $this->isMultilineCommaSeparatedConstant($tokens, $index)) { + continue; + } + + if ($token->isComment() || $token->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) { + if ($tokens[$whitespaceTokenIndex]->equals([T_WHITESPACE]) && str_contains($tokens[$whitespaceTokenIndex]->getContent(), "\n")) { + continue; + } + } + + if ($tokens[$whitespaceTokenIndex]->isWhitespace() && str_contains($tokens[$whitespaceTokenIndex]->getContent(), "\n")) { + $nextNextToken = $tokens[$whitespaceTokenIndex + 1]; + if (\defined('T_ATTRIBUTE')) { // @TODO: drop condition and else when PHP 8.0+ is required + if ($nextNextToken->isGivenKind(T_ATTRIBUTE)) { + continue; + } + } else { + if ($nextNextToken->isComment() && str_starts_with($nextNextToken->getContent(), '#[')) { + continue; + } + } + + if ($nextNextToken->isGivenKind(T_DOC_COMMENT)) { + continue; + } + } + + $tokens->ensureWhitespaceAtIndex($whitespaceTokenIndex, 0, ' '); + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + $tokenMapContainASingleSpaceKeys = array_keys(self::$tokenMapContainASingleSpace); + $tokenMapPrecededByASingleSpaceKeys = array_keys(self::$tokenMapPrecededByASingleSpace); + $tokenMapFollowedByASingleSpaceKeys = array_keys(self::$tokenMapFollowedByASingleSpace); + + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('constructs_contain_a_single_space', 'List of constructs which must contain a single space.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([new AllowedValueSubset($tokenMapContainASingleSpaceKeys)]) + ->setDefault($tokenMapContainASingleSpaceKeys) + ->getOption(), + (new FixerOptionBuilder('constructs_preceded_by_a_single_space', 'List of constructs which must be preceded by a single space.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([new AllowedValueSubset($tokenMapPrecededByASingleSpaceKeys)]) + ->setDefault($tokenMapPrecededByASingleSpaceKeys) + ->getOption(), + (new FixerOptionBuilder('constructs_followed_by_a_single_space', 'List of constructs which must be followed by a single space.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([new AllowedValueSubset($tokenMapFollowedByASingleSpaceKeys)]) + ->setDefault($tokenMapFollowedByASingleSpaceKeys) + ->getOption(), + ]); + } + + private function isMultiLineReturn(Tokens $tokens, int $index): bool + { + ++$index; + $tokenFollowingReturn = $tokens[$index]; + + if ( + !$tokenFollowingReturn->isGivenKind(T_WHITESPACE) + || !str_contains($tokenFollowingReturn->getContent(), "\n") + ) { + return false; + } + + $nestedCount = 0; + + for ($indexEnd = \count($tokens) - 1, ++$index; $index < $indexEnd; ++$index) { + if (str_contains($tokens[$index]->getContent(), "\n")) { + return true; + } + + if ($tokens[$index]->equals('{')) { + ++$nestedCount; + } elseif ($tokens[$index]->equals('}')) { + --$nestedCount; + } elseif (0 === $nestedCount && $tokens[$index]->equalsAny([';', [T_CLOSE_TAG]])) { + break; + } + } + + return false; + } + + private function isMultilineExtendsOrImplementsWithMoreThanOneAncestor(Tokens $tokens, int $index): bool + { + $hasMoreThanOneAncestor = false; + + while (++$index) { + $token = $tokens[$index]; + + if ($token->equals(',')) { + $hasMoreThanOneAncestor = true; + + continue; + } + + if ($token->equals('{')) { + return false; + } + + if ($hasMoreThanOneAncestor && str_contains($token->getContent(), "\n")) { + return true; + } + } + + return false; + } + + private function isMultilineCommaSeparatedConstant(Tokens $tokens, int $constantIndex): bool + { + $isMultilineConstant = false; + $hasMoreThanOneConstant = false; + $index = $constantIndex; + while (!$tokens[$index]->equalsAny([';', [T_CLOSE_TAG]])) { + ++$index; + + $isMultilineConstant = $isMultilineConstant || str_contains($tokens[$index]->getContent(), "\n"); + + if ($tokens[$index]->equals(',')) { + $hasMoreThanOneConstant = true; + } + + $blockType = Tokens::detectBlockType($tokens[$index]); + + if (null !== $blockType && true === $blockType['isStart']) { + $index = $tokens->findBlockEnd($blockType['type'], $index); + } + } + + return $hasMoreThanOneConstant && $isMultilineConstant; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ListNotation/ListSyntaxFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ListNotation/ListSyntaxFixer.php new file mode 100644 index 00000000..3bc18cea --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ListNotation/ListSyntaxFixer.php @@ -0,0 +1,130 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ListNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class ListSyntaxFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** + * @var null|int + */ + private $candidateTokenKind; + + /** + * @param array{syntax: 'long'|'short'} $configuration + * + * @throws InvalidFixerConfigurationException + */ + public function configure(array $configuration): void + { + parent::configure($configuration); + + $this->candidateTokenKind = 'long' === $this->configuration['syntax'] ? CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN : T_LIST; + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'List (`array` destructuring) assignment should be declared using the configured syntax. Requires PHP >= 7.1.', + [ + new CodeSample( + " 'long'] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before BinaryOperatorSpacesFixer, TernaryOperatorSpacesFixer. + */ + public function getPriority(): int + { + return 2; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound($this->candidateTokenKind); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; 0 <= $index; --$index) { + if ($tokens[$index]->isGivenKind($this->candidateTokenKind)) { + if (T_LIST === $this->candidateTokenKind) { + $this->fixToShortSyntax($tokens, $index); + } else { + $this->fixToLongSyntax($tokens, $index); + } + } + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('syntax', 'Whether to use the `long` or `short` syntax for array destructuring.')) + ->setAllowedValues(['long', 'short']) + ->setDefault('short') + ->getOption(), + ]); + } + + private function fixToLongSyntax(Tokens $tokens, int $index): void + { + static $typesOfInterest = [ + [CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE], + '[', // [CT::T_ARRAY_SQUARE_BRACE_OPEN], + ]; + + $closeIndex = $tokens->getNextTokenOfKind($index, $typesOfInterest); + if (!$tokens[$closeIndex]->isGivenKind(CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE)) { + return; + } + + $tokens[$index] = new Token('('); + $tokens[$closeIndex] = new Token(')'); + $tokens->insertAt($index, new Token([T_LIST, 'list'])); + } + + private function fixToShortSyntax(Tokens $tokens, int $index): void + { + $openIndex = $tokens->getNextTokenOfKind($index, ['(']); + $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex); + + $tokens[$openIndex] = new Token([CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, '[']); + $tokens[$closeIndex] = new Token([CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, ']']); + + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/BlankLineAfterNamespaceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/BlankLineAfterNamespaceFixer.php new file mode 100644 index 00000000..7c27bef6 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/BlankLineAfterNamespaceFixer.php @@ -0,0 +1,127 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\NamespaceNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Fixer for rules defined in PSR2 ¶3. + * + * @author Dariusz Rumiński + */ +final class BlankLineAfterNamespaceFixer extends AbstractFixer implements WhitespacesAwareFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'There MUST be one blank line after the namespace declaration.', + [ + new CodeSample("isTokenKindFound(T_NAMESPACE); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $lastIndex = $tokens->count() - 1; + + for ($index = $lastIndex; $index >= 0; --$index) { + $token = $tokens[$index]; + + if (!$token->isGivenKind(T_NAMESPACE)) { + continue; + } + + $semicolonIndex = $tokens->getNextTokenOfKind($index, [';', '{', [T_CLOSE_TAG]]); + $semicolonToken = $tokens[$semicolonIndex]; + + if (!$semicolonToken->equals(';')) { + continue; + } + + $indexToEnsureBlankLineAfter = $this->getIndexToEnsureBlankLineAfter($tokens, $semicolonIndex); + $indexToEnsureBlankLine = $tokens->getNonEmptySibling($indexToEnsureBlankLineAfter, 1); + + if (null !== $indexToEnsureBlankLine && $tokens[$indexToEnsureBlankLine]->isWhitespace()) { + $tokens[$indexToEnsureBlankLine] = $this->getTokenToInsert($tokens[$indexToEnsureBlankLine]->getContent(), $indexToEnsureBlankLine === $lastIndex); + } else { + $tokens->insertAt($indexToEnsureBlankLineAfter + 1, $this->getTokenToInsert('', $indexToEnsureBlankLineAfter === $lastIndex)); + } + } + } + + private function getIndexToEnsureBlankLineAfter(Tokens $tokens, int $index): int + { + $indexToEnsureBlankLine = $index; + $nextIndex = $tokens->getNonEmptySibling($indexToEnsureBlankLine, 1); + + while (null !== $nextIndex) { + $token = $tokens[$nextIndex]; + + if ($token->isWhitespace()) { + if (Preg::match('/\R/', $token->getContent())) { + break; + } + $nextNextIndex = $tokens->getNonEmptySibling($nextIndex, 1); + + if (!$tokens[$nextNextIndex]->isComment()) { + break; + } + } + + if (!$token->isWhitespace() && !$token->isComment()) { + break; + } + + $indexToEnsureBlankLine = $nextIndex; + $nextIndex = $tokens->getNonEmptySibling($indexToEnsureBlankLine, 1); + } + + return $indexToEnsureBlankLine; + } + + private function getTokenToInsert(string $currentContent, bool $isLastIndex): Token + { + $ending = $this->whitespacesConfig->getLineEnding(); + + $emptyLines = $isLastIndex ? $ending : $ending.$ending; + $indent = Preg::match('/^.*\R( *)$/s', $currentContent, $matches) ? $matches[1] : ''; + + return new Token([T_WHITESPACE, $emptyLines.$indent]); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/BlankLinesBeforeNamespaceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/BlankLinesBeforeNamespaceFixer.php new file mode 100644 index 00000000..4d2a78e7 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/BlankLinesBeforeNamespaceFixer.php @@ -0,0 +1,227 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\NamespaceNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use Symfony\Component\OptionsResolver\Options; + +/** + * @author Graham Campbell + * @author Greg Korba + */ +final class BlankLinesBeforeNamespaceFixer extends AbstractFixer implements WhitespacesAwareFixerInterface, ConfigurableFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Controls blank lines before a namespace declaration.', + [ + new CodeSample(" 1]), + new CodeSample(" 2]), + new CodeSample(" 2]), + new CodeSample(" 0, 'max_line_breaks' => 0]), + ] + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_NAMESPACE); + } + + /** + * {@inheritdoc} + * + * Must run after BlankLineAfterOpeningTagFixer, HeaderCommentFixer. + */ + public function getPriority(): int + { + return -31; + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('min_line_breaks', 'Minimum line breaks that should exist before namespace declaration.')) + ->setAllowedTypes(['int']) + ->setDefault(2) + ->setNormalizer(static function (Options $options, int $value): int { + if ($value < 0) { + throw new InvalidFixerConfigurationException( + (new self())->getName(), + 'Option `min_line_breaks` cannot be lower than 0.' + ); + } + + return $value; + }) + ->getOption(), + (new FixerOptionBuilder('max_line_breaks', 'Maximum line breaks that should exist before namespace declaration.')) + ->setAllowedTypes(['int']) + ->setDefault(2) + ->setNormalizer(static function (Options $options, int $value): int { + if ($value < 0) { + throw new InvalidFixerConfigurationException( + (new self())->getName(), + 'Option `max_line_breaks` cannot be lower than 0.' + ); + } + + if ($value < $options['min_line_breaks']) { + throw new InvalidFixerConfigurationException( + (new self())->getName(), + 'Option `max_line_breaks` cannot have lower value than `min_line_breaks`.' + ); + } + + return $value; + }) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + $token = $tokens[$index]; + + if ($token->isGivenKind(T_NAMESPACE)) { + $this->fixLinesBeforeNamespace( + $tokens, + $index, + $this->configuration['min_line_breaks'], + $this->configuration['max_line_breaks'] + ); + } + } + } + + /** + * Make sure # of line breaks prefixing namespace is within given range. + * + * @param int $expectedMin min. # of line breaks + * @param int $expectedMax max. # of line breaks + */ + protected function fixLinesBeforeNamespace(Tokens $tokens, int $index, int $expectedMin, int $expectedMax): void + { + // Let's determine the total numbers of new lines before the namespace + // and the opening token + $openingTokenIndex = null; + $precedingNewlines = 0; + $newlineInOpening = false; + $openingToken = null; + + for ($i = 1; $i <= 2; ++$i) { + if (isset($tokens[$index - $i])) { + $token = $tokens[$index - $i]; + + if ($token->isGivenKind(T_OPEN_TAG)) { + $openingToken = $token; + $openingTokenIndex = $index - $i; + $newlineInOpening = str_contains($token->getContent(), "\n"); + + if ($newlineInOpening) { + ++$precedingNewlines; + } + + break; + } + + if (false === $token->isGivenKind(T_WHITESPACE)) { + break; + } + + $precedingNewlines += substr_count($token->getContent(), "\n"); + } + } + + if ($precedingNewlines >= $expectedMin && $precedingNewlines <= $expectedMax) { + return; + } + + $previousIndex = $index - 1; + $previous = $tokens[$previousIndex]; + + if (0 === $expectedMax) { + // Remove all the previous new lines + if ($previous->isWhitespace()) { + $tokens->clearAt($previousIndex); + } + + // Remove new lines in opening token + if ($newlineInOpening) { + $tokens[$openingTokenIndex] = new Token([T_OPEN_TAG, rtrim($openingToken->getContent()).' ']); + } + + return; + } + + $lineEnding = $this->whitespacesConfig->getLineEnding(); + + // Allow only as many line breaks as configured: + // - keep as-is when current preceding line breaks are within configured range + // - use configured max line breaks if currently there is more preceding line breaks + // - use configured min line breaks if currently there is less preceding line breaks + $newlinesForWhitespaceToken = $precedingNewlines >= $expectedMax + ? $expectedMax + : max($precedingNewlines, $expectedMin); + + if (null !== $openingToken) { + // Use the configured line ending for the PHP opening tag + $content = rtrim($openingToken->getContent()); + $newContent = $content.$lineEnding; + $tokens[$openingTokenIndex] = new Token([T_OPEN_TAG, $newContent]); + --$newlinesForWhitespaceToken; + } + + if (0 === $newlinesForWhitespaceToken) { + // We have all the needed new lines in the opening tag + if ($previous->isWhitespace()) { + // Let's remove the previous token containing extra new lines + $tokens->clearAt($previousIndex); + } + + return; + } + + if ($previous->isWhitespace()) { + // Fix the previous whitespace token + $tokens[$previousIndex] = new Token( + [ + T_WHITESPACE, + str_repeat($lineEnding, $newlinesForWhitespaceToken).substr( + $previous->getContent(), + strrpos($previous->getContent(), "\n") + 1 + ), + ] + ); + } else { + // Add a new whitespace token + $tokens->insertAt($index, new Token([T_WHITESPACE, str_repeat($lineEnding, $newlinesForWhitespaceToken)])); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/CleanNamespaceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/CleanNamespaceFixer.php new file mode 100644 index 00000000..f39ed81d --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/CleanNamespaceFixer.php @@ -0,0 +1,108 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\NamespaceNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Tokenizer\Tokens; + +final class CleanNamespaceFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + $samples = []; + + foreach (['namespace Foo \\ Bar;', 'echo foo /* comment */ \\ bar();'] as $sample) { + $samples[] = new VersionSpecificCodeSample( + "isTokenKindFound(T_NS_SEPARATOR); + } + + /** + * {@inheritdoc} + * + * Must run before PhpUnitDataProviderReturnTypeFixer. + */ + public function getPriority(): int + { + return 3; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $count = $tokens->count(); + + for ($index = 0; $index < $count; ++$index) { + if ($tokens[$index]->isGivenKind(T_NS_SEPARATOR)) { + $previousIndex = $tokens->getPrevMeaningfulToken($index); + + $index = $this->fixNamespace( + $tokens, + $tokens[$previousIndex]->isGivenKind(T_STRING) ? $previousIndex : $index + ); + } + } + } + + /** + * @param int $index start of namespace + */ + private function fixNamespace(Tokens $tokens, int $index): int + { + $tillIndex = $index; + + // go to the end of the namespace + while ($tokens[$tillIndex]->isGivenKind([T_NS_SEPARATOR, T_STRING])) { + $tillIndex = $tokens->getNextMeaningfulToken($tillIndex); + } + + $tillIndex = $tokens->getPrevMeaningfulToken($tillIndex); + + $spaceIndices = []; + + for (; $index <= $tillIndex; ++$index) { + if ($tokens[$index]->isGivenKind(T_WHITESPACE)) { + $spaceIndices[] = $index; + } elseif ($tokens[$index]->isComment()) { + $tokens->clearAt($index); + } + } + + if ($tokens[$index - 1]->isWhitespace()) { + array_pop($spaceIndices); + } + + foreach ($spaceIndices as $i) { + $tokens->clearAt($i); + } + + return $index; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/NoBlankLinesBeforeNamespaceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/NoBlankLinesBeforeNamespaceFixer.php new file mode 100644 index 00000000..20dc38ea --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/NoBlankLinesBeforeNamespaceFixer.php @@ -0,0 +1,76 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\NamespaceNotation; + +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Graham Campbell + * + * @deprecated Use `blank_lines_before_namespace` with config: ['min_line_breaks' => 0, 'max_line_breaks' => 1] + */ +final class NoBlankLinesBeforeNamespaceFixer extends AbstractProxyFixer implements WhitespacesAwareFixerInterface, DeprecatedFixerInterface +{ + public function getSuccessorsNames(): array + { + return array_keys($this->proxyFixers); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_NAMESPACE); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'There should be no blank lines before a namespace declaration.', + [ + new CodeSample( + "configure([ + 'min_line_breaks' => 0, + 'max_line_breaks' => 1, + ]); + + return [ + $blankLineBeforeNamespace, + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/NoLeadingNamespaceWhitespaceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/NoLeadingNamespaceWhitespaceFixer.php new file mode 100644 index 00000000..0d866d4a --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/NoLeadingNamespaceWhitespaceFixer.php @@ -0,0 +1,95 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\NamespaceNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Bram Gotink + * @author Dariusz Rumiński + */ +final class NoLeadingNamespaceWhitespaceFixer extends AbstractFixer implements WhitespacesAwareFixerInterface +{ + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_NAMESPACE); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'The namespace declaration line shouldn\'t contain leading whitespace.', + [ + new CodeSample( + 'isGivenKind(T_NAMESPACE)) { + continue; + } + + $beforeNamespaceIndex = $index - 1; + $beforeNamespace = $tokens[$beforeNamespaceIndex]; + + if (!$beforeNamespace->isWhitespace()) { + if (!self::endsWithWhitespace($beforeNamespace->getContent())) { + $tokens->insertAt($index, new Token([T_WHITESPACE, $this->whitespacesConfig->getLineEnding()])); + } + + continue; + } + + $lastNewline = strrpos($beforeNamespace->getContent(), "\n"); + + if (false === $lastNewline) { + $beforeBeforeNamespace = $tokens[$index - 2]; + + if (self::endsWithWhitespace($beforeBeforeNamespace->getContent())) { + $tokens->clearAt($beforeNamespaceIndex); + } else { + $tokens[$beforeNamespaceIndex] = new Token([T_WHITESPACE, ' ']); + } + } else { + $tokens[$beforeNamespaceIndex] = new Token([T_WHITESPACE, substr($beforeNamespace->getContent(), 0, $lastNewline + 1)]); + } + } + } + + private static function endsWithWhitespace(string $str): bool + { + if ('' === $str) { + return false; + } + + return '' === trim(substr($str, -1)); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/SingleBlankLineBeforeNamespaceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/SingleBlankLineBeforeNamespaceFixer.php new file mode 100644 index 00000000..67de6a24 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/SingleBlankLineBeforeNamespaceFixer.php @@ -0,0 +1,75 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\NamespaceNotation; + +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Graham Campbell + * + * @deprecated Use `blank_lines_before_namespace` with config: ['min_line_breaks' => 2, 'max_line_breaks' => 2] (default) + */ +final class SingleBlankLineBeforeNamespaceFixer extends AbstractProxyFixer implements WhitespacesAwareFixerInterface, DeprecatedFixerInterface +{ + public function getSuccessorsNames(): array + { + return array_keys($this->proxyFixers); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'There should be exactly one blank line before a namespace declaration.', + [ + new CodeSample("isTokenKindFound(T_NAMESPACE); + } + + /** + * {@inheritdoc} + * + * Must run after HeaderCommentFixer. + */ + public function getPriority(): int + { + return parent::getPriority(); + } + + protected function createProxyFixers(): array + { + $blankLineBeforeNamespace = new BlankLinesBeforeNamespaceFixer(); + $blankLineBeforeNamespace->configure([ + 'min_line_breaks' => 2, + 'max_line_breaks' => 2, + ]); + + return [ + $blankLineBeforeNamespace, + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Naming/NoHomoglyphNamesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Naming/NoHomoglyphNamesFixer.php new file mode 100644 index 00000000..55c5ffda --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Naming/NoHomoglyphNamesFixer.php @@ -0,0 +1,230 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Naming; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Fred Cox + * @author Dariusz Rumiński + */ +final class NoHomoglyphNamesFixer extends AbstractFixer +{ + /** + * Used the program https://github.com/mcfedr/homoglyph-download + * to generate this list from + * http://homoglyphs.net/?text=abcdefghijklmnopqrstuvwxyz&lang=en&exc7=1&exc8=1&exc13=1&exc14=1. + * + * Symbols replaced include + * - Latin homoglyphs + * - IPA extensions + * - Greek and Coptic + * - Cyrillic + * - Cyrillic Supplement + * - Letterlike Symbols + * - Latin Numbers + * - Fullwidth Latin + * + * This is not the complete list of unicode homographs, but limited + * to those you are more likely to have typed/copied by accident + * + * @var array + */ + private static array $replacements = [ + 'O' => '0', + '0' => '0', + 'I' => '1', + '1' => '1', + '2' => '2', + '3' => '3', + '4' => '4', + '5' => '5', + '6' => '6', + '7' => '7', + '8' => '8', + '9' => '9', + 'Α' => 'A', + 'А' => 'A', + 'A' => 'A', + 'ʙ' => 'B', + 'Β' => 'B', + 'В' => 'B', + 'B' => 'B', + 'Ϲ' => 'C', + 'С' => 'C', + 'Ⅽ' => 'C', + 'C' => 'C', + 'Ⅾ' => 'D', + 'D' => 'D', + 'Ε' => 'E', + 'Е' => 'E', + 'E' => 'E', + 'Ϝ' => 'F', + 'F' => 'F', + 'ɢ' => 'G', + 'Ԍ' => 'G', + 'G' => 'G', + 'ʜ' => 'H', + 'Η' => 'H', + 'Н' => 'H', + 'H' => 'H', + 'l' => 'I', + 'Ι' => 'I', + 'І' => 'I', + 'Ⅰ' => 'I', + 'I' => 'I', + 'Ј' => 'J', + 'J' => 'J', + 'Κ' => 'K', + 'К' => 'K', + 'K' => 'K', + 'K' => 'K', + 'ʟ' => 'L', + 'Ⅼ' => 'L', + 'L' => 'L', + 'Μ' => 'M', + 'М' => 'M', + 'Ⅿ' => 'M', + 'M' => 'M', + 'ɴ' => 'N', + 'Ν' => 'N', + 'N' => 'N', + 'Ο' => 'O', + 'О' => 'O', + 'O' => 'O', + 'Ρ' => 'P', + 'Р' => 'P', + 'P' => 'P', + 'Q' => 'Q', + 'ʀ' => 'R', + 'R' => 'R', + 'Ѕ' => 'S', + 'S' => 'S', + 'Τ' => 'T', + 'Т' => 'T', + 'T' => 'T', + 'U' => 'U', + 'Ѵ' => 'V', + 'Ⅴ' => 'V', + 'V' => 'V', + 'W' => 'W', + 'Χ' => 'X', + 'Х' => 'X', + 'Ⅹ' => 'X', + 'X' => 'X', + 'ʏ' => 'Y', + 'Υ' => 'Y', + 'Ү' => 'Y', + 'Y' => 'Y', + 'Ζ' => 'Z', + 'Z' => 'Z', + '_' => '_', + 'ɑ' => 'a', + 'а' => 'a', + 'a' => 'a', + 'Ь' => 'b', + 'b' => 'b', + 'ϲ' => 'c', + 'с' => 'c', + 'ⅽ' => 'c', + 'c' => 'c', + 'ԁ' => 'd', + 'ⅾ' => 'd', + 'd' => 'd', + 'е' => 'e', + 'e' => 'e', + 'f' => 'f', + 'ɡ' => 'g', + 'g' => 'g', + 'һ' => 'h', + 'h' => 'h', + 'ɩ' => 'i', + 'і' => 'i', + 'ⅰ' => 'i', + 'i' => 'i', + 'ј' => 'j', + 'j' => 'j', + 'k' => 'k', + 'ⅼ' => 'l', + 'l' => 'l', + 'ⅿ' => 'm', + 'm' => 'm', + 'n' => 'n', + 'ο' => 'o', + 'о' => 'o', + 'o' => 'o', + 'р' => 'p', + 'p' => 'p', + 'q' => 'q', + 'r' => 'r', + 'ѕ' => 's', + 's' => 's', + 't' => 't', + 'u' => 'u', + 'ν' => 'v', + 'ѵ' => 'v', + 'ⅴ' => 'v', + 'v' => 'v', + 'ѡ' => 'w', + 'w' => 'w', + 'х' => 'x', + 'ⅹ' => 'x', + 'x' => 'x', + 'у' => 'y', + 'y' => 'y', + 'z' => 'z', + ]; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Replace accidental usage of homoglyphs (non ascii characters) in names.', + [new CodeSample("isAnyTokenKindsFound([T_VARIABLE, T_STRING]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind([T_VARIABLE, T_STRING])) { + continue; + } + + $replaced = Preg::replaceCallback('/[^[:ascii:]]/u', static fn (array $matches): string => self::$replacements[$matches[0]] ?? $matches[0], $token->getContent(), -1, $count); + + if ($count) { + $tokens->offsetSet($index, new Token([$token->getId(), $replaced])); + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/AssignNullCoalescingToCoalesceEqualFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/AssignNullCoalescingToCoalesceEqualFixer.php new file mode 100644 index 00000000..b1c37606 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/AssignNullCoalescingToCoalesceEqualFixer.php @@ -0,0 +1,71 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Operator; + +use PhpCsFixer\Fixer\AbstractShortOperatorFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class AssignNullCoalescingToCoalesceEqualFixer extends AbstractShortOperatorFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Use the null coalescing assignment operator `??=` where possible.', + [ + new CodeSample( + "isTokenKindFound(T_COALESCE); + } + + protected function isOperatorTokenCandidate(Tokens $tokens, int $index): bool + { + if (!$tokens[$index]->isGivenKind(T_COALESCE)) { + return false; + } + + // make sure after '??' does not contain '? :' + + $nextIndex = $tokens->getNextTokenOfKind($index, ['?', ';', [T_CLOSE_TAG]]); + + return !$tokens[$nextIndex]->equals('?'); + } + + protected function getReplacementToken(Token $token): Token + { + return new Token([T_COALESCE_EQUAL, '??=']); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/BinaryOperatorSpacesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/BinaryOperatorSpacesFixer.php new file mode 100644 index 00000000..3f95b189 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/BinaryOperatorSpacesFixer.php @@ -0,0 +1,938 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Operator; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; +use PhpCsFixer\Utils; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; + +/** + * @author Dariusz Rumiński + */ +final class BinaryOperatorSpacesFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** + * @internal + */ + public const SINGLE_SPACE = 'single_space'; + + /** + * @internal + */ + public const AT_LEAST_SINGLE_SPACE = 'at_least_single_space'; + + /** + * @internal + */ + public const NO_SPACE = 'no_space'; + + /** + * @internal + */ + public const ALIGN = 'align'; + + /** + * @internal + */ + public const ALIGN_BY_SCOPE = 'align_by_scope'; + + /** + * @internal + */ + public const ALIGN_SINGLE_SPACE = 'align_single_space'; + + /** + * @internal + */ + public const ALIGN_SINGLE_SPACE_BY_SCOPE = 'align_single_space_by_scope'; + + /** + * @internal + */ + public const ALIGN_SINGLE_SPACE_MINIMAL = 'align_single_space_minimal'; + + /** + * @internal + */ + public const ALIGN_SINGLE_SPACE_MINIMAL_BY_SCOPE = 'align_single_space_minimal_by_scope'; + + /** + * @internal + * + * @const Placeholder used as anchor for right alignment. + */ + public const ALIGN_PLACEHOLDER = "\x2 ALIGNABLE%d \x3"; + + /** + * @var string[] + */ + private const SUPPORTED_OPERATORS = [ + '=', + '*', + '/', + '%', + '<', + '>', + '|', + '^', + '+', + '-', + '&', + '&=', + '&&', + '||', + '.=', + '/=', + '=>', + '==', + '>=', + '===', + '!=', + '<>', + '!==', + '<=', + 'and', + 'or', + 'xor', + '-=', + '%=', + '*=', + '|=', + '+=', + '<<', + '<<=', + '>>', + '>>=', + '^=', + '**', + '**=', + '<=>', + '??', + '??=', + ]; + + /** + * Keep track of the deepest level ever achieved while + * parsing the code. Used later to replace alignment + * placeholders with spaces. + */ + private int $deepestLevel; + + /** + * Level counter of the current nest level. + * So one level alignments are not mixed with + * other level ones. + */ + private int $currentLevel; + + /** + * @var array + */ + private static array $allowedValues = [ + self::ALIGN, + self::ALIGN_BY_SCOPE, + self::ALIGN_SINGLE_SPACE, + self::ALIGN_SINGLE_SPACE_MINIMAL, + self::ALIGN_SINGLE_SPACE_BY_SCOPE, + self::ALIGN_SINGLE_SPACE_MINIMAL_BY_SCOPE, + self::SINGLE_SPACE, + self::NO_SPACE, + self::AT_LEAST_SINGLE_SPACE, + null, + ]; + + private TokensAnalyzer $tokensAnalyzer; + + /** + * @var array + */ + private array $alignOperatorTokens = []; + + /** + * @var array + */ + private array $operators = []; + + public function configure(array $configuration): void + { + parent::configure($configuration); + + $this->operators = $this->resolveOperatorsFromConfig(); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Binary operators should be surrounded by space as configured.', + [ + new CodeSample( + " ['=' => 'align', 'xor' => null]] + ), + new CodeSample( + ' ['+=' => 'align_single_space']] + ), + new CodeSample( + ' ['===' => 'align_single_space_minimal']] + ), + new CodeSample( + ' ['|' => 'no_space']] + ), + new CodeSample( + ' 1, + "baaaaaaaaaaar" => 11, +]; +', + ['operators' => ['=>' => 'single_space']] + ), + new CodeSample( + ' 12, + "baaaaaaaaaaar" => 13, + + "baz" => 1, +]; +', + ['operators' => ['=>' => 'align']] + ), + new CodeSample( + ' 12, + "baaaaaaaaaaar" => 13, + + "baz" => 1, +]; +', + ['operators' => ['=>' => 'align_by_scope']] + ), + new CodeSample( + ' 12, + "baaaaaaaaaaar" => 13, + + "baz" => 1, +]; +', + ['operators' => ['=>' => 'align_single_space']] + ), + new CodeSample( + ' 12, + "baaaaaaaaaaar" => 13, + + "baz" => 1, +]; +', + ['operators' => ['=>' => 'align_single_space_by_scope']] + ), + new CodeSample( + ' 12, + "baaaaaaaaaaar" => 13, + + "baz" => 1, +]; +', + ['operators' => ['=>' => 'align_single_space_minimal']] + ), + new CodeSample( + ' 12, + "baaaaaaaaaaar" => 13, + + "baz" => 1, +]; +', + ['operators' => ['=>' => 'align_single_space_minimal_by_scope']] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run after ArrayIndentationFixer, ArraySyntaxFixer, AssignNullCoalescingToCoalesceEqualFixer, ListSyntaxFixer, LongToShorthandOperatorFixer, ModernizeStrposFixer, NoMultilineWhitespaceAroundDoubleArrowFixer, NoUnsetCastFixer, PowToExponentiationFixer, StandardizeNotEqualsFixer, StrictComparisonFixer. + */ + public function getPriority(): int + { + return -32; + } + + public function isCandidate(Tokens $tokens): bool + { + return true; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $this->tokensAnalyzer = new TokensAnalyzer($tokens); + + // last and first tokens cannot be an operator + for ($index = $tokens->count() - 2; $index > 0; --$index) { + if (!$this->tokensAnalyzer->isBinaryOperator($index)) { + continue; + } + + if ('=' === $tokens[$index]->getContent()) { + $isDeclare = $this->isEqualPartOfDeclareStatement($tokens, $index); + if (false === $isDeclare) { + $this->fixWhiteSpaceAroundOperator($tokens, $index); + } else { + $index = $isDeclare; // skip `declare(foo ==bar)`, see `declare_equal_normalize` + } + } else { + $this->fixWhiteSpaceAroundOperator($tokens, $index); + } + + // previous of binary operator is now never an operator / previous of declare statement cannot be an operator + --$index; + } + + if (\count($this->alignOperatorTokens) > 0) { + $this->fixAlignment($tokens, $this->alignOperatorTokens); + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('default', 'Default fix strategy.')) + ->setDefault(self::SINGLE_SPACE) + ->setAllowedValues(self::$allowedValues) + ->getOption(), + (new FixerOptionBuilder('operators', 'Dictionary of `binary operator` => `fix strategy` values that differ from the default strategy. Supported are: '.Utils::naturalLanguageJoinWithBackticks(self::SUPPORTED_OPERATORS).'.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([static function (array $option): bool { + foreach ($option as $operator => $value) { + if (!\in_array($operator, self::SUPPORTED_OPERATORS, true)) { + throw new InvalidOptionsException( + sprintf( + 'Unexpected "operators" key, expected any of %s, got "%s".', + Utils::naturalLanguageJoin(self::SUPPORTED_OPERATORS), + \gettype($operator).'#'.$operator + ) + ); + } + + if (!\in_array($value, self::$allowedValues, true)) { + throw new InvalidOptionsException( + sprintf( + 'Unexpected value for operator "%s", expected any of %s, got "%s".', + $operator, + Utils::naturalLanguageJoin(array_map( + static fn ($value): string => Utils::toString($value), + self::$allowedValues + )), + \is_object($value) ? \get_class($value) : (null === $value ? 'null' : \gettype($value).'#'.$value) + ) + ); + } + } + + return true; + }]) + ->setDefault([]) + ->getOption(), + ]); + } + + private function fixWhiteSpaceAroundOperator(Tokens $tokens, int $index): void + { + $tokenContent = strtolower($tokens[$index]->getContent()); + + if (!\array_key_exists($tokenContent, $this->operators)) { + return; // not configured to be changed + } + + if (self::SINGLE_SPACE === $this->operators[$tokenContent]) { + $this->fixWhiteSpaceAroundOperatorToSingleSpace($tokens, $index); + + return; + } + + if (self::AT_LEAST_SINGLE_SPACE === $this->operators[$tokenContent]) { + $this->fixWhiteSpaceAroundOperatorToAtLeastSingleSpace($tokens, $index); + + return; + } + + if (self::NO_SPACE === $this->operators[$tokenContent]) { + $this->fixWhiteSpaceAroundOperatorToNoSpace($tokens, $index); + + return; + } + + // schedule for alignment + $this->alignOperatorTokens[$tokenContent] = $this->operators[$tokenContent]; + + if ( + self::ALIGN === $this->operators[$tokenContent] + || self::ALIGN_BY_SCOPE === $this->operators[$tokenContent] + ) { + return; + } + + // fix white space after operator + if ($tokens[$index + 1]->isWhitespace()) { + if ( + self::ALIGN_SINGLE_SPACE_MINIMAL === $this->operators[$tokenContent] + || self::ALIGN_SINGLE_SPACE_MINIMAL_BY_SCOPE === $this->operators[$tokenContent] + ) { + $tokens[$index + 1] = new Token([T_WHITESPACE, ' ']); + } + + return; + } + + $tokens->insertAt($index + 1, new Token([T_WHITESPACE, ' '])); + } + + private function fixWhiteSpaceAroundOperatorToSingleSpace(Tokens $tokens, int $index): void + { + // fix white space after operator + if ($tokens[$index + 1]->isWhitespace()) { + $content = $tokens[$index + 1]->getContent(); + if (' ' !== $content && !str_contains($content, "\n") && !$tokens[$tokens->getNextNonWhitespace($index + 1)]->isComment()) { + $tokens[$index + 1] = new Token([T_WHITESPACE, ' ']); + } + } else { + $tokens->insertAt($index + 1, new Token([T_WHITESPACE, ' '])); + } + + // fix white space before operator + if ($tokens[$index - 1]->isWhitespace()) { + $content = $tokens[$index - 1]->getContent(); + if (' ' !== $content && !str_contains($content, "\n") && !$tokens[$tokens->getPrevNonWhitespace($index - 1)]->isComment()) { + $tokens[$index - 1] = new Token([T_WHITESPACE, ' ']); + } + } else { + $tokens->insertAt($index, new Token([T_WHITESPACE, ' '])); + } + } + + private function fixWhiteSpaceAroundOperatorToAtLeastSingleSpace(Tokens $tokens, int $index): void + { + // fix white space after operator + if (!$tokens[$index + 1]->isWhitespace()) { + $tokens->insertAt($index + 1, new Token([T_WHITESPACE, ' '])); + } + + // fix white space before operator + if (!$tokens[$index - 1]->isWhitespace()) { + $tokens->insertAt($index, new Token([T_WHITESPACE, ' '])); + } + } + + private function fixWhiteSpaceAroundOperatorToNoSpace(Tokens $tokens, int $index): void + { + // fix white space after operator + if ($tokens[$index + 1]->isWhitespace()) { + $content = $tokens[$index + 1]->getContent(); + if (!str_contains($content, "\n") && !$tokens[$tokens->getNextNonWhitespace($index + 1)]->isComment()) { + $tokens->clearAt($index + 1); + } + } + + // fix white space before operator + if ($tokens[$index - 1]->isWhitespace()) { + $content = $tokens[$index - 1]->getContent(); + if (!str_contains($content, "\n") && !$tokens[$tokens->getPrevNonWhitespace($index - 1)]->isComment()) { + $tokens->clearAt($index - 1); + } + } + } + + /** + * @return false|int index of T_DECLARE where the `=` belongs to or `false` + */ + private function isEqualPartOfDeclareStatement(Tokens $tokens, int $index) + { + $prevMeaningfulIndex = $tokens->getPrevMeaningfulToken($index); + if ($tokens[$prevMeaningfulIndex]->isGivenKind(T_STRING)) { + $prevMeaningfulIndex = $tokens->getPrevMeaningfulToken($prevMeaningfulIndex); + if ($tokens[$prevMeaningfulIndex]->equals('(')) { + $prevMeaningfulIndex = $tokens->getPrevMeaningfulToken($prevMeaningfulIndex); + if ($tokens[$prevMeaningfulIndex]->isGivenKind(T_DECLARE)) { + return $prevMeaningfulIndex; + } + } + } + + return false; + } + + /** + * @return array + */ + private function resolveOperatorsFromConfig(): array + { + $operators = []; + + if (null !== $this->configuration['default']) { + foreach (self::SUPPORTED_OPERATORS as $operator) { + $operators[$operator] = $this->configuration['default']; + } + } + + foreach ($this->configuration['operators'] as $operator => $value) { + if (null === $value) { + unset($operators[$operator]); + } else { + $operators[$operator] = $value; + } + } + + return $operators; + } + + // Alignment logic related methods + + /** + * @param array $toAlign + */ + private function fixAlignment(Tokens $tokens, array $toAlign): void + { + $this->deepestLevel = 0; + $this->currentLevel = 0; + + foreach ($toAlign as $tokenContent => $alignStrategy) { + // This fixer works partially on Tokens and partially on string representation of code. + // During the process of fixing internal state of single Token may be affected by injecting ALIGN_PLACEHOLDER to its content. + // The placeholder will be resolved by `replacePlaceholders` method by removing placeholder or changing it into spaces. + // That way of fixing the code causes disturbances in marking Token as changed - if code is perfectly valid then placeholder + // still be injected and removed, which will cause the `changed` flag to be set. + // To handle that unwanted behavior we work on clone of Tokens collection and then override original collection with fixed collection. + $tokensClone = clone $tokens; + + if ('=>' === $tokenContent) { + $this->injectAlignmentPlaceholdersForArrow($tokensClone, 0, \count($tokens)); + } else { + $this->injectAlignmentPlaceholdersDefault($tokensClone, 0, \count($tokens), $tokenContent); + } + + // for all tokens that should be aligned but do not have anything to align with, fix spacing if needed + if ( + self::ALIGN_SINGLE_SPACE === $alignStrategy + || self::ALIGN_SINGLE_SPACE_MINIMAL === $alignStrategy + || self::ALIGN_SINGLE_SPACE_BY_SCOPE === $alignStrategy + || self::ALIGN_SINGLE_SPACE_MINIMAL_BY_SCOPE === $alignStrategy + ) { + if ('=>' === $tokenContent) { + for ($index = $tokens->count() - 2; $index > 0; --$index) { + if ($tokens[$index]->isGivenKind(T_DOUBLE_ARROW)) { // always binary operator, never part of declare statement + $this->fixWhiteSpaceBeforeOperator($tokensClone, $index, $alignStrategy); + } + } + } elseif ('=' === $tokenContent) { + for ($index = $tokens->count() - 2; $index > 0; --$index) { + if ('=' === $tokens[$index]->getContent() && !$this->isEqualPartOfDeclareStatement($tokens, $index) && $this->tokensAnalyzer->isBinaryOperator($index)) { + $this->fixWhiteSpaceBeforeOperator($tokensClone, $index, $alignStrategy); + } + } + } else { + for ($index = $tokens->count() - 2; $index > 0; --$index) { + $content = $tokens[$index]->getContent(); + if (strtolower($content) === $tokenContent && $this->tokensAnalyzer->isBinaryOperator($index)) { // never part of declare statement + $this->fixWhiteSpaceBeforeOperator($tokensClone, $index, $alignStrategy); + } + } + } + } + + $tokens->setCode($this->replacePlaceholders($tokensClone, $alignStrategy, $tokenContent)); + } + } + + private function injectAlignmentPlaceholdersDefault(Tokens $tokens, int $startAt, int $endAt, string $tokenContent): void + { + $newLineFoundSinceLastPlaceholder = true; + + for ($index = $startAt; $index < $endAt; ++$index) { + $token = $tokens[$index]; + $content = $token->getContent(); + + if (str_contains($content, "\n")) { + $newLineFoundSinceLastPlaceholder = true; + } + + if ( + strtolower($content) === $tokenContent + && $this->tokensAnalyzer->isBinaryOperator($index) + && ('=' !== $content || !$this->isEqualPartOfDeclareStatement($tokens, $index)) + && $newLineFoundSinceLastPlaceholder + ) { + $tokens[$index] = new Token(sprintf(self::ALIGN_PLACEHOLDER, $this->currentLevel).$content); + $newLineFoundSinceLastPlaceholder = false; + + continue; + } + + if ($token->isGivenKind(T_FN)) { + $from = $tokens->getNextMeaningfulToken($index); + $until = $this->tokensAnalyzer->getLastTokenIndexOfArrowFunction($index); + $this->injectAlignmentPlaceholders($tokens, $from + 1, $until - 1, $tokenContent); + $index = $until; + + continue; + } + + if ($token->isGivenKind([T_FUNCTION, T_CLASS])) { + $index = $tokens->getNextTokenOfKind($index, ['{', ';', '(']); + // We don't align `=` on multi-line definition of function parameters with default values + if ($tokens[$index]->equals('(')) { + $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + + continue; + } + + if ($tokens[$index]->equals(';')) { + continue; + } + + // Update the token to the `{` one in order to apply the following logic + $token = $tokens[$index]; + } + + if ($token->equals('{')) { + $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); + $this->injectAlignmentPlaceholders($tokens, $index + 1, $until - 1, $tokenContent); + $index = $until; + + continue; + } + + if ($token->equals('(')) { + $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + $this->injectAlignmentPlaceholders($tokens, $index + 1, $until - 1, $tokenContent); + $index = $until; + + continue; + } + + if ($token->equals('[')) { + $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE, $index); + + continue; + } + + if ($token->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) { + $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $index); + $this->injectAlignmentPlaceholders($tokens, $index + 1, $until - 1, $tokenContent); + $index = $until; + + continue; + } + } + } + + private function injectAlignmentPlaceholders(Tokens $tokens, int $from, int $until, string $tokenContent): void + { + // Only inject placeholders for multi-line code + if ($tokens->isPartialCodeMultiline($from, $until)) { + ++$this->deepestLevel; + $currentLevel = $this->currentLevel; + $this->currentLevel = $this->deepestLevel; + $this->injectAlignmentPlaceholdersDefault($tokens, $from, $until, $tokenContent); + $this->currentLevel = $currentLevel; + } + } + + private function injectAlignmentPlaceholdersForArrow(Tokens $tokens, int $startAt, int $endAt): void + { + $newLineFoundSinceLastPlaceholder = true; + $yieldFoundSinceLastPlaceholder = false; + + for ($index = $startAt; $index < $endAt; ++$index) { + /** @var Token $token */ + $token = $tokens[$index]; + $content = $token->getContent(); + + if (str_contains($content, "\n")) { + $newLineFoundSinceLastPlaceholder = true; + } + + if ($token->isGivenKind(T_YIELD)) { + $yieldFoundSinceLastPlaceholder = true; + } + + if ($token->isGivenKind(T_FN)) { + $yieldFoundSinceLastPlaceholder = false; + $from = $tokens->getNextMeaningfulToken($index); + $until = $this->tokensAnalyzer->getLastTokenIndexOfArrowFunction($index); + $this->injectArrayAlignmentPlaceholders($tokens, $from + 1, $until - 1); + $index = $until; + + continue; + } + + if ($token->isGivenKind(T_ARRAY)) { // don't use "$tokens->isArray()" here, short arrays are handled in the next case + $yieldFoundSinceLastPlaceholder = false; + $from = $tokens->getNextMeaningfulToken($index); + $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $from); + $index = $until; + + $this->injectArrayAlignmentPlaceholders($tokens, $from + 1, $until - 1); + + continue; + } + + if ($token->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) { + $yieldFoundSinceLastPlaceholder = false; + $from = $index; + $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $from); + $index = $until; + + $this->injectArrayAlignmentPlaceholders($tokens, $from + 1, $until - 1); + + continue; + } + + // no need to analyze for `isBinaryOperator` (always true), nor if part of declare statement (not valid PHP) + // there is also no need to analyse the second arrow of a line + if ($token->isGivenKind(T_DOUBLE_ARROW) && $newLineFoundSinceLastPlaceholder) { + if ($yieldFoundSinceLastPlaceholder) { + ++$this->deepestLevel; + ++$this->currentLevel; + } + $tokenContent = sprintf(self::ALIGN_PLACEHOLDER, $this->currentLevel).$token->getContent(); + + $nextToken = $tokens[$index + 1]; + if (!$nextToken->isWhitespace()) { + $tokenContent .= ' '; + } elseif ($nextToken->isWhitespace(" \t")) { + $tokens[$index + 1] = new Token([T_WHITESPACE, ' ']); + } + + $tokens[$index] = new Token([T_DOUBLE_ARROW, $tokenContent]); + $newLineFoundSinceLastPlaceholder = false; + $yieldFoundSinceLastPlaceholder = false; + + continue; + } + + if ($token->equals(';')) { + ++$this->deepestLevel; + ++$this->currentLevel; + + continue; + } + + if ($token->equals(',')) { + for ($i = $index; $i < $endAt - 1; ++$i) { + if (str_contains($tokens[$i - 1]->getContent(), "\n")) { + $newLineFoundSinceLastPlaceholder = true; + + break; + } + + if ($tokens[$i + 1]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN])) { + $arrayStartIndex = $tokens[$i + 1]->isGivenKind(T_ARRAY) + ? $tokens->getNextMeaningfulToken($i + 1) + : $i + 1; + $blockType = Tokens::detectBlockType($tokens[$arrayStartIndex]); + $arrayEndIndex = $tokens->findBlockEnd($blockType['type'], $arrayStartIndex); + + if ($tokens->isPartialCodeMultiline($arrayStartIndex, $arrayEndIndex)) { + break; + } + } + + ++$index; + } + } + + if ($token->equals('{')) { + $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); + $this->injectArrayAlignmentPlaceholders($tokens, $index + 1, $until - 1); + $index = $until; + + continue; + } + + if ($token->equals('(')) { + $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + $this->injectArrayAlignmentPlaceholders($tokens, $index + 1, $until - 1); + $index = $until; + + continue; + } + } + } + + private function injectArrayAlignmentPlaceholders(Tokens $tokens, int $from, int $until): void + { + // Only inject placeholders for multi-line arrays + if ($tokens->isPartialCodeMultiline($from, $until)) { + ++$this->deepestLevel; + $currentLevel = $this->currentLevel; + $this->currentLevel = $this->deepestLevel; + $this->injectAlignmentPlaceholdersForArrow($tokens, $from, $until); + $this->currentLevel = $currentLevel; + } + } + + private function fixWhiteSpaceBeforeOperator(Tokens $tokens, int $index, string $alignStrategy): void + { + // fix white space after operator is not needed as BinaryOperatorSpacesFixer took care of this (if strategy is _not_ ALIGN) + if (!$tokens[$index - 1]->isWhitespace()) { + $tokens->insertAt($index, new Token([T_WHITESPACE, ' '])); + + return; + } + + if ( + self::ALIGN_SINGLE_SPACE_MINIMAL !== $alignStrategy && self::ALIGN_SINGLE_SPACE_MINIMAL_BY_SCOPE !== $alignStrategy + || $tokens[$tokens->getPrevNonWhitespace($index - 1)]->isComment() + ) { + return; + } + + $content = $tokens[$index - 1]->getContent(); + if (' ' !== $content && !str_contains($content, "\n")) { + $tokens[$index - 1] = new Token([T_WHITESPACE, ' ']); + } + } + + /** + * Look for group of placeholders and provide vertical alignment. + */ + private function replacePlaceholders(Tokens $tokens, string $alignStrategy, string $tokenContent): string + { + $tmpCode = $tokens->generateCode(); + + for ($j = 0; $j <= $this->deepestLevel; ++$j) { + $placeholder = sprintf(self::ALIGN_PLACEHOLDER, $j); + + if (!str_contains($tmpCode, $placeholder)) { + continue; + } + + $lines = explode("\n", $tmpCode); + $groups = []; + $groupIndex = 0; + $groups[$groupIndex] = []; + + foreach ($lines as $index => $line) { + if (substr_count($line, $placeholder) > 0) { + $groups[$groupIndex][] = $index; + } elseif ( + self::ALIGN_BY_SCOPE !== $alignStrategy + && self::ALIGN_SINGLE_SPACE_BY_SCOPE !== $alignStrategy + && self::ALIGN_SINGLE_SPACE_MINIMAL_BY_SCOPE !== $alignStrategy + ) { + ++$groupIndex; + $groups[$groupIndex] = []; + } + } + + foreach ($groups as $group) { + if (\count($group) < 1) { + continue; + } + + if (self::ALIGN !== $alignStrategy) { + // move placeholders to match strategy + foreach ($group as $index) { + $currentPosition = strpos($lines[$index], $placeholder); + $before = substr($lines[$index], 0, $currentPosition); + + if ( + self::ALIGN_SINGLE_SPACE === $alignStrategy + || self::ALIGN_SINGLE_SPACE_BY_SCOPE === $alignStrategy + ) { + if (!str_ends_with($before, ' ')) { // if last char of before-content is not ' '; add it + $before .= ' '; + } + } elseif ( + self::ALIGN_SINGLE_SPACE_MINIMAL === $alignStrategy + || self::ALIGN_SINGLE_SPACE_MINIMAL_BY_SCOPE === $alignStrategy + ) { + if (!Preg::match('/^\h+$/', $before)) { // if indent; do not move, leave to other fixer + $before = rtrim($before).' '; + } + } + + $lines[$index] = $before.substr($lines[$index], $currentPosition); + } + } + + $rightmostSymbol = 0; + foreach ($group as $index) { + $rightmostSymbol = max($rightmostSymbol, mb_strpos($lines[$index], $placeholder)); + } + + foreach ($group as $index) { + $line = $lines[$index]; + $currentSymbol = mb_strpos($line, $placeholder); + $delta = abs($rightmostSymbol - $currentSymbol); + + if ($delta > 0) { + $line = str_replace($placeholder, str_repeat(' ', $delta).$placeholder, $line); + $lines[$index] = $line; + } + } + } + + $tmpCode = str_replace($placeholder, '', implode("\n", $lines)); + } + + return $tmpCode; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/ConcatSpaceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/ConcatSpaceFixer.php new file mode 100644 index 00000000..8c452604 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/ConcatSpaceFixer.php @@ -0,0 +1,160 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Operator; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class ConcatSpaceFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** + * @var null|string + */ + private $fixCallback; + + public function configure(array $configuration): void + { + parent::configure($configuration); + + if ('one' === $this->configuration['spacing']) { + $this->fixCallback = 'fixConcatenationToSingleSpace'; + } else { + $this->fixCallback = 'fixConcatenationToNoSpace'; + } + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Concatenation should be spaced according to configuration.', + [ + new CodeSample( + " 'none'] + ), + new CodeSample( + " 'one'] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run after NoUnneededControlParenthesesFixer, SingleLineThrowFixer. + */ + public function getPriority(): int + { + return 0; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound('.'); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $callBack = $this->fixCallback; + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + if ($tokens[$index]->equals('.')) { + $this->{$callBack}($tokens, $index); + } + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('spacing', 'Spacing to apply around concatenation operator.')) + ->setAllowedValues(['one', 'none']) + ->setDefault('none') + ->getOption(), + ]); + } + + /** + * @param int $index index of concatenation '.' token + */ + private function fixConcatenationToNoSpace(Tokens $tokens, int $index): void + { + $prevNonWhitespaceToken = $tokens[$tokens->getPrevNonWhitespace($index)]; + + if (!$prevNonWhitespaceToken->isGivenKind([T_LNUMBER, T_COMMENT, T_DOC_COMMENT]) || str_starts_with($prevNonWhitespaceToken->getContent(), '/*')) { + $tokens->removeLeadingWhitespace($index, " \t"); + } + + if (!$tokens[$tokens->getNextNonWhitespace($index)]->isGivenKind([T_LNUMBER, T_COMMENT, T_DOC_COMMENT])) { + $tokens->removeTrailingWhitespace($index, " \t"); + } + } + + /** + * @param int $index index of concatenation '.' token + */ + private function fixConcatenationToSingleSpace(Tokens $tokens, int $index): void + { + $this->fixWhiteSpaceAroundConcatToken($tokens, $index, 1); + $this->fixWhiteSpaceAroundConcatToken($tokens, $index, -1); + } + + /** + * @param int $index index of concatenation '.' token + * @param int $offset 1 or -1 + */ + private function fixWhiteSpaceAroundConcatToken(Tokens $tokens, int $index, int $offset): void + { + if (-1 !== $offset && 1 !== $offset) { + throw new \InvalidArgumentException(sprintf( + 'Expected `-1|1` for "$offset", got "%s"', + $offset + )); + } + + $offsetIndex = $index + $offset; + + if (!$tokens[$offsetIndex]->isWhitespace()) { + $tokens->insertAt($index + (1 === $offset ? 1 : 0), new Token([T_WHITESPACE, ' '])); + + return; + } + + if (str_contains($tokens[$offsetIndex]->getContent(), "\n")) { + return; + } + + if ($tokens[$index + $offset * 2]->isComment()) { + return; + } + + $tokens[$offsetIndex] = new Token([T_WHITESPACE, ' ']); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/IncrementStyleFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/IncrementStyleFixer.php new file mode 100644 index 00000000..4d2d9b8a --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/IncrementStyleFixer.php @@ -0,0 +1,166 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Operator; + +use PhpCsFixer\Fixer\AbstractIncrementOperatorFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Gregor Harlan + * @author Kuba Werłos + */ +final class IncrementStyleFixer extends AbstractIncrementOperatorFixer implements ConfigurableFixerInterface +{ + /** + * @internal + */ + public const STYLE_PRE = 'pre'; + + /** + * @internal + */ + public const STYLE_POST = 'post'; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Pre- or post-increment and decrement operators should be used if possible.', + [ + new CodeSample(" self::STYLE_POST] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before NoSpacesInsideParenthesisFixer, SpacesInsideParenthesesFixer. + * Must run after StandardizeIncrementFixer. + */ + public function getPriority(): int + { + return 15; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound([T_INC, T_DEC]); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('style', 'Whether to use pre- or post-increment and decrement operators.')) + ->setAllowedValues([self::STYLE_PRE, self::STYLE_POST]) + ->setDefault(self::STYLE_PRE) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + + for ($index = $tokens->count() - 1; 0 <= $index; --$index) { + $token = $tokens[$index]; + + if (!$token->isGivenKind([T_INC, T_DEC])) { + continue; + } + + if (self::STYLE_PRE === $this->configuration['style'] && $tokensAnalyzer->isUnarySuccessorOperator($index)) { + $nextToken = $tokens[$tokens->getNextMeaningfulToken($index)]; + + if (!$nextToken->equalsAny([';', ')'])) { + continue; + } + + $startIndex = $this->findStart($tokens, $index); + $prevToken = $tokens[$tokens->getPrevMeaningfulToken($startIndex)]; + + if ($prevToken->equalsAny([';', '{', '}', [T_OPEN_TAG], ')'])) { + $tokens->clearAt($index); + $tokens->insertAt($startIndex, clone $token); + } + } elseif (self::STYLE_POST === $this->configuration['style'] && $tokensAnalyzer->isUnaryPredecessorOperator($index)) { + $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; + + if (!$prevToken->equalsAny([';', '{', '}', [T_OPEN_TAG], ')'])) { + continue; + } + + $endIndex = $this->findEnd($tokens, $index); + $nextToken = $tokens[$tokens->getNextMeaningfulToken($endIndex)]; + + if ($nextToken->equalsAny([';', ')'])) { + $tokens->clearAt($index); + $tokens->insertAt($tokens->getNextNonWhitespace($endIndex), clone $token); + } + } + } + } + + private function findEnd(Tokens $tokens, int $index): int + { + $nextIndex = $tokens->getNextMeaningfulToken($index); + $nextToken = $tokens[$nextIndex]; + + while ($nextToken->equalsAny([ + '$', + '(', + '[', + [CT::T_DYNAMIC_PROP_BRACE_OPEN], + [CT::T_DYNAMIC_VAR_BRACE_OPEN], + [CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN], + [T_NS_SEPARATOR], + [T_STATIC], + [T_STRING], + [T_VARIABLE], + ])) { + $blockType = Tokens::detectBlockType($nextToken); + + if (null !== $blockType) { + $nextIndex = $tokens->findBlockEnd($blockType['type'], $nextIndex); + } + + $index = $nextIndex; + $nextIndex = $tokens->getNextMeaningfulToken($nextIndex); + $nextToken = $tokens[$nextIndex]; + } + + if ($nextToken->isObjectOperator()) { + return $this->findEnd($tokens, $nextIndex); + } + + if ($nextToken->isGivenKind(T_PAAMAYIM_NEKUDOTAYIM)) { + return $this->findEnd($tokens, $tokens->getNextMeaningfulToken($nextIndex)); + } + + return $index; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/LogicalOperatorsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/LogicalOperatorsFixer.php new file mode 100644 index 00000000..5e05aac6 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/LogicalOperatorsFixer.php @@ -0,0 +1,67 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Operator; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Haralan Dobrev + */ +final class LogicalOperatorsFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Use `&&` and `||` logical operators instead of `and` and `or`.', + [ + new CodeSample( + 'isAnyTokenKindsFound([T_LOGICAL_AND, T_LOGICAL_OR]); + } + + public function isRisky(): bool + { + return true; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if ($token->isGivenKind(T_LOGICAL_AND)) { + $tokens[$index] = new Token([T_BOOLEAN_AND, '&&']); + } elseif ($token->isGivenKind(T_LOGICAL_OR)) { + $tokens[$index] = new Token([T_BOOLEAN_OR, '||']); + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/LongToShorthandOperatorFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/LongToShorthandOperatorFixer.php new file mode 100644 index 00000000..ca57caa4 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/LongToShorthandOperatorFixer.php @@ -0,0 +1,138 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Operator; + +use PhpCsFixer\Fixer\AbstractShortOperatorFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +final class LongToShorthandOperatorFixer extends AbstractShortOperatorFixer +{ + /** + * @var array + */ + private static array $operators = [ + '+' => [T_PLUS_EQUAL, '+='], + '-' => [T_MINUS_EQUAL, '-='], + '*' => [T_MUL_EQUAL, '*='], + '/' => [T_DIV_EQUAL, '/='], + '&' => [T_AND_EQUAL, '&='], + '.' => [T_CONCAT_EQUAL, '.='], + '%' => [T_MOD_EQUAL, '%='], + '|' => [T_OR_EQUAL, '|='], + '^' => [T_XOR_EQUAL, '^='], + ]; + + /** + * @var string[] + */ + private array $operatorTypes; + + private TokensAnalyzer $tokensAnalyzer; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Shorthand notation for operators should be used if possible.', + [ + new CodeSample("isAnyTokenKindsFound(array_keys(self::$operators))) { + return true; + } + + // @TODO: drop condition when PHP 8.0 is required and the "&" issues went away + return \defined('T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG'); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $this->operatorTypes = array_keys(self::$operators); + $this->tokensAnalyzer = new TokensAnalyzer($tokens); + + parent::applyFix($file, $tokens); + } + + protected function isOperatorTokenCandidate(Tokens $tokens, int $index): bool + { + if (!$tokens[$index]->equalsAny($this->operatorTypes)) { + return false; + } + + while (null !== $index) { + $index = $tokens->getNextMeaningfulToken($index); + $otherToken = $tokens[$index]; + + if ($otherToken->equalsAny([';', [T_CLOSE_TAG]])) { + return true; + } + + // fast precedence check + if ($otherToken->equals('?') || $otherToken->isGivenKind(T_INSTANCEOF)) { + return false; + } + + $blockType = Tokens::detectBlockType($otherToken); + + if (null !== $blockType) { + if (false === $blockType['isStart']) { + return true; + } + + $index = $tokens->findBlockEnd($blockType['type'], $index); + + continue; + } + + // precedence check + if ($this->tokensAnalyzer->isBinaryOperator($index)) { + return false; + } + } + + return false; // unreachable, but keeps SCA happy + } + + protected function getReplacementToken(Token $token): Token + { + return new Token(self::$operators[$token->getContent()]); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NewWithBracesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NewWithBracesFixer.php new file mode 100644 index 00000000..1fad4c76 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NewWithBracesFixer.php @@ -0,0 +1,87 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Operator; + +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; + +/** + * @author Dariusz Rumiński + * + * @deprecated + */ +final class NewWithBracesFixer extends AbstractProxyFixer implements ConfigurableFixerInterface, DeprecatedFixerInterface +{ + private NewWithParenthesesFixer $newWithParenthesesFixer; + + public function __construct() + { + $this->newWithParenthesesFixer = new NewWithParenthesesFixer(); + + parent::__construct(); + } + + public function getDefinition(): FixerDefinitionInterface + { + $fixerDefinition = $this->newWithParenthesesFixer->getDefinition(); + + return new FixerDefinition( + 'All instances created with `new` keyword must (not) be followed by braces.', + $fixerDefinition->getCodeSamples(), + $fixerDefinition->getDescription(), + $fixerDefinition->getRiskyDescription(), + ); + } + + /** + * {@inheritdoc} + * + * Must run before ClassDefinitionFixer. + */ + public function getPriority(): int + { + return $this->newWithParenthesesFixer->getPriority(); + } + + public function configure(array $configuration): void + { + $this->newWithParenthesesFixer->configure($configuration); + + parent::configure($configuration); + } + + public function getSuccessorsNames(): array + { + return [ + $this->newWithParenthesesFixer->getName(), + ]; + } + + protected function createProxyFixers(): array + { + return [ + $this->newWithParenthesesFixer, + ]; + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return $this->newWithParenthesesFixer->createConfigurationDefinition(); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NewWithParenthesesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NewWithParenthesesFixer.php new file mode 100644 index 00000000..81f95ece --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NewWithParenthesesFixer.php @@ -0,0 +1,200 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Operator; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class NewWithParenthesesFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'All instances created with `new` keyword must (not) be followed by parentheses.', + [ + new CodeSample(" false] + ), + new CodeSample( + " false] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before ClassDefinitionFixer. + */ + public function getPriority(): int + { + return 37; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_NEW); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + static $nextTokenKinds = null; + + if (null === $nextTokenKinds) { + $nextTokenKinds = [ + '?', + ';', + ',', + '(', + ')', + '[', + ']', + ':', + '<', + '>', + '+', + '-', + '*', + '/', + '%', + '&', + '^', + '|', + [T_CLASS], + [T_IS_SMALLER_OR_EQUAL], + [T_IS_GREATER_OR_EQUAL], + [T_IS_EQUAL], + [T_IS_NOT_EQUAL], + [T_IS_IDENTICAL], + [T_IS_NOT_IDENTICAL], + [T_CLOSE_TAG], + [T_LOGICAL_AND], + [T_LOGICAL_OR], + [T_LOGICAL_XOR], + [T_BOOLEAN_AND], + [T_BOOLEAN_OR], + [T_SL], + [T_SR], + [T_INSTANCEOF], + [T_AS], + [T_DOUBLE_ARROW], + [T_POW], + [T_SPACESHIP], + [CT::T_ARRAY_SQUARE_BRACE_OPEN], + [CT::T_ARRAY_SQUARE_BRACE_CLOSE], + [CT::T_BRACE_CLASS_INSTANTIATION_OPEN], + [CT::T_BRACE_CLASS_INSTANTIATION_CLOSE], + ]; + + if (\defined('T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG')) { // @TODO: drop condition when PHP 8.1+ is required + $nextTokenKinds[] = [T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG]; + $nextTokenKinds[] = [T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG]; + } + } + + for ($index = $tokens->count() - 3; $index > 0; --$index) { + if (!$tokens[$index]->isGivenKind(T_NEW)) { + continue; + } + + $nextIndex = $tokens->getNextTokenOfKind($index, $nextTokenKinds); + + // new anonymous class definition + if ($tokens[$nextIndex]->isGivenKind(T_CLASS)) { + $nextIndex = $tokens->getNextMeaningfulToken($nextIndex); + + if (true === $this->configuration['anonymous_class']) { + $this->ensureParenthesesAt($tokens, $nextIndex); + } else { + $this->ensureNoParenthesesAt($tokens, $nextIndex); + } + + continue; + } + + // entrance into array index syntax - need to look for exit + + while ($tokens[$nextIndex]->equals('[') || $tokens[$nextIndex]->isGivenKind(CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN)) { + $nextIndex = $tokens->findBlockEnd(Tokens::detectBlockType($tokens[$nextIndex])['type'], $nextIndex); + $nextIndex = $tokens->getNextMeaningfulToken($nextIndex); + } + + if (true === $this->configuration['named_class']) { + $this->ensureParenthesesAt($tokens, $nextIndex); + } else { + $this->ensureNoParenthesesAt($tokens, $nextIndex); + } + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('named_class', 'Whether named classes should be followed by parentheses.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), + (new FixerOptionBuilder('anonymous_class', 'Whether anonymous classes should be followed by parentheses.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), + ]); + } + + private function ensureParenthesesAt(Tokens $tokens, int $index): void + { + $token = $tokens[$index]; + + if (!$token->equals('(') && !$token->isObjectOperator()) { + $tokens->insertAt( + $tokens->getPrevMeaningfulToken($index) + 1, + [new Token('('), new Token(')')] + ); + } + } + + private function ensureNoParenthesesAt(Tokens $tokens, int $index): void + { + if (!$tokens[$index]->equals('(')) { + return; + } + + $closingIndex = $tokens->getNextMeaningfulToken($index); + + // constructor has arguments - parentheses can not be removed + if (!$tokens[$closingIndex]->equals(')')) { + return; + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($closingIndex); + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NoSpaceAroundDoubleColonFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NoSpaceAroundDoubleColonFixer.php new file mode 100644 index 00000000..d897fff5 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NoSpaceAroundDoubleColonFixer.php @@ -0,0 +1,73 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Operator; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Tokens; + +final class NoSpaceAroundDoubleColonFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'There must be no space around double colons (also called Scope Resolution Operator or Paamayim Nekudotayim).', + [new CodeSample("isTokenKindFound(T_DOUBLE_COLON); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = \count($tokens) - 2; $index > 1; --$index) { + if ($tokens[$index]->isGivenKind(T_DOUBLE_COLON)) { + $this->removeSpace($tokens, $index, 1); + $this->removeSpace($tokens, $index, -1); + } + } + } + + /** + * @param -1|1 $direction + */ + private function removeSpace(Tokens $tokens, int $index, int $direction): void + { + if (!$tokens[$index + $direction]->isWhitespace()) { + return; + } + + if ($tokens[$tokens->getNonWhitespaceSibling($index, $direction)]->isComment()) { + return; + } + + $tokens->clearAt($index + $direction); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NoUselessConcatOperatorFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NoUselessConcatOperatorFixer.php new file mode 100644 index 00000000..6d9629b5 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NoUselessConcatOperatorFixer.php @@ -0,0 +1,339 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Operator; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class NoUselessConcatOperatorFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + private const STR_DOUBLE_QUOTE = 0; + private const STR_DOUBLE_QUOTE_VAR = 1; + private const STR_SINGLE_QUOTE = 2; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'There should not be useless concat operations.', + [ + new CodeSample(" true]), + ], + ); + } + + /** + * {@inheritdoc} + * + * Must run before DateTimeCreateFromFormatCallFixer, EregToPregFixer, PhpUnitDedicateAssertInternalTypeFixer, RegularCallableCallFixer, SetTypeToCastFixer. + * Must run after NoBinaryStringFixer, SingleQuoteFixer. + */ + public function getPriority(): int + { + return 5; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound('.') && $tokens->isAnyTokenKindsFound([T_CONSTANT_ENCAPSED_STRING, '"']); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index > 0; --$index) { + if (!$tokens[$index]->equals('.')) { + continue; + } + + $nextMeaningfulTokenIndex = $tokens->getNextMeaningfulToken($index); + + if ($this->containsLinebreak($tokens, $index, $nextMeaningfulTokenIndex)) { + continue; + } + + $secondOperand = $this->getConcatOperandType($tokens, $nextMeaningfulTokenIndex, 1); + + if (null === $secondOperand) { + continue; + } + + $prevMeaningfulTokenIndex = $tokens->getPrevMeaningfulToken($index); + + if ($this->containsLinebreak($tokens, $prevMeaningfulTokenIndex, $index)) { + continue; + } + + $firstOperand = $this->getConcatOperandType($tokens, $prevMeaningfulTokenIndex, -1); + + if (null === $firstOperand) { + continue; + } + + $this->fixConcatOperation($tokens, $firstOperand, $index, $secondOperand); + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('juggle_simple_strings', 'Allow for simple string quote juggling if it results in more concat-operations merges.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + + /** + * @param array{ + * start: int, + * end: int, + * type: self::STR_*, + * } $firstOperand + * @param array{ + * start: int, + * end: int, + * type: self::STR_*, + * } $secondOperand + */ + private function fixConcatOperation(Tokens $tokens, array $firstOperand, int $concatIndex, array $secondOperand): void + { + // if both operands are of the same type then these operands can always be merged + + if ( + (self::STR_DOUBLE_QUOTE === $firstOperand['type'] && self::STR_DOUBLE_QUOTE === $secondOperand['type']) + || (self::STR_SINGLE_QUOTE === $firstOperand['type'] && self::STR_SINGLE_QUOTE === $secondOperand['type']) + ) { + $this->mergeConstantEscapedStringOperands($tokens, $firstOperand, $concatIndex, $secondOperand); + + return; + } + + if (self::STR_DOUBLE_QUOTE_VAR === $firstOperand['type'] && self::STR_DOUBLE_QUOTE_VAR === $secondOperand['type']) { + $this->mergeConstantEscapedStringVarOperands($tokens, $firstOperand, $concatIndex, $secondOperand); + + return; + } + + // if any is double and the other is not, check for simple other, than merge with " + + $operands = [ + [$firstOperand, $secondOperand], + [$secondOperand, $firstOperand], + ]; + + foreach ($operands as $operandPair) { + [$operand1, $operand2] = $operandPair; + + if (self::STR_DOUBLE_QUOTE_VAR === $operand1['type'] && self::STR_DOUBLE_QUOTE === $operand2['type']) { + $this->mergeConstantEscapedStringVarOperands($tokens, $firstOperand, $concatIndex, $secondOperand); + + return; + } + + if (false === $this->configuration['juggle_simple_strings']) { + continue; + } + + if (self::STR_DOUBLE_QUOTE === $operand1['type'] && self::STR_SINGLE_QUOTE === $operand2['type']) { + $operantContent = $tokens[$operand2['start']]->getContent(); + + if ($this->isSimpleQuotedStringContent($operantContent)) { + $this->mergeConstantEscapedStringOperands($tokens, $firstOperand, $concatIndex, $secondOperand); + } + + return; + } + + if (self::STR_DOUBLE_QUOTE_VAR === $operand1['type'] && self::STR_SINGLE_QUOTE === $operand2['type']) { + $operantContent = $tokens[$operand2['start']]->getContent(); + + if ($this->isSimpleQuotedStringContent($operantContent)) { + $this->mergeConstantEscapedStringVarOperands($tokens, $firstOperand, $concatIndex, $secondOperand); + } + + return; + } + } + } + + /** + * @param -1|1 $direction + * + * @return null|array{ + * start: int, + * end: int, + * type: self::STR_*, + * } + */ + private function getConcatOperandType(Tokens $tokens, int $index, int $direction): ?array + { + if ($tokens[$index]->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) { + $firstChar = $tokens[$index]->getContent(); + + if ('b' === $firstChar[0] || 'B' === $firstChar[0]) { + return null; // we don't care about these, priorities are set to do deal with these cases + } + + return [ + 'start' => $index, + 'end' => $index, + 'type' => '"' === $firstChar[0] ? self::STR_DOUBLE_QUOTE : self::STR_SINGLE_QUOTE, + ]; + } + + if ($tokens[$index]->equals('"')) { + $end = $tokens->getTokenOfKindSibling($index, $direction, ['"']); + + return [ + 'start' => 1 === $direction ? $index : $end, + 'end' => 1 === $direction ? $end : $index, + 'type' => self::STR_DOUBLE_QUOTE_VAR, + ]; + } + + return null; + } + + /** + * @param array{ + * start: int, + * end: int, + * type: self::STR_*, + * } $firstOperand + * @param array{ + * start: int, + * end: int, + * type: self::STR_*, + * } $secondOperand + */ + private function mergeConstantEscapedStringOperands( + Tokens $tokens, + array $firstOperand, + int $concatOperatorIndex, + array $secondOperand + ): void { + $quote = self::STR_DOUBLE_QUOTE === $firstOperand['type'] || self::STR_DOUBLE_QUOTE === $secondOperand['type'] ? '"' : "'"; + $firstOperandTokenContent = $tokens[$firstOperand['start']]->getContent(); + $secondOperandTokenContent = $tokens[$secondOperand['start']]->getContent(); + + $tokens[$firstOperand['start']] = new Token( + [ + T_CONSTANT_ENCAPSED_STRING, + $quote.substr($firstOperandTokenContent, 1, -1).substr($secondOperandTokenContent, 1, -1).$quote, + ], + ); + + $this->clearConcatAndAround($tokens, $concatOperatorIndex); + $tokens->clearTokenAndMergeSurroundingWhitespace($secondOperand['start']); + } + + /** + * @param array{ + * start: int, + * end: int, + * type: self::STR_*, + * } $firstOperand + * @param array{ + * start: int, + * end: int, + * type: self::STR_*, + * } $secondOperand + */ + private function mergeConstantEscapedStringVarOperands( + Tokens $tokens, + array $firstOperand, + int $concatOperatorIndex, + array $secondOperand + ): void { + // build uo the new content + $newContent = ''; + + foreach ([$firstOperand, $secondOperand] as $operant) { + $operandContent = ''; + + for ($i = $operant['start']; $i <= $operant['end'];) { + $operandContent .= $tokens[$i]->getContent(); + $i = $tokens->getNextMeaningfulToken($i); + } + + $newContent .= substr($operandContent, 1, -1); + } + + // remove tokens making up the concat statement + + for ($i = $secondOperand['end']; $i >= $secondOperand['start'];) { + $tokens->clearTokenAndMergeSurroundingWhitespace($i); + $i = $tokens->getPrevMeaningfulToken($i); + } + + $this->clearConcatAndAround($tokens, $concatOperatorIndex); + + for ($i = $firstOperand['end']; $i > $firstOperand['start'];) { + $tokens->clearTokenAndMergeSurroundingWhitespace($i); + $i = $tokens->getPrevMeaningfulToken($i); + } + + // insert new tokens based on the new content + + $newTokens = Tokens::fromCode('overrideRange($firstOperand['start'], $firstOperand['start'], $insertTokens); + } + + private function clearConcatAndAround(Tokens $tokens, int $concatOperatorIndex): void + { + if ($tokens[$concatOperatorIndex + 1]->isWhitespace()) { + $tokens->clearTokenAndMergeSurroundingWhitespace($concatOperatorIndex + 1); + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($concatOperatorIndex); + + if ($tokens[$concatOperatorIndex - 1]->isWhitespace()) { + $tokens->clearTokenAndMergeSurroundingWhitespace($concatOperatorIndex - 1); + } + } + + private function isSimpleQuotedStringContent(string $candidate): bool + { + return !Preg::match('#[\$"\'\\\]#', substr($candidate, 1, -1)); + } + + private function containsLinebreak(Tokens $tokens, int $startIndex, int $endIndex): bool + { + for ($i = $endIndex; $i > $startIndex; --$i) { + if (Preg::match('/\R/', $tokens[$i]->getContent())) { + return true; + } + } + + return false; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NoUselessNullsafeOperatorFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NoUselessNullsafeOperatorFixer.php new file mode 100644 index 00000000..2754e152 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NoUselessNullsafeOperatorFixer.php @@ -0,0 +1,73 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Operator; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class NoUselessNullsafeOperatorFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'There should not be useless Null-safe operator `?->` used.', + [ + new VersionSpecificCodeSample( + 'parentMethod(); + } +} +', + new VersionSpecification(8_00_00) + ), + ] + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return \PHP_VERSION_ID >= 8_00_00 && $tokens->isAllTokenKindsFound([T_VARIABLE, T_NULLSAFE_OBJECT_OPERATOR]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + if (!$tokens[$index]->isGivenKind(T_NULLSAFE_OBJECT_OPERATOR)) { + continue; + } + + $nullsafeObjectOperatorIndex = $index; + $index = $tokens->getPrevMeaningfulToken($index); + + if (!$tokens[$index]->isGivenKind(T_VARIABLE)) { + continue; + } + + if ('$this' !== strtolower($tokens[$index]->getContent())) { + continue; + } + + $tokens[$nullsafeObjectOperatorIndex] = new Token([T_OBJECT_OPERATOR, '->']); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NotOperatorWithSpaceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NotOperatorWithSpaceFixer.php new file mode 100644 index 00000000..f40dcc22 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NotOperatorWithSpaceFixer.php @@ -0,0 +1,75 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Operator; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Javier Spagnoletti + */ +final class NotOperatorWithSpaceFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Logical NOT operators (`!`) should have leading and trailing whitespaces.', + [new CodeSample( + 'isTokenKindFound('!'); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + $token = $tokens[$index]; + + if ($token->equals('!')) { + if (!$tokens[$index + 1]->isWhitespace()) { + $tokens->insertAt($index + 1, new Token([T_WHITESPACE, ' '])); + } + + if (!$tokens[$index - 1]->isWhitespace()) { + $tokens->insertAt($index, new Token([T_WHITESPACE, ' '])); + } + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NotOperatorWithSuccessorSpaceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NotOperatorWithSuccessorSpaceFixer.php new file mode 100644 index 00000000..bf228b52 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NotOperatorWithSuccessorSpaceFixer.php @@ -0,0 +1,68 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Operator; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Javier Spagnoletti + */ +final class NotOperatorWithSuccessorSpaceFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Logical NOT operators (`!`) should have one trailing whitespace.', + [new CodeSample( + 'isTokenKindFound('!'); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + $token = $tokens[$index]; + + if ($token->equals('!')) { + $tokens->ensureWhitespaceAtIndex($index + 1, 0, ' '); + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/ObjectOperatorWithoutWhitespaceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/ObjectOperatorWithoutWhitespaceFixer.php new file mode 100644 index 00000000..fe6e7ac9 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/ObjectOperatorWithoutWhitespaceFixer.php @@ -0,0 +1,62 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Operator; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Fabien Potencier + * @author Dariusz Rumiński + */ +final class ObjectOperatorWithoutWhitespaceFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'There should not be space before or after object operators `->` and `?->`.', + [new CodeSample(" b;\n")] + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound(Token::getObjectOperatorKinds()); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + // [Structure] there should not be space before or after "->" or "?->" + foreach ($tokens as $index => $token) { + if (!$token->isObjectOperator()) { + continue; + } + + // clear whitespace before -> + if ($tokens[$index - 1]->isWhitespace(" \t") && !$tokens[$index - 2]->isComment()) { + $tokens->clearAt($index - 1); + } + + // clear whitespace after -> + if ($tokens[$index + 1]->isWhitespace(" \t") && !$tokens[$index + 2]->isComment()) { + $tokens->clearAt($index + 1); + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/OperatorLinebreakFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/OperatorLinebreakFixer.php new file mode 100644 index 00000000..ce388053 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/OperatorLinebreakFixer.php @@ -0,0 +1,279 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Operator; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\AlternativeSyntaxAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\GotoLabelAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\ReferenceAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\SwitchAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Kuba Werłos + */ +final class OperatorLinebreakFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + private const BOOLEAN_OPERATORS = [[T_BOOLEAN_AND], [T_BOOLEAN_OR], [T_LOGICAL_AND], [T_LOGICAL_OR], [T_LOGICAL_XOR]]; + + private string $position = 'beginning'; + + /** + * @var array|string> + */ + private array $operators = []; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Operators - when multiline - must always be at the beginning or at the end of the line.', + [ + new CodeSample(' 'end'] + ), + ] + ); + } + + public function configure(array $configuration): void + { + parent::configure($configuration); + + $this->position = $this->configuration['position']; + $this->operators = self::BOOLEAN_OPERATORS; + + if (false === $this->configuration['only_booleans']) { + $this->operators = array_merge($this->operators, self::getNonBooleanOperators()); + } + } + + public function isCandidate(Tokens $tokens): bool + { + return true; + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('only_booleans', 'Whether to limit operators to only boolean ones.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + (new FixerOptionBuilder('position', 'Whether to place operators at the beginning or at the end of the line.')) + ->setAllowedValues(['beginning', 'end']) + ->setDefault($this->position) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $referenceAnalyzer = new ReferenceAnalyzer(); + $gotoLabelAnalyzer = new GotoLabelAnalyzer(); + $alternativeSyntaxAnalyzer = new AlternativeSyntaxAnalyzer(); + + $index = $tokens->count(); + while ($index > 1) { + --$index; + + if (!$tokens[$index]->equalsAny($this->operators, false)) { + continue; + } + + if ($gotoLabelAnalyzer->belongsToGoToLabel($tokens, $index)) { + continue; + } + + if ($referenceAnalyzer->isReference($tokens, $index)) { + continue; + } + + if ($alternativeSyntaxAnalyzer->belongsToAlternativeSyntax($tokens, $index)) { + continue; + } + + if (SwitchAnalyzer::belongsToSwitch($tokens, $index)) { + continue; + } + + $operatorIndices = [$index]; + if ($tokens[$index]->equals(':')) { + /** @var int $prevIndex */ + $prevIndex = $tokens->getPrevMeaningfulToken($index); + if ($tokens[$prevIndex]->equals('?')) { + $operatorIndices = [$prevIndex, $index]; + $index = $prevIndex; + } + } + + $this->fixOperatorLinebreak($tokens, $operatorIndices); + } + } + + /** + * @param int[] $operatorIndices + */ + private function fixOperatorLinebreak(Tokens $tokens, array $operatorIndices): void + { + /** @var int $prevIndex */ + $prevIndex = $tokens->getPrevMeaningfulToken(min($operatorIndices)); + $indexStart = $prevIndex + 1; + + /** @var int $nextIndex */ + $nextIndex = $tokens->getNextMeaningfulToken(max($operatorIndices)); + $indexEnd = $nextIndex - 1; + + if (!$this->isMultiline($tokens, $indexStart, $indexEnd)) { + return; // operator is not surrounded by multiline whitespaces, do not touch it + } + + if ('beginning' === $this->position) { + if (!$this->isMultiline($tokens, max($operatorIndices), $indexEnd)) { + return; // operator already is placed correctly + } + $this->fixMoveToTheBeginning($tokens, $operatorIndices); + + return; + } + + if (!$this->isMultiline($tokens, $indexStart, min($operatorIndices))) { + return; // operator already is placed correctly + } + $this->fixMoveToTheEnd($tokens, $operatorIndices); + } + + /** + * @param int[] $operatorIndices + */ + private function fixMoveToTheBeginning(Tokens $tokens, array $operatorIndices): void + { + /** @var int $prevIndex */ + $prevIndex = $tokens->getNonEmptySibling(min($operatorIndices), -1); + + /** @var int $nextIndex */ + $nextIndex = $tokens->getNextMeaningfulToken(max($operatorIndices)); + + for ($i = $nextIndex - 1; $i > max($operatorIndices); --$i) { + if ($tokens[$i]->isWhitespace() && Preg::match('/\R/u', $tokens[$i]->getContent())) { + $isWhitespaceBefore = $tokens[$prevIndex]->isWhitespace(); + $inserts = $this->getReplacementsAndClear($tokens, $operatorIndices, -1); + if ($isWhitespaceBefore) { + $inserts[] = new Token([T_WHITESPACE, ' ']); + } + $tokens->insertAt($nextIndex, $inserts); + + break; + } + } + } + + /** + * @param int[] $operatorIndices + */ + private function fixMoveToTheEnd(Tokens $tokens, array $operatorIndices): void + { + /** @var int $prevIndex */ + $prevIndex = $tokens->getPrevMeaningfulToken(min($operatorIndices)); + + /** @var int $nextIndex */ + $nextIndex = $tokens->getNonEmptySibling(max($operatorIndices), 1); + + for ($i = $prevIndex + 1; $i < max($operatorIndices); ++$i) { + if ($tokens[$i]->isWhitespace() && Preg::match('/\R/u', $tokens[$i]->getContent())) { + $isWhitespaceAfter = $tokens[$nextIndex]->isWhitespace(); + $inserts = $this->getReplacementsAndClear($tokens, $operatorIndices, 1); + if ($isWhitespaceAfter) { + array_unshift($inserts, new Token([T_WHITESPACE, ' '])); + } + $tokens->insertAt($prevIndex + 1, $inserts); + + break; + } + } + } + + /** + * @param int[] $indices + * + * @return Token[] + */ + private function getReplacementsAndClear(Tokens $tokens, array $indices, int $direction): array + { + return array_map( + static function (int $index) use ($tokens, $direction): Token { + $clone = $tokens[$index]; + + if ($tokens[$index + $direction]->isWhitespace()) { + $tokens->clearAt($index + $direction); + } + + $tokens->clearAt($index); + + return $clone; + }, + $indices + ); + } + + private function isMultiline(Tokens $tokens, int $indexStart, int $indexEnd): bool + { + for ($index = $indexStart; $index <= $indexEnd; ++$index) { + if (str_contains($tokens[$index]->getContent(), "\n")) { + return true; + } + } + + return false; + } + + /** + * @return list + */ + private static function getNonBooleanOperators(): array + { + return array_merge( + [ + '%', '&', '*', '+', '-', '.', '/', ':', '<', '=', '>', '?', '^', '|', + [T_AND_EQUAL], [T_CONCAT_EQUAL], [T_DIV_EQUAL], [T_DOUBLE_ARROW], [T_IS_EQUAL], [T_IS_GREATER_OR_EQUAL], + [T_IS_IDENTICAL], [T_IS_NOT_EQUAL], [T_IS_NOT_IDENTICAL], [T_IS_SMALLER_OR_EQUAL], [T_MINUS_EQUAL], + [T_MOD_EQUAL], [T_MUL_EQUAL], [T_OR_EQUAL], [T_PAAMAYIM_NEKUDOTAYIM], [T_PLUS_EQUAL], [T_POW], + [T_POW_EQUAL], [T_SL], [T_SL_EQUAL], [T_SR], [T_SR_EQUAL], [T_XOR_EQUAL], + [T_COALESCE], [T_SPACESHIP], + ], + array_map(static fn (int $id): array => [$id], Token::getObjectOperatorKinds()), + ); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/StandardizeIncrementFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/StandardizeIncrementFixer.php new file mode 100644 index 00000000..1aaae169 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/StandardizeIncrementFixer.php @@ -0,0 +1,122 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Operator; + +use PhpCsFixer\Fixer\AbstractIncrementOperatorFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author ntzm + */ +final class StandardizeIncrementFixer extends AbstractIncrementOperatorFixer +{ + private const EXPRESSION_END_TOKENS = [ + ';', + ')', + ']', + ',', + ':', + [CT::T_DYNAMIC_PROP_BRACE_CLOSE], + [CT::T_DYNAMIC_VAR_BRACE_CLOSE], + [T_CLOSE_TAG], + ]; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Increment and decrement operators should be used if possible.', + [ + new CodeSample("isAnyTokenKindsFound([T_PLUS_EQUAL, T_MINUS_EQUAL]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index > 0; --$index) { + $expressionEnd = $tokens[$index]; + if (!$expressionEnd->equalsAny(self::EXPRESSION_END_TOKENS)) { + continue; + } + + $numberIndex = $tokens->getPrevMeaningfulToken($index); + $number = $tokens[$numberIndex]; + if (!$number->isGivenKind(T_LNUMBER) || '1' !== $number->getContent()) { + continue; + } + + $operatorIndex = $tokens->getPrevMeaningfulToken($numberIndex); + $operator = $tokens[$operatorIndex]; + if (!$operator->isGivenKind([T_PLUS_EQUAL, T_MINUS_EQUAL])) { + continue; + } + + $startIndex = $this->findStart($tokens, $operatorIndex); + + $this->clearRangeLeaveComments( + $tokens, + $tokens->getPrevMeaningfulToken($operatorIndex) + 1, + $numberIndex + ); + + $tokens->insertAt( + $startIndex, + new Token($operator->isGivenKind(T_PLUS_EQUAL) ? [T_INC, '++'] : [T_DEC, '--']) + ); + } + } + + /** + * Clear tokens in the given range unless they are comments. + */ + private function clearRangeLeaveComments(Tokens $tokens, int $indexStart, int $indexEnd): void + { + for ($i = $indexStart; $i <= $indexEnd; ++$i) { + $token = $tokens[$i]; + + if ($token->isComment()) { + continue; + } + + if ($token->isWhitespace("\n\r")) { + continue; + } + + $tokens->clearAt($i); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/StandardizeNotEqualsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/StandardizeNotEqualsFixer.php new file mode 100644 index 00000000..2dab6092 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/StandardizeNotEqualsFixer.php @@ -0,0 +1,60 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Operator; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class StandardizeNotEqualsFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Replace all `<>` with `!=`.', + [new CodeSample(" \$c;\n")] + ); + } + + /** + * {@inheritdoc} + * + * Must run before BinaryOperatorSpacesFixer. + */ + public function getPriority(): int + { + return 0; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_IS_NOT_EQUAL); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if ($token->isGivenKind(T_IS_NOT_EQUAL)) { + $tokens[$index] = new Token([T_IS_NOT_EQUAL, '!=']); + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/TernaryOperatorSpacesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/TernaryOperatorSpacesFixer.php new file mode 100644 index 00000000..758c7939 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/TernaryOperatorSpacesFixer.php @@ -0,0 +1,131 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Operator; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\AlternativeSyntaxAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\GotoLabelAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\SwitchAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class TernaryOperatorSpacesFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Standardize spaces around ternary operator.', + [new CodeSample("isAllTokenKindsFound(['?', ':']); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $alternativeSyntaxAnalyzer = new AlternativeSyntaxAnalyzer(); + $gotoLabelAnalyzer = new GotoLabelAnalyzer(); + $ternaryOperatorIndices = []; + + foreach ($tokens as $index => $token) { + if (!$token->equalsAny(['?', ':'])) { + continue; + } + + if (SwitchAnalyzer::belongsToSwitch($tokens, $index)) { + continue; + } + + if ($alternativeSyntaxAnalyzer->belongsToAlternativeSyntax($tokens, $index)) { + continue; + } + + if ($gotoLabelAnalyzer->belongsToGoToLabel($tokens, $index)) { + continue; + } + + $ternaryOperatorIndices[] = $index; + } + + foreach (array_reverse($ternaryOperatorIndices) as $index) { + $token = $tokens[$index]; + + if ($token->equals('?')) { + $nextNonWhitespaceIndex = $tokens->getNextNonWhitespace($index); + + if ($tokens[$nextNonWhitespaceIndex]->equals(':')) { + // for `$a ?: $b` remove spaces between `?` and `:` + $tokens->ensureWhitespaceAtIndex($index + 1, 0, ''); + } else { + // for `$a ? $b : $c` ensure space after `?` + $this->ensureWhitespaceExistence($tokens, $index + 1, true); + } + + // for `$a ? $b : $c` ensure space before `?` + $this->ensureWhitespaceExistence($tokens, $index - 1, false); + + continue; + } + + if ($token->equals(':')) { + // for `$a ? $b : $c` ensure space after `:` + $this->ensureWhitespaceExistence($tokens, $index + 1, true); + + $prevNonWhitespaceToken = $tokens[$tokens->getPrevNonWhitespace($index)]; + + if (!$prevNonWhitespaceToken->equals('?')) { + // for `$a ? $b : $c` ensure space before `:` + $this->ensureWhitespaceExistence($tokens, $index - 1, false); + } + } + } + } + + private function ensureWhitespaceExistence(Tokens $tokens, int $index, bool $after): void + { + if ($tokens[$index]->isWhitespace()) { + if ( + !str_contains($tokens[$index]->getContent(), "\n") + && !$tokens[$index - 1]->isComment() + ) { + $tokens[$index] = new Token([T_WHITESPACE, ' ']); + } + + return; + } + + $index += $after ? 0 : 1; + $tokens->insertAt($index, new Token([T_WHITESPACE, ' '])); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/TernaryToElvisOperatorFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/TernaryToElvisOperatorFixer.php new file mode 100644 index 00000000..191d8bf9 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/TernaryToElvisOperatorFixer.php @@ -0,0 +1,216 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Operator; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\RangeAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Tokens; + +final class TernaryToElvisOperatorFixer extends AbstractFixer +{ + /** + * Lower precedence and other valid preceding tokens. + * + * Ordered by most common types first. + * + * @var list + */ + private const VALID_BEFORE_ENDTYPES = [ + '=', + [T_OPEN_TAG], + [T_OPEN_TAG_WITH_ECHO], + '(', + ',', + ';', + '[', + '{', + '}', + [CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN], + [T_AND_EQUAL], // &= + [T_CONCAT_EQUAL], // .= + [T_DIV_EQUAL], // /= + [T_MINUS_EQUAL], // -= + [T_MOD_EQUAL], // %= + [T_MUL_EQUAL], // *= + [T_OR_EQUAL], // |= + [T_PLUS_EQUAL], // += + [T_POW_EQUAL], // **= + [T_SL_EQUAL], // <<= + [T_SR_EQUAL], // >>= + [T_XOR_EQUAL], // ^= + ]; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Use the Elvis operator `?:` where possible.', + [ + new CodeSample( + "isTokenKindFound('?'); + } + + public function isRisky(): bool + { + return true; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = \count($tokens) - 5; $index > 1; --$index) { + if (!$tokens[$index]->equals('?')) { + continue; + } + + $nextIndex = $tokens->getNextMeaningfulToken($index); + + if ($tokens[$nextIndex]->equals(':')) { + continue; // Elvis is alive! + } + + // get and check what is before the `?` operator + + $beforeOperator = $this->getBeforeOperator($tokens, $index); + + if (null === $beforeOperator) { + continue; // contains something we cannot fix because of priorities + } + + // get what is after the `?` token + + $afterOperator = $this->getAfterOperator($tokens, $index); + + // if before and after the `?` operator are the same (in meaningful matter), clear after + + if (RangeAnalyzer::rangeEqualsRange($tokens, $beforeOperator, $afterOperator)) { + $this->clearMeaningfulFromRange($tokens, $afterOperator); + } + } + } + + /** + * @return ?array{start: int, end: int} null if contains ++/-- operator + */ + private function getBeforeOperator(Tokens $tokens, int $index): ?array + { + $blockEdgeDefinitions = Tokens::getBlockEdgeDefinitions(); + $index = $tokens->getPrevMeaningfulToken($index); + $before = ['end' => $index]; + + while (!$tokens[$index]->equalsAny(self::VALID_BEFORE_ENDTYPES)) { + if ($tokens[$index]->isGivenKind([T_INC, T_DEC])) { + return null; + } + + $blockType = Tokens::detectBlockType($tokens[$index]); + + if (null === $blockType || $blockType['isStart']) { + $before['start'] = $index; + $index = $tokens->getPrevMeaningfulToken($index); + + continue; + } + + $blockType = $blockEdgeDefinitions[$blockType['type']]; + $openCount = 1; + + do { + $index = $tokens->getPrevMeaningfulToken($index); + + if ($tokens[$index]->isGivenKind([T_INC, T_DEC])) { + return null; + } + + if ($tokens[$index]->equals($blockType['start'])) { + ++$openCount; + + continue; + } + + if ($tokens[$index]->equals($blockType['end'])) { + --$openCount; + } + } while (1 >= $openCount); + + $before['start'] = $index; + $index = $tokens->getPrevMeaningfulToken($index); + } + + if (!isset($before['start'])) { + return null; + } + + return $before; + } + + /** + * @return array{start: int, end: int} + */ + private function getAfterOperator(Tokens $tokens, int $index): array + { + $index = $tokens->getNextMeaningfulToken($index); + $after = ['start' => $index]; + + while (!$tokens[$index]->equals(':')) { + $blockType = Tokens::detectBlockType($tokens[$index]); + + if (null !== $blockType) { + $index = $tokens->findBlockEnd($blockType['type'], $index); + } + + $after['end'] = $index; + $index = $tokens->getNextMeaningfulToken($index); + } + + return $after; + } + + /** + * @param array{start: int, end: int} $range + */ + private function clearMeaningfulFromRange(Tokens $tokens, array $range): void + { + // $range['end'] must be meaningful! + for ($i = $range['end']; $i >= $range['start']; $i = $tokens->getPrevMeaningfulToken($i)) { + $tokens->clearTokenAndMergeSurroundingWhitespace($i); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/TernaryToNullCoalescingFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/TernaryToNullCoalescingFixer.php new file mode 100644 index 00000000..83cf9337 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/TernaryToNullCoalescingFixer.php @@ -0,0 +1,224 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Operator; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Filippo Tessarotto + */ +final class TernaryToNullCoalescingFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Use `null` coalescing operator `??` where possible. Requires PHP >= 7.0.', + [ + new CodeSample( + "isTokenKindFound(T_ISSET); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $issetIndices = array_keys($tokens->findGivenKind(T_ISSET)); + + while ($issetIndex = array_pop($issetIndices)) { + $this->fixIsset($tokens, $issetIndex); + } + } + + /** + * @param int $index of `T_ISSET` token + */ + private function fixIsset(Tokens $tokens, int $index): void + { + $prevTokenIndex = $tokens->getPrevMeaningfulToken($index); + + if ($this->isHigherPrecedenceAssociativityOperator($tokens[$prevTokenIndex])) { + return; + } + + $startBraceIndex = $tokens->getNextTokenOfKind($index, ['(']); + $endBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startBraceIndex); + + $ternaryQuestionMarkIndex = $tokens->getNextMeaningfulToken($endBraceIndex); + + if (!$tokens[$ternaryQuestionMarkIndex]->equals('?')) { + return; // we are not in a ternary operator + } + + // search what is inside the isset() + $issetTokens = $this->getMeaningfulSequence($tokens, $startBraceIndex, $endBraceIndex); + + if ($this->hasChangingContent($issetTokens)) { + return; // some weird stuff inside the isset + } + + $issetCode = $issetTokens->generateCode(); + + if ('$this' === $issetCode) { + return; // null coalescing operator does not with $this + } + + // search what is inside the middle argument of ternary operator + $ternaryColonIndex = $tokens->getNextTokenOfKind($ternaryQuestionMarkIndex, [':']); + $ternaryFirstOperandTokens = $this->getMeaningfulSequence($tokens, $ternaryQuestionMarkIndex, $ternaryColonIndex); + + if ($issetCode !== $ternaryFirstOperandTokens->generateCode()) { + return; // regardless of non-meaningful tokens, the operands are different + } + + $ternaryFirstOperandIndex = $tokens->getNextMeaningfulToken($ternaryQuestionMarkIndex); + + // preserve comments and spaces + $comments = []; + $commentStarted = false; + + for ($loopIndex = $index; $loopIndex < $ternaryFirstOperandIndex; ++$loopIndex) { + if ($tokens[$loopIndex]->isComment()) { + $comments[] = $tokens[$loopIndex]; + $commentStarted = true; + } elseif ($commentStarted) { + if ($tokens[$loopIndex]->isWhitespace()) { + $comments[] = $tokens[$loopIndex]; + } + + $commentStarted = false; + } + } + + $tokens[$ternaryColonIndex] = new Token([T_COALESCE, '??']); + $tokens->overrideRange($index, $ternaryFirstOperandIndex - 1, $comments); + } + + /** + * Get the sequence of meaningful tokens and returns a new Tokens instance. + * + * @param int $start start index + * @param int $end end index + */ + private function getMeaningfulSequence(Tokens $tokens, int $start, int $end): Tokens + { + $sequence = []; + $index = $start; + + while ($index < $end) { + $index = $tokens->getNextMeaningfulToken($index); + + if ($index >= $end || null === $index) { + break; + } + + $sequence[] = $tokens[$index]; + } + + return Tokens::fromArray($sequence); + } + + /** + * Check if the requested token is an operator computed + * before the ternary operator along with the `isset()`. + */ + private function isHigherPrecedenceAssociativityOperator(Token $token): bool + { + static $operatorsPerId = [ + T_ARRAY_CAST => true, + T_BOOLEAN_AND => true, + T_BOOLEAN_OR => true, + T_BOOL_CAST => true, + T_COALESCE => true, + T_DEC => true, + T_DOUBLE_CAST => true, + T_INC => true, + T_INT_CAST => true, + T_IS_EQUAL => true, + T_IS_GREATER_OR_EQUAL => true, + T_IS_IDENTICAL => true, + T_IS_NOT_EQUAL => true, + T_IS_NOT_IDENTICAL => true, + T_IS_SMALLER_OR_EQUAL => true, + T_OBJECT_CAST => true, + T_POW => true, + T_SL => true, + T_SPACESHIP => true, + T_SR => true, + T_STRING_CAST => true, + T_UNSET_CAST => true, + ]; + + static $operatorsPerContent = [ + '!', + '%', + '&', + '*', + '+', + '-', + '/', + ':', + '^', + '|', + '~', + '.', + ]; + + return isset($operatorsPerId[$token->getId()]) || $token->equalsAny($operatorsPerContent); + } + + /** + * Check if the `isset()` content may change if called multiple times. + * + * @param Tokens $tokens The original token list + */ + private function hasChangingContent(Tokens $tokens): bool + { + static $operatorsPerId = [ + T_DEC, + T_INC, + T_YIELD, + T_YIELD_FROM, + ]; + + foreach ($tokens as $token) { + if ($token->isGivenKind($operatorsPerId) || $token->equals('(')) { + return true; + } + } + + return false; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/UnaryOperatorSpacesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/UnaryOperatorSpacesFixer.php new file mode 100644 index 00000000..873e9326 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/UnaryOperatorSpacesFixer.php @@ -0,0 +1,105 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Operator; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Gregor Harlan + * @author Dariusz Rumiński + */ +final class UnaryOperatorSpacesFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Unary operators should be placed adjacent to their operands.', + [ + new CodeSample(" false] + ), + new CodeSample( + ' true] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before NotOperatorWithSpaceFixer, NotOperatorWithSuccessorSpaceFixer. + */ + public function getPriority(): int + { + return 0; + } + + public function isCandidate(Tokens $tokens): bool + { + return true; + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('only_dec_inc', 'Limit to increment and decrement operators.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + if (true === $this->configuration['only_dec_inc'] && !$tokens[$index]->isGivenKind([T_DEC, T_INC])) { + continue; + } + + if ($tokensAnalyzer->isUnarySuccessorOperator($index)) { + if (!$tokens[$tokens->getPrevNonWhitespace($index)]->isComment()) { + $tokens->removeLeadingWhitespace($index); + } + + continue; + } + + if ($tokensAnalyzer->isUnaryPredecessorOperator($index)) { + $tokens->removeTrailingWhitespace($index); + + continue; + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/BlankLineAfterOpeningTagFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/BlankLineAfterOpeningTagFixer.php new file mode 100644 index 00000000..d4af8196 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/BlankLineAfterOpeningTagFixer.php @@ -0,0 +1,88 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\PhpTag; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Ceeram + */ +final class BlankLineAfterOpeningTagFixer extends AbstractFixer implements WhitespacesAwareFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Ensure there is no code on the same line as the PHP open tag and it is followed by a blank line.', + [new CodeSample("isMonolithicPhp() && !$tokens->isTokenKindFound(T_OPEN_TAG_WITH_ECHO); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $lineEnding = $this->whitespacesConfig->getLineEnding(); + + $newlineFound = false; + foreach ($tokens as $token) { + if (($token->isWhitespace() || $token->isGivenKind(T_OPEN_TAG)) && str_contains($token->getContent(), "\n")) { + $newlineFound = true; + + break; + } + } + + // ignore one-line files + if (!$newlineFound) { + return; + } + + $openTagIndex = $tokens[0]->isGivenKind(T_INLINE_HTML) ? 1 : 0; + $token = $tokens[$openTagIndex]; + + if (!str_contains($token->getContent(), "\n")) { + $tokens[$openTagIndex] = new Token([$token->getId(), rtrim($token->getContent()).$lineEnding]); + } + + $newLineIndex = $openTagIndex + 1; + if (isset($tokens[$newLineIndex]) && !str_contains($tokens[$newLineIndex]->getContent(), "\n")) { + if ($tokens[$newLineIndex]->isWhitespace()) { + $tokens[$newLineIndex] = new Token([T_WHITESPACE, $lineEnding.$tokens[$newLineIndex]->getContent()]); + } else { + $tokens->insertAt($newLineIndex, new Token([T_WHITESPACE, $lineEnding])); + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/EchoTagSyntaxFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/EchoTagSyntaxFixer.php new file mode 100644 index 00000000..7c61daad --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/EchoTagSyntaxFixer.php @@ -0,0 +1,256 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\PhpTag; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Michele Locati + */ +final class EchoTagSyntaxFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** @internal */ + public const OPTION_FORMAT = 'format'; + + /** @internal */ + public const OPTION_SHORTEN_SIMPLE_STATEMENTS_ONLY = 'shorten_simple_statements_only'; + + /** @internal */ + public const OPTION_LONG_FUNCTION = 'long_function'; + + /** @internal */ + public const FORMAT_SHORT = 'short'; + + /** @internal */ + public const FORMAT_LONG = 'long'; + + /** @internal */ + public const LONG_FUNCTION_ECHO = 'echo'; + + /** @internal */ + public const LONG_FUNCTION_PRINT = 'print'; + + private const SUPPORTED_FORMAT_OPTIONS = [ + self::FORMAT_LONG, + self::FORMAT_SHORT, + ]; + + private const SUPPORTED_LONGFUNCTION_OPTIONS = [ + self::LONG_FUNCTION_ECHO, + self::LONG_FUNCTION_PRINT, + ]; + + public function getDefinition(): FixerDefinitionInterface + { + $sample = <<<'EOT' + + + + + + EOT; + + return new FixerDefinition( + 'Replaces short-echo ` self::FORMAT_LONG]), + new CodeSample($sample, [self::OPTION_FORMAT => self::FORMAT_LONG, self::OPTION_LONG_FUNCTION => self::LONG_FUNCTION_PRINT]), + new CodeSample($sample, [self::OPTION_FORMAT => self::FORMAT_SHORT]), + new CodeSample($sample, [self::OPTION_FORMAT => self::FORMAT_SHORT, self::OPTION_SHORTEN_SIMPLE_STATEMENTS_ONLY => false]), + ], + null + ); + } + + /** + * {@inheritdoc} + * + * Must run before NoMixedEchoPrintFixer. + */ + public function getPriority(): int + { + return 0; + } + + public function isCandidate(Tokens $tokens): bool + { + if (self::FORMAT_SHORT === $this->configuration[self::OPTION_FORMAT]) { + return $tokens->isAnyTokenKindsFound([T_ECHO, T_PRINT]); + } + + return $tokens->isTokenKindFound(T_OPEN_TAG_WITH_ECHO); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder(self::OPTION_FORMAT, 'The desired language construct.')) + ->setAllowedValues(self::SUPPORTED_FORMAT_OPTIONS) + ->setDefault(self::FORMAT_LONG) + ->getOption(), + (new FixerOptionBuilder(self::OPTION_LONG_FUNCTION, 'The function to be used to expand the short echo tags.')) + ->setAllowedValues(self::SUPPORTED_LONGFUNCTION_OPTIONS) + ->setDefault(self::LONG_FUNCTION_ECHO) + ->getOption(), + (new FixerOptionBuilder(self::OPTION_SHORTEN_SIMPLE_STATEMENTS_ONLY, 'Render short-echo tags only in case of simple code.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + if (self::FORMAT_SHORT === $this->configuration[self::OPTION_FORMAT]) { + $this->longToShort($tokens); + } else { + $this->shortToLong($tokens); + } + } + + private function longToShort(Tokens $tokens): void + { + $count = $tokens->count(); + + for ($index = 0; $index < $count; ++$index) { + if (!$tokens[$index]->isGivenKind(T_OPEN_TAG)) { + continue; + } + + $nextMeaningful = $tokens->getNextMeaningfulToken($index); + + if (null === $nextMeaningful) { + return; + } + + if (!$tokens[$nextMeaningful]->isGivenKind([T_ECHO, T_PRINT])) { + $index = $nextMeaningful; + + continue; + } + + if (true === $this->configuration[self::OPTION_SHORTEN_SIMPLE_STATEMENTS_ONLY] && $this->isComplexCode($tokens, $nextMeaningful + 1)) { + $index = $nextMeaningful; + + continue; + } + + $newTokens = $this->buildLongToShortTokens($tokens, $index, $nextMeaningful); + $tokens->overrideRange($index, $nextMeaningful, $newTokens); + $count = $tokens->count(); + } + } + + private function shortToLong(Tokens $tokens): void + { + if (self::LONG_FUNCTION_PRINT === $this->configuration[self::OPTION_LONG_FUNCTION]) { + $echoToken = [T_PRINT, 'print']; + } else { + $echoToken = [T_ECHO, 'echo']; + } + + $index = -1; + + while (true) { + $index = $tokens->getNextTokenOfKind($index, [[T_OPEN_TAG_WITH_ECHO]]); + + if (null === $index) { + return; + } + + $replace = [new Token([T_OPEN_TAG, 'isWhitespace()) { + $replace[] = new Token([T_WHITESPACE, ' ']); + } + + $tokens->overrideRange($index, $index, $replace); + ++$index; + } + } + + /** + * Check if $tokens, starting at $index, contains "complex code", that is, the content + * of the echo tag contains more than a simple "echo something". + * + * This is done by a very quick test: if the tag contains non-whitespace tokens after + * a semicolon, we consider it as "complex". + * + * @example `` is false (not complex) + * @example `` is false (not "complex") + * @example `` is true ("complex") + */ + private function isComplexCode(Tokens $tokens, int $index): bool + { + $semicolonFound = false; + + for ($count = $tokens->count(); $index < $count; ++$index) { + $token = $tokens[$index]; + + if ($token->isGivenKind(T_CLOSE_TAG)) { + return false; + } + + if (';' === $token->getContent()) { + $semicolonFound = true; + } elseif ($semicolonFound && !$token->isWhitespace()) { + return true; + } + } + + return false; + } + + /** + * Builds the list of tokens that replace a long echo sequence. + * + * @return list + */ + private function buildLongToShortTokens(Tokens $tokens, int $openTagIndex, int $echoTagIndex): array + { + $result = [new Token([T_OPEN_TAG_WITH_ECHO, 'getNextNonWhitespace($openTagIndex); + + if ($start === $echoTagIndex) { + // No non-whitespace tokens between $openTagIndex and $echoTagIndex + return $result; + } + + // Find the last non-whitespace index before $echoTagIndex + $end = $echoTagIndex - 1; + + while ($tokens[$end]->isWhitespace()) { + --$end; + } + + // Copy the non-whitespace tokens between $openTagIndex and $echoTagIndex + for ($index = $start; $index <= $end; ++$index) { + $result[] = clone $tokens[$index]; + } + + return $result; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/FullOpeningTagFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/FullOpeningTagFixer.php new file mode 100644 index 00000000..61186ccd --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/FullOpeningTagFixer.php @@ -0,0 +1,123 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\PhpTag; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Fixer for rules defined in PSR1 ¶2.1. + * + * @author Dariusz Rumiński + */ +final class FullOpeningTagFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'PHP code must use the long `generateCode(); + + // replace all echo ' echo ' $token) { + if ($token->isGivenKind(T_OPEN_TAG)) { + $tokenContent = $token->getContent(); + $possibleOpenContent = substr($content, $tokensOldContentLength, 5); + + if (false === $possibleOpenContent || 'isGivenKind([T_COMMENT, T_DOC_COMMENT, T_CONSTANT_ENCAPSED_STRING, T_ENCAPSED_AND_WHITESPACE, T_STRING])) { + $tokenContent = ''; + $tokenContentLength = 0; + $parts = explode('getContent()); + $iLast = \count($parts) - 1; + + foreach ($parts as $i => $part) { + $tokenContent .= $part; + $tokenContentLength += \strlen($part); + + if ($i !== $iLast) { + $originalTokenContent = substr($content, $tokensOldContentLength + $tokenContentLength, 5); + if ('getId(), $tokenContent]); + $token = $newTokens[$index]; + } + + $tokensOldContentLength += \strlen($token->getContent()); + } + + $tokens->overrideRange(0, $tokens->count() - 1, $newTokens); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/LinebreakAfterOpeningTagFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/LinebreakAfterOpeningTagFixer.php new file mode 100644 index 00000000..2a827161 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/LinebreakAfterOpeningTagFixer.php @@ -0,0 +1,68 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\PhpTag; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Ceeram + */ +final class LinebreakAfterOpeningTagFixer extends AbstractFixer implements WhitespacesAwareFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Ensure there is no code on the same line as the PHP open tag.', + [new CodeSample("isMonolithicPhp() && !$tokens->isTokenKindFound(T_OPEN_TAG_WITH_ECHO); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $openTagIndex = $tokens[0]->isGivenKind(T_INLINE_HTML) ? 1 : 0; + + // ignore if linebreak already present + if (str_contains($tokens[$openTagIndex]->getContent(), "\n")) { + return; + } + + $newlineFound = false; + foreach ($tokens as $token) { + if (($token->isWhitespace() || $token->isGivenKind(T_OPEN_TAG)) && str_contains($token->getContent(), "\n")) { + $newlineFound = true; + + break; + } + } + + // ignore one-line files + if (!$newlineFound) { + return; + } + + $tokens[$openTagIndex] = new Token([T_OPEN_TAG, rtrim($tokens[$openTagIndex]->getContent()).$this->whitespacesConfig->getLineEnding()]); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/NoClosingTagFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/NoClosingTagFixer.php new file mode 100644 index 00000000..14c40c03 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/NoClosingTagFixer.php @@ -0,0 +1,59 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\PhpTag; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Fixer for rules defined in PSR2 ¶2.2. + * + * @author Dariusz Rumiński + */ +final class NoClosingTagFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'The closing `?>` tag MUST be omitted from files containing only PHP.', + [new CodeSample("\n")] + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return \count($tokens) >= 2 && $tokens->isMonolithicPhp() && $tokens->isTokenKindFound(T_CLOSE_TAG); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $closeTags = $tokens->findGivenKind(T_CLOSE_TAG); + $index = array_key_first($closeTags); + + if (isset($tokens[$index - 1]) && $tokens[$index - 1]->isWhitespace()) { + $tokens->clearAt($index - 1); + } + $tokens->clearAt($index); + + $prevIndex = $tokens->getPrevMeaningfulToken($index); + if (!$tokens[$prevIndex]->equalsAny([';', '}', [T_OPEN_TAG]])) { + $tokens->insertAt($prevIndex + 1, new Token(';')); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitConstructFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitConstructFixer.php new file mode 100644 index 00000000..58c2c7f6 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitConstructFixer.php @@ -0,0 +1,198 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\PhpUnit; + +use PhpCsFixer\Fixer\AbstractPhpUnitFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class PhpUnitConstructFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface +{ + /** + * @var array + */ + private static array $assertionFixers = [ + 'assertSame' => 'fixAssertPositive', + 'assertEquals' => 'fixAssertPositive', + 'assertNotEquals' => 'fixAssertNegative', + 'assertNotSame' => 'fixAssertNegative', + ]; + + public function isRisky(): bool + { + return true; + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'PHPUnit assertion method calls like `->assertSame(true, $foo)` should be written with dedicated method like `->assertTrue($foo)`.', + [ + new CodeSample( + 'assertEquals(false, $b); + $this->assertSame(true, $a); + $this->assertNotEquals(null, $c); + $this->assertNotSame(null, $d); + } +} +' + ), + new CodeSample( + 'assertEquals(false, $b); + $this->assertSame(true, $a); + $this->assertNotEquals(null, $c); + $this->assertNotSame(null, $d); + } +} +', + ['assertions' => ['assertSame', 'assertNotSame']] + ), + ], + null, + 'Fixer could be risky if one is overriding PHPUnit\'s native methods.' + ); + } + + /** + * {@inheritdoc} + * + * Must run before PhpUnitDedicateAssertFixer. + */ + public function getPriority(): int + { + return -8; + } + + protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void + { + // no assertions to be fixed - fast return + if ([] === $this->configuration['assertions']) { + return; + } + + foreach ($this->configuration['assertions'] as $assertionMethod) { + $assertionFixer = self::$assertionFixers[$assertionMethod]; + + for ($index = $startIndex; $index < $endIndex; ++$index) { + $index = $this->{$assertionFixer}($tokens, $index, $assertionMethod); + + if (null === $index) { + break; + } + } + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('assertions', 'List of assertion methods to fix.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([new AllowedValueSubset(array_keys(self::$assertionFixers))]) + ->setDefault([ + 'assertEquals', + 'assertSame', + 'assertNotEquals', + 'assertNotSame', + ]) + ->getOption(), + ]); + } + + private function fixAssertNegative(Tokens $tokens, int $index, string $method): ?int + { + static $map = [ + 'false' => 'assertNotFalse', + 'null' => 'assertNotNull', + 'true' => 'assertNotTrue', + ]; + + return $this->fixAssert($map, $tokens, $index, $method); + } + + private function fixAssertPositive(Tokens $tokens, int $index, string $method): ?int + { + static $map = [ + 'false' => 'assertFalse', + 'null' => 'assertNull', + 'true' => 'assertTrue', + ]; + + return $this->fixAssert($map, $tokens, $index, $method); + } + + /** + * @param array $map + */ + private function fixAssert(array $map, Tokens $tokens, int $index, string $method): ?int + { + $functionsAnalyzer = new FunctionsAnalyzer(); + + $sequence = $tokens->findSequence( + [ + [T_STRING, $method], + '(', + ], + $index + ); + + if (null === $sequence) { + return null; + } + + $sequenceIndices = array_keys($sequence); + + if (!$functionsAnalyzer->isTheSameClassCall($tokens, $sequenceIndices[0])) { + return null; + } + + $sequenceIndices[2] = $tokens->getNextMeaningfulToken($sequenceIndices[1]); + $firstParameterToken = $tokens[$sequenceIndices[2]]; + + if (!$firstParameterToken->isNativeConstant()) { + return $sequenceIndices[2]; + } + + $sequenceIndices[3] = $tokens->getNextMeaningfulToken($sequenceIndices[2]); + + // return if first method argument is an expression, not value + if (!$tokens[$sequenceIndices[3]]->equals(',')) { + return $sequenceIndices[3]; + } + + $tokens[$sequenceIndices[0]] = new Token([T_STRING, $map[strtolower($firstParameterToken->getContent())]]); + $tokens->clearRange($sequenceIndices[2], $tokens->getNextNonWhitespace($sequenceIndices[3]) - 1); + + return $sequenceIndices[3]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDataProviderNameFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDataProviderNameFixer.php new file mode 100644 index 00000000..c42123b8 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDataProviderNameFixer.php @@ -0,0 +1,172 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\PhpUnit; + +use PhpCsFixer\Fixer\AbstractPhpUnitFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\DataProviderAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Kuba Werłos + */ +final class PhpUnitDataProviderNameFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Data provider names must match the name of the test.', + [ + new CodeSample( + ' 'data_', + 'suffix' => '', + ] + ), + new CodeSample( + ' 'provides', + 'suffix' => 'Data', + ] + ), + ], + null, + 'Fixer could be risky if one is calling data provider by name as function.' + ); + } + + public function getConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('prefix', 'Prefix that replaces "test".')) + ->setAllowedTypes(['string']) + ->setDefault('provide') + ->getOption(), + (new FixerOptionBuilder('suffix', 'Suffix to be present at the end.')) + ->setAllowedTypes(['string']) + ->setDefault('Cases') + ->getOption(), + ]); + } + + public function getPriority(): int + { + return 0; + } + + public function isRisky(): bool + { + return true; + } + + protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void + { + $dataProviderAnalyzer = new DataProviderAnalyzer(); + foreach ($dataProviderAnalyzer->getDataProviders($tokens, $startIndex, $endIndex) as $dataProviderAnalysis) { + if (\count($dataProviderAnalysis->getUsageIndices()) > 1) { + continue; + } + + $usageIndex = $dataProviderAnalysis->getUsageIndices()[0]; + if (substr_count($tokens[$usageIndex]->getContent(), '@dataProvider') > 1) { + continue; + } + + $testNameIndex = $tokens->getNextTokenOfKind($usageIndex, [[T_STRING]]); + + $dataProviderNewName = $this->getProviderNameForTestName($tokens[$testNameIndex]->getContent()); + if (null !== $tokens->findSequence([[T_FUNCTION], [T_STRING, $dataProviderNewName]], $startIndex, $endIndex)) { + continue; + } + + $tokens[$dataProviderAnalysis->getNameIndex()] = new Token([T_STRING, $dataProviderNewName]); + + $newCommentContent = Preg::replace( + sprintf('/(@dataProvider\s+)%s/', $dataProviderAnalysis->getName()), + sprintf('$1%s', $dataProviderNewName), + $tokens[$usageIndex]->getContent(), + ); + + $tokens[$usageIndex] = new Token([T_DOC_COMMENT, $newCommentContent]); + } + } + + private function getProviderNameForTestName(string $name): string + { + $name = Preg::replace('/^test_*/i', '', $name); + + if ('' === $this->configuration['prefix']) { + $name = lcfirst($name); + } elseif ('_' !== substr($this->configuration['prefix'], -1)) { + $name = ucfirst($name); + } + + return $this->configuration['prefix'].$name.$this->configuration['suffix']; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDataProviderReturnTypeFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDataProviderReturnTypeFixer.php new file mode 100644 index 00000000..4c077585 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDataProviderReturnTypeFixer.php @@ -0,0 +1,120 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\PhpUnit; + +use PhpCsFixer\Fixer\AbstractPhpUnitFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\DataProviderAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Kuba Werłos + */ +final class PhpUnitDataProviderReturnTypeFixer extends AbstractPhpUnitFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'The return type of PHPUnit data provider must be `iterable`.', + [ + new CodeSample( + 'getDataProviders($tokens, $startIndex, $endIndex)) as $dataProviderAnalysis) { + $typeAnalysis = $functionsAnalyzer->getFunctionReturnType($tokens, $dataProviderAnalysis->getNameIndex()); + + if (null === $typeAnalysis) { + $argumentsStart = $tokens->getNextTokenOfKind($dataProviderAnalysis->getNameIndex(), ['(']); + $argumentsEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $argumentsStart); + + $tokens->insertAt( + $argumentsEnd + 1, + [ + new Token([CT::T_TYPE_COLON, ':']), + new Token([T_WHITESPACE, ' ']), + new Token([T_STRING, 'iterable']), + ], + ); + + continue; + } + + if ('iterable' === $typeAnalysis->getName()) { + continue; + } + + $typeStartIndex = $tokens->getNextMeaningfulToken($typeAnalysis->getStartIndex() - 1); + $typeEndIndex = $typeAnalysis->getEndIndex(); + + // @TODO: drop condition and it's body when PHP 8+ is required + if ($tokens->generatePartialCode($typeStartIndex, $typeEndIndex) !== $typeAnalysis->getName()) { + continue; + } + + $tokens->overrideRange($typeStartIndex, $typeEndIndex, [new Token([T_STRING, 'iterable'])]); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDataProviderStaticFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDataProviderStaticFixer.php new file mode 100644 index 00000000..a2860325 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDataProviderStaticFixer.php @@ -0,0 +1,131 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\PhpUnit; + +use PhpCsFixer\Fixer\AbstractPhpUnitFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\DataProviderAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Kuba Werłos + */ +final class PhpUnitDataProviderStaticFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Data providers must be static.', + [ + new CodeSample( + 'getData1(); } + public function provideSomethingCases2() { self::getData2(); } +} +', + ['force' => true] + ), + new CodeSample( + 'getData1(); } + public function provideSomething2Cases() { self::getData2(); } +} +', + ['force' => false] + ), + ], + null, + 'Fixer could be risky if one is calling data provider function dynamically.' + ); + } + + public function isRisky(): bool + { + return true; + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder( + 'force', + 'Whether to make the data providers static even if they have a dynamic class call' + .' (may introduce fatal error "using $this when not in object context",' + .' and you may have to adjust the code manually by converting dynamic calls to static ones).' + )) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + + protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void + { + $dataProviderAnalyzer = new DataProviderAnalyzer(); + $tokensAnalyzer = new TokensAnalyzer($tokens); + + $inserts = []; + foreach ($dataProviderAnalyzer->getDataProviders($tokens, $startIndex, $endIndex) as $dataProviderDefinitionIndex) { + $methodStartIndex = $tokens->getNextTokenOfKind($dataProviderDefinitionIndex->getNameIndex(), ['{']); + if (null !== $methodStartIndex) { + $methodEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $methodStartIndex); + + if (false === $this->configuration['force'] && null !== $tokens->findSequence([[T_VARIABLE, '$this']], $methodStartIndex, $methodEndIndex)) { + continue; + } + } + $functionIndex = $tokens->getPrevTokenOfKind($dataProviderDefinitionIndex->getNameIndex(), [[T_FUNCTION]]); + + $methodAttributes = $tokensAnalyzer->getMethodAttributes($functionIndex); + if (false !== $methodAttributes['static']) { + continue; + } + + $inserts[$functionIndex] = [new Token([T_STATIC, 'static']), new Token([T_WHITESPACE, ' '])]; + } + $tokens->insertSlices($inserts); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDedicateAssertFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDedicateAssertFixer.php new file mode 100644 index 00000000..ed599f5f --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDedicateAssertFixer.php @@ -0,0 +1,628 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\PhpUnit; + +use PhpCsFixer\Fixer\AbstractPhpUnitFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class PhpUnitDedicateAssertFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface +{ + /** + * @var array|true> + */ + private static array $fixMap = [ + 'array_key_exists' => [ + 'positive' => 'assertArrayHasKey', + 'negative' => 'assertArrayNotHasKey', + 'argument_count' => 2, + ], + 'empty' => [ + 'positive' => 'assertEmpty', + 'negative' => 'assertNotEmpty', + ], + 'file_exists' => [ + 'positive' => 'assertFileExists', + 'negative' => 'assertFileNotExists', + ], + 'is_array' => true, + 'is_bool' => true, + 'is_callable' => true, + 'is_dir' => [ + 'positive' => 'assertDirectoryExists', + 'negative' => 'assertDirectoryNotExists', + ], + 'is_double' => true, + 'is_float' => true, + 'is_infinite' => [ + 'positive' => 'assertInfinite', + 'negative' => 'assertFinite', + ], + 'is_int' => true, + 'is_integer' => true, + 'is_long' => true, + 'is_nan' => [ + 'positive' => 'assertNan', + 'negative' => false, + ], + 'is_null' => [ + 'positive' => 'assertNull', + 'negative' => 'assertNotNull', + ], + 'is_numeric' => true, + 'is_object' => true, + 'is_readable' => [ + 'positive' => 'assertIsReadable', + 'negative' => 'assertNotIsReadable', + ], + 'is_real' => true, + 'is_resource' => true, + 'is_scalar' => true, + 'is_string' => true, + 'is_writable' => [ + 'positive' => 'assertIsWritable', + 'negative' => 'assertNotIsWritable', + ], + 'str_contains' => [ // since 7.5 + 'positive' => 'assertStringContainsString', + 'negative' => 'assertStringNotContainsString', + 'argument_count' => 2, + 'swap_arguments' => true, + ], + 'str_ends_with' => [ // since 3.4 + 'positive' => 'assertStringEndsWith', + 'negative' => 'assertStringEndsNotWith', + 'argument_count' => 2, + 'swap_arguments' => true, + ], + 'str_starts_with' => [ // since 3.4 + 'positive' => 'assertStringStartsWith', + 'negative' => 'assertStringStartsNotWith', + 'argument_count' => 2, + 'swap_arguments' => true, + ], + ]; + + /** + * @var string[] + */ + private array $functions = []; + + public function configure(array $configuration): void + { + parent::configure($configuration); + + // assertions added in 3.0: assertArrayNotHasKey assertArrayHasKey assertFileNotExists assertFileExists assertNotNull, assertNull + $this->functions = [ + 'array_key_exists', + 'file_exists', + 'is_null', + 'str_ends_with', + 'str_starts_with', + ]; + + if (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_3_5)) { + // assertions added in 3.5: assertInternalType assertNotEmpty assertEmpty + $this->functions = array_merge($this->functions, [ + 'empty', + 'is_array', + 'is_bool', + 'is_boolean', + 'is_callable', + 'is_double', + 'is_float', + 'is_int', + 'is_integer', + 'is_long', + 'is_numeric', + 'is_object', + 'is_real', + 'is_scalar', + 'is_string', + ]); + } + + if (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_5_0)) { + // assertions added in 5.0: assertFinite assertInfinite assertNan + $this->functions = array_merge($this->functions, [ + 'is_infinite', + 'is_nan', + ]); + } + + if (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_5_6)) { + // assertions added in 5.6: assertDirectoryExists assertDirectoryNotExists assertIsReadable assertNotIsReadable assertIsWritable assertNotIsWritable + $this->functions = array_merge($this->functions, [ + 'is_dir', + 'is_readable', + 'is_writable', + ]); + } + + if (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_7_5)) { + $this->functions = array_merge($this->functions, [ + 'str_contains', + ]); + } + } + + public function isRisky(): bool + { + return true; + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'PHPUnit assertions like `assertInternalType`, `assertFileExists`, should be used over `assertTrue`.', + [ + new CodeSample( + 'assertTrue(is_float( $a), "my message"); + $this->assertTrue(is_nan($a)); + } +} +' + ), + new CodeSample( + 'assertTrue(is_dir($a)); + $this->assertTrue(is_writable($a)); + $this->assertTrue(is_readable($a)); + } +} +', + ['target' => PhpUnitTargetVersion::VERSION_5_6] + ), + ], + null, + 'Fixer could be risky if one is overriding PHPUnit\'s native methods.' + ); + } + + /** + * {@inheritdoc} + * + * Must run before NoUnusedImportsFixer, PhpUnitDedicateAssertInternalTypeFixer. + * Must run after ModernizeStrposFixer, NoAliasFunctionsFixer, PhpUnitConstructFixer. + */ + public function getPriority(): int + { + return -9; + } + + protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void + { + $argumentsAnalyzer = new ArgumentsAnalyzer(); + + foreach ($this->getPreviousAssertCall($tokens, $startIndex, $endIndex) as $assertCall) { + // test and fix for assertTrue/False to dedicated asserts + if ('asserttrue' === $assertCall['loweredName'] || 'assertfalse' === $assertCall['loweredName']) { + $this->fixAssertTrueFalse($tokens, $argumentsAnalyzer, $assertCall); + + continue; + } + + if ( + 'assertsame' === $assertCall['loweredName'] + || 'assertnotsame' === $assertCall['loweredName'] + || 'assertequals' === $assertCall['loweredName'] + || 'assertnotequals' === $assertCall['loweredName'] + ) { + $this->fixAssertSameEquals($tokens, $assertCall); + + continue; + } + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('target', 'Target version of PHPUnit.')) + ->setAllowedTypes(['string']) + ->setAllowedValues([ + PhpUnitTargetVersion::VERSION_3_0, + PhpUnitTargetVersion::VERSION_3_5, + PhpUnitTargetVersion::VERSION_5_0, + PhpUnitTargetVersion::VERSION_5_6, + PhpUnitTargetVersion::VERSION_NEWEST, + ]) + ->setDefault(PhpUnitTargetVersion::VERSION_NEWEST) + ->getOption(), + ]); + } + + /** + * @param array{ + * index: int, + * loweredName: string, + * openBraceIndex: int, + * closeBraceIndex: int, + * } $assertCall + */ + private function fixAssertTrueFalse(Tokens $tokens, ArgumentsAnalyzer $argumentsAnalyzer, array $assertCall): void + { + $testDefaultNamespaceTokenIndex = null; + $testIndex = $tokens->getNextMeaningfulToken($assertCall['openBraceIndex']); + + if (!$tokens[$testIndex]->isGivenKind([T_EMPTY, T_STRING])) { + if ($this->fixAssertTrueFalseInstanceof($tokens, $assertCall, $testIndex)) { + return; + } + + if (!$tokens[$testIndex]->isGivenKind(T_NS_SEPARATOR)) { + return; + } + + $testDefaultNamespaceTokenIndex = $testIndex; + $testIndex = $tokens->getNextMeaningfulToken($testIndex); + } + + $testOpenIndex = $tokens->getNextMeaningfulToken($testIndex); + + if (!$tokens[$testOpenIndex]->equals('(')) { + return; + } + + $testCloseIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $testOpenIndex); + $assertCallCloseIndex = $tokens->getNextMeaningfulToken($testCloseIndex); + + if (!$tokens[$assertCallCloseIndex]->equalsAny([')', ','])) { + return; + } + + $content = strtolower($tokens[$testIndex]->getContent()); + + if (!\in_array($content, $this->functions, true)) { + return; + } + + $arguments = $argumentsAnalyzer->getArguments($tokens, $testOpenIndex, $testCloseIndex); + $isPositive = 'asserttrue' === $assertCall['loweredName']; + + if (\is_array(self::$fixMap[$content])) { + $expectedCount = self::$fixMap[$content]['argument_count'] ?? 1; + + if ($expectedCount !== \count($arguments)) { + return; + } + + $isPositive = $isPositive ? 'positive' : 'negative'; + + if (false === self::$fixMap[$content][$isPositive]) { + return; + } + + $tokens[$assertCall['index']] = new Token([T_STRING, self::$fixMap[$content][$isPositive]]); + $this->removeFunctionCall($tokens, $testDefaultNamespaceTokenIndex, $testIndex, $testOpenIndex, $testCloseIndex); + + if (self::$fixMap[$content]['swap_arguments'] ?? false) { + if (2 !== $expectedCount) { + throw new \RuntimeException('Can only swap two arguments, please update map or logic.'); + } + + $this->swapArguments($tokens, $arguments); + } + + return; + } + + if (1 !== \count($arguments)) { + return; + } + + $type = substr($content, 3); + + $tokens[$assertCall['index']] = new Token([T_STRING, $isPositive ? 'assertInternalType' : 'assertNotInternalType']); + $tokens[$testIndex] = new Token([T_CONSTANT_ENCAPSED_STRING, "'".$type."'"]); + $tokens[$testOpenIndex] = new Token(','); + + $tokens->clearTokenAndMergeSurroundingWhitespace($testCloseIndex); + $commaIndex = $tokens->getPrevMeaningfulToken($testCloseIndex); + + if ($tokens[$commaIndex]->equals(',')) { + $tokens->removeTrailingWhitespace($commaIndex); + $tokens->clearAt($commaIndex); + } + + if (!$tokens[$testOpenIndex + 1]->isWhitespace()) { + $tokens->insertAt($testOpenIndex + 1, new Token([T_WHITESPACE, ' '])); + } + + if (null !== $testDefaultNamespaceTokenIndex) { + $tokens->clearTokenAndMergeSurroundingWhitespace($testDefaultNamespaceTokenIndex); + } + } + + /** + * @param array{ + * index: int, + * loweredName: string, + * openBraceIndex: int, + * closeBraceIndex: int, + * } $assertCall + */ + private function fixAssertTrueFalseInstanceof(Tokens $tokens, array $assertCall, int $testIndex): bool + { + if ($tokens[$testIndex]->equals('!')) { + $variableIndex = $tokens->getNextMeaningfulToken($testIndex); + $positive = false; + } else { + $variableIndex = $testIndex; + $positive = true; + } + + if (!$tokens[$variableIndex]->isGivenKind(T_VARIABLE)) { + return false; + } + + $instanceOfIndex = $tokens->getNextMeaningfulToken($variableIndex); + + if (!$tokens[$instanceOfIndex]->isGivenKind(T_INSTANCEOF)) { + return false; + } + + $classEndIndex = $instanceOfIndex; + $classPartTokens = []; + + do { + $classEndIndex = $tokens->getNextMeaningfulToken($classEndIndex); + $classPartTokens[] = $tokens[$classEndIndex]; + } while ($tokens[$classEndIndex]->isGivenKind([T_STRING, T_NS_SEPARATOR, T_VARIABLE])); + + if ($tokens[$classEndIndex]->equalsAny([',', ')'])) { // do the fixing + array_pop($classPartTokens); + $isInstanceOfVar = reset($classPartTokens)->isGivenKind(T_VARIABLE); + $insertIndex = $testIndex - 1; + $newTokens = []; + + foreach ($classPartTokens as $token) { + $newTokens[++$insertIndex] = clone $token; + } + + if (!$isInstanceOfVar) { + $newTokens[++$insertIndex] = new Token([T_DOUBLE_COLON, '::']); + $newTokens[++$insertIndex] = new Token([CT::T_CLASS_CONSTANT, 'class']); + } + + $newTokens[++$insertIndex] = new Token(','); + $newTokens[++$insertIndex] = new Token([T_WHITESPACE, ' ']); + $newTokens[++$insertIndex] = clone $tokens[$variableIndex]; + + for ($i = $classEndIndex - 1; $i >= $testIndex; --$i) { + if (!$tokens[$i]->isComment()) { + $tokens->clearTokenAndMergeSurroundingWhitespace($i); + } + } + + $tokens->insertSlices($newTokens); + $tokens[$assertCall['index']] = new Token([T_STRING, $positive ? 'assertInstanceOf' : 'assertNotInstanceOf']); + } + + return true; + } + + /** + * @param array{ + * index: int, + * loweredName: string, + * openBraceIndex: int, + * closeBraceIndex: int, + * } $assertCall + */ + private function fixAssertSameEquals(Tokens $tokens, array $assertCall): void + { + // @ $this->/self::assertEquals/Same([$nextIndex]) + $expectedIndex = $tokens->getNextMeaningfulToken($assertCall['openBraceIndex']); + + // do not fix + // let $a = [1,2]; $b = "2"; + // "$this->assertEquals("2", count($a)); $this->assertEquals($b, count($a)); $this->assertEquals(2.1, count($a));" + + if ($tokens[$expectedIndex]->isGivenKind([T_VARIABLE])) { + if (!$tokens[$tokens->getNextMeaningfulToken($expectedIndex)]->equals(',')) { + return; + } + } elseif (!$tokens[$expectedIndex]->isGivenKind([T_LNUMBER, T_VARIABLE])) { + return; + } + + // @ $this->/self::assertEquals/Same([$nextIndex,$commaIndex]) + $commaIndex = $tokens->getNextMeaningfulToken($expectedIndex); + + if (!$tokens[$commaIndex]->equals(',')) { + return; + } + + // @ $this->/self::assertEquals/Same([$nextIndex,$commaIndex,$countCallIndex]) + $countCallIndex = $tokens->getNextMeaningfulToken($commaIndex); + + if ($tokens[$countCallIndex]->isGivenKind(T_NS_SEPARATOR)) { + $defaultNamespaceTokenIndex = $countCallIndex; + $countCallIndex = $tokens->getNextMeaningfulToken($countCallIndex); + } else { + $defaultNamespaceTokenIndex = null; + } + + if (!$tokens[$countCallIndex]->isGivenKind(T_STRING)) { + return; + } + + $lowerContent = strtolower($tokens[$countCallIndex]->getContent()); + + if ('count' !== $lowerContent && 'sizeof' !== $lowerContent) { + return; // not a call to "count" or "sizeOf" + } + + // @ $this->/self::assertEquals/Same([$nextIndex,$commaIndex,[$defaultNamespaceTokenIndex,]$countCallIndex,$countCallOpenBraceIndex]) + $countCallOpenBraceIndex = $tokens->getNextMeaningfulToken($countCallIndex); + + if (!$tokens[$countCallOpenBraceIndex]->equals('(')) { + return; + } + + $countCallCloseBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $countCallOpenBraceIndex); + $afterCountCallCloseBraceIndex = $tokens->getNextMeaningfulToken($countCallCloseBraceIndex); + + if (!$tokens[$afterCountCallCloseBraceIndex]->equalsAny([')', ','])) { + return; + } + + $this->removeFunctionCall( + $tokens, + $defaultNamespaceTokenIndex, + $countCallIndex, + $countCallOpenBraceIndex, + $countCallCloseBraceIndex + ); + + $tokens[$assertCall['index']] = new Token([ + T_STRING, + false === strpos($assertCall['loweredName'], 'not', 6) ? 'assertCount' : 'assertNotCount', + ]); + } + + /** + * @return iterable + */ + private function getPreviousAssertCall(Tokens $tokens, int $startIndex, int $endIndex): iterable + { + $functionsAnalyzer = new FunctionsAnalyzer(); + + for ($index = $endIndex; $index > $startIndex; --$index) { + $index = $tokens->getPrevTokenOfKind($index, [[T_STRING]]); + + if (null === $index) { + return; + } + + // test if "assert" something call + $loweredContent = strtolower($tokens[$index]->getContent()); + + if (!str_starts_with($loweredContent, 'assert')) { + continue; + } + + // test candidate for simple calls like: ([\]+'some fixable call'(...)) + $openBraceIndex = $tokens->getNextMeaningfulToken($index); + + if (!$tokens[$openBraceIndex]->equals('(')) { + continue; + } + + if (!$functionsAnalyzer->isTheSameClassCall($tokens, $index)) { + continue; + } + + yield [ + 'index' => $index, + 'loweredName' => $loweredContent, + 'openBraceIndex' => $openBraceIndex, + 'closeBraceIndex' => $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openBraceIndex), + ]; + } + } + + private function removeFunctionCall(Tokens $tokens, ?int $callNSIndex, int $callIndex, int $openIndex, int $closeIndex): void + { + $tokens->clearTokenAndMergeSurroundingWhitespace($callIndex); + + if (null !== $callNSIndex) { + $tokens->clearTokenAndMergeSurroundingWhitespace($callNSIndex); + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($openIndex); + $commaIndex = $tokens->getPrevMeaningfulToken($closeIndex); + + if ($tokens[$commaIndex]->equals(',')) { + $tokens->removeTrailingWhitespace($commaIndex); + $tokens->clearAt($commaIndex); + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($closeIndex); + } + + /** + * @param array $argumentsIndices + */ + private function swapArguments(Tokens $tokens, array $argumentsIndices): void + { + [$firstArgumentIndex, $secondArgumentIndex] = array_keys($argumentsIndices); + + $firstArgumentEndIndex = $argumentsIndices[$firstArgumentIndex]; + $secondArgumentEndIndex = $argumentsIndices[$secondArgumentIndex]; + + $firstClone = $this->cloneAndClearTokens($tokens, $firstArgumentIndex, $firstArgumentEndIndex); + $secondClone = $this->cloneAndClearTokens($tokens, $secondArgumentIndex, $secondArgumentEndIndex); + + if (!$firstClone[0]->isWhitespace()) { + array_unshift($firstClone, new Token([T_WHITESPACE, ' '])); + } + + $tokens->insertAt($secondArgumentIndex, $firstClone); + + if ($secondClone[0]->isWhitespace()) { + array_shift($secondClone); + } + + $tokens->insertAt($firstArgumentIndex, $secondClone); + } + + /** + * @return list + */ + private function cloneAndClearTokens(Tokens $tokens, int $start, int $end): array + { + $clone = []; + + for ($i = $start; $i <= $end; ++$i) { + if ('' === $tokens[$i]->getContent()) { + continue; + } + + $clone[] = clone $tokens[$i]; + $tokens->clearAt($i); + } + + return $clone; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDedicateAssertInternalTypeFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDedicateAssertInternalTypeFixer.php new file mode 100644 index 00000000..b6e955ea --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDedicateAssertInternalTypeFixer.php @@ -0,0 +1,191 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\PhpUnit; + +use PhpCsFixer\Fixer\AbstractPhpUnitFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Filippo Tessarotto + */ +final class PhpUnitDedicateAssertInternalTypeFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface +{ + /** + * @var array + */ + private array $typeToDedicatedAssertMap = [ + 'array' => 'assertIsArray', + 'boolean' => 'assertIsBool', + 'bool' => 'assertIsBool', + 'double' => 'assertIsFloat', + 'float' => 'assertIsFloat', + 'integer' => 'assertIsInt', + 'int' => 'assertIsInt', + 'null' => 'assertNull', + 'numeric' => 'assertIsNumeric', + 'object' => 'assertIsObject', + 'real' => 'assertIsFloat', + 'resource' => 'assertIsResource', + 'string' => 'assertIsString', + 'scalar' => 'assertIsScalar', + 'callable' => 'assertIsCallable', + 'iterable' => 'assertIsIterable', + ]; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'PHPUnit assertions like `assertIsArray` should be used over `assertInternalType`.', + [ + new CodeSample( + 'assertInternalType("array", $var); + $this->assertInternalType("boolean", $var); + } +} +' + ), + new CodeSample( + 'assertInternalType("array", $var); + $this->assertInternalType("boolean", $var); + } +} +', + ['target' => PhpUnitTargetVersion::VERSION_7_5] + ), + ], + null, + 'Risky when PHPUnit methods are overridden or when project has PHPUnit incompatibilities.' + ); + } + + public function isRisky(): bool + { + return true; + } + + /** + * {@inheritdoc} + * + * Must run after NoBinaryStringFixer, NoUselessConcatOperatorFixer, PhpUnitDedicateAssertFixer. + */ + public function getPriority(): int + { + return -16; + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('target', 'Target version of PHPUnit.')) + ->setAllowedTypes(['string']) + ->setAllowedValues([PhpUnitTargetVersion::VERSION_7_5, PhpUnitTargetVersion::VERSION_NEWEST]) + ->setDefault(PhpUnitTargetVersion::VERSION_NEWEST) + ->getOption(), + ]); + } + + protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void + { + $anonymousClassIndices = []; + $tokenAnalyzer = new TokensAnalyzer($tokens); + + for ($index = $startIndex; $index < $endIndex; ++$index) { + if (!$tokens[$index]->isGivenKind(T_CLASS) || !$tokenAnalyzer->isAnonymousClass($index)) { + continue; + } + + $openingBraceIndex = $tokens->getNextTokenOfKind($index, ['{']); + $closingBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $openingBraceIndex); + + $anonymousClassIndices[$closingBraceIndex] = $openingBraceIndex; + } + + for ($index = $endIndex - 1; $index > $startIndex; --$index) { + if (isset($anonymousClassIndices[$index])) { + $index = $anonymousClassIndices[$index]; + + continue; + } + + if (!$tokens[$index]->isGivenKind(T_STRING)) { + continue; + } + + $functionName = strtolower($tokens[$index]->getContent()); + + if ('assertinternaltype' !== $functionName && 'assertnotinternaltype' !== $functionName) { + continue; + } + + $bracketTokenIndex = $tokens->getNextMeaningfulToken($index); + + if (!$tokens[$bracketTokenIndex]->equals('(')) { + continue; + } + + $expectedTypeTokenIndex = $tokens->getNextMeaningfulToken($bracketTokenIndex); + $expectedTypeToken = $tokens[$expectedTypeTokenIndex]; + + if (!$expectedTypeToken->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) { + continue; + } + + $expectedType = trim($expectedTypeToken->getContent(), '\'"'); + + if (!isset($this->typeToDedicatedAssertMap[$expectedType])) { + continue; + } + + $commaTokenIndex = $tokens->getNextMeaningfulToken($expectedTypeTokenIndex); + + if (!$tokens[$commaTokenIndex]->equals(',')) { + continue; + } + + $newAssertion = $this->typeToDedicatedAssertMap[$expectedType]; + + if ('assertnotinternaltype' === $functionName) { + $newAssertion = str_replace('Is', 'IsNot', $newAssertion); + $newAssertion = str_replace('Null', 'NotNull', $newAssertion); + } + + $nextMeaningfulTokenIndex = $tokens->getNextMeaningfulToken($commaTokenIndex); + + $tokens->overrideRange($index, $nextMeaningfulTokenIndex - 1, [ + new Token([T_STRING, $newAssertion]), + new Token('('), + ]); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitExpectationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitExpectationFixer.php new file mode 100644 index 00000000..ca8464d2 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitExpectationFixer.php @@ -0,0 +1,274 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\PhpUnit; + +use PhpCsFixer\Fixer\AbstractPhpUnitFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\WhitespacesAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class PhpUnitExpectationFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface +{ + /** + * @var array + */ + private array $methodMap = []; + + public function configure(array $configuration): void + { + parent::configure($configuration); + + $this->methodMap = [ + 'setExpectedException' => 'expectExceptionMessage', + ]; + + if (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_5_6)) { + $this->methodMap['setExpectedExceptionRegExp'] = 'expectExceptionMessageRegExp'; + } + + if (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_8_4)) { + $this->methodMap['setExpectedExceptionRegExp'] = 'expectExceptionMessageMatches'; + $this->methodMap['expectExceptionMessageRegExp'] = 'expectExceptionMessageMatches'; + } + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Usages of `->setExpectedException*` methods MUST be replaced by `->expectException*` methods.', + [ + new CodeSample( + 'setExpectedException("RuntimeException", "Msg", 123); + foo(); + } + + public function testBar() + { + $this->setExpectedExceptionRegExp("RuntimeException", "/Msg.*/", 123); + bar(); + } +} +' + ), + new CodeSample( + 'setExpectedException("RuntimeException", null, 123); + foo(); + } + + public function testBar() + { + $this->setExpectedExceptionRegExp("RuntimeException", "/Msg.*/", 123); + bar(); + } +} +', + ['target' => PhpUnitTargetVersion::VERSION_8_4] + ), + new CodeSample( + 'setExpectedException("RuntimeException", null, 123); + foo(); + } + + public function testBar() + { + $this->setExpectedExceptionRegExp("RuntimeException", "/Msg.*/", 123); + bar(); + } +} +', + ['target' => PhpUnitTargetVersion::VERSION_5_6] + ), + new CodeSample( + 'setExpectedException("RuntimeException", "Msg", 123); + foo(); + } + + public function testBar() + { + $this->setExpectedExceptionRegExp("RuntimeException", "/Msg.*/", 123); + bar(); + } +} +', + ['target' => PhpUnitTargetVersion::VERSION_5_2] + ), + ], + null, + 'Risky when PHPUnit classes are overridden or not accessible, or when project has PHPUnit incompatibilities.' + ); + } + + /** + * {@inheritdoc} + * + * Must run after PhpUnitNoExpectationAnnotationFixer. + */ + public function getPriority(): int + { + return 0; + } + + public function isRisky(): bool + { + return true; + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('target', 'Target version of PHPUnit.')) + ->setAllowedTypes(['string']) + ->setAllowedValues([PhpUnitTargetVersion::VERSION_5_2, PhpUnitTargetVersion::VERSION_5_6, PhpUnitTargetVersion::VERSION_8_4, PhpUnitTargetVersion::VERSION_NEWEST]) + ->setDefault(PhpUnitTargetVersion::VERSION_NEWEST) + ->getOption(), + ]); + } + + protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void + { + foreach (Token::getObjectOperatorKinds() as $objectOperator) { + $this->applyPhpUnitClassFixWithObjectOperator($tokens, $startIndex, $endIndex, $objectOperator); + } + } + + private function applyPhpUnitClassFixWithObjectOperator(Tokens $tokens, int $startIndex, int $endIndex, int $objectOperator): void + { + $argumentsAnalyzer = new ArgumentsAnalyzer(); + + $oldMethodSequence = [ + [T_VARIABLE, '$this'], + [$objectOperator], + [T_STRING], + ]; + + for ($index = $startIndex; $startIndex < $endIndex; ++$index) { + $match = $tokens->findSequence($oldMethodSequence, $index); + + if (null === $match) { + return; + } + + [$thisIndex, , $index] = array_keys($match); + + if (!isset($this->methodMap[$tokens[$index]->getContent()])) { + continue; + } + + $openIndex = $tokens->getNextTokenOfKind($index, ['(']); + $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex); + $commaIndex = $tokens->getPrevMeaningfulToken($closeIndex); + if ($tokens[$commaIndex]->equals(',')) { + $tokens->removeTrailingWhitespace($commaIndex); + $tokens->clearAt($commaIndex); + } + + $arguments = $argumentsAnalyzer->getArguments($tokens, $openIndex, $closeIndex); + $argumentsCnt = \count($arguments); + + $argumentsReplacements = ['expectException', $this->methodMap[$tokens[$index]->getContent()], 'expectExceptionCode']; + + $indent = $this->whitespacesConfig->getLineEnding().WhitespacesAnalyzer::detectIndent($tokens, $thisIndex); + + $isMultilineWhitespace = false; + + for ($cnt = $argumentsCnt - 1; $cnt >= 1; --$cnt) { + $argStart = array_keys($arguments)[$cnt]; + $argBefore = $tokens->getPrevMeaningfulToken($argStart); + + if ('expectExceptionMessage' === $argumentsReplacements[$cnt]) { + $paramIndicatorIndex = $tokens->getNextMeaningfulToken($argBefore); + $afterParamIndicatorIndex = $tokens->getNextMeaningfulToken($paramIndicatorIndex); + + if ( + $tokens[$paramIndicatorIndex]->equals([T_STRING, 'null'], false) + && $tokens[$afterParamIndicatorIndex]->equals(')') + ) { + if ($tokens[$argBefore + 1]->isWhitespace()) { + $tokens->clearTokenAndMergeSurroundingWhitespace($argBefore + 1); + } + $tokens->clearTokenAndMergeSurroundingWhitespace($argBefore); + $tokens->clearTokenAndMergeSurroundingWhitespace($paramIndicatorIndex); + + continue; + } + } + + $isMultilineWhitespace = $isMultilineWhitespace || ($tokens[$argStart]->isWhitespace() && !$tokens[$argStart]->isWhitespace(" \t")); + $tokensOverrideArgStart = [ + new Token([T_WHITESPACE, $indent]), + new Token([T_VARIABLE, '$this']), + new Token([T_OBJECT_OPERATOR, '->']), + new Token([T_STRING, $argumentsReplacements[$cnt]]), + new Token('('), + ]; + $tokensOverrideArgBefore = [ + new Token(')'), + new Token(';'), + ]; + + if ($isMultilineWhitespace) { + $tokensOverrideArgStart[] = new Token([T_WHITESPACE, $indent.$this->whitespacesConfig->getIndent()]); + array_unshift($tokensOverrideArgBefore, new Token([T_WHITESPACE, $indent])); + } + + if ($tokens[$argStart]->isWhitespace()) { + $tokens->overrideRange($argStart, $argStart, $tokensOverrideArgStart); + } else { + $tokens->insertAt($argStart, $tokensOverrideArgStart); + } + + $tokens->overrideRange($argBefore, $argBefore, $tokensOverrideArgBefore); + } + + $methodName = 'expectException'; + if ('expectExceptionMessageRegExp' === $tokens[$index]->getContent()) { + $methodName = $this->methodMap[$tokens[$index]->getContent()]; + } + $tokens[$index] = new Token([T_STRING, $methodName]); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitFqcnAnnotationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitFqcnAnnotationFixer.php new file mode 100644 index 00000000..65ae1ada --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitFqcnAnnotationFixer.php @@ -0,0 +1,86 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\PhpUnit; + +use PhpCsFixer\Fixer\AbstractPhpUnitFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Roland Franssen + */ +final class PhpUnitFqcnAnnotationFixer extends AbstractPhpUnitFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'PHPUnit annotations should be a FQCNs including a root namespace.', + [new CodeSample( + 'getPrevTokenOfKind($startIndex, [[T_DOC_COMMENT]]); + + if (null !== $prevDocCommentIndex) { + $startIndex = $prevDocCommentIndex; + } + + $this->fixPhpUnitClass($tokens, $startIndex, $endIndex); + } + + private function fixPhpUnitClass(Tokens $tokens, int $startIndex, int $endIndex): void + { + for ($index = $startIndex; $index < $endIndex; ++$index) { + if ($tokens[$index]->isGivenKind(T_DOC_COMMENT)) { + $tokens[$index] = new Token([T_DOC_COMMENT, Preg::replace( + '~^(\s*\*\s*@(?:expectedException|covers|coversDefaultClass|uses)\h+)(?!(?:self|static)::)(\w.*)$~m', + '$1\\\\$2', + $tokens[$index]->getContent() + )]); + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitInternalClassFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitInternalClassFixer.php new file mode 100644 index 00000000..6d89deb5 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitInternalClassFixer.php @@ -0,0 +1,103 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\PhpUnit; + +use PhpCsFixer\Fixer\AbstractPhpUnitFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Gert de Pagter + */ +final class PhpUnitInternalClassFixer extends AbstractPhpUnitFixer implements WhitespacesAwareFixerInterface, ConfigurableFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'All PHPUnit test classes should be marked as internal.', + [ + new CodeSample(" ['final']] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before FinalInternalClassFixer, PhpdocSeparationFixer. + */ + public function getPriority(): int + { + return 68; + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + $types = ['normal', 'final', 'abstract']; + + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('types', 'What types of classes to mark as internal.')) + ->setAllowedValues([new AllowedValueSubset($types)]) + ->setAllowedTypes(['array']) + ->setDefault(['normal', 'final']) + ->getOption(), + ]); + } + + protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void + { + $classIndex = $tokens->getPrevTokenOfKind($startIndex, [[T_CLASS]]); + + if (!$this->isAllowedByConfiguration($tokens, $classIndex)) { + return; + } + + $this->ensureIsDockBlockWithAnnotation( + $tokens, + $classIndex, + 'internal', + ['internal'] + ); + } + + private function isAllowedByConfiguration(Tokens $tokens, int $index): bool + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + $modifiers = $tokensAnalyzer->getClassyModifiers($index); + + if (isset($modifiers['final'])) { + return \in_array('final', $this->configuration['types'], true); + } + + if (isset($modifiers['abstract'])) { + return \in_array('abstract', $this->configuration['types'], true); + } + + return \in_array('normal', $this->configuration['types'], true); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitMethodCasingFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitMethodCasingFixer.php new file mode 100644 index 00000000..04a3e5f7 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitMethodCasingFixer.php @@ -0,0 +1,196 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\PhpUnit; + +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\DocBlock\Line; +use PhpCsFixer\Fixer\AbstractPhpUnitFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; +use PhpCsFixer\Utils; + +/** + * @author Filippo Tessarotto + */ +final class PhpUnitMethodCasingFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface +{ + /** + * @internal + */ + public const CAMEL_CASE = 'camel_case'; + + /** + * @internal + */ + public const SNAKE_CASE = 'snake_case'; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Enforce camel (or snake) case for PHPUnit test methods, following configuration.', + [ + new CodeSample( + ' self::SNAKE_CASE] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run after PhpUnitTestAnnotationFixer. + */ + public function getPriority(): int + { + return 0; + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('case', 'Apply camel or snake case to test methods.')) + ->setAllowedValues([self::CAMEL_CASE, self::SNAKE_CASE]) + ->setDefault(self::CAMEL_CASE) + ->getOption(), + ]); + } + + protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void + { + for ($index = $endIndex - 1; $index > $startIndex; --$index) { + if (!$this->isTestMethod($tokens, $index)) { + continue; + } + + $functionNameIndex = $tokens->getNextMeaningfulToken($index); + $functionName = $tokens[$functionNameIndex]->getContent(); + $newFunctionName = $this->updateMethodCasing($functionName); + + if ($newFunctionName !== $functionName) { + $tokens[$functionNameIndex] = new Token([T_STRING, $newFunctionName]); + } + + $docBlockIndex = $this->getDocBlockIndex($tokens, $index); + + if ($this->isPHPDoc($tokens, $docBlockIndex)) { + $this->updateDocBlock($tokens, $docBlockIndex); + } + } + } + + private function updateMethodCasing(string $functionName): string + { + $parts = explode('::', $functionName); + + $functionNamePart = array_pop($parts); + + if (self::CAMEL_CASE === $this->configuration['case']) { + $newFunctionNamePart = $functionNamePart; + $newFunctionNamePart = ucwords($newFunctionNamePart, '_'); + $newFunctionNamePart = str_replace('_', '', $newFunctionNamePart); + $newFunctionNamePart = lcfirst($newFunctionNamePart); + } else { + $newFunctionNamePart = Utils::camelCaseToUnderscore($functionNamePart); + } + + $parts[] = $newFunctionNamePart; + + return implode('::', $parts); + } + + private function isTestMethod(Tokens $tokens, int $index): bool + { + // Check if we are dealing with a (non-abstract, non-lambda) function + if (!$this->isMethod($tokens, $index)) { + return false; + } + + // if the function name starts with test it's a test + $functionNameIndex = $tokens->getNextMeaningfulToken($index); + $functionName = $tokens[$functionNameIndex]->getContent(); + + if (str_starts_with($functionName, 'test')) { + return true; + } + + $docBlockIndex = $this->getDocBlockIndex($tokens, $index); + + return + $this->isPHPDoc($tokens, $docBlockIndex) // If the function doesn't have test in its name, and no doc block, it's not a test + && str_contains($tokens[$docBlockIndex]->getContent(), '@test'); + } + + private function isMethod(Tokens $tokens, int $index): bool + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + + return $tokens[$index]->isGivenKind(T_FUNCTION) && !$tokensAnalyzer->isLambda($index); + } + + private function updateDocBlock(Tokens $tokens, int $docBlockIndex): void + { + $doc = new DocBlock($tokens[$docBlockIndex]->getContent()); + $lines = $doc->getLines(); + + $docBlockNeedsUpdate = false; + for ($inc = 0; $inc < \count($lines); ++$inc) { + $lineContent = $lines[$inc]->getContent(); + if (!str_contains($lineContent, '@depends')) { + continue; + } + + $newLineContent = Preg::replaceCallback('/(@depends\s+)(.+)(\b)/', fn (array $matches): string => sprintf( + '%s%s%s', + $matches[1], + $this->updateMethodCasing($matches[2]), + $matches[3] + ), $lineContent); + + if ($newLineContent !== $lineContent) { + $lines[$inc] = new Line($newLineContent); + $docBlockNeedsUpdate = true; + } + } + + if ($docBlockNeedsUpdate) { + $lines = implode('', $lines); + $tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $lines]); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitMockFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitMockFixer.php new file mode 100644 index 00000000..a3d3484c --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitMockFixer.php @@ -0,0 +1,127 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\PhpUnit; + +use PhpCsFixer\Fixer\AbstractPhpUnitFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class PhpUnitMockFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface +{ + /** + * @var bool + */ + private $fixCreatePartialMock; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Usages of `->getMock` and `->getMockWithoutInvokingTheOriginalConstructor` methods MUST be replaced by `->createMock` or `->createPartialMock` methods.', + [ + new CodeSample( + 'getMockWithoutInvokingTheOriginalConstructor("Foo"); + $mock1 = $this->getMock("Foo"); + $mock1 = $this->getMock("Bar", ["aaa"]); + $mock1 = $this->getMock("Baz", ["aaa"], ["argument"]); // version with more than 2 params is not supported + } +} +' + ), + new CodeSample( + 'getMock("Foo"); + $mock1 = $this->getMock("Bar", ["aaa"]); // version with multiple params is not supported + } +} +', + ['target' => PhpUnitTargetVersion::VERSION_5_4] + ), + ], + null, + 'Risky when PHPUnit classes are overridden or not accessible, or when project has PHPUnit incompatibilities.' + ); + } + + public function isRisky(): bool + { + return true; + } + + public function configure(array $configuration): void + { + parent::configure($configuration); + + $this->fixCreatePartialMock = PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_5_5); + } + + protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void + { + $argumentsAnalyzer = new ArgumentsAnalyzer(); + + for ($index = $startIndex; $index < $endIndex; ++$index) { + if (!$tokens[$index]->isObjectOperator()) { + continue; + } + + $index = $tokens->getNextMeaningfulToken($index); + + if ($tokens[$index]->equals([T_STRING, 'getMockWithoutInvokingTheOriginalConstructor'], false)) { + $tokens[$index] = new Token([T_STRING, 'createMock']); + } elseif ($tokens[$index]->equals([T_STRING, 'getMock'], false)) { + $openingParenthesis = $tokens->getNextMeaningfulToken($index); + $closingParenthesis = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openingParenthesis); + + $argumentsCount = $argumentsAnalyzer->countArguments($tokens, $openingParenthesis, $closingParenthesis); + + if (1 === $argumentsCount) { + $tokens[$index] = new Token([T_STRING, 'createMock']); + } elseif (2 === $argumentsCount && true === $this->fixCreatePartialMock) { + $tokens[$index] = new Token([T_STRING, 'createPartialMock']); + } + } + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('target', 'Target version of PHPUnit.')) + ->setAllowedTypes(['string']) + ->setAllowedValues([PhpUnitTargetVersion::VERSION_5_4, PhpUnitTargetVersion::VERSION_5_5, PhpUnitTargetVersion::VERSION_NEWEST]) + ->setDefault(PhpUnitTargetVersion::VERSION_NEWEST) + ->getOption(), + ]); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitMockShortWillReturnFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitMockShortWillReturnFixer.php new file mode 100644 index 00000000..3350b669 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitMockShortWillReturnFixer.php @@ -0,0 +1,123 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\PhpUnit; + +use PhpCsFixer\Fixer\AbstractPhpUnitFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Michał Adamski + * @author Kuba Werłos + */ +final class PhpUnitMockShortWillReturnFixer extends AbstractPhpUnitFixer +{ + private const RETURN_METHODS_MAP = [ + 'returnargument' => 'willReturnArgument', + 'returncallback' => 'willReturnCallback', + 'returnself' => 'willReturnSelf', + 'returnvalue' => 'willReturn', + 'returnvaluemap' => 'willReturnMap', + ]; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Usage of PHPUnit\'s mock e.g. `->will($this->returnValue(..))` must be replaced by its shorter equivalent such as `->willReturn(...)`.', + [ + new CodeSample('createMock(Some::class); + $someMock->method("some")->will($this->returnSelf()); + $someMock->method("some")->will($this->returnValue("example")); + $someMock->method("some")->will($this->returnArgument(2)); + $someMock->method("some")->will($this->returnCallback("str_rot13")); + $someMock->method("some")->will($this->returnValueMap(["a","b","c"])); + } +} +'), + ], + null, + 'Risky when PHPUnit classes are overridden or not accessible, or when project has PHPUnit incompatibilities.' + ); + } + + public function isRisky(): bool + { + return true; + } + + protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void + { + $functionsAnalyzer = new FunctionsAnalyzer(); + + for ($index = $startIndex; $index < $endIndex; ++$index) { + if (!$tokens[$index]->isObjectOperator()) { + continue; + } + + $functionToReplaceIndex = $tokens->getNextMeaningfulToken($index); + if (!$tokens[$functionToReplaceIndex]->equals([T_STRING, 'will'], false)) { + continue; + } + + $functionToReplaceOpeningBraceIndex = $tokens->getNextMeaningfulToken($functionToReplaceIndex); + + if (!$tokens[$functionToReplaceOpeningBraceIndex]->equals('(')) { + continue; + } + + $classReferenceIndex = $tokens->getNextMeaningfulToken($functionToReplaceOpeningBraceIndex); + $objectOperatorIndex = $tokens->getNextMeaningfulToken($classReferenceIndex); + $functionToRemoveIndex = $tokens->getNextMeaningfulToken($objectOperatorIndex); + + if (!$functionsAnalyzer->isTheSameClassCall($tokens, $functionToRemoveIndex)) { + continue; + } + + if (!\array_key_exists(strtolower($tokens[$functionToRemoveIndex]->getContent()), self::RETURN_METHODS_MAP)) { + continue; + } + + $openingBraceIndex = $tokens->getNextMeaningfulToken($functionToRemoveIndex); + + if ($tokens[$tokens->getNextMeaningfulToken($openingBraceIndex)]->isGivenKind(CT::T_FIRST_CLASS_CALLABLE)) { + continue; + } + + $closingBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openingBraceIndex); + + $tokens[$functionToReplaceIndex] = new Token([T_STRING, self::RETURN_METHODS_MAP[strtolower($tokens[$functionToRemoveIndex]->getContent())]]); + $tokens->clearTokenAndMergeSurroundingWhitespace($classReferenceIndex); + $tokens->clearTokenAndMergeSurroundingWhitespace($objectOperatorIndex); + $tokens->clearTokenAndMergeSurroundingWhitespace($functionToRemoveIndex); + $tokens->clearTokenAndMergeSurroundingWhitespace($openingBraceIndex); + $tokens->clearTokenAndMergeSurroundingWhitespace($closingBraceIndex); + + $commaAfterClosingBraceIndex = $tokens->getNextMeaningfulToken($closingBraceIndex); + if ($tokens[$commaAfterClosingBraceIndex]->equals(',')) { + $tokens->clearTokenAndMergeSurroundingWhitespace($commaAfterClosingBraceIndex); + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitNamespacedFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitNamespacedFixer.php new file mode 100644 index 00000000..d521007d --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitNamespacedFixer.php @@ -0,0 +1,227 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\PhpUnit; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\ClassyAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class PhpUnitNamespacedFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** + * @var string + */ + private $originalClassRegEx; + + /** + * Class Mappings. + * + * * [original classname => new classname] Some classes which match the + * original class regular expression do not have a same-compound name- + * space class and need a dedicated translation table. This trans- + * lation table is defined in @see configure. + * + * @var array Class Mappings + */ + private $classMap; + + public function getDefinition(): FixerDefinitionInterface + { + $codeSample = ' PhpUnitTargetVersion::VERSION_4_8]), + ], + "PHPUnit v6 has finally fully switched to namespaces.\n" + ."You could start preparing the upgrade by switching from non-namespaced TestCase to namespaced one.\n" + .'Forward compatibility layer (`\PHPUnit\Framework\TestCase` class) was backported to PHPUnit v4.8.35 and PHPUnit v5.4.0.'."\n" + .'Extended forward compatibility layer (`PHPUnit\Framework\Assert`, `PHPUnit\Framework\BaseTestListener`, `PHPUnit\Framework\TestListener` classes) was introduced in v5.7.0.'."\n", + 'Risky when PHPUnit classes are overridden or not accessible, or when project has PHPUnit incompatibilities.' + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_STRING); + } + + public function isRisky(): bool + { + return true; + } + + public function configure(array $configuration): void + { + parent::configure($configuration); + + if (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_6_0)) { + $this->originalClassRegEx = '/^PHPUnit_\w+$/i'; + // @noinspection ClassConstantCanBeUsedInspection + $this->classMap = [ + 'PHPUnit_Extensions_PhptTestCase' => 'PHPUnit\Runner\PhptTestCase', + 'PHPUnit_Framework_Constraint' => 'PHPUnit\Framework\Constraint\Constraint', + 'PHPUnit_Framework_Constraint_StringMatches' => 'PHPUnit\Framework\Constraint\StringMatchesFormatDescription', + 'PHPUnit_Framework_Constraint_JsonMatches_ErrorMessageProvider' => 'PHPUnit\Framework\Constraint\JsonMatchesErrorMessageProvider', + 'PHPUnit_Framework_Constraint_PCREMatch' => 'PHPUnit\Framework\Constraint\RegularExpression', + 'PHPUnit_Framework_Constraint_ExceptionMessageRegExp' => 'PHPUnit\Framework\Constraint\ExceptionMessageRegularExpression', + 'PHPUnit_Framework_Constraint_And' => 'PHPUnit\Framework\Constraint\LogicalAnd', + 'PHPUnit_Framework_Constraint_Or' => 'PHPUnit\Framework\Constraint\LogicalOr', + 'PHPUnit_Framework_Constraint_Not' => 'PHPUnit\Framework\Constraint\LogicalNot', + 'PHPUnit_Framework_Constraint_Xor' => 'PHPUnit\Framework\Constraint\LogicalXor', + 'PHPUnit_Framework_Error' => 'PHPUnit\Framework\Error\Error', + 'PHPUnit_Framework_TestSuite_DataProvider' => 'PHPUnit\Framework\DataProviderTestSuite', + 'PHPUnit_Framework_MockObject_Invocation_Static' => 'PHPUnit\Framework\MockObject\Invocation\StaticInvocation', + 'PHPUnit_Framework_MockObject_Invocation_Object' => 'PHPUnit\Framework\MockObject\Invocation\ObjectInvocation', + 'PHPUnit_Framework_MockObject_Stub_Return' => 'PHPUnit\Framework\MockObject\Stub\ReturnStub', + 'PHPUnit_Runner_Filter_Group_Exclude' => 'PHPUnit\Runner\Filter\ExcludeGroupFilterIterator', + 'PHPUnit_Runner_Filter_Group_Include' => 'PHPUnit\Runner\Filter\IncludeGroupFilterIterator', + 'PHPUnit_Runner_Filter_Test' => 'PHPUnit\Runner\Filter\NameFilterIterator', + 'PHPUnit_Util_PHP' => 'PHPUnit\Util\PHP\AbstractPhpProcess', + 'PHPUnit_Util_PHP_Default' => 'PHPUnit\Util\PHP\DefaultPhpProcess', + 'PHPUnit_Util_PHP_Windows' => 'PHPUnit\Util\PHP\WindowsPhpProcess', + 'PHPUnit_Util_Regex' => 'PHPUnit\Util\RegularExpression', + 'PHPUnit_Util_TestDox_ResultPrinter_XML' => 'PHPUnit\Util\TestDox\XmlResultPrinter', + 'PHPUnit_Util_TestDox_ResultPrinter_HTML' => 'PHPUnit\Util\TestDox\HtmlResultPrinter', + 'PHPUnit_Util_TestDox_ResultPrinter_Text' => 'PHPUnit\Util\TestDox\TextResultPrinter', + 'PHPUnit_Util_TestSuiteIterator' => 'PHPUnit\Framework\TestSuiteIterator', + 'PHPUnit_Util_XML' => 'PHPUnit\Util\Xml', + ]; + } elseif (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_5_7)) { + $this->originalClassRegEx = '/^PHPUnit_Framework_(TestCase|Assert|BaseTestListener|TestListener)+$/i'; + $this->classMap = []; + } else { + $this->originalClassRegEx = '/^PHPUnit_Framework_TestCase$/i'; + $this->classMap = []; + } + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $importedOriginalClassesMap = []; + $currIndex = 0; + + while (true) { + $currIndex = $tokens->getNextTokenOfKind($currIndex, [[T_STRING]]); + + if (null === $currIndex) { + break; + } + + $prevIndex = $tokens->getPrevMeaningfulToken($currIndex); + + if ($tokens[$prevIndex]->isGivenKind([T_CONST, T_DOUBLE_COLON])) { + continue; + } + + $originalClass = $tokens[$currIndex]->getContent(); + $allowedReplacementScenarios = (new ClassyAnalyzer())->isClassyInvocation($tokens, $currIndex) + || $this->isImport($tokens, $currIndex); + + if (!$allowedReplacementScenarios || !Preg::match($this->originalClassRegEx, $originalClass)) { + ++$currIndex; + + continue; + } + + $substituteTokens = $this->generateReplacement($originalClass); + + $tokens->clearAt($currIndex); + $tokens->insertAt( + $currIndex, + isset($importedOriginalClassesMap[$originalClass]) ? $substituteTokens[$substituteTokens->getSize() - 1] : $substituteTokens + ); + + $prevIndex = $tokens->getPrevMeaningfulToken($currIndex); + if ($tokens[$prevIndex]->isGivenKind(T_USE)) { + $importedOriginalClassesMap[$originalClass] = true; + } elseif ($tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) { + $prevIndex = $tokens->getPrevMeaningfulToken($prevIndex); + + if ($tokens[$prevIndex]->isGivenKind(T_USE)) { + $importedOriginalClassesMap[$originalClass] = true; + } + } + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('target', 'Target version of PHPUnit.')) + ->setAllowedTypes(['string']) + ->setAllowedValues([PhpUnitTargetVersion::VERSION_4_8, PhpUnitTargetVersion::VERSION_5_7, PhpUnitTargetVersion::VERSION_6_0, PhpUnitTargetVersion::VERSION_NEWEST]) + ->setDefault(PhpUnitTargetVersion::VERSION_NEWEST) + ->getOption(), + ]); + } + + private function generateReplacement(string $originalClassName): Tokens + { + $delimiter = '_'; + $string = $originalClassName; + + $map = array_change_key_case($this->classMap); + if (isset($map[strtolower($originalClassName)])) { + $delimiter = '\\'; + $string = $map[strtolower($originalClassName)]; + } + + $parts = explode($delimiter, $string); + $tokensArray = []; + + while ([] !== $parts) { + $tokensArray[] = new Token([T_STRING, array_shift($parts)]); + if ([] !== $parts) { + $tokensArray[] = new Token([T_NS_SEPARATOR, '\\']); + } + } + + return Tokens::fromArray($tokensArray); + } + + private function isImport(Tokens $tokens, int $currIndex): bool + { + $prevIndex = $tokens->getPrevMeaningfulToken($currIndex); + + if ($tokens[$prevIndex]->isGivenKind([T_NS_SEPARATOR])) { + $prevIndex = $tokens->getPrevMeaningfulToken($prevIndex); + } + + return $tokens[$prevIndex]->isGivenKind([T_USE]); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitNoExpectationAnnotationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitNoExpectationAnnotationFixer.php new file mode 100644 index 00000000..f6fa1f0f --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitNoExpectationAnnotationFixer.php @@ -0,0 +1,268 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\PhpUnit; + +use PhpCsFixer\DocBlock\Annotation; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\Fixer\AbstractPhpUnitFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\WhitespacesAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Dariusz Rumiński + */ +final class PhpUnitNoExpectationAnnotationFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface +{ + /** + * @var bool + */ + private $fixMessageRegExp; + + public function configure(array $configuration): void + { + parent::configure($configuration); + + $this->fixMessageRegExp = PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_4_3); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Usages of `@expectedException*` annotations MUST be replaced by `->setExpectedException*` methods.', + [ + new CodeSample( + ' PhpUnitTargetVersion::VERSION_3_2] + ), + ], + null, + 'Risky when PHPUnit classes are overridden or not accessible, or when project has PHPUnit incompatibilities.' + ); + } + + /** + * {@inheritdoc} + * + * Must run before NoEmptyPhpdocFixer, PhpUnitExpectationFixer. + */ + public function getPriority(): int + { + return 10; + } + + public function isRisky(): bool + { + return true; + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('target', 'Target version of PHPUnit.')) + ->setAllowedTypes(['string']) + ->setAllowedValues([PhpUnitTargetVersion::VERSION_3_2, PhpUnitTargetVersion::VERSION_4_3, PhpUnitTargetVersion::VERSION_NEWEST]) + ->setDefault(PhpUnitTargetVersion::VERSION_NEWEST) + ->getOption(), + (new FixerOptionBuilder('use_class_const', 'Use ::class notation.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), + ]); + } + + protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + + for ($i = $endIndex - 1; $i > $startIndex; --$i) { + if (!$tokens[$i]->isGivenKind(T_FUNCTION) || $tokensAnalyzer->isLambda($i)) { + continue; + } + + $functionIndex = $i; + $docBlockIndex = $i; + + // ignore abstract functions + $braceIndex = $tokens->getNextTokenOfKind($functionIndex, [';', '{']); + if (!$tokens[$braceIndex]->equals('{')) { + continue; + } + + do { + $docBlockIndex = $tokens->getPrevNonWhitespace($docBlockIndex); + } while ($tokens[$docBlockIndex]->isGivenKind([T_PUBLIC, T_PROTECTED, T_PRIVATE, T_FINAL, T_ABSTRACT, T_COMMENT])); + + if (!$tokens[$docBlockIndex]->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + $doc = new DocBlock($tokens[$docBlockIndex]->getContent()); + $annotations = []; + + foreach ($doc->getAnnotationsOfType([ + 'expectedException', + 'expectedExceptionCode', + 'expectedExceptionMessage', + 'expectedExceptionMessageRegExp', + ]) as $annotation) { + $tag = $annotation->getTag()->getName(); + $content = $this->extractContentFromAnnotation($annotation); + $annotations[$tag] = $content; + $annotation->remove(); + } + + if (!isset($annotations['expectedException'])) { + continue; + } + + if (!$this->fixMessageRegExp && isset($annotations['expectedExceptionMessageRegExp'])) { + continue; + } + + $originalIndent = WhitespacesAnalyzer::detectIndent($tokens, $docBlockIndex); + + $paramList = $this->annotationsToParamList($annotations); + + $newMethodsCode = '' + .(isset($annotations['expectedExceptionMessageRegExp']) ? 'setExpectedExceptionRegExp' : 'setExpectedException') + .'(' + .implode(', ', $paramList) + .');'; + $newMethods = Tokens::fromCode($newMethodsCode); + $newMethods[0] = new Token([ + T_WHITESPACE, + $this->whitespacesConfig->getLineEnding().$originalIndent.$this->whitespacesConfig->getIndent(), + ]); + + // apply changes + $docContent = $doc->getContent(); + if ('' === $docContent) { + $docContent = '/** */'; + } + $tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $docContent]); + $tokens->insertAt($braceIndex + 1, $newMethods); + + $whitespaceIndex = $braceIndex + $newMethods->getSize() + 1; + $tokens[$whitespaceIndex] = new Token([ + T_WHITESPACE, + $this->whitespacesConfig->getLineEnding().$tokens[$whitespaceIndex]->getContent(), + ]); + + $i = $docBlockIndex; + } + } + + private function extractContentFromAnnotation(Annotation $annotation): string + { + $tag = $annotation->getTag()->getName(); + + if (!Preg::match('/@'.$tag.'\s+(.+)$/s', $annotation->getContent(), $matches)) { + return ''; + } + + $content = Preg::replace('/\*+\/$/', '', $matches[1]); + + if (Preg::match('/\R/u', $content)) { + $content = Preg::replace('/\s*\R+\s*\*\s*/u', ' ', $content); + } + + return rtrim($content); + } + + /** + * @param array $annotations + * + * @return list + */ + private function annotationsToParamList(array $annotations): array + { + $params = []; + $exceptionClass = ltrim($annotations['expectedException'], '\\'); + + if (str_contains($exceptionClass, '*')) { + $exceptionClass = substr($exceptionClass, 0, strpos($exceptionClass, '*')); + } + + $exceptionClass = trim($exceptionClass); + + if (true === $this->configuration['use_class_const']) { + $params[] = "\\{$exceptionClass}::class"; + } else { + $params[] = "'{$exceptionClass}'"; + } + + if (isset($annotations['expectedExceptionMessage'])) { + $params[] = var_export($annotations['expectedExceptionMessage'], true); + } elseif (isset($annotations['expectedExceptionMessageRegExp'])) { + $params[] = var_export($annotations['expectedExceptionMessageRegExp'], true); + } elseif (isset($annotations['expectedExceptionCode'])) { + $params[] = 'null'; + } + + if (isset($annotations['expectedExceptionCode'])) { + $params[] = $annotations['expectedExceptionCode']; + } + + return $params; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitSetUpTearDownVisibilityFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitSetUpTearDownVisibilityFixer.php new file mode 100644 index 00000000..3fbd98ce --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitSetUpTearDownVisibilityFixer.php @@ -0,0 +1,108 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\PhpUnit; + +use PhpCsFixer\Fixer\AbstractPhpUnitFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Gert de Pagter + */ +final class PhpUnitSetUpTearDownVisibilityFixer extends AbstractPhpUnitFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Changes the visibility of the `setUp()` and `tearDown()` functions of PHPUnit to `protected`, to match the PHPUnit TestCase.', + [ + new CodeSample( + 'hello = "hello"; + } + + public function tearDown() + { + $this->hello = null; + } +} +' + ), + ], + null, + 'This fixer may change functions named `setUp()` or `tearDown()` outside of PHPUnit tests, '. + 'when a class is wrongly seen as a PHPUnit test.' + ); + } + + public function isRisky(): bool + { + return true; + } + + protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void + { + $counter = 0; + $tokensAnalyzer = new TokensAnalyzer($tokens); + + for ($i = $endIndex - 1; $i > $startIndex; --$i) { + if (2 === $counter) { + break; // we've seen both method we are interested in, so stop analyzing this class + } + + if (!$this->isSetupOrTearDownMethod($tokens, $i)) { + continue; + } + + ++$counter; + $visibility = $tokensAnalyzer->getMethodAttributes($i)['visibility']; + + if (T_PUBLIC === $visibility) { + $index = $tokens->getPrevTokenOfKind($i, [[T_PUBLIC]]); + $tokens[$index] = new Token([T_PROTECTED, 'protected']); + + continue; + } + + if (null === $visibility) { + $tokens->insertAt($i, [new Token([T_PROTECTED, 'protected']), new Token([T_WHITESPACE, ' '])]); + } + } + } + + private function isSetupOrTearDownMethod(Tokens $tokens, int $index): bool + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + + $isMethod = $tokens[$index]->isGivenKind(T_FUNCTION) && !$tokensAnalyzer->isLambda($index); + if (!$isMethod) { + return false; + } + + $functionNameIndex = $tokens->getNextMeaningfulToken($index); + $functionName = strtolower($tokens[$functionNameIndex]->getContent()); + + return 'setup' === $functionName || 'teardown' === $functionName; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitSizeClassFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitSizeClassFixer.php new file mode 100644 index 00000000..0696cdfd --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitSizeClassFixer.php @@ -0,0 +1,89 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\PhpUnit; + +use PhpCsFixer\Fixer\AbstractPhpUnitFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Jefersson Nathan + */ +final class PhpUnitSizeClassFixer extends AbstractPhpUnitFixer implements WhitespacesAwareFixerInterface, ConfigurableFixerInterface +{ + private const SIZES = ['small', 'medium', 'large']; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'All PHPUnit test cases should have `@small`, `@medium` or `@large` annotation to enable run time limits.', + [ + new CodeSample(" 'medium']), + ], + 'The special groups [small, medium, large] provides a way to identify tests that are taking long to be executed.' + ); + } + + /** + * {@inheritdoc} + * + * Must run before PhpdocSeparationFixer. + */ + public function getPriority(): int + { + return 0; + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('group', 'Define a specific group to be used in case no group is already in use.')) + ->setAllowedValues(self::SIZES) + ->setDefault('small') + ->getOption(), + ]); + } + + protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void + { + $classIndex = $tokens->getPrevTokenOfKind($startIndex, [[T_CLASS]]); + + if ($this->isAbstractClass($tokens, $classIndex)) { + return; + } + + $this->ensureIsDockBlockWithAnnotation( + $tokens, + $classIndex, + $this->configuration['group'], + self::SIZES + ); + } + + private function isAbstractClass(Tokens $tokens, int $i): bool + { + $typeIndex = $tokens->getPrevMeaningfulToken($i); + + return $tokens[$typeIndex]->isGivenKind(T_ABSTRACT); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitStrictFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitStrictFixer.php new file mode 100644 index 00000000..5d3d565f --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitStrictFixer.php @@ -0,0 +1,141 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\PhpUnit; + +use PhpCsFixer\Fixer\AbstractPhpUnitFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class PhpUnitStrictFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface +{ + /** + * @var array + */ + private static array $assertionMap = [ + 'assertAttributeEquals' => 'assertAttributeSame', + 'assertAttributeNotEquals' => 'assertAttributeNotSame', + 'assertEquals' => 'assertSame', + 'assertNotEquals' => 'assertNotSame', + ]; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'PHPUnit methods like `assertSame` should be used instead of `assertEquals`.', + [ + new CodeSample( + 'assertAttributeEquals(a(), b()); + $this->assertAttributeNotEquals(a(), b()); + $this->assertEquals(a(), b()); + $this->assertNotEquals(a(), b()); + } +} +' + ), + new CodeSample( + 'assertAttributeEquals(a(), b()); + $this->assertAttributeNotEquals(a(), b()); + $this->assertEquals(a(), b()); + $this->assertNotEquals(a(), b()); + } +} +', + ['assertions' => ['assertEquals']] + ), + ], + null, + 'Risky when any of the functions are overridden or when testing object equality.' + ); + } + + public function isRisky(): bool + { + return true; + } + + protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void + { + $argumentsAnalyzer = new ArgumentsAnalyzer(); + $functionsAnalyzer = new FunctionsAnalyzer(); + + foreach ($this->configuration['assertions'] as $methodBefore) { + $methodAfter = self::$assertionMap[$methodBefore]; + + for ($index = $startIndex; $index < $endIndex; ++$index) { + $methodIndex = $tokens->getNextTokenOfKind($index, [[T_STRING, $methodBefore]]); + + if (null === $methodIndex) { + break; + } + + if (!$functionsAnalyzer->isTheSameClassCall($tokens, $methodIndex)) { + continue; + } + + $openingParenthesisIndex = $tokens->getNextMeaningfulToken($methodIndex); + $argumentsCount = $argumentsAnalyzer->countArguments( + $tokens, + $openingParenthesisIndex, + $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openingParenthesisIndex) + ); + + if (2 === $argumentsCount || 3 === $argumentsCount) { + $tokens[$methodIndex] = new Token([T_STRING, $methodAfter]); + } + + $index = $methodIndex; + } + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('assertions', 'List of assertion methods to fix.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([new AllowedValueSubset(array_keys(self::$assertionMap))]) + ->setDefault([ + 'assertAttributeEquals', + 'assertAttributeNotEquals', + 'assertEquals', + 'assertNotEquals', + ]) + ->getOption(), + ]); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTargetVersion.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTargetVersion.php new file mode 100644 index 00000000..4bd1800a --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTargetVersion.php @@ -0,0 +1,56 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\PhpUnit; + +use Composer\Semver\Comparator; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class PhpUnitTargetVersion +{ + public const VERSION_3_0 = '3.0'; + public const VERSION_3_2 = '3.2'; + public const VERSION_3_5 = '3.5'; + public const VERSION_4_3 = '4.3'; + public const VERSION_4_8 = '4.8'; + public const VERSION_5_0 = '5.0'; + public const VERSION_5_2 = '5.2'; + public const VERSION_5_4 = '5.4'; + public const VERSION_5_5 = '5.5'; + public const VERSION_5_6 = '5.6'; + public const VERSION_5_7 = '5.7'; + public const VERSION_6_0 = '6.0'; + public const VERSION_7_5 = '7.5'; + public const VERSION_8_4 = '8.4'; + public const VERSION_NEWEST = 'newest'; + + private function __construct() {} + + public static function fulfills(string $candidate, string $target): bool + { + if (self::VERSION_NEWEST === $target) { + throw new \LogicException(sprintf('Parameter `target` shall not be provided as "%s", determine proper target for tested PHPUnit feature instead.', self::VERSION_NEWEST)); + } + + if (self::VERSION_NEWEST === $candidate) { + return true; + } + + return Comparator::greaterThanOrEqualTo($candidate, $target); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestAnnotationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestAnnotationFixer.php new file mode 100644 index 00000000..3ae0ca06 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestAnnotationFixer.php @@ -0,0 +1,402 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\PhpUnit; + +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\DocBlock\Line; +use PhpCsFixer\Fixer\AbstractPhpUnitFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\WhitespacesAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Gert de Pagter + */ +final class PhpUnitTestAnnotationFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface +{ + public function isRisky(): bool + { + return true; + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Adds or removes @test annotations from tests, following configuration.', + [ + new CodeSample('whitespacesConfig->getLineEnding()), + new CodeSample('whitespacesConfig->getLineEnding(), ['style' => 'annotation']), + ], + null, + 'This fixer may change the name of your tests, and could cause incompatibility with'. + ' abstract classes or interfaces.' + ); + } + + /** + * {@inheritdoc} + * + * Must run before NoEmptyPhpdocFixer, PhpUnitMethodCasingFixer, PhpdocTrimFixer. + */ + public function getPriority(): int + { + return 10; + } + + protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void + { + if ('annotation' === $this->configuration['style']) { + $this->applyTestAnnotation($tokens, $startIndex, $endIndex); + } else { + $this->applyTestPrefix($tokens, $startIndex, $endIndex); + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('style', 'Whether to use the @test annotation or not.')) + ->setAllowedValues(['prefix', 'annotation']) + ->setDefault('prefix') + ->getOption(), + ]); + } + + private function applyTestAnnotation(Tokens $tokens, int $startIndex, int $endIndex): void + { + for ($i = $endIndex - 1; $i > $startIndex; --$i) { + if (!$this->isTestMethod($tokens, $i)) { + continue; + } + + $functionNameIndex = $tokens->getNextMeaningfulToken($i); + $functionName = $tokens[$functionNameIndex]->getContent(); + + if ($this->hasTestPrefix($functionName) && !$this->hasProperTestAnnotation($tokens, $i)) { + $newFunctionName = $this->removeTestPrefix($functionName); + $tokens[$functionNameIndex] = new Token([T_STRING, $newFunctionName]); + } + + $docBlockIndex = $this->getDocBlockIndex($tokens, $i); + + if ($this->isPHPDoc($tokens, $docBlockIndex)) { + $lines = $this->updateDocBlock($tokens, $docBlockIndex); + $lines = $this->addTestAnnotation($lines, $tokens, $docBlockIndex); + $lines = implode('', $lines); + + $tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $lines]); + } else { + // Create a new docblock if it didn't have one before; + $this->createDocBlock($tokens, $docBlockIndex); + } + } + } + + private function applyTestPrefix(Tokens $tokens, int $startIndex, int $endIndex): void + { + for ($i = $endIndex - 1; $i > $startIndex; --$i) { + // We explicitly check again if the function has a doc block to save some time. + if (!$this->isTestMethod($tokens, $i)) { + continue; + } + + $docBlockIndex = $this->getDocBlockIndex($tokens, $i); + + if (!$this->isPHPDoc($tokens, $docBlockIndex)) { + continue; + } + + $lines = $this->updateDocBlock($tokens, $docBlockIndex); + $lines = implode('', $lines); + $tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $lines]); + + $functionNameIndex = $tokens->getNextMeaningfulToken($i); + $functionName = $tokens[$functionNameIndex]->getContent(); + + if ($this->hasTestPrefix($functionName)) { + continue; + } + + $newFunctionName = $this->addTestPrefix($functionName); + $tokens[$functionNameIndex] = new Token([T_STRING, $newFunctionName]); + } + } + + private function isTestMethod(Tokens $tokens, int $index): bool + { + // Check if we are dealing with a (non-abstract, non-lambda) function + if (!$this->isMethod($tokens, $index)) { + return false; + } + + // if the function name starts with test it is a test + $functionNameIndex = $tokens->getNextMeaningfulToken($index); + $functionName = $tokens[$functionNameIndex]->getContent(); + + if ($this->hasTestPrefix($functionName)) { + return true; + } + + $docBlockIndex = $this->getDocBlockIndex($tokens, $index); + + // If the function doesn't have test in its name, and no doc block, it is not a test + return + $this->isPHPDoc($tokens, $docBlockIndex) + && str_contains($tokens[$docBlockIndex]->getContent(), '@test'); + } + + private function isMethod(Tokens $tokens, int $index): bool + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + + return $tokens[$index]->isGivenKind(T_FUNCTION) && !$tokensAnalyzer->isLambda($index); + } + + private function hasTestPrefix(string $functionName): bool + { + return str_starts_with($functionName, 'test'); + } + + private function hasProperTestAnnotation(Tokens $tokens, int $index): bool + { + $docBlockIndex = $this->getDocBlockIndex($tokens, $index); + $doc = $tokens[$docBlockIndex]->getContent(); + + return Preg::match('/\*\s+@test\b/', $doc); + } + + private function removeTestPrefix(string $functionName): string + { + $remainder = Preg::replace('/^test(?=[A-Z_])_?/', '', $functionName); + + if ('' === $remainder) { + return $functionName; + } + + return lcfirst($remainder); + } + + private function addTestPrefix(string $functionName): string + { + return 'test'.ucfirst($functionName); + } + + private function createDocBlock(Tokens $tokens, int $docBlockIndex): void + { + $lineEnd = $this->whitespacesConfig->getLineEnding(); + $originalIndent = WhitespacesAnalyzer::detectIndent($tokens, $tokens->getNextNonWhitespace($docBlockIndex)); + $toInsert = [ + new Token([T_DOC_COMMENT, '/**'.$lineEnd."{$originalIndent} * @test".$lineEnd."{$originalIndent} */"]), + new Token([T_WHITESPACE, $lineEnd.$originalIndent]), + ]; + $index = $tokens->getNextMeaningfulToken($docBlockIndex); + $tokens->insertAt($index, $toInsert); + } + + /** + * @return list + */ + private function updateDocBlock(Tokens $tokens, int $docBlockIndex): array + { + $doc = new DocBlock($tokens[$docBlockIndex]->getContent()); + $lines = $doc->getLines(); + + return $this->updateLines($lines, $tokens, $docBlockIndex); + } + + /** + * @param list $lines + * + * @return list + */ + private function updateLines(array $lines, Tokens $tokens, int $docBlockIndex): array + { + $needsAnnotation = 'annotation' === $this->configuration['style']; + + $doc = new DocBlock($tokens[$docBlockIndex]->getContent()); + for ($i = 0; $i < \count($lines); ++$i) { + // If we need to add test annotation and it is a single line comment we need to deal with that separately + if ($needsAnnotation && ($lines[$i]->isTheStart() && $lines[$i]->isTheEnd())) { + if (!$this->doesDocBlockContainTest($doc)) { + $lines = $this->splitUpDocBlock($lines, $tokens, $docBlockIndex); + + return $this->updateLines($lines, $tokens, $docBlockIndex); + } + // One we split it up, we run the function again, so we deal with other things in a proper way + } + + if (!$needsAnnotation + && str_contains($lines[$i]->getContent(), ' @test') + && !str_contains($lines[$i]->getContent(), '@testWith') + && !str_contains($lines[$i]->getContent(), '@testdox') + ) { + // We remove @test from the doc block + $lines[$i] = new Line(str_replace(' @test', '', $lines[$i]->getContent())); + } + // ignore the line if it isn't @depends + if (!str_contains($lines[$i]->getContent(), '@depends')) { + continue; + } + + $lines[$i] = $this->updateDependsAnnotation($lines[$i]); + } + + return $lines; + } + + /** + * Take a one line doc block, and turn it into a multi line doc block. + * + * @param Line[] $lines + * + * @return Line[] + */ + private function splitUpDocBlock(array $lines, Tokens $tokens, int $docBlockIndex): array + { + $lineContent = $this->getSingleLineDocBlockEntry($lines); + $lineEnd = $this->whitespacesConfig->getLineEnding(); + $originalIndent = WhitespacesAnalyzer::detectIndent($tokens, $tokens->getNextNonWhitespace($docBlockIndex)); + + return [ + new Line('/**'.$lineEnd), + new Line($originalIndent.' * '.$lineContent.$lineEnd), + new Line($originalIndent.' */'), + ]; + } + + /** + * @todo check whether it's doable to use \PhpCsFixer\DocBlock\DocBlock::getSingleLineDocBlockEntry instead + * + * @param Line[] $lines + */ + private function getSingleLineDocBlockEntry(array $lines): string + { + $line = $lines[0]; + $line = str_replace('*/', '', $line->getContent()); + $line = trim($line); + $line = str_split($line); + $i = \count($line); + do { + --$i; + } while ('*' !== $line[$i] && '*' !== $line[$i - 1] && '/' !== $line[$i - 2]); + if (' ' === $line[$i]) { + ++$i; + } + $line = \array_slice($line, $i); + + return implode('', $line); + } + + /** + * Updates the depends tag on the current doc block. + */ + private function updateDependsAnnotation(Line $line): Line + { + if ('annotation' === $this->configuration['style']) { + return $this->removeTestPrefixFromDependsAnnotation($line); + } + + return $this->addTestPrefixToDependsAnnotation($line); + } + + private function removeTestPrefixFromDependsAnnotation(Line $line): Line + { + $line = str_split($line->getContent()); + + $dependsIndex = $this->findWhereDependsFunctionNameStarts($line); + $dependsFunctionName = implode('', \array_slice($line, $dependsIndex)); + + if ($this->hasTestPrefix($dependsFunctionName)) { + $dependsFunctionName = $this->removeTestPrefix($dependsFunctionName); + } + array_splice($line, $dependsIndex); + + return new Line(implode('', $line).$dependsFunctionName); + } + + private function addTestPrefixToDependsAnnotation(Line $line): Line + { + $line = str_split($line->getContent()); + $dependsIndex = $this->findWhereDependsFunctionNameStarts($line); + $dependsFunctionName = implode('', \array_slice($line, $dependsIndex)); + + if (!$this->hasTestPrefix($dependsFunctionName)) { + $dependsFunctionName = $this->addTestPrefix($dependsFunctionName); + } + + array_splice($line, $dependsIndex); + + return new Line(implode('', $line).$dependsFunctionName); + } + + /** + * Helps to find where the function name in the doc block starts. + * + * @param list $line + */ + private function findWhereDependsFunctionNameStarts(array $line): int + { + $index = stripos(implode('', $line), '@depends') + 8; + + while (' ' === $line[$index]) { + ++$index; + } + + return $index; + } + + /** + * @param Line[] $lines + * + * @return Line[] + */ + private function addTestAnnotation(array $lines, Tokens $tokens, int $docBlockIndex): array + { + $doc = new DocBlock($tokens[$docBlockIndex]->getContent()); + + if (!$this->doesDocBlockContainTest($doc)) { + $originalIndent = WhitespacesAnalyzer::detectIndent($tokens, $docBlockIndex); + $lineEnd = $this->whitespacesConfig->getLineEnding(); + + array_splice($lines, -1, 0, $originalIndent.' *'.$lineEnd.$originalIndent.' * @test'.$lineEnd); + } + + return $lines; + } + + private function doesDocBlockContainTest(DocBlock $doc): bool + { + return 0 !== \count($doc->getAnnotationsOfType('test')); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestCaseStaticMethodCallsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestCaseStaticMethodCallsFixer.php new file mode 100644 index 00000000..20537717 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestCaseStaticMethodCallsFixer.php @@ -0,0 +1,491 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\PhpUnit; + +use PhpCsFixer\Fixer\AbstractPhpUnitFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; +use PhpCsFixer\Utils; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; + +/** + * @author Filippo Tessarotto + */ +final class PhpUnitTestCaseStaticMethodCallsFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface +{ + /** + * @internal + */ + public const CALL_TYPE_THIS = 'this'; + + /** + * @internal + */ + public const CALL_TYPE_SELF = 'self'; + + /** + * @internal + */ + public const CALL_TYPE_STATIC = 'static'; + + /** + * @var array + */ + private const STATIC_METHODS = [ + // Assert methods + 'anything' => true, + 'arrayHasKey' => true, + 'assertArrayHasKey' => true, + 'assertArrayIsEqualToArrayIgnoringListOfKeys' => true, + 'assertArrayIsEqualToArrayOnlyConsideringListOfKeys' => true, + 'assertArrayIsIdenticalToArrayIgnoringListOfKeys' => true, + 'assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys' => true, + 'assertArrayNotHasKey' => true, + 'assertArraySubset' => true, + 'assertAttributeContains' => true, + 'assertAttributeContainsOnly' => true, + 'assertAttributeCount' => true, + 'assertAttributeEmpty' => true, + 'assertAttributeEquals' => true, + 'assertAttributeGreaterThan' => true, + 'assertAttributeGreaterThanOrEqual' => true, + 'assertAttributeInstanceOf' => true, + 'assertAttributeInternalType' => true, + 'assertAttributeLessThan' => true, + 'assertAttributeLessThanOrEqual' => true, + 'assertAttributeNotContains' => true, + 'assertAttributeNotContainsOnly' => true, + 'assertAttributeNotCount' => true, + 'assertAttributeNotEmpty' => true, + 'assertAttributeNotEquals' => true, + 'assertAttributeNotInstanceOf' => true, + 'assertAttributeNotInternalType' => true, + 'assertAttributeNotSame' => true, + 'assertAttributeSame' => true, + 'assertClassHasAttribute' => true, + 'assertClassHasStaticAttribute' => true, + 'assertClassNotHasAttribute' => true, + 'assertClassNotHasStaticAttribute' => true, + 'assertContains' => true, + 'assertContainsEquals' => true, + 'assertContainsOnly' => true, + 'assertContainsOnlyInstancesOf' => true, + 'assertCount' => true, + 'assertDirectoryDoesNotExist' => true, + 'assertDirectoryExists' => true, + 'assertDirectoryIsNotReadable' => true, + 'assertDirectoryIsNotWritable' => true, + 'assertDirectoryIsReadable' => true, + 'assertDirectoryIsWritable' => true, + 'assertDirectoryNotExists' => true, + 'assertDirectoryNotIsReadable' => true, + 'assertDirectoryNotIsWritable' => true, + 'assertDoesNotMatchRegularExpression' => true, + 'assertEmpty' => true, + 'assertEquals' => true, + 'assertEqualsCanonicalizing' => true, + 'assertEqualsIgnoringCase' => true, + 'assertEqualsWithDelta' => true, + 'assertEqualXMLStructure' => true, + 'assertFalse' => true, + 'assertFileDoesNotExist' => true, + 'assertFileEquals' => true, + 'assertFileEqualsCanonicalizing' => true, + 'assertFileEqualsIgnoringCase' => true, + 'assertFileExists' => true, + 'assertFileIsNotReadable' => true, + 'assertFileIsNotWritable' => true, + 'assertFileIsReadable' => true, + 'assertFileIsWritable' => true, + 'assertFileMatchesFormat' => true, + 'assertFileMatchesFormatFile' => true, + 'assertFileNotEquals' => true, + 'assertFileNotEqualsCanonicalizing' => true, + 'assertFileNotEqualsIgnoringCase' => true, + 'assertFileNotExists' => true, + 'assertFileNotIsReadable' => true, + 'assertFileNotIsWritable' => true, + 'assertFinite' => true, + 'assertGreaterThan' => true, + 'assertGreaterThanOrEqual' => true, + 'assertInfinite' => true, + 'assertInstanceOf' => true, + 'assertInternalType' => true, + 'assertIsArray' => true, + 'assertIsBool' => true, + 'assertIsCallable' => true, + 'assertIsClosedResource' => true, + 'assertIsFloat' => true, + 'assertIsInt' => true, + 'assertIsIterable' => true, + 'assertIsList' => true, + 'assertIsNotArray' => true, + 'assertIsNotBool' => true, + 'assertIsNotCallable' => true, + 'assertIsNotClosedResource' => true, + 'assertIsNotFloat' => true, + 'assertIsNotInt' => true, + 'assertIsNotIterable' => true, + 'assertIsNotNumeric' => true, + 'assertIsNotObject' => true, + 'assertIsNotReadable' => true, + 'assertIsNotResource' => true, + 'assertIsNotScalar' => true, + 'assertIsNotString' => true, + 'assertIsNotWritable' => true, + 'assertIsNumeric' => true, + 'assertIsObject' => true, + 'assertIsReadable' => true, + 'assertIsResource' => true, + 'assertIsScalar' => true, + 'assertIsString' => true, + 'assertIsWritable' => true, + 'assertJson' => true, + 'assertJsonFileEqualsJsonFile' => true, + 'assertJsonFileNotEqualsJsonFile' => true, + 'assertJsonStringEqualsJsonFile' => true, + 'assertJsonStringEqualsJsonString' => true, + 'assertJsonStringNotEqualsJsonFile' => true, + 'assertJsonStringNotEqualsJsonString' => true, + 'assertLessThan' => true, + 'assertLessThanOrEqual' => true, + 'assertMatchesRegularExpression' => true, + 'assertNan' => true, + 'assertNotContains' => true, + 'assertNotContainsEquals' => true, + 'assertNotContainsOnly' => true, + 'assertNotCount' => true, + 'assertNotEmpty' => true, + 'assertNotEquals' => true, + 'assertNotEqualsCanonicalizing' => true, + 'assertNotEqualsIgnoringCase' => true, + 'assertNotEqualsWithDelta' => true, + 'assertNotFalse' => true, + 'assertNotInstanceOf' => true, + 'assertNotInternalType' => true, + 'assertNotIsReadable' => true, + 'assertNotIsWritable' => true, + 'assertNotNull' => true, + 'assertNotRegExp' => true, + 'assertNotSame' => true, + 'assertNotSameSize' => true, + 'assertNotTrue' => true, + 'assertNull' => true, + 'assertObjectEquals' => true, + 'assertObjectHasAttribute' => true, + 'assertObjectHasProperty' => true, + 'assertObjectNotHasAttribute' => true, + 'assertObjectNotHasProperty' => true, + 'assertRegExp' => true, + 'assertSame' => true, + 'assertSameSize' => true, + 'assertStringContainsString' => true, + 'assertStringContainsStringIgnoringCase' => true, + 'assertStringContainsStringIgnoringLineEndings' => true, + 'assertStringEndsNotWith' => true, + 'assertStringEndsWith' => true, + 'assertStringEqualsFile' => true, + 'assertStringEqualsFileCanonicalizing' => true, + 'assertStringEqualsFileIgnoringCase' => true, + 'assertStringEqualsStringIgnoringLineEndings' => true, + 'assertStringMatchesFormat' => true, + 'assertStringMatchesFormatFile' => true, + 'assertStringNotContainsString' => true, + 'assertStringNotContainsStringIgnoringCase' => true, + 'assertStringNotEqualsFile' => true, + 'assertStringNotEqualsFileCanonicalizing' => true, + 'assertStringNotEqualsFileIgnoringCase' => true, + 'assertStringNotMatchesFormat' => true, + 'assertStringNotMatchesFormatFile' => true, + 'assertStringStartsNotWith' => true, + 'assertStringStartsWith' => true, + 'assertThat' => true, + 'assertTrue' => true, + 'assertXmlFileEqualsXmlFile' => true, + 'assertXmlFileNotEqualsXmlFile' => true, + 'assertXmlStringEqualsXmlFile' => true, + 'assertXmlStringEqualsXmlString' => true, + 'assertXmlStringNotEqualsXmlFile' => true, + 'assertXmlStringNotEqualsXmlString' => true, + 'attribute' => true, + 'attributeEqualTo' => true, + 'callback' => true, + 'classHasAttribute' => true, + 'classHasStaticAttribute' => true, + 'contains' => true, + 'containsEqual' => true, + 'containsIdentical' => true, + 'containsOnly' => true, + 'containsOnlyInstancesOf' => true, + 'countOf' => true, + 'directoryExists' => true, + 'equalTo' => true, + 'equalToCanonicalizing' => true, + 'equalToIgnoringCase' => true, + 'equalToWithDelta' => true, + 'fail' => true, + 'fileExists' => true, + 'getCount' => true, + 'getObjectAttribute' => true, + 'getStaticAttribute' => true, + 'greaterThan' => true, + 'greaterThanOrEqual' => true, + 'identicalTo' => true, + 'isEmpty' => true, + 'isFalse' => true, + 'isFinite' => true, + 'isInfinite' => true, + 'isInstanceOf' => true, + 'isJson' => true, + 'isList' => true, + 'isNan' => true, + 'isNull' => true, + 'isReadable' => true, + 'isTrue' => true, + 'isType' => true, + 'isWritable' => true, + 'lessThan' => true, + 'lessThanOrEqual' => true, + 'logicalAnd' => true, + 'logicalNot' => true, + 'logicalOr' => true, + 'logicalXor' => true, + 'markTestIncomplete' => true, + 'markTestSkipped' => true, + 'matches' => true, + 'matchesRegularExpression' => true, + 'objectEquals' => true, + 'objectHasAttribute' => true, + 'readAttribute' => true, + 'resetCount' => true, + 'stringContains' => true, + 'stringEndsWith' => true, + 'stringEqualsStringIgnoringLineEndings' => true, + 'stringStartsWith' => true, + + // TestCase methods + 'any' => true, + 'at' => true, + 'atLeast' => true, + 'atLeastOnce' => true, + 'atMost' => true, + 'exactly' => true, + 'never' => true, + 'once' => true, + 'onConsecutiveCalls' => true, + 'returnArgument' => true, + 'returnCallback' => true, + 'returnSelf' => true, + 'returnValue' => true, + 'returnValueMap' => true, + 'setUpBeforeClass' => true, + 'tearDownAfterClass' => true, + 'throwException' => true, + ]; + + /** + * @var array + */ + private const ALLOWED_VALUES = [ + self::CALL_TYPE_THIS => true, + self::CALL_TYPE_SELF => true, + self::CALL_TYPE_STATIC => true, + ]; + + /** + * @var array>> + */ + private array $conversionMap = [ + self::CALL_TYPE_THIS => [[T_OBJECT_OPERATOR, '->'], [T_VARIABLE, '$this']], + self::CALL_TYPE_SELF => [[T_DOUBLE_COLON, '::'], [T_STRING, 'self']], + self::CALL_TYPE_STATIC => [[T_DOUBLE_COLON, '::'], [T_STATIC, 'static']], + ]; + + public function getDefinition(): FixerDefinitionInterface + { + $codeSample = 'assertSame(1, 2); + self::assertSame(1, 2); + static::assertSame(1, 2); + } +} +'; + + return new FixerDefinition( + 'Calls to `PHPUnit\Framework\TestCase` static methods must all be of the same type, either `$this->`, `self::` or `static::`.', + [ + new CodeSample($codeSample), + new CodeSample($codeSample, ['call_type' => self::CALL_TYPE_THIS]), + ], + null, + 'Risky when PHPUnit methods are overridden or not accessible, or when project has PHPUnit incompatibilities.' + ); + } + + /** + * {@inheritdoc} + * + * Must run before SelfStaticAccessorFixer. + */ + public function getPriority(): int + { + return 0; + } + + public function isRisky(): bool + { + return true; + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('call_type', 'The call type to use for referring to PHPUnit methods.')) + ->setAllowedTypes(['string']) + ->setAllowedValues(array_keys(self::ALLOWED_VALUES)) + ->setDefault('static') + ->getOption(), + (new FixerOptionBuilder('methods', 'Dictionary of `method` => `call_type` values that differ from the default strategy.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([static function (array $option): bool { + foreach ($option as $method => $value) { + if (!isset(self::STATIC_METHODS[$method])) { + throw new InvalidOptionsException( + sprintf( + 'Unexpected "methods" key, expected any of %s, got "%s".', + Utils::naturalLanguageJoin(array_keys(self::STATIC_METHODS)), + \gettype($method).'#'.$method + ) + ); + } + + if (!isset(self::ALLOWED_VALUES[$value])) { + throw new InvalidOptionsException( + sprintf( + 'Unexpected value for method "%s", expected any of %s, got "%s".', + $method, + Utils::naturalLanguageJoin(array_keys(self::ALLOWED_VALUES)), + \is_object($value) ? \get_class($value) : (null === $value ? 'null' : \gettype($value).'#'.$value) + ) + ); + } + } + + return true; + }]) + ->setDefault([]) + ->getOption(), + ]); + } + + protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void + { + $analyzer = new TokensAnalyzer($tokens); + + for ($index = $startIndex; $index < $endIndex; ++$index) { + // skip anonymous classes + if ($tokens[$index]->isGivenKind(T_CLASS)) { + $index = $this->findEndOfNextBlock($tokens, $index); + + continue; + } + + $callType = $this->configuration['call_type']; + + if ($tokens[$index]->isGivenKind(T_FUNCTION)) { + // skip lambda + if ($analyzer->isLambda($index)) { + $index = $this->findEndOfNextBlock($tokens, $index); + + continue; + } + + // do not change `self` to `this` in static methods + if ('this' === $callType) { + $attributes = $analyzer->getMethodAttributes($index); + + if (false !== $attributes['static']) { + $index = $this->findEndOfNextBlock($tokens, $index); + + continue; + } + } + } + + if (!$tokens[$index]->isGivenKind(T_STRING) || !isset(self::STATIC_METHODS[$tokens[$index]->getContent()])) { + continue; + } + + $nextIndex = $tokens->getNextMeaningfulToken($index); + + if (!$tokens[$nextIndex]->equals('(')) { + $index = $nextIndex; + + continue; + } + + if ($tokens[$tokens->getNextMeaningfulToken($nextIndex)]->isGivenKind(CT::T_FIRST_CLASS_CALLABLE)) { + continue; + } + + $methodName = $tokens[$index]->getContent(); + + if (isset($this->configuration['methods'][$methodName])) { + $callType = $this->configuration['methods'][$methodName]; + } + + $operatorIndex = $tokens->getPrevMeaningfulToken($index); + $referenceIndex = $tokens->getPrevMeaningfulToken($operatorIndex); + + if (!$this->needsConversion($tokens, $index, $referenceIndex, $callType)) { + continue; + } + + $tokens[$operatorIndex] = new Token($this->conversionMap[$callType][0]); + $tokens[$referenceIndex] = new Token($this->conversionMap[$callType][1]); + } + } + + private function needsConversion(Tokens $tokens, int $index, int $referenceIndex, string $callType): bool + { + $functionsAnalyzer = new FunctionsAnalyzer(); + + return $functionsAnalyzer->isTheSameClassCall($tokens, $index) + && !$tokens[$referenceIndex]->equals($this->conversionMap[$callType][1], false); + } + + private function findEndOfNextBlock(Tokens $tokens, int $index): int + { + $nextIndex = $tokens->getNextTokenOfKind($index, [';', '{']); + + return $tokens[$nextIndex]->equals('{') + ? $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $nextIndex) + : $nextIndex; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestClassRequiresCoversFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestClassRequiresCoversFixer.php new file mode 100644 index 00000000..a8373290 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestClassRequiresCoversFixer.php @@ -0,0 +1,82 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\PhpUnit; + +use PhpCsFixer\Fixer\AbstractPhpUnitFixer; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Dariusz Rumiński + */ +final class PhpUnitTestClassRequiresCoversFixer extends AbstractPhpUnitFixer implements WhitespacesAwareFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Adds a default `@coversNothing` annotation to PHPUnit test classes that have no `@covers*` annotation.', + [ + new CodeSample( + 'assertSame(a(), b()); + } +} +' + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before PhpdocSeparationFixer. + */ + public function getPriority(): int + { + return 0; + } + + protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void + { + $classIndex = $tokens->getPrevTokenOfKind($startIndex, [[T_CLASS]]); + + $tokensAnalyzer = new TokensAnalyzer($tokens); + $modifiers = $tokensAnalyzer->getClassyModifiers($classIndex); + + if (isset($modifiers['abstract'])) { + return; // don't add `@covers` annotation for abstract base classes + } + + $this->ensureIsDockBlockWithAnnotation( + $tokens, + $classIndex, + 'coversNothing', + [ + 'covers', + 'coversDefaultClass', + 'coversNothing', + ] + ); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/AlignMultilineCommentFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/AlignMultilineCommentFixer.php new file mode 100644 index 00000000..60c27d10 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/AlignMultilineCommentFixer.php @@ -0,0 +1,167 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Filippo Tessarotto + * @author Julien Falque + */ +final class AlignMultilineCommentFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface +{ + /** + * @var null|int[] + */ + private $tokenKinds; + + public function configure(array $configuration): void + { + parent::configure($configuration); + + $this->tokenKinds = [T_DOC_COMMENT]; + if ('phpdocs_only' !== $this->configuration['comment_type']) { + $this->tokenKinds[] = T_COMMENT; + } + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Each line of multi-line DocComments must have an asterisk [PSR-5] and must be aligned with the first one.', + [ + new CodeSample( + ' 'phpdocs_like'] + ), + new CodeSample( + ' 'all_multiline'] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before CommentToPhpdocFixer, GeneralPhpdocAnnotationRemoveFixer, GeneralPhpdocTagRenameFixer, NoBlankLinesAfterPhpdocFixer, NoEmptyPhpdocFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocAlignFixer, PhpdocAnnotationWithoutDotFixer, PhpdocArrayTypeFixer, PhpdocInlineTagNormalizerFixer, PhpdocLineSpanFixer, PhpdocListTypeFixer, PhpdocNoAccessFixer, PhpdocNoAliasTagFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocNoUselessInheritdocFixer, PhpdocOrderByValueFixer, PhpdocOrderFixer, PhpdocParamOrderFixer, PhpdocReadonlyClassCommentToKeywordFixer, PhpdocReturnSelfReferenceFixer, PhpdocSeparationFixer, PhpdocSingleLineVarSpacingFixer, PhpdocSummaryFixer, PhpdocTagCasingFixer, PhpdocTagTypeFixer, PhpdocToParamTypeFixer, PhpdocToPropertyTypeFixer, PhpdocToReturnTypeFixer, PhpdocTrimConsecutiveBlankLineSeparationFixer, PhpdocTrimFixer, PhpdocTypesOrderFixer, PhpdocVarAnnotationCorrectOrderFixer, PhpdocVarWithoutNameFixer. + * Must run after ArrayIndentationFixer. + */ + public function getPriority(): int + { + return 27; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound($this->tokenKinds); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $lineEnding = $this->whitespacesConfig->getLineEnding(); + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind($this->tokenKinds)) { + continue; + } + + $whitespace = ''; + $previousIndex = $index - 1; + + if ($tokens[$previousIndex]->isWhitespace()) { + $whitespace = $tokens[$previousIndex]->getContent(); + --$previousIndex; + } + + if ($tokens[$previousIndex]->isGivenKind(T_OPEN_TAG)) { + $whitespace = Preg::replace('/\S/', '', $tokens[$previousIndex]->getContent()).$whitespace; + } + + if (!Preg::match('/\R(\h*)$/', $whitespace, $matches)) { + continue; + } + + if ($token->isGivenKind(T_COMMENT) && 'all_multiline' !== $this->configuration['comment_type'] && Preg::match('/\R(?:\R|\s*[^\s\*])/', $token->getContent())) { + continue; + } + + $indentation = $matches[1]; + $lines = Preg::split('/\R/u', $token->getContent()); + + foreach ($lines as $lineNumber => $line) { + if (0 === $lineNumber) { + continue; + } + + $line = ltrim($line); + + if ($token->isGivenKind(T_COMMENT) && (!isset($line[0]) || '*' !== $line[0])) { + continue; + } + + if (!isset($line[0])) { + $line = '*'; + } elseif ('*' !== $line[0]) { + $line = '* '.$line; + } + + $lines[$lineNumber] = $indentation.' '.$line; + } + + $tokens[$index] = new Token([$token->getId(), implode($lineEnding, $lines)]); + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('comment_type', 'Whether to fix PHPDoc comments only (`phpdocs_only`), any multi-line comment whose lines all start with an asterisk (`phpdocs_like`) or any multi-line comment (`all_multiline`).')) + ->setAllowedValues(['phpdocs_only', 'phpdocs_like', 'all_multiline']) + ->setDefault('phpdocs_only') + ->getOption(), + ]); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/GeneralPhpdocAnnotationRemoveFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/GeneralPhpdocAnnotationRemoveFixer.php new file mode 100644 index 00000000..b2282e86 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/GeneralPhpdocAnnotationRemoveFixer.php @@ -0,0 +1,162 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\DocBlock\Annotation; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Graham Campbell + * @author Dariusz Rumiński + */ +final class GeneralPhpdocAnnotationRemoveFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Configured annotations should be omitted from PHPDoc.', + [ + new CodeSample( + ' ['author']] + ), + new CodeSample( + ' ['author'], 'case_sensitive' => false] + ), + new CodeSample( + ' ['package', 'subpackage']] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before NoEmptyPhpdocFixer, PhpdocAlignFixer, PhpdocLineSpanFixer, PhpdocSeparationFixer, PhpdocTrimFixer. + * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. + */ + public function getPriority(): int + { + return 10; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + if (0 === \count($this->configuration['annotations'])) { + return; + } + + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + $doc = new DocBlock($token->getContent()); + $annotations = $this->getAnnotationsToRemove($doc); + + // nothing to do if there are no annotations + if (0 === \count($annotations)) { + continue; + } + + foreach ($annotations as $annotation) { + $annotation->remove(); + } + + if ('' === $doc->getContent()) { + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } else { + $tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]); + } + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('annotations', 'List of annotations to remove, e.g. `["author"]`.')) + ->setAllowedTypes(['array']) + ->setDefault([]) + ->getOption(), + (new FixerOptionBuilder('case_sensitive', 'Should annotations be case sensitive.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), + ]); + } + + /** + * @return list + */ + private function getAnnotationsToRemove(DocBlock $doc): array + { + if (true === $this->configuration['case_sensitive']) { + return $doc->getAnnotationsOfType($this->configuration['annotations']); + } + + $typesToSearchFor = array_map(static fn (string $type): string => strtolower($type), $this->configuration['annotations']); + + $annotations = []; + + foreach ($doc->getAnnotations() as $annotation) { + $tagName = strtolower($annotation->getTag()->getName()); + if (\in_array($tagName, $typesToSearchFor, true)) { + $annotations[] = $annotation; + } + } + + return $annotations; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/GeneralPhpdocTagRenameFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/GeneralPhpdocTagRenameFixer.php new file mode 100644 index 00000000..7f68db60 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/GeneralPhpdocTagRenameFixer.php @@ -0,0 +1,199 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; +use Symfony\Component\OptionsResolver\Options; + +final class GeneralPhpdocTagRenameFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Renames PHPDoc tags.', + [ + new CodeSample(" [ + 'inheritDocs' => 'inheritDoc', + ], + ]), + new CodeSample(" [ + 'inheritDocs' => 'inheritDoc', + ], + 'fix_annotation' => false, + ]), + new CodeSample(" [ + 'inheritDocs' => 'inheritDoc', + ], + 'fix_inline' => false, + ]), + new CodeSample(" [ + 'inheritDocs' => 'inheritDoc', + ], + 'case_sensitive' => true, + ]), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before PhpdocAddMissingParamAnnotationFixer, PhpdocAlignFixer. + * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. + */ + public function getPriority(): int + { + // must be run before PhpdocAddMissingParamAnnotationFixer + return 11; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('fix_annotation', 'Whether annotation tags should be fixed.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), + (new FixerOptionBuilder('fix_inline', 'Whether inline tags should be fixed.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), + (new FixerOptionBuilder('replacements', 'A map of tags to replace.')) + ->setAllowedTypes(['array']) + ->setNormalizer(static function (Options $options, array $value): array { + $normalizedValue = []; + + foreach ($value as $from => $to) { + if (!\is_string($from)) { + throw new InvalidOptionsException('Tag to replace must be a string.'); + } + + if (!\is_string($to)) { + throw new InvalidOptionsException(sprintf( + 'Tag to replace to from "%s" must be a string.', + $from + )); + } + + if (!Preg::match('#^\S+$#', $to) || str_contains($to, '*/')) { + throw new InvalidOptionsException(sprintf( + 'Tag "%s" cannot be replaced by invalid tag "%s".', + $from, + $to + )); + } + + $from = trim($from); + $to = trim($to); + + if (false === $options['case_sensitive']) { + $lowercaseFrom = strtolower($from); + + if (isset($normalizedValue[$lowercaseFrom]) && $normalizedValue[$lowercaseFrom] !== $to) { + throw new InvalidOptionsException(sprintf( + 'Tag "%s" cannot be configured to be replaced with several different tags when case sensitivity is off.', + $from + )); + } + + $from = $lowercaseFrom; + } + + $normalizedValue[$from] = $to; + } + + foreach ($normalizedValue as $from => $to) { + if (isset($normalizedValue[$to]) && $normalizedValue[$to] !== $to) { + throw new InvalidOptionsException(sprintf( + 'Cannot change tag "%1$s" to tag "%2$s", as the tag "%2$s" is configured to be replaced to "%3$s".', + $from, + $to, + $normalizedValue[$to] + )); + } + } + + return $normalizedValue; + }) + ->setDefault([]) + ->getOption(), + (new FixerOptionBuilder('case_sensitive', 'Whether tags should be replaced only if they have exact same casing.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + if (0 === \count($this->configuration['replacements'])) { + return; + } + + if (true === $this->configuration['fix_annotation']) { + $regex = true === $this->configuration['fix_inline'] + ? '/"[^"]*"(*SKIP)(*FAIL)|\b(?<=@)(%s)\b/' + : '/"[^"]*"(*SKIP)(*FAIL)|(?configuration['case_sensitive']; + $replacements = $this->configuration['replacements']; + $regex = sprintf($regex, implode('|', array_keys($replacements))); + + if ($caseInsensitive) { + $regex .= 'i'; + } + + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + $tokens[$index] = new Token([T_DOC_COMMENT, Preg::replaceCallback( + $regex, + static function (array $matches) use ($caseInsensitive, $replacements) { + if ($caseInsensitive) { + $matches[1] = strtolower($matches[1]); + } + + return $replacements[$matches[1]]; + }, + $token->getContent() + )]); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/NoBlankLinesAfterPhpdocFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/NoBlankLinesAfterPhpdocFixer.php new file mode 100644 index 00000000..33fb2d0e --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/NoBlankLinesAfterPhpdocFixer.php @@ -0,0 +1,110 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Graham Campbell + */ +final class NoBlankLinesAfterPhpdocFixer extends AbstractFixer +{ + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'There should not be blank lines between docblock and the documented element.', + [ + new CodeSample( + ' $token) { + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + // get the next non-whitespace token inc comments, provided + // that there is whitespace between it and the current token + $next = $tokens->getNextNonWhitespace($index); + if ($index + 2 === $next && false === $tokens[$next]->isGivenKind($forbiddenSuccessors)) { + $this->fixWhitespace($tokens, $index + 1); + } + } + } + + /** + * Cleanup a whitespace token. + */ + private function fixWhitespace(Tokens $tokens, int $index): void + { + $content = $tokens[$index]->getContent(); + // if there is more than one new line in the whitespace, then we need to fix it + if (substr_count($content, "\n") > 1) { + // the final bit of the whitespace must be the next statement's indentation + $tokens[$index] = new Token([T_WHITESPACE, substr($content, strrpos($content, "\n"))]); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/NoEmptyPhpdocFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/NoEmptyPhpdocFixer.php new file mode 100644 index 00000000..27d7af64 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/NoEmptyPhpdocFixer.php @@ -0,0 +1,84 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class NoEmptyPhpdocFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'There should not be empty PHPDoc blocks.', + [new CodeSample("isTokenKindFound(T_DOC_COMMENT); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + if (!Preg::match('#^/\*\*[\s\*]*\*/$#', $token->getContent())) { + continue; + } + + if ( + $tokens[$index - 1]->isGivenKind([T_OPEN_TAG, T_WHITESPACE]) + && substr_count($tokens[$index - 1]->getContent(), "\n") > 0 + && $tokens[$index + 1]->isGivenKind(T_WHITESPACE) + && Preg::match('/^\R/', $tokens[$index + 1]->getContent()) + ) { + $tokens[$index - 1] = new Token([ + $tokens[$index - 1]->getId(), + Preg::replace('/\h*$/', '', $tokens[$index - 1]->getContent()), + ]); + + $newContent = Preg::replace('/^\R/', '', $tokens[$index + 1]->getContent()); + if ('' === $newContent) { + $tokens->clearAt($index + 1); + } else { + $tokens[$index + 1] = new Token([T_WHITESPACE, $newContent]); + } + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixer.php new file mode 100644 index 00000000..90cf1ad6 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixer.php @@ -0,0 +1,725 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\DocBlock\Annotation; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\DocBlock\TypeExpression; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\TypeAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\NamespacesAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @phpstan-type _TypeInfo array{ + * types: list, + * allows_null: bool, + * } + * @phpstan-type _DocumentElement array{ + * index: int, + * type: 'classy'|'function'|'property', + * modifiers: array, + * types: array, + * } + */ +final class NoSuperfluousPhpdocTagsFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** @var _TypeInfo */ + private const NO_TYPE_INFO = [ + 'types' => [], + 'allows_null' => true, + ]; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Removes `@param`, `@return` and `@var` tags that don\'t provide any useful information.', + [ + new CodeSample( + ' true], + ), + new CodeSample( + ' true], + ), + new CodeSample( + ' true], + ), + new CodeSample( + ' true], + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before NoEmptyPhpdocFixer, PhpdocAlignFixer, VoidReturnFixer. + * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, FullyQualifiedStrictTypesFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocIndentFixer, PhpdocLineSpanFixer, PhpdocReturnSelfReferenceFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocToParamTypeFixer, PhpdocToPropertyTypeFixer, PhpdocToReturnTypeFixer, PhpdocTypesFixer. + */ + public function getPriority(): int + { + return 6; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + + $namespaceUseAnalyzer = new NamespaceUsesAnalyzer(); + $shortNames = []; + $currentSymbol = null; + $currentSymbolEndIndex = null; + + foreach ($namespaceUseAnalyzer->getDeclarationsFromTokens($tokens) as $namespaceUseAnalysis) { + $shortNames[strtolower($namespaceUseAnalysis->getShortName())] = strtolower($namespaceUseAnalysis->getFullName()); + } + + $symbolKinds = [T_CLASS, T_INTERFACE]; + if (\defined('T_ENUM')) { // @TODO drop the condition when requiring PHP 8.1+ + $symbolKinds[] = T_ENUM; + } + + foreach ($tokens as $index => $token) { + if ($index === $currentSymbolEndIndex) { + $currentSymbol = null; + $currentSymbolEndIndex = null; + + continue; + } + + if ($token->isGivenKind(T_CLASS) && $tokensAnalyzer->isAnonymousClass($index)) { + continue; + } + + if ($token->isGivenKind($symbolKinds)) { + $currentSymbol = $tokens[$tokens->getNextMeaningfulToken($index)]->getContent(); + $currentSymbolEndIndex = $tokens->findBlockEnd( + Tokens::BLOCK_TYPE_CURLY_BRACE, + $tokens->getNextTokenOfKind($index, ['{']), + ); + + continue; + } + + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + $documentedElement = $this->findDocumentedElement($tokens, $index); + + if (null === $documentedElement) { + continue; + } + + $content = $initialContent = $token->getContent(); + + if (true === $this->configuration['remove_inheritdoc']) { + $content = $this->removeSuperfluousInheritDoc($content); + } + + $namespace = (new NamespacesAnalyzer())->getNamespaceAt($tokens, $index)->getFullName(); + if ('' === $namespace) { + $namespace = null; + } + + if ('function' === $documentedElement['type']) { + $content = $this->fixFunctionDocComment($content, $tokens, $documentedElement, $namespace, $currentSymbol, $shortNames); + } elseif ('property' === $documentedElement['type']) { + $content = $this->fixPropertyDocComment($content, $tokens, $documentedElement, $namespace, $currentSymbol, $shortNames); + } elseif ('classy' === $documentedElement['type']) { + $content = $this->fixClassDocComment($content, $documentedElement); + } else { + throw new \RuntimeException('Unknown type.'); + } + + if ('' === $content) { + $content = '/** */'; + } + + if ($content !== $initialContent) { + $tokens[$index] = new Token([T_DOC_COMMENT, $content]); + } + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('allow_mixed', 'Whether type `mixed` without description is allowed (`true`) or considered superfluous (`false`).')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + (new FixerOptionBuilder('remove_inheritdoc', 'Remove `@inheritDoc` tags.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + (new FixerOptionBuilder('allow_hidden_params', 'Whether `param` annotation for hidden params in method signature are allowed.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) // @TODO set to `true` on 4.0 + ->getOption(), + (new FixerOptionBuilder('allow_unused_params', 'Whether `param` annotation without actual signature is allowed (`true`) or considered superfluous (`false`).')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + + /** + * @return null|_DocumentElement + */ + private function findDocumentedElement(Tokens $tokens, int $docCommentIndex): ?array + { + $modifierKinds = [ + T_PRIVATE, + T_PROTECTED, + T_PUBLIC, + T_ABSTRACT, + T_FINAL, + T_STATIC, + ]; + + $typeKinds = [ + CT::T_NULLABLE_TYPE, + CT::T_ARRAY_TYPEHINT, + CT::T_TYPE_ALTERNATION, + CT::T_TYPE_INTERSECTION, + T_STRING, + T_NS_SEPARATOR, + ]; + + if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required + $modifierKinds[] = T_READONLY; + } + + $element = [ + 'modifiers' => [], + 'types' => [], + ]; + + $index = $tokens->getNextMeaningfulToken($docCommentIndex); + + // @TODO: drop condition when PHP 8.0+ is required + if (null !== $index && \defined('T_ATTRIBUTE') && $tokens[$index]->isGivenKind(T_ATTRIBUTE)) { + do { + $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ATTRIBUTE, $index); + $index = $tokens->getNextMeaningfulToken($index); + } while (null !== $index && $tokens[$index]->isGivenKind(T_ATTRIBUTE)); + } + + while (true) { + if (null === $index) { + break; + } + + if ($tokens[$index]->isClassy()) { + $element['index'] = $index; + $element['type'] = 'classy'; + + return $element; + } + + if ($tokens[$index]->isGivenKind([T_FUNCTION, T_FN])) { + $element['index'] = $index; + $element['type'] = 'function'; + + return $element; + } + + if ($tokens[$index]->isGivenKind(T_VARIABLE)) { + $element['index'] = $index; + $element['type'] = 'property'; + + return $element; + } + + if ($tokens[$index]->isGivenKind($modifierKinds)) { + $element['modifiers'][$index] = $tokens[$index]; + } elseif ($tokens[$index]->isGivenKind($typeKinds)) { + $element['types'][$index] = $tokens[$index]; + } else { + break; + } + + $index = $tokens->getNextMeaningfulToken($index); + } + + return null; + } + + /** + * @param _DocumentElement&array{type: 'function'} $element + * @param null|non-empty-string $namespace + * @param array $shortNames + */ + private function fixFunctionDocComment( + string $content, + Tokens $tokens, + array $element, + ?string $namespace, + ?string $currentSymbol, + array $shortNames + ): string { + $docBlock = new DocBlock($content); + + $openingParenthesisIndex = $tokens->getNextTokenOfKind($element['index'], ['(']); + $closingParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openingParenthesisIndex); + + $argumentsInfo = $this->getArgumentsInfo( + $tokens, + $openingParenthesisIndex + 1, + $closingParenthesisIndex - 1 + ); + + foreach ($docBlock->getAnnotationsOfType('param') as $annotation) { + $argumentName = $annotation->getVariableName(); + + if (null === $argumentName) { + if ($this->annotationIsSuperfluous($annotation, self::NO_TYPE_INFO, $namespace, $currentSymbol, $shortNames)) { + $annotation->remove(); + } + + continue; + } + + if (!isset($argumentsInfo[$argumentName]) && true === $this->configuration['allow_unused_params']) { + continue; + } + + if (!isset($argumentsInfo[$argumentName]) || $this->annotationIsSuperfluous($annotation, $argumentsInfo[$argumentName], $namespace, $currentSymbol, $shortNames)) { + $annotation->remove(); + } + } + + $returnTypeInfo = $this->getReturnTypeInfo($tokens, $closingParenthesisIndex); + + foreach ($docBlock->getAnnotationsOfType('return') as $annotation) { + if ($this->annotationIsSuperfluous($annotation, $returnTypeInfo, $namespace, $currentSymbol, $shortNames)) { + $annotation->remove(); + } + } + + $this->removeSuperfluousModifierAnnotation($docBlock, $element); + + return $docBlock->getContent(); + } + + /** + * @param _DocumentElement&array{type: 'property'} $element + * @param null|non-empty-string $namespace + * @param array $shortNames + */ + private function fixPropertyDocComment( + string $content, + Tokens $tokens, + array $element, + ?string $namespace, + ?string $currentSymbol, + array $shortNames + ): string { + if (\count($element['types']) > 0) { + $propertyTypeInfo = $this->parseTypeHint($tokens, array_key_first($element['types'])); + } else { + $propertyTypeInfo = self::NO_TYPE_INFO; + } + + $docBlock = new DocBlock($content); + + foreach ($docBlock->getAnnotationsOfType('var') as $annotation) { + if ($this->annotationIsSuperfluous($annotation, $propertyTypeInfo, $namespace, $currentSymbol, $shortNames)) { + $annotation->remove(); + } + } + + return $docBlock->getContent(); + } + + /** + * @param _DocumentElement&array{type: 'classy'} $element + */ + private function fixClassDocComment(string $content, array $element): string + { + $docBlock = new DocBlock($content); + + $this->removeSuperfluousModifierAnnotation($docBlock, $element); + + return $docBlock->getContent(); + } + + /** + * @return array + */ + private function getArgumentsInfo(Tokens $tokens, int $start, int $end): array + { + $argumentsInfo = []; + + for ($index = $start; $index <= $end; ++$index) { + $token = $tokens[$index]; + + if (!$token->isGivenKind(T_VARIABLE)) { + continue; + } + + $beforeArgumentIndex = $tokens->getPrevTokenOfKind($index, ['(', ',']); + $typeIndex = $tokens->getNextMeaningfulToken($beforeArgumentIndex); + + if ($typeIndex !== $index) { + $info = $this->parseTypeHint($tokens, $typeIndex); + } else { + $info = self::NO_TYPE_INFO; + } + + if (!$info['allows_null']) { + $nextIndex = $tokens->getNextMeaningfulToken($index); + if ( + $tokens[$nextIndex]->equals('=') + && $tokens[$tokens->getNextMeaningfulToken($nextIndex)]->equals([T_STRING, 'null'], false) + ) { + $info['allows_null'] = true; + } + } + + $argumentsInfo[$token->getContent()] = $info; + } + + // virtualise "hidden params" as if they would be regular ones + if (true === $this->configuration['allow_hidden_params']) { + $paramsString = $tokens->generatePartialCode($start, $end); + Preg::matchAll('|/\*[^$]*(\$\w+)[^*]*\*/|', $paramsString, $matches); + foreach ($matches[1] as $match) { + $argumentsInfo[$match] = self::NO_TYPE_INFO; // HINT: one could try to extract actual type for hidden param, for now we only indicate it's existence + } + } + + return $argumentsInfo; + } + + /** + * @return _TypeInfo + */ + private function getReturnTypeInfo(Tokens $tokens, int $closingParenthesisIndex): array + { + $colonIndex = $tokens->getNextMeaningfulToken($closingParenthesisIndex); + + return $tokens[$colonIndex]->isGivenKind(CT::T_TYPE_COLON) + ? $this->parseTypeHint($tokens, $tokens->getNextMeaningfulToken($colonIndex)) + : self::NO_TYPE_INFO; + } + + /** + * @param int $index The index of the first token of the type hint + * + * @return _TypeInfo + */ + private function parseTypeHint(Tokens $tokens, int $index): array + { + $allowsNull = false; + + $types = []; + + while (true) { + $type = ''; + + if (\defined('T_READONLY') && $tokens[$index]->isGivenKind(T_READONLY)) { // @TODO: simplify condition when PHP 8.1+ is required + $index = $tokens->getNextMeaningfulToken($index); + } + + if ($tokens[$index]->isGivenKind([CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC, CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED, CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE])) { + $index = $tokens->getNextMeaningfulToken($index); + + continue; + } + + if ($tokens[$index]->isGivenKind(CT::T_NULLABLE_TYPE)) { + $allowsNull = true; + $index = $tokens->getNextMeaningfulToken($index); + } + + while ($tokens[$index]->isGivenKind([T_NS_SEPARATOR, T_STATIC, T_STRING, CT::T_ARRAY_TYPEHINT, T_CALLABLE])) { + $type .= $tokens[$index]->getContent(); + $index = $tokens->getNextMeaningfulToken($index); + } + + if ('' === $type) { + break; + } + + $types[] = $type; + + if (!$tokens[$index]->isGivenKind([CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION])) { + break; + } + + $index = $tokens->getNextMeaningfulToken($index); + } + + return [ + 'types' => $types, + 'allows_null' => $allowsNull, + ]; + } + + /** + * @param _TypeInfo $info + * @param null|non-empty-string $namespace + * @param array $symbolShortNames + */ + private function annotationIsSuperfluous( + Annotation $annotation, + array $info, + ?string $namespace, + ?string $currentSymbol, + array $symbolShortNames + ): bool { + if ('param' === $annotation->getTag()->getName()) { + $regex = '{\*\h*@param(?:\h+'.TypeExpression::REGEX_TYPES.')?(?!\S)(?:\h+(?:\&\h*)?(?:\.{3}\h*)?\$\S+)?(?:\s+(?(?!\*+\/)\S+))?}s'; + } elseif ('var' === $annotation->getTag()->getName()) { + $regex = '{\*\h*@var(?:\h+'.TypeExpression::REGEX_TYPES.')?(?!\S)(?:\h+\$\S+)?(?:\s+(?(?!\*\/)\S+))?}s'; + } else { + $regex = '{\*\h*@return(?:\h+'.TypeExpression::REGEX_TYPES.')?(?!\S)(?:\s+(?(?!\*\/)\S+))?}s'; + } + + if (!Preg::match($regex, $annotation->getContent(), $matches)) { + // Unable to match the annotation, it must be malformed or has unsupported format. + // Either way we don't want to tinker with it. + return false; + } + + if (isset($matches['description'])) { + return false; + } + + if (!isset($matches['types']) || '' === $matches['types']) { + // If there's no type info in the annotation, further checks make no sense, exit early. + return true; + } + + $annotationTypes = $this->toComparableNames($annotation->getTypes(), $namespace, $currentSymbol, $symbolShortNames); + + if (['null'] === $annotationTypes && ['null'] !== $info['types']) { + return false; + } + + if (['mixed'] === $annotationTypes && [] === $info['types']) { + return false === $this->configuration['allow_mixed']; + } + + $actualTypes = $info['types']; + + if ($info['allows_null']) { + $actualTypes[] = 'null'; + } + + $actualTypes = $this->toComparableNames($actualTypes, $namespace, $currentSymbol, $symbolShortNames); + + if ($annotationTypes === $actualTypes) { + return true; + } + + // retry comparison with annotation type unioned with null + // phpstan implies the null presence from the native type + return $actualTypes === $this->toComparableNames(array_merge($annotationTypes, ['null']), null, null, []); + } + + /** + * Normalizes types to make them comparable. + * + * Converts given types to lowercase, replaces imports aliases with + * their matching FQCN, and finally sorts the result. + * + * @param list $types The types to normalize + * @param null|non-empty-string $namespace + * @param array $symbolShortNames The imports aliases + * + * @return list The normalized types + */ + private function toComparableNames(array $types, ?string $namespace, ?string $currentSymbol, array $symbolShortNames): array + { + $normalized = array_map( + function (string $type) use ($namespace, $currentSymbol, $symbolShortNames): string { + if (str_contains($type, '&')) { + $intersects = explode('&', $type); + + $intersects = $this->toComparableNames($intersects, $namespace, $currentSymbol, $symbolShortNames); + + return implode('&', $intersects); + } + + if ('self' === $type && null !== $currentSymbol) { + $type = $currentSymbol; + } + + $type = strtolower($type); + + if (isset($symbolShortNames[$type])) { + return $symbolShortNames[$type]; // always FQCN /wo leading backslash and in lower-case + } + + if (str_starts_with($type, '\\')) { + return substr($type, 1); + } + + if (null !== $namespace && !(new TypeAnalysis($type))->isReservedType()) { + $type = strtolower($namespace).'\\'.$type; + } + + return $type; + }, + $types + ); + + sort($normalized); + + return $normalized; + } + + private function removeSuperfluousInheritDoc(string $docComment): string + { + return Preg::replace('~ + # $1: before @inheritDoc tag + ( + # beginning of comment or a PHPDoc tag + (?: + ^/\*\* + (?: + \R + [ \t]*(?:\*[ \t]*)? + )*? + | + @\N+ + ) + + # empty comment lines + (?: + \R + [ \t]*(?:\*[ \t]*?)? + )* + ) + + # spaces before @inheritDoc tag + [ \t]* + + # @inheritDoc tag + (?:@inheritDocs?|\{@inheritDocs?\}) + + # $2: after @inheritDoc tag + ( + # empty comment lines + (?: + \R + [ \t]*(?:\*[ \t]*)? + )* + + # a PHPDoc tag or end of comment + (?: + @\N+ + | + (?: + \R + [ \t]*(?:\*[ \t]*)? + )* + [ \t]*\*/$ + ) + ) + ~ix', '$1$2', $docComment); + } + + /** + * @param _DocumentElement $element + */ + private function removeSuperfluousModifierAnnotation(DocBlock $docBlock, array $element): void + { + foreach (['abstract' => T_ABSTRACT, 'final' => T_FINAL] as $annotationType => $modifierToken) { + $annotations = $docBlock->getAnnotationsOfType($annotationType); + + foreach ($element['modifiers'] as $token) { + if ($token->isGivenKind($modifierToken)) { + foreach ($annotations as $annotation) { + $annotation->remove(); + } + } + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocAddMissingParamAnnotationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocAddMissingParamAnnotationFixer.php new file mode 100644 index 00000000..0739be25 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocAddMissingParamAnnotationFixer.php @@ -0,0 +1,276 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\DocBlock\Line; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class PhpdocAddMissingParamAnnotationFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'PHPDoc should contain `@param` for all params.', + [ + new CodeSample( + ' true] + ), + new CodeSample( + ' false] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before NoEmptyPhpdocFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAlignFixer, PhpdocOrderFixer. + * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, GeneralPhpdocTagRenameFixer, PhpdocIndentFixer, PhpdocNoAliasTagFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. + */ + public function getPriority(): int + { + return 10; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $argumentsAnalyzer = new ArgumentsAnalyzer(); + + for ($index = 0, $limit = $tokens->count(); $index < $limit; ++$index) { + $token = $tokens[$index]; + + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + $tokenContent = $token->getContent(); + + if (false !== stripos($tokenContent, 'inheritdoc')) { + continue; + } + + // ignore one-line phpdocs like `/** foo */`, as there is no place to put new annotations + if (!str_contains($tokenContent, "\n")) { + continue; + } + + $mainIndex = $index; + $index = $tokens->getNextMeaningfulToken($index); + + if (null === $index) { + return; + } + + while ($tokens[$index]->isGivenKind([ + T_ABSTRACT, + T_FINAL, + T_PRIVATE, + T_PROTECTED, + T_PUBLIC, + T_STATIC, + ])) { + $index = $tokens->getNextMeaningfulToken($index); + } + + if (!$tokens[$index]->isGivenKind(T_FUNCTION)) { + continue; + } + + $openIndex = $tokens->getNextTokenOfKind($index, ['(']); + $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex); + + $arguments = []; + + foreach ($argumentsAnalyzer->getArguments($tokens, $openIndex, $index) as $start => $end) { + $argumentInfo = $this->prepareArgumentInformation($tokens, $start, $end); + + if (false === $this->configuration['only_untyped'] || '' === $argumentInfo['type']) { + $arguments[$argumentInfo['name']] = $argumentInfo; + } + } + + if (0 === \count($arguments)) { + continue; + } + + $doc = new DocBlock($tokenContent); + $lastParamLine = null; + + foreach ($doc->getAnnotationsOfType('param') as $annotation) { + $pregMatched = Preg::match('/^[^$]+(\$\w+).*$/s', $annotation->getContent(), $matches); + + if ($pregMatched) { + unset($arguments[$matches[1]]); + } + + $lastParamLine = max($lastParamLine, $annotation->getEnd()); + } + + if (0 === \count($arguments)) { + continue; + } + + $lines = $doc->getLines(); + $linesCount = \count($lines); + + Preg::match('/^(\s*).*$/', $lines[$linesCount - 1]->getContent(), $matches); + $indent = $matches[1]; + + $newLines = []; + + foreach ($arguments as $argument) { + $type = '' !== $argument['type'] ? $argument['type'] : 'mixed'; + + if (!str_starts_with($type, '?') && 'null' === strtolower($argument['default'])) { + $type = 'null|'.$type; + } + + $newLines[] = new Line(sprintf( + '%s* @param %s %s%s', + $indent, + $type, + $argument['name'], + $this->whitespacesConfig->getLineEnding() + )); + } + + array_splice( + $lines, + $lastParamLine > 0 ? $lastParamLine + 1 : $linesCount - 1, + 0, + $newLines + ); + + $tokens[$mainIndex] = new Token([T_DOC_COMMENT, implode('', $lines)]); + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('only_untyped', 'Whether to add missing `@param` annotations for untyped parameters only.')) + ->setDefault(true) + ->setAllowedTypes(['bool']) + ->getOption(), + ]); + } + + /** + * @return array{default: string, name: string, type: string} + */ + private function prepareArgumentInformation(Tokens $tokens, int $start, int $end): array + { + $info = [ + 'default' => '', + 'name' => '', + 'type' => '', + ]; + + $sawName = false; + + for ($index = $start; $index <= $end; ++$index) { + $token = $tokens[$index]; + + if ( + $token->isComment() + || $token->isWhitespace() + || $token->isGivenKind([ + CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE, + CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED, + CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC, + ]) + || (\defined('T_READONLY') && $token->isGivenKind(T_READONLY)) + ) { + continue; + } + + if ($token->isGivenKind(T_VARIABLE)) { + $sawName = true; + $info['name'] = $token->getContent(); + + continue; + } + + if ($token->equals('=')) { + continue; + } + + if ($sawName) { + $info['default'] .= $token->getContent(); + } elseif (!$token->equals('&')) { + if ($token->isGivenKind(T_ELLIPSIS)) { + if ('' === $info['type']) { + $info['type'] = 'array'; + } else { + $info['type'] .= '[]'; + } + } else { + $info['type'] .= $token->getContent(); + } + } + } + + return $info; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocAlignFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocAlignFixer.php new file mode 100644 index 00000000..454f84c8 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocAlignFixer.php @@ -0,0 +1,498 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\DocBlock\TypeExpression; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; + +/** + * @author Fabien Potencier + * @author Jordi Boggiano + * @author Sebastiaan Stok + * @author Graham Campbell + * @author Dariusz Rumiński + * @author Jakub Kwaśniewski + */ +final class PhpdocAlignFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface +{ + /** + * @internal + */ + public const ALIGN_LEFT = 'left'; + + /** + * @internal + */ + public const ALIGN_VERTICAL = 'vertical'; + + private const DEFAULT_TAGS = [ + 'method', + 'param', + 'property', + 'return', + 'throws', + 'type', + 'var', + ]; + + private const TAGS_WITH_NAME = [ + 'param', + 'property', + 'property-read', + 'property-write', + 'phpstan-param', + 'phpstan-property', + 'phpstan-property-read', + 'phpstan-property-write', + 'phpstan-assert', + 'phpstan-assert-if-true', + 'phpstan-assert-if-false', + 'psalm-param', + 'psalm-param-out', + 'psalm-property', + 'psalm-property-read', + 'psalm-property-write', + 'psalm-assert', + 'psalm-assert-if-true', + 'psalm-assert-if-false', + ]; + + private const TAGS_WITH_METHOD_SIGNATURE = [ + 'method', + 'phpstan-method', + 'psalm-method', + ]; + + private const DEFAULT_SPACING = 1; + + private const DEFAULT_SPACING_KEY = '_default'; + + private string $regex; + + private string $regexCommentLine; + + private string $align; + + /** + * same spacing for all or specific for different tags. + * + * @var int|int[] + */ + private $spacing = 1; + + public function configure(array $configuration): void + { + parent::configure($configuration); + + $tagsWithNameToAlign = array_intersect($this->configuration['tags'], self::TAGS_WITH_NAME); + $tagsWithMethodSignatureToAlign = array_intersect($this->configuration['tags'], self::TAGS_WITH_METHOD_SIGNATURE); + $tagsWithoutNameToAlign = array_diff($this->configuration['tags'], $tagsWithNameToAlign, $tagsWithMethodSignatureToAlign); + + $indentRegex = '^(?P(?:\ {2}|\t)*)\ ?'; + + $types = []; + + // e.g. @param <$var> + if ([] !== $tagsWithNameToAlign) { + $types[] = '(?P'.implode('|', $tagsWithNameToAlign).')\s+(?P(?:'.TypeExpression::REGEX_TYPES.')?)\s*(?P(?:&|\.{3})?\$\S+)'; + } + + // e.g. @return + if ([] !== $tagsWithoutNameToAlign) { + $types[] = '(?P'.implode('|', $tagsWithoutNameToAlign).')\s+(?P(?:'.TypeExpression::REGEX_TYPES.')?)'; + } + + // e.g. @method + if ([] !== $tagsWithMethodSignatureToAlign) { + $types[] = '(?P'.implode('|', $tagsWithMethodSignatureToAlign).')(\s+(?Pstatic))?(\s+(?P(?:'.TypeExpression::REGEX_TYPES.')?))\s+(?P.+\))'; + } + + // optional + $desc = '(?:\s+(?P\V*))'; + + $this->regex = '/'.$indentRegex.'\*\h*@(?J)(?:'.implode('|', $types).')'.$desc.'\h*\r?$/'; + $this->regexCommentLine = '/'.$indentRegex.'\*(?!\h?+@)(?:\s+(?P\V+))(?align = $this->configuration['align']; + $this->spacing = $this->configuration['spacing']; + } + + public function getDefinition(): FixerDefinitionInterface + { + $code = <<<'EOF' + self::ALIGN_VERTICAL]), + new CodeSample($code, ['align' => self::ALIGN_LEFT]), + new CodeSample($code, ['align' => self::ALIGN_LEFT, 'spacing' => 2]), + new CodeSample($code, ['align' => self::ALIGN_LEFT, 'spacing' => ['param' => 2]]), + ], + ); + } + + /** + * {@inheritdoc} + * + * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, GeneralPhpdocAnnotationRemoveFixer, GeneralPhpdocTagRenameFixer, NoBlankLinesAfterPhpdocFixer, NoEmptyPhpdocFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocAnnotationWithoutDotFixer, PhpdocArrayTypeFixer, PhpdocIndentFixer, PhpdocInlineTagNormalizerFixer, PhpdocLineSpanFixer, PhpdocListTypeFixer, PhpdocNoAccessFixer, PhpdocNoAliasTagFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocNoUselessInheritdocFixer, PhpdocOrderByValueFixer, PhpdocOrderFixer, PhpdocParamOrderFixer, PhpdocReadonlyClassCommentToKeywordFixer, PhpdocReturnSelfReferenceFixer, PhpdocScalarFixer, PhpdocSeparationFixer, PhpdocSingleLineVarSpacingFixer, PhpdocSummaryFixer, PhpdocTagCasingFixer, PhpdocTagTypeFixer, PhpdocToCommentFixer, PhpdocToParamTypeFixer, PhpdocToPropertyTypeFixer, PhpdocToReturnTypeFixer, PhpdocTrimConsecutiveBlankLineSeparationFixer, PhpdocTrimFixer, PhpdocTypesFixer, PhpdocTypesOrderFixer, PhpdocVarAnnotationCorrectOrderFixer, PhpdocVarWithoutNameFixer. + */ + public function getPriority(): int + { + /* + * Should be run after all other docblock fixers. This because they + * modify other annotations to change their type and or separation + * which totally change the behavior of this fixer. It's important that + * annotations are of the correct type, and are grouped correctly + * before running this fixer. + */ + return -42; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + $content = $token->getContent(); + $docBlock = new DocBlock($content); + $this->fixDocBlock($docBlock); + $newContent = $docBlock->getContent(); + if ($newContent !== $content) { + $tokens[$index] = new Token([T_DOC_COMMENT, $newContent]); + } + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + $allowPositiveIntegers = static function ($value) { + $spacings = \is_array($value) ? $value : [$value]; + foreach ($spacings as $val) { + if (\is_int($val) && $val <= 0) { + throw new InvalidOptionsException('The option "spacing" is invalid. All spacings must be greater than zero.'); + } + } + + return true; + }; + + $tags = new FixerOptionBuilder( + 'tags', + 'The tags that should be aligned. Allowed values are tags with name (`\''.implode('\', \'', self::TAGS_WITH_NAME).'\'`), tags with method signature (`\''.implode('\', \'', self::TAGS_WITH_METHOD_SIGNATURE).'\'`) and any custom tag with description (e.g. `@tag `).' + ); + $tags + ->setAllowedTypes(['array']) + ->setDefault(self::DEFAULT_TAGS) + ; + + $align = new FixerOptionBuilder('align', 'How comments should be aligned.'); + $align + ->setAllowedTypes(['string']) + ->setAllowedValues([self::ALIGN_LEFT, self::ALIGN_VERTICAL]) + ->setDefault(self::ALIGN_VERTICAL) + ; + + $spacing = new FixerOptionBuilder( + 'spacing', + 'Spacing between tag, hint, comment, signature, etc. You can set same spacing for all tags using a positive integer or different spacings for different tags using an associative array of positive integers `[\'tagA\' => spacingForA, \'tagB\' => spacingForB]`. If you want to define default spacing to more than 1 space use `_default` key in config array, e.g.: `[\'tagA\' => spacingForA, \'tagB\' => spacingForB, \'_default\' => spacingForAllOthers]`.' + ); + $spacing->setAllowedTypes(['int', 'int[]']) + ->setAllowedValues([$allowPositiveIntegers]) + ->setDefault(self::DEFAULT_SPACING) + ; + + return new FixerConfigurationResolver([$tags->getOption(), $align->getOption(), $spacing->getOption()]); + } + + private function fixDocBlock(DocBlock $docBlock): void + { + $lineEnding = $this->whitespacesConfig->getLineEnding(); + + for ($i = 0, $l = \count($docBlock->getLines()); $i < $l; ++$i) { + $matches = $this->getMatches($docBlock->getLine($i)->getContent()); + + if (null === $matches) { + continue; + } + + $current = $i; + $items = [$matches]; + + while (true) { + if (null === $docBlock->getLine(++$i)) { + break 2; + } + + $matches = $this->getMatches($docBlock->getLine($i)->getContent(), true); + if (null === $matches) { + break; + } + + $items[] = $matches; + } + + // compute the max length of the tag, hint and variables + $hasStatic = false; + $tagMax = 0; + $hintMax = 0; + $varMax = 0; + + foreach ($items as $item) { + if (null === $item['tag']) { + continue; + } + + $hasStatic |= '' !== $item['static']; + $tagMax = max($tagMax, \strlen($item['tag'])); + $hintMax = max($hintMax, \strlen($item['hint'])); + $varMax = max($varMax, \strlen($item['var'])); + } + + $itemOpeningLine = null; + + $currTag = null; + $spacingForTag = $this->spacingForTag($currTag); + + // update + foreach ($items as $j => $item) { + if (null === $item['tag']) { + if ('@' === $item['desc'][0]) { + $line = $item['indent'].' * '.$item['desc']; + $docBlock->getLine($current + $j)->setContent($line.$lineEnding); + + continue; + } + + $extraIndent = 2 * $spacingForTag; + + if (\in_array($itemOpeningLine['tag'], self::TAGS_WITH_NAME, true) || \in_array($itemOpeningLine['tag'], self::TAGS_WITH_METHOD_SIGNATURE, true)) { + $extraIndent += $varMax + $spacingForTag; + } + + if ($hasStatic) { + $extraIndent += 7; // \strlen('static '); + } + + $line = + $item['indent'] + .' * ' + .('' !== $itemOpeningLine['hint'] ? ' ' : '') + .$this->getIndent( + $tagMax + $hintMax + $extraIndent, + $this->getLeftAlignedDescriptionIndent($items, $j) + ) + .$item['desc']; + + $docBlock->getLine($current + $j)->setContent($line.$lineEnding); + + continue; + } + + $currTag = $item['tag']; + + $spacingForTag = $this->spacingForTag($currTag); + + $itemOpeningLine = $item; + + $line = + $item['indent'] + .' * @' + .$item['tag']; + + if ($hasStatic) { + $line .= + $this->getIndent( + $tagMax - \strlen($item['tag']) + $spacingForTag, + '' !== $item['static'] ? $spacingForTag : 0 + ) + .('' !== $item['static'] ? $item['static'] : $this->getIndent(6 /* \strlen('static') */, 0)); + $hintVerticalAlignIndent = $spacingForTag; + } else { + $hintVerticalAlignIndent = $tagMax - \strlen($item['tag']) + $spacingForTag; + } + + $line .= + $this->getIndent( + $hintVerticalAlignIndent, + '' !== $item['hint'] ? $spacingForTag : 0 + ) + .$item['hint']; + + if ('' !== $item['var']) { + $line .= + $this->getIndent((0 !== $hintMax ? $hintMax : -1) - \strlen($item['hint']) + $spacingForTag, $spacingForTag) + .$item['var'] + .( + '' !== $item['desc'] + ? $this->getIndent($varMax - \strlen($item['var']) + $spacingForTag, $spacingForTag).$item['desc'] + : '' + ); + } elseif ('' !== $item['desc']) { + $line .= $this->getIndent($hintMax - \strlen($item['hint']) + $spacingForTag, $spacingForTag).$item['desc']; + } + + $docBlock->getLine($current + $j)->setContent($line.$lineEnding); + } + } + } + + private function spacingForTag(?string $tag): int + { + return (\is_int($this->spacing)) + ? $this->spacing + : ($this->spacing[$tag] ?? $this->spacing[self::DEFAULT_SPACING_KEY] ?? self::DEFAULT_SPACING); + } + + /** + * @TODO Introduce proper DTO instead of an array + * + * @return null|array{indent: null|string, tag: null|string, hint: string, var: null|string, static: string, desc?: null|string} + */ + private function getMatches(string $line, bool $matchCommentOnly = false): ?array + { + if (Preg::match($this->regex, $line, $matches)) { + if (isset($matches['tag2']) && '' !== $matches['tag2']) { + $matches['tag'] = $matches['tag2']; + $matches['hint'] = $matches['hint2']; + $matches['var'] = ''; + } + + if (isset($matches['tag3']) && '' !== $matches['tag3']) { + $matches['tag'] = $matches['tag3']; + $matches['hint'] = $matches['hint3']; + $matches['var'] = $matches['signature']; + + // Since static can be both a return type declaration & a keyword that defines static methods + // we assume it's a type declaration when only one value is present + if ('' === $matches['hint'] && '' !== $matches['static']) { + $matches['hint'] = $matches['static']; + $matches['static'] = ''; + } + } + + if (isset($matches['hint'])) { + $matches['hint'] = trim($matches['hint']); + } + + if (!isset($matches['static'])) { + $matches['static'] = ''; + } + + return $matches; + } + + if ($matchCommentOnly && Preg::match($this->regexCommentLine, $line, $matches)) { + $matches['tag'] = null; + $matches['var'] = ''; + $matches['hint'] = ''; + $matches['static'] = ''; + + return $matches; + } + + return null; + } + + private function getIndent(int $verticalAlignIndent, int $leftAlignIndent = 1): string + { + $indent = self::ALIGN_VERTICAL === $this->align ? $verticalAlignIndent : $leftAlignIndent; + + return str_repeat(' ', $indent); + } + + /** + * @param non-empty-list $items + */ + private function getLeftAlignedDescriptionIndent(array $items, int $index): int + { + if (self::ALIGN_LEFT !== $this->align) { + return 0; + } + + // Find last tagged line: + $item = null; + for (; $index >= 0; --$index) { + $item = $items[$index]; + if (null !== $item['tag']) { + break; + } + } + + // No last tag found — no indent: + if (null === $item) { + return 0; + } + + $spacingForTag = $this->spacingForTag($item['tag']); + + // Indent according to existing values: + return + $this->getSentenceIndent($item['static'], $spacingForTag) + + $this->getSentenceIndent($item['tag'], $spacingForTag) + + $this->getSentenceIndent($item['hint'], $spacingForTag) + + $this->getSentenceIndent($item['var'], $spacingForTag); + } + + /** + * Get indent for sentence. + */ + private function getSentenceIndent(?string $sentence, int $spacingForTag = 1): int + { + if (null === $sentence) { + return 0; + } + + $length = \strlen($sentence); + + return 0 === $length ? 0 : $length + $spacingForTag; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocAnnotationWithoutDotFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocAnnotationWithoutDotFixer.php new file mode 100644 index 00000000..8cacdc38 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocAnnotationWithoutDotFixer.php @@ -0,0 +1,123 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class PhpdocAnnotationWithoutDotFixer extends AbstractFixer +{ + /** + * @var string[] + */ + private array $tags = ['throws', 'return', 'param', 'internal', 'deprecated', 'var', 'type']; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'PHPDoc annotation descriptions should not be a sentence.', + [new CodeSample('isTokenKindFound(T_DOC_COMMENT); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + $doc = new DocBlock($token->getContent()); + $annotations = $doc->getAnnotations(); + + if (0 === \count($annotations)) { + continue; + } + + foreach ($annotations as $annotation) { + if ( + !$annotation->getTag()->valid() || !\in_array($annotation->getTag()->getName(), $this->tags, true) + ) { + continue; + } + + $lineAfterAnnotation = $doc->getLine($annotation->getEnd() + 1); + if (null !== $lineAfterAnnotation) { + $lineAfterAnnotationTrimmed = ltrim($lineAfterAnnotation->getContent()); + if ('' === $lineAfterAnnotationTrimmed || !str_starts_with($lineAfterAnnotationTrimmed, '*')) { + // malformed PHPDoc, missing asterisk ! + continue; + } + } + + $content = $annotation->getContent(); + + if ( + !Preg::match('/[.。]\h*$/u', $content) + || Preg::match('/[.。](?!\h*$)/u', $content, $matches) + ) { + continue; + } + + $endLine = $doc->getLine($annotation->getEnd()); + $endLine->setContent(Preg::replace('/(?getContent())); + + $startLine = $doc->getLine($annotation->getStart()); + $optionalTypeRegEx = $annotation->supportTypes() + ? sprintf('(?:%s\s+(?:\$\w+\s+)?)?', preg_quote(implode('|', $annotation->getTypes()), '/')) + : ''; + $content = Preg::replaceCallback( + '/^(\s*\*\s*@\w+\s+'.$optionalTypeRegEx.')(\p{Lu}?(?=\p{Ll}|\p{Zs}))(.*)$/', + static fn (array $matches): string => $matches[1].mb_strtolower($matches[2]).$matches[3], + $startLine->getContent(), + 1 + ); + $startLine->setContent($content); + } + + $tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocArrayTypeFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocArrayTypeFixer.php new file mode 100644 index 00000000..f3f64c11 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocArrayTypeFixer.php @@ -0,0 +1,92 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractPhpdocTypesFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Tokens; + +final class PhpdocArrayTypeFixer extends AbstractPhpdocTypesFixer +{ + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + public function isRisky(): bool + { + return true; + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'PHPDoc `array` type must be used instead of `T[]`.', + [ + new CodeSample(<<<'PHP' + ', $level); + }, + $type, + ); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocIndentFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocIndentFixer.php new file mode 100644 index 00000000..9690977a --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocIndentFixer.php @@ -0,0 +1,135 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Utils; + +/** + * @author Ceeram + * @author Graham Campbell + */ +final class PhpdocIndentFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Docblocks should have the same indentation as the documented subject.', + [new CodeSample('isTokenKindFound(T_DOC_COMMENT); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + $nextIndex = $tokens->getNextMeaningfulToken($index); + + // skip if there is no next token or if next token is block end `}` + if (null === $nextIndex || $tokens[$nextIndex]->equals('}')) { + continue; + } + + $prevIndex = $index - 1; + $prevToken = $tokens[$prevIndex]; + + // ignore inline docblocks + if ( + $prevToken->isGivenKind(T_OPEN_TAG) + || ($prevToken->isWhitespace(" \t") && !$tokens[$index - 2]->isGivenKind(T_OPEN_TAG)) + || $prevToken->equalsAny([';', ',', '{', '(']) + ) { + continue; + } + + if ($tokens[$nextIndex - 1]->isWhitespace()) { + $indent = Utils::calculateTrailingWhitespaceIndent($tokens[$nextIndex - 1]); + } else { + $indent = ''; + } + + $newPrevContent = $this->fixWhitespaceBeforeDocblock($prevToken->getContent(), $indent); + + if ('' !== $newPrevContent) { + if ($prevToken->isArray()) { + $tokens[$prevIndex] = new Token([$prevToken->getId(), $newPrevContent]); + } else { + $tokens[$prevIndex] = new Token($newPrevContent); + } + } else { + $tokens->clearAt($prevIndex); + } + + $tokens[$index] = new Token([T_DOC_COMMENT, $this->fixDocBlock($token->getContent(), $indent)]); + } + } + + /** + * Fix indentation of Docblock. + * + * @param string $content Docblock contents + * @param string $indent Indentation to apply + * + * @return string Dockblock contents including correct indentation + */ + private function fixDocBlock(string $content, string $indent): string + { + return ltrim(Preg::replace('/^\h*\*/m', $indent.' *', $content)); + } + + /** + * @param string $content Whitespace before Docblock + * @param string $indent Indentation of the documented subject + * + * @return string Whitespace including correct indentation for Dockblock after this whitespace + */ + private function fixWhitespaceBeforeDocblock(string $content, string $indent): string + { + return rtrim($content, " \t").$indent; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocInlineTagNormalizerFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocInlineTagNormalizerFixer.php new file mode 100644 index 00000000..668dd843 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocInlineTagNormalizerFixer.php @@ -0,0 +1,107 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class PhpdocInlineTagNormalizerFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Fixes PHPDoc inline tags.', + [ + new CodeSample( + " ['TUTORIAL']] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before PhpdocAlignFixer. + * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. + */ + public function getPriority(): int + { + return 0; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + if (0 === \count($this->configuration['tags'])) { + return; + } + + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + // Move `@` inside tag, for example @{tag} -> {@tag}, replace multiple curly brackets, + // remove spaces between '{' and '@', remove white space between end + // of text and closing bracket and between the tag and inline comment. + $content = Preg::replaceCallback( + sprintf( + '#(?:@{+|{+\h*@)\h*(%s)\b([^}]*)(?:}+)#i', + implode('|', array_map(static fn (string $tag): string => preg_quote($tag, '/'), $this->configuration['tags'])) + ), + static function (array $matches): string { + $doc = trim($matches[2]); + + if ('' === $doc) { + return '{@'.$matches[1].'}'; + } + + return '{@'.$matches[1].' '.$doc.'}'; + }, + $token->getContent() + ); + + $tokens[$index] = new Token([T_DOC_COMMENT, $content]); + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('tags', 'The list of tags to normalize.')) + ->setAllowedTypes(['array']) + ->setDefault(['example', 'id', 'internal', 'inheritdoc', 'inheritdocs', 'link', 'source', 'toc', 'tutorial']) + ->getOption(), + ]); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocLineSpanFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocLineSpanFixer.php new file mode 100644 index 00000000..a667dd20 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocLineSpanFixer.php @@ -0,0 +1,156 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\WhitespacesAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Gert de Pagter + */ +final class PhpdocLineSpanFixer extends AbstractFixer implements WhitespacesAwareFixerInterface, ConfigurableFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Changes doc blocks from single to multi line, or reversed. Works for class constants, properties and methods only.', + [ + new CodeSample(" 'single'] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before NoSuperfluousPhpdocTagsFixer, PhpdocAlignFixer. + * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, GeneralPhpdocAnnotationRemoveFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. + */ + public function getPriority(): int + { + return 7; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('const', 'Whether const blocks should be single or multi line.')) + ->setAllowedValues(['single', 'multi', null]) + ->setDefault('multi') + ->getOption(), + (new FixerOptionBuilder('property', 'Whether property doc blocks should be single or multi line.')) + ->setAllowedValues(['single', 'multi', null]) + ->setDefault('multi') + ->getOption(), + (new FixerOptionBuilder('method', 'Whether method doc blocks should be single or multi line.')) + ->setAllowedValues(['single', 'multi', null]) + ->setDefault('multi') + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $analyzer = new TokensAnalyzer($tokens); + + foreach ($analyzer->getClassyElements() as $index => $element) { + if (!$this->hasDocBlock($tokens, $index)) { + continue; + } + + $type = $element['type']; + + if (!isset($this->configuration[$type])) { + continue; + } + + $docIndex = $this->getDocBlockIndex($tokens, $index); + $doc = new DocBlock($tokens[$docIndex]->getContent()); + + if ('multi' === $this->configuration[$type]) { + $doc->makeMultiLine(WhitespacesAnalyzer::detectIndent($tokens, $docIndex), $this->whitespacesConfig->getLineEnding()); + } elseif ('single' === $this->configuration[$type]) { + $doc->makeSingleLine(); + } + + $tokens->offsetSet($docIndex, new Token([T_DOC_COMMENT, $doc->getContent()])); + } + } + + private function hasDocBlock(Tokens $tokens, int $index): bool + { + $docBlockIndex = $this->getDocBlockIndex($tokens, $index); + + return $tokens[$docBlockIndex]->isGivenKind(T_DOC_COMMENT); + } + + private function getDocBlockIndex(Tokens $tokens, int $index): int + { + $propertyPartKinds = [ + T_PUBLIC, + T_PROTECTED, + T_PRIVATE, + T_FINAL, + T_ABSTRACT, + T_COMMENT, + T_VAR, + T_STATIC, + T_STRING, + T_NS_SEPARATOR, + CT::T_ARRAY_TYPEHINT, + CT::T_NULLABLE_TYPE, + ]; + + if (\defined('T_ATTRIBUTE')) { // @TODO: drop condition when PHP 8.0+ is required + $propertyPartKinds[] = T_ATTRIBUTE; + } + + if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required + $propertyPartKinds[] = T_READONLY; + } + + do { + $index = $tokens->getPrevNonWhitespace($index); + + if ($tokens[$index]->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) { + $index = $tokens->getPrevTokenOfKind($index, [[T_ATTRIBUTE]]); + } + } while ($tokens[$index]->isGivenKind($propertyPartKinds)); + + return $index; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocListTypeFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocListTypeFixer.php new file mode 100644 index 00000000..cfd56af1 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocListTypeFixer.php @@ -0,0 +1,70 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractPhpdocTypesFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Tokens; + +final class PhpdocListTypeFixer extends AbstractPhpdocTypesFixer +{ + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + public function isRisky(): bool + { + return true; + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'PHPDoc `list` type must be used instead of `array` without a key.', + [ + new CodeSample(<<<'PHP' + $x + * @param array> $y + */ + + PHP), + ], + null, + 'Risky when `array` key should be present, but is missing.' + ); + } + + /** + * {@inheritdoc} + * + * Must run before PhpdocAlignFixer, PhpdocTypesOrderFixer. + * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocArrayTypeFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. + */ + public function getPriority(): int + { + return 1; + } + + protected function normalize(string $type): string + { + return Preg::replace('/array(?=<(?:[^,<]|<[^>]+>)+(>|{|\\())/i', 'list', $type); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoAccessFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoAccessFixer.php new file mode 100644 index 00000000..12300026 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoAccessFixer.php @@ -0,0 +1,67 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; + +/** + * @author Graham Campbell + * @author Dariusz Rumiński + */ +final class PhpdocNoAccessFixer extends AbstractProxyFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + '`@access` annotations should be omitted from PHPDoc.', + [ + new CodeSample( + 'configure(['annotations' => ['access']]); + + return [$fixer]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoAliasTagFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoAliasTagFixer.php new file mode 100644 index 00000000..5bd71385 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoAliasTagFixer.php @@ -0,0 +1,126 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\ConfigurationException\InvalidConfigurationException; +use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; + +/** + * Case-sensitive tag replace fixer (does not process inline tags like {@inheritdoc}). + * + * @author Graham Campbell + * @author Dariusz Rumiński + */ +final class PhpdocNoAliasTagFixer extends AbstractProxyFixer implements ConfigurableFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'No alias PHPDoc tags should be used.', + [ + new CodeSample( + ' ['link' => 'website']] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before PhpdocAddMissingParamAnnotationFixer, PhpdocAlignFixer, PhpdocSingleLineVarSpacingFixer. + * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. + */ + public function getPriority(): int + { + return parent::getPriority(); + } + + public function configure(array $configuration): void + { + parent::configure($configuration); + + /** @var GeneralPhpdocTagRenameFixer $generalPhpdocTagRenameFixer */ + $generalPhpdocTagRenameFixer = $this->proxyFixers['general_phpdoc_tag_rename']; + + try { + $generalPhpdocTagRenameFixer->configure([ + 'fix_annotation' => true, + 'fix_inline' => false, + 'replacements' => $this->configuration['replacements'], + 'case_sensitive' => true, + ]); + } catch (InvalidConfigurationException $exception) { + throw new InvalidFixerConfigurationException( + $this->getName(), + Preg::replace('/^\[.+?\] /', '', $exception->getMessage()), + $exception + ); + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('replacements', 'Mapping between replaced annotations with new ones.')) + ->setAllowedTypes(['array']) + ->setDefault([ + 'property-read' => 'property', + 'property-write' => 'property', + 'type' => 'var', + 'link' => 'see', + ]) + ->getOption(), + ]); + } + + protected function createProxyFixers(): array + { + return [new GeneralPhpdocTagRenameFixer()]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoEmptyReturnFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoEmptyReturnFixer.php new file mode 100644 index 00000000..3b22bad9 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoEmptyReturnFixer.php @@ -0,0 +1,117 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\DocBlock\Annotation; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Graham Campbell + */ +final class PhpdocNoEmptyReturnFixer extends AbstractFixer +{ + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + '`@return void` and `@return null` annotations should be omitted from PHPDoc.', + [ + new CodeSample( + ' $token) { + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + $doc = new DocBlock($token->getContent()); + $annotations = $doc->getAnnotationsOfType('return'); + + if (0 === \count($annotations)) { + continue; + } + + foreach ($annotations as $annotation) { + $this->fixAnnotation($annotation); + } + + $newContent = $doc->getContent(); + + if ($newContent === $token->getContent()) { + continue; + } + + if ('' === $newContent) { + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + + continue; + } + + $tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]); + } + } + + /** + * Remove `return void` or `return null` annotations. + */ + private function fixAnnotation(Annotation $annotation): void + { + $types = $annotation->getNormalizedTypes(); + + if (1 === \count($types) && ('null' === $types[0] || 'void' === $types[0])) { + $annotation->remove(); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoPackageFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoPackageFixer.php new file mode 100644 index 00000000..e254ad0c --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoPackageFixer.php @@ -0,0 +1,67 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; + +/** + * @author Graham Campbell + * @author Dariusz Rumiński + */ +final class PhpdocNoPackageFixer extends AbstractProxyFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + '`@package` and `@subpackage` annotations should be omitted from PHPDoc.', + [ + new CodeSample( + 'configure(['annotations' => ['package', 'subpackage']]); + + return [$fixer]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoUselessInheritdocFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoUselessInheritdocFixer.php new file mode 100644 index 00000000..4174f2d9 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoUselessInheritdocFixer.php @@ -0,0 +1,151 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Remove inheritdoc tags from classy that does not inherit. + */ +final class PhpdocNoUselessInheritdocFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Classy that does not inherit must not have `@inheritdoc` tags.', + [ + new CodeSample("isTokenKindFound(T_DOC_COMMENT) && $tokens->isAnyTokenKindsFound([T_CLASS, T_INTERFACE]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + // min. offset 4 as minimal candidate is @: isGivenKind([T_CLASS, T_INTERFACE])) { + $index = $this->fixClassy($tokens, $index); + } + } + } + + private function fixClassy(Tokens $tokens, int $index): int + { + // figure out where the classy starts + $classOpenIndex = $tokens->getNextTokenOfKind($index, ['{']); + + // figure out where the classy ends + $classEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classOpenIndex); + + // is classy extending or implementing some interface + $extendingOrImplementing = $this->isExtendingOrImplementing($tokens, $index, $classOpenIndex); + + if (!$extendingOrImplementing) { + // PHPDoc of classy should not have inherit tag even when using traits as Traits cannot provide this information + $this->fixClassyOutside($tokens, $index); + } + + // figure out if the classy uses a trait + if (!$extendingOrImplementing && $this->isUsingTrait($tokens, $index, $classOpenIndex, $classEndIndex)) { + $extendingOrImplementing = true; + } + + $this->fixClassyInside($tokens, $classOpenIndex, $classEndIndex, !$extendingOrImplementing); + + return $classEndIndex; + } + + private function fixClassyInside(Tokens $tokens, int $classOpenIndex, int $classEndIndex, bool $fixThisLevel): void + { + for ($i = $classOpenIndex; $i < $classEndIndex; ++$i) { + if ($tokens[$i]->isGivenKind(T_CLASS)) { + $i = $this->fixClassy($tokens, $i); + } elseif ($fixThisLevel && $tokens[$i]->isGivenKind(T_DOC_COMMENT)) { + $this->fixToken($tokens, $i); + } + } + } + + private function fixClassyOutside(Tokens $tokens, int $classIndex): void + { + $previousIndex = $tokens->getPrevNonWhitespace($classIndex); + if ($tokens[$previousIndex]->isGivenKind(T_DOC_COMMENT)) { + $this->fixToken($tokens, $previousIndex); + } + } + + private function fixToken(Tokens $tokens, int $tokenIndex): void + { + $count = 0; + $content = Preg::replaceCallback( + '#(\h*(?:@{*|{*\h*@)\h*inheritdoc\h*)([^}]*)((?:}*)\h*)#i', + static fn (array $matches): string => ' '.$matches[2], + $tokens[$tokenIndex]->getContent(), + -1, + $count + ); + + if ($count) { + $tokens[$tokenIndex] = new Token([T_DOC_COMMENT, $content]); + } + } + + private function isExtendingOrImplementing(Tokens $tokens, int $classIndex, int $classOpenIndex): bool + { + for ($index = $classIndex; $index < $classOpenIndex; ++$index) { + if ($tokens[$index]->isGivenKind([T_EXTENDS, T_IMPLEMENTS])) { + return true; + } + } + + return false; + } + + private function isUsingTrait(Tokens $tokens, int $classIndex, int $classOpenIndex, int $classCloseIndex): bool + { + if ($tokens[$classIndex]->isGivenKind(T_INTERFACE)) { + // cannot use Trait inside an interface + return false; + } + + $useIndex = $tokens->getNextTokenOfKind($classOpenIndex, [[CT::T_USE_TRAIT]]); + + return null !== $useIndex && $useIndex < $classCloseIndex; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocOrderByValueFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocOrderByValueFixer.php new file mode 100644 index 00000000..7b8568c5 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocOrderByValueFixer.php @@ -0,0 +1,214 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use Symfony\Component\OptionsResolver\Options; + +/** + * @author Filippo Tessarotto + * @author Andreas Möller + */ +final class PhpdocOrderByValueFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Order PHPDoc tags by value.', + [ + new CodeSample( + ' [ + 'author', + ], + ] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before PhpdocAlignFixer. + * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpUnitFqcnAnnotationFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. + */ + public function getPriority(): int + { + return -10; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAllTokenKindsFound([T_CLASS, T_DOC_COMMENT]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + if ([] === $this->configuration['annotations']) { + return; + } + + for ($index = $tokens->count() - 1; $index > 0; --$index) { + foreach ($this->configuration['annotations'] as $type => $typeLowerCase) { + $findPattern = sprintf( + '/@%s\s.+@%s\s/s', + $type, + $type + ); + + if ( + !$tokens[$index]->isGivenKind(T_DOC_COMMENT) + || !Preg::match($findPattern, $tokens[$index]->getContent()) + ) { + continue; + } + + $docBlock = new DocBlock($tokens[$index]->getContent()); + + $annotations = $docBlock->getAnnotationsOfType($type); + $annotationMap = []; + + if (\in_array($type, ['property', 'property-read', 'property-write'], true)) { + $replacePattern = sprintf( + '/(?s)\*\s*@%s\s+(?P.+\s+)?\$(?P\S+).*/', + $type + ); + + $replacement = '\2'; + } elseif ('method' === $type) { + $replacePattern = '/(?s)\*\s*@method\s+(?P.+\s+)?(?P.+)\(.*/'; + $replacement = '\2'; + } else { + $replacePattern = sprintf( + '/\*\s*@%s\s+(?P.+)/', + $typeLowerCase + ); + + $replacement = '\1'; + } + + foreach ($annotations as $annotation) { + $rawContent = $annotation->getContent(); + + $comparableContent = Preg::replace( + $replacePattern, + $replacement, + strtolower(trim($rawContent)) + ); + + $annotationMap[$comparableContent] = $rawContent; + } + + $orderedAnnotationMap = $annotationMap; + + ksort($orderedAnnotationMap, SORT_STRING); + + if ($orderedAnnotationMap === $annotationMap) { + continue; + } + + $lines = $docBlock->getLines(); + + foreach (array_reverse($annotations) as $annotation) { + array_splice( + $lines, + $annotation->getStart(), + $annotation->getEnd() - $annotation->getStart() + 1, + array_pop($orderedAnnotationMap) + ); + } + + $tokens[$index] = new Token([T_DOC_COMMENT, implode('', $lines)]); + } + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + $allowedValues = [ + 'author', + 'covers', + 'coversNothing', + 'dataProvider', + 'depends', + 'group', + 'internal', + 'method', + 'mixin', + 'property', + 'property-read', + 'property-write', + 'requires', + 'throws', + 'uses', + ]; + + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('annotations', 'List of annotations to order, e.g. `["covers"]`.')) + ->setAllowedTypes([ + 'array', + ]) + ->setAllowedValues([ + new AllowedValueSubset($allowedValues), + ]) + ->setNormalizer(static function (Options $options, array $value): array { + $normalized = []; + + foreach ($value as $annotation) { + // since we will be using "strtolower" on the input annotations when building the sorting + // map we must match the type in lower case as well + $normalized[$annotation] = strtolower($annotation); + } + + return $normalized; + }) + ->setDefault([ + 'covers', + ]) + ->getOption(), + ]); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocOrderFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocOrderFixer.php new file mode 100644 index 00000000..7764e9af --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocOrderFixer.php @@ -0,0 +1,208 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; + +/** + * @author Graham Campbell + * @author Jakub Kwaśniewski + */ +final class PhpdocOrderFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** + * @const string[] + * + * @TODO: 4.0 - change default to ['param', 'return', 'throws'] + */ + private const ORDER_DEFAULT = ['param', 'throws', 'return']; + + public function getDefinition(): FixerDefinitionInterface + { + $code = <<<'EOF' + self::ORDER_DEFAULT]), + new CodeSample($code, ['order' => ['param', 'return', 'throws']]), + new CodeSample($code, ['order' => ['param', 'custom', 'throws', 'return']]), + ], + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + /** + * {@inheritdoc} + * + * Must run before PhpdocAlignFixer, PhpdocSeparationFixer, PhpdocTrimFixer. + * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocIndentFixer, PhpdocNoEmptyReturnFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. + */ + public function getPriority(): int + { + return -2; + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('order', 'Sequence in which annotations in PHPDoc should be ordered.')) + ->setAllowedTypes(['string[]']) + ->setAllowedValues([static function (array $order): bool { + if (\count($order) < 2) { + throw new InvalidOptionsException('The option "order" value is invalid. Minimum two tags are required.'); + } + + return true; + }]) + ->setDefault(self::ORDER_DEFAULT) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + // assuming annotations are already grouped by tags + $content = $token->getContent(); + + // sort annotations + $successors = $this->configuration['order']; + while (\count($successors) >= 3) { + $predecessor = array_shift($successors); + $content = $this->moveAnnotationsBefore($predecessor, $successors, $content); + } + + // we're parsing the content last time to make sure the internal + // state of the docblock is correct after the modifications + $predecessors = $this->configuration['order']; + $last = array_pop($predecessors); + $content = $this->moveAnnotationsAfter($last, $predecessors, $content); + + // persist the content at the end + $tokens[$index] = new Token([T_DOC_COMMENT, $content]); + } + } + + /** + * Move all given annotations in before given set of annotations. + * + * @param string $move Tag of annotations that should be moved + * @param string[] $before Tags of annotations that should moved annotations be placed before + */ + private function moveAnnotationsBefore(string $move, array $before, string $content): string + { + $doc = new DocBlock($content); + $toBeMoved = $doc->getAnnotationsOfType($move); + + // nothing to do if there are no annotations to be moved + if (0 === \count($toBeMoved)) { + return $content; + } + + $others = $doc->getAnnotationsOfType($before); + + if (0 === \count($others)) { + return $content; + } + + // get the index of the final line of the final toBoMoved annotation + $end = end($toBeMoved)->getEnd(); + + $line = $doc->getLine($end); + + // move stuff about if required + foreach ($others as $other) { + if ($other->getStart() < $end) { + // we're doing this to maintain the original line indices + $line->setContent($line->getContent().$other->getContent()); + $other->remove(); + } + } + + return $doc->getContent(); + } + + /** + * Move all given annotations after given set of annotations. + * + * @param string $move Tag of annotations that should be moved + * @param string[] $after Tags of annotations that should moved annotations be placed after + */ + private function moveAnnotationsAfter(string $move, array $after, string $content): string + { + $doc = new DocBlock($content); + $toBeMoved = $doc->getAnnotationsOfType($move); + + // nothing to do if there are no annotations to be moved + if (0 === \count($toBeMoved)) { + return $content; + } + + $others = $doc->getAnnotationsOfType($after); + + // nothing to do if there are no other annotations + if (0 === \count($others)) { + return $content; + } + + // get the index of the first line of the first toBeMoved annotation + $start = $toBeMoved[0]->getStart(); + $line = $doc->getLine($start); + + // move stuff about if required + foreach (array_reverse($others) as $other) { + if ($other->getEnd() > $start) { + // we're doing this to maintain the original line indices + $line->setContent($other->getContent().$line->getContent()); + $other->remove(); + } + } + + return $doc->getContent(); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocParamOrderFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocParamOrderFixer.php new file mode 100644 index 00000000..afe587e6 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocParamOrderFixer.php @@ -0,0 +1,272 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\DocBlock\Annotation; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\DocBlock\TypeExpression; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Jonathan Gruber + */ +final class PhpdocParamOrderFixer extends AbstractFixer +{ + private const PARAM_TAG = 'param'; + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + /** + * {@inheritdoc} + * + * Must run before PhpdocAlignFixer. + * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. + */ + public function getPriority(): int + { + return parent::getPriority(); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Orders all `@param` annotations in DocBlocks according to method signature.', + [ + new CodeSample( + ' $token) { + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + // Check for function / closure token + $nextFunctionToken = $tokens->getNextTokenOfKind($index, [[T_FUNCTION], [T_FN]]); + if (null === $nextFunctionToken) { + return; + } + + // Find start index of param block (opening parenthesis) + $paramBlockStart = $tokens->getNextTokenOfKind($index, ['(']); + if (null === $paramBlockStart) { + return; + } + + $doc = new DocBlock($tokens[$index]->getContent()); + $paramAnnotations = $doc->getAnnotationsOfType(self::PARAM_TAG); + + if ([] === $paramAnnotations) { + continue; + } + + $paramNames = $this->getFunctionParamNames($tokens, $paramBlockStart); + $doc = $this->rewriteDocBlock($doc, $paramNames, $paramAnnotations); + + $tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]); + } + } + + /** + * @return Token[] + */ + private function getFunctionParamNames(Tokens $tokens, int $paramBlockStart): array + { + $paramBlockEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $paramBlockStart); + + $paramNames = []; + for ( + $i = $tokens->getNextTokenOfKind($paramBlockStart, [[T_VARIABLE]]); + null !== $i && $i < $paramBlockEnd; + $i = $tokens->getNextTokenOfKind($i, [[T_VARIABLE]]) + ) { + $paramNames[] = $tokens[$i]; + } + + return $paramNames; + } + + /** + * Overwrite the param annotations in order. + * + * @param Token[] $paramNames + * @param Annotation[] $paramAnnotations + */ + private function rewriteDocBlock(DocBlock $doc, array $paramNames, array $paramAnnotations): DocBlock + { + $orderedAnnotations = $this->sortParamAnnotations($paramNames, $paramAnnotations); + $otherAnnotations = $this->getOtherAnnotationsBetweenParams($doc, $paramAnnotations); + + // Append annotations found between param ones + if ([] !== $otherAnnotations) { + array_push($orderedAnnotations, ...$otherAnnotations); + } + + // Overwrite all annotations between first and last @param tag in order + $paramsStart = reset($paramAnnotations)->getStart(); + $paramsEnd = end($paramAnnotations)->getEnd(); + + foreach ($doc->getAnnotations() as $annotation) { + if ($annotation->getStart() < $paramsStart || $annotation->getEnd() > $paramsEnd) { + continue; + } + + $annotation->remove(); + $doc + ->getLine($annotation->getStart()) + ->setContent(current($orderedAnnotations)) + ; + + next($orderedAnnotations); + } + + return $doc; + } + + /** + * Sort the param annotations according to the function parameters. + * + * @param Token[] $funcParamNames + * @param Annotation[] $paramAnnotations + * + * @return list + */ + private function sortParamAnnotations(array $funcParamNames, array $paramAnnotations): array + { + $validParams = []; + foreach ($funcParamNames as $paramName) { + $indices = $this->findParamAnnotationByIdentifier($paramAnnotations, $paramName->getContent()); + + // Found an exactly matching @param annotation + if (\is_array($indices)) { + foreach ($indices as $index) { + $validParams[$index] = $paramAnnotations[$index]->getContent(); + } + } + } + + // Detect superfluous annotations + /** @var Annotation[] $invalidParams */ + $invalidParams = array_diff_key($paramAnnotations, $validParams); + $invalidParams = array_values($invalidParams); + + // Append invalid parameters to the (ordered) valid ones + $orderedParams = array_values($validParams); + foreach ($invalidParams as $params) { + $orderedParams[] = $params->getContent(); + } + + return $orderedParams; + } + + /** + * Fetch all annotations except the param ones. + * + * @param Annotation[] $paramAnnotations + * + * @return list + */ + private function getOtherAnnotationsBetweenParams(DocBlock $doc, array $paramAnnotations): array + { + if (0 === \count($paramAnnotations)) { + return []; + } + + $paramsStart = reset($paramAnnotations)->getStart(); + $paramsEnd = end($paramAnnotations)->getEnd(); + + $otherAnnotations = []; + foreach ($doc->getAnnotations() as $annotation) { + if ($annotation->getStart() < $paramsStart || $annotation->getEnd() > $paramsEnd) { + continue; + } + + if (self::PARAM_TAG !== $annotation->getTag()->getName()) { + $otherAnnotations[] = $annotation->getContent(); + } + } + + return $otherAnnotations; + } + + /** + * Return the indices of the lines of a specific parameter annotation. + * + * @param Annotation[] $paramAnnotations + * + * @return ?list + */ + private function findParamAnnotationByIdentifier(array $paramAnnotations, string $identifier): ?array + { + $blockLevel = 0; + $blockMatch = false; + $blockIndices = []; + + $paramRegex = '/\*\h*@param\h*(?:|'.TypeExpression::REGEX_TYPES.'\h*)&?(?=\$\b)'.preg_quote($identifier).'\b/'; + + foreach ($paramAnnotations as $i => $param) { + $blockStart = Preg::match('/\s*{\s*/', $param->getContent()); + $blockEndMatches = Preg::matchAll('/}[\*\s\n]*/', $param->getContent()); + + if (0 === $blockLevel && Preg::match($paramRegex, $param->getContent())) { + if ($blockStart) { + $blockMatch = true; // Start of a nested block + } else { + return [$i]; // Top level match + } + } + + if ($blockStart) { + ++$blockLevel; + } + + if (0 !== $blockEndMatches) { + $blockLevel -= $blockEndMatches; + } + + if ($blockMatch) { + $blockIndices[] = $i; + if (0 === $blockLevel) { + return $blockIndices; + } + } + } + + return null; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocReturnSelfReferenceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocReturnSelfReferenceFixer.php new file mode 100644 index 00000000..f672e1ee --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocReturnSelfReferenceFixer.php @@ -0,0 +1,220 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; +use PhpCsFixer\Utils; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; +use Symfony\Component\OptionsResolver\Options; + +final class PhpdocReturnSelfReferenceFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** + * @var string[] + */ + private static array $toTypes = [ + '$this', + 'static', + 'self', + ]; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'The type of `@return` annotations of methods returning a reference to itself must the configured one.', + [ + new CodeSample( + ' ['this' => 'self']] + ), + ] + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return \count($tokens) > 10 && $tokens->isAllTokenKindsFound([T_DOC_COMMENT, T_FUNCTION]) && $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds()); + } + + /** + * {@inheritdoc} + * + * Must run before NoSuperfluousPhpdocTagsFixer, PhpdocAlignFixer. + * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. + */ + public function getPriority(): int + { + return 10; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + + foreach ($tokensAnalyzer->getClassyElements() as $index => $element) { + if ('method' === $element['type']) { + $this->fixMethod($tokens, $index); + } + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + $default = [ + 'this' => '$this', + '@this' => '$this', + '$self' => 'self', + '@self' => 'self', + '$static' => 'static', + '@static' => 'static', + ]; + + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('replacements', 'Mapping between replaced return types with new ones.')) + ->setAllowedTypes(['array']) + ->setNormalizer(static function (Options $options, array $value) use ($default): array { + $normalizedValue = []; + + foreach ($value as $from => $to) { + if (\is_string($from)) { + $from = strtolower($from); + } + + if (!isset($default[$from])) { + throw new InvalidOptionsException(sprintf( + 'Unknown key "%s", expected any of %s.', + \gettype($from).'#'.$from, + Utils::naturalLanguageJoin(array_keys($default)) + )); + } + + if (!\in_array($to, self::$toTypes, true)) { + throw new InvalidOptionsException(sprintf( + 'Unknown value "%s", expected any of %s.', + \is_object($to) ? \get_class($to) : \gettype($to).(\is_resource($to) ? '' : '#'.$to), + Utils::naturalLanguageJoin(self::$toTypes) + )); + } + + $normalizedValue[$from] = $to; + } + + return $normalizedValue; + }) + ->setDefault($default) + ->getOption(), + ]); + } + + private function fixMethod(Tokens $tokens, int $index): void + { + static $methodModifiers = [T_STATIC, T_FINAL, T_ABSTRACT, T_PRIVATE, T_PROTECTED, T_PUBLIC]; + + // find PHPDoc of method (if any) + while (true) { + $tokenIndex = $tokens->getPrevMeaningfulToken($index); + if (!$tokens[$tokenIndex]->isGivenKind($methodModifiers)) { + break; + } + + $index = $tokenIndex; + } + + $docIndex = $tokens->getPrevNonWhitespace($index); + if (!$tokens[$docIndex]->isGivenKind(T_DOC_COMMENT)) { + return; + } + + // find @return + $docBlock = new DocBlock($tokens[$docIndex]->getContent()); + $returnsBlock = $docBlock->getAnnotationsOfType('return'); + + if (0 === \count($returnsBlock)) { + return; // no return annotation found + } + + $returnsBlock = $returnsBlock[0]; + $types = $returnsBlock->getTypes(); + + if (0 === \count($types)) { + return; // no return type(s) found + } + + $newTypes = []; + + foreach ($types as $type) { + $newTypes[] = $this->configuration['replacements'][strtolower($type)] ?? $type; + } + + if ($types === $newTypes) { + return; + } + + $returnsBlock->setTypes($newTypes); + $tokens[$docIndex] = new Token([T_DOC_COMMENT, $docBlock->getContent()]); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocScalarFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocScalarFixer.php new file mode 100644 index 00000000..70a6b323 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocScalarFixer.php @@ -0,0 +1,128 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractPhpdocTypesFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; + +/** + * @author Graham Campbell + */ +final class PhpdocScalarFixer extends AbstractPhpdocTypesFixer implements ConfigurableFixerInterface +{ + /** + * The types to fix. + * + * @var array + */ + private static array $types = [ + 'boolean' => 'bool', + 'callback' => 'callable', + 'double' => 'float', + 'integer' => 'int', + 'real' => 'float', + 'str' => 'string', + ]; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Scalar types should always be written in the same form. `int` not `integer`, `bool` not `boolean`, `float` not `real` or `double`.', + [ + new CodeSample(' ['boolean']] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before GeneralPhpdocAnnotationRemoveFixer, GeneralPhpdocTagRenameFixer, NoBlankLinesAfterPhpdocFixer, NoEmptyPhpdocFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocAlignFixer, PhpdocArrayTypeFixer, PhpdocInlineTagNormalizerFixer, PhpdocLineSpanFixer, PhpdocListTypeFixer, PhpdocNoAccessFixer, PhpdocNoAliasTagFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocNoUselessInheritdocFixer, PhpdocOrderByValueFixer, PhpdocOrderFixer, PhpdocParamOrderFixer, PhpdocReadonlyClassCommentToKeywordFixer, PhpdocReturnSelfReferenceFixer, PhpdocSeparationFixer, PhpdocSingleLineVarSpacingFixer, PhpdocSummaryFixer, PhpdocTagCasingFixer, PhpdocTagTypeFixer, PhpdocToParamTypeFixer, PhpdocToPropertyTypeFixer, PhpdocToReturnTypeFixer, PhpdocTrimConsecutiveBlankLineSeparationFixer, PhpdocTrimFixer, PhpdocTypesOrderFixer, PhpdocVarAnnotationCorrectOrderFixer, PhpdocVarWithoutNameFixer. + * Must run after PhpdocTypesFixer. + */ + public function getPriority(): int + { + /* + * Should be run before all other docblock fixers apart from the + * phpdoc_to_comment and phpdoc_indent fixer to make sure all fixers + * apply correct indentation to new code they add. This should run + * before alignment of params is done since this fixer might change + * the type and thereby un-aligning the params. We also must run after + * the phpdoc_types_fixer because it can convert types to things that + * we can fix. + */ + return 15; + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + $types = array_keys(self::$types); + + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('types', 'A list of types to fix.')) + ->setAllowedValues([new AllowedValueSubset($types)]) + ->setDefault($types) + ->getOption(), + ]); + } + + protected function normalize(string $type): string + { + $suffix = ''; + while (str_ends_with($type, '[]')) { + $type = substr($type, 0, -2); + $suffix .= '[]'; + } + + if (\in_array($type, $this->configuration['types'], true)) { + $type = self::$types[$type]; + } + + return $type.$suffix; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocSeparationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocSeparationFixer.php new file mode 100644 index 00000000..838c519f --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocSeparationFixer.php @@ -0,0 +1,320 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\DocBlock\Annotation; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; + +/** + * @author Graham Campbell + * @author Jakub Kwaśniewski + */ +final class PhpdocSeparationFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** + * @internal + * + * @var string[][] + */ + public const OPTION_GROUPS_DEFAULT = [ + ['author', 'copyright', 'license'], + ['category', 'package', 'subpackage'], + ['property', 'property-read', 'property-write'], + ['deprecated', 'link', 'see', 'since'], + ]; + + /** + * @var string[][] + */ + private array $groups; + + public function getDefinition(): FixerDefinitionInterface + { + $code = <<<'EOF' + [ + ['deprecated', 'link', 'see', 'since'], + ['author', 'copyright', 'license'], + ['category', 'package', 'subpackage'], + ['property', 'property-read', 'property-write'], + ['param', 'return'], + ]]), + new CodeSample($code, ['groups' => [ + ['author', 'throws', 'custom'], + ['return', 'param'], + ]]), + new CodeSample( + <<<'EOF' + [['ORM\*'], ['Assert\*']]], + ), + new CodeSample($code, ['skip_unlisted_annotations' => true]), + ], + ); + } + + public function configure(array $configuration): void + { + parent::configure($configuration); + + $this->groups = $this->configuration['groups']; + } + + /** + * {@inheritdoc} + * + * Must run before PhpdocAlignFixer. + * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, GeneralPhpdocAnnotationRemoveFixer, PhpUnitInternalClassFixer, PhpUnitSizeClassFixer, PhpUnitTestClassRequiresCoversFixer, PhpdocIndentFixer, PhpdocNoAccessFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocOrderFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. + */ + public function getPriority(): int + { + return -3; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + $doc = new DocBlock($token->getContent()); + $this->fixDescription($doc); + $this->fixAnnotations($doc); + + $tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]); + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + $allowTagToBelongToOnlyOneGroup = static function (array $groups): bool { + $tags = []; + foreach ($groups as $groupIndex => $group) { + foreach ($group as $member) { + if (isset($tags[$member])) { + if ($groupIndex === $tags[$member]) { + throw new InvalidOptionsException( + 'The option "groups" value is invalid. '. + 'The "'.$member.'" tag is specified more than once.' + ); + } + + throw new InvalidOptionsException( + 'The option "groups" value is invalid. '. + 'The "'.$member.'" tag belongs to more than one group.' + ); + } + $tags[$member] = $groupIndex; + } + } + + return true; + }; + + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('groups', 'Sets of annotation types to be grouped together. Use `*` to match any tag character.')) + ->setAllowedTypes(['string[][]']) + ->setDefault(self::OPTION_GROUPS_DEFAULT) + ->setAllowedValues([$allowTagToBelongToOnlyOneGroup]) + ->getOption(), + (new FixerOptionBuilder('skip_unlisted_annotations', 'Whether to skip annotations that are not listed in any group.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) // @TODO 4.0: set to `true`. + ->getOption(), + ]); + } + + /** + * Make sure the description is separated from the annotations. + */ + private function fixDescription(DocBlock $doc): void + { + foreach ($doc->getLines() as $index => $line) { + if ($line->containsATag()) { + break; + } + + if ($line->containsUsefulContent()) { + $next = $doc->getLine($index + 1); + + if (null !== $next && $next->containsATag()) { + $line->addBlank(); + + break; + } + } + } + } + + /** + * Make sure the annotations are correctly separated. + */ + private function fixAnnotations(DocBlock $doc): void + { + foreach ($doc->getAnnotations() as $index => $annotation) { + $next = $doc->getAnnotation($index + 1); + + if (null === $next) { + break; + } + + $shouldBeTogether = $this->shouldBeTogether($annotation, $next, $this->groups); + + if (true === $shouldBeTogether) { + $this->ensureAreTogether($doc, $annotation, $next); + } elseif (false === $shouldBeTogether || false === $this->configuration['skip_unlisted_annotations']) { + $this->ensureAreSeparate($doc, $annotation, $next); + } + } + } + + /** + * Force the given annotations to immediately follow each other. + */ + private function ensureAreTogether(DocBlock $doc, Annotation $first, Annotation $second): void + { + $pos = $first->getEnd(); + $final = $second->getStart(); + + for (++$pos; $pos < $final; ++$pos) { + $doc->getLine($pos)->remove(); + } + } + + /** + * Force the given annotations to have one empty line between each other. + */ + private function ensureAreSeparate(DocBlock $doc, Annotation $first, Annotation $second): void + { + $pos = $first->getEnd(); + $final = $second->getStart() - 1; + + // check if we need to add a line, or need to remove one or more lines + if ($pos === $final) { + $doc->getLine($pos)->addBlank(); + + return; + } + + for (++$pos; $pos < $final; ++$pos) { + $doc->getLine($pos)->remove(); + } + } + + /** + * @param list> $groups + */ + private function shouldBeTogether(Annotation $first, Annotation $second, array $groups): ?bool + { + $firstName = $this->tagName($first); + $secondName = $this->tagName($second); + + // A tag could not be read. + if (null === $firstName || null === $secondName) { + return null; + } + + if ($firstName === $secondName) { + return true; + } + + foreach ($groups as $group) { + $firstTagIsInGroup = $this->isInGroup($firstName, $group); + $secondTagIsInGroup = $this->isInGroup($secondName, $group); + + if ($firstTagIsInGroup) { + return $secondTagIsInGroup; + } + + if ($secondTagIsInGroup) { + return false; + } + } + + return null; + } + + private function tagName(Annotation $annotation): ?string + { + Preg::match('/@([a-zA-Z0-9_\\\\-]+(?=\s|$|\())/', $annotation->getContent(), $matches); + + return $matches[1] ?? null; + } + + /** + * @param list $group + */ + private function isInGroup(string $tag, array $group): bool + { + foreach ($group as $tagInGroup) { + $tagInGroup = str_replace('*', '\*', $tagInGroup); + $tagInGroup = preg_quote($tagInGroup, '/'); + $tagInGroup = str_replace('\\\\\*', '.*?', $tagInGroup); + + if (Preg::match("/^{$tagInGroup}$/", $tag)) { + return true; + } + } + + return false; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocSingleLineVarSpacingFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocSingleLineVarSpacingFixer.php new file mode 100644 index 00000000..9c2bda1d --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocSingleLineVarSpacingFixer.php @@ -0,0 +1,89 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Fixer for part of rule defined in PSR5 ¶7.22. + */ +final class PhpdocSingleLineVarSpacingFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Single line `@var` PHPDoc should have proper spacing.', + [new CodeSample("isAnyTokenKindsFound([T_COMMENT, T_DOC_COMMENT]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + /** @var Token $token */ + foreach ($tokens as $index => $token) { + if (!$token->isComment()) { + continue; + } + + $content = $token->getContent(); + $fixedContent = $this->fixTokenContent($content); + + if ($content !== $fixedContent) { + $tokens[$index] = new Token([T_DOC_COMMENT, $fixedContent]); + } + } + } + + private function fixTokenContent(string $content): string + { + return Preg::replaceCallback( + '#^/\*\*\h*@var\h+(\S+)\h*(\$\S+)?\h*([^\n]*)\*/$#', + static function (array $matches) { + $content = '/** @var'; + + for ($i = 1, $m = \count($matches); $i < $m; ++$i) { + if ('' !== $matches[$i]) { + $content .= ' '.$matches[$i]; + } + } + + return rtrim($content).' */'; + }, + $content + ); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocSummaryFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocSummaryFixer.php new file mode 100644 index 00000000..65406b37 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocSummaryFixer.php @@ -0,0 +1,99 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\DocBlock\ShortDescription; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Graham Campbell + */ +final class PhpdocSummaryFixer extends AbstractFixer implements WhitespacesAwareFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'PHPDoc summary should end in either a full stop, exclamation mark, or question mark.', + [new CodeSample('isTokenKindFound(T_DOC_COMMENT); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + $doc = new DocBlock($token->getContent()); + $end = (new ShortDescription($doc))->getEnd(); + + if (null !== $end) { + $line = $doc->getLine($end); + $content = rtrim($line->getContent()); + + if ( + // final line of Description is NOT properly formatted + !$this->isCorrectlyFormatted($content) + // and first line of Description, if different than final line, does NOT indicate a list + && (1 === $end || ($doc->isMultiLine() && ':' !== substr(rtrim($doc->getLine(1)->getContent()), -1))) + ) { + $line->setContent($content.'.'.$this->whitespacesConfig->getLineEnding()); + $tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]); + } + } + } + } + + /** + * Is the last line of the short description correctly formatted? + */ + private function isCorrectlyFormatted(string $content): bool + { + if (false !== stripos($content, '{@inheritdoc}')) { + return true; + } + + return $content !== rtrim($content, '.:。!?¡¿!?'); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTagCasingFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTagCasingFixer.php new file mode 100644 index 00000000..cdeb5315 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTagCasingFixer.php @@ -0,0 +1,97 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\ConfigurationException\InvalidConfigurationException; +use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; + +final class PhpdocTagCasingFixer extends AbstractProxyFixer implements ConfigurableFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Fixes casing of PHPDoc tags.', + [ + new CodeSample(" ['foo'], + ]), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before PhpdocAlignFixer. + * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. + */ + public function getPriority(): int + { + return parent::getPriority(); + } + + public function configure(array $configuration): void + { + parent::configure($configuration); + + $replacements = []; + foreach ($this->configuration['tags'] as $tag) { + $replacements[$tag] = $tag; + } + + /** @var GeneralPhpdocTagRenameFixer $generalPhpdocTagRenameFixer */ + $generalPhpdocTagRenameFixer = $this->proxyFixers['general_phpdoc_tag_rename']; + + try { + $generalPhpdocTagRenameFixer->configure([ + 'fix_annotation' => true, + 'fix_inline' => true, + 'replacements' => $replacements, + 'case_sensitive' => false, + ]); + } catch (InvalidConfigurationException $exception) { + throw new InvalidFixerConfigurationException( + $this->getName(), + Preg::replace('/^\[.+?\] /', '', $exception->getMessage()), + $exception + ); + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('tags', 'List of tags to fix with their expected casing.')) + ->setAllowedTypes(['array']) + ->setDefault(['inheritDoc']) + ->getOption(), + ]); + } + + protected function createProxyFixers(): array + { + return [new GeneralPhpdocTagRenameFixer()]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTagTypeFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTagTypeFixer.php new file mode 100644 index 00000000..274db397 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTagTypeFixer.php @@ -0,0 +1,200 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; +use Symfony\Component\OptionsResolver\Options; + +final class PhpdocTagTypeFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + private const TAG_REGEX = '/^(?: + (? + (?:@(?.+?)(?:\s.+)?) + ) + | + {(? + (?:@(?.+?)(?:\s.+)?) + )} + )$/x'; + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Forces PHPDoc tags to be either regular annotations or inline.', + [ + new CodeSample( + " ['inheritdoc' => 'inline']] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before PhpdocAlignFixer. + * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. + */ + public function getPriority(): int + { + return 0; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + if (0 === \count($this->configuration['tags'])) { + return; + } + + $regularExpression = sprintf( + '/({?@(?:%s).*?(?:(?=\s\*\/)|(?=\n)}?))/i', + implode('|', array_map( + static fn (string $tag): string => preg_quote($tag, '/'), + array_keys($this->configuration['tags']) + )) + ); + + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + $parts = Preg::split( + $regularExpression, + $token->getContent(), + -1, + PREG_SPLIT_DELIM_CAPTURE + ); + + for ($i = 1, $max = \count($parts) - 1; $i < $max; $i += 2) { + if (!Preg::match(self::TAG_REGEX, $parts[$i], $matches)) { + continue; + } + + if ('' !== $matches['tag']) { + $tag = $matches['tag']; + $tagName = $matches['tag_name']; + } else { + $tag = $matches['inlined_tag']; + $tagName = $matches['inlined_tag_name']; + } + + $tagName = strtolower($tagName); + if (!isset($this->configuration['tags'][$tagName])) { + continue; + } + + if ('inline' === $this->configuration['tags'][$tagName]) { + $parts[$i] = '{'.$tag.'}'; + + continue; + } + + if (!$this->tagIsSurroundedByText($parts, $i)) { + $parts[$i] = $tag; + } + } + + $tokens[$index] = new Token([T_DOC_COMMENT, implode('', $parts)]); + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('tags', 'The list of tags to fix.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([static function (array $value): bool { + foreach ($value as $type) { + if (!\in_array($type, ['annotation', 'inline'], true)) { + throw new InvalidOptionsException("Unknown tag type \"{$type}\"."); + } + } + + return true; + }]) + ->setDefault([ + 'api' => 'annotation', + 'author' => 'annotation', + 'copyright' => 'annotation', + 'deprecated' => 'annotation', + 'example' => 'annotation', + 'global' => 'annotation', + 'inheritDoc' => 'annotation', + 'internal' => 'annotation', + 'license' => 'annotation', + 'method' => 'annotation', + 'package' => 'annotation', + 'param' => 'annotation', + 'property' => 'annotation', + 'return' => 'annotation', + 'see' => 'annotation', + 'since' => 'annotation', + 'throws' => 'annotation', + 'todo' => 'annotation', + 'uses' => 'annotation', + 'var' => 'annotation', + 'version' => 'annotation', + ]) + ->setNormalizer(static function (Options $options, array $value): array { + $normalized = []; + + foreach ($value as $tag => $type) { + $normalized[strtolower($tag)] = $type; + } + + return $normalized; + }) + ->getOption(), + ]); + } + + /** + * @param list $parts + */ + private function tagIsSurroundedByText(array $parts, int $index): bool + { + return + Preg::match('/(^|\R)\h*[^@\s]\N*/', $this->cleanComment($parts[$index - 1])) + || Preg::match('/^.*?\R\s*[^@\s]/', $this->cleanComment($parts[$index + 1])); + } + + private function cleanComment(string $comment): string + { + $comment = Preg::replace('/^\/\*\*|\*\/$/', '', $comment); + + return Preg::replace('/(\R)(\h*\*)?\h*/', '$1', $comment); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocToCommentFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocToCommentFixer.php new file mode 100644 index 00000000..4bd4848e --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocToCommentFixer.php @@ -0,0 +1,172 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\CommentsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Ceeram + * @author Dariusz Rumiński + */ +final class PhpdocToCommentFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** + * @var string[] + */ + private array $ignoredTags = []; + private bool $allowBeforeReturnStatement = false; + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + /** + * {@inheritdoc} + * + * Must run before GeneralPhpdocAnnotationRemoveFixer, GeneralPhpdocTagRenameFixer, NoBlankLinesAfterPhpdocFixer, NoEmptyCommentFixer, NoEmptyPhpdocFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocAlignFixer, PhpdocAnnotationWithoutDotFixer, PhpdocArrayTypeFixer, PhpdocIndentFixer, PhpdocInlineTagNormalizerFixer, PhpdocLineSpanFixer, PhpdocListTypeFixer, PhpdocNoAccessFixer, PhpdocNoAliasTagFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocNoUselessInheritdocFixer, PhpdocOrderByValueFixer, PhpdocOrderFixer, PhpdocParamOrderFixer, PhpdocReadonlyClassCommentToKeywordFixer, PhpdocReturnSelfReferenceFixer, PhpdocSeparationFixer, PhpdocSingleLineVarSpacingFixer, PhpdocSummaryFixer, PhpdocTagCasingFixer, PhpdocTagTypeFixer, PhpdocToParamTypeFixer, PhpdocToPropertyTypeFixer, PhpdocToReturnTypeFixer, PhpdocTrimConsecutiveBlankLineSeparationFixer, PhpdocTrimFixer, PhpdocTypesOrderFixer, PhpdocVarAnnotationCorrectOrderFixer, PhpdocVarWithoutNameFixer, SingleLineCommentSpacingFixer, SingleLineCommentStyleFixer. + * Must run after CommentToPhpdocFixer. + */ + public function getPriority(): int + { + /* + * Should be run before all other docblock fixers so that these fixers + * don't touch doc comments which are meant to be converted to regular + * comments. + */ + return 25; + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Docblocks should only be used on structural elements.', + [ + new CodeSample( + ' $sqlite) { + $sqlite->open($path); +} +' + ), + new CodeSample( + ' $sqlite) { + $sqlite->open($path); +} + +/** @todo This should be a PHPDoc as the tag is on "ignored_tags" list */ +foreach($connections as $key => $sqlite) { + $sqlite->open($path); +} +', + ['ignored_tags' => ['todo']] + ), + new CodeSample( + ' $sqlite) { + $sqlite->open($path); +} + +function returnClassName() { + /** @var class-string */ + return \StdClass::class; +} +', + ['allow_before_return_statement' => true] + ), + ] + ); + } + + public function configure(array $configuration = null): void + { + parent::configure($configuration); + + $this->ignoredTags = array_map( + static fn (string $tag): string => strtolower($tag), + $this->configuration['ignored_tags'] + ); + + $this->allowBeforeReturnStatement = true === $this->configuration['allow_before_return_statement']; + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('ignored_tags', 'List of ignored tags (matched case insensitively).')) + ->setAllowedTypes(['array']) + ->setDefault([]) + ->getOption(), + (new FixerOptionBuilder('allow_before_return_statement', 'Whether to allow PHPDoc before return statement.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) // @TODO 4.0: set to `true` + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $commentsAnalyzer = new CommentsAnalyzer(); + + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + if ($commentsAnalyzer->isHeaderComment($tokens, $index)) { + continue; + } + + if ($this->allowBeforeReturnStatement && $commentsAnalyzer->isBeforeReturn($tokens, $index)) { + continue; + } + + if ($commentsAnalyzer->isBeforeStructuralElement($tokens, $index)) { + continue; + } + + if (0 < Preg::matchAll('~\@([a-zA-Z0-9_\\\\-]+)\b~', $token->getContent(), $matches)) { + foreach ($matches[1] as $match) { + if (\in_array(strtolower($match), $this->ignoredTags, true)) { + continue 2; + } + } + } + + $tokens[$index] = new Token([T_COMMENT, '/*'.ltrim($token->getContent(), '/*')]); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTrimConsecutiveBlankLineSeparationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTrimConsecutiveBlankLineSeparationFixer.php new file mode 100644 index 00000000..33a4a037 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTrimConsecutiveBlankLineSeparationFixer.php @@ -0,0 +1,188 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\DocBlock\Line; +use PhpCsFixer\DocBlock\ShortDescription; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Nobu Funaki + * @author Dariusz Rumiński + */ +final class PhpdocTrimConsecutiveBlankLineSeparationFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Removes extra blank lines after summary and after description in PHPDoc.', + [ + new CodeSample( + 'isTokenKindFound(T_DOC_COMMENT); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + $doc = new DocBlock($token->getContent()); + $summaryEnd = (new ShortDescription($doc))->getEnd(); + + if (null !== $summaryEnd) { + $this->fixSummary($doc, $summaryEnd); + $this->fixDescription($doc, $summaryEnd); + } + + $this->fixAllTheRest($doc); + + $tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]); + } + } + + private function fixSummary(DocBlock $doc, int $summaryEnd): void + { + $nonBlankLineAfterSummary = $this->findNonBlankLine($doc, $summaryEnd); + + $this->removeExtraBlankLinesBetween($doc, $summaryEnd, $nonBlankLineAfterSummary); + } + + private function fixDescription(DocBlock $doc, int $summaryEnd): void + { + $annotationStart = $this->findFirstAnnotationOrEnd($doc); + + // assuming the end of the Description appears before the first Annotation + $descriptionEnd = $this->reverseFindLastUsefulContent($doc, $annotationStart); + + if (null === $descriptionEnd || $summaryEnd === $descriptionEnd) { + return; // no Description + } + + if ($annotationStart === \count($doc->getLines()) - 1) { + return; // no content after Description + } + + $this->removeExtraBlankLinesBetween($doc, $descriptionEnd, $annotationStart); + } + + private function fixAllTheRest(DocBlock $doc): void + { + $annotationStart = $this->findFirstAnnotationOrEnd($doc); + $lastLine = $this->reverseFindLastUsefulContent($doc, \count($doc->getLines()) - 1); + + if (null !== $lastLine && $annotationStart !== $lastLine) { + $this->removeExtraBlankLinesBetween($doc, $annotationStart, $lastLine); + } + } + + private function removeExtraBlankLinesBetween(DocBlock $doc, int $from, int $to): void + { + for ($index = $from + 1; $index < $to; ++$index) { + $line = $doc->getLine($index); + $next = $doc->getLine($index + 1); + $this->removeExtraBlankLine($line, $next); + } + } + + private function removeExtraBlankLine(Line $current, Line $next): void + { + if (!$current->isTheEnd() && !$current->containsUsefulContent() + && !$next->isTheEnd() && !$next->containsUsefulContent()) { + $current->remove(); + } + } + + private function findNonBlankLine(DocBlock $doc, int $after): ?int + { + foreach ($doc->getLines() as $index => $line) { + if ($index <= $after) { + continue; + } + + if ($line->containsATag() || $line->containsUsefulContent() || $line->isTheEnd()) { + return $index; + } + } + + return null; + } + + private function findFirstAnnotationOrEnd(DocBlock $doc): int + { + $index = null; + foreach ($doc->getLines() as $index => $line) { + if ($line->containsATag()) { + return $index; + } + } + + return $index; // no Annotation, return the last line + } + + private function reverseFindLastUsefulContent(DocBlock $doc, int $from): ?int + { + for ($index = $from - 1; $index >= 0; --$index) { + if ($doc->getLine($index)->containsUsefulContent()) { + return $index; + } + } + + return null; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTrimFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTrimFixer.php new file mode 100644 index 00000000..c472d0d9 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTrimFixer.php @@ -0,0 +1,115 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Graham Campbell + */ +final class PhpdocTrimFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'PHPDoc should start and end with content, excluding the very first and last line of the docblocks.', + [new CodeSample('isTokenKindFound(T_DOC_COMMENT); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + $content = $token->getContent(); + $content = $this->fixStart($content); + // we need re-parse the docblock after fixing the start before + // fixing the end in order for the lines to be correctly indexed + $content = $this->fixEnd($content); + $tokens[$index] = new Token([T_DOC_COMMENT, $content]); + } + } + + /** + * Make sure the first useful line starts immediately after the first line. + */ + private function fixStart(string $content): string + { + return Preg::replace( + '~ + (^/\*\*) # DocComment begin + (?: + \R\h*(?:\*\h*)? # lines without useful content + (?!\R\h*\*/) # not followed by a DocComment end + )+ + (\R\h*(?:\*\h*)?\S) # first line with useful content + ~x', + '$1$2', + $content + ); + } + + /** + * Make sure the last useful line is immediately before the final line. + */ + private function fixEnd(string $content): string + { + return Preg::replace( + '~ + (\R\h*(?:\*\h*)?\S.*?) # last line with useful content + (?: + (? + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractPhpdocTypesFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; + +/** + * @author Graham Campbell + * @author Dariusz Rumiński + */ +final class PhpdocTypesFixer extends AbstractPhpdocTypesFixer implements ConfigurableFixerInterface +{ + /** + * Available types, grouped. + * + * @var array + */ + private const POSSIBLE_TYPES = [ + 'simple' => [ + 'array', + 'bool', + 'callable', + 'float', + 'int', + 'iterable', + 'null', + 'object', + 'string', + ], + 'alias' => [ + 'boolean', + 'double', + 'integer', + ], + 'meta' => [ + '$this', + 'false', + 'mixed', + 'parent', + 'resource', + 'scalar', + 'self', + 'static', + 'true', + 'void', + ], + ]; + + /** @var array */ + private array $typesSetToFix; + + public function configure(array $configuration): void + { + parent::configure($configuration); + + $typesToFix = array_merge(...array_map(static fn (string $group): array => self::POSSIBLE_TYPES[$group], $this->configuration['groups'])); + + $this->typesSetToFix = array_combine($typesToFix, array_fill(0, \count($typesToFix), true)); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'The correct case must be used for standard PHP types in PHPDoc.', + [ + new CodeSample( + ' ['simple', 'alias']] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before GeneralPhpdocAnnotationRemoveFixer, GeneralPhpdocTagRenameFixer, NoBlankLinesAfterPhpdocFixer, NoEmptyPhpdocFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocAlignFixer, PhpdocArrayTypeFixer, PhpdocInlineTagNormalizerFixer, PhpdocLineSpanFixer, PhpdocListTypeFixer, PhpdocNoAccessFixer, PhpdocNoAliasTagFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocNoUselessInheritdocFixer, PhpdocOrderByValueFixer, PhpdocOrderFixer, PhpdocParamOrderFixer, PhpdocReadonlyClassCommentToKeywordFixer, PhpdocReturnSelfReferenceFixer, PhpdocScalarFixer, PhpdocSeparationFixer, PhpdocSingleLineVarSpacingFixer, PhpdocSummaryFixer, PhpdocTagCasingFixer, PhpdocTagTypeFixer, PhpdocToParamTypeFixer, PhpdocToPropertyTypeFixer, PhpdocToReturnTypeFixer, PhpdocTrimConsecutiveBlankLineSeparationFixer, PhpdocTrimFixer, PhpdocTypesOrderFixer, PhpdocVarAnnotationCorrectOrderFixer, PhpdocVarWithoutNameFixer. + * Must run after PhpdocIndentFixer. + */ + public function getPriority(): int + { + /* + * Should be run before all other docblock fixers apart from the + * phpdoc_to_comment and phpdoc_indent fixer to make sure all fixers + * apply correct indentation to new code they add. This should run + * before alignment of params is done since this fixer might change + * the type and thereby un-aligning the params. We also must run before + * the phpdoc_scalar_fixer so that it can make changes after us. + */ + return 16; + } + + protected function normalize(string $type): string + { + $typeLower = strtolower($type); + if (isset($this->typesSetToFix[$typeLower])) { + $type = $typeLower; + } + + // normalize shape/callable/generic identifiers too + // TODO parse them as inner types and this will be not needed then + return Preg::replaceCallback( + '/^(\??\s*)([^()[\]{}<>\'"]+)(?])/', + fn ($matches) => $matches[1].$this->normalize($matches[2]).$matches[3], + $type + ); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + $possibleGroups = array_keys(self::POSSIBLE_TYPES); + + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('groups', 'Type groups to fix.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([new AllowedValueSubset($possibleGroups)]) + ->setDefault($possibleGroups) + ->getOption(), + ]); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTypesOrderFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTypesOrderFixer.php new file mode 100644 index 00000000..0bf50195 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTypesOrderFixer.php @@ -0,0 +1,200 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\DocBlock\Annotation; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\DocBlock\TypeExpression; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class PhpdocTypesOrderFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Sorts PHPDoc types.', + [ + new CodeSample( + ' 'always_last'] + ), + new CodeSample( + ' 'alpha'] + ), + new CodeSample( + ' 'alpha', + 'null_adjustment' => 'always_last', + ] + ), + new CodeSample( + ' 'alpha', + 'null_adjustment' => 'none', + ] + ), + new CodeSample( + ' true] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before PhpdocAlignFixer. + * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocArrayTypeFixer, PhpdocIndentFixer, PhpdocListTypeFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. + */ + public function getPriority(): int + { + return 0; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('sort_algorithm', 'The sorting algorithm to apply.')) + ->setAllowedValues(['alpha', 'none']) + ->setDefault('alpha') + ->getOption(), + (new FixerOptionBuilder('null_adjustment', 'Forces the position of `null` (overrides `sort_algorithm`).')) + ->setAllowedValues(['always_first', 'always_last', 'none']) + ->setDefault('always_first') + ->getOption(), + (new FixerOptionBuilder('case_sensitive', 'Whether the sorting should be case sensitive.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + $doc = new DocBlock($token->getContent()); + $annotations = $doc->getAnnotationsOfType(Annotation::getTagsWithTypes()); + + if (0 === \count($annotations)) { + continue; + } + + foreach ($annotations as $annotation) { + // fix main types + if (null !== $annotation->getTypeExpression()) { + $annotation->setTypes( + $this->sortTypes( + $annotation->getTypeExpression() + ) + ); + } + + // fix @method parameters types + $line = $doc->getLine($annotation->getStart()); + $line->setContent(Preg::replaceCallback('/\*\h*@method\h+'.TypeExpression::REGEX_TYPES.'\h+\K(?&callable)/', function (array $matches) { + $typeExpression = new TypeExpression($matches[0], null, []); + + return implode('|', $this->sortTypes($typeExpression)); + }, $line->getContent())); + } + + $tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]); + } + } + + /** + * @return list + */ + private function sortTypes(TypeExpression $typeExpression): array + { + $normalizeType = static fn (string $type): string => Preg::replace('/^\(*\??\\\?/', '', $type); + + $typeExpression->sortTypes( + function (TypeExpression $a, TypeExpression $b) use ($normalizeType): int { + $a = $normalizeType($a->toString()); + $b = $normalizeType($b->toString()); + $lowerCaseA = strtolower($a); + $lowerCaseB = strtolower($b); + + if ('none' !== $this->configuration['null_adjustment']) { + if ('null' === $lowerCaseA && 'null' !== $lowerCaseB) { + return 'always_last' === $this->configuration['null_adjustment'] ? 1 : -1; + } + if ('null' !== $lowerCaseA && 'null' === $lowerCaseB) { + return 'always_last' === $this->configuration['null_adjustment'] ? -1 : 1; + } + } + + if ('alpha' === $this->configuration['sort_algorithm']) { + return true === $this->configuration['case_sensitive'] ? $a <=> $b : strcasecmp($a, $b); + } + + return 0; + } + ); + + return $typeExpression->getTypes(); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocVarAnnotationCorrectOrderFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocVarAnnotationCorrectOrderFixer.php new file mode 100644 index 00000000..400f8dd7 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocVarAnnotationCorrectOrderFixer.php @@ -0,0 +1,81 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Kuba Werłos + */ +final class PhpdocVarAnnotationCorrectOrderFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + '`@var` and `@type` annotations must have type and name in the correct order.', + [new CodeSample('isTokenKindFound(T_DOC_COMMENT); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + if (false === stripos($token->getContent(), '@var') && false === stripos($token->getContent(), '@type')) { + continue; + } + + $newContent = Preg::replace( + '/(@(?:type|var)\s*)(\$\S+)(\h+)([^\$](?:[^<\s]|<[^>]*>)*)(\s|\*)/i', + '$1$4$3$2$5', + $token->getContent() + ); + + if ($newContent === $token->getContent()) { + continue; + } + + $tokens[$index] = new Token([$token->getId(), $newContent]); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocVarWithoutNameFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocVarWithoutNameFixer.php new file mode 100644 index 00000000..f42b2d9f --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocVarWithoutNameFixer.php @@ -0,0 +1,152 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\DocBlock\Line; +use PhpCsFixer\DocBlock\TypeExpression; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Graham Campbell + * @author Dave van der Brugge + */ +final class PhpdocVarWithoutNameFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + '`@var` and `@type` annotations of classy properties should not contain the name.', + [new CodeSample('isTokenKindFound(T_DOC_COMMENT) && $tokens->isAnyTokenKindsFound([T_CLASS, T_TRAIT]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + $nextIndex = $tokens->getNextMeaningfulToken($index); + + if (null === $nextIndex) { + continue; + } + + // For people writing "static public $foo" instead of "public static $foo" + if ($tokens[$nextIndex]->isGivenKind(T_STATIC)) { + $nextIndex = $tokens->getNextMeaningfulToken($nextIndex); + } + + // We want only doc blocks that are for properties and thus have specified access modifiers next + $propertyModifierKinds = [T_PRIVATE, T_PROTECTED, T_PUBLIC, T_VAR]; + + if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required + $propertyModifierKinds[] = T_READONLY; + } + + if (!$tokens[$nextIndex]->isGivenKind($propertyModifierKinds)) { + continue; + } + + $doc = new DocBlock($token->getContent()); + + $firstLevelLines = $this->getFirstLevelLines($doc); + $annotations = $doc->getAnnotationsOfType(['type', 'var']); + + foreach ($annotations as $annotation) { + if (isset($firstLevelLines[$annotation->getStart()])) { + $this->fixLine($firstLevelLines[$annotation->getStart()]); + } + } + + $tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]); + } + } + + private function fixLine(Line $line): void + { + Preg::matchAll('/ \$'.TypeExpression::REGEX_IDENTIFIER.'(?getContent(), $matches); + + if (isset($matches[0])) { + foreach ($matches[0] as $match) { + $line->setContent(str_replace($match, '', $line->getContent())); + } + } + } + + /** + * @return Line[] + */ + private function getFirstLevelLines(DocBlock $docBlock): array + { + $nested = 0; + $lines = $docBlock->getLines(); + + foreach ($lines as $index => $line) { + $content = $line->getContent(); + + if (Preg::match('/\s*\*\s*}$/', $content)) { + --$nested; + } + + if ($nested > 0) { + unset($lines[$index]); + } + + if (Preg::match('/\s\{$/', $content)) { + ++$nested; + } + } + + return $lines; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/NoUselessReturnFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/NoUselessReturnFixer.php new file mode 100644 index 00000000..aba3072c --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/NoUselessReturnFixer.php @@ -0,0 +1,103 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ReturnNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Tokens; + +final class NoUselessReturnFixer extends AbstractFixer +{ + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAllTokenKindsFound([T_FUNCTION, T_RETURN]); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'There should not be an empty `return` statement at the end of a function.', + [ + new CodeSample( + ' $token) { + if (!$token->isGivenKind(T_FUNCTION)) { + continue; + } + + $index = $tokens->getNextTokenOfKind($index, [';', '{']); + if ($tokens[$index]->equals('{')) { + $this->fixFunction($tokens, $index, $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index)); + } + } + } + + /** + * @param int $start Token index of the opening brace token of the function + * @param int $end Token index of the closing brace token of the function + */ + private function fixFunction(Tokens $tokens, int $start, int $end): void + { + for ($index = $end; $index > $start; --$index) { + if (!$tokens[$index]->isGivenKind(T_RETURN)) { + continue; + } + + $nextAt = $tokens->getNextMeaningfulToken($index); + if (!$tokens[$nextAt]->equals(';')) { + continue; + } + + if ($tokens->getNextMeaningfulToken($nextAt) !== $end) { + continue; + } + + $previous = $tokens->getPrevMeaningfulToken($index); + if ($tokens[$previous]->equalsAny([[T_ELSE], ')'])) { + continue; + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + $tokens->clearTokenAndMergeSurroundingWhitespace($nextAt); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/ReturnAssignmentFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/ReturnAssignmentFixer.php new file mode 100644 index 00000000..1e883382 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/ReturnAssignmentFixer.php @@ -0,0 +1,527 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ReturnNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +final class ReturnAssignmentFixer extends AbstractFixer +{ + private TokensAnalyzer $tokensAnalyzer; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Local, dynamic and directly referenced variables should not be assigned and directly returned by a function or method.', + [new CodeSample("isAllTokenKindsFound([T_FUNCTION, T_RETURN, T_VARIABLE]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $tokenCount = \count($tokens); + $this->tokensAnalyzer = new TokensAnalyzer($tokens); + + for ($index = 1; $index < $tokenCount; ++$index) { + if (!$tokens[$index]->isGivenKind(T_FUNCTION)) { + continue; + } + + $next = $tokens->getNextMeaningfulToken($index); + if ($tokens[$next]->isGivenKind(CT::T_RETURN_REF)) { + continue; + } + + $functionOpenIndex = $tokens->getNextTokenOfKind($index, ['{', ';']); + if ($tokens[$functionOpenIndex]->equals(';')) { // abstract function + $index = $functionOpenIndex - 1; + + continue; + } + + $functionCloseIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $functionOpenIndex); + $totalTokensAdded = 0; + + do { + $tokensAdded = $this->fixFunction( + $tokens, + $index, + $functionOpenIndex, + $functionCloseIndex + ); + + $functionCloseIndex += $tokensAdded; + $totalTokensAdded += $tokensAdded; + } while ($tokensAdded > 0); + + $index = $functionCloseIndex; + $tokenCount += $totalTokensAdded; + } + } + + /** + * @param int $functionIndex token index of T_FUNCTION + * @param int $functionOpenIndex token index of the opening brace token of the function + * @param int $functionCloseIndex token index of the closing brace token of the function + * + * @return int >= 0 number of tokens inserted into the Tokens collection + */ + private function fixFunction(Tokens $tokens, int $functionIndex, int $functionOpenIndex, int $functionCloseIndex): int + { + static $riskyKinds = [ + CT::T_DYNAMIC_VAR_BRACE_OPEN, // "$h = ${$g};" case + T_EVAL, // "$c = eval('return $this;');" case + T_GLOBAL, + T_INCLUDE, // loading additional symbols we cannot analyze here + T_INCLUDE_ONCE, // " + T_REQUIRE, // " + T_REQUIRE_ONCE, // " + ]; + + $inserted = 0; + $candidates = []; + $isRisky = false; + + if ($tokens[$tokens->getNextMeaningfulToken($functionIndex)]->isGivenKind(CT::T_RETURN_REF)) { + $isRisky = true; + } + + // go through the function declaration and check if references are passed + // - check if it will be risky to fix return statements of this function + for ($index = $functionIndex + 1; $index < $functionOpenIndex; ++$index) { + if ($tokens[$index]->equals('&')) { + $isRisky = true; + + break; + } + } + + // go through all the tokens of the body of the function: + // - check if it will be risky to fix return statements of this function + // - check nested functions; fix when found and update the upper limit + number of inserted token + // - check for return statements that might be fixed (based on if fixing will be risky, which is only know after analyzing the whole function) + + for ($index = $functionOpenIndex + 1; $index < $functionCloseIndex; ++$index) { + if ($tokens[$index]->isGivenKind(T_FUNCTION)) { + $nestedFunctionOpenIndex = $tokens->getNextTokenOfKind($index, ['{', ';']); + if ($tokens[$nestedFunctionOpenIndex]->equals(';')) { // abstract function + $index = $nestedFunctionOpenIndex - 1; + + continue; + } + + $nestedFunctionCloseIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $nestedFunctionOpenIndex); + + $tokensAdded = $this->fixFunction( + $tokens, + $index, + $nestedFunctionOpenIndex, + $nestedFunctionCloseIndex + ); + + $index = $nestedFunctionCloseIndex + $tokensAdded; + $functionCloseIndex += $tokensAdded; + $inserted += $tokensAdded; + } + + if ($isRisky) { + continue; // don't bother to look into anything else than nested functions as the current is risky already + } + + if ($tokens[$index]->equals('&')) { + $isRisky = true; + + continue; + } + + if ($tokens[$index]->isGivenKind(T_RETURN)) { + $candidates[] = $index; + + continue; + } + + // test if there is anything in the function body that might + // change global state or indirect changes (like through references, eval, etc.) + + if ($tokens[$index]->isGivenKind($riskyKinds)) { + $isRisky = true; + + continue; + } + + if ($tokens[$index]->isGivenKind(T_STATIC)) { + $nextIndex = $tokens->getNextMeaningfulToken($index); + + if (!$tokens[$nextIndex]->isGivenKind(T_FUNCTION)) { + $isRisky = true; // "static $a" case + + continue; + } + } + + if ($tokens[$index]->equals('$')) { + $nextIndex = $tokens->getNextMeaningfulToken($index); + if ($tokens[$nextIndex]->isGivenKind(T_VARIABLE)) { + $isRisky = true; // "$$a" case + + continue; + } + } + + if ($this->tokensAnalyzer->isSuperGlobal($index)) { + $isRisky = true; + + continue; + } + } + + if ($isRisky) { + return $inserted; + } + + // fix the candidates in reverse order when applicable + for ($i = \count($candidates) - 1; $i >= 0; --$i) { + $index = $candidates[$i]; + + // Check if returning only a variable (i.e. not the result of an expression, function call etc.) + $returnVarIndex = $tokens->getNextMeaningfulToken($index); + if (!$tokens[$returnVarIndex]->isGivenKind(T_VARIABLE)) { + continue; // example: "return 1;" + } + + $endReturnVarIndex = $tokens->getNextMeaningfulToken($returnVarIndex); + if (!$tokens[$endReturnVarIndex]->equalsAny([';', [T_CLOSE_TAG]])) { + continue; // example: "return $a + 1;" + } + + // Check that the variable is assigned just before it is returned + $assignVarEndIndex = $tokens->getPrevMeaningfulToken($index); + if (!$tokens[$assignVarEndIndex]->equals(';')) { + continue; // example: "? return $a;" + } + + // Note: here we are @ "; return $a;" (or "; return $a ? >") + while (true) { + $prevMeaningFul = $tokens->getPrevMeaningfulToken($assignVarEndIndex); + + if (!$tokens[$prevMeaningFul]->equals(')')) { + break; + } + + $assignVarEndIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $prevMeaningFul); + } + + $assignVarOperatorIndex = $tokens->getPrevTokenOfKind( + $assignVarEndIndex, + ['=', ';', '{', '}', [T_OPEN_TAG], [T_OPEN_TAG_WITH_ECHO]] + ); + + if ($tokens[$assignVarOperatorIndex]->equals('}')) { + $startIndex = $this->isCloseBracePartOfDefinition($tokens, $assignVarOperatorIndex); // test for `anonymous class`, `lambda` and `match` + + if (null === $startIndex) { + continue; + } + + $assignVarOperatorIndex = $tokens->getPrevMeaningfulToken($startIndex); + } + + if (!$tokens[$assignVarOperatorIndex]->equals('=')) { + continue; + } + + // Note: here we are @ "= [^;{] ; return $a;" + $assignVarIndex = $tokens->getPrevMeaningfulToken($assignVarOperatorIndex); + if (!$tokens[$assignVarIndex]->equals($tokens[$returnVarIndex], false)) { + continue; + } + + // Note: here we are @ "$a = [^;{] ; return $a;" + $beforeAssignVarIndex = $tokens->getPrevMeaningfulToken($assignVarIndex); + if (!$tokens[$beforeAssignVarIndex]->equalsAny([';', '{', '}'])) { + continue; + } + + // Check if there is a `catch` or `finally` block between the assignment and the return + if ($this->isUsedInCatchOrFinally($tokens, $returnVarIndex, $functionOpenIndex, $functionCloseIndex)) { + continue; + } + + // Note: here we are @ "[;{}] $a = [^;{] ; return $a;" + $inserted += $this->simplifyReturnStatement( + $tokens, + $assignVarIndex, + $assignVarOperatorIndex, + $index, + $endReturnVarIndex + ); + } + + return $inserted; + } + + /** + * @return int >= 0 number of tokens inserted into the Tokens collection + */ + private function simplifyReturnStatement( + Tokens $tokens, + int $assignVarIndex, + int $assignVarOperatorIndex, + int $returnIndex, + int $returnVarEndIndex + ): int { + $inserted = 0; + $originalIndent = $tokens[$assignVarIndex - 1]->isWhitespace() + ? $tokens[$assignVarIndex - 1]->getContent() + : null; + + // remove the return statement + if ($tokens[$returnVarEndIndex]->equals(';')) { // do not remove PHP close tags + $tokens->clearTokenAndMergeSurroundingWhitespace($returnVarEndIndex); + } + + for ($i = $returnIndex; $i <= $returnVarEndIndex - 1; ++$i) { + $this->clearIfSave($tokens, $i); + } + + // remove no longer needed indentation of the old/remove return statement + if ($tokens[$returnIndex - 1]->isWhitespace()) { + $content = $tokens[$returnIndex - 1]->getContent(); + $fistLinebreakPos = strrpos($content, "\n"); + $content = false === $fistLinebreakPos + ? ' ' + : substr($content, $fistLinebreakPos); + + $tokens[$returnIndex - 1] = new Token([T_WHITESPACE, $content]); + } + + // remove the variable and the assignment + for ($i = $assignVarIndex; $i <= $assignVarOperatorIndex; ++$i) { + $this->clearIfSave($tokens, $i); + } + + // insert new return statement + $tokens->insertAt($assignVarIndex, new Token([T_RETURN, 'return'])); + ++$inserted; + + // use the original indent of the var assignment for the new return statement + if ( + null !== $originalIndent + && $tokens[$assignVarIndex - 1]->isWhitespace() + && $originalIndent !== $tokens[$assignVarIndex - 1]->getContent() + ) { + $tokens[$assignVarIndex - 1] = new Token([T_WHITESPACE, $originalIndent]); + } + + // remove trailing space after the new return statement which might be added during the cleanup process + $nextIndex = $tokens->getNonEmptySibling($assignVarIndex, 1); + if (!$tokens[$nextIndex]->isWhitespace()) { + $tokens->insertAt($nextIndex, new Token([T_WHITESPACE, ' '])); + ++$inserted; + } + + return $inserted; + } + + private function clearIfSave(Tokens $tokens, int $index): void + { + if ($tokens[$index]->isComment()) { + return; + } + + if ($tokens[$index]->isWhitespace() && $tokens[$tokens->getPrevNonWhitespace($index)]->isComment()) { + return; + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } + + /** + * @param int $index open brace index + * + * @return null|int index of the first token of a definition (lambda, anonymous class or match) or `null` if not an anonymous + */ + private function isCloseBracePartOfDefinition(Tokens $tokens, int $index): ?int + { + $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); + $candidateIndex = $this->isOpenBraceOfLambda($tokens, $index); + + if (null !== $candidateIndex) { + return $candidateIndex; + } + + $candidateIndex = $this->isOpenBraceOfAnonymousClass($tokens, $index); + + return $candidateIndex ?? $this->isOpenBraceOfMatch($tokens, $index); + } + + /** + * @param int $index open brace index + * + * @return null|int index of T_NEW of anonymous class or `null` if not an anonymous + */ + private function isOpenBraceOfAnonymousClass(Tokens $tokens, int $index): ?int + { + do { + $index = $tokens->getPrevMeaningfulToken($index); + } while ($tokens[$index]->equalsAny([',', [T_STRING], [T_IMPLEMENTS], [T_EXTENDS], [T_NS_SEPARATOR]])); + + if ($tokens[$index]->equals(')')) { // skip constructor braces and content within + $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + $index = $tokens->getPrevMeaningfulToken($index); + } + + if (!$tokens[$index]->isGivenKind(T_CLASS) || !$this->tokensAnalyzer->isAnonymousClass($index)) { + return null; + } + + return $tokens->getPrevTokenOfKind($index, [[T_NEW]]); + } + + /** + * @param int $index open brace index + * + * @return null|int index of T_FUNCTION or T_STATIC of lambda or `null` if not a lambda + */ + private function isOpenBraceOfLambda(Tokens $tokens, int $index): ?int + { + $index = $tokens->getPrevMeaningfulToken($index); + + if (!$tokens[$index]->equals(')')) { + return null; + } + + $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + $index = $tokens->getPrevMeaningfulToken($index); + + if ($tokens[$index]->isGivenKind(CT::T_USE_LAMBDA)) { + $index = $tokens->getPrevTokenOfKind($index, [')']); + $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + $index = $tokens->getPrevMeaningfulToken($index); + } + + if ($tokens[$index]->isGivenKind(CT::T_RETURN_REF)) { + $index = $tokens->getPrevMeaningfulToken($index); + } + + if (!$tokens[$index]->isGivenKind(T_FUNCTION)) { + return null; + } + + $staticCandidate = $tokens->getPrevMeaningfulToken($index); + + return $tokens[$staticCandidate]->isGivenKind(T_STATIC) ? $staticCandidate : $index; + } + + /** + * @param int $index open brace index + * + * @return null|int index of T_MATCH or `null` if not a `match` + */ + private function isOpenBraceOfMatch(Tokens $tokens, int $index): ?int + { + if (!\defined('T_MATCH') || !$tokens->isTokenKindFound(T_MATCH)) { // @TODO: drop condition when PHP 8.0+ is required + return null; + } + + $index = $tokens->getPrevMeaningfulToken($index); + + if (!$tokens[$index]->equals(')')) { + return null; + } + + $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + $index = $tokens->getPrevMeaningfulToken($index); + + return $tokens[$index]->isGivenKind(T_MATCH) ? $index : null; + } + + private function isUsedInCatchOrFinally(Tokens $tokens, int $returnVarIndex, int $functionOpenIndex, int $functionCloseIndex): bool + { + // Find try + $tryIndex = $tokens->getPrevTokenOfKind($returnVarIndex, [[T_TRY]]); + if (null === $tryIndex || $tryIndex <= $functionOpenIndex) { + return false; + } + $tryOpenIndex = $tokens->getNextTokenOfKind($tryIndex, ['{']); + $tryCloseIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $tryOpenIndex); + + // Find catch or finally + $nextIndex = $tokens->getNextMeaningfulToken($tryCloseIndex); + if (null === $nextIndex) { + return false; + } + + // Find catches + while ($tokens[$nextIndex]->isGivenKind(T_CATCH)) { + $catchOpenIndex = $tokens->getNextTokenOfKind($nextIndex, ['{']); + $catchCloseIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $catchOpenIndex); + + if ($catchCloseIndex >= $functionCloseIndex) { + return false; + } + $varIndex = $tokens->getNextTokenOfKind($catchOpenIndex, [$tokens[$returnVarIndex]]); + // Check if the variable is used in the finally block + if (null !== $varIndex && $varIndex < $catchCloseIndex) { + return true; + } + + $nextIndex = $tokens->getNextMeaningfulToken($catchCloseIndex); + if (null === $nextIndex) { + return false; + } + } + + if (!$tokens[$nextIndex]->isGivenKind(T_FINALLY)) { + return false; + } + + $finallyIndex = $nextIndex; + if ($finallyIndex >= $functionCloseIndex) { + return false; + } + $finallyOpenIndex = $tokens->getNextTokenOfKind($finallyIndex, ['{']); + $finallyCloseIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $finallyOpenIndex); + $varIndex = $tokens->getNextTokenOfKind($finallyOpenIndex, [$tokens[$returnVarIndex]]); + // Check if the variable is used in the finally block + if (null !== $varIndex && $varIndex < $finallyCloseIndex) { + return true; + } + + return false; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/SimplifiedNullReturnFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/SimplifiedNullReturnFixer.php new file mode 100644 index 00000000..b745b212 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/SimplifiedNullReturnFixer.php @@ -0,0 +1,149 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ReturnNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Graham Campbell + */ +final class SimplifiedNullReturnFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'A return statement wishing to return `void` should not return `null`.', + [ + new CodeSample("isTokenKindFound(T_RETURN); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_RETURN)) { + continue; + } + + if ($this->needFixing($tokens, $index)) { + $this->clear($tokens, $index); + } + } + } + + /** + * Clear the return statement located at a given index. + */ + private function clear(Tokens $tokens, int $index): void + { + while (!$tokens[++$index]->equals(';')) { + if ($this->shouldClearToken($tokens, $index)) { + $tokens->clearAt($index); + } + } + } + + /** + * Does the return statement located at a given index need fixing? + */ + private function needFixing(Tokens $tokens, int $index): bool + { + if ($this->isStrictOrNullableReturnTypeFunction($tokens, $index)) { + return false; + } + + $content = ''; + while (!$tokens[$index]->equals(';')) { + $index = $tokens->getNextMeaningfulToken($index); + $content .= $tokens[$index]->getContent(); + } + + $content = ltrim($content, '('); + $content = rtrim($content, ');'); + + return 'null' === strtolower($content); + } + + /** + * Is the return within a function with a non-void or nullable return type? + * + * @param int $returnIndex Current return token index + */ + private function isStrictOrNullableReturnTypeFunction(Tokens $tokens, int $returnIndex): bool + { + $functionIndex = $returnIndex; + do { + $functionIndex = $tokens->getPrevTokenOfKind($functionIndex, [[T_FUNCTION]]); + if (null === $functionIndex) { + return false; + } + $openingCurlyBraceIndex = $tokens->getNextTokenOfKind($functionIndex, ['{']); + $closingCurlyBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $openingCurlyBraceIndex); + } while ($closingCurlyBraceIndex < $returnIndex); + + $possibleVoidIndex = $tokens->getPrevMeaningfulToken($openingCurlyBraceIndex); + $isStrictReturnType = $tokens[$possibleVoidIndex]->isGivenKind([T_STRING, CT::T_ARRAY_TYPEHINT]) + && 'void' !== $tokens[$possibleVoidIndex]->getContent(); + + $nullableTypeIndex = $tokens->getNextTokenOfKind($functionIndex, [[CT::T_NULLABLE_TYPE]]); + $isNullableReturnType = null !== $nullableTypeIndex && $nullableTypeIndex < $openingCurlyBraceIndex; + + return $isStrictReturnType || $isNullableReturnType; + } + + /** + * Should we clear the specific token? + * + * If the token is a comment, or is whitespace that is immediately before a + * comment, then we'll leave it alone. + */ + private function shouldClearToken(Tokens $tokens, int $index): bool + { + $token = $tokens[$index]; + + return !$token->isComment() && !($token->isWhitespace() && $tokens[$index + 1]->isComment()); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/MultilineWhitespaceBeforeSemicolonsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/MultilineWhitespaceBeforeSemicolonsFixer.php new file mode 100644 index 00000000..f92ced5c --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/MultilineWhitespaceBeforeSemicolonsFixer.php @@ -0,0 +1,228 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Semicolon; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\WhitespacesAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Graham Campbell + * @author Egidijus Girčys + */ +final class MultilineWhitespaceBeforeSemicolonsFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface +{ + /** + * @internal + */ + public const STRATEGY_NO_MULTI_LINE = 'no_multi_line'; + + /** + * @internal + */ + public const STRATEGY_NEW_LINE_FOR_CHAINED_CALLS = 'new_line_for_chained_calls'; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Forbid multi-line whitespace before the closing semicolon or move the semicolon to the new line for chained calls.', + [ + new CodeSample( + 'method1() + ->method2() + ->method(3); +', + ['strategy' => self::STRATEGY_NEW_LINE_FOR_CHAINED_CALLS] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before SpaceAfterSemicolonFixer. + * Must run after CombineConsecutiveIssetsFixer, GetClassToClassKeywordFixer, NoEmptyStatementFixer, SimplifiedIfReturnFixer, SingleImportPerStatementFixer. + */ + public function getPriority(): int + { + return 0; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(';'); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder( + 'strategy', + 'Forbid multi-line whitespace or move the semicolon to the new line for chained calls.' + )) + ->setAllowedValues([self::STRATEGY_NO_MULTI_LINE, self::STRATEGY_NEW_LINE_FOR_CHAINED_CALLS]) + ->setDefault(self::STRATEGY_NO_MULTI_LINE) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $lineEnding = $this->whitespacesConfig->getLineEnding(); + + for ($index = 0, $count = \count($tokens); $index < $count; ++$index) { + if (!$tokens[$index]->equals(';')) { + continue; + } + + $previousIndex = $index - 1; + $previous = $tokens[$previousIndex]; + + $indent = $this->findWhitespaceBeforeFirstCall($index, $tokens); + if (self::STRATEGY_NEW_LINE_FOR_CHAINED_CALLS === $this->configuration['strategy'] && null !== $indent) { + if ($previous->isWhitespace() && $previous->getContent() === $lineEnding.$indent) { + continue; + } + + // unset whitespace and semicolon + if ($previous->isWhitespace()) { + $tokens->clearAt($previousIndex); + } + $tokens->clearAt($index); + + // find the line ending token index after the semicolon + $index = $this->getNewLineIndex($index, $tokens); + + // appended new line to the last method call + $newline = new Token([T_WHITESPACE, $lineEnding.$indent]); + + // insert the new line with indented semicolon + $tokens->insertAt($index++, [$newline, new Token(';')]); + } else { + if (!$previous->isWhitespace() || !str_contains($previous->getContent(), "\n")) { + continue; + } + + $content = $previous->getContent(); + if (str_starts_with($content, $lineEnding) && $tokens[$index - 2]->isComment()) { + // if there is comment between closing parenthesis and semicolon + + // unset whitespace and semicolon + $tokens->clearAt($previousIndex); + $tokens->clearAt($index); + + // find the significant token index before the semicolon + $significantTokenIndex = $this->getPreviousSignificantTokenIndex($index, $tokens); + + // insert the semicolon + $tokens->insertAt($significantTokenIndex + 1, [new Token(';')]); + } else { + // if there is whitespace between closing bracket and semicolon, just remove it + $tokens->clearAt($previousIndex); + } + } + } + } + + /** + * Find the index for the next new line. Return the given index when there's no new line. + */ + private function getNewLineIndex(int $index, Tokens $tokens): int + { + $lineEnding = $this->whitespacesConfig->getLineEnding(); + + for ($index, $count = \count($tokens); $index < $count; ++$index) { + if (false !== strstr($tokens[$index]->getContent(), $lineEnding)) { + return $index; + } + } + + return $index; + } + + /** + * Find the index for the previous significant token. Return the given index when there's no significant token. + */ + private function getPreviousSignificantTokenIndex(int $index, Tokens $tokens): int + { + $stopTokens = [ + T_LNUMBER, + T_DNUMBER, + T_STRING, + T_VARIABLE, + T_CONSTANT_ENCAPSED_STRING, + ]; + for ($index; $index > 0; --$index) { + if ($tokens[$index]->isGivenKind($stopTokens) || $tokens[$index]->equals(')')) { + return $index; + } + } + + return $index; + } + + /** + * Checks if the semicolon closes a multiline call and returns the whitespace of the first call at $index. + * i.e. it will return the whitespace marked with '____' in the example underneath. + * + * .. + * ____$this->methodCall() + * ->anotherCall(); + * .. + */ + private function findWhitespaceBeforeFirstCall(int $index, Tokens $tokens): ?string + { + $isMultilineCall = false; + $prevIndex = $tokens->getPrevMeaningfulToken($index); + + while (!$tokens[$prevIndex]->equalsAny([';', ':', '{', '}', [T_OPEN_TAG], [T_OPEN_TAG_WITH_ECHO], [T_ELSE]])) { + $index = $prevIndex; + $prevIndex = $tokens->getPrevMeaningfulToken($index); + + $blockType = Tokens::detectBlockType($tokens[$index]); + if (null !== $blockType && !$blockType['isStart']) { + $prevIndex = $tokens->findBlockStart($blockType['type'], $index); + + continue; + } + + if ($tokens[$index]->isObjectOperator() || $tokens[$index]->isGivenKind(T_DOUBLE_COLON)) { + $prevIndex = $tokens->getPrevMeaningfulToken($index); + $isMultilineCall |= $tokens->isPartialCodeMultiline($prevIndex, $index); + } + } + + return $isMultilineCall ? WhitespacesAnalyzer::detectIndent($tokens, $index) : null; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/NoEmptyStatementFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/NoEmptyStatementFixer.php new file mode 100644 index 00000000..15c1e670 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/NoEmptyStatementFixer.php @@ -0,0 +1,186 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Semicolon; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Dariusz Rumiński + */ +final class NoEmptyStatementFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Remove useless (semicolon) statements.', + [ + new CodeSample("isTokenKindFound(';'); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = 0, $count = $tokens->count(); $index < $count; ++$index) { + if ($tokens[$index]->isGivenKind([T_BREAK, T_CONTINUE])) { + $index = $tokens->getNextMeaningfulToken($index); + + if ($tokens[$index]->equals([T_LNUMBER, '1'])) { + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } + + continue; + } + + // skip T_FOR parenthesis to ignore double `;` like `for ($i = 1; ; ++$i) {...}` + if ($tokens[$index]->isGivenKind(T_FOR)) { + $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $tokens->getNextMeaningfulToken($index)) + 1; + + continue; + } + + if (!$tokens[$index]->equals(';')) { + continue; + } + + $previousMeaningfulIndex = $tokens->getPrevMeaningfulToken($index); + + // A semicolon can always be removed if it follows a semicolon, '{' or opening tag. + if ($tokens[$previousMeaningfulIndex]->equalsAny(['{', ';', [T_OPEN_TAG]])) { + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + + continue; + } + + // A semicolon might be removed if it follows a '}' but only if the brace is part of certain structures. + if ($tokens[$previousMeaningfulIndex]->equals('}')) { + $this->fixSemicolonAfterCurlyBraceClose($tokens, $index, $previousMeaningfulIndex); + + continue; + } + + // A semicolon might be removed together with its noop statement, for example "getPrevMeaningfulToken($previousMeaningfulIndex); + + if ( + $tokens[$prePreviousMeaningfulIndex]->equalsAny([';', '{', '}', [T_OPEN_TAG]]) + && $tokens[$previousMeaningfulIndex]->isGivenKind([T_CONSTANT_ENCAPSED_STRING, T_DNUMBER, T_LNUMBER, T_STRING, T_VARIABLE]) + ) { + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + $tokens->clearTokenAndMergeSurroundingWhitespace($previousMeaningfulIndex); + } + } + } + + /** + * Fix semicolon after closing curly brace if needed. + * + * Test for the following cases + * - just '{' '}' block (following open tag or ';') + * - if, else, elseif + * - interface, trait, class (but not anonymous) + * - catch, finally (but not try) + * - for, foreach, while (but not 'do - while') + * - switch + * - function (declaration, but not lambda) + * - declare (with '{' '}') + * - namespace (with '{' '}') + * + * @param int $index Semicolon index + */ + private function fixSemicolonAfterCurlyBraceClose(Tokens $tokens, int $index, int $curlyCloseIndex): void + { + static $beforeCurlyOpeningKinds = null; + + if (null === $beforeCurlyOpeningKinds) { + $beforeCurlyOpeningKinds = [T_ELSE, T_FINALLY, T_NAMESPACE, T_OPEN_TAG]; + } + + $curlyOpeningIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $curlyCloseIndex); + $beforeCurlyOpeningIndex = $tokens->getPrevMeaningfulToken($curlyOpeningIndex); + + if ($tokens[$beforeCurlyOpeningIndex]->isGivenKind($beforeCurlyOpeningKinds) || $tokens[$beforeCurlyOpeningIndex]->equalsAny([';', '{', '}'])) { + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + + return; + } + + // check for namespaces and class, interface and trait definitions + if ($tokens[$beforeCurlyOpeningIndex]->isGivenKind(T_STRING)) { + $classyTestIndex = $tokens->getPrevMeaningfulToken($beforeCurlyOpeningIndex); + + while ($tokens[$classyTestIndex]->equals(',') || $tokens[$classyTestIndex]->isGivenKind([T_STRING, T_NS_SEPARATOR, T_EXTENDS, T_IMPLEMENTS])) { + $classyTestIndex = $tokens->getPrevMeaningfulToken($classyTestIndex); + } + + $tokensAnalyzer = new TokensAnalyzer($tokens); + + if ( + $tokens[$classyTestIndex]->isGivenKind(T_NAMESPACE) + || ($tokens[$classyTestIndex]->isClassy() && !$tokensAnalyzer->isAnonymousClass($classyTestIndex)) + ) { + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } + + return; + } + + // early return check, below only control structures with conditions are fixed + if (!$tokens[$beforeCurlyOpeningIndex]->equals(')')) { + return; + } + + $openingBraceIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $beforeCurlyOpeningIndex); + $beforeOpeningBraceIndex = $tokens->getPrevMeaningfulToken($openingBraceIndex); + + if ($tokens[$beforeOpeningBraceIndex]->isGivenKind([T_IF, T_ELSEIF, T_FOR, T_FOREACH, T_WHILE, T_SWITCH, T_CATCH, T_DECLARE])) { + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + + return; + } + + // check for function definition + if ($tokens[$beforeOpeningBraceIndex]->isGivenKind(T_STRING)) { + $beforeStringIndex = $tokens->getPrevMeaningfulToken($beforeOpeningBraceIndex); + + if ($tokens[$beforeStringIndex]->isGivenKind(T_FUNCTION)) { + $tokens->clearTokenAndMergeSurroundingWhitespace($index); // implicit return + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/NoSinglelineWhitespaceBeforeSemicolonsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/NoSinglelineWhitespaceBeforeSemicolonsFixer.php new file mode 100644 index 00000000..f06d9d73 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/NoSinglelineWhitespaceBeforeSemicolonsFixer.php @@ -0,0 +1,66 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Semicolon; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Graham Campbell + */ +final class NoSinglelineWhitespaceBeforeSemicolonsFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Single-line whitespace before closing semicolon are prohibited.', + [new CodeSample("foo() ;\n")] + ); + } + + /** + * {@inheritdoc} + * + * Must run after CombineConsecutiveIssetsFixer, FunctionToConstantFixer, LongToShorthandOperatorFixer, NoEmptyStatementFixer, NoUnneededImportAliasFixer, SimplifiedIfReturnFixer, SingleImportPerStatementFixer. + */ + public function getPriority(): int + { + return 0; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(';'); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if (!$token->equals(';') || !$tokens[$index - 1]->isWhitespace(" \t")) { + continue; + } + + if ($tokens[$index - 2]->equals(';')) { + // do not remove all whitespace before the semicolon because it is also whitespace after another semicolon + $tokens->ensureWhitespaceAtIndex($index - 1, 0, ' '); + } elseif (!$tokens[$index - 2]->isComment()) { + $tokens->clearAt($index - 1); + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/SemicolonAfterInstructionFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/SemicolonAfterInstructionFixer.php new file mode 100644 index 00000000..2fe9ab07 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/SemicolonAfterInstructionFixer.php @@ -0,0 +1,64 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Semicolon; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class SemicolonAfterInstructionFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Instructions must be terminated with a semicolon.', + [new CodeSample("\n")] + ); + } + + /** + * {@inheritdoc} + * + * Must run before SimplifiedIfReturnFixer. + */ + public function getPriority(): int + { + return 2; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_CLOSE_TAG); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = \count($tokens) - 1; $index > 1; --$index) { + if (!$tokens[$index]->isGivenKind(T_CLOSE_TAG)) { + continue; + } + + $prev = $tokens->getPrevMeaningfulToken($index); + if ($tokens[$prev]->equalsAny([';', '{', '}', ':', [T_OPEN_TAG]])) { + continue; + } + + $tokens->insertAt($prev + 1, new Token(';')); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/SpaceAfterSemicolonFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/SpaceAfterSemicolonFixer.php new file mode 100644 index 00000000..63be6cfe --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/SpaceAfterSemicolonFixer.php @@ -0,0 +1,134 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Semicolon; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class SpaceAfterSemicolonFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Fix whitespace after a semicolon.', + [ + new CodeSample( + " true, + ]), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run after CombineConsecutiveUnsetsFixer, MultilineWhitespaceBeforeSemicolonsFixer, NoEmptyStatementFixer, OrderedClassElementsFixer, SingleImportPerStatementFixer, SingleTraitInsertPerStatementFixer. + */ + public function getPriority(): int + { + return -1; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(';'); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('remove_in_empty_for_expressions', 'Whether spaces should be removed for empty `for` expressions.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $insideForParenthesesUntil = null; + + for ($index = 0, $max = \count($tokens) - 1; $index < $max; ++$index) { + if (true === $this->configuration['remove_in_empty_for_expressions']) { + if ($tokens[$index]->isGivenKind(T_FOR)) { + $index = $tokens->getNextMeaningfulToken($index); + $insideForParenthesesUntil = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + + continue; + } + + if ($index === $insideForParenthesesUntil) { + $insideForParenthesesUntil = null; + + continue; + } + } + + if (!$tokens[$index]->equals(';')) { + continue; + } + + if (!$tokens[$index + 1]->isWhitespace()) { + if ( + !$tokens[$index + 1]->equalsAny([')', [T_INLINE_HTML]]) && ( + false === $this->configuration['remove_in_empty_for_expressions'] + || !$tokens[$index + 1]->equals(';') + ) + ) { + $tokens->insertAt($index + 1, new Token([T_WHITESPACE, ' '])); + ++$max; + } + + continue; + } + + if ( + null !== $insideForParenthesesUntil + && ($tokens[$index + 2]->equals(';') || $index + 2 === $insideForParenthesesUntil) + && !Preg::match('/\R/', $tokens[$index + 1]->getContent()) + ) { + $tokens->clearAt($index + 1); + + continue; + } + + if ( + isset($tokens[$index + 2]) + && !$tokens[$index + 1]->equals([T_WHITESPACE, ' ']) + && $tokens[$index + 1]->isWhitespace(" \t") + && !$tokens[$index + 2]->isComment() + && !$tokens[$index + 2]->equals(')') + ) { + $tokens[$index + 1] = new Token([T_WHITESPACE, ' ']); + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Strict/DeclareStrictTypesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Strict/DeclareStrictTypesFixer.php new file mode 100644 index 00000000..7e8172c2 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Strict/DeclareStrictTypesFixer.php @@ -0,0 +1,132 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Strict; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Jordi Boggiano + */ +final class DeclareStrictTypesFixer extends AbstractFixer implements WhitespacesAwareFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Force strict types declaration in all files. Requires PHP >= 7.0.', + [ + new CodeSample( + "isMonolithicPhp() && !$tokens->isTokenKindFound(T_OPEN_TAG_WITH_ECHO); + } + + public function isRisky(): bool + { + return true; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $openTagIndex = $tokens[0]->isGivenKind(T_INLINE_HTML) ? 1 : 0; + + $sequenceLocation = $tokens->findSequence([[T_DECLARE, 'declare'], '(', [T_STRING, 'strict_types'], '=', [T_LNUMBER], ')'], $openTagIndex, null, false); + if (null === $sequenceLocation) { + $this->insertSequence($openTagIndex, $tokens); // declaration not found, insert one + + return; + } + + $this->fixStrictTypesCasingAndValue($tokens, $sequenceLocation); + } + + /** + * @param array $sequence + */ + private function fixStrictTypesCasingAndValue(Tokens $tokens, array $sequence): void + { + /** @var int $index */ + /** @var Token $token */ + foreach ($sequence as $index => $token) { + if ($token->isGivenKind(T_STRING)) { + $tokens[$index] = new Token([T_STRING, strtolower($token->getContent())]); + + continue; + } + if ($token->isGivenKind(T_LNUMBER)) { + $tokens[$index] = new Token([T_LNUMBER, '1']); + + break; + } + } + } + + private function insertSequence(int $openTagIndex, Tokens $tokens): void + { + $sequence = [ + new Token([T_DECLARE, 'declare']), + new Token('('), + new Token([T_STRING, 'strict_types']), + new Token('='), + new Token([T_LNUMBER, '1']), + new Token(')'), + new Token(';'), + ]; + $nextIndex = $openTagIndex + \count($sequence) + 1; + + $tokens->insertAt($openTagIndex + 1, $sequence); + + // transform "getContent(); + if (!str_contains($content, ' ') || str_contains($content, "\n")) { + $tokens[$openTagIndex] = new Token([$tokens[$openTagIndex]->getId(), trim($tokens[$openTagIndex]->getContent()).' ']); + } + + if (\count($tokens) === $nextIndex) { + return; // no more tokens after sequence, single_blank_line_at_eof might add a line + } + + $lineEnding = $this->whitespacesConfig->getLineEnding(); + if ($tokens[$nextIndex]->isWhitespace()) { + $content = $tokens[$nextIndex]->getContent(); + $tokens[$nextIndex] = new Token([T_WHITESPACE, $lineEnding.ltrim($content, " \t")]); + } else { + $tokens->insertAt($nextIndex, new Token([T_WHITESPACE, $lineEnding])); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Strict/StrictComparisonFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Strict/StrictComparisonFixer.php new file mode 100644 index 00000000..18222c59 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Strict/StrictComparisonFixer.php @@ -0,0 +1,80 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Strict; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class StrictComparisonFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Comparisons should be strict.', + [new CodeSample("isAnyTokenKindsFound([T_IS_EQUAL, T_IS_NOT_EQUAL]); + } + + public function isRisky(): bool + { + return true; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + static $map = [ + T_IS_EQUAL => [ + 'id' => T_IS_IDENTICAL, + 'content' => '===', + ], + T_IS_NOT_EQUAL => [ + 'id' => T_IS_NOT_IDENTICAL, + 'content' => '!==', + ], + ]; + + foreach ($tokens as $index => $token) { + $tokenId = $token->getId(); + + if (isset($map[$tokenId])) { + $tokens[$index] = new Token([$map[$tokenId]['id'], $map[$tokenId]['content']]); + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Strict/StrictParamFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Strict/StrictParamFixer.php new file mode 100644 index 00000000..e0f12f33 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Strict/StrictParamFixer.php @@ -0,0 +1,168 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Strict; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class StrictParamFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Functions should be used with `$strict` param set to `true`.', + [new CodeSample("isTokenKindFound(T_STRING); + } + + public function isRisky(): bool + { + return true; + } + + /** + * {@inheritdoc} + * + * Must run before MethodArgumentSpaceFixer, NativeFunctionInvocationFixer. + */ + public function getPriority(): int + { + return 31; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $functionsAnalyzer = new FunctionsAnalyzer(); + + static $map = null; + + if (null === $map) { + $trueToken = new Token([T_STRING, 'true']); + + $map = [ + 'array_keys' => [null, null, $trueToken], + 'array_search' => [null, null, $trueToken], + 'base64_decode' => [null, $trueToken], + 'in_array' => [null, null, $trueToken], + 'mb_detect_encoding' => [null, [new Token([T_STRING, 'mb_detect_order']), new Token('('), new Token(')')], $trueToken], + ]; + } + + for ($index = $tokens->count() - 1; 0 <= $index; --$index) { + $token = $tokens[$index]; + + $nextIndex = $tokens->getNextMeaningfulToken($index); + if (null !== $nextIndex && !$tokens[$nextIndex]->equals('(')) { + continue; + } + + $lowercaseContent = strtolower($token->getContent()); + if (isset($map[$lowercaseContent]) && $functionsAnalyzer->isGlobalFunctionCall($tokens, $index)) { + $this->fixFunction($tokens, $index, $map[$lowercaseContent]); + } + } + } + + /** + * @param list $functionParams + */ + private function fixFunction(Tokens $tokens, int $functionIndex, array $functionParams): void + { + $startBraceIndex = $tokens->getNextTokenOfKind($functionIndex, ['(']); + $endBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startBraceIndex); + $paramsQuantity = 0; + $expectParam = true; + + for ($index = $startBraceIndex + 1; $index < $endBraceIndex; ++$index) { + $token = $tokens[$index]; + + if ($expectParam && !$token->isWhitespace() && !$token->isComment()) { + ++$paramsQuantity; + $expectParam = false; + } + + if ($token->equals('(')) { + $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + + continue; + } + + if ($token->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) { + $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $index); + + continue; + } + + if ($token->equals(',')) { + $expectParam = true; + + continue; + } + } + + $functionParamsQuantity = \count($functionParams); + + if ($paramsQuantity === $functionParamsQuantity) { + return; + } + + $tokensToInsert = []; + + for ($i = $paramsQuantity; $i < $functionParamsQuantity; ++$i) { + // function call do not have all params that are required to set useStrict flag, exit from method! + if (null === $functionParams[$i]) { + return; + } + + $tokensToInsert[] = new Token(','); + $tokensToInsert[] = new Token([T_WHITESPACE, ' ']); + + if (!\is_array($functionParams[$i])) { + $tokensToInsert[] = clone $functionParams[$i]; + + continue; + } + + foreach ($functionParams[$i] as $param) { + $tokensToInsert[] = clone $param; + } + } + + $beforeEndBraceIndex = $tokens->getPrevMeaningfulToken($endBraceIndex); + + if ($tokens[$beforeEndBraceIndex]->equals(',')) { + array_shift($tokensToInsert); + $tokensToInsert[] = new Token(','); + } + + $tokens->insertAt($beforeEndBraceIndex + 1, $tokensToInsert); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/EscapeImplicitBackslashesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/EscapeImplicitBackslashesFixer.php new file mode 100644 index 00000000..786134ff --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/EscapeImplicitBackslashesFixer.php @@ -0,0 +1,138 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\StringNotation; + +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; + +/** + * @author Filippo Tessarotto + * @author Michael Vorisek + * + * @deprecated Use `string_implicit_backslashes` with config: ['single_quoted' => 'ignore', 'double_quoted' => 'escape', 'heredoc' => 'escape'] (default) + */ +final class EscapeImplicitBackslashesFixer extends AbstractProxyFixer implements ConfigurableFixerInterface, DeprecatedFixerInterface +{ + public function getSuccessorsNames(): array + { + return array_keys($this->proxyFixers); + } + + public function getDefinition(): FixerDefinitionInterface + { + $codeSample = <<<'EOF' + true] + ), + new CodeSample( + $codeSample, + ['double_quoted' => false] + ), + new CodeSample( + $codeSample, + ['heredoc_syntax' => false] + ), + ], + 'In PHP double-quoted strings and heredocs some chars like `n`, `$` or `u` have special meanings if preceded by a backslash ' + .'(and some are special only if followed by other special chars), while a backslash preceding other chars are interpreted like a plain ' + .'backslash. The precise list of those special chars is hard to remember and to identify quickly: this fixer escapes backslashes ' + ."that do not start a special interpretation with the char after them.\n" + .'It is possible to fix also single-quoted strings: in this case there is no special chars apart from single-quote and backslash ' + .'itself, so the fixer simply ensure that all backslashes are escaped. Both single and double backslashes are allowed in single-quoted ' + .'strings, so the purpose in this context is mainly to have a uniformed way to have them written all over the codebase.' + ); + } + + /** + * {@inheritdoc} + * + * Must run before HeredocToNowdocFixer, SingleQuoteFixer. + * Must run after BacktickToShellExecFixer, MultilineStringToHeredocFixer. + */ + public function getPriority(): int + { + return parent::getPriority(); + } + + public function configure(array $configuration): void + { + parent::configure($configuration); + + /** @var StringImplicitBackslashesFixer */ + $stringImplicitBackslashesFixer = $this->proxyFixers['string_implicit_backslashes']; + + $stringImplicitBackslashesFixer->configure([ + 'single_quoted' => true === $this->configuration['single_quoted'] ? 'escape' : 'ignore', + 'double_quoted' => true === $this->configuration['double_quoted'] ? 'escape' : 'ignore', + 'heredoc' => true === $this->configuration['heredoc_syntax'] ? 'escape' : 'ignore', + ]); + } + + protected function createProxyFixers(): array + { + $stringImplicitBackslashesFixer = new StringImplicitBackslashesFixer(); + $stringImplicitBackslashesFixer->configure([ + 'single_quoted' => 'ignore', + 'double_quoted' => 'escape', + 'heredoc' => 'escape', + ]); + + return [ + $stringImplicitBackslashesFixer, + ]; + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('single_quoted', 'Whether to fix single-quoted strings.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + (new FixerOptionBuilder('double_quoted', 'Whether to fix double-quoted strings.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), + (new FixerOptionBuilder('heredoc_syntax', 'Whether to fix heredoc syntax.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), + ]); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/ExplicitStringVariableFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/ExplicitStringVariableFixer.php new file mode 100644 index 00000000..ca0c741f --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/ExplicitStringVariableFixer.php @@ -0,0 +1,165 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\StringNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Filippo Tessarotto + */ +final class ExplicitStringVariableFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Converts implicit variables into explicit ones in double-quoted strings or heredoc syntax.', + [new CodeSample( + <<<'EOT' + country !"; + $c = "I have $farm[0] chickens !"; + + EOT + )], + 'The reasoning behind this rule is the following:' + ."\n".'- When there are two valid ways of doing the same thing, using both is confusing, there should be a coding standard to follow.' + ."\n".'- PHP manual marks `"$var"` syntax as implicit and `"${var}"` syntax as explicit: explicit code should always be preferred.' + ."\n".'- Explicit syntax allows word concatenation inside strings, e.g. `"${var}IsAVar"`, implicit doesn\'t.' + ."\n".'- Explicit syntax is easier to detect for IDE/editors and therefore has colors/highlight with higher contrast, which is easier to read.' + ."\n".'Backtick operator is skipped because it is harder to handle; you can use `backtick_to_shell_exec` fixer to normalize backticks to strings.' + ); + } + + /** + * {@inheritdoc} + * + * Must run after BacktickToShellExecFixer. + */ + public function getPriority(): int + { + return 0; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_VARIABLE); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $backtickStarted = false; + for ($index = \count($tokens) - 1; $index > 0; --$index) { + $token = $tokens[$index]; + + if ($token->equals('`')) { + $backtickStarted = !$backtickStarted; + + continue; + } + + if ($backtickStarted || !$token->isGivenKind(T_VARIABLE)) { + continue; + } + + $prevToken = $tokens[$index - 1]; + + if (!$this->isStringPartToken($prevToken)) { + continue; + } + + $distinctVariableIndex = $index; + $variableTokens = [ + $distinctVariableIndex => [ + 'tokens' => [$index => $token], + 'firstVariableTokenIndex' => $index, + 'lastVariableTokenIndex' => $index, + ], + ]; + + $nextIndex = $index + 1; + $squareBracketCount = 0; + + while (!$this->isStringPartToken($tokens[$nextIndex])) { + if ($tokens[$nextIndex]->isGivenKind(T_CURLY_OPEN)) { + $nextIndex = $tokens->getNextTokenOfKind($nextIndex, [[CT::T_CURLY_CLOSE]]); + } elseif ($tokens[$nextIndex]->isGivenKind(T_VARIABLE) && 1 !== $squareBracketCount) { + $distinctVariableIndex = $nextIndex; + $variableTokens[$distinctVariableIndex] = [ + 'tokens' => [$nextIndex => $tokens[$nextIndex]], + 'firstVariableTokenIndex' => $nextIndex, + 'lastVariableTokenIndex' => $nextIndex, + ]; + } else { + $variableTokens[$distinctVariableIndex]['tokens'][$nextIndex] = $tokens[$nextIndex]; + $variableTokens[$distinctVariableIndex]['lastVariableTokenIndex'] = $nextIndex; + + if ($tokens[$nextIndex]->equalsAny(['[', ']'])) { + ++$squareBracketCount; + } + } + + ++$nextIndex; + } + krsort($variableTokens, SORT_NUMERIC); + + foreach ($variableTokens as $distinctVariableSet) { + if (1 === \count($distinctVariableSet['tokens'])) { + $singleVariableIndex = array_key_first($distinctVariableSet['tokens']); + $singleVariableToken = current($distinctVariableSet['tokens']); + $tokens->overrideRange($singleVariableIndex, $singleVariableIndex, [ + new Token([T_CURLY_OPEN, '{']), + new Token([T_VARIABLE, $singleVariableToken->getContent()]), + new Token([CT::T_CURLY_CLOSE, '}']), + ]); + } else { + foreach ($distinctVariableSet['tokens'] as $variablePartIndex => $variablePartToken) { + if ($variablePartToken->isGivenKind(T_NUM_STRING)) { + $tokens[$variablePartIndex] = new Token([T_LNUMBER, $variablePartToken->getContent()]); + + continue; + } + + if ($variablePartToken->isGivenKind(T_STRING) && $tokens[$variablePartIndex + 1]->equals(']')) { + $tokens[$variablePartIndex] = new Token([T_CONSTANT_ENCAPSED_STRING, "'".$variablePartToken->getContent()."'"]); + } + } + + $tokens->insertAt($distinctVariableSet['lastVariableTokenIndex'] + 1, new Token([CT::T_CURLY_CLOSE, '}'])); + $tokens->insertAt($distinctVariableSet['firstVariableTokenIndex'], new Token([T_CURLY_OPEN, '{'])); + } + } + } + } + + /** + * Check if token is a part of a string. + * + * @param Token $token The token to check + */ + private function isStringPartToken(Token $token): bool + { + return $token->isGivenKind(T_ENCAPSED_AND_WHITESPACE) + || $token->isGivenKind(T_START_HEREDOC) + || '"' === $token->getContent() + || 'b"' === strtolower($token->getContent()); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/HeredocClosingMarkerFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/HeredocClosingMarkerFixer.php new file mode 100644 index 00000000..a4245c9f --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/HeredocClosingMarkerFixer.php @@ -0,0 +1,188 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\StringNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Michael Vorisek + */ +final class HeredocClosingMarkerFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** + * @var list + */ + public const RESERVED_CLOSING_MARKERS = [ + 'CSS', + 'DIFF', + 'HTML', + 'JS', + 'JSON', + 'MD', + 'PHP', + 'PYTHON', + 'RST', + 'TS', + 'SQL', + 'XML', + 'YAML', + ]; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Unify `heredoc` or `nowdoc` closing marker.', + [ + new CodeSample( + <<<'EOD' + 'EOF'] + ), + new CodeSample( + <<<'EOD_' + true] + ), + ] + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_START_HEREDOC); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder( + 'closing_marker', + 'Preferred closing marker.' + )) + ->setAllowedTypes(['string']) + ->setDefault('EOD') + ->getOption(), + (new FixerOptionBuilder( + 'reserved_closing_markers', + 'Reserved closing markers to be kept unchanged.' + )) + ->setAllowedTypes(['array']) + ->setDefault(self::RESERVED_CLOSING_MARKERS) + ->getOption(), + (new FixerOptionBuilder( + 'explicit_heredoc_style', + 'Whether the closing marker should be wrapped in double quotes.' + )) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $reservedClosingMarkersMap = null; + + $startIndex = null; + foreach ($tokens as $index => $token) { + if ($token->isGivenKind(T_START_HEREDOC)) { + $startIndex = $index; + + continue; + } + + if (null !== $startIndex && $token->isGivenKind(T_END_HEREDOC)) { + $existingClosingMarker = trim($token->getContent()); + + if (null === $reservedClosingMarkersMap) { + $reservedClosingMarkersMap = []; + foreach ($this->configuration['reserved_closing_markers'] as $v) { + $reservedClosingMarkersMap[mb_strtoupper($v)] = $v; + } + } + + $existingClosingMarker = mb_strtoupper($existingClosingMarker); + do { + $newClosingMarker = $reservedClosingMarkersMap[$existingClosingMarker] ?? null; + if (!str_ends_with($existingClosingMarker, '_')) { + break; + } + $existingClosingMarker = substr($existingClosingMarker, 0, -1); + } while (null === $newClosingMarker); + + if (null === $newClosingMarker) { + $newClosingMarker = $this->configuration['closing_marker']; + } + + $content = $tokens->generatePartialCode($startIndex + 1, $index - 1); + while (Preg::match('~(^|[\r\n])\s*'.preg_quote($newClosingMarker, '~').'(?!\w)~', $content)) { + $newClosingMarker .= '_'; + } + + [$tokens[$startIndex], $tokens[$index]] = $this->convertClosingMarker($tokens[$startIndex], $token, $newClosingMarker); + + $startIndex = null; + + continue; + } + } + } + + /** + * @return array{Token, Token} + */ + private function convertClosingMarker(Token $startToken, Token $endToken, string $newClosingMarker): array + { + $isNowdoc = str_contains($startToken->getContent(), '\''); + + $markerQuote = $isNowdoc + ? '\'' + : (true === $this->configuration['explicit_heredoc_style'] ? '"' : ''); + + return [new Token([ + $startToken->getId(), + Preg::replace('/<<<\h*\K["\']?[^\s"\']+["\']?/', $markerQuote.$newClosingMarker.$markerQuote, $startToken->getContent()), + ]), new Token([ + $endToken->getId(), + Preg::replace('/\S+/', $newClosingMarker, $endToken->getContent()), + ])]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/HeredocToNowdocFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/HeredocToNowdocFixer.php new file mode 100644 index 00000000..800c79db --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/HeredocToNowdocFixer.php @@ -0,0 +1,107 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\StringNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Gregor Harlan + */ +final class HeredocToNowdocFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Convert `heredoc` to `nowdoc` where possible.', + [ + new CodeSample( + <<<'EOF' + isTokenKindFound(T_START_HEREDOC); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_START_HEREDOC) || str_contains($token->getContent(), "'")) { + continue; + } + + if ($tokens[$index + 1]->isGivenKind(T_END_HEREDOC)) { + $tokens[$index] = $this->convertToNowdoc($token); + + continue; + } + + if ( + !$tokens[$index + 1]->isGivenKind(T_ENCAPSED_AND_WHITESPACE) + || !$tokens[$index + 2]->isGivenKind(T_END_HEREDOC) + ) { + continue; + } + + $content = $tokens[$index + 1]->getContent(); + // regex: odd number of backslashes, not followed by dollar + if (Preg::match('/(?convertToNowdoc($token); + $content = str_replace(['\\\\', '\\$'], ['\\', '$'], $content); + $tokens[$index + 1] = new Token([ + $tokens[$index + 1]->getId(), + $content, + ]); + } + } + + /** + * Transforms the heredoc start token to nowdoc notation. + */ + private function convertToNowdoc(Token $token): Token + { + return new Token([ + $token->getId(), + Preg::replace('/^([Bb]?<<<)(\h*)"?([^\s"]+)"?/', '$1$2\'$3\'', $token->getContent()), + ]); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/MultilineStringToHeredocFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/MultilineStringToHeredocFixer.php new file mode 100644 index 00000000..841a68fa --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/MultilineStringToHeredocFixer.php @@ -0,0 +1,166 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\StringNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Michael Vorisek + */ +final class MultilineStringToHeredocFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Convert multiline string to `heredoc` or `nowdoc`.', + [ + new CodeSample( + <<<'EOD' + getName()}"; + EOD."\n" + ), + ] + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound([T_CONSTANT_ENCAPSED_STRING, T_ENCAPSED_AND_WHITESPACE]); + } + + /** + * {@inheritdoc} + * + * Must run before EscapeImplicitBackslashesFixer, HeredocIndentationFixer, StringImplicitBackslashesFixer. + */ + public function getPriority(): int + { + return 16; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $inHeredoc = false; + $complexStringStartIndex = null; + foreach ($tokens as $index => $token) { + if ($token->isGivenKind([T_START_HEREDOC, T_END_HEREDOC])) { + $inHeredoc = $token->isGivenKind(T_START_HEREDOC) || !$token->isGivenKind(T_END_HEREDOC); + + continue; + } + + if (null === $complexStringStartIndex) { + if ($token->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) { + $this->convertStringToHeredoc($tokens, $index, $index); + + // skip next 2 added tokens if replaced + if ($tokens[$index]->isGivenKind(T_START_HEREDOC)) { + $inHeredoc = true; + } + } elseif ($token->equalsAny(['"', 'b"', 'B"'])) { + $complexStringStartIndex = $index; + } + } elseif ($token->equals('"')) { + $this->convertStringToHeredoc($tokens, $complexStringStartIndex, $index); + + $complexStringStartIndex = null; + } + } + } + + private function convertStringToHeredoc(Tokens $tokens, int $stringStartIndex, int $stringEndIndex): void + { + $closingMarker = 'EOD'; + + if ($tokens[$stringStartIndex]->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) { + $content = $tokens[$stringStartIndex]->getContent(); + if ('b' === strtolower(substr($content, 0, 1))) { + $content = substr($content, 1); + } + $isSingleQuoted = str_starts_with($content, '\''); + $content = substr($content, 1, -1); + + if ($isSingleQuoted) { + $content = Preg::replace('~\\\\([\\\\\'])~', '$1', $content); + } else { + $content = Preg::replace('~(\\\\\\\\)|\\\\(")~', '$1$2', $content); + } + + $constantStringToken = new Token([T_ENCAPSED_AND_WHITESPACE, $content."\n"]); + } else { + $content = $tokens->generatePartialCode($stringStartIndex + 1, $stringEndIndex - 1); + $isSingleQuoted = false; + $constantStringToken = null; + } + + if (!str_contains($content, "\n") && !str_contains($content, "\r")) { + return; + } + + while (Preg::match('~(^|[\r\n])\s*'.preg_quote($closingMarker, '~').'(?!\w)~', $content)) { + $closingMarker .= '_'; + } + + $quoting = $isSingleQuoted ? '\'' : ''; + $heredocStartToken = new Token([T_START_HEREDOC, '<<<'.$quoting.$closingMarker.$quoting."\n"]); + $heredocEndToken = new Token([T_END_HEREDOC, $closingMarker]); + + if (null !== $constantStringToken) { + $tokens->overrideRange($stringStartIndex, $stringEndIndex, [ + $heredocStartToken, + $constantStringToken, + $heredocEndToken, + ]); + } else { + for ($i = $stringStartIndex + 1; $i < $stringEndIndex; ++$i) { + if ($tokens[$i]->isGivenKind(T_ENCAPSED_AND_WHITESPACE)) { + $tokens[$i] = new Token([ + $tokens[$i]->getId(), + Preg::replace('~(\\\\\\\\)|\\\\(")~', '$1$2', $tokens[$i]->getContent()), + ]); + } + } + + $tokens[$stringStartIndex] = $heredocStartToken; + $tokens[$stringEndIndex] = $heredocEndToken; + if ($tokens[$stringEndIndex - 1]->isGivenKind(T_ENCAPSED_AND_WHITESPACE)) { + $tokens[$stringEndIndex - 1] = new Token([ + $tokens[$stringEndIndex - 1]->getId(), + $tokens[$stringEndIndex - 1]->getContent()."\n", + ]); + } else { + $tokens->insertAt($stringEndIndex, new Token([ + T_ENCAPSED_AND_WHITESPACE, + "\n", + ])); + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/NoBinaryStringFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/NoBinaryStringFixer.php new file mode 100644 index 00000000..3c41ab5b --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/NoBinaryStringFixer.php @@ -0,0 +1,75 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\StringNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author ntzm + */ +final class NoBinaryStringFixer extends AbstractFixer +{ + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound( + [ + T_CONSTANT_ENCAPSED_STRING, + T_START_HEREDOC, + 'b"', + ] + ); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'There should not be a binary flag before strings.', + [ + new CodeSample(" $token) { + if ($token->isGivenKind([T_CONSTANT_ENCAPSED_STRING, T_START_HEREDOC])) { + $content = $token->getContent(); + + if ('b' === strtolower($content[0])) { + $tokens[$index] = new Token([$token->getId(), substr($content, 1)]); + } + } elseif ($token->equals('b"')) { + $tokens[$index] = new Token('"'); + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/NoTrailingWhitespaceInStringFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/NoTrailingWhitespaceInStringFixer.php new file mode 100644 index 00000000..5e046073 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/NoTrailingWhitespaceInStringFixer.php @@ -0,0 +1,100 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\StringNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Gregor Harlan + */ +final class NoTrailingWhitespaceInStringFixer extends AbstractFixer +{ + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound([T_CONSTANT_ENCAPSED_STRING, T_ENCAPSED_AND_WHITESPACE, T_INLINE_HTML]); + } + + public function isRisky(): bool + { + return true; + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'There must be no trailing whitespace in strings.', + [ + new CodeSample( + "count() - 1, $last = true; $index >= 0; --$index, $last = false) { + /** @var Token $token */ + $token = $tokens[$index]; + + if (!$token->isGivenKind([T_CONSTANT_ENCAPSED_STRING, T_ENCAPSED_AND_WHITESPACE, T_INLINE_HTML])) { + continue; + } + + $isInlineHtml = $token->isGivenKind(T_INLINE_HTML); + $regex = $isInlineHtml && $last ? '/\h+(?=\R|$)/' : '/\h+(?=\R)/'; + $content = Preg::replace($regex, '', $token->getContent()); + + if ($token->getContent() === $content) { + continue; + } + + if (!$isInlineHtml || 0 === $index) { + $this->updateContent($tokens, $index, $content); + + continue; + } + + $prev = $index - 1; + + if ($tokens[$prev]->equals([T_CLOSE_TAG, '?>']) && Preg::match('/^\R/', $content, $match)) { + $tokens[$prev] = new Token([T_CLOSE_TAG, $tokens[$prev]->getContent().$match[0]]); + $content = substr($content, \strlen($match[0])); + $content = false === $content ? '' : $content; // @phpstan-ignore-line due to https://github.com/phpstan/phpstan/issues/1215 , awaiting PHP8 as min requirement of Fixer + } + + $this->updateContent($tokens, $index, $content); + } + } + + private function updateContent(Tokens $tokens, int $index, string $content): void + { + if ('' === $content) { + $tokens->clearAt($index); + + return; + } + + $tokens[$index] = new Token([$tokens[$index]->getId(), $content]); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/SimpleToComplexStringVariableFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/SimpleToComplexStringVariableFixer.php new file mode 100644 index 00000000..75891ac0 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/SimpleToComplexStringVariableFixer.php @@ -0,0 +1,110 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\StringNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dave van der Brugge + */ +final class SimpleToComplexStringVariableFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Converts explicit variables in double-quoted strings and heredoc syntax from simple to complex format (`${` to `{$`).', + [ + new CodeSample( + <<<'EOT' + isTokenKindFound(T_DOLLAR_OPEN_CURLY_BRACES); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = \count($tokens) - 3; $index > 0; --$index) { + $token = $tokens[$index]; + + if (!$token->isGivenKind(T_DOLLAR_OPEN_CURLY_BRACES)) { + continue; + } + + $varnameToken = $tokens[$index + 1]; + + if (!$varnameToken->isGivenKind(T_STRING_VARNAME)) { + continue; + } + + $dollarCloseToken = $tokens[$index + 2]; + + if (!$dollarCloseToken->isGivenKind(CT::T_DOLLAR_CLOSE_CURLY_BRACES)) { + continue; + } + + $tokenOfStringBeforeToken = $tokens[$index - 1]; + $stringContent = $tokenOfStringBeforeToken->getContent(); + + if (str_ends_with($stringContent, '$') && !str_ends_with($stringContent, '\\$')) { + $newContent = substr($stringContent, 0, -1).'\\$'; + $tokenOfStringBeforeToken = new Token([T_ENCAPSED_AND_WHITESPACE, $newContent]); + } + + $tokens->overrideRange($index - 1, $index + 2, [ + $tokenOfStringBeforeToken, + new Token([T_CURLY_OPEN, '{']), + new Token([T_VARIABLE, '$'.$varnameToken->getContent()]), + new Token([CT::T_CURLY_CLOSE, '}']), + ]); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/SingleQuoteFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/SingleQuoteFixer.php new file mode 100644 index 00000000..a56c6b4e --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/SingleQuoteFixer.php @@ -0,0 +1,109 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\StringNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Gregor Harlan + */ +final class SingleQuoteFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + $codeSample = <<<'EOF' + true] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before NoUselessConcatOperatorFixer. + * Must run after BacktickToShellExecFixer, EscapeImplicitBackslashesFixer, StringImplicitBackslashesFixer. + */ + public function getPriority(): int + { + return 10; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_CONSTANT_ENCAPSED_STRING); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) { + continue; + } + + $content = $token->getContent(); + $prefix = ''; + + if ('b' === strtolower($content[0])) { + $prefix = $content[0]; + $content = substr($content, 1); + } + + if ( + '"' === $content[0] + && (true === $this->configuration['strings_containing_single_quote_chars'] || !str_contains($content, "'")) + // regex: odd number of backslashes, not followed by double quote or dollar + && !Preg::match('/(?setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/StringImplicitBackslashesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/StringImplicitBackslashesFixer.php new file mode 100644 index 00000000..da353576 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/StringImplicitBackslashesFixer.php @@ -0,0 +1,177 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\StringNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Filippo Tessarotto + * @author Michael Vorisek + */ +final class StringImplicitBackslashesFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + $codeSample = <<<'EOF' + 'escape'] + ), + new CodeSample( + $codeSample, + ['double_quoted' => 'unescape'] + ), + new CodeSample( + $codeSample, + ['heredoc' => 'unescape'] + ), + ], + 'In PHP double-quoted strings and heredocs some chars like `n`, `$` or `u` have special meanings if preceded by a backslash ' + .'(and some are special only if followed by other special chars), while a backslash preceding other chars are interpreted like a plain ' + .'backslash. The precise list of those special chars is hard to remember and to identify quickly: this fixer escapes backslashes ' + ."that do not start a special interpretation with the char after them.\n" + .'It is possible to fix also single-quoted strings: in this case there is no special chars apart from single-quote and backslash ' + .'itself, so the fixer simply ensure that all backslashes are escaped. Both single and double backslashes are allowed in single-quoted ' + .'strings, so the purpose in this context is mainly to have a uniformed way to have them written all over the codebase.' + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound([T_ENCAPSED_AND_WHITESPACE, T_CONSTANT_ENCAPSED_STRING]); + } + + /** + * {@inheritdoc} + * + * Must run before HeredocToNowdocFixer, SingleQuoteFixer. + * Must run after BacktickToShellExecFixer, MultilineStringToHeredocFixer. + */ + public function getPriority(): int + { + return 15; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $singleQuotedReservedRegex = '[\'\\\]'; + $doubleQuotedReservedRegex = '(?:[efnrtv$"\\\0-7]|x[0-9A-Fa-f]|u{|$)'; + $heredocSyntaxReservedRegex = '(?:[efnrtv$\\\0-7]|x[0-9A-Fa-f]|u{|$)'; + + $doubleQuoteOpened = false; + foreach ($tokens as $index => $token) { + if ($token->equalsAny(['"', 'b"', 'B"'])) { + $doubleQuoteOpened = !$doubleQuoteOpened; + } + + if (!$token->isGivenKind([T_ENCAPSED_AND_WHITESPACE, T_CONSTANT_ENCAPSED_STRING])) { + continue; + } + + $content = $token->getContent(); + if (!str_contains($content, '\\')) { + continue; + } + + // nowdoc syntax + if ($token->isGivenKind(T_ENCAPSED_AND_WHITESPACE) && '\'' === substr(rtrim($tokens[$index - 1]->getContent()), -1)) { + continue; + } + + $firstTwoCharacters = strtolower(substr($content, 0, 2)); + $isSingleQuotedString = $token->isGivenKind(T_CONSTANT_ENCAPSED_STRING) && ('\'' === $content[0] || 'b\'' === $firstTwoCharacters); + $isDoubleQuotedString = + ($token->isGivenKind(T_CONSTANT_ENCAPSED_STRING) && ('"' === $content[0] || 'b"' === $firstTwoCharacters)) + || ($token->isGivenKind(T_ENCAPSED_AND_WHITESPACE) && $doubleQuoteOpened); + + if ($isSingleQuotedString + ? 'ignore' === $this->configuration['single_quoted'] + : ($isDoubleQuotedString + ? 'ignore' === $this->configuration['double_quoted'] + : 'ignore' === $this->configuration['heredoc']) + ) { + continue; + } + + $escapeBackslashes = $isSingleQuotedString + ? 'escape' === $this->configuration['single_quoted'] + : ($isDoubleQuotedString + ? 'escape' === $this->configuration['double_quoted'] + : 'escape' === $this->configuration['heredoc']); + + $reservedRegex = $isSingleQuotedString + ? $singleQuotedReservedRegex + : ($isDoubleQuotedString + ? $doubleQuotedReservedRegex + : $heredocSyntaxReservedRegex); + + if ($escapeBackslashes) { + $regex = '/(?getId(), $newContent]); + } + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('single_quoted', 'Whether to escape backslashes in single-quoted strings.')) + ->setAllowedValues(['escape', 'unescape', 'ignore']) + ->setDefault('unescape') + ->getOption(), + (new FixerOptionBuilder('double_quoted', 'Whether to escape backslashes in double-quoted strings.')) + ->setAllowedValues(['escape', 'unescape', 'ignore']) + ->setDefault('escape') + ->getOption(), + (new FixerOptionBuilder('heredoc', 'Whether to escape backslashes in heredoc syntax.')) + ->setAllowedValues(['escape', 'unescape', 'ignore']) + ->setDefault('escape') + ->getOption(), + ]); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/StringLengthToEmptyFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/StringLengthToEmptyFixer.php new file mode 100644 index 00000000..361cc73c --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/StringLengthToEmptyFixer.php @@ -0,0 +1,322 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\StringNotation; + +use PhpCsFixer\AbstractFunctionReferenceFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class StringLengthToEmptyFixer extends AbstractFunctionReferenceFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'String tests for empty must be done against `\'\'`, not with `strlen`.', + [new CodeSample("findStrLengthCalls($tokens) as $candidate) { + [$functionNameIndex, $openParenthesisIndex, $closeParenthesisIndex] = $candidate; + $arguments = $argumentsAnalyzer->getArguments($tokens, $openParenthesisIndex, $closeParenthesisIndex); + + if (1 !== \count($arguments)) { + continue; // must be one argument + } + + // test for leading `\` before `strlen` call + + $nextIndex = $tokens->getNextMeaningfulToken($closeParenthesisIndex); + $previousIndex = $tokens->getPrevMeaningfulToken($functionNameIndex); + + if ($tokens[$previousIndex]->isGivenKind(T_NS_SEPARATOR)) { + $namespaceSeparatorIndex = $previousIndex; + $previousIndex = $tokens->getPrevMeaningfulToken($previousIndex); + } else { + $namespaceSeparatorIndex = null; + } + + // test for yoda vs non-yoda fix case + + if ($this->isOperatorOfInterest($tokens[$previousIndex])) { // test if valid yoda case to fix + $operatorIndex = $previousIndex; + $operandIndex = $tokens->getPrevMeaningfulToken($previousIndex); + + if (!$this->isOperandOfInterest($tokens[$operandIndex])) { // test if operand is `0` or `1` + continue; + } + + $replacement = $this->getReplacementYoda($tokens[$operatorIndex], $tokens[$operandIndex]); + + if (null === $replacement) { + continue; + } + + if ($this->isOfHigherPrecedence($tokens[$nextIndex])) { // is of higher precedence right; continue + continue; + } + + if ($this->isOfHigherPrecedence($tokens[$tokens->getPrevMeaningfulToken($operandIndex)])) { // is of higher precedence left; continue + continue; + } + } elseif ($this->isOperatorOfInterest($tokens[$nextIndex])) { // test if valid !yoda case to fix + $operatorIndex = $nextIndex; + $operandIndex = $tokens->getNextMeaningfulToken($nextIndex); + + if (!$this->isOperandOfInterest($tokens[$operandIndex])) { // test if operand is `0` or `1` + continue; + } + + $replacement = $this->getReplacementNotYoda($tokens[$operatorIndex], $tokens[$operandIndex]); + + if (null === $replacement) { + continue; + } + + if ($this->isOfHigherPrecedence($tokens[$tokens->getNextMeaningfulToken($operandIndex)])) { // is of higher precedence right; continue + continue; + } + + if ($this->isOfHigherPrecedence($tokens[$previousIndex])) { // is of higher precedence left; continue + continue; + } + } else { + continue; + } + + // prepare for fixing + + $keepParentheses = $this->keepParentheses($tokens, $openParenthesisIndex, $closeParenthesisIndex); + + if (T_IS_IDENTICAL === $replacement) { + $operandContent = '==='; + } else { // T_IS_NOT_IDENTICAL === $replacement + $operandContent = '!=='; + } + + // apply fixing + + $tokens[$operandIndex] = new Token([T_CONSTANT_ENCAPSED_STRING, "''"]); + $tokens[$operatorIndex] = new Token([$replacement, $operandContent]); + + if (!$keepParentheses) { + $tokens->clearTokenAndMergeSurroundingWhitespace($closeParenthesisIndex); + $tokens->clearTokenAndMergeSurroundingWhitespace($openParenthesisIndex); + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($functionNameIndex); + + if (null !== $namespaceSeparatorIndex) { + $tokens->clearTokenAndMergeSurroundingWhitespace($namespaceSeparatorIndex); + } + } + } + + private function getReplacementYoda(Token $operator, Token $operand): ?int + { + /* Yoda 0 + + 0 === strlen($b) | '' === $b + 0 !== strlen($b) | '' !== $b + 0 <= strlen($b) | X makes no sense, assume overridden + 0 >= strlen($b) | '' === $b + 0 < strlen($b) | '' !== $b + 0 > strlen($b) | X makes no sense, assume overridden + */ + + if ('0' === $operand->getContent()) { + if ($operator->isGivenKind([T_IS_IDENTICAL, T_IS_GREATER_OR_EQUAL])) { + return T_IS_IDENTICAL; + } + + if ($operator->isGivenKind(T_IS_NOT_IDENTICAL) || $operator->equals('<')) { + return T_IS_NOT_IDENTICAL; + } + + return null; + } + + /* Yoda 1 + + 1 === strlen($b) | X cannot simplify + 1 !== strlen($b) | X cannot simplify + 1 <= strlen($b) | '' !== $b + 1 >= strlen($b) | cannot simplify + 1 < strlen($b) | cannot simplify + 1 > strlen($b) | '' === $b + */ + + if ($operator->isGivenKind(T_IS_SMALLER_OR_EQUAL)) { + return T_IS_NOT_IDENTICAL; + } + + if ($operator->equals('>')) { + return T_IS_IDENTICAL; + } + + return null; + } + + private function getReplacementNotYoda(Token $operator, Token $operand): ?int + { + /* Not Yoda 0 + + strlen($b) === 0 | $b === '' + strlen($b) !== 0 | $b !== '' + strlen($b) <= 0 | $b === '' + strlen($b) >= 0 | X makes no sense, assume overridden + strlen($b) < 0 | X makes no sense, assume overridden + strlen($b) > 0 | $b !== '' + */ + + if ('0' === $operand->getContent()) { + if ($operator->isGivenKind([T_IS_IDENTICAL, T_IS_SMALLER_OR_EQUAL])) { + return T_IS_IDENTICAL; + } + + if ($operator->isGivenKind(T_IS_NOT_IDENTICAL) || $operator->equals('>')) { + return T_IS_NOT_IDENTICAL; + } + + return null; + } + + /* Not Yoda 1 + + strlen($b) === 1 | X cannot simplify + strlen($b) !== 1 | X cannot simplify + strlen($b) <= 1 | X cannot simplify + strlen($b) >= 1 | $b !== '' + strlen($b) < 1 | $b === '' + strlen($b) > 1 | X cannot simplify + */ + + if ($operator->isGivenKind(T_IS_GREATER_OR_EQUAL)) { + return T_IS_NOT_IDENTICAL; + } + + if ($operator->equals('<')) { + return T_IS_IDENTICAL; + } + + return null; + } + + private function isOperandOfInterest(Token $token): bool + { + if (!$token->isGivenKind(T_LNUMBER)) { + return false; + } + + $content = $token->getContent(); + + return '0' === $content || '1' === $content; + } + + private function isOperatorOfInterest(Token $token): bool + { + return + $token->isGivenKind([T_IS_IDENTICAL, T_IS_NOT_IDENTICAL, T_IS_SMALLER_OR_EQUAL, T_IS_GREATER_OR_EQUAL]) + || $token->equals('<') || $token->equals('>'); + } + + private function isOfHigherPrecedence(Token $token): bool + { + static $operatorsPerContent = [ + '!', + '%', + '*', + '+', + '-', + '.', + '/', + '~', + '?', + ]; + + return $token->isGivenKind([T_INSTANCEOF, T_POW, T_SL, T_SR]) || $token->equalsAny($operatorsPerContent); + } + + private function keepParentheses(Tokens $tokens, int $openParenthesisIndex, int $closeParenthesisIndex): bool + { + $i = $tokens->getNextMeaningfulToken($openParenthesisIndex); + + if ($tokens[$i]->isCast()) { + $i = $tokens->getNextMeaningfulToken($i); + } + + for (; $i < $closeParenthesisIndex; ++$i) { + $token = $tokens[$i]; + + if ($token->isGivenKind([T_VARIABLE, T_STRING]) || $token->isObjectOperator() || $token->isWhitespace() || $token->isComment()) { + continue; + } + + $blockType = Tokens::detectBlockType($token); + + if (null !== $blockType && $blockType['isStart']) { + $i = $tokens->findBlockEnd($blockType['type'], $i); + + continue; + } + + return true; + } + + return false; + } + + private function findStrLengthCalls(Tokens $tokens): \Generator + { + $candidates = []; + $count = \count($tokens); + + for ($i = 0; $i < $count; ++$i) { + $candidate = $this->find('strlen', $tokens, $i, $count); + + if (null === $candidate) { + break; + } + + $i = $candidate[1]; // proceed to openParenthesisIndex + $candidates[] = $candidate; + } + + foreach (array_reverse($candidates) as $candidate) { + yield $candidate; + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/StringLineEndingFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/StringLineEndingFixer.php new file mode 100644 index 00000000..aae69262 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/StringLineEndingFixer.php @@ -0,0 +1,76 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\StringNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Fixes the line endings in multi-line strings. + * + * @author Ilija Tovilo + */ +final class StringLineEndingFixer extends AbstractFixer implements WhitespacesAwareFixerInterface +{ + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound([T_CONSTANT_ENCAPSED_STRING, T_ENCAPSED_AND_WHITESPACE, T_INLINE_HTML]); + } + + public function isRisky(): bool + { + return true; + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'All multi-line strings must use correct line ending.', + [ + new CodeSample( + "whitespacesConfig->getLineEnding(); + + foreach ($tokens as $tokenIndex => $token) { + if (!$token->isGivenKind([T_CONSTANT_ENCAPSED_STRING, T_ENCAPSED_AND_WHITESPACE, T_INLINE_HTML])) { + continue; + } + + $tokens[$tokenIndex] = new Token([ + $token->getId(), + Preg::replace( + '#\R#u', + $ending, + $token->getContent() + ), + ]); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/ArrayIndentationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/ArrayIndentationFixer.php new file mode 100644 index 00000000..80ef9ef9 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/ArrayIndentationFixer.php @@ -0,0 +1,197 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Whitespace; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\Indentation; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class ArrayIndentationFixer extends AbstractFixer implements WhitespacesAwareFixerInterface +{ + use Indentation; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Each element of an array must be indented exactly once.', + [ + new CodeSample(" [\n 'baz' => true,\n ],\n];\n"), + ] + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound([T_ARRAY, T_LIST, CT::T_ARRAY_SQUARE_BRACE_OPEN, CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN]); + } + + /** + * {@inheritdoc} + * + * Must run before AlignMultilineCommentFixer, BinaryOperatorSpacesFixer. + * Must run after MethodArgumentSpaceFixer. + */ + public function getPriority(): int + { + return 29; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $lastIndent = ''; + $scopes = []; + $previousLineInitialIndent = ''; + $previousLineNewIndent = ''; + + foreach ($tokens as $index => $token) { + $currentScope = [] !== $scopes ? \count($scopes) - 1 : null; + + if ($token->isComment()) { + continue; + } + + if ( + $token->isGivenKind([CT::T_ARRAY_SQUARE_BRACE_OPEN, CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN]) + || ($token->equals('(') && $tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind([T_ARRAY, T_LIST])) + ) { + $blockType = Tokens::detectBlockType($token); + $endIndex = $tokens->findBlockEnd($blockType['type'], $index); + + $scopes[] = [ + 'type' => 'array', + 'end_index' => $endIndex, + 'initial_indent' => $lastIndent, + ]; + + continue; + } + + if ($this->isNewLineToken($tokens, $index)) { + $lastIndent = $this->extractIndent($this->computeNewLineContent($tokens, $index)); + } + + if (null === $currentScope) { + continue; + } + + if ($token->isWhitespace()) { + if (!Preg::match('/\R/', $token->getContent())) { + continue; + } + + if ('array' === $scopes[$currentScope]['type']) { + $indent = false; + + for ($searchEndIndex = $index + 1; $searchEndIndex < $scopes[$currentScope]['end_index']; ++$searchEndIndex) { + $searchEndToken = $tokens[$searchEndIndex]; + + if ( + (!$searchEndToken->isWhitespace() && !$searchEndToken->isComment()) + || ($searchEndToken->isWhitespace() && Preg::match('/\R/', $searchEndToken->getContent())) + ) { + $indent = true; + + break; + } + } + + $content = Preg::replace( + '/(\R+)\h*$/', + '$1'.$scopes[$currentScope]['initial_indent'].($indent ? $this->whitespacesConfig->getIndent() : ''), + $token->getContent() + ); + + $previousLineInitialIndent = $this->extractIndent($token->getContent()); + $previousLineNewIndent = $this->extractIndent($content); + } else { + $content = Preg::replace( + '/(\R)'.preg_quote($scopes[$currentScope]['initial_indent'], '/').'(\h*)$/', + '$1'.$scopes[$currentScope]['new_indent'].'$2', + $token->getContent() + ); + } + + $tokens[$index] = new Token([T_WHITESPACE, $content]); + $lastIndent = $this->extractIndent($content); + + continue; + } + + if ($index === $scopes[$currentScope]['end_index']) { + while ([] !== $scopes && $index === $scopes[$currentScope]['end_index']) { + array_pop($scopes); + --$currentScope; + } + + continue; + } + + if ($token->equals(',')) { + continue; + } + + if ('expression' !== $scopes[$currentScope]['type']) { + $endIndex = $this->findExpressionEndIndex($tokens, $index, $scopes[$currentScope]['end_index']); + + if ($endIndex === $index) { + continue; + } + + $scopes[] = [ + 'type' => 'expression', + 'end_index' => $endIndex, + 'initial_indent' => $previousLineInitialIndent, + 'new_indent' => $previousLineNewIndent, + ]; + } + } + } + + private function findExpressionEndIndex(Tokens $tokens, int $index, int $parentScopeEndIndex): int + { + $endIndex = null; + + for ($searchEndIndex = $index + 1; $searchEndIndex < $parentScopeEndIndex; ++$searchEndIndex) { + $searchEndToken = $tokens[$searchEndIndex]; + + if ($searchEndToken->equalsAny(['(', '{']) + || $searchEndToken->isGivenKind([CT::T_ARRAY_SQUARE_BRACE_OPEN, CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN]) + ) { + $type = Tokens::detectBlockType($searchEndToken); + $searchEndIndex = $tokens->findBlockEnd( + $type['type'], + $searchEndIndex + ); + + continue; + } + + if ($searchEndToken->equals(',')) { + $endIndex = $tokens->getPrevMeaningfulToken($searchEndIndex); + + break; + } + } + + return $endIndex ?? $tokens->getPrevMeaningfulToken($parentScopeEndIndex); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/BlankLineBeforeStatementFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/BlankLineBeforeStatementFixer.php new file mode 100644 index 00000000..8434490f --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/BlankLineBeforeStatementFixer.php @@ -0,0 +1,368 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Whitespace; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Dariusz Rumiński + * @author Andreas Möller + */ +final class BlankLineBeforeStatementFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface +{ + /** + * @var array + */ + private static array $tokenMap = [ + 'break' => T_BREAK, + 'case' => T_CASE, + 'continue' => T_CONTINUE, + 'declare' => T_DECLARE, + 'default' => T_DEFAULT, + 'do' => T_DO, + 'exit' => T_EXIT, + 'for' => T_FOR, + 'foreach' => T_FOREACH, + 'goto' => T_GOTO, + 'if' => T_IF, + 'include' => T_INCLUDE, + 'include_once' => T_INCLUDE_ONCE, + 'phpdoc' => T_DOC_COMMENT, + 'require' => T_REQUIRE, + 'require_once' => T_REQUIRE_ONCE, + 'return' => T_RETURN, + 'switch' => T_SWITCH, + 'throw' => T_THROW, + 'try' => T_TRY, + 'while' => T_WHILE, + 'yield' => T_YIELD, + 'yield_from' => T_YIELD_FROM, + ]; + + /** + * @var list + */ + private array $fixTokenMap = []; + + public function configure(array $configuration): void + { + parent::configure($configuration); + + $this->fixTokenMap = []; + + foreach ($this->configuration['statements'] as $key) { + $this->fixTokenMap[$key] = self::$tokenMap[$key]; + } + + $this->fixTokenMap = array_values($this->fixTokenMap); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'An empty line feed must precede any configured statement.', + [ + new CodeSample( + 'process(); + break; + case 44: + break; +} +', + [ + 'statements' => ['break'], + ] + ), + new CodeSample( + 'isTired()) { + $bar->sleep(); + continue; + } +} +', + [ + 'statements' => ['continue'], + ] + ), + new CodeSample( + ' 0); +', + [ + 'statements' => ['do'], + ] + ), + new CodeSample( + ' ['exit'], + ] + ), + new CodeSample( + ' ['goto'], + ] + ), + new CodeSample( + ' ['if'], + ] + ), + new CodeSample( + ' ['return'], + ] + ), + new CodeSample( + ' ['switch'], + ] + ), + new CodeSample( + 'bar(); + throw new \UnexpectedValueException("A cannot be null."); +} +', + [ + 'statements' => ['throw'], + ] + ), + new CodeSample( + 'bar(); +} catch (\Exception $exception) { + $a = -1; +} +', + [ + 'statements' => ['try'], + ] + ), + new CodeSample( + ' ['yield'], + ] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run after NoExtraBlankLinesFixer, NoUselessElseFixer, NoUselessReturnFixer, ReturnAssignmentFixer, YieldFromArrayToYieldsFixer. + */ + public function getPriority(): int + { + return -21; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound($this->fixTokenMap); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $analyzer = new TokensAnalyzer($tokens); + + for ($index = $tokens->count() - 1; $index > 0; --$index) { + $token = $tokens[$index]; + + if (!$token->isGivenKind($this->fixTokenMap)) { + continue; + } + + if ($token->isGivenKind(T_WHILE) && $analyzer->isWhilePartOfDoWhile($index)) { + continue; + } + + if ($token->isGivenKind(T_CASE) && $analyzer->isEnumCase($index)) { + continue; + } + + $insertBlankLineIndex = $this->getInsertBlankLineIndex($tokens, $index); + $prevNonWhitespace = $tokens->getPrevNonWhitespace($insertBlankLineIndex); + + if ($this->shouldAddBlankLine($tokens, $prevNonWhitespace)) { + $this->insertBlankLine($tokens, $insertBlankLineIndex); + } + + $index = $prevNonWhitespace; + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('statements', 'List of statements which must be preceded by an empty line.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([new AllowedValueSubset(array_keys(self::$tokenMap))]) + ->setDefault([ + 'break', + 'continue', + 'declare', + 'return', + 'throw', + 'try', + ]) + ->getOption(), + ]); + } + + private function getInsertBlankLineIndex(Tokens $tokens, int $index): int + { + while ($index > 0) { + if ($tokens[$index - 1]->isWhitespace() && substr_count($tokens[$index - 1]->getContent(), "\n") > 1) { + break; + } + + $prevIndex = $tokens->getPrevNonWhitespace($index); + + if (!$tokens[$prevIndex]->isComment()) { + break; + } + + if (!$tokens[$prevIndex - 1]->isWhitespace()) { + break; + } + + if (1 !== substr_count($tokens[$prevIndex - 1]->getContent(), "\n")) { + break; + } + + $index = $prevIndex; + } + + return $index; + } + + private function shouldAddBlankLine(Tokens $tokens, int $prevNonWhitespace): bool + { + $prevNonWhitespaceToken = $tokens[$prevNonWhitespace]; + + if ($prevNonWhitespaceToken->isComment()) { + for ($j = $prevNonWhitespace - 1; $j >= 0; --$j) { + if (str_contains($tokens[$j]->getContent(), "\n")) { + return false; + } + + if ($tokens[$j]->isWhitespace() || $tokens[$j]->isComment()) { + continue; + } + + return $tokens[$j]->equalsAny([';', '}']); + } + } + + return $prevNonWhitespaceToken->equalsAny([';', '}']); + } + + private function insertBlankLine(Tokens $tokens, int $index): void + { + $prevIndex = $index - 1; + $prevToken = $tokens[$prevIndex]; + $lineEnding = $this->whitespacesConfig->getLineEnding(); + + if ($prevToken->isWhitespace()) { + $newlinesCount = substr_count($prevToken->getContent(), "\n"); + + if (0 === $newlinesCount) { + $tokens[$prevIndex] = new Token([T_WHITESPACE, rtrim($prevToken->getContent(), " \t").$lineEnding.$lineEnding]); + } elseif (1 === $newlinesCount) { + $tokens[$prevIndex] = new Token([T_WHITESPACE, $lineEnding.$prevToken->getContent()]); + } + } else { + $tokens->insertAt($index, new Token([T_WHITESPACE, $lineEnding.$lineEnding])); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/BlankLineBetweenImportGroupsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/BlankLineBetweenImportGroupsFixer.php new file mode 100644 index 00000000..1976aefb --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/BlankLineBetweenImportGroupsFixer.php @@ -0,0 +1,180 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Whitespace; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\WhitespacesAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Sander Verkuil + */ +final class BlankLineBetweenImportGroupsFixer extends AbstractFixer implements WhitespacesAwareFixerInterface +{ + private const IMPORT_TYPE_CLASS = 'class'; + + private const IMPORT_TYPE_CONST = 'const'; + + private const IMPORT_TYPE_FUNCTION = 'function'; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Putting blank lines between `use` statement groups.', + [ + new CodeSample( + 'isTokenKindFound(T_USE); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + $namespacesImports = $tokensAnalyzer->getImportUseIndexes(true); + + foreach (array_reverse($namespacesImports) as $uses) { + $this->walkOverUses($tokens, $uses); + } + } + + /** + * @param int[] $uses + */ + private function walkOverUses(Tokens $tokens, array $uses): void + { + $usesCount = \count($uses); + + if ($usesCount < 2) { + return; // nothing to fix + } + + $previousType = null; + + for ($i = $usesCount - 1; $i >= 0; --$i) { + $index = $uses[$i]; + $startIndex = $tokens->getNextMeaningfulToken($index + 1); + $endIndex = $tokens->getNextTokenOfKind($startIndex, [';', [T_CLOSE_TAG]]); + + if ($tokens[$startIndex]->isGivenKind(CT::T_CONST_IMPORT)) { + $type = self::IMPORT_TYPE_CONST; + } elseif ($tokens[$startIndex]->isGivenKind(CT::T_FUNCTION_IMPORT)) { + $type = self::IMPORT_TYPE_FUNCTION; + } else { + $type = self::IMPORT_TYPE_CLASS; + } + + if (null !== $previousType && $type !== $previousType) { + $this->ensureLine($tokens, $endIndex + 1); + } + + $previousType = $type; + } + } + + private function ensureLine(Tokens $tokens, int $index): void + { + static $lineEnding; + + if (null === $lineEnding) { + $lineEnding = $this->whitespacesConfig->getLineEnding(); + $lineEnding .= $lineEnding; + } + + $index = $this->getInsertIndex($tokens, $index); + $indent = WhitespacesAnalyzer::detectIndent($tokens, $index); + + $tokens->ensureWhitespaceAtIndex($index, 1, $lineEnding.$indent); + } + + private function getInsertIndex(Tokens $tokens, int $index): int + { + $tokensCount = \count($tokens); + + for (; $index < $tokensCount - 1; ++$index) { + if (!$tokens[$index]->isWhitespace() && !$tokens[$index]->isComment()) { + return $index - 1; + } + + $content = $tokens[$index]->getContent(); + + if (str_contains($content, "\n")) { + return $index; + } + } + + return $index; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/CompactNullableTypeDeclarationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/CompactNullableTypeDeclarationFixer.php new file mode 100644 index 00000000..e1af647e --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/CompactNullableTypeDeclarationFixer.php @@ -0,0 +1,72 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Whitespace; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Jack Cherng + */ +final class CompactNullableTypeDeclarationFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Remove extra spaces in a nullable type declaration.', + [ + new CodeSample( + "isTokenKindFound(CT::T_NULLABLE_TYPE); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + static $typehintKinds = [ + CT::T_ARRAY_TYPEHINT, + T_CALLABLE, + T_NS_SEPARATOR, + T_STATIC, + T_STRING, + ]; + + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + if (!$tokens[$index]->isGivenKind(CT::T_NULLABLE_TYPE)) { + continue; + } + + // remove whitespaces only if there are only whitespaces + // between '?' and the variable type + if ( + $tokens[$index + 1]->isWhitespace() + && $tokens[$index + 2]->isGivenKind($typehintKinds) + ) { + $tokens->removeTrailingWhitespace($index); + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/CompactNullableTypehintFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/CompactNullableTypehintFixer.php new file mode 100644 index 00000000..89c4ec65 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/CompactNullableTypehintFixer.php @@ -0,0 +1,63 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Whitespace; + +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; + +/** + * @author Jack Cherng + * + * @deprecated + */ +final class CompactNullableTypehintFixer extends AbstractProxyFixer implements DeprecatedFixerInterface +{ + private CompactNullableTypeDeclarationFixer $compactNullableTypeDeclarationFixer; + + public function __construct() + { + $this->compactNullableTypeDeclarationFixer = new CompactNullableTypeDeclarationFixer(); + + parent::__construct(); + } + + public function getDefinition(): FixerDefinitionInterface + { + $fixerDefinition = $this->compactNullableTypeDeclarationFixer->getDefinition(); + + return new FixerDefinition( + 'Remove extra spaces in a nullable typehint.', + $fixerDefinition->getCodeSamples(), + $fixerDefinition->getDescription(), + $fixerDefinition->getRiskyDescription(), + ); + } + + public function getSuccessorsNames(): array + { + return [ + $this->compactNullableTypeDeclarationFixer->getName(), + ]; + } + + protected function createProxyFixers(): array + { + return [ + $this->compactNullableTypeDeclarationFixer, + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/HeredocIndentationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/HeredocIndentationFixer.php new file mode 100644 index 00000000..e40b64de --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/HeredocIndentationFixer.php @@ -0,0 +1,167 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Whitespace; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\WhitespacesAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Gregor Harlan + */ +final class HeredocIndentationFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Heredoc/nowdoc content must be properly indented.', + [ + new CodeSample( + <<<'SAMPLE' + 'same_as_start'] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run after BracesFixer, MultilineStringToHeredocFixer, StatementIndentationFixer. + */ + public function getPriority(): int + { + return -26; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_START_HEREDOC); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('indentation', 'Whether the indentation should be the same as in the start token line or one level more.')) + ->setAllowedValues(['start_plus_one', 'same_as_start']) + ->setDefault('start_plus_one') + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = \count($tokens) - 1; 0 <= $index; --$index) { + if (!$tokens[$index]->isGivenKind(T_END_HEREDOC)) { + continue; + } + + $end = $index; + $index = $tokens->getPrevTokenOfKind($index, [[T_START_HEREDOC]]); + + $this->fixIndentation($tokens, $index, $end); + } + } + + private function fixIndentation(Tokens $tokens, int $start, int $end): void + { + $indent = WhitespacesAnalyzer::detectIndent($tokens, $start); + + if ('start_plus_one' === $this->configuration['indentation']) { + $indent .= $this->whitespacesConfig->getIndent(); + } + + Preg::match('/^\h*/', $tokens[$end]->getContent(), $matches); + $currentIndent = $matches[0]; + $currentIndentLength = \strlen($currentIndent); + + $content = $indent.substr($tokens[$end]->getContent(), $currentIndentLength); + $tokens[$end] = new Token([T_END_HEREDOC, $content]); + + if ($end === $start + 1) { + return; + } + + $index = $end - 1; + + for ($last = true; $index > $start; --$index, $last = false) { + if (!$tokens[$index]->isGivenKind([T_ENCAPSED_AND_WHITESPACE, T_WHITESPACE])) { + continue; + } + + $content = $tokens[$index]->getContent(); + + if ('' !== $currentIndent) { + $content = Preg::replace('/(?<=\v)(?!'.$currentIndent.')\h+/', '', $content); + } + + $regexEnd = $last && !$currentIndent ? '(?!\v|$)' : '(?!\v)'; + $content = Preg::replace('/(?<=\v)'.$currentIndent.$regexEnd.'/', $indent, $content); + + $tokens[$index] = new Token([$tokens[$index]->getId(), $content]); + } + + ++$index; + + if (!$tokens[$index]->isGivenKind(T_ENCAPSED_AND_WHITESPACE)) { + $tokens->insertAt($index, new Token([T_ENCAPSED_AND_WHITESPACE, $indent])); + + return; + } + + $content = $tokens[$index]->getContent(); + + if (!\in_array($content[0], ["\r", "\n"], true) && (!$currentIndent || str_starts_with($content, $currentIndent))) { + $content = $indent.substr($content, $currentIndentLength); + } elseif ($currentIndent) { + $content = Preg::replace('/^(?!'.$currentIndent.')\h+/', '', $content); + } + + $tokens[$index] = new Token([T_ENCAPSED_AND_WHITESPACE, $content]); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/IndentationTypeFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/IndentationTypeFixer.php new file mode 100644 index 00000000..d88c74ba --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/IndentationTypeFixer.php @@ -0,0 +1,142 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Whitespace; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Fixer for rules defined in PSR2 ¶2.4. + * + * @author Dariusz Rumiński + */ +final class IndentationTypeFixer extends AbstractFixer implements WhitespacesAwareFixerInterface +{ + /** + * @var string + */ + private $indent; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Code MUST use configured indentation type.', + [ + new CodeSample("isAnyTokenKindsFound([T_COMMENT, T_DOC_COMMENT, T_WHITESPACE]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $this->indent = $this->whitespacesConfig->getIndent(); + + foreach ($tokens as $index => $token) { + if ($token->isComment()) { + $tokens[$index] = $this->fixIndentInComment($tokens, $index); + + continue; + } + + if ($token->isWhitespace()) { + $tokens[$index] = $this->fixIndentToken($tokens, $index); + + continue; + } + } + } + + private function fixIndentInComment(Tokens $tokens, int $index): Token + { + $content = Preg::replace('/^(?:(?getContent(), -1, $count); + + // Also check for more tabs. + while (0 !== $count) { + $content = Preg::replace('/^(\ +)?\t/m', '\1 ', $content, -1, $count); + } + + $indent = $this->indent; + + // change indent to expected one + $content = Preg::replaceCallback('/^(?: )+/m', fn (array $matches): string => $this->getExpectedIndent($matches[0], $indent), $content); + + return new Token([$tokens[$index]->getId(), $content]); + } + + private function fixIndentToken(Tokens $tokens, int $index): Token + { + $content = $tokens[$index]->getContent(); + $previousTokenHasTrailingLinebreak = false; + + // @TODO this can be removed when we have a transformer for "T_OPEN_TAG" to "T_OPEN_TAG + T_WHITESPACE" + if (str_contains($tokens[$index - 1]->getContent(), "\n")) { + $content = "\n".$content; + $previousTokenHasTrailingLinebreak = true; + } + + $indent = $this->indent; + $newContent = Preg::replaceCallback( + '/(\R)(\h+)/', // find indent + function (array $matches) use ($indent): string { + // normalize mixed indent + $content = Preg::replace('/(?:(?getExpectedIndent($content, $indent); + }, + $content + ); + + if ($previousTokenHasTrailingLinebreak) { + $newContent = substr($newContent, 1); + } + + return new Token([T_WHITESPACE, $newContent]); + } + + /** + * @return string mixed + */ + private function getExpectedIndent(string $content, string $indent): string + { + if ("\t" === $indent) { + $content = str_replace(' ', $indent, $content); + } + + return $content; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/LineEndingFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/LineEndingFixer.php new file mode 100644 index 00000000..adc2016f --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/LineEndingFixer.php @@ -0,0 +1,85 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Whitespace; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Fixer for rules defined in PSR2 ¶2.2. + * + * @author Fabien Potencier + * @author Dariusz Rumiński + */ +final class LineEndingFixer extends AbstractFixer implements WhitespacesAwareFixerInterface +{ + public function isCandidate(Tokens $tokens): bool + { + return true; + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'All PHP files must use same line ending.', + [ + new CodeSample( + "whitespacesConfig->getLineEnding(); + + for ($index = 0, $count = \count($tokens); $index < $count; ++$index) { + $token = $tokens[$index]; + + if ($token->isGivenKind(T_ENCAPSED_AND_WHITESPACE)) { + if ($tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind(T_END_HEREDOC)) { + $tokens[$index] = new Token([ + $token->getId(), + Preg::replace( + '#\R#', + $ending, + $token->getContent() + ), + ]); + } + + continue; + } + + if ($token->isGivenKind([T_CLOSE_TAG, T_COMMENT, T_DOC_COMMENT, T_OPEN_TAG, T_START_HEREDOC, T_WHITESPACE])) { + $tokens[$index] = new Token([ + $token->getId(), + Preg::replace( + '#\R#', + $ending, + $token->getContent() + ), + ]); + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/MethodChainingIndentationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/MethodChainingIndentationFixer.php new file mode 100644 index 00000000..dc7fff6e --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/MethodChainingIndentationFixer.php @@ -0,0 +1,221 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Whitespace; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Vladimir Boliev + */ +final class MethodChainingIndentationFixer extends AbstractFixer implements WhitespacesAwareFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Method chaining MUST be properly indented. Method chaining with different levels of indentation is not supported.', + [new CodeSample("setEmail('voff.web@gmail.com')\n ->setPassword('233434');\n")] + ); + } + + /** + * {@inheritdoc} + * + * Must run after NoSpaceAroundDoubleColonFixer. + */ + public function getPriority(): int + { + return 0; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound(Token::getObjectOperatorKinds()); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $lineEnding = $this->whitespacesConfig->getLineEnding(); + + for ($index = 1, $count = \count($tokens); $index < $count; ++$index) { + if (!$tokens[$index]->isObjectOperator()) { + continue; + } + + $endParenthesisIndex = $tokens->getNextTokenOfKind($index, ['(', ';', ',', [T_CLOSE_TAG]]); + + if (null === $endParenthesisIndex || !$tokens[$endParenthesisIndex]->equals('(')) { + continue; + } + + if ($this->canBeMovedToNextLine($index, $tokens)) { + $newline = new Token([T_WHITESPACE, $lineEnding]); + + if ($tokens[$index - 1]->isWhitespace()) { + $tokens[$index - 1] = $newline; + } else { + $tokens->insertAt($index, $newline); + ++$index; + ++$endParenthesisIndex; + } + } + + $currentIndent = $this->getIndentAt($tokens, $index - 1); + + if (null === $currentIndent) { + continue; + } + + $expectedIndent = $this->getExpectedIndentAt($tokens, $index); + + if ($currentIndent !== $expectedIndent) { + $tokens[$index - 1] = new Token([T_WHITESPACE, $lineEnding.$expectedIndent]); + } + + $endParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $endParenthesisIndex); + + for ($searchIndex = $index + 1; $searchIndex < $endParenthesisIndex; ++$searchIndex) { + $searchToken = $tokens[$searchIndex]; + + if (!$searchToken->isWhitespace()) { + continue; + } + + $content = $searchToken->getContent(); + + if (!Preg::match('/\R/', $content)) { + continue; + } + + $content = Preg::replace( + '/(\R)'.$currentIndent.'(\h*)$/D', + '$1'.$expectedIndent.'$2', + $content + ); + + $tokens[$searchIndex] = new Token([$searchToken->getId(), $content]); + } + } + } + + /** + * @param int $index index of the first token on the line to indent + */ + private function getExpectedIndentAt(Tokens $tokens, int $index): string + { + $index = $tokens->getPrevMeaningfulToken($index); + $indent = $this->whitespacesConfig->getIndent(); + + for ($i = $index; $i >= 0; --$i) { + if ($tokens[$i]->equals(')')) { + $i = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $i); + } + + $currentIndent = $this->getIndentAt($tokens, $i); + if (null === $currentIndent) { + continue; + } + + if ($this->currentLineRequiresExtraIndentLevel($tokens, $i, $index)) { + return $currentIndent.$indent; + } + + return $currentIndent; + } + + return $indent; + } + + /** + * @param int $index position of the object operator token ("->" or "?->") + */ + private function canBeMovedToNextLine(int $index, Tokens $tokens): bool + { + $prevMeaningful = $tokens->getPrevMeaningfulToken($index); + $hasCommentBefore = false; + + for ($i = $index - 1; $i > $prevMeaningful; --$i) { + if ($tokens[$i]->isComment()) { + $hasCommentBefore = true; + + continue; + } + + if ($tokens[$i]->isWhitespace() && Preg::match('/\R/', $tokens[$i]->getContent())) { + return $hasCommentBefore; + } + } + + return false; + } + + /** + * @param int $index index of the indentation token + */ + private function getIndentAt(Tokens $tokens, int $index): ?string + { + if (Preg::match('/\R{1}(\h*)$/', $this->getIndentContentAt($tokens, $index), $matches)) { + return $matches[1]; + } + + return null; + } + + private function getIndentContentAt(Tokens $tokens, int $index): string + { + if (!$tokens[$index]->isGivenKind([T_WHITESPACE, T_INLINE_HTML])) { + return ''; + } + + $content = $tokens[$index]->getContent(); + + if ($tokens[$index]->isWhitespace() && $tokens[$index - 1]->isGivenKind(T_OPEN_TAG)) { + $content = $tokens[$index - 1]->getContent().$content; + } + + if (Preg::match('/\R/', $content)) { + return $content; + } + + return ''; + } + + /** + * @param int $start index of first meaningful token on previous line + * @param int $end index of last token on previous line + */ + private function currentLineRequiresExtraIndentLevel(Tokens $tokens, int $start, int $end): bool + { + $firstMeaningful = $tokens->getNextMeaningfulToken($start); + + if ($tokens[$firstMeaningful]->isObjectOperator()) { + $thirdMeaningful = $tokens->getNextMeaningfulToken($tokens->getNextMeaningfulToken($firstMeaningful)); + + return + $tokens[$thirdMeaningful]->equals('(') + && $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $thirdMeaningful) > $end; + } + + return + !$tokens[$end]->equals(')') + || $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $end) >= $start; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoExtraBlankLinesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoExtraBlankLinesFixer.php new file mode 100644 index 00000000..f01db92b --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoExtraBlankLinesFixer.php @@ -0,0 +1,470 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Whitespace; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\SwitchAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; +use PhpCsFixer\Utils; + +/** + * @author Dariusz Rumiński + */ +final class NoExtraBlankLinesFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface +{ + /** + * @var string[] + */ + private static array $availableTokens = [ + 'attribute', + 'break', + 'case', + 'continue', + 'curly_brace_block', + 'default', + 'extra', + 'parenthesis_brace_block', + 'return', + 'square_brace_block', + 'switch', + 'throw', + 'use', + 'use_trait', + ]; + + /** + * @var array key is token id, value is name of callback + */ + private array $tokenKindCallbackMap; + + /** + * @var array token prototype, value is name of callback + */ + private array $tokenEqualsMap; + + private Tokens $tokens; + + private TokensAnalyzer $tokensAnalyzer; + + public function configure(array $configuration): void + { + if (isset($configuration['tokens']) && \in_array('use_trait', $configuration['tokens'], true)) { + Utils::triggerDeprecation(new \RuntimeException('Option "tokens: use_trait" used in `no_extra_blank_lines` rule is deprecated, use the rule `class_attributes_separation` with `elements: trait_import` instead.')); + } + + parent::configure($configuration); + + $tokensConfiguration = $this->configuration['tokens']; + + $this->tokenEqualsMap = []; + + if (\in_array('curly_brace_block', $tokensConfiguration, true)) { + $this->tokenEqualsMap['{'] = 'fixStructureOpenCloseIfMultiLine'; // i.e. not: CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN + } + + if (\in_array('parenthesis_brace_block', $tokensConfiguration, true)) { + $this->tokenEqualsMap['('] = 'fixStructureOpenCloseIfMultiLine'; // i.e. not: CT::T_BRACE_CLASS_INSTANTIATION_OPEN + } + + static $configMap = [ + 'attribute' => [CT::T_ATTRIBUTE_CLOSE, 'fixAfterToken'], + 'break' => [T_BREAK, 'fixAfterToken'], + 'case' => [T_CASE, 'fixAfterCaseToken'], + 'continue' => [T_CONTINUE, 'fixAfterToken'], + 'default' => [T_DEFAULT, 'fixAfterToken'], + 'extra' => [T_WHITESPACE, 'removeMultipleBlankLines'], + 'return' => [T_RETURN, 'fixAfterToken'], + 'square_brace_block' => [CT::T_ARRAY_SQUARE_BRACE_OPEN, 'fixStructureOpenCloseIfMultiLine'], + 'switch' => [T_SWITCH, 'fixAfterToken'], + 'throw' => [T_THROW, 'fixAfterThrowToken'], + 'use' => [T_USE, 'removeBetweenUse'], + 'use_trait' => [CT::T_USE_TRAIT, 'removeBetweenUse'], + ]; + + $this->tokenKindCallbackMap = []; + + foreach ($tokensConfiguration as $config) { + if (isset($configMap[$config])) { + $this->tokenKindCallbackMap[$configMap[$config][0]] = $configMap[$config][1]; + } + } + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Removes extra blank lines and/or blank lines following configuration.', + [ + new CodeSample( + ' ['break']] + ), + new CodeSample( + ' ['continue']] + ), + new CodeSample( + ' ['curly_brace_block']] + ), + new CodeSample( + ' ['extra']] + ), + new CodeSample( + ' ['parenthesis_brace_block']] + ), + new CodeSample( + ' ['return']] + ), + new CodeSample( + ' ['square_brace_block']] + ), + new CodeSample( + ' ['throw']] + ), + new CodeSample( + ' ['use']] + ), + new CodeSample( + ' ['switch', 'case', 'default']] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before BlankLineBeforeStatementFixer. + * Must run after ClassAttributesSeparationFixer, CombineConsecutiveUnsetsFixer, EmptyLoopBodyFixer, EmptyLoopConditionFixer, FunctionToConstantFixer, LongToShorthandOperatorFixer, ModernizeStrposFixer, NoEmptyCommentFixer, NoEmptyPhpdocFixer, NoEmptyStatementFixer, NoUnusedImportsFixer, NoUselessElseFixer, NoUselessReturnFixer, NoUselessSprintfFixer, PhpdocReadonlyClassCommentToKeywordFixer, StringLengthToEmptyFixer, YieldFromArrayToYieldsFixer. + */ + public function getPriority(): int + { + return -20; + } + + public function isCandidate(Tokens $tokens): bool + { + return true; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $this->tokens = $tokens; + $this->tokensAnalyzer = new TokensAnalyzer($this->tokens); + + for ($index = $tokens->getSize() - 1; $index > 0; --$index) { + $this->fixByToken($tokens[$index], $index); + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('tokens', 'List of tokens to fix.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([new AllowedValueSubset(self::$availableTokens)]) + ->setDefault(['extra']) + ->getOption(), + ]); + } + + private function fixByToken(Token $token, int $index): void + { + foreach ($this->tokenKindCallbackMap as $kind => $callback) { + if (!$token->isGivenKind($kind)) { + continue; + } + + $this->{$callback}($index); + + return; + } + + foreach ($this->tokenEqualsMap as $equals => $callback) { + if (!$token->equals($equals)) { + continue; + } + + $this->{$callback}($index); + + return; + } + } + + private function removeBetweenUse(int $index): void + { + $next = $this->tokens->getNextTokenOfKind($index, [';', [T_CLOSE_TAG]]); + + if (null === $next || $this->tokens[$next]->isGivenKind(T_CLOSE_TAG)) { + return; + } + + $nextUseCandidate = $this->tokens->getNextMeaningfulToken($next); + + if (null === $nextUseCandidate || !$this->tokens[$nextUseCandidate]->isGivenKind($this->tokens[$index]->getId()) || !$this->containsLinebreak($index, $nextUseCandidate)) { + return; + } + + $this->removeEmptyLinesAfterLineWithTokenAt($next); + } + + private function removeMultipleBlankLines(int $index): void + { + $expected = $this->tokens[$index - 1]->isGivenKind(T_OPEN_TAG) && Preg::match('/\R$/', $this->tokens[$index - 1]->getContent()) ? 1 : 2; + + $parts = Preg::split('/(.*\R)/', $this->tokens[$index]->getContent(), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + $count = \count($parts); + + if ($count > $expected) { + $this->tokens[$index] = new Token([T_WHITESPACE, implode('', \array_slice($parts, 0, $expected)).rtrim($parts[$count - 1], "\r\n")]); + } + } + + private function fixAfterToken(int $index): void + { + for ($i = $index - 1; $i > 0; --$i) { + if ($this->tokens[$i]->isGivenKind(T_FUNCTION) && $this->tokensAnalyzer->isLambda($i)) { + return; + } + + if ($this->tokens[$i]->isGivenKind(T_CLASS) && $this->tokensAnalyzer->isAnonymousClass($i)) { + return; + } + + if ($this->tokens[$i]->isWhitespace() && str_contains($this->tokens[$i]->getContent(), "\n")) { + break; + } + } + + $this->removeEmptyLinesAfterLineWithTokenAt($index); + } + + private function fixAfterCaseToken(int $index): void + { + if (\defined('T_ENUM')) { // @TODO: drop condition when PHP 8.1+ is required + $enumSwitchIndex = $this->tokens->getPrevTokenOfKind($index, [[T_SWITCH], [T_ENUM]]); + + if (!$this->tokens[$enumSwitchIndex]->isGivenKind(T_SWITCH)) { + return; + } + } + + $this->removeEmptyLinesAfterLineWithTokenAt($index); + } + + private function fixAfterThrowToken(int $index): void + { + $prevIndex = $this->tokens->getPrevMeaningfulToken($index); + + if (!$this->tokens[$prevIndex]->equalsAny([';', '{', '}', ':', [T_OPEN_TAG]])) { + return; + } + + if ($this->tokens[$prevIndex]->equals(':') && !SwitchAnalyzer::belongsToSwitch($this->tokens, $prevIndex)) { + return; + } + + $this->fixAfterToken($index); + } + + /** + * Remove white line(s) after the index of a block type, + * but only if the block is not on one line. + * + * @param int $index body start + */ + private function fixStructureOpenCloseIfMultiLine(int $index): void + { + $blockTypeInfo = Tokens::detectBlockType($this->tokens[$index]); + $bodyEnd = $this->tokens->findBlockEnd($blockTypeInfo['type'], $index); + + for ($i = $bodyEnd - 1; $i >= $index; --$i) { + if (str_contains($this->tokens[$i]->getContent(), "\n")) { + $this->removeEmptyLinesAfterLineWithTokenAt($i); + $this->removeEmptyLinesAfterLineWithTokenAt($index); + + break; + } + } + } + + private function removeEmptyLinesAfterLineWithTokenAt(int $index): void + { + // find the line break + $parenthesesDepth = 0; + $tokenCount = \count($this->tokens); + for ($end = $index; $end < $tokenCount; ++$end) { + if ($this->tokens[$end]->equals('(')) { + ++$parenthesesDepth; + + continue; + } + + if ($this->tokens[$end]->equals(')')) { + --$parenthesesDepth; + if ($parenthesesDepth < 0) { + return; + } + + continue; + } + + if ( + $this->tokens[$end]->equals('}') + || str_contains($this->tokens[$end]->getContent(), "\n") + ) { + break; + } + } + + if ($end === $tokenCount) { + return; // not found, early return + } + + $ending = $this->whitespacesConfig->getLineEnding(); + + for ($i = $end; $i < $tokenCount && $this->tokens[$i]->isWhitespace(); ++$i) { + $content = $this->tokens[$i]->getContent(); + + if (substr_count($content, "\n") < 1) { + continue; + } + + $newContent = Preg::replace('/^.*\R(\h*)$/s', $ending.'$1', $content); + + $this->tokens[$i] = new Token([T_WHITESPACE, $newContent]); + } + } + + private function containsLinebreak(int $startIndex, int $endIndex): bool + { + for ($i = $endIndex; $i > $startIndex; --$i) { + if (Preg::match('/\R/', $this->tokens[$i]->getContent())) { + return true; + } + } + + return false; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoSpacesAroundOffsetFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoSpacesAroundOffsetFixer.php new file mode 100644 index 00000000..b3a0849f --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoSpacesAroundOffsetFixer.php @@ -0,0 +1,99 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Whitespace; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Javier Spagnoletti + */ +final class NoSpacesAroundOffsetFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'There MUST NOT be spaces around offset braces.', + [ + new CodeSample(" ['inside']]), + new CodeSample(" ['outside']]), + ] + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound(['[', CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if (!$token->equalsAny(['[', [CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN]])) { + continue; + } + + if (\in_array('inside', $this->configuration['positions'], true)) { + if ($token->equals('[')) { + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE, $index); + } else { + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_INDEX_CURLY_BRACE, $index); + } + + // remove space after opening `[` or `{` + if ($tokens[$index + 1]->isWhitespace(" \t")) { + $tokens->clearAt($index + 1); + } + + // remove space before closing `]` or `}` + if ($tokens[$endIndex - 1]->isWhitespace(" \t")) { + $tokens->clearAt($endIndex - 1); + } + } + + if (\in_array('outside', $this->configuration['positions'], true)) { + $prevNonWhitespaceIndex = $tokens->getPrevNonWhitespace($index); + if ($tokens[$prevNonWhitespaceIndex]->isComment()) { + continue; + } + + $tokens->removeLeadingWhitespace($index); + } + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + $values = ['inside', 'outside']; + + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('positions', 'Whether spacing should be fixed inside and/or outside the offset braces.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([new AllowedValueSubset($values)]) + ->setDefault($values) + ->getOption(), + ]); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoSpacesInsideParenthesisFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoSpacesInsideParenthesisFixer.php new file mode 100644 index 00000000..d8507512 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoSpacesInsideParenthesisFixer.php @@ -0,0 +1,75 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Whitespace; + +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Fixer for rules defined in PSR2 ¶4.3, ¶4.6, ¶5. + * + * @author Marc Aubé + * @author Dariusz Rumiński + * + * @deprecated in favor of SpacesInsideParenthesisFixer + */ +final class NoSpacesInsideParenthesisFixer extends AbstractProxyFixer implements DeprecatedFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'There MUST NOT be a space after the opening parenthesis. There MUST NOT be a space before the closing parenthesis.', + [ + new CodeSample("proxyFixers); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound('('); + } + + protected function createProxyFixers(): array + { + return [new SpacesInsideParenthesesFixer()]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoTrailingWhitespaceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoTrailingWhitespaceFixer.php new file mode 100644 index 00000000..c21ff39f --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoTrailingWhitespaceFixer.php @@ -0,0 +1,104 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Whitespace; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Fixer for rules defined in PSR2 ¶2.3. + * + * Don't add trailing spaces at the end of non-blank lines. + * + * @author Fabien Potencier + * @author Dariusz Rumiński + */ +final class NoTrailingWhitespaceFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Remove trailing whitespace at the end of non-blank lines.', + [new CodeSample("= 0; --$index) { + $token = $tokens[$index]; + if ( + $token->isGivenKind(T_OPEN_TAG) + && $tokens->offsetExists($index + 1) + && $tokens[$index + 1]->isWhitespace() + && Preg::match('/(.*)\h$/', $token->getContent(), $openTagMatches) + && Preg::match('/^(\R)(.*)$/s', $tokens[$index + 1]->getContent(), $whitespaceMatches) + ) { + $tokens[$index] = new Token([T_OPEN_TAG, $openTagMatches[1].$whitespaceMatches[1]]); + $tokens->ensureWhitespaceAtIndex($index + 1, 0, $whitespaceMatches[2]); + + continue; + } + + if (!$token->isWhitespace()) { + continue; + } + + $lines = Preg::split('/(\\R+)/', $token->getContent(), -1, PREG_SPLIT_DELIM_CAPTURE); + $linesSize = \count($lines); + + // fix only multiline whitespaces or singleline whitespaces at the end of file + if ($linesSize > 1 || !isset($tokens[$index + 1])) { + if (!$tokens[$index - 1]->isGivenKind(T_OPEN_TAG) || !Preg::match('/(.*)\R$/', $tokens[$index - 1]->getContent())) { + $lines[0] = rtrim($lines[0], " \t"); + } + + for ($i = 1; $i < $linesSize; ++$i) { + $trimmedLine = rtrim($lines[$i], " \t"); + if ('' !== $trimmedLine) { + $lines[$i] = $trimmedLine; + } + } + + $content = implode('', $lines); + if ('' !== $content) { + $tokens[$index] = new Token([$token->getId(), $content]); + } else { + $tokens->clearAt($index); + } + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoWhitespaceInBlankLineFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoWhitespaceInBlankLineFixer.php new file mode 100644 index 00000000..352ce7a8 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoWhitespaceInBlankLineFixer.php @@ -0,0 +1,89 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Whitespace; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class NoWhitespaceInBlankLineFixer extends AbstractFixer implements WhitespacesAwareFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Remove trailing whitespace at the end of blank lines.', + [new CodeSample("isWhitespace()) { + $this->fixWhitespaceToken($tokens, $i); + } + } + } + + private function fixWhitespaceToken(Tokens $tokens, int $index): void + { + $content = $tokens[$index]->getContent(); + $lines = Preg::split("/(\r\n|\n)/", $content); + $lineCount = \count($lines); + + if ( + // fix T_WHITESPACES with at least 3 lines (eg `\n \n`) + $lineCount > 2 + // and T_WHITESPACES with at least 2 lines at the end of file or after open tag with linebreak + || ($lineCount > 0 && (!isset($tokens[$index + 1]) || $tokens[$index - 1]->isGivenKind(T_OPEN_TAG))) + ) { + $lMax = isset($tokens[$index + 1]) ? $lineCount - 1 : $lineCount; + + $lStart = 1; + if ($tokens[$index - 1]->isGivenKind(T_OPEN_TAG) && "\n" === substr($tokens[$index - 1]->getContent(), -1)) { + $lStart = 0; + } + + for ($l = $lStart; $l < $lMax; ++$l) { + $lines[$l] = Preg::replace('/^\h+$/', '', $lines[$l]); + } + $content = implode($this->whitespacesConfig->getLineEnding(), $lines); + $tokens->ensureWhitespaceAtIndex($index, 0, $content); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/SingleBlankLineAtEofFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/SingleBlankLineAtEofFixer.php new file mode 100644 index 00000000..6fbea69d --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/SingleBlankLineAtEofFixer.php @@ -0,0 +1,64 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Whitespace; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * A file must always end with a line endings character. + * + * Fixer for rules defined in PSR2 ¶2.2. + * + * @author Fabien Potencier + * @author Dariusz Rumiński + */ +final class SingleBlankLineAtEofFixer extends AbstractFixer implements WhitespacesAwareFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'A PHP file without end tag must always end with a single empty line feed.', + [ + new CodeSample("count(); + + if ($count > 0 && !$tokens[$count - 1]->isGivenKind([T_INLINE_HTML, T_CLOSE_TAG, T_OPEN_TAG])) { + $tokens->ensureWhitespaceAtIndex($count - 1, 1, $this->whitespacesConfig->getLineEnding()); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/SpacesInsideParenthesesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/SpacesInsideParenthesesFixer.php new file mode 100644 index 00000000..a5da0b86 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/SpacesInsideParenthesesFixer.php @@ -0,0 +1,210 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Whitespace; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Fixer for rules defined in PSR2 ¶4.3, ¶4.6, ¶5. + * + * @author Marc Aubé + * @author Dariusz Rumiński + */ +final class SpacesInsideParenthesesFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Parentheses must be declared using the configured whitespace.', + [ + new CodeSample(" 'none'] + ), + new CodeSample( + " 'single'] + ), + new CodeSample( + " 'single'] + ), + ], + 'By default there are not any additional spaces inside parentheses, however with `space=single` configuration option whitespace inside parentheses will be unified to single space.' + ); + } + + /** + * {@inheritdoc} + * + * Must run before FunctionToConstantFixer, GetClassToClassKeywordFixer, StringLengthToEmptyFixer. + * Must run after CombineConsecutiveIssetsFixer, CombineNestedDirnameFixer, IncrementStyleFixer, LambdaNotUsedImportFixer, ModernizeStrposFixer, NoUselessSprintfFixer, PowToExponentiationFixer. + */ + public function getPriority(): int + { + return 3; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound(['(', CT::T_BRACE_CLASS_INSTANTIATION_OPEN]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + if ('none' === $this->configuration['space']) { + foreach ($tokens as $index => $token) { + if (!$token->equalsAny(['(', [CT::T_BRACE_CLASS_INSTANTIATION_OPEN]])) { + continue; + } + + $prevIndex = $tokens->getPrevMeaningfulToken($index); + + // ignore parenthesis for T_ARRAY + if (null !== $prevIndex && $tokens[$prevIndex]->isGivenKind(T_ARRAY)) { + continue; + } + + $blockType = Tokens::detectBlockType($tokens[$index]); + $endIndex = $tokens->findBlockEnd($blockType['type'], $index); + + // remove space after opening `(` + if (!$tokens[$tokens->getNextNonWhitespace($index)]->isComment()) { + $this->removeSpaceAroundToken($tokens, $index + 1); + } + + // remove space before closing `)` if it is not `list($a, $b, )` case + if (!$tokens[$tokens->getPrevMeaningfulToken($endIndex)]->equals(',')) { + $this->removeSpaceAroundToken($tokens, $endIndex - 1); + } + } + } + + if ('single' === $this->configuration['space']) { + foreach ($tokens as $index => $token) { + if (!$token->equalsAny(['(', [CT::T_BRACE_CLASS_INSTANTIATION_OPEN]])) { + continue; + } + + $blockType = Tokens::detectBlockType($tokens[$index]); + $endParenthesisIndex = $tokens->findBlockEnd($blockType['type'], $index); + + // if not other content than spaces in block remove spaces + $blockContent = $this->getBlockContent($index, $endParenthesisIndex, $tokens); + if (1 === \count($blockContent) && \in_array(' ', $blockContent, true)) { + $this->removeSpaceAroundToken($tokens, $index + 1); + + continue; + } + + // don't process if the next token is `)` + $nextMeaningfulTokenIndex = $tokens->getNextMeaningfulToken($index); + if (')' === $tokens[$nextMeaningfulTokenIndex]->getContent()) { + continue; + } + + $afterParenthesisIndex = $tokens->getNextNonWhitespace($endParenthesisIndex); + $afterParenthesisToken = $tokens[$afterParenthesisIndex]; + + if ($afterParenthesisToken->isGivenKind(CT::T_USE_LAMBDA)) { + $useStartParenthesisIndex = $tokens->getNextTokenOfKind($afterParenthesisIndex, ['(']); + $useEndParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $useStartParenthesisIndex); + + // add single-line edge whitespaces inside use parentheses + $this->fixParenthesisInnerEdge($tokens, $useStartParenthesisIndex, $useEndParenthesisIndex); + } + + // add single-line edge whitespaces inside parameters list parentheses + $this->fixParenthesisInnerEdge($tokens, $index, $endParenthesisIndex); + } + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('space', 'Whether to have `single` or `none` space inside parentheses.')) + ->setAllowedValues(['none', 'single']) + ->setDefault('none') + ->getOption(), + ]); + } + + /** + * Remove spaces from token at a given index. + */ + private function removeSpaceAroundToken(Tokens $tokens, int $index): void + { + $token = $tokens[$index]; + + if ($token->isWhitespace() && !str_contains($token->getContent(), "\n")) { + $tokens->clearAt($index); + } + } + + private function fixParenthesisInnerEdge(Tokens $tokens, int $start, int $end): void + { + // fix white space before ')' + if ($tokens[$end - 1]->isWhitespace()) { + $content = $tokens[$end - 1]->getContent(); + if (' ' !== $content && !str_contains($content, "\n") && !$tokens[$tokens->getPrevNonWhitespace($end - 1)]->isComment()) { + $tokens[$end - 1] = new Token([T_WHITESPACE, ' ']); + } + } else { + $tokens->insertAt($end, new Token([T_WHITESPACE, ' '])); + } + + // fix white space after '(' + if ($tokens[$start + 1]->isWhitespace()) { + $content = $tokens[$start + 1]->getContent(); + if (' ' !== $content && !str_contains($content, "\n") && !$tokens[$tokens->getNextNonWhitespace($start + 1)]->isComment()) { + $tokens[$start + 1] = new Token([T_WHITESPACE, ' ']); + } + } else { + $tokens->insertAt($start + 1, new Token([T_WHITESPACE, ' '])); + } + } + + /** + * @return list + */ + private function getBlockContent(int $startIndex, int $endIndex, Tokens $tokens): array + { + // + 1 for ( + $contents = []; + for ($i = ($startIndex + 1); $i < $endIndex; ++$i) { + $contents[] = $tokens[$i]->getContent(); + } + + return $contents; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/StatementIndentationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/StatementIndentationFixer.php new file mode 100644 index 00000000..783ec506 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/StatementIndentationFixer.php @@ -0,0 +1,797 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Whitespace; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\Indentation; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\AlternativeSyntaxAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class StatementIndentationFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface +{ + use Indentation; + + private AlternativeSyntaxAnalyzer $alternativeSyntaxAnalyzer; + + private bool $bracesFixerCompatibility; + + public function __construct(bool $bracesFixerCompatibility = false) + { + parent::__construct(); + + $this->bracesFixerCompatibility = $bracesFixerCompatibility; + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Each statement must be indented.', + [ + new CodeSample( + ' false] + ), + new CodeSample( + ' true] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before HeredocIndentationFixer. + * Must run after BracesPositionFixer, ClassAttributesSeparationFixer, CurlyBracesPositionFixer, FullyQualifiedStrictTypesFixer, GlobalNamespaceImportFixer, MethodArgumentSpaceFixer, NoUselessElseFixer, YieldFromArrayToYieldsFixer. + */ + public function getPriority(): int + { + return -3; + } + + public function isCandidate(Tokens $tokens): bool + { + return true; + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('stick_comment_to_next_continuous_control_statement', 'Last comment of code block counts as comment for next block.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $this->alternativeSyntaxAnalyzer = new AlternativeSyntaxAnalyzer(); + + $blockSignatureFirstTokens = [ + T_USE, + T_IF, + T_ELSE, + T_ELSEIF, + T_FOR, + T_FOREACH, + T_WHILE, + T_DO, + T_SWITCH, + T_CASE, + T_DEFAULT, + T_TRY, + T_CLASS, + T_INTERFACE, + T_TRAIT, + T_EXTENDS, + T_IMPLEMENTS, + ]; + $controlStructurePossibiblyWithoutBracesTokens = [ + T_IF, + T_ELSE, + T_ELSEIF, + T_FOR, + T_FOREACH, + T_WHILE, + T_DO, + ]; + if (\defined('T_MATCH')) { // @TODO: drop condition when PHP 8.0+ is required + $blockSignatureFirstTokens[] = T_MATCH; + } + + $blockFirstTokens = ['{', [CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN], [CT::T_USE_TRAIT], [CT::T_GROUP_IMPORT_BRACE_OPEN]]; + if (\defined('T_ATTRIBUTE')) { // @TODO: drop condition when PHP 8.0+ is required + $blockFirstTokens[] = [T_ATTRIBUTE]; + } + + $endIndex = \count($tokens) - 1; + if ($tokens[$endIndex]->isWhitespace()) { + --$endIndex; + } + + $lastIndent = $this->getLineIndentationWithBracesCompatibility( + $tokens, + 0, + $this->extractIndent($this->computeNewLineContent($tokens, 0)), + ); + + $methodModifierTokens = [ + // https://github.com/php/php-langspec/blob/master/spec/19-grammar.md#grammar-visibility-modifier + T_PUBLIC, + T_PROTECTED, + T_PRIVATE, + // https://github.com/php/php-langspec/blob/master/spec/19-grammar.md#grammar-static-modifier + T_STATIC, + // https://github.com/php/php-langspec/blob/master/spec/19-grammar.md#grammar-class-modifier + T_ABSTRACT, + T_FINAL, + ]; + + $methodModifierIndents = []; + + /** + * @var list $scopes + */ + $scopes = [ + [ + 'type' => 'block', + 'skip' => false, + 'end_index' => $endIndex, + 'end_index_inclusive' => true, + 'initial_indent' => $lastIndent, + 'is_indented_block' => false, + ], + ]; + + $previousLineInitialIndent = ''; + $previousLineNewIndent = ''; + $noBracesBlockStarts = []; + $alternativeBlockStarts = []; + $caseBlockStarts = []; + + foreach ($tokens as $index => $token) { + $currentScope = \count($scopes) - 1; + + if (isset($noBracesBlockStarts[$index])) { + $scopes[] = [ + 'type' => 'block', + 'skip' => false, + 'end_index' => $this->findStatementEndIndex($tokens, $index, \count($tokens) - 1), + 'end_index_inclusive' => true, + 'initial_indent' => $this->getLineIndentationWithBracesCompatibility($tokens, $index, $lastIndent), + 'is_indented_block' => true, + ]; + ++$currentScope; + } + + if ( + $token->equalsAny($blockFirstTokens) + || ($token->equals('(') && !$tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_ARRAY)) + || isset($alternativeBlockStarts[$index]) + || isset($caseBlockStarts[$index]) + ) { + $endIndexInclusive = true; + + if ($token->isGivenKind([T_EXTENDS, T_IMPLEMENTS])) { + $endIndex = $tokens->getNextTokenOfKind($index, ['{']); + } elseif ($token->isGivenKind(CT::T_USE_TRAIT)) { + $endIndex = $tokens->getNextTokenOfKind($index, [';']); + } elseif ($token->equals(':')) { + if (isset($caseBlockStarts[$index])) { + [$endIndex, $endIndexInclusive] = $this->findCaseBlockEnd($tokens, $index); + } else { + $endIndex = $this->alternativeSyntaxAnalyzer->findAlternativeSyntaxBlockEnd($tokens, $alternativeBlockStarts[$index]); + } + } elseif ($token->isGivenKind(CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN)) { + $endIndex = $tokens->getNextTokenOfKind($index, [[CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE]]); + } elseif ($token->isGivenKind(CT::T_GROUP_IMPORT_BRACE_OPEN)) { + $endIndex = $tokens->getNextTokenOfKind($index, [[CT::T_GROUP_IMPORT_BRACE_CLOSE]]); + } elseif ($token->equals('{')) { + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); + } elseif ($token->equals('(')) { + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + } else { + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ATTRIBUTE, $index); + } + + if ('block_signature' === $scopes[$currentScope]['type']) { + $initialIndent = $scopes[$currentScope]['initial_indent']; + } else { + $initialIndent = $this->getLineIndentationWithBracesCompatibility($tokens, $index, $lastIndent); + } + + $skip = false; + if ($this->bracesFixerCompatibility) { + $prevIndex = $tokens->getPrevMeaningfulToken($index); + if (null !== $prevIndex) { + $prevIndex = $tokens->getPrevMeaningfulToken($prevIndex); + } + if (null !== $prevIndex && $tokens[$prevIndex]->isGivenKind([T_FUNCTION, T_FN])) { + $skip = true; + } + } + + $scopes[] = [ + 'type' => 'block', + 'skip' => $skip, + 'end_index' => $endIndex, + 'end_index_inclusive' => $endIndexInclusive, + 'initial_indent' => $initialIndent, + 'is_indented_block' => true, + ]; + ++$currentScope; + + while ($index >= $scopes[$currentScope]['end_index']) { + array_pop($scopes); + + --$currentScope; + } + + continue; + } + + if ($token->isGivenKind($blockSignatureFirstTokens)) { + $lastWhitespaceIndex = null; + $closingParenthesisIndex = null; + + for ($endIndex = $index + 1, $max = \count($tokens); $endIndex < $max; ++$endIndex) { + $endToken = $tokens[$endIndex]; + + if ($endToken->equals('(')) { + $closingParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $endIndex); + $endIndex = $closingParenthesisIndex; + + continue; + } + + if ($endToken->equalsAny(['{', ';', [T_DOUBLE_ARROW], [T_IMPLEMENTS]])) { + break; + } + + if ($endToken->equals(':')) { + if ($token->isGivenKind([T_CASE, T_DEFAULT])) { + $caseBlockStarts[$endIndex] = $index; + } else { + $alternativeBlockStarts[$endIndex] = $index; + } + + break; + } + + if (!$token->isGivenKind($controlStructurePossibiblyWithoutBracesTokens)) { + continue; + } + + if ($endToken->isWhitespace()) { + $lastWhitespaceIndex = $endIndex; + + continue; + } + + if (!$endToken->isComment()) { + $noBraceBlockStartIndex = $lastWhitespaceIndex ?? $endIndex; + $noBracesBlockStarts[$noBraceBlockStartIndex] = true; + + $endIndex = $closingParenthesisIndex ?? $index; + + break; + } + } + + $scopes[] = [ + 'type' => 'block_signature', + 'skip' => false, + 'end_index' => $endIndex, + 'end_index_inclusive' => true, + 'initial_indent' => $this->getLineIndentationWithBracesCompatibility($tokens, $index, $lastIndent), + 'is_indented_block' => $token->isGivenKind([T_EXTENDS, T_IMPLEMENTS]), + ]; + + continue; + } + + if ($token->isGivenKind($methodModifierTokens)) { + $methodModifierIndents[$index] = $lastIndent; + + continue; + } + + if ($token->isGivenKind(T_FUNCTION)) { + $x = $tokens->getPrevMeaningfulToken($index); + while ( + null !== $x + && $tokens[$x]->isGivenKind($methodModifierTokens) + && \array_key_exists($x, $methodModifierIndents) + ) { + $lastIndent = $methodModifierIndents[$x]; + $x = $tokens->getPrevMeaningfulToken($x); + } + + $methodModifierIndents = []; + $endIndex = $index + 1; + + for ($max = \count($tokens); $endIndex < $max; ++$endIndex) { + if ($tokens[$endIndex]->equals('(')) { + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $endIndex); + + continue; + } + + if ($tokens[$endIndex]->equalsAny(['{', ';'])) { + break; + } + } + + $scopes[] = [ + 'type' => 'block_signature', + 'skip' => false, + 'end_index' => $endIndex, + 'end_index_inclusive' => true, + 'initial_indent' => $this->getLineIndentationWithBracesCompatibility($tokens, $index, $lastIndent), + 'is_indented_block' => $token->isGivenKind([T_EXTENDS, T_IMPLEMENTS]), + ]; + + continue; + } + + if ( + $token->isWhitespace() + || ($index > 0 && $tokens[$index - 1]->isGivenKind(T_OPEN_TAG)) + ) { + $previousOpenTagContent = $tokens[$index - 1]->isGivenKind(T_OPEN_TAG) + ? Preg::replace('/\S/', '', $tokens[$index - 1]->getContent()) + : ''; + + $content = $previousOpenTagContent.($token->isWhitespace() ? $token->getContent() : ''); + + if (!Preg::match('/\R/', $content)) { + continue; + } + + $nextToken = $tokens[$index + 1] ?? null; + + if ( + $this->bracesFixerCompatibility + && null !== $nextToken + && $nextToken->isComment() + && !$this->isCommentWithFixableIndentation($tokens, $index + 1) + ) { + continue; + } + + if ('block' === $scopes[$currentScope]['type'] || 'block_signature' === $scopes[$currentScope]['type']) { + $indent = false; + + if ($scopes[$currentScope]['is_indented_block']) { + $firstNonWhitespaceTokenIndex = null; + $nextNewlineIndex = null; + for ($searchIndex = $index + 1, $max = \count($tokens); $searchIndex < $max; ++$searchIndex) { + $searchToken = $tokens[$searchIndex]; + + if (!$searchToken->isWhitespace()) { + if (null === $firstNonWhitespaceTokenIndex) { + $firstNonWhitespaceTokenIndex = $searchIndex; + } + + continue; + } + + if (Preg::match('/\R/', $searchToken->getContent())) { + $nextNewlineIndex = $searchIndex; + + break; + } + } + + $endIndex = $scopes[$currentScope]['end_index']; + + if (!$scopes[$currentScope]['end_index_inclusive']) { + ++$endIndex; + } + + if ( + (null !== $firstNonWhitespaceTokenIndex && $firstNonWhitespaceTokenIndex < $endIndex) + || (null !== $nextNewlineIndex && $nextNewlineIndex < $endIndex) + ) { + if ( + // do we touch whitespace directly before comment... + $tokens[$firstNonWhitespaceTokenIndex]->isGivenKind(T_COMMENT) + // ...and afterwards, there is only comment or `}` + && $tokens[$tokens->getNextMeaningfulToken($firstNonWhitespaceTokenIndex)]->equals('}') + ) { + if ( + // ... and the comment was only content in docblock + $tokens[$tokens->getPrevMeaningfulToken($firstNonWhitespaceTokenIndex)]->equals('{') + ) { + $indent = true; + } else { + // or it was dedicated comment for next control loop + // ^^ we need to check if there is a control group afterwards, and in that case don't make extra indent level + $nextIndex = $tokens->getNextMeaningfulToken($firstNonWhitespaceTokenIndex); + $nextNextIndex = $tokens->getNextMeaningfulToken($nextIndex); + + if (null !== $nextNextIndex && $tokens[$nextNextIndex]->isGivenKind([T_ELSE, T_ELSEIF])) { + $indent = true !== $this->configuration['stick_comment_to_next_continuous_control_statement']; + } else { + $indent = true; + } + } + } else { + $indent = true; + } + } + } + + $previousLineInitialIndent = $this->extractIndent($content); + + if ($scopes[$currentScope]['skip']) { + $whitespaces = $previousLineInitialIndent; + } else { + $whitespaces = $scopes[$currentScope]['initial_indent'].($indent ? $this->whitespacesConfig->getIndent() : ''); + } + + $content = Preg::replace( + '/(\R+)\h*$/', + '$1'.$whitespaces, + $content + ); + + $previousLineNewIndent = $this->extractIndent($content); + } else { + $content = Preg::replace( + '/(\R)'.$scopes[$currentScope]['initial_indent'].'(\h*)$/D', + '$1'.$scopes[$currentScope]['new_indent'].'$2', + $content + ); + } + + $lastIndent = $this->extractIndent($content); + + if ('' !== $previousOpenTagContent) { + $content = Preg::replace("/^{$previousOpenTagContent}/", '', $content); + } + + if ('' !== $content) { + $tokens->ensureWhitespaceAtIndex($index, 0, $content); + } elseif ($token->isWhitespace()) { + $tokens->clearAt($index); + } + + if (null !== $nextToken && $nextToken->isComment()) { + $tokens[$index + 1] = new Token([ + $nextToken->getId(), + Preg::replace( + '/(\R)'.preg_quote($previousLineInitialIndent, '/').'(\h*\S+.*)/', + '$1'.$previousLineNewIndent.'$2', + $nextToken->getContent() + ), + ]); + } + + if ($token->isWhitespace()) { + continue; + } + } + + if ($this->isNewLineToken($tokens, $index)) { + $lastIndent = $this->extractIndent($this->computeNewLineContent($tokens, $index)); + } + + while ($index >= $scopes[$currentScope]['end_index']) { + array_pop($scopes); + + if ([] === $scopes) { + return; + } + + --$currentScope; + } + + if ($token->isComment() || $token->equalsAny([';', ',', '}', [T_OPEN_TAG], [T_CLOSE_TAG], [CT::T_ATTRIBUTE_CLOSE]])) { + continue; + } + + if ('statement' !== $scopes[$currentScope]['type'] && 'block_signature' !== $scopes[$currentScope]['type']) { + $endIndex = $this->findStatementEndIndex($tokens, $index, $scopes[$currentScope]['end_index']); + + if ($endIndex === $index) { + continue; + } + + $scopes[] = [ + 'type' => 'statement', + 'skip' => false, + 'end_index' => $endIndex, + 'end_index_inclusive' => false, + 'initial_indent' => $previousLineInitialIndent, + 'new_indent' => $previousLineNewIndent, + ]; + } + } + } + + private function findStatementEndIndex(Tokens $tokens, int $index, int $parentScopeEndIndex): int + { + $endIndex = null; + + $ifLevel = 0; + $doWhileLevel = 0; + for ($searchEndIndex = $index; $searchEndIndex <= $parentScopeEndIndex; ++$searchEndIndex) { + $searchEndToken = $tokens[$searchEndIndex]; + + if ( + $searchEndToken->isGivenKind(T_IF) + && !$tokens[$tokens->getPrevMeaningfulToken($searchEndIndex)]->isGivenKind(T_ELSE) + ) { + ++$ifLevel; + + continue; + } + + if ($searchEndToken->isGivenKind(T_DO)) { + ++$doWhileLevel; + + continue; + } + + if ($searchEndToken->equalsAny(['(', '{', [CT::T_ARRAY_SQUARE_BRACE_OPEN]])) { + if ($searchEndToken->equals('(')) { + $blockType = Tokens::BLOCK_TYPE_PARENTHESIS_BRACE; + } elseif ($searchEndToken->equals('{')) { + $blockType = Tokens::BLOCK_TYPE_CURLY_BRACE; + } else { + $blockType = Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE; + } + + $searchEndIndex = $tokens->findBlockEnd($blockType, $searchEndIndex); + $searchEndToken = $tokens[$searchEndIndex]; + } + + if (!$searchEndToken->equalsAny([';', ',', '}', [T_CLOSE_TAG]])) { + continue; + } + + $controlStructureContinuationIndex = $tokens->getNextMeaningfulToken($searchEndIndex); + + if ( + $ifLevel > 0 + && null !== $controlStructureContinuationIndex + && $tokens[$controlStructureContinuationIndex]->isGivenKind([T_ELSE, T_ELSEIF]) + ) { + if ( + $tokens[$controlStructureContinuationIndex]->isGivenKind(T_ELSE) + && !$tokens[$tokens->getNextMeaningfulToken($controlStructureContinuationIndex)]->isGivenKind(T_IF) + ) { + --$ifLevel; + } + + $searchEndIndex = $controlStructureContinuationIndex; + + continue; + } + + if ( + $doWhileLevel > 0 + && null !== $controlStructureContinuationIndex + && $tokens[$controlStructureContinuationIndex]->isGivenKind([T_WHILE]) + ) { + --$doWhileLevel; + $searchEndIndex = $controlStructureContinuationIndex; + + continue; + } + + $endIndex = $tokens->getPrevNonWhitespace($searchEndIndex); + + break; + } + + return $endIndex ?? $tokens->getPrevMeaningfulToken($parentScopeEndIndex); + } + + /** + * @return array{int, bool} + */ + private function findCaseBlockEnd(Tokens $tokens, int $index): array + { + for ($max = \count($tokens); $index < $max; ++$index) { + if ($tokens[$index]->isGivenKind(T_SWITCH)) { + $braceIndex = $tokens->getNextMeaningfulToken( + $tokens->findBlockEnd( + Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, + $tokens->getNextMeaningfulToken($index) + ) + ); + + if ($tokens[$braceIndex]->equals(':')) { + $index = $this->alternativeSyntaxAnalyzer->findAlternativeSyntaxBlockEnd($tokens, $index); + } else { + $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $braceIndex); + } + + continue; + } + + if ($tokens[$index]->equals('{')) { + $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); + + continue; + } + + if ($tokens[$index]->equalsAny([[T_CASE], [T_DEFAULT]])) { + return [$index, true]; + } + + if ($tokens[$index]->equalsAny(['}', [T_ENDSWITCH]])) { + return [$tokens->getPrevNonWhitespace($index), false]; + } + } + + throw new \LogicException('End of case block not found.'); + } + + private function getLineIndentationWithBracesCompatibility(Tokens $tokens, int $index, string $regularIndent): string + { + if ( + $this->bracesFixerCompatibility + && $tokens[$index]->isGivenKind(T_OPEN_TAG) + && Preg::match('/\R/', $tokens[$index]->getContent()) + && isset($tokens[$index + 1]) + && $tokens[$index + 1]->isWhitespace() + && Preg::match('/\h+$/D', $tokens[$index + 1]->getContent()) + ) { + return Preg::replace('/.*?(\h+)$/sD', '$1', $tokens[$index + 1]->getContent()); + } + + return $regularIndent; + } + + /** + * Returns whether the token at given index is a comment whose indentation + * can be fixed. + * + * Indentation of a comment is not changed when the comment is part of a + * multi-line message whose lines are all single-line comments and at least + * one line has meaningful content. + */ + private function isCommentWithFixableIndentation(Tokens $tokens, int $index): bool + { + if (!$tokens[$index]->isComment()) { + return false; + } + + if (str_starts_with($tokens[$index]->getContent(), '/*')) { + return true; + } + + $indent = preg_quote($this->whitespacesConfig->getIndent(), '~'); + + if (Preg::match("~^(//|#)({$indent}.*)?$~", $tokens[$index]->getContent())) { + return false; + } + + $firstCommentIndex = $index; + while (true) { + $firstCommentCandidateIndex = $this->getSiblingContinuousSingleLineComment($tokens, $firstCommentIndex, false); + if (null === $firstCommentCandidateIndex) { + break; + } + + $firstCommentIndex = $firstCommentCandidateIndex; + } + + $lastCommentIndex = $index; + while (true) { + $lastCommentCandidateIndex = $this->getSiblingContinuousSingleLineComment($tokens, $lastCommentIndex, true); + if (null === $lastCommentCandidateIndex) { + break; + } + + $lastCommentIndex = $lastCommentCandidateIndex; + } + + if ($firstCommentIndex === $lastCommentIndex) { + return true; + } + + for ($i = $firstCommentIndex + 1; $i < $lastCommentIndex; ++$i) { + if (!$tokens[$i]->isWhitespace() && !$tokens[$i]->isComment()) { + return false; + } + } + + return true; + } + + private function getSiblingContinuousSingleLineComment(Tokens $tokens, int $index, bool $after): ?int + { + $siblingIndex = $index; + do { + if ($after) { + $siblingIndex = $tokens->getNextTokenOfKind($siblingIndex, [[T_COMMENT]]); + } else { + $siblingIndex = $tokens->getPrevTokenOfKind($siblingIndex, [[T_COMMENT]]); + } + + if (null === $siblingIndex) { + return null; + } + } while (str_starts_with($tokens[$siblingIndex]->getContent(), '/*')); + + $newLines = 0; + for ($i = min($siblingIndex, $index) + 1, $max = max($siblingIndex, $index); $i < $max; ++$i) { + if ($tokens[$i]->isWhitespace() && Preg::match('/\R/', $tokens[$i]->getContent())) { + if (1 === $newLines || Preg::match('/\R.*\R/', $tokens[$i]->getContent())) { + return null; + } + + ++$newLines; + } + } + + return $siblingIndex; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/TypeDeclarationSpacesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/TypeDeclarationSpacesFixer.php new file mode 100644 index 00000000..29cd7f5a --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/TypeDeclarationSpacesFixer.php @@ -0,0 +1,199 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Whitespace; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\TypeAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Dariusz Rumiński + * @author John Paul E. Balandan, CPA + */ +final class TypeDeclarationSpacesFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Ensure single space between a variable and its type declaration in function arguments and properties.', + [ + new CodeSample( + ' (string) $c; + } +} +', + ['elements' => ['function']] + ), + new CodeSample( + ' ['property']] + ), + ] + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound([...Token::getClassyTokenKinds(), T_FN, T_FUNCTION]); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('elements', 'Structural elements where the spacing after the type declaration should be fixed.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([new AllowedValueSubset(['function', 'property'])]) + ->setDefault(['function', 'property']) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $functionsAnalyzer = new FunctionsAnalyzer(); + + foreach (array_reverse($this->getElements($tokens), true) as $index => $type) { + if ('property' === $type && \in_array('property', $this->configuration['elements'], true)) { + $this->ensureSingleSpaceAtPropertyTypehint($tokens, $index); + + continue; + } + + if ('method' === $type && \in_array('function', $this->configuration['elements'], true)) { + $this->ensureSingleSpaceAtFunctionArgumentTypehint($functionsAnalyzer, $tokens, $index); + + // implicit continue; + } + } + } + + /** + * @return array + * + * @phpstan-return array + */ + private function getElements(Tokens $tokens): array + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + + $elements = array_map( + static fn (array $element): string => $element['type'], + array_filter( + $tokensAnalyzer->getClassyElements(), + static fn (array $element): bool => \in_array($element['type'], ['method', 'property'], true) + ) + ); + + foreach ($tokens as $index => $token) { + if ( + $token->isGivenKind(T_FN) + || ($token->isGivenKind(T_FUNCTION) && !isset($elements[$index])) + ) { + $elements[$index] = 'method'; + } + } + + return $elements; + } + + private function ensureSingleSpaceAtFunctionArgumentTypehint(FunctionsAnalyzer $functionsAnalyzer, Tokens $tokens, int $index): void + { + foreach (array_reverse($functionsAnalyzer->getFunctionArguments($tokens, $index)) as $argumentInfo) { + $argumentType = $argumentInfo->getTypeAnalysis(); + + if (null === $argumentType) { + continue; + } + + $tokens->ensureWhitespaceAtIndex($argumentType->getEndIndex() + 1, 0, ' '); + } + } + + private function ensureSingleSpaceAtPropertyTypehint(Tokens $tokens, int $index): void + { + $propertyIndex = $index; + $propertyModifiers = [T_PRIVATE, T_PROTECTED, T_PUBLIC, T_STATIC, T_VAR]; + + if (\defined('T_READONLY')) { + $propertyModifiers[] = T_READONLY; // @TODO drop condition when PHP 8.1 is supported + } + + do { + $index = $tokens->getPrevMeaningfulToken($index); + } while (!$tokens[$index]->isGivenKind($propertyModifiers)); + + $propertyType = $this->collectTypeAnalysis($tokens, $index, $propertyIndex); + + if (null === $propertyType) { + return; + } + + $tokens->ensureWhitespaceAtIndex($propertyType->getEndIndex() + 1, 0, ' '); + } + + private function collectTypeAnalysis(Tokens $tokens, int $startIndex, int $endIndex): ?TypeAnalysis + { + $type = ''; + $typeStartIndex = $tokens->getNextMeaningfulToken($startIndex); + $typeEndIndex = $typeStartIndex; + + for ($i = $typeStartIndex; $i < $endIndex; ++$i) { + if ($tokens[$i]->isWhitespace() || $tokens[$i]->isComment()) { + continue; + } + + $type .= $tokens[$i]->getContent(); + $typeEndIndex = $i; + } + + return '' !== $type ? new TypeAnalysis($type, $typeStartIndex, $typeEndIndex) : null; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/TypesSpacesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/TypesSpacesFixer.php new file mode 100644 index 00000000..72d8e77e --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/TypesSpacesFixer.php @@ -0,0 +1,156 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Whitespace; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class TypesSpacesFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + public function configure(array $configuration): void + { + parent::configure($configuration); + + if (!isset($this->configuration['space_multiple_catch'])) { + $this->configuration['space_multiple_catch'] = $this->configuration['space']; + } + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'A single space or none should be around union type and intersection type operators.', + [ + new CodeSample( + " 'single'] + ), + new VersionSpecificCodeSample( + "isAnyTokenKindsFound([CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION]); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('space', 'Spacing to apply around union type and intersection type operators.')) + ->setAllowedValues(['none', 'single']) + ->setDefault('none') + ->getOption(), + (new FixerOptionBuilder('space_multiple_catch', 'Spacing to apply around type operator when catching exceptions of multiple types, use `null` to follow the value configured for `space`.')) + ->setAllowedValues(['none', 'single', null]) + ->setDefault(null) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $tokenCount = $tokens->count() - 1; + + for ($index = 0; $index < $tokenCount; ++$index) { + if ($tokens[$index]->isGivenKind([CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION])) { + $tokenCount += $this->fixSpacing($tokens, $index, 'single' === $this->configuration['space']); + + continue; + } + + if ($tokens[$index]->isGivenKind(T_CATCH)) { + while (true) { + $index = $tokens->getNextTokenOfKind($index, [')', [CT::T_TYPE_ALTERNATION]]); + + if ($tokens[$index]->equals(')')) { + break; + } + + $tokenCount += $this->fixSpacing($tokens, $index, 'single' === $this->configuration['space_multiple_catch']); + } + + // implicit continue + } + } + } + + private function fixSpacing(Tokens $tokens, int $index, bool $singleSpace): int + { + if (!$singleSpace) { + $this->ensureNoSpace($tokens, $index + 1); + $this->ensureNoSpace($tokens, $index - 1); + + return 0; + } + + $addedTokenCount = 0; + $addedTokenCount += $this->ensureSingleSpace($tokens, $index + 1, 0); + $addedTokenCount += $this->ensureSingleSpace($tokens, $index - 1, 1); + + return $addedTokenCount; + } + + private function ensureSingleSpace(Tokens $tokens, int $index, int $offset): int + { + if (!$tokens[$index]->isWhitespace()) { + $tokens->insertSlices([$index + $offset => new Token([T_WHITESPACE, ' '])]); + + return 1; + } + + if (' ' !== $tokens[$index]->getContent() && !Preg::match('/\R/', $tokens[$index]->getContent())) { + $tokens[$index] = new Token([T_WHITESPACE, ' ']); + } + + return 0; + } + + private function ensureNoSpace(Tokens $tokens, int $index): void + { + if ($tokens[$index]->isWhitespace() && !Preg::match('/\R/', $tokens[$index]->getContent())) { + $tokens->clearAt($index); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/WhitespacesAwareFixerInterface.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/WhitespacesAwareFixerInterface.php new file mode 100644 index 00000000..c03a3e78 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/WhitespacesAwareFixerInterface.php @@ -0,0 +1,25 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer; + +use PhpCsFixer\WhitespacesFixerConfig; + +/** + * @author Dariusz Rumiński + */ +interface WhitespacesAwareFixerInterface extends FixerInterface +{ + public function setWhitespacesConfig(WhitespacesFixerConfig $config): void; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/AliasedFixerOption.php b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/AliasedFixerOption.php new file mode 100644 index 00000000..65a7a3b3 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/AliasedFixerOption.php @@ -0,0 +1,73 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\FixerConfiguration; + +/** + * @author ntzm + * + * @internal + */ +final class AliasedFixerOption implements FixerOptionInterface +{ + private FixerOptionInterface $fixerOption; + + private string $alias; + + public function __construct(FixerOptionInterface $fixerOption, string $alias) + { + $this->fixerOption = $fixerOption; + $this->alias = $alias; + } + + public function getAlias(): string + { + return $this->alias; + } + + public function getName(): string + { + return $this->fixerOption->getName(); + } + + public function getDescription(): string + { + return $this->fixerOption->getDescription(); + } + + public function hasDefault(): bool + { + return $this->fixerOption->hasDefault(); + } + + public function getDefault() + { + return $this->fixerOption->getDefault(); + } + + public function getAllowedTypes(): ?array + { + return $this->fixerOption->getAllowedTypes(); + } + + public function getAllowedValues(): ?array + { + return $this->fixerOption->getAllowedValues(); + } + + public function getNormalizer(): ?\Closure + { + return $this->fixerOption->getNormalizer(); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/AliasedFixerOptionBuilder.php b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/AliasedFixerOptionBuilder.php new file mode 100644 index 00000000..13bb3faa --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/AliasedFixerOptionBuilder.php @@ -0,0 +1,78 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\FixerConfiguration; + +/** + * @author ntzm + * + * @internal + */ +final class AliasedFixerOptionBuilder +{ + private FixerOptionBuilder $optionBuilder; + + private string $alias; + + public function __construct(FixerOptionBuilder $optionBuilder, string $alias) + { + $this->optionBuilder = $optionBuilder; + $this->alias = $alias; + } + + /** + * @param mixed $default + */ + public function setDefault($default): self + { + $this->optionBuilder->setDefault($default); + + return $this; + } + + /** + * @param list $allowedTypes + */ + public function setAllowedTypes(array $allowedTypes): self + { + $this->optionBuilder->setAllowedTypes($allowedTypes); + + return $this; + } + + /** + * @param list $allowedValues + */ + public function setAllowedValues(array $allowedValues): self + { + $this->optionBuilder->setAllowedValues($allowedValues); + + return $this; + } + + public function setNormalizer(\Closure $normalizer): self + { + $this->optionBuilder->setNormalizer($normalizer); + + return $this; + } + + public function getOption(): AliasedFixerOption + { + return new AliasedFixerOption( + $this->optionBuilder->getOption(), + $this->alias + ); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/AllowedValueSubset.php b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/AllowedValueSubset.php new file mode 100644 index 00000000..fad56516 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/AllowedValueSubset.php @@ -0,0 +1,63 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\FixerConfiguration; + +/** + * @internal + */ +final class AllowedValueSubset +{ + /** + * @var list + */ + private array $allowedValues; + + /** + * @param list $allowedValues + */ + public function __construct(array $allowedValues) + { + $this->allowedValues = $allowedValues; + sort($this->allowedValues, SORT_FLAG_CASE | SORT_STRING); + } + + /** + * Checks whether the given values are a subset of the allowed ones. + * + * @param mixed $values the value to validate + */ + public function __invoke($values): bool + { + if (!\is_array($values)) { + return false; + } + + foreach ($values as $value) { + if (!\in_array($value, $this->allowedValues, true)) { + return false; + } + } + + return true; + } + + /** + * @return list + */ + public function getAllowedValues(): array + { + return $this->allowedValues; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/DeprecatedFixerOption.php b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/DeprecatedFixerOption.php new file mode 100644 index 00000000..7224a673 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/DeprecatedFixerOption.php @@ -0,0 +1,68 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\FixerConfiguration; + +final class DeprecatedFixerOption implements DeprecatedFixerOptionInterface +{ + private FixerOptionInterface $option; + + private string $deprecationMessage; + + public function __construct(FixerOptionInterface $option, string $deprecationMessage) + { + $this->option = $option; + $this->deprecationMessage = $deprecationMessage; + } + + public function getName(): string + { + return $this->option->getName(); + } + + public function getDescription(): string + { + return $this->option->getDescription(); + } + + public function hasDefault(): bool + { + return $this->option->hasDefault(); + } + + public function getDefault() + { + return $this->option->getDefault(); + } + + public function getAllowedTypes(): ?array + { + return $this->option->getAllowedTypes(); + } + + public function getAllowedValues(): ?array + { + return $this->option->getAllowedValues(); + } + + public function getNormalizer(): ?\Closure + { + return $this->option->getNormalizer(); + } + + public function getDeprecationMessage(): string + { + return $this->deprecationMessage; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/DeprecatedFixerOptionInterface.php b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/DeprecatedFixerOptionInterface.php new file mode 100644 index 00000000..d847aea3 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/DeprecatedFixerOptionInterface.php @@ -0,0 +1,20 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\FixerConfiguration; + +interface DeprecatedFixerOptionInterface extends FixerOptionInterface +{ + public function getDeprecationMessage(): string; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerConfigurationResolver.php b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerConfigurationResolver.php new file mode 100644 index 00000000..ea073f25 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerConfigurationResolver.php @@ -0,0 +1,125 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\FixerConfiguration; + +use PhpCsFixer\Utils; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; +use Symfony\Component\OptionsResolver\OptionsResolver; + +final class FixerConfigurationResolver implements FixerConfigurationResolverInterface +{ + /** + * @var list + */ + private array $options = []; + + /** + * @var list + */ + private array $registeredNames = []; + + /** + * @param iterable $options + */ + public function __construct(iterable $options) + { + $fixerOptionSorter = new FixerOptionSorter(); + + foreach ($fixerOptionSorter->sort($options) as $option) { + $this->addOption($option); + } + + if (0 === \count($this->registeredNames)) { + throw new \LogicException('Options cannot be empty.'); + } + } + + public function getOptions(): array + { + return $this->options; + } + + public function resolve(array $configuration): array + { + $resolver = new OptionsResolver(); + + foreach ($this->options as $option) { + $name = $option->getName(); + + if ($option instanceof AliasedFixerOption) { + $alias = $option->getAlias(); + + if (\array_key_exists($alias, $configuration)) { + if (\array_key_exists($name, $configuration)) { + throw new InvalidOptionsException(sprintf('Aliased option "%s"/"%s" is passed multiple times.', $name, $alias)); + } + + Utils::triggerDeprecation(new \RuntimeException(sprintf( + 'Option "%s" is deprecated, use "%s" instead.', + $alias, + $name + ))); + + $configuration[$name] = $configuration[$alias]; + unset($configuration[$alias]); + } + } + + if ($option->hasDefault()) { + $resolver->setDefault($name, $option->getDefault()); + } else { + $resolver->setRequired($name); + } + + $allowedValues = $option->getAllowedValues(); + if (null !== $allowedValues) { + foreach ($allowedValues as &$allowedValue) { + if (\is_object($allowedValue) && \is_callable($allowedValue)) { + $allowedValue = static fn (/* mixed */ $values) => $allowedValue($values); + } + } + + $resolver->setAllowedValues($name, $allowedValues); + } + + $allowedTypes = $option->getAllowedTypes(); + if (null !== $allowedTypes) { + $resolver->setAllowedTypes($name, $allowedTypes); + } + + $normalizer = $option->getNormalizer(); + if (null !== $normalizer) { + $resolver->setNormalizer($name, $normalizer); + } + } + + return $resolver->resolve($configuration); + } + + /** + * @throws \LogicException when the option is already defined + */ + private function addOption(FixerOptionInterface $option): void + { + $name = $option->getName(); + + if (\in_array($name, $this->registeredNames, true)) { + throw new \LogicException(sprintf('The "%s" option is defined multiple times.', $name)); + } + + $this->options[] = $option; + $this->registeredNames[] = $name; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerConfigurationResolverInterface.php b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerConfigurationResolverInterface.php new file mode 100644 index 00000000..284cdbd3 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerConfigurationResolverInterface.php @@ -0,0 +1,30 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\FixerConfiguration; + +interface FixerConfigurationResolverInterface +{ + /** + * @return list + */ + public function getOptions(): array; + + /** + * @param array $configuration + * + * @return array + */ + public function resolve(array $configuration): array; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOption.php b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOption.php new file mode 100644 index 00000000..2866bdc1 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOption.php @@ -0,0 +1,141 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\FixerConfiguration; + +final class FixerOption implements FixerOptionInterface +{ + private string $name; + + private string $description; + + private bool $isRequired; + + /** + * @var mixed + */ + private $default; + + /** + * @var null|list + */ + private $allowedTypes; + + /** + * @var null|list + */ + private $allowedValues; + + /** + * @var null|\Closure + */ + private $normalizer; + + /** + * @param mixed $default + * @param null|list $allowedTypes + * @param null|list $allowedValues + */ + public function __construct( + string $name, + string $description, + bool $isRequired = true, + $default = null, + ?array $allowedTypes = null, + ?array $allowedValues = null, + ?\Closure $normalizer = null + ) { + if ($isRequired && null !== $default) { + throw new \LogicException('Required options cannot have a default value.'); + } + + if (null !== $allowedValues) { + foreach ($allowedValues as &$allowedValue) { + if ($allowedValue instanceof \Closure) { + $allowedValue = $this->unbind($allowedValue); + } + } + } + + $this->name = $name; + $this->description = $description; + $this->isRequired = $isRequired; + $this->default = $default; + $this->allowedTypes = $allowedTypes; + $this->allowedValues = $allowedValues; + + if (null !== $normalizer) { + $this->normalizer = $this->unbind($normalizer); + } + } + + public function getName(): string + { + return $this->name; + } + + public function getDescription(): string + { + return $this->description; + } + + public function hasDefault(): bool + { + return !$this->isRequired; + } + + public function getDefault() + { + if (!$this->hasDefault()) { + throw new \LogicException('No default value defined.'); + } + + return $this->default; + } + + public function getAllowedTypes(): ?array + { + return $this->allowedTypes; + } + + public function getAllowedValues(): ?array + { + return $this->allowedValues; + } + + public function getNormalizer(): ?\Closure + { + return $this->normalizer; + } + + /** + * Unbinds the given closure to avoid memory leaks. + * + * The closures provided to this class were probably defined in a fixer + * class and thus bound to it by default. The configuration will then be + * stored in {@see AbstractFixer::$configurationDefinition}, leading to the + * following cyclic reference: + * + * fixer -> configuration definition -> options -> closures -> fixer + * + * This cyclic reference prevent the garbage collector to free memory as + * all elements are still referenced. + * + * See {@see https://bugs.php.net/bug.php?id=69639 Bug #69639} for details. + */ + private function unbind(\Closure $closure): \Closure + { + return $closure->bindTo(null); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOptionBuilder.php b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOptionBuilder.php new file mode 100644 index 00000000..307518dc --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOptionBuilder.php @@ -0,0 +1,131 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\FixerConfiguration; + +final class FixerOptionBuilder +{ + private string $name; + + private string $description; + + /** + * @var mixed + */ + private $default; + + private bool $isRequired = true; + + /** + * @var null|list + */ + private $allowedTypes; + + /** + * @var null|list + */ + private $allowedValues; + + /** + * @var null|\Closure + */ + private $normalizer; + + /** + * @var null|string + */ + private $deprecationMessage; + + public function __construct(string $name, string $description) + { + $this->name = $name; + $this->description = $description; + } + + /** + * @param mixed $default + * + * @return $this + */ + public function setDefault($default): self + { + $this->default = $default; + $this->isRequired = false; + + return $this; + } + + /** + * @param list $allowedTypes + * + * @return $this + */ + public function setAllowedTypes(array $allowedTypes): self + { + $this->allowedTypes = $allowedTypes; + + return $this; + } + + /** + * @param list $allowedValues + * + * @return $this + */ + public function setAllowedValues(array $allowedValues): self + { + $this->allowedValues = $allowedValues; + + return $this; + } + + /** + * @return $this + */ + public function setNormalizer(\Closure $normalizer): self + { + $this->normalizer = $normalizer; + + return $this; + } + + /** + * @return $this + */ + public function setDeprecationMessage(?string $deprecationMessage): self + { + $this->deprecationMessage = $deprecationMessage; + + return $this; + } + + public function getOption(): FixerOptionInterface + { + $option = new FixerOption( + $this->name, + $this->description, + $this->isRequired, + $this->default, + $this->allowedTypes, + $this->allowedValues, + $this->normalizer + ); + + if (null !== $this->deprecationMessage) { + $option = new DeprecatedFixerOption($option, $this->deprecationMessage); + } + + return $option; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOptionInterface.php b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOptionInterface.php new file mode 100644 index 00000000..d760e4df --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOptionInterface.php @@ -0,0 +1,43 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\FixerConfiguration; + +interface FixerOptionInterface +{ + public function getName(): string; + + public function getDescription(): string; + + public function hasDefault(): bool; + + /** + * @return mixed + * + * @throws \LogicException when no default value is defined + */ + public function getDefault(); + + /** + * @return null|list + */ + public function getAllowedTypes(): ?array; + + /** + * @return null|list + */ + public function getAllowedValues(): ?array; + + public function getNormalizer(): ?\Closure; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOptionSorter.php b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOptionSorter.php new file mode 100644 index 00000000..bb526874 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOptionSorter.php @@ -0,0 +1,37 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\FixerConfiguration; + +/** + * @internal + */ +final class FixerOptionSorter +{ + /** + * @param iterable $options + * + * @return list + */ + public function sort(iterable $options): array + { + if (!\is_array($options)) { + $options = iterator_to_array($options, false); + } + + usort($options, static fn (FixerOptionInterface $a, FixerOptionInterface $b): int => $a->getName() <=> $b->getName()); + + return $options; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/InvalidOptionsForEnvException.php b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/InvalidOptionsForEnvException.php new file mode 100644 index 00000000..4c12d5da --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/InvalidOptionsForEnvException.php @@ -0,0 +1,24 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\FixerConfiguration; + +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class InvalidOptionsForEnvException extends InvalidOptionsException {} diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/CodeSample.php b/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/CodeSample.php new file mode 100644 index 00000000..b0dd2fed --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/CodeSample.php @@ -0,0 +1,47 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\FixerDefinition; + +/** + * @author Dariusz Rumiński + */ +final class CodeSample implements CodeSampleInterface +{ + private string $code; + + /** + * @var null|array + */ + private ?array $configuration; + + /** + * @param null|array $configuration + */ + public function __construct(string $code, ?array $configuration = null) + { + $this->code = $code; + $this->configuration = $configuration; + } + + public function getCode(): string + { + return $this->code; + } + + public function getConfiguration(): ?array + { + return $this->configuration; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/CodeSampleInterface.php b/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/CodeSampleInterface.php new file mode 100644 index 00000000..9bce5eb8 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/CodeSampleInterface.php @@ -0,0 +1,28 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\FixerDefinition; + +/** + * @author Dariusz Rumiński + */ +interface CodeSampleInterface +{ + public function getCode(): string; + + /** + * @return null|array + */ + public function getConfiguration(): ?array; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/FileSpecificCodeSample.php b/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/FileSpecificCodeSample.php new file mode 100644 index 00000000..effd0e52 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/FileSpecificCodeSample.php @@ -0,0 +1,54 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\FixerDefinition; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class FileSpecificCodeSample implements FileSpecificCodeSampleInterface +{ + private CodeSampleInterface $codeSample; + + private \SplFileInfo $splFileInfo; + + /** + * @param null|array $configuration + */ + public function __construct( + string $code, + \SplFileInfo $splFileInfo, + ?array $configuration = null + ) { + $this->codeSample = new CodeSample($code, $configuration); + $this->splFileInfo = $splFileInfo; + } + + public function getCode(): string + { + return $this->codeSample->getCode(); + } + + public function getConfiguration(): ?array + { + return $this->codeSample->getConfiguration(); + } + + public function getSplFileInfo(): \SplFileInfo + { + return $this->splFileInfo; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/FileSpecificCodeSampleInterface.php b/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/FileSpecificCodeSampleInterface.php new file mode 100644 index 00000000..df6f092f --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/FileSpecificCodeSampleInterface.php @@ -0,0 +1,25 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\FixerDefinition; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +interface FileSpecificCodeSampleInterface extends CodeSampleInterface +{ + public function getSplFileInfo(): \SplFileInfo; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/FixerDefinition.php b/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/FixerDefinition.php new file mode 100644 index 00000000..f4ae9159 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/FixerDefinition.php @@ -0,0 +1,74 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\FixerDefinition; + +/** + * @author Dariusz Rumiński + */ +final class FixerDefinition implements FixerDefinitionInterface +{ + private string $summary; + + /** + * @var list + */ + private array $codeSamples; + + /** + * Description of Fixer and benefit of using it. + */ + private ?string $description; + + /** + * Description why Fixer is risky. + */ + private ?string $riskyDescription; + + /** + * @param list $codeSamples array of samples, where single sample is [code, configuration] + * @param null|string $riskyDescription null for non-risky fixer + */ + public function __construct( + string $summary, + array $codeSamples, + ?string $description = null, + ?string $riskyDescription = null + ) { + $this->summary = $summary; + $this->codeSamples = $codeSamples; + $this->description = $description; + $this->riskyDescription = $riskyDescription; + } + + public function getSummary(): string + { + return $this->summary; + } + + public function getDescription(): ?string + { + return $this->description; + } + + public function getRiskyDescription(): ?string + { + return $this->riskyDescription; + } + + public function getCodeSamples(): array + { + return $this->codeSamples; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/FixerDefinitionInterface.php b/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/FixerDefinitionInterface.php new file mode 100644 index 00000000..5bc2f934 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/FixerDefinitionInterface.php @@ -0,0 +1,37 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\FixerDefinition; + +/** + * @author Dariusz Rumiński + */ +interface FixerDefinitionInterface +{ + public function getSummary(): string; + + public function getDescription(): ?string; + + /** + * @return null|string null for non-risky fixer + */ + public function getRiskyDescription(): ?string; + + /** + * Array of samples, where single sample is [code, configuration]. + * + * @return list + */ + public function getCodeSamples(): array; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecificCodeSample.php b/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecificCodeSample.php new file mode 100644 index 00000000..f4b98111 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecificCodeSample.php @@ -0,0 +1,52 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\FixerDefinition; + +/** + * @author Andreas Möller + */ +final class VersionSpecificCodeSample implements VersionSpecificCodeSampleInterface +{ + private CodeSampleInterface $codeSample; + + private VersionSpecificationInterface $versionSpecification; + + /** + * @param null|array $configuration + */ + public function __construct( + string $code, + VersionSpecificationInterface $versionSpecification, + ?array $configuration = null + ) { + $this->codeSample = new CodeSample($code, $configuration); + $this->versionSpecification = $versionSpecification; + } + + public function getCode(): string + { + return $this->codeSample->getCode(); + } + + public function getConfiguration(): ?array + { + return $this->codeSample->getConfiguration(); + } + + public function isSuitableFor(int $version): bool + { + return $this->versionSpecification->isSatisfiedBy($version); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecificCodeSampleInterface.php b/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecificCodeSampleInterface.php new file mode 100644 index 00000000..74e4f446 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecificCodeSampleInterface.php @@ -0,0 +1,23 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\FixerDefinition; + +/** + * @author Andreas Moeller + */ +interface VersionSpecificCodeSampleInterface extends CodeSampleInterface +{ + public function isSuitableFor(int $version): bool; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecification.php b/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecification.php new file mode 100644 index 00000000..2910fe72 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecification.php @@ -0,0 +1,74 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\FixerDefinition; + +/** + * @author Andreas Möller + */ +final class VersionSpecification implements VersionSpecificationInterface +{ + /** + * @var null|int<1, max> + */ + private ?int $minimum; + + /** + * @var null|int<1, max> + */ + private ?int $maximum; + + /** + * @param null|int<1, max> $minimum + * @param null|int<1, max> $maximum + * + * @throws \InvalidArgumentException + */ + public function __construct(?int $minimum = null, ?int $maximum = null) + { + if (null === $minimum && null === $maximum) { + throw new \InvalidArgumentException('Minimum or maximum need to be specified.'); + } + + if (null !== $minimum && 1 > $minimum) { + throw new \InvalidArgumentException('Minimum needs to be either null or an integer greater than 0.'); + } + + if (null !== $maximum) { + if (1 > $maximum) { + throw new \InvalidArgumentException('Maximum needs to be either null or an integer greater than 0.'); + } + + if (null !== $minimum && $maximum < $minimum) { + throw new \InvalidArgumentException('Maximum should not be lower than the minimum.'); + } + } + + $this->minimum = $minimum; + $this->maximum = $maximum; + } + + public function isSatisfiedBy(int $version): bool + { + if (null !== $this->minimum && $version < $this->minimum) { + return false; + } + + if (null !== $this->maximum && $version > $this->maximum) { + return false; + } + + return true; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecificationInterface.php b/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecificationInterface.php new file mode 100644 index 00000000..502cdc4a --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecificationInterface.php @@ -0,0 +1,23 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\FixerDefinition; + +/** + * @author Andreas Möller + */ +interface VersionSpecificationInterface +{ + public function isSatisfiedBy(int $version): bool; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerFactory.php b/vendor/friendsofphp/php-cs-fixer/src/FixerFactory.php new file mode 100644 index 00000000..c49c6146 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerFactory.php @@ -0,0 +1,241 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer; + +use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\FixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\RuleSet\RuleSetInterface; +use Symfony\Component\Finder\Finder as SymfonyFinder; +use Symfony\Component\Finder\SplFileInfo; + +/** + * Class provides a way to create a group of fixers. + * + * Fixers may be registered (made the factory aware of them) by + * registering a custom fixer and default, built in fixers. + * Then, one can attach Config instance to fixer instances. + * + * Finally factory creates a ready to use group of fixers. + * + * @author Dariusz Rumiński + * + * @internal + */ +final class FixerFactory +{ + private FixerNameValidator $nameValidator; + + /** + * @var list + */ + private array $fixers = []; + + /** + * @var array + */ + private array $fixersByName = []; + + public function __construct() + { + $this->nameValidator = new FixerNameValidator(); + } + + public function setWhitespacesConfig(WhitespacesFixerConfig $config): self + { + foreach ($this->fixers as $fixer) { + if ($fixer instanceof WhitespacesAwareFixerInterface) { + $fixer->setWhitespacesConfig($config); + } + } + + return $this; + } + + /** + * @return list + */ + public function getFixers(): array + { + $this->fixers = Utils::sortFixers($this->fixers); + + return $this->fixers; + } + + /** + * @return $this + */ + public function registerBuiltInFixers(): self + { + static $builtInFixers = null; + + if (null === $builtInFixers) { + /** @var list> */ + $builtInFixers = []; + + /** @var SplFileInfo $file */ + foreach (SymfonyFinder::create()->files()->in(__DIR__.'/Fixer')->name('*Fixer.php')->depth(1) as $file) { + $relativeNamespace = $file->getRelativePath(); + $fixerClass = 'PhpCsFixer\\Fixer\\'.('' !== $relativeNamespace ? $relativeNamespace.'\\' : '').$file->getBasename('.php'); + $builtInFixers[] = $fixerClass; + } + } + + foreach ($builtInFixers as $class) { + /** @var FixerInterface */ + $fixer = new $class(); + $this->registerFixer($fixer, false); + } + + return $this; + } + + /** + * @param FixerInterface[] $fixers + * + * @return $this + */ + public function registerCustomFixers(iterable $fixers): self + { + foreach ($fixers as $fixer) { + $this->registerFixer($fixer, true); + } + + return $this; + } + + /** + * @return $this + */ + public function registerFixer(FixerInterface $fixer, bool $isCustom): self + { + $name = $fixer->getName(); + + if (isset($this->fixersByName[$name])) { + throw new \UnexpectedValueException(sprintf('Fixer named "%s" is already registered.', $name)); + } + + if (!$this->nameValidator->isValid($name, $isCustom)) { + throw new \UnexpectedValueException(sprintf('Fixer named "%s" has invalid name.', $name)); + } + + $this->fixers[] = $fixer; + $this->fixersByName[$name] = $fixer; + + return $this; + } + + /** + * Apply RuleSet on fixers to filter out all unwanted fixers. + * + * @return $this + */ + public function useRuleSet(RuleSetInterface $ruleSet): self + { + $fixers = []; + $fixersByName = []; + $fixerConflicts = []; + + $fixerNames = array_keys($ruleSet->getRules()); + foreach ($fixerNames as $name) { + if (!\array_key_exists($name, $this->fixersByName)) { + throw new \UnexpectedValueException(sprintf('Rule "%s" does not exist.', $name)); + } + + $fixer = $this->fixersByName[$name]; + $config = $ruleSet->getRuleConfiguration($name); + + if (null !== $config) { + if ($fixer instanceof ConfigurableFixerInterface) { + if (\count($config) < 1) { + throw new InvalidFixerConfigurationException($fixer->getName(), 'Configuration must be an array and may not be empty.'); + } + + $fixer->configure($config); + } else { + throw new InvalidFixerConfigurationException($fixer->getName(), 'Is not configurable.'); + } + } + + $fixers[] = $fixer; + $fixersByName[$name] = $fixer; + $conflicts = array_intersect($this->getFixersConflicts($fixer), $fixerNames); + + if (\count($conflicts) > 0) { + $fixerConflicts[$name] = $conflicts; + } + } + + if (\count($fixerConflicts) > 0) { + throw new \UnexpectedValueException($this->generateConflictMessage($fixerConflicts)); + } + + $this->fixers = $fixers; + $this->fixersByName = $fixersByName; + + return $this; + } + + /** + * Check if fixer exists. + */ + public function hasRule(string $name): bool + { + return isset($this->fixersByName[$name]); + } + + /** + * @return list + */ + private function getFixersConflicts(FixerInterface $fixer): array + { + static $conflictMap = [ + 'blank_lines_before_namespace' => [ + 'no_blank_lines_before_namespace', + 'single_blank_line_before_namespace', + ], + 'no_blank_lines_before_namespace' => ['single_blank_line_before_namespace'], + 'single_import_per_statement' => ['group_import'], + ]; + + $fixerName = $fixer->getName(); + + return \array_key_exists($fixerName, $conflictMap) ? $conflictMap[$fixerName] : []; + } + + /** + * @param array $fixerConflicts + */ + private function generateConflictMessage(array $fixerConflicts): string + { + $message = 'Rule contains conflicting fixers:'; + $report = []; + + foreach ($fixerConflicts as $fixer => $fixers) { + // filter mutual conflicts + $report[$fixer] = array_filter( + $fixers, + static fn (string $candidate): bool => !\array_key_exists($candidate, $report) || !\in_array($fixer, $report[$candidate], true) + ); + + if (\count($report[$fixer]) > 0) { + $message .= sprintf("\n- \"%s\" with %s", $fixer, Utils::naturalLanguageJoin($report[$fixer])); + } + } + + return $message; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerFileProcessedEvent.php b/vendor/friendsofphp/php-cs-fixer/src/FixerFileProcessedEvent.php new file mode 100644 index 00000000..b609da49 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerFileProcessedEvent.php @@ -0,0 +1,51 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer; + +use Symfony\Contracts\EventDispatcher\Event; + +/** + * Event that is fired when file was processed by Fixer. + * + * @author Dariusz Rumiński + * + * @internal + */ +final class FixerFileProcessedEvent extends Event +{ + /** + * Event name. + */ + public const NAME = 'fixer.file_processed'; + + public const STATUS_INVALID = 1; + public const STATUS_SKIPPED = 2; + public const STATUS_NO_CHANGES = 3; + public const STATUS_FIXED = 4; + public const STATUS_EXCEPTION = 5; + public const STATUS_LINT = 6; + + private int $status; + + public function __construct(int $status) + { + $this->status = $status; + } + + public function getStatus(): int + { + return $this->status; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerNameValidator.php b/vendor/friendsofphp/php-cs-fixer/src/FixerNameValidator.php new file mode 100644 index 00000000..8f19cb06 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerNameValidator.php @@ -0,0 +1,32 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class FixerNameValidator +{ + public function isValid(string $name, bool $isCustom): bool + { + if (!$isCustom) { + return Preg::match('/^[a-z][a-z0-9_]*$/', $name); + } + + return Preg::match('/^[A-Z][a-zA-Z0-9]*\/[a-z][a-z0-9_]*$/', $name); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Indicator/PhpUnitTestCaseIndicator.php b/vendor/friendsofphp/php-cs-fixer/src/Indicator/PhpUnitTestCaseIndicator.php new file mode 100644 index 00000000..44d05c27 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Indicator/PhpUnitTestCaseIndicator.php @@ -0,0 +1,90 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Indicator; + +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @internal + */ +final class PhpUnitTestCaseIndicator +{ + public function isPhpUnitClass(Tokens $tokens, int $index): bool + { + if (!$tokens[$index]->isGivenKind(T_CLASS)) { + throw new \LogicException(sprintf('No "T_CLASS" at given index %d, got "%s".', $index, $tokens[$index]->getName())); + } + + $index = $tokens->getNextMeaningfulToken($index); + + if (!$tokens[$index]->isGivenKind(T_STRING)) { + return false; + } + + $extendsIndex = $tokens->getNextTokenOfKind($index, ['{', [T_EXTENDS]]); + + if (!$tokens[$extendsIndex]->isGivenKind(T_EXTENDS)) { + return false; + } + + if (Preg::match('/(?:Test|TestCase)$/', $tokens[$index]->getContent())) { + return true; + } + + while (null !== $index = $tokens->getNextMeaningfulToken($index)) { + if ($tokens[$index]->equals('{')) { + break; // end of class signature + } + + if (!$tokens[$index]->isGivenKind(T_STRING)) { + continue; // not part of extends nor part of implements; so continue + } + + if (Preg::match('/(?:Test|TestCase)(?:Interface)?$/', $tokens[$index]->getContent())) { + return true; + } + } + + return false; + } + + /** + * Returns an indices of PHPUnit classes in reverse appearance order. + * Order is important - it's reverted, so if we inject tokens into collection, + * we do it for bottom of file first, and then to the top of the file, so we + * mitigate risk of not visiting whole collections (final indices). + * + * @return iterable array of [int start, int end] indices from later to earlier classes + */ + public function findPhpUnitClasses(Tokens $tokens): iterable + { + for ($index = $tokens->count() - 1; $index > 0; --$index) { + if (!$tokens[$index]->isGivenKind(T_CLASS) || !$this->isPhpUnitClass($tokens, $index)) { + continue; + } + + $startIndex = $tokens->getNextTokenOfKind($index, ['{']); + + if (null === $startIndex) { + return; + } + + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $startIndex); + + yield [$startIndex, $endIndex]; + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Linter/CachingLinter.php b/vendor/friendsofphp/php-cs-fixer/src/Linter/CachingLinter.php new file mode 100644 index 00000000..b8590600 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Linter/CachingLinter.php @@ -0,0 +1,62 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Linter; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class CachingLinter implements LinterInterface +{ + private LinterInterface $sublinter; + + /** + * @var array + */ + private array $cache = []; + + public function __construct(LinterInterface $linter) + { + $this->sublinter = $linter; + } + + public function isAsync(): bool + { + return $this->sublinter->isAsync(); + } + + public function lintFile(string $path): LintingResultInterface + { + $checksum = md5(file_get_contents($path)); + + if (!isset($this->cache[$checksum])) { + $this->cache[$checksum] = $this->sublinter->lintFile($path); + } + + return $this->cache[$checksum]; + } + + public function lintSource(string $source): LintingResultInterface + { + $checksum = md5($source); + + if (!isset($this->cache[$checksum])) { + $this->cache[$checksum] = $this->sublinter->lintSource($source); + } + + return $this->cache[$checksum]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Linter/Linter.php b/vendor/friendsofphp/php-cs-fixer/src/Linter/Linter.php new file mode 100644 index 00000000..b8619b44 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Linter/Linter.php @@ -0,0 +1,47 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Linter; + +/** + * Handle PHP code linting process. + * + * @author Dariusz Rumiński + * + * @internal + */ +final class Linter implements LinterInterface +{ + private LinterInterface $subLinter; + + public function __construct() + { + $this->subLinter = new TokenizerLinter(); + } + + public function isAsync(): bool + { + return $this->subLinter->isAsync(); + } + + public function lintFile(string $path): LintingResultInterface + { + return $this->subLinter->lintFile($path); + } + + public function lintSource(string $source): LintingResultInterface + { + return $this->subLinter->lintSource($source); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Linter/LinterInterface.php b/vendor/friendsofphp/php-cs-fixer/src/Linter/LinterInterface.php new file mode 100644 index 00000000..1b5c2db6 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Linter/LinterInterface.php @@ -0,0 +1,35 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Linter; + +/** + * Interface for PHP code linting process manager. + * + * @author Dariusz Rumiński + */ +interface LinterInterface +{ + public function isAsync(): bool; + + /** + * Lint PHP file. + */ + public function lintFile(string $path): LintingResultInterface; + + /** + * Lint PHP code. + */ + public function lintSource(string $source): LintingResultInterface; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Linter/LintingException.php b/vendor/friendsofphp/php-cs-fixer/src/Linter/LintingException.php new file mode 100644 index 00000000..1874bac1 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Linter/LintingException.php @@ -0,0 +1,24 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Linter; + +/** + * @author Dariusz Rumiński + * + * @final + * + * @TODO 4.0 make class "final" + */ +class LintingException extends \RuntimeException {} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Linter/LintingResultInterface.php b/vendor/friendsofphp/php-cs-fixer/src/Linter/LintingResultInterface.php new file mode 100644 index 00000000..855d695b --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Linter/LintingResultInterface.php @@ -0,0 +1,26 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Linter; + +/** + * @author Dariusz Rumiński + */ +interface LintingResultInterface +{ + /** + * Check if linting process was successful and raise LintingException if not. + */ + public function check(): void; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Linter/ProcessLinter.php b/vendor/friendsofphp/php-cs-fixer/src/Linter/ProcessLinter.php new file mode 100644 index 00000000..7dd42b78 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Linter/ProcessLinter.php @@ -0,0 +1,151 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Linter; + +use PhpCsFixer\FileReader; +use PhpCsFixer\FileRemoval; +use Symfony\Component\Filesystem\Exception\IOException; +use Symfony\Component\Process\PhpExecutableFinder; +use Symfony\Component\Process\Process; + +/** + * Handle PHP code linting using separated process of `php -l _file_`. + * + * @author Dariusz Rumiński + * + * @internal + */ +final class ProcessLinter implements LinterInterface +{ + private FileRemoval $fileRemoval; + + private ProcessLinterProcessBuilder $processBuilder; + + /** + * Temporary file for code linting. + * + * @var null|string + */ + private $temporaryFile; + + /** + * @param null|string $executable PHP executable, null for autodetection + */ + public function __construct(?string $executable = null) + { + if (null === $executable) { + $executableFinder = new PhpExecutableFinder(); + $executable = $executableFinder->find(false); + + if (false === $executable) { + throw new UnavailableLinterException('Cannot find PHP executable.'); + } + + if ('phpdbg' === \PHP_SAPI) { + if (!str_contains($executable, 'phpdbg')) { + throw new UnavailableLinterException('Automatically found PHP executable is non-standard phpdbg. Could not find proper PHP executable.'); + } + + // automatically found executable is `phpdbg`, let us try to fallback to regular `php` + $executable = str_replace('phpdbg', 'php', $executable); + + if (!is_executable($executable)) { + throw new UnavailableLinterException('Automatically found PHP executable is phpdbg. Could not find proper PHP executable.'); + } + } + } + + $this->processBuilder = new ProcessLinterProcessBuilder($executable); + $this->fileRemoval = new FileRemoval(); + } + + public function __destruct() + { + if (null !== $this->temporaryFile) { + $this->fileRemoval->delete($this->temporaryFile); + } + } + + /** + * This class is not intended to be serialized, + * and cannot be deserialized (see __wakeup method). + */ + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.self::class); + } + + /** + * Disable the deserialization of the class to prevent attacker executing + * code by leveraging the __destruct method. + * + * @see https://owasp.org/www-community/vulnerabilities/PHP_Object_Injection + */ + public function __wakeup(): void + { + throw new \BadMethodCallException('Cannot unserialize '.self::class); + } + + public function isAsync(): bool + { + return true; + } + + public function lintFile(string $path): LintingResultInterface + { + return new ProcessLintingResult($this->createProcessForFile($path), $path); + } + + public function lintSource(string $source): LintingResultInterface + { + return new ProcessLintingResult($this->createProcessForSource($source), $this->temporaryFile); + } + + /** + * @param string $path path to file + */ + private function createProcessForFile(string $path): Process + { + // in case php://stdin + if (!is_file($path)) { + return $this->createProcessForSource(FileReader::createSingleton()->read($path)); + } + + $process = $this->processBuilder->build($path); + $process->setTimeout(10); + $process->start(); + + return $process; + } + + /** + * Create process that lint PHP code. + * + * @param string $source code + */ + private function createProcessForSource(string $source): Process + { + if (null === $this->temporaryFile) { + $this->temporaryFile = tempnam(sys_get_temp_dir(), 'cs_fixer_tmp_'); + $this->fileRemoval->observe($this->temporaryFile); + } + + if (false === @file_put_contents($this->temporaryFile, $source)) { + throw new IOException(sprintf('Failed to write file "%s".', $this->temporaryFile), 0, null, $this->temporaryFile); + } + + return $this->createProcessForFile($this->temporaryFile); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Linter/ProcessLinterProcessBuilder.php b/vendor/friendsofphp/php-cs-fixer/src/Linter/ProcessLinterProcessBuilder.php new file mode 100644 index 00000000..ee9a555e --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Linter/ProcessLinterProcessBuilder.php @@ -0,0 +1,44 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Linter; + +use Symfony\Component\Process\Process; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class ProcessLinterProcessBuilder +{ + private string $executable; + + /** + * @param string $executable PHP executable + */ + public function __construct(string $executable) + { + $this->executable = $executable; + } + + public function build(string $path): Process + { + return new Process([ + $this->executable, + '-l', + $path, + ]); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Linter/ProcessLintingResult.php b/vendor/friendsofphp/php-cs-fixer/src/Linter/ProcessLintingResult.php new file mode 100644 index 00000000..7d282d16 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Linter/ProcessLintingResult.php @@ -0,0 +1,86 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Linter; + +use Symfony\Component\Process\Process; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class ProcessLintingResult implements LintingResultInterface +{ + private Process $process; + + private ?string $path; + + private ?bool $isSuccessful = null; + + public function __construct(Process $process, ?string $path = null) + { + $this->process = $process; + $this->path = $path; + } + + public function check(): void + { + if (!$this->isSuccessful()) { + // on some systems stderr is used, but on others, it's not + throw new LintingException($this->getProcessErrorMessage(), $this->process->getExitCode()); + } + } + + private function getProcessErrorMessage(): string + { + $errorOutput = $this->process->getErrorOutput(); + $output = strtok(ltrim('' !== $errorOutput ? $errorOutput : $this->process->getOutput()), "\n"); + + if (false === $output) { + return 'Fatal error: Unable to lint file.'; + } + + if (null !== $this->path) { + $needle = sprintf('in %s ', $this->path); + $pos = strrpos($output, $needle); + + if (false !== $pos) { + $output = sprintf('%s%s', substr($output, 0, $pos), substr($output, $pos + \strlen($needle))); + } + } + + $prefix = substr($output, 0, 18); + + if ('PHP Parse error: ' === $prefix) { + return sprintf('Parse error: %s.', substr($output, 18)); + } + + if ('PHP Fatal error: ' === $prefix) { + return sprintf('Fatal error: %s.', substr($output, 18)); + } + + return sprintf('%s.', $output); + } + + private function isSuccessful(): bool + { + if (null === $this->isSuccessful) { + $this->process->wait(); + $this->isSuccessful = $this->process->isSuccessful(); + } + + return $this->isSuccessful; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Linter/TokenizerLinter.php b/vendor/friendsofphp/php-cs-fixer/src/Linter/TokenizerLinter.php new file mode 100644 index 00000000..b0920d30 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Linter/TokenizerLinter.php @@ -0,0 +1,56 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Linter; + +use PhpCsFixer\FileReader; +use PhpCsFixer\Tokenizer\CodeHasher; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Handle PHP code linting. + * + * @author Dariusz Rumiński + * + * @internal + */ +final class TokenizerLinter implements LinterInterface +{ + public function isAsync(): bool + { + return false; + } + + public function lintFile(string $path): LintingResultInterface + { + return $this->lintSource(FileReader::createSingleton()->read($path)); + } + + public function lintSource(string $source): LintingResultInterface + { + try { + // To lint, we will parse the source into Tokens. + // During that process, it might throw a ParseError or CompileError. + // If it won't, cache of tokenized version of source will be kept, which is great for Runner. + // Yet, first we need to clear already existing cache to not hit it and lint the code indeed. + $codeHash = CodeHasher::calculateCodeHash($source); + Tokens::clearCache($codeHash); + Tokens::fromCode($source); + + return new TokenizerLintingResult(); + } catch (\CompileError|\ParseError $e) { + return new TokenizerLintingResult($e); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Linter/TokenizerLintingResult.php b/vendor/friendsofphp/php-cs-fixer/src/Linter/TokenizerLintingResult.php new file mode 100644 index 00000000..4880d928 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Linter/TokenizerLintingResult.php @@ -0,0 +1,46 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Linter; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class TokenizerLintingResult implements LintingResultInterface +{ + private ?\Error $error; + + public function __construct(?\Error $error = null) + { + $this->error = $error; + } + + public function check(): void + { + if (null !== $this->error) { + throw new LintingException( + sprintf('%s: %s on line %d.', $this->getMessagePrefix(), $this->error->getMessage(), $this->error->getLine()), + $this->error->getCode(), + $this->error + ); + } + } + + private function getMessagePrefix(): string + { + return $this->error instanceof \ParseError ? 'Parse error' : 'Fatal error'; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Linter/UnavailableLinterException.php b/vendor/friendsofphp/php-cs-fixer/src/Linter/UnavailableLinterException.php new file mode 100644 index 00000000..df67b5f0 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Linter/UnavailableLinterException.php @@ -0,0 +1,26 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Linter; + +/** + * Exception that is thrown when the chosen linter is not available on the environment. + * + * @author Dariusz Rumiński + * + * @final + * + * @TODO 4.0 make class "final" + */ +class UnavailableLinterException extends \RuntimeException {} diff --git a/vendor/friendsofphp/php-cs-fixer/src/PharChecker.php b/vendor/friendsofphp/php-cs-fixer/src/PharChecker.php new file mode 100644 index 00000000..b7cb356c --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/PharChecker.php @@ -0,0 +1,38 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer; + +/** + * @internal + */ +final class PharChecker implements PharCheckerInterface +{ + public function checkFileValidity(string $filename): ?string + { + try { + $phar = new \Phar($filename); + // free the variable to unlock the file + unset($phar); + } catch (\Exception $e) { + if (!$e instanceof \UnexpectedValueException && !$e instanceof \PharException) { + throw $e; + } + + return 'Failed to create Phar instance. '.$e->getMessage(); + } + + return null; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/PharCheckerInterface.php b/vendor/friendsofphp/php-cs-fixer/src/PharCheckerInterface.php new file mode 100644 index 00000000..6ae22088 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/PharCheckerInterface.php @@ -0,0 +1,26 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer; + +/** + * @internal + */ +interface PharCheckerInterface +{ + /** + * @return null|string the invalidity reason if any, null otherwise + */ + public function checkFileValidity(string $filename): ?string; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Preg.php b/vendor/friendsofphp/php-cs-fixer/src/Preg.php new file mode 100644 index 00000000..8d8a6112 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Preg.php @@ -0,0 +1,203 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer; + +/** + * This class replaces preg_* functions to better handling UTF8 strings, + * ensuring no matter "u" modifier is present or absent subject will be handled correctly. + * + * @author Kuba Werłos + * + * @internal + */ +final class Preg +{ + /** + * @param null|string[] $matches + * @param int-mask<0, 256, 512> $flags + * + * @throws PregException + */ + public static function match(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool + { + $result = @preg_match(self::addUtf8Modifier($pattern), $subject, $matches, $flags, $offset); + if (false !== $result && PREG_NO_ERROR === preg_last_error()) { + return 1 === $result; + } + + $result = @preg_match(self::removeUtf8Modifier($pattern), $subject, $matches, $flags, $offset); + if (false !== $result && PREG_NO_ERROR === preg_last_error()) { + return 1 === $result; + } + + throw self::newPregException(preg_last_error(), preg_last_error_msg(), __METHOD__, (array) $pattern); + } + + /** + * @param null|string[] $matches + * + * @throws PregException + */ + public static function matchAll(string $pattern, string $subject, ?array &$matches = null, int $flags = PREG_PATTERN_ORDER, int $offset = 0): int + { + $result = @preg_match_all(self::addUtf8Modifier($pattern), $subject, $matches, $flags, $offset); + if (false !== $result && PREG_NO_ERROR === preg_last_error()) { + return $result; + } + + $result = @preg_match_all(self::removeUtf8Modifier($pattern), $subject, $matches, $flags, $offset); + if (false !== $result && PREG_NO_ERROR === preg_last_error()) { + return $result; + } + + throw self::newPregException(preg_last_error(), preg_last_error_msg(), __METHOD__, (array) $pattern); + } + + /** + * @param string|string[] $subject + * + * @throws PregException + */ + public static function replace(string $pattern, string $replacement, $subject, int $limit = -1, ?int &$count = null): string + { + $result = @preg_replace(self::addUtf8Modifier($pattern), $replacement, $subject, $limit, $count); + if (null !== $result && PREG_NO_ERROR === preg_last_error()) { + return $result; + } + + $result = @preg_replace(self::removeUtf8Modifier($pattern), $replacement, $subject, $limit, $count); + if (null !== $result && PREG_NO_ERROR === preg_last_error()) { + return $result; + } + + throw self::newPregException(preg_last_error(), preg_last_error_msg(), __METHOD__, (array) $pattern); + } + + /** + * @throws PregException + */ + public static function replaceCallback(string $pattern, callable $callback, string $subject, int $limit = -1, ?int &$count = null): string + { + $result = @preg_replace_callback(self::addUtf8Modifier($pattern), $callback, $subject, $limit, $count); + if (null !== $result && PREG_NO_ERROR === preg_last_error()) { + return $result; + } + + $result = @preg_replace_callback(self::removeUtf8Modifier($pattern), $callback, $subject, $limit, $count); + if (null !== $result && PREG_NO_ERROR === preg_last_error()) { + return $result; + } + + throw self::newPregException(preg_last_error(), preg_last_error_msg(), __METHOD__, (array) $pattern); + } + + /** + * @return string[] + * + * @throws PregException + */ + public static function split(string $pattern, string $subject, int $limit = -1, int $flags = 0): array + { + $result = @preg_split(self::addUtf8Modifier($pattern), $subject, $limit, $flags); + if (false !== $result && PREG_NO_ERROR === preg_last_error()) { + return $result; + } + + $result = @preg_split(self::removeUtf8Modifier($pattern), $subject, $limit, $flags); + if (false !== $result && PREG_NO_ERROR === preg_last_error()) { + return $result; + } + + throw self::newPregException(preg_last_error(), preg_last_error_msg(), __METHOD__, (array) $pattern); + } + + /** + * @param string|string[] $pattern + * + * @return string|string[] + */ + private static function addUtf8Modifier($pattern) + { + if (\is_array($pattern)) { + return array_map(__METHOD__, $pattern); + } + + return $pattern.'u'; + } + + /** + * @param string|string[] $pattern + * + * @return string|string[] + */ + private static function removeUtf8Modifier($pattern) + { + if (\is_array($pattern)) { + return array_map(__METHOD__, $pattern); + } + + if ('' === $pattern) { + return ''; + } + + $delimiter = $pattern[0]; + + $endDelimiterPosition = strrpos($pattern, $delimiter); + + return substr($pattern, 0, $endDelimiterPosition).str_replace('u', '', substr($pattern, $endDelimiterPosition)); + } + + /** + * Create PregException. + * + * Create the generic PregException message and if possible due to finding + * an invalid pattern, tell more about such kind of error in the message. + * + * @param string[] $patterns + */ + private static function newPregException(int $error, string $errorMsg, string $method, array $patterns): PregException + { + foreach ($patterns as $pattern) { + $result = null; + $errorMessage = null; + + try { + $result = ExecutorWithoutErrorHandler::execute(static fn () => preg_match($pattern, '')); + } catch (ExecutorWithoutErrorHandlerException $e) { + $result = false; + $errorMessage = $e->getMessage(); + } + + if (false !== $result) { + continue; + } + + $code = preg_last_error(); + + $message = sprintf( + '(code: %d) %s', + $code, + preg_replace('~preg_[a-z_]+[()]{2}: ~', '', $errorMessage) + ); + + return new PregException( + sprintf('%s(): Invalid PCRE pattern "%s": %s (version: %s)', $method, $pattern, $message, PCRE_VERSION), + $code + ); + } + + return new PregException(sprintf('Error occurred when calling %s: %s.', $method, $errorMsg), $error); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/PregException.php b/vendor/friendsofphp/php-cs-fixer/src/PregException.php new file mode 100644 index 00000000..57519e8a --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/PregException.php @@ -0,0 +1,24 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer; + +/** + * Exception that is thrown when PCRE function encounters an error. + * + * @author Kuba Werłos + * + * @internal + */ +final class PregException extends \RuntimeException {} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/AbstractMigrationSetDescription.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/AbstractMigrationSetDescription.php new file mode 100644 index 00000000..57e1911b --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/AbstractMigrationSetDescription.php @@ -0,0 +1,38 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet; + +use PhpCsFixer\Preg; + +/** + * @internal + */ +abstract class AbstractMigrationSetDescription extends AbstractRuleSetDescription +{ + public function getDescription(): string + { + $name = $this->getName(); + + if (Preg::match('#^@PHPUnit(\d+)(\d)Migration.*$#', $name, $matches)) { + return sprintf('Rules to improve tests code for PHPUnit %d.%d compatibility.', $matches[1], $matches[2]); + } + + if (Preg::match('#^@PHP([\d]{2})Migration.*$#', $name, $matches)) { + return sprintf('Rules to improve code for PHP %d.%d compatibility.', $matches[1][0], $matches[1][1]); + } + + throw new \RuntimeException(sprintf('Cannot generate description for "%s" "%s".', static::class, $name)); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/AbstractRuleSetDescription.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/AbstractRuleSetDescription.php new file mode 100644 index 00000000..db565b41 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/AbstractRuleSetDescription.php @@ -0,0 +1,35 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet; + +/** + * @internal + */ +abstract class AbstractRuleSetDescription implements RuleSetDescriptionInterface +{ + public function __construct() {} + + public function getName(): string + { + $name = substr(static::class, 1 + strrpos(static::class, '\\'), -3); + + return '@'.str_replace('Risky', ':risky', $name); + } + + public function isRisky(): bool + { + return str_contains(static::class, 'Risky'); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/DeprecatedRuleSetDescriptionInterface.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/DeprecatedRuleSetDescriptionInterface.php new file mode 100644 index 00000000..ae52441d --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/DeprecatedRuleSetDescriptionInterface.php @@ -0,0 +1,28 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet; + +/** + * @author Greg Korba + */ +interface DeprecatedRuleSetDescriptionInterface extends RuleSetDescriptionInterface +{ + /** + * Returns names of rule sets to use instead, if any. + * + * @return list + */ + public function getSuccessorsNames(): array; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/RuleSet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/RuleSet.php new file mode 100644 index 00000000..6fb130d3 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/RuleSet.php @@ -0,0 +1,152 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet; + +use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException; +use PhpCsFixer\Utils; + +/** + * Set of rules to be used by fixer. + * + * @author Dariusz Rumiński + * + * @internal + */ +final class RuleSet implements RuleSetInterface +{ + /** + * Group of rules generated from input set. + * + * The key is name of rule, value is configuration array or true. + * The key must not point to any set. + * + * @var array|true> + */ + private array $rules; + + public function __construct(array $set = []) + { + foreach ($set as $name => $value) { + if ('' === $name) { + throw new \InvalidArgumentException('Rule/set name must not be empty.'); + } + + if (\is_int($name)) { + throw new \InvalidArgumentException(sprintf('Missing value for "%s" rule/set.', $value)); + } + + if (!\is_bool($value) && !\is_array($value)) { + $message = str_starts_with($name, '@') ? 'Set must be enabled (true) or disabled (false). Other values are not allowed.' : 'Rule must be enabled (true), disabled (false) or configured (non-empty, assoc array). Other values are not allowed.'; + + if (null === $value) { + $message .= ' To disable the '.(str_starts_with($name, '@') ? 'set' : 'rule').', use "FALSE" instead of "NULL".'; + } + + throw new InvalidFixerConfigurationException($name, $message); + } + } + + $this->resolveSet($set); + } + + public function hasRule(string $rule): bool + { + return \array_key_exists($rule, $this->rules); + } + + public function getRuleConfiguration(string $rule): ?array + { + if (!$this->hasRule($rule)) { + throw new \InvalidArgumentException(sprintf('Rule "%s" is not in the set.', $rule)); + } + + if (true === $this->rules[$rule]) { + return null; + } + + return $this->rules[$rule]; + } + + public function getRules(): array + { + return $this->rules; + } + + /** + * Resolve input set into group of rules. + * + * @param array|bool> $rules + */ + private function resolveSet(array $rules): void + { + $resolvedRules = []; + + // expand sets + foreach ($rules as $name => $value) { + if (str_starts_with($name, '@')) { + if (!\is_bool($value)) { + throw new \UnexpectedValueException(sprintf('Nested rule set "%s" configuration must be a boolean.', $name)); + } + + $set = $this->resolveSubset($name, $value); + $resolvedRules = array_merge($resolvedRules, $set); + } else { + $resolvedRules[$name] = $value; + } + } + + // filter out all resolvedRules that are off + $resolvedRules = array_filter($resolvedRules); + + $this->rules = $resolvedRules; + } + + /** + * Resolve set rules as part of another set. + * + * If set value is false then disable all fixers in set, + * if not then get value from set item. + * + * @return array|bool> + */ + private function resolveSubset(string $setName, bool $setValue): array + { + $ruleSet = RuleSets::getSetDefinition($setName); + + if ($ruleSet instanceof DeprecatedRuleSetDescriptionInterface) { + $messageEnd = [] === $ruleSet->getSuccessorsNames() + ? 'No replacement available' + : sprintf('Use %s instead', Utils::naturalLanguageJoin($ruleSet->getSuccessorsNames())); + + Utils::triggerDeprecation(new \RuntimeException("Rule set \"{$setName}\" is deprecated. {$messageEnd}.")); + } + + $rules = $ruleSet->getRules(); + + foreach ($rules as $name => $value) { + if (str_starts_with($name, '@')) { + $set = $this->resolveSubset($name, $setValue); + unset($rules[$name]); + $rules = array_merge($rules, $set); + } elseif (!$setValue) { + $rules[$name] = false; + } else { + $rules[$name] = $value; + } + } + + return $rules; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/RuleSetDescriptionInterface.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/RuleSetDescriptionInterface.php new file mode 100644 index 00000000..156aed3a --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/RuleSetDescriptionInterface.php @@ -0,0 +1,34 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet; + +/** + * @internal + */ +interface RuleSetDescriptionInterface +{ + public function getDescription(): string; + + public function getName(): string; + + /** + * Get all rules from rules set. + * + * @return array|bool> + */ + public function getRules(): array; + + public function isRisky(): bool; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/RuleSetInterface.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/RuleSetInterface.php new file mode 100644 index 00000000..02424825 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/RuleSetInterface.php @@ -0,0 +1,49 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet; + +/** + * Set of rules to be used by fixer. + * + * Example of set: ["@PSR2" => true, "@PSR1" => false, "strict" => true]. + * + * @author Dariusz Rumiński + */ +interface RuleSetInterface +{ + /** + * @param array|bool> $set + */ + public function __construct(array $set = []); + + /** + * Get configuration for given rule. + * + * @return null|array + */ + public function getRuleConfiguration(string $rule): ?array; + + /** + * Get all rules from rules set. + * + * @return array|true> + */ + public function getRules(): array; + + /** + * Check given rule is in rules set. + */ + public function hasRule(string $rule): bool; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/RuleSets.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/RuleSets.php new file mode 100644 index 00000000..54cf2769 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/RuleSets.php @@ -0,0 +1,70 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet; + +use Symfony\Component\Finder\Finder; + +/** + * Set of rule sets to be used by fixer. + * + * @internal + */ +final class RuleSets +{ + /** + * @var array + */ + private static $setDefinitions; + + /** + * @return array + */ + public static function getSetDefinitions(): array + { + if (null === self::$setDefinitions) { + self::$setDefinitions = []; + + foreach (Finder::create()->files()->in(__DIR__.'/Sets') as $file) { + $class = 'PhpCsFixer\RuleSet\Sets\\'.$file->getBasename('.php'); + $set = new $class(); + + self::$setDefinitions[$set->getName()] = $set; + } + + uksort(self::$setDefinitions, static fn (string $x, string $y): int => strnatcmp($x, $y)); + } + + return self::$setDefinitions; + } + + /** + * @return list + */ + public static function getSetDefinitionNames(): array + { + return array_keys(self::getSetDefinitions()); + } + + public static function getSetDefinition(string $name): RuleSetDescriptionInterface + { + $definitions = self::getSetDefinitions(); + + if (!isset($definitions[$name])) { + throw new \InvalidArgumentException(sprintf('Set "%s" does not exist.', $name)); + } + + return $definitions[$name]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/DoctrineAnnotationSet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/DoctrineAnnotationSet.php new file mode 100644 index 00000000..53535465 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/DoctrineAnnotationSet.php @@ -0,0 +1,42 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractRuleSetDescription; + +/** + * @internal + */ +final class DoctrineAnnotationSet extends AbstractRuleSetDescription +{ + public function getRules(): array + { + return [ + 'doctrine_annotation_array_assignment' => [ + 'operator' => ':', + ], + 'doctrine_annotation_braces' => true, + 'doctrine_annotation_indentation' => true, + 'doctrine_annotation_spaces' => [ + 'before_array_assignments_colon' => false, + ], + ]; + } + + public function getDescription(): string + { + return 'Rules covering Doctrine annotations with configuration based on examples found in `Doctrine Annotation documentation `_ and `Symfony documentation `_.'; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERCS1x0RiskySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERCS1x0RiskySet.php new file mode 100644 index 00000000..3e446037 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERCS1x0RiskySet.php @@ -0,0 +1,44 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractRuleSetDescription; + +/** + * @internal + * + * PER Coding Style v1.0. + * + * @see https://github.com/php-fig/per-coding-style/blob/1.0.0/spec.md + */ +final class PERCS1x0RiskySet extends AbstractRuleSetDescription +{ + public function getName(): string + { + return '@PER-CS1.0:risky'; + } + + public function getRules(): array + { + return [ + '@PSR12:risky' => true, + ]; + } + + public function getDescription(): string + { + return 'Rules that follow `PER Coding Style 1.0 `_.'; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERCS1x0Set.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERCS1x0Set.php new file mode 100644 index 00000000..3ad0a6fc --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERCS1x0Set.php @@ -0,0 +1,44 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractRuleSetDescription; + +/** + * @internal + * + * PER Coding Style v1.0. + * + * @see https://github.com/php-fig/per-coding-style/blob/1.0.0/spec.md + */ +final class PERCS1x0Set extends AbstractRuleSetDescription +{ + public function getName(): string + { + return '@PER-CS1.0'; + } + + public function getRules(): array + { + return [ + '@PSR12' => true, + ]; + } + + public function getDescription(): string + { + return 'Rules that follow `PER Coding Style 1.0 `_.'; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERCS2x0RiskySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERCS2x0RiskySet.php new file mode 100644 index 00000000..4a75a93d --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERCS2x0RiskySet.php @@ -0,0 +1,44 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractRuleSetDescription; + +/** + * @internal + * + * PER Coding Style v2.0. + * + * @see https://github.com/php-fig/per-coding-style/blob/2.0.0/spec.md + */ +final class PERCS2x0RiskySet extends AbstractRuleSetDescription +{ + public function getName(): string + { + return '@PER-CS2.0:risky'; + } + + public function getRules(): array + { + return [ + '@PER-CS1.0:risky' => true, + ]; + } + + public function getDescription(): string + { + return 'Rules that follow `PER Coding Style 2.0 `_.'; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERCS2x0Set.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERCS2x0Set.php new file mode 100644 index 00000000..fd16550a --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERCS2x0Set.php @@ -0,0 +1,51 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractRuleSetDescription; + +/** + * @internal + * + * PER Coding Style v2.0. + * + * @see https://github.com/php-fig/per-coding-style/blob/2.0.0/spec.md + */ +final class PERCS2x0Set extends AbstractRuleSetDescription +{ + public function getName(): string + { + return '@PER-CS2.0'; + } + + public function getRules(): array + { + return [ + '@PER-CS1.0' => true, + 'cast_spaces' => true, + 'concat_space' => ['spacing' => 'one'], + 'function_declaration' => [ + 'closure_fn_spacing' => 'none', + ], + 'method_argument_space' => true, + 'single_line_empty_body' => true, + ]; + } + + public function getDescription(): string + { + return 'Rules that follow `PER Coding Style 2.0 `_.'; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERCSRiskySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERCSRiskySet.php new file mode 100644 index 00000000..6e6f7908 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERCSRiskySet.php @@ -0,0 +1,40 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractRuleSetDescription; + +/** + * @internal + */ +final class PERCSRiskySet extends AbstractRuleSetDescription +{ + public function getName(): string + { + return '@PER-CS:risky'; + } + + public function getRules(): array + { + return [ + '@PER-CS2.0:risky' => true, + ]; + } + + public function getDescription(): string + { + return 'Alias for the latest revision of PER-CS risky rules. Use it if you always want to be in sync with newest PER-CS standard.'; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERCSSet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERCSSet.php new file mode 100644 index 00000000..0fe59349 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERCSSet.php @@ -0,0 +1,40 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractRuleSetDescription; + +/** + * @internal + */ +final class PERCSSet extends AbstractRuleSetDescription +{ + public function getName(): string + { + return '@PER-CS'; + } + + public function getRules(): array + { + return [ + '@PER-CS2.0' => true, + ]; + } + + public function getDescription(): string + { + return 'Alias for the latest revision of PER-CS rules. Use it if you always want to be in sync with newest PER-CS standard.'; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERRiskySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERRiskySet.php new file mode 100644 index 00000000..9f156c28 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERRiskySet.php @@ -0,0 +1,52 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractRuleSetDescription; +use PhpCsFixer\RuleSet\DeprecatedRuleSetDescriptionInterface; + +/** + * @internal + * + * @deprecated use `@PER-CS:risky` instead + * + * @TODO 4.0 remove me + * + * Last updated to PER Coding Style v2.0. + */ +final class PERRiskySet extends AbstractRuleSetDescription implements DeprecatedRuleSetDescriptionInterface +{ + public function getName(): string + { + return '@PER:risky'; + } + + public function getRules(): array + { + return [ + '@PER-CS:risky' => true, + ]; + } + + public function getDescription(): string + { + return 'Alias for the newest PER-CS risky rules. It is recommended you use ``@PER-CS2.0:risky`` instead if you want to stick with stable ruleset.'; + } + + public function getSuccessorsNames(): array + { + return ['@PER-CS:risky']; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERSet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERSet.php new file mode 100644 index 00000000..81de37de --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERSet.php @@ -0,0 +1,47 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractRuleSetDescription; +use PhpCsFixer\RuleSet\DeprecatedRuleSetDescriptionInterface; + +/** + * @internal + * + * @deprecated use `@PER-CS` instead + * + * @TODO 4.0 remove me + * + * Last updated to PER Coding Style v2.0. + */ +final class PERSet extends AbstractRuleSetDescription implements DeprecatedRuleSetDescriptionInterface +{ + public function getRules(): array + { + return [ + '@PER-CS' => true, + ]; + } + + public function getDescription(): string + { + return 'Alias for the newest PER-CS rules. It is recommended you use ``@PER-CS2.0`` instead if you want to stick with stable ruleset.'; + } + + public function getSuccessorsNames(): array + { + return ['@PER-CS']; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP54MigrationSet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP54MigrationSet.php new file mode 100644 index 00000000..bb877922 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP54MigrationSet.php @@ -0,0 +1,30 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractMigrationSetDescription; + +/** + * @internal + */ +final class PHP54MigrationSet extends AbstractMigrationSetDescription +{ + public function getRules(): array + { + return [ + 'array_syntax' => true, + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP56MigrationRiskySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP56MigrationRiskySet.php new file mode 100644 index 00000000..848b3501 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP56MigrationRiskySet.php @@ -0,0 +1,30 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractMigrationSetDescription; + +/** + * @internal + */ +final class PHP56MigrationRiskySet extends AbstractMigrationSetDescription +{ + public function getRules(): array + { + return [ + 'pow_to_exponentiation' => true, + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP70MigrationRiskySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP70MigrationRiskySet.php new file mode 100644 index 00000000..a7fcb211 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP70MigrationRiskySet.php @@ -0,0 +1,39 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractMigrationSetDescription; + +/** + * @internal + */ +final class PHP70MigrationRiskySet extends AbstractMigrationSetDescription +{ + public function getRules(): array + { + return [ + '@PHP56Migration:risky' => true, + 'combine_nested_dirname' => true, + 'declare_strict_types' => true, + 'non_printable_character' => true, + 'random_api_migration' => [ + 'replacements' => [ + 'mt_rand' => 'random_int', + 'rand' => 'random_int', + ], + ], + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP70MigrationSet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP70MigrationSet.php new file mode 100644 index 00000000..62b0ad99 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP70MigrationSet.php @@ -0,0 +1,31 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractMigrationSetDescription; + +/** + * @internal + */ +final class PHP70MigrationSet extends AbstractMigrationSetDescription +{ + public function getRules(): array + { + return [ + '@PHP54Migration' => true, + 'ternary_to_null_coalescing' => true, + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP71MigrationRiskySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP71MigrationRiskySet.php new file mode 100644 index 00000000..5a57f262 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP71MigrationRiskySet.php @@ -0,0 +1,31 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractMigrationSetDescription; + +/** + * @internal + */ +final class PHP71MigrationRiskySet extends AbstractMigrationSetDescription +{ + public function getRules(): array + { + return [ + '@PHP70Migration:risky' => true, + 'void_return' => true, + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP71MigrationSet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP71MigrationSet.php new file mode 100644 index 00000000..9379628d --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP71MigrationSet.php @@ -0,0 +1,32 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractMigrationSetDescription; + +/** + * @internal + */ +final class PHP71MigrationSet extends AbstractMigrationSetDescription +{ + public function getRules(): array + { + return [ + '@PHP70Migration' => true, + 'list_syntax' => true, + 'visibility_required' => true, + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP73MigrationSet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP73MigrationSet.php new file mode 100644 index 00000000..aa0ed8fb --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP73MigrationSet.php @@ -0,0 +1,34 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractMigrationSetDescription; + +/** + * @internal + */ +final class PHP73MigrationSet extends AbstractMigrationSetDescription +{ + public function getRules(): array + { + return [ + '@PHP71Migration' => true, + 'heredoc_indentation' => true, + 'method_argument_space' => ['after_heredoc' => true], + 'no_whitespace_before_comma_in_array' => ['after_heredoc' => true], + 'trailing_comma_in_multiline' => ['after_heredoc' => true], + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP74MigrationRiskySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP74MigrationRiskySet.php new file mode 100644 index 00000000..0a1bc17d --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP74MigrationRiskySet.php @@ -0,0 +1,33 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractMigrationSetDescription; + +/** + * @internal + */ +final class PHP74MigrationRiskySet extends AbstractMigrationSetDescription +{ + public function getRules(): array + { + return [ + '@PHP71Migration:risky' => true, + 'implode_call' => true, + 'no_alias_functions' => true, + 'use_arrow_functions' => true, + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP74MigrationSet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP74MigrationSet.php new file mode 100644 index 00000000..8af7296a --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP74MigrationSet.php @@ -0,0 +1,33 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractMigrationSetDescription; + +/** + * @internal + */ +final class PHP74MigrationSet extends AbstractMigrationSetDescription +{ + public function getRules(): array + { + return [ + '@PHP73Migration' => true, + 'assign_null_coalescing_to_coalesce_equal' => true, + 'normalize_index_brace' => true, + 'short_scalar_cast' => true, + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP80MigrationRiskySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP80MigrationRiskySet.php new file mode 100644 index 00000000..4027b6ed --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP80MigrationRiskySet.php @@ -0,0 +1,40 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractMigrationSetDescription; + +/** + * @internal + */ +final class PHP80MigrationRiskySet extends AbstractMigrationSetDescription +{ + public function getRules(): array + { + return [ + '@PHP74Migration:risky' => true, + 'get_class_to_class_keyword' => true, + 'modernize_strpos' => true, + 'no_alias_functions' => [ + 'sets' => [ + '@all', + ], + ], + 'no_php4_constructor' => true, + 'no_unneeded_final_method' => true, // final private method (not constructor) are no longer allowed >= PHP8.0 + 'no_unreachable_default_argument_value' => true, + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP80MigrationSet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP80MigrationSet.php new file mode 100644 index 00000000..8b49a55c --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP80MigrationSet.php @@ -0,0 +1,32 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractMigrationSetDescription; + +/** + * @internal + */ +final class PHP80MigrationSet extends AbstractMigrationSetDescription +{ + public function getRules(): array + { + return [ + '@PHP74Migration' => true, + 'clean_namespace' => true, + 'no_unset_cast' => true, + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP81MigrationSet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP81MigrationSet.php new file mode 100644 index 00000000..eaa27e9d --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP81MigrationSet.php @@ -0,0 +1,31 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractMigrationSetDescription; + +/** + * @internal + */ +final class PHP81MigrationSet extends AbstractMigrationSetDescription +{ + public function getRules(): array + { + return [ + '@PHP80Migration' => true, + 'octal_notation' => true, + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP82MigrationSet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP82MigrationSet.php new file mode 100644 index 00000000..6a9da7db --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP82MigrationSet.php @@ -0,0 +1,31 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractMigrationSetDescription; + +/** + * @internal + */ +final class PHP82MigrationSet extends AbstractMigrationSetDescription +{ + public function getRules(): array + { + return [ + '@PHP81Migration' => true, + 'simple_to_complex_string_variable' => true, + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP83MigrationSet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP83MigrationSet.php new file mode 100644 index 00000000..a2bdfbd9 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP83MigrationSet.php @@ -0,0 +1,30 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractMigrationSetDescription; + +/** + * @internal + */ +final class PHP83MigrationSet extends AbstractMigrationSetDescription +{ + public function getRules(): array + { + return [ + '@PHP82Migration' => true, + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit100MigrationRiskySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit100MigrationRiskySet.php new file mode 100644 index 00000000..433fa7ac --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit100MigrationRiskySet.php @@ -0,0 +1,31 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractMigrationSetDescription; + +/** + * @internal + */ +final class PHPUnit100MigrationRiskySet extends AbstractMigrationSetDescription +{ + public function getRules(): array + { + return [ + '@PHPUnit84Migration:risky' => true, + 'php_unit_data_provider_static' => ['force' => true], + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit30MigrationRiskySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit30MigrationRiskySet.php new file mode 100644 index 00000000..1cabfc04 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit30MigrationRiskySet.php @@ -0,0 +1,33 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\Fixer\PhpUnit\PhpUnitTargetVersion; +use PhpCsFixer\RuleSet\AbstractMigrationSetDescription; + +/** + * @internal + */ +final class PHPUnit30MigrationRiskySet extends AbstractMigrationSetDescription +{ + public function getRules(): array + { + return [ + 'php_unit_dedicate_assert' => [ + 'target' => PhpUnitTargetVersion::VERSION_3_0, + ], + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit32MigrationRiskySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit32MigrationRiskySet.php new file mode 100644 index 00000000..fb4cbfb5 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit32MigrationRiskySet.php @@ -0,0 +1,34 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\Fixer\PhpUnit\PhpUnitTargetVersion; +use PhpCsFixer\RuleSet\AbstractMigrationSetDescription; + +/** + * @internal + */ +final class PHPUnit32MigrationRiskySet extends AbstractMigrationSetDescription +{ + public function getRules(): array + { + return [ + '@PHPUnit30Migration:risky' => true, + 'php_unit_no_expectation_annotation' => [ + 'target' => PhpUnitTargetVersion::VERSION_3_2, + ], + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit35MigrationRiskySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit35MigrationRiskySet.php new file mode 100644 index 00000000..6a52afac --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit35MigrationRiskySet.php @@ -0,0 +1,34 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\Fixer\PhpUnit\PhpUnitTargetVersion; +use PhpCsFixer\RuleSet\AbstractMigrationSetDescription; + +/** + * @internal + */ +final class PHPUnit35MigrationRiskySet extends AbstractMigrationSetDescription +{ + public function getRules(): array + { + return [ + '@PHPUnit32Migration:risky' => true, + 'php_unit_dedicate_assert' => [ + 'target' => PhpUnitTargetVersion::VERSION_3_5, + ], + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit43MigrationRiskySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit43MigrationRiskySet.php new file mode 100644 index 00000000..13927970 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit43MigrationRiskySet.php @@ -0,0 +1,34 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\Fixer\PhpUnit\PhpUnitTargetVersion; +use PhpCsFixer\RuleSet\AbstractMigrationSetDescription; + +/** + * @internal + */ +final class PHPUnit43MigrationRiskySet extends AbstractMigrationSetDescription +{ + public function getRules(): array + { + return [ + '@PHPUnit35Migration:risky' => true, + 'php_unit_no_expectation_annotation' => [ + 'target' => PhpUnitTargetVersion::VERSION_4_3, + ], + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit48MigrationRiskySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit48MigrationRiskySet.php new file mode 100644 index 00000000..fcb1b57e --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit48MigrationRiskySet.php @@ -0,0 +1,34 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\Fixer\PhpUnit\PhpUnitTargetVersion; +use PhpCsFixer\RuleSet\AbstractMigrationSetDescription; + +/** + * @internal + */ +final class PHPUnit48MigrationRiskySet extends AbstractMigrationSetDescription +{ + public function getRules(): array + { + return [ + '@PHPUnit43Migration:risky' => true, + 'php_unit_namespaced' => [ + 'target' => PhpUnitTargetVersion::VERSION_4_8, + ], + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit50MigrationRiskySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit50MigrationRiskySet.php new file mode 100644 index 00000000..38474584 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit50MigrationRiskySet.php @@ -0,0 +1,34 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\Fixer\PhpUnit\PhpUnitTargetVersion; +use PhpCsFixer\RuleSet\AbstractMigrationSetDescription; + +/** + * @internal + */ +final class PHPUnit50MigrationRiskySet extends AbstractMigrationSetDescription +{ + public function getRules(): array + { + return [ + '@PHPUnit48Migration:risky' => true, + 'php_unit_dedicate_assert' => [ + 'target' => PhpUnitTargetVersion::VERSION_5_0, + ], + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit52MigrationRiskySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit52MigrationRiskySet.php new file mode 100644 index 00000000..d0f71ee3 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit52MigrationRiskySet.php @@ -0,0 +1,34 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\Fixer\PhpUnit\PhpUnitTargetVersion; +use PhpCsFixer\RuleSet\AbstractMigrationSetDescription; + +/** + * @internal + */ +final class PHPUnit52MigrationRiskySet extends AbstractMigrationSetDescription +{ + public function getRules(): array + { + return [ + '@PHPUnit50Migration:risky' => true, + 'php_unit_expectation' => [ + 'target' => PhpUnitTargetVersion::VERSION_5_2, + ], + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit54MigrationRiskySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit54MigrationRiskySet.php new file mode 100644 index 00000000..b7c87922 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit54MigrationRiskySet.php @@ -0,0 +1,34 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\Fixer\PhpUnit\PhpUnitTargetVersion; +use PhpCsFixer\RuleSet\AbstractMigrationSetDescription; + +/** + * @internal + */ +final class PHPUnit54MigrationRiskySet extends AbstractMigrationSetDescription +{ + public function getRules(): array + { + return [ + '@PHPUnit52Migration:risky' => true, + 'php_unit_mock' => [ + 'target' => PhpUnitTargetVersion::VERSION_5_4, + ], + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit55MigrationRiskySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit55MigrationRiskySet.php new file mode 100644 index 00000000..e3c1647d --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit55MigrationRiskySet.php @@ -0,0 +1,34 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\Fixer\PhpUnit\PhpUnitTargetVersion; +use PhpCsFixer\RuleSet\AbstractMigrationSetDescription; + +/** + * @internal + */ +final class PHPUnit55MigrationRiskySet extends AbstractMigrationSetDescription +{ + public function getRules(): array + { + return [ + '@PHPUnit54Migration:risky' => true, + 'php_unit_mock' => [ + 'target' => PhpUnitTargetVersion::VERSION_5_5, + ], + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit56MigrationRiskySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit56MigrationRiskySet.php new file mode 100644 index 00000000..a1038bf8 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit56MigrationRiskySet.php @@ -0,0 +1,37 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\Fixer\PhpUnit\PhpUnitTargetVersion; +use PhpCsFixer\RuleSet\AbstractMigrationSetDescription; + +/** + * @internal + */ +final class PHPUnit56MigrationRiskySet extends AbstractMigrationSetDescription +{ + public function getRules(): array + { + return [ + '@PHPUnit55Migration:risky' => true, + 'php_unit_dedicate_assert' => [ + 'target' => PhpUnitTargetVersion::VERSION_5_6, + ], + 'php_unit_expectation' => [ + 'target' => PhpUnitTargetVersion::VERSION_5_6, + ], + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit57MigrationRiskySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit57MigrationRiskySet.php new file mode 100644 index 00000000..84076e11 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit57MigrationRiskySet.php @@ -0,0 +1,34 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\Fixer\PhpUnit\PhpUnitTargetVersion; +use PhpCsFixer\RuleSet\AbstractMigrationSetDescription; + +/** + * @internal + */ +final class PHPUnit57MigrationRiskySet extends AbstractMigrationSetDescription +{ + public function getRules(): array + { + return [ + '@PHPUnit56Migration:risky' => true, + 'php_unit_namespaced' => [ + 'target' => PhpUnitTargetVersion::VERSION_5_7, + ], + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit60MigrationRiskySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit60MigrationRiskySet.php new file mode 100644 index 00000000..6bc7f711 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit60MigrationRiskySet.php @@ -0,0 +1,34 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\Fixer\PhpUnit\PhpUnitTargetVersion; +use PhpCsFixer\RuleSet\AbstractMigrationSetDescription; + +/** + * @internal + */ +final class PHPUnit60MigrationRiskySet extends AbstractMigrationSetDescription +{ + public function getRules(): array + { + return [ + '@PHPUnit57Migration:risky' => true, + 'php_unit_namespaced' => [ + 'target' => PhpUnitTargetVersion::VERSION_6_0, + ], + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit75MigrationRiskySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit75MigrationRiskySet.php new file mode 100644 index 00000000..a7efa234 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit75MigrationRiskySet.php @@ -0,0 +1,34 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\Fixer\PhpUnit\PhpUnitTargetVersion; +use PhpCsFixer\RuleSet\AbstractMigrationSetDescription; + +/** + * @internal + */ +final class PHPUnit75MigrationRiskySet extends AbstractMigrationSetDescription +{ + public function getRules(): array + { + return [ + '@PHPUnit60Migration:risky' => true, + 'php_unit_dedicate_assert_internal_type' => [ + 'target' => PhpUnitTargetVersion::VERSION_7_5, + ], + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit84MigrationRiskySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit84MigrationRiskySet.php new file mode 100644 index 00000000..aaf5fc31 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit84MigrationRiskySet.php @@ -0,0 +1,35 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\Fixer\PhpUnit\PhpUnitTargetVersion; +use PhpCsFixer\RuleSet\AbstractMigrationSetDescription; + +/** + * @internal + */ +final class PHPUnit84MigrationRiskySet extends AbstractMigrationSetDescription +{ + public function getRules(): array + { + return [ + '@PHPUnit60Migration:risky' => true, + '@PHPUnit75Migration:risky' => true, + 'php_unit_expectation' => [ + 'target' => PhpUnitTargetVersion::VERSION_8_4, + ], + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PSR12RiskySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PSR12RiskySet.php new file mode 100644 index 00000000..84fd3efa --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PSR12RiskySet.php @@ -0,0 +1,36 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractRuleSetDescription; + +/** + * @internal + */ +final class PSR12RiskySet extends AbstractRuleSetDescription +{ + public function getRules(): array + { + return [ + 'no_trailing_whitespace_in_string' => true, + 'no_unreachable_default_argument_value' => true, + ]; + } + + public function getDescription(): string + { + return 'Rules that follow `PSR-12 `_ standard.'; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PSR12Set.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PSR12Set.php new file mode 100644 index 00000000..47719b10 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PSR12Set.php @@ -0,0 +1,78 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractRuleSetDescription; + +/** + * @internal + */ +final class PSR12Set extends AbstractRuleSetDescription +{ + public function getRules(): array + { + return [ + '@PSR2' => true, + 'binary_operator_spaces' => [ + 'default' => 'at_least_single_space', + ], + 'blank_line_after_opening_tag' => true, + 'blank_line_between_import_groups' => true, + 'blank_lines_before_namespace' => true, + 'braces_position' => [ + 'allow_single_line_empty_anonymous_classes' => true, + ], + 'class_definition' => [ + 'inline_constructor_arguments' => false, // handled by method_argument_space fixer + 'space_before_parenthesis' => true, // defined in PSR12 ¶8. Anonymous Classes + ], + 'compact_nullable_type_declaration' => true, + 'declare_equal_normalize' => true, + 'lowercase_cast' => true, + 'lowercase_static_reference' => true, + 'new_with_parentheses' => true, + 'no_blank_lines_after_class_opening' => true, + 'no_leading_import_slash' => true, + 'no_whitespace_in_blank_line' => true, + 'ordered_class_elements' => [ + 'order' => [ + 'use_trait', + ], + ], + 'ordered_imports' => [ + 'imports_order' => [ + 'class', + 'function', + 'const', + ], + 'sort_algorithm' => 'none', + ], + 'return_type_declaration' => true, + 'short_scalar_cast' => true, + 'single_import_per_statement' => ['group_to_single_imports' => false], + 'single_trait_insert_per_statement' => true, + 'ternary_operator_spaces' => true, + 'unary_operator_spaces' => [ + 'only_dec_inc' => true, + ], + 'visibility_required' => true, + ]; + } + + public function getDescription(): string + { + return 'Rules that follow `PSR-12 `_ standard.'; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PSR1Set.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PSR1Set.php new file mode 100644 index 00000000..3c82ea56 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PSR1Set.php @@ -0,0 +1,36 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractRuleSetDescription; + +/** + * @internal + */ +final class PSR1Set extends AbstractRuleSetDescription +{ + public function getRules(): array + { + return [ + 'encoding' => true, + 'full_opening_tag' => true, + ]; + } + + public function getDescription(): string + { + return 'Rules that follow `PSR-1 `_ standard.'; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PSR2Set.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PSR2Set.php new file mode 100644 index 00000000..19d023d7 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PSR2Set.php @@ -0,0 +1,70 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractRuleSetDescription; + +/** + * @internal + */ +final class PSR2Set extends AbstractRuleSetDescription +{ + public function getRules(): array + { + return [ + '@PSR1' => true, + 'blank_line_after_namespace' => true, + 'braces_position' => true, + 'class_definition' => true, + 'constant_case' => true, + 'control_structure_braces' => true, + 'control_structure_continuation_position' => true, + 'elseif' => true, + 'function_declaration' => true, + 'indentation_type' => true, + 'line_ending' => true, + 'lowercase_keywords' => true, + 'method_argument_space' => [ + 'attribute_placement' => 'ignore', + 'on_multiline' => 'ensure_fully_multiline', + ], + 'no_break_comment' => true, + 'no_closing_tag' => true, + 'no_multiple_statements_per_line' => true, + 'no_space_around_double_colon' => true, + 'no_spaces_after_function_name' => true, + 'no_trailing_whitespace' => true, + 'no_trailing_whitespace_in_comment' => true, + 'single_blank_line_at_eof' => true, + 'single_class_element_per_statement' => [ + 'elements' => [ + 'property', + ], + ], + 'single_import_per_statement' => true, + 'single_line_after_imports' => true, + 'spaces_inside_parentheses' => true, + 'statement_indentation' => true, + 'switch_case_semicolon_to_colon' => true, + 'switch_case_space' => true, + 'visibility_required' => ['elements' => ['method', 'property']], + ]; + } + + public function getDescription(): string + { + return 'Rules that follow `PSR-2 `_ standard.'; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PhpCsFixerRiskySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PhpCsFixerRiskySet.php new file mode 100644 index 00000000..44a8d0fc --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PhpCsFixerRiskySet.php @@ -0,0 +1,68 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractRuleSetDescription; + +/** + * @internal + */ +final class PhpCsFixerRiskySet extends AbstractRuleSetDescription +{ + public function getRules(): array + { + return [ + '@PER-CS:risky' => true, + '@Symfony:risky' => true, + 'comment_to_phpdoc' => true, + 'final_internal_class' => true, + 'get_class_to_class_keyword' => false, + 'modernize_strpos' => false, + // @TODO: consider switching to `true`, like in @Symfony + 'native_constant_invocation' => [ + 'fix_built_in' => false, + 'include' => [ + 'DIRECTORY_SEPARATOR', + 'PHP_INT_SIZE', + 'PHP_SAPI', + 'PHP_VERSION_ID', + ], + 'scope' => 'namespaced', + 'strict' => true, + ], + 'no_alias_functions' => [ + 'sets' => [ + '@all', + ], + ], + 'no_unreachable_default_argument_value' => true, + 'no_unset_on_property' => true, + 'php_unit_data_provider_name' => true, + 'php_unit_data_provider_return_type' => true, + 'php_unit_data_provider_static' => ['force' => true], + 'php_unit_strict' => true, + 'php_unit_test_case_static_method_calls' => ['call_type' => 'self'], + 'static_lambda' => true, + 'strict_comparison' => true, + 'strict_param' => true, + 'yield_from_array_to_yields' => true, + ]; + } + + public function getDescription(): string + { + return 'Rule set as used by the PHP-CS-Fixer development team, highly opinionated.'; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PhpCsFixerSet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PhpCsFixerSet.php new file mode 100644 index 00000000..291735ea --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PhpCsFixerSet.php @@ -0,0 +1,133 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractRuleSetDescription; + +/** + * @internal + */ +final class PhpCsFixerSet extends AbstractRuleSetDescription +{ + public function getRules(): array + { + return [ + '@PER-CS' => true, + '@Symfony' => true, + 'array_indentation' => true, + 'blank_line_before_statement' => [ + 'statements' => [ + 'break', + 'case', + 'continue', + 'declare', + 'default', + 'exit', + 'goto', + 'include', + 'include_once', + 'phpdoc', + 'require', + 'require_once', + 'return', + 'switch', + 'throw', + 'try', + 'yield', + 'yield_from', + ], + ], + 'combine_consecutive_issets' => true, + 'combine_consecutive_unsets' => true, + 'empty_loop_body' => true, + 'explicit_indirect_variable' => true, + 'explicit_string_variable' => true, + 'fully_qualified_strict_types' => [ + 'import_symbols' => true, + ], + 'heredoc_to_nowdoc' => true, + 'method_argument_space' => [ + 'on_multiline' => 'ensure_fully_multiline', + ], + 'method_chaining_indentation' => true, + 'multiline_comment_opening_closing' => true, + 'multiline_whitespace_before_semicolons' => [ + 'strategy' => 'new_line_for_chained_calls', + ], + 'no_extra_blank_lines' => [ + 'tokens' => [ + 'attribute', + 'break', + 'case', + 'continue', + 'curly_brace_block', + 'default', + 'extra', + 'parenthesis_brace_block', + 'return', + 'square_brace_block', + 'switch', + 'throw', + 'use', + ], + ], + 'no_superfluous_elseif' => true, + 'no_superfluous_phpdoc_tags' => [ + 'allow_mixed' => true, + 'remove_inheritdoc' => true, + ], + 'no_unneeded_control_parentheses' => [ + 'statements' => [ + 'break', + 'clone', + 'continue', + 'echo_print', + 'negative_instanceof', + 'others', + 'return', + 'switch_case', + 'yield', + 'yield_from', + ], + ], + 'no_useless_else' => true, + 'no_useless_return' => true, + 'no_whitespace_before_comma_in_array' => ['after_heredoc' => true], + 'nullable_type_declaration_for_default_null_value' => false, + 'ordered_class_elements' => true, + 'ordered_types' => true, + 'php_unit_internal_class' => true, + 'php_unit_test_class_requires_covers' => true, + 'phpdoc_add_missing_param_annotation' => true, + 'phpdoc_no_empty_return' => true, + 'phpdoc_order_by_value' => true, + 'phpdoc_types_order' => true, + 'phpdoc_var_annotation_correct_order' => true, + 'protected_to_private' => true, + 'return_assignment' => true, + 'self_static_accessor' => true, + 'single_line_comment_style' => true, + 'single_line_empty_body' => true, + 'single_line_throw' => false, + 'string_implicit_backslashes' => ['single_quoted' => 'ignore'], + 'whitespace_after_comma_in_array' => ['ensure_single_space' => true], + ]; + } + + public function getDescription(): string + { + return 'Rule set as used by the PHP-CS-Fixer development team, highly opinionated.'; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/SymfonyRiskySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/SymfonyRiskySet.php new file mode 100644 index 00000000..bd9221bf --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/SymfonyRiskySet.php @@ -0,0 +1,79 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractRuleSetDescription; + +/** + * @internal + */ +final class SymfonyRiskySet extends AbstractRuleSetDescription +{ + public function getRules(): array + { + return [ + '@PHP56Migration:risky' => true, + '@PSR12:risky' => true, + 'array_push' => true, + 'combine_nested_dirname' => true, + 'dir_constant' => true, + 'ereg_to_preg' => true, + 'error_suppression' => true, + 'fopen_flag_order' => true, + 'fopen_flags' => [ + 'b_mode' => false, + ], + 'function_to_constant' => true, + 'get_class_to_class_keyword' => true, + 'implode_call' => true, + 'is_null' => true, + 'logical_operators' => true, + 'long_to_shorthand_operator' => true, + 'modernize_strpos' => true, + 'modernize_types_casting' => true, + 'native_constant_invocation' => ['strict' => false], + 'native_function_invocation' => [ + 'include' => [ + '@compiler_optimized', + ], + 'scope' => 'namespaced', + 'strict' => true, + ], + 'no_alias_functions' => true, + 'no_homoglyph_names' => true, + 'no_php4_constructor' => true, + 'no_unneeded_final_method' => true, + 'no_unreachable_default_argument_value' => false, + 'no_useless_sprintf' => true, + 'non_printable_character' => true, + 'ordered_traits' => true, + 'php_unit_construct' => true, + 'php_unit_mock_short_will_return' => true, + 'php_unit_set_up_tear_down_visibility' => true, + 'php_unit_test_annotation' => true, + 'psr_autoloading' => true, + 'self_accessor' => true, + 'set_type_to_cast' => true, + 'string_length_to_empty' => true, + 'string_line_ending' => true, + 'ternary_to_elvis_operator' => true, + ]; + } + + public function getDescription(): string + { + return 'Rules that follow the official `Symfony Coding Standards `_.'; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/SymfonySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/SymfonySet.php new file mode 100644 index 00000000..1b4f38cd --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/SymfonySet.php @@ -0,0 +1,234 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\Fixer\Phpdoc\PhpdocSeparationFixer; +use PhpCsFixer\RuleSet\AbstractRuleSetDescription; + +/** + * @internal + */ +final class SymfonySet extends AbstractRuleSetDescription +{ + public function getRules(): array + { + return [ + '@PER-CS2.0' => true, + 'align_multiline_comment' => true, + 'array_syntax' => true, + 'backtick_to_shell_exec' => true, + 'binary_operator_spaces' => true, + 'blank_line_before_statement' => [ + 'statements' => [ + 'return', + ], + ], + 'braces_position' => [ + 'allow_single_line_anonymous_functions' => true, + 'allow_single_line_empty_anonymous_classes' => true, + ], + 'class_attributes_separation' => [ + 'elements' => [ + 'method' => 'one', + ], + ], + 'class_definition' => [ + 'single_line' => true, + ], + 'class_reference_name_casing' => true, + 'clean_namespace' => true, + 'concat_space' => true, // overrides @PER-CS2.0 + 'declare_parentheses' => true, + 'echo_tag_syntax' => true, + 'empty_loop_body' => ['style' => 'braces'], + 'empty_loop_condition' => true, + 'fully_qualified_strict_types' => true, + 'function_declaration' => true, // overrides @PER-CS2.0 + 'general_phpdoc_tag_rename' => [ + 'replacements' => [ + 'inheritDocs' => 'inheritDoc', + ], + ], + 'global_namespace_import' => [ + 'import_classes' => false, + 'import_constants' => false, + 'import_functions' => false, + ], + 'include' => true, + 'increment_style' => true, + 'integer_literal_case' => true, + 'lambda_not_used_import' => true, + 'linebreak_after_opening_tag' => true, + 'magic_constant_casing' => true, + 'magic_method_casing' => true, + 'method_argument_space' => [ // overrides @PER-CS2.0 + 'on_multiline' => 'ignore', + ], + 'native_function_casing' => true, + 'native_type_declaration_casing' => true, + 'no_alias_language_construct_call' => true, + 'no_alternative_syntax' => true, + 'no_binary_string' => true, + 'no_blank_lines_after_phpdoc' => true, + 'no_empty_comment' => true, + 'no_empty_phpdoc' => true, + 'no_empty_statement' => true, + 'no_extra_blank_lines' => [ + 'tokens' => [ + 'attribute', + 'case', + 'continue', + 'curly_brace_block', + 'default', + 'extra', + 'parenthesis_brace_block', + 'square_brace_block', + 'switch', + 'throw', + 'use', + ], + ], + 'no_leading_namespace_whitespace' => true, + 'no_mixed_echo_print' => true, + 'no_multiline_whitespace_around_double_arrow' => true, + 'no_null_property_initialization' => true, + 'no_short_bool_cast' => true, + 'no_singleline_whitespace_before_semicolons' => true, + 'no_spaces_around_offset' => true, + 'no_superfluous_phpdoc_tags' => [ + 'allow_hidden_params' => true, + 'remove_inheritdoc' => true, + ], + 'no_trailing_comma_in_singleline' => true, + 'no_unneeded_braces' => [ + 'namespaces' => true, + ], + 'no_unneeded_control_parentheses' => [ + 'statements' => [ + 'break', + 'clone', + 'continue', + 'echo_print', + 'others', + 'return', + 'switch_case', + 'yield', + 'yield_from', + ], + ], + 'no_unneeded_import_alias' => true, + 'no_unset_cast' => true, + 'no_unused_imports' => true, + 'no_useless_concat_operator' => true, + 'no_useless_nullsafe_operator' => true, + 'no_whitespace_before_comma_in_array' => true, + 'normalize_index_brace' => true, + 'nullable_type_declaration' => true, + 'nullable_type_declaration_for_default_null_value' => true, + 'object_operator_without_whitespace' => true, + 'operator_linebreak' => [ + 'only_booleans' => true, + ], + 'ordered_imports' => [ + 'imports_order' => [ + 'class', + 'function', + 'const', + ], + 'sort_algorithm' => 'alpha', + ], + 'ordered_types' => [ + 'null_adjustment' => 'always_last', + 'sort_algorithm' => 'none', + ], + 'php_unit_fqcn_annotation' => true, + 'php_unit_method_casing' => true, + 'phpdoc_align' => true, + 'phpdoc_annotation_without_dot' => true, + 'phpdoc_indent' => true, + 'phpdoc_inline_tag_normalizer' => true, + 'phpdoc_no_access' => true, + 'phpdoc_no_alias_tag' => true, + 'phpdoc_no_package' => true, + 'phpdoc_no_useless_inheritdoc' => true, + 'phpdoc_order' => [ + 'order' => [ + 'param', + 'return', + 'throws', + ], + ], + 'phpdoc_return_self_reference' => true, + 'phpdoc_scalar' => true, + 'phpdoc_separation' => [ + 'groups' => [ + ['Annotation', 'NamedArgumentConstructor', 'Target'], + ...PhpdocSeparationFixer::OPTION_GROUPS_DEFAULT, + ], + ], + 'phpdoc_single_line_var_spacing' => true, + 'phpdoc_summary' => true, + 'phpdoc_tag_type' => [ + 'tags' => [ + 'inheritDoc' => 'inline', + ], + ], + 'phpdoc_to_comment' => true, + 'phpdoc_trim' => true, + 'phpdoc_trim_consecutive_blank_line_separation' => true, + 'phpdoc_types' => true, + 'phpdoc_types_order' => [ + 'null_adjustment' => 'always_last', + 'sort_algorithm' => 'none', + ], + 'phpdoc_var_without_name' => true, + 'semicolon_after_instruction' => true, + 'simple_to_complex_string_variable' => true, + 'single_class_element_per_statement' => true, + 'single_import_per_statement' => true, + 'single_line_comment_spacing' => true, + 'single_line_comment_style' => [ + 'comment_types' => [ + 'hash', + ], + ], + 'single_line_empty_body' => false, // overrides @PER-CS2.0 + 'single_line_throw' => true, + 'single_quote' => true, + 'single_space_around_construct' => true, + 'space_after_semicolon' => [ + 'remove_in_empty_for_expressions' => true, + ], + 'standardize_increment' => true, + 'standardize_not_equals' => true, + 'statement_indentation' => [ + 'stick_comment_to_next_continuous_control_statement' => true, + ], + 'switch_continue_to_break' => true, + 'trailing_comma_in_multiline' => true, + 'trim_array_spaces' => true, + 'type_declaration_spaces' => true, + 'types_spaces' => true, + 'unary_operator_spaces' => true, + 'whitespace_after_comma_in_array' => true, + 'yoda_style' => true, + ]; + } + + public function getDescription(): string + { + return 'Rules that follow the official `Symfony Coding Standards `_.'; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Runner/FileCachingLintingIterator.php b/vendor/friendsofphp/php-cs-fixer/src/Runner/FileCachingLintingIterator.php new file mode 100644 index 00000000..c07dbbd2 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Runner/FileCachingLintingIterator.php @@ -0,0 +1,84 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Runner; + +use PhpCsFixer\Linter\LinterInterface; +use PhpCsFixer\Linter\LintingResultInterface; + +/** + * @author Dariusz Rumiński + * + * @internal + * + * @extends \CachingIterator> + */ +final class FileCachingLintingIterator extends \CachingIterator +{ + private LinterInterface $linter; + + /** + * @var LintingResultInterface + */ + private $currentResult; + + /** + * @var LintingResultInterface + */ + private $nextResult; + + /** + * @param \Iterator $iterator + */ + public function __construct(\Iterator $iterator, LinterInterface $linter) + { + parent::__construct($iterator); + + $this->linter = $linter; + } + + public function currentLintingResult(): LintingResultInterface + { + return $this->currentResult; + } + + public function next(): void + { + parent::next(); + + $this->currentResult = $this->nextResult; + + if ($this->hasNext()) { + $this->nextResult = $this->handleItem($this->getInnerIterator()->current()); + } + } + + public function rewind(): void + { + parent::rewind(); + + if ($this->valid()) { + $this->currentResult = $this->handleItem($this->current()); + } + + if ($this->hasNext()) { + $this->nextResult = $this->handleItem($this->getInnerIterator()->current()); + } + } + + private function handleItem(\SplFileInfo $file): LintingResultInterface + { + return $this->linter->lintFile($file->getRealPath()); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Runner/FileFilterIterator.php b/vendor/friendsofphp/php-cs-fixer/src/Runner/FileFilterIterator.php new file mode 100644 index 00000000..c0f736fd --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Runner/FileFilterIterator.php @@ -0,0 +1,111 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Runner; + +use PhpCsFixer\Cache\CacheManagerInterface; +use PhpCsFixer\FileReader; +use PhpCsFixer\FixerFileProcessedEvent; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Contracts\EventDispatcher\Event; + +/** + * @author Dariusz Rumiński + * + * @internal + * + * @extends \FilterIterator> + */ +final class FileFilterIterator extends \FilterIterator +{ + private ?EventDispatcherInterface $eventDispatcher; + + private CacheManagerInterface $cacheManager; + + /** + * @var array + */ + private array $visitedElements = []; + + /** + * @param \Traversable<\SplFileInfo> $iterator + */ + public function __construct( + \Traversable $iterator, + ?EventDispatcherInterface $eventDispatcher, + CacheManagerInterface $cacheManager + ) { + if (!$iterator instanceof \Iterator) { + $iterator = new \IteratorIterator($iterator); + } + + parent::__construct($iterator); + + $this->eventDispatcher = $eventDispatcher; + $this->cacheManager = $cacheManager; + } + + public function accept(): bool + { + $file = $this->current(); + if (!$file instanceof \SplFileInfo) { + throw new \RuntimeException( + sprintf( + 'Expected instance of "\SplFileInfo", got "%s".', + get_debug_type($file) + ) + ); + } + + $path = $file->isLink() ? $file->getPathname() : $file->getRealPath(); + + if (isset($this->visitedElements[$path])) { + return false; + } + + $this->visitedElements[$path] = true; + + if (!$file->isFile() || $file->isLink()) { + return false; + } + + $content = FileReader::createSingleton()->read($path); + + // mark as skipped: + if ( + // empty file + '' === $content + // file that does not need fixing due to cache + || !$this->cacheManager->needFixing($file->getPathname(), $content) + ) { + $this->dispatchEvent( + FixerFileProcessedEvent::NAME, + new FixerFileProcessedEvent(FixerFileProcessedEvent::STATUS_SKIPPED) + ); + + return false; + } + + return true; + } + + private function dispatchEvent(string $name, Event $event): void + { + if (null === $this->eventDispatcher) { + return; + } + + $this->eventDispatcher->dispatch($event, $name); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Runner/FileLintingIterator.php b/vendor/friendsofphp/php-cs-fixer/src/Runner/FileLintingIterator.php new file mode 100644 index 00000000..09cec8da --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Runner/FileLintingIterator.php @@ -0,0 +1,69 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Runner; + +use PhpCsFixer\Linter\LinterInterface; +use PhpCsFixer\Linter\LintingResultInterface; + +/** + * @author Dariusz Rumiński + * + * @internal + * + * @extends \IteratorIterator> + */ +final class FileLintingIterator extends \IteratorIterator +{ + /** + * @var null|LintingResultInterface + */ + private $currentResult; + + private LinterInterface $linter; + + /** + * @param \Iterator $iterator + */ + public function __construct(\Iterator $iterator, LinterInterface $linter) + { + parent::__construct($iterator); + + $this->linter = $linter; + } + + public function currentLintingResult(): ?LintingResultInterface + { + return $this->currentResult; + } + + public function next(): void + { + parent::next(); + + $this->currentResult = $this->valid() ? $this->handleItem($this->current()) : null; + } + + public function rewind(): void + { + parent::rewind(); + + $this->currentResult = $this->valid() ? $this->handleItem($this->current()) : null; + } + + private function handleItem(\SplFileInfo $file): LintingResultInterface + { + return $this->linter->lintFile($file->getRealPath()); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Runner/Runner.php b/vendor/friendsofphp/php-cs-fixer/src/Runner/Runner.php new file mode 100644 index 00000000..7a3bc822 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Runner/Runner.php @@ -0,0 +1,300 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Runner; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Cache\CacheManagerInterface; +use PhpCsFixer\Cache\Directory; +use PhpCsFixer\Cache\DirectoryInterface; +use PhpCsFixer\Differ\DifferInterface; +use PhpCsFixer\Error\Error; +use PhpCsFixer\Error\ErrorsManager; +use PhpCsFixer\FileReader; +use PhpCsFixer\Fixer\FixerInterface; +use PhpCsFixer\FixerFileProcessedEvent; +use PhpCsFixer\Linter\LinterInterface; +use PhpCsFixer\Linter\LintingException; +use PhpCsFixer\Linter\LintingResultInterface; +use PhpCsFixer\Tokenizer\Tokens; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Filesystem\Exception\IOException; +use Symfony\Contracts\EventDispatcher\Event; + +/** + * @author Dariusz Rumiński + */ +final class Runner +{ + private DifferInterface $differ; + + private ?DirectoryInterface $directory; + + private ?EventDispatcherInterface $eventDispatcher; + + private ErrorsManager $errorsManager; + + private CacheManagerInterface $cacheManager; + + private bool $isDryRun; + + private LinterInterface $linter; + + /** + * @var \Traversable<\SplFileInfo> + */ + private $finder; + + /** + * @var list + */ + private array $fixers; + + private bool $stopOnViolation; + + /** + * @param \Traversable<\SplFileInfo> $finder + * @param list $fixers + */ + public function __construct( + \Traversable $finder, + array $fixers, + DifferInterface $differ, + ?EventDispatcherInterface $eventDispatcher, + ErrorsManager $errorsManager, + LinterInterface $linter, + bool $isDryRun, + CacheManagerInterface $cacheManager, + ?DirectoryInterface $directory = null, + bool $stopOnViolation = false + ) { + $this->finder = $finder; + $this->fixers = $fixers; + $this->differ = $differ; + $this->eventDispatcher = $eventDispatcher; + $this->errorsManager = $errorsManager; + $this->linter = $linter; + $this->isDryRun = $isDryRun; + $this->cacheManager = $cacheManager; + $this->directory = $directory ?? new Directory(''); + $this->stopOnViolation = $stopOnViolation; + } + + /** + * @return array, diff: string}> + */ + public function fix(): array + { + $changed = []; + + $finder = $this->finder; + $finderIterator = $finder instanceof \IteratorAggregate ? $finder->getIterator() : $finder; + $fileFilteredFileIterator = new FileFilterIterator( + $finderIterator, + $this->eventDispatcher, + $this->cacheManager + ); + + $collection = $this->linter->isAsync() + ? new FileCachingLintingIterator($fileFilteredFileIterator, $this->linter) + : new FileLintingIterator($fileFilteredFileIterator, $this->linter); + + foreach ($collection as $file) { + $fixInfo = $this->fixFile($file, $collection->currentLintingResult()); + + // we do not need Tokens to still caching just fixed file - so clear the cache + Tokens::clearCache(); + + if (null !== $fixInfo) { + $name = $this->directory->getRelativePathTo($file->__toString()); + $changed[$name] = $fixInfo; + + if ($this->stopOnViolation) { + break; + } + } + } + + return $changed; + } + + /** + * @return null|array{appliedFixers: list, diff: string} + */ + private function fixFile(\SplFileInfo $file, LintingResultInterface $lintingResult): ?array + { + $name = $file->getPathname(); + + try { + $lintingResult->check(); + } catch (LintingException $e) { + $this->dispatchEvent( + FixerFileProcessedEvent::NAME, + new FixerFileProcessedEvent(FixerFileProcessedEvent::STATUS_INVALID) + ); + + $this->errorsManager->report(new Error(Error::TYPE_INVALID, $name, $e)); + + return null; + } + + $old = FileReader::createSingleton()->read($file->getRealPath()); + + $tokens = Tokens::fromCode($old); + $oldHash = $tokens->getCodeHash(); + + $newHash = $oldHash; + $new = $old; + + $appliedFixers = []; + + try { + foreach ($this->fixers as $fixer) { + // for custom fixers we don't know is it safe to run `->fix()` without checking `->supports()` and `->isCandidate()`, + // thus we need to check it and conditionally skip fixing + if ( + !$fixer instanceof AbstractFixer + && (!$fixer->supports($file) || !$fixer->isCandidate($tokens)) + ) { + continue; + } + + $fixer->fix($file, $tokens); + + if ($tokens->isChanged()) { + $tokens->clearEmptyTokens(); + $tokens->clearChanged(); + $appliedFixers[] = $fixer->getName(); + } + } + } catch (\ParseError $e) { + $this->dispatchEvent( + FixerFileProcessedEvent::NAME, + new FixerFileProcessedEvent(FixerFileProcessedEvent::STATUS_LINT) + ); + + $this->errorsManager->report(new Error(Error::TYPE_LINT, $name, $e)); + + return null; + } catch (\Throwable $e) { + $this->processException($name, $e); + + return null; + } + + $fixInfo = null; + + if ([] !== $appliedFixers) { + $new = $tokens->generateCode(); + $newHash = $tokens->getCodeHash(); + } + + // We need to check if content was changed and then applied changes. + // But we can't simply check $appliedFixers, because one fixer may revert + // work of other and both of them will mark collection as changed. + // Therefore we need to check if code hashes changed. + if ($oldHash !== $newHash) { + $fixInfo = [ + 'appliedFixers' => $appliedFixers, + 'diff' => $this->differ->diff($old, $new, $file), + ]; + + try { + $this->linter->lintSource($new)->check(); + } catch (LintingException $e) { + $this->dispatchEvent( + FixerFileProcessedEvent::NAME, + new FixerFileProcessedEvent(FixerFileProcessedEvent::STATUS_LINT) + ); + + $this->errorsManager->report(new Error(Error::TYPE_LINT, $name, $e, $fixInfo['appliedFixers'], $fixInfo['diff'])); + + return null; + } + + if (!$this->isDryRun) { + $fileName = $file->getRealPath(); + + if (!file_exists($fileName)) { + throw new IOException( + sprintf('Failed to write file "%s" (no longer) exists.', $file->getPathname()), + 0, + null, + $file->getPathname() + ); + } + + if (is_dir($fileName)) { + throw new IOException( + sprintf('Cannot write file "%s" as the location exists as directory.', $fileName), + 0, + null, + $fileName + ); + } + + if (!is_writable($fileName)) { + throw new IOException( + sprintf('Cannot write to file "%s" as it is not writable.', $fileName), + 0, + null, + $fileName + ); + } + + if (false === @file_put_contents($fileName, $new)) { + $error = error_get_last(); + + throw new IOException( + sprintf('Failed to write file "%s", "%s".', $fileName, null !== $error ? $error['message'] : 'no reason available'), + 0, + null, + $fileName + ); + } + } + } + + $this->cacheManager->setFile($name, $new); + + $this->dispatchEvent( + FixerFileProcessedEvent::NAME, + new FixerFileProcessedEvent(null !== $fixInfo ? FixerFileProcessedEvent::STATUS_FIXED : FixerFileProcessedEvent::STATUS_NO_CHANGES) + ); + + return $fixInfo; + } + + /** + * Process an exception that occurred. + */ + private function processException(string $name, \Throwable $e): void + { + $this->dispatchEvent( + FixerFileProcessedEvent::NAME, + new FixerFileProcessedEvent(FixerFileProcessedEvent::STATUS_EXCEPTION) + ); + + $this->errorsManager->report(new Error(Error::TYPE_EXCEPTION, $name, $e)); + } + + private function dispatchEvent(string $name, Event $event): void + { + if (null === $this->eventDispatcher) { + return; + } + + $this->eventDispatcher->dispatch($event, $name); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/StdinFileInfo.php b/vendor/friendsofphp/php-cs-fixer/src/StdinFileInfo.php new file mode 100644 index 00000000..7ce19091 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/StdinFileInfo.php @@ -0,0 +1,171 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer; + +/** + * @author Davi Koscianski Vidal + * + * @internal + */ +final class StdinFileInfo extends \SplFileInfo +{ + public function __construct() + { + parent::__construct(__FILE__); + } + + public function __toString(): string + { + return $this->getRealPath(); + } + + public function getRealPath(): string + { + // So file_get_contents & friends will work. + // Warning - this stream is not seekable, so `file_get_contents` will work only once! Consider using `FileReader`. + return 'php://stdin'; + } + + public function getATime(): int + { + return 0; + } + + public function getBasename($suffix = null): string + { + return $this->getFilename(); + } + + public function getCTime(): int + { + return 0; + } + + public function getExtension(): string + { + return '.php'; + } + + public function getFileInfo($className = null): \SplFileInfo + { + throw new \BadMethodCallException(sprintf('Method "%s" is not implemented.', __METHOD__)); + } + + public function getFilename(): string + { + /* + * Useful so fixers depending on PHP-only files still work. + * + * The idea to use STDIN is to parse PHP-only files, so we can + * assume that there will be always a PHP file out there. + */ + + return 'stdin.php'; + } + + public function getGroup(): int + { + return 0; + } + + public function getInode(): int + { + return 0; + } + + public function getLinkTarget(): string + { + return ''; + } + + public function getMTime(): int + { + return 0; + } + + public function getOwner(): int + { + return 0; + } + + public function getPath(): string + { + return ''; + } + + public function getPathInfo($className = null): \SplFileInfo + { + throw new \BadMethodCallException(sprintf('Method "%s" is not implemented.', __METHOD__)); + } + + public function getPathname(): string + { + return $this->getFilename(); + } + + public function getPerms(): int + { + return 0; + } + + public function getSize(): int + { + return 0; + } + + public function getType(): string + { + return 'file'; + } + + public function isDir(): bool + { + return false; + } + + public function isExecutable(): bool + { + return false; + } + + public function isFile(): bool + { + return true; + } + + public function isLink(): bool + { + return false; + } + + public function isReadable(): bool + { + return true; + } + + public function isWritable(): bool + { + return false; + } + + public function openFile($openMode = 'r', $useIncludePath = false, $context = null): \SplFileObject + { + throw new \BadMethodCallException(sprintf('Method "%s" is not implemented.', __METHOD__)); + } + + public function setFileClass($className = null): void {} + + public function setInfoClass($className = null): void {} +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/AbstractTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/AbstractTransformer.php new file mode 100644 index 00000000..5c8de2eb --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/AbstractTransformer.php @@ -0,0 +1,40 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer; + +use PhpCsFixer\Utils; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +abstract class AbstractTransformer implements TransformerInterface +{ + public function getName(): string + { + $nameParts = explode('\\', static::class); + $name = substr(end($nameParts), 0, -\strlen('Transformer')); + + return Utils::camelCaseToUnderscore($name); + } + + public function getPriority(): int + { + return 0; + } + + abstract public function getCustomTokens(): array; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/AbstractTypeTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/AbstractTypeTransformer.php new file mode 100644 index 00000000..8c5476b4 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/AbstractTypeTransformer.php @@ -0,0 +1,89 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +abstract class AbstractTypeTransformer extends AbstractTransformer +{ + private const TYPE_END_TOKENS = [')', [T_CALLABLE], [T_NS_SEPARATOR], [T_STATIC], [T_STRING], [CT::T_ARRAY_TYPEHINT]]; + + private const TYPE_TOKENS = [ + '|', '&', '(', + ...self::TYPE_END_TOKENS, + [CT::T_TYPE_ALTERNATION], [CT::T_TYPE_INTERSECTION], // some siblings may already be transformed + [T_WHITESPACE], [T_COMMENT], [T_DOC_COMMENT], // technically these can be inside of type tokens array + ]; + + abstract protected function replaceToken(Tokens $tokens, int $index): void; + + /** + * @param array{0: int, 1: string}|string $originalToken + */ + protected function doProcess(Tokens $tokens, int $index, $originalToken): void + { + if (!$tokens[$index]->equals($originalToken)) { + return; + } + + if (!$this->isPartOfType($tokens, $index)) { + return; + } + + $this->replaceToken($tokens, $index); + } + + private function isPartOfType(Tokens $tokens, int $index): bool + { + // return types and non-capturing catches + $typeColonIndex = $tokens->getTokenNotOfKindSibling($index, -1, self::TYPE_TOKENS); + if ($tokens[$typeColonIndex]->isGivenKind([T_CATCH, CT::T_TYPE_COLON, T_CONST])) { + return true; + } + + // for parameter there will be splat operator or variable after the type ("&" is ambiguous and can be reference or bitwise and) + $afterTypeIndex = $tokens->getTokenNotOfKindSibling($index, 1, self::TYPE_TOKENS); + + if ($tokens[$afterTypeIndex]->isGivenKind(T_ELLIPSIS)) { + return true; + } + + if (!$tokens[$afterTypeIndex]->isGivenKind(T_VARIABLE)) { + return false; + } + + $beforeVariableIndex = $tokens->getPrevMeaningfulToken($afterTypeIndex); + if ($tokens[$beforeVariableIndex]->equals('&')) { + $prevIndex = $tokens->getPrevTokenOfKind( + $index, + [ + '{', + '}', + ';', + [T_CLOSE_TAG], + [T_FN], + [T_FUNCTION], + ], + ); + + return null !== $prevIndex && $tokens[$prevIndex]->isGivenKind([T_FN, T_FUNCTION]); + } + + return $tokens[$beforeVariableIndex]->equalsAny(self::TYPE_END_TOKENS); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/AlternativeSyntaxAnalyzer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/AlternativeSyntaxAnalyzer.php new file mode 100644 index 00000000..723d4c95 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/AlternativeSyntaxAnalyzer.php @@ -0,0 +1,116 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Analyzer; + +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @internal + * + * @TODO 4.0 remove this analyzer and move this logic into a transformer + */ +final class AlternativeSyntaxAnalyzer +{ + private const ALTERNATIVE_SYNTAX_BLOCK_EDGES = [ + T_IF => [T_ENDIF, T_ELSE, T_ELSEIF], + T_ELSE => [T_ENDIF], + T_ELSEIF => [T_ENDIF, T_ELSE, T_ELSEIF], + T_FOR => [T_ENDFOR], + T_FOREACH => [T_ENDFOREACH], + T_WHILE => [T_ENDWHILE], + T_SWITCH => [T_ENDSWITCH], + ]; + + public function belongsToAlternativeSyntax(Tokens $tokens, int $index): bool + { + if (!$tokens[$index]->equals(':')) { + return false; + } + + $prevIndex = $tokens->getPrevMeaningfulToken($index); + + if ($tokens[$prevIndex]->isGivenKind(T_ELSE)) { + return true; + } + + if (!$tokens[$prevIndex]->equals(')')) { + return false; + } + + $openParenthesisIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $prevIndex); + $beforeOpenParenthesisIndex = $tokens->getPrevMeaningfulToken($openParenthesisIndex); + + return $tokens[$beforeOpenParenthesisIndex]->isGivenKind([ + T_DECLARE, + T_ELSEIF, + T_FOR, + T_FOREACH, + T_IF, + T_SWITCH, + T_WHILE, + ]); + } + + public function findAlternativeSyntaxBlockEnd(Tokens $tokens, int $index): int + { + if (!isset($tokens[$index])) { + throw new \InvalidArgumentException("There is no token at index {$index}."); + } + + if (!$this->isStartOfAlternativeSyntaxBlock($tokens, $index)) { + throw new \InvalidArgumentException("Token at index {$index} is not the start of an alternative syntax block."); + } + + $startTokenKind = $tokens[$index]->getId(); + $endTokenKinds = self::ALTERNATIVE_SYNTAX_BLOCK_EDGES[$startTokenKind]; + + $findKinds = [[$startTokenKind]]; + foreach ($endTokenKinds as $endTokenKind) { + $findKinds[] = [$endTokenKind]; + } + + while (true) { + $index = $tokens->getNextTokenOfKind($index, $findKinds); + + if ($tokens[$index]->isGivenKind($endTokenKinds)) { + return $index; + } + + if ($this->isStartOfAlternativeSyntaxBlock($tokens, $index)) { + $index = $this->findAlternativeSyntaxBlockEnd($tokens, $index); + } + } + } + + private function isStartOfAlternativeSyntaxBlock(Tokens $tokens, int $index): bool + { + $map = self::ALTERNATIVE_SYNTAX_BLOCK_EDGES; + $startTokenKind = $tokens[$index]->getId(); + + if (null === $startTokenKind || !isset($map[$startTokenKind])) { + return false; + } + + $index = $tokens->getNextMeaningfulToken($index); + + if ($tokens[$index]->equals('(')) { + $index = $tokens->getNextMeaningfulToken( + $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index) + ); + } + + return $tokens[$index]->equals(':'); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/AbstractControlCaseStructuresAnalysis.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/AbstractControlCaseStructuresAnalysis.php new file mode 100644 index 00000000..a2bc675f --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/AbstractControlCaseStructuresAnalysis.php @@ -0,0 +1,49 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Analyzer\Analysis; + +/** + * @internal + */ +abstract class AbstractControlCaseStructuresAnalysis +{ + private int $index; + + private int $open; + + private int $close; + + public function __construct(int $index, int $open, int $close) + { + $this->index = $index; + $this->open = $open; + $this->close = $close; + } + + public function getIndex(): int + { + return $this->index; + } + + public function getOpenIndex(): int + { + return $this->open; + } + + public function getCloseIndex(): int + { + return $this->close; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/ArgumentAnalysis.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/ArgumentAnalysis.php new file mode 100644 index 00000000..a4e72a70 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/ArgumentAnalysis.php @@ -0,0 +1,79 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Analyzer\Analysis; + +/** + * @internal + */ +final class ArgumentAnalysis +{ + /** + * The name of the argument. + */ + private ?string $name; + + /** + * The index where the name is located in the supplied Tokens object. + */ + private ?int $nameIndex; + + /** + * The default value of the argument. + */ + private ?string $default; + + /** + * The type analysis of the argument. + */ + private ?TypeAnalysis $typeAnalysis; + + public function __construct(?string $name, ?int $nameIndex, ?string $default, ?TypeAnalysis $typeAnalysis = null) + { + $this->name = $name; + $this->nameIndex = $nameIndex; + $this->default = $default ?? null; + $this->typeAnalysis = $typeAnalysis ?? null; + } + + public function getDefault(): ?string + { + return $this->default; + } + + public function hasDefault(): bool + { + return null !== $this->default; + } + + public function getName(): ?string + { + return $this->name; + } + + public function getNameIndex(): ?int + { + return $this->nameIndex; + } + + public function getTypeAnalysis(): ?TypeAnalysis + { + return $this->typeAnalysis; + } + + public function hasTypeAnalysis(): bool + { + return null !== $this->typeAnalysis; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/CaseAnalysis.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/CaseAnalysis.php new file mode 100644 index 00000000..df8c0dc3 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/CaseAnalysis.php @@ -0,0 +1,43 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Analyzer\Analysis; + +/** + * @author Kuba Werłos + * + * @internal + */ +final class CaseAnalysis +{ + private int $index; + + private int $colonIndex; + + public function __construct(int $index, int $colonIndex) + { + $this->index = $index; + $this->colonIndex = $colonIndex; + } + + public function getIndex(): int + { + return $this->index; + } + + public function getColonIndex(): int + { + return $this->colonIndex; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/DataProviderAnalysis.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/DataProviderAnalysis.php new file mode 100644 index 00000000..8e3b15fe --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/DataProviderAnalysis.php @@ -0,0 +1,63 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Analyzer\Analysis; + +use PhpCsFixer\Console\Application; +use PhpCsFixer\Utils; + +final class DataProviderAnalysis +{ + private string $name; + + private int $nameIndex; + + /** @var list */ + private array $usageIndices; + + /** + * @param list $usageIndices + */ + public function __construct(string $name, int $nameIndex, array $usageIndices) + { + if (!array_is_list($usageIndices)) { + Utils::triggerDeprecation(new \InvalidArgumentException(sprintf( + 'Parameter "usageIndices" should be a list. This will be enforced in version %d.0.', + Application::getMajorVersion() + 1 + ))); + } + + $this->name = $name; + $this->nameIndex = $nameIndex; + $this->usageIndices = $usageIndices; + } + + public function getName(): string + { + return $this->name; + } + + public function getNameIndex(): int + { + return $this->nameIndex; + } + + /** + * @return list + */ + public function getUsageIndices(): array + { + return $this->usageIndices; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/DefaultAnalysis.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/DefaultAnalysis.php new file mode 100644 index 00000000..b742b29a --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/DefaultAnalysis.php @@ -0,0 +1,41 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Analyzer\Analysis; + +/** + * @internal + */ +final class DefaultAnalysis +{ + private int $index; + + private int $colonIndex; + + public function __construct(int $index, int $colonIndex) + { + $this->index = $index; + $this->colonIndex = $colonIndex; + } + + public function getIndex(): int + { + return $this->index; + } + + public function getColonIndex(): int + { + return $this->colonIndex; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/EnumAnalysis.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/EnumAnalysis.php new file mode 100644 index 00000000..6260cca1 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/EnumAnalysis.php @@ -0,0 +1,44 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Analyzer\Analysis; + +/** + * @internal + */ +final class EnumAnalysis extends AbstractControlCaseStructuresAnalysis +{ + /** + * @var list + */ + private array $cases; + + /** + * @param list $cases + */ + public function __construct(int $index, int $open, int $close, array $cases) + { + parent::__construct($index, $open, $close); + + $this->cases = $cases; + } + + /** + * @return list + */ + public function getCases(): array + { + return $this->cases; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/MatchAnalysis.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/MatchAnalysis.php new file mode 100644 index 00000000..2ac1b977 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/MatchAnalysis.php @@ -0,0 +1,35 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Analyzer\Analysis; + +/** + * @internal + */ +final class MatchAnalysis extends AbstractControlCaseStructuresAnalysis +{ + private ?DefaultAnalysis $defaultAnalysis; + + public function __construct(int $index, int $open, int $close, ?DefaultAnalysis $defaultAnalysis) + { + parent::__construct($index, $open, $close); + + $this->defaultAnalysis = $defaultAnalysis; + } + + public function getDefaultAnalysis(): ?DefaultAnalysis + { + return $this->defaultAnalysis; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/NamespaceAnalysis.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/NamespaceAnalysis.php new file mode 100644 index 00000000..6702a122 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/NamespaceAnalysis.php @@ -0,0 +1,96 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Analyzer\Analysis; + +/** + * @internal + */ +final class NamespaceAnalysis implements StartEndTokenAwareAnalysis +{ + /** + * The fully qualified namespace name. + */ + private string $fullName; + + /** + * The short version of the namespace. + */ + private string $shortName; + + /** + * The start index of the namespace declaration in the analyzed Tokens. + */ + private int $startIndex; + + /** + * The end index of the namespace declaration in the analyzed Tokens. + */ + private int $endIndex; + + /** + * The start index of the scope of the namespace in the analyzed Tokens. + */ + private int $scopeStartIndex; + + /** + * The end index of the scope of the namespace in the analyzed Tokens. + */ + private int $scopeEndIndex; + + public function __construct(string $fullName, string $shortName, int $startIndex, int $endIndex, int $scopeStartIndex, int $scopeEndIndex) + { + $this->fullName = $fullName; + $this->shortName = $shortName; + $this->startIndex = $startIndex; + $this->endIndex = $endIndex; + $this->scopeStartIndex = $scopeStartIndex; + $this->scopeEndIndex = $scopeEndIndex; + } + + public function getFullName(): string + { + return $this->fullName; + } + + public function getShortName(): string + { + return $this->shortName; + } + + public function getStartIndex(): int + { + return $this->startIndex; + } + + public function getEndIndex(): int + { + return $this->endIndex; + } + + public function getScopeStartIndex(): int + { + return $this->scopeStartIndex; + } + + public function getScopeEndIndex(): int + { + return $this->scopeEndIndex; + } + + public function isGlobalNamespace(): bool + { + return '' === $this->getFullName(); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/NamespaceUseAnalysis.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/NamespaceUseAnalysis.php new file mode 100644 index 00000000..c1586fbd --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/NamespaceUseAnalysis.php @@ -0,0 +1,162 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Analyzer\Analysis; + +/** + * @author VeeWee + * @author Greg Korba + * + * @internal + */ +final class NamespaceUseAnalysis implements StartEndTokenAwareAnalysis +{ + public const TYPE_CLASS = 1; // "classy" could be class, interface or trait + public const TYPE_FUNCTION = 2; + public const TYPE_CONSTANT = 3; + + /** + * The fully qualified use namespace. + */ + private string $fullName; + + /** + * The short version of use namespace or the alias name in case of aliased use statements. + */ + private string $shortName; + + /** + * Is the use statement part of multi-use (`use A, B, C;`, `use A\{B, C};`)? + */ + private bool $isInMulti; + + /** + * Is the use statement being aliased? + */ + private bool $isAliased; + + /** + * The start index of the namespace declaration in the analyzed Tokens. + */ + private int $startIndex; + + /** + * The end index of the namespace declaration in the analyzed Tokens. + */ + private int $endIndex; + + /** + * The start index of the single import in the multi-use statement. + */ + private ?int $chunkStartIndex; + + /** + * The end index of the single import in the multi-use statement. + */ + private ?int $chunkEndIndex; + + /** + * The type of import: class, function or constant. + */ + private int $type; + + /** + * @param self::TYPE_* $type + */ + public function __construct( + int $type, + string $fullName, + string $shortName, + bool $isAliased, + bool $isInMulti, + int $startIndex, + int $endIndex, + ?int $chunkStartIndex = null, + ?int $chunkEndIndex = null + ) { + if (true === $isInMulti && (null === $chunkStartIndex || null === $chunkEndIndex)) { + throw new \LogicException('Chunk start and end index must be set when the import is part of a multi-use statement.'); + } + + $this->type = $type; + $this->fullName = $fullName; + $this->shortName = $shortName; + $this->isAliased = $isAliased; + $this->isInMulti = $isInMulti; + $this->startIndex = $startIndex; + $this->endIndex = $endIndex; + $this->chunkStartIndex = $chunkStartIndex; + $this->chunkEndIndex = $chunkEndIndex; + } + + public function getFullName(): string + { + return $this->fullName; + } + + public function getShortName(): string + { + return $this->shortName; + } + + public function isAliased(): bool + { + return $this->isAliased; + } + + public function isInMulti(): bool + { + return $this->isInMulti; + } + + public function getStartIndex(): int + { + return $this->startIndex; + } + + public function getEndIndex(): int + { + return $this->endIndex; + } + + public function getChunkStartIndex(): ?int + { + return $this->chunkStartIndex; + } + + public function getChunkEndIndex(): ?int + { + return $this->chunkEndIndex; + } + + public function getType(): int + { + return $this->type; + } + + public function isClass(): bool + { + return self::TYPE_CLASS === $this->type; + } + + public function isFunction(): bool + { + return self::TYPE_FUNCTION === $this->type; + } + + public function isConstant(): bool + { + return self::TYPE_CONSTANT === $this->type; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/StartEndTokenAwareAnalysis.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/StartEndTokenAwareAnalysis.php new file mode 100644 index 00000000..0b2f318b --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/StartEndTokenAwareAnalysis.php @@ -0,0 +1,28 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Analyzer\Analysis; + +interface StartEndTokenAwareAnalysis +{ + /** + * The start index of the analyzed subject inside of the Tokens. + */ + public function getStartIndex(): int; + + /** + * The end index of the analyzed subject inside of the Tokens. + */ + public function getEndIndex(): int; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/SwitchAnalysis.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/SwitchAnalysis.php new file mode 100644 index 00000000..f81b48e2 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/SwitchAnalysis.php @@ -0,0 +1,52 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Analyzer\Analysis; + +/** + * @internal + */ +final class SwitchAnalysis extends AbstractControlCaseStructuresAnalysis +{ + /** + * @var list + */ + private array $cases; + + private ?DefaultAnalysis $defaultAnalysis; + + /** + * @param list $cases + */ + public function __construct(int $index, int $open, int $close, array $cases, ?DefaultAnalysis $defaultAnalysis) + { + parent::__construct($index, $open, $close); + + $this->cases = $cases; + $this->defaultAnalysis = $defaultAnalysis; + } + + /** + * @return list + */ + public function getCases(): array + { + return $this->cases; + } + + public function getDefaultAnalysis(): ?DefaultAnalysis + { + return $this->defaultAnalysis; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/TypeAnalysis.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/TypeAnalysis.php new file mode 100644 index 00000000..27bf893f --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/TypeAnalysis.php @@ -0,0 +1,106 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Analyzer\Analysis; + +/** + * @internal + */ +final class TypeAnalysis implements StartEndTokenAwareAnalysis +{ + /** + * This list contains soft and hard reserved types that can be used or will be used by PHP at some point. + * + * More info: + * + * @see https://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration.types + * @see https://php.net/manual/en/reserved.other-reserved-words.php + * @see https://php.net/manual/en/language.pseudo-types.php + * + * @var list + */ + private static array $reservedTypes = [ + 'array', + 'bool', + 'callable', + 'false', + 'float', + 'int', + 'iterable', + 'mixed', + 'never', + 'null', + 'object', + 'parent', + 'resource', + 'self', + 'static', + 'string', + 'true', + 'void', + ]; + + private string $name; + + private int $startIndex; + + private int $endIndex; + + private bool $nullable = false; + + /** + * @param ($startIndex is null ? null : int) $endIndex + */ + public function __construct(string $name, int $startIndex = null, int $endIndex = null) + { + $this->name = $name; + + if (str_starts_with($name, '?')) { + $this->name = substr($name, 1); + $this->nullable = true; + } elseif (\PHP_VERSION_ID >= 8_00_00) { + $this->nullable = \in_array('null', array_map('trim', explode('|', strtolower($name))), true); + } + + if (null !== $startIndex) { + $this->startIndex = $startIndex; + $this->endIndex = $endIndex; + } + } + + public function getName(): string + { + return $this->name; + } + + public function getStartIndex(): int + { + return $this->startIndex; + } + + public function getEndIndex(): int + { + return $this->endIndex; + } + + public function isReservedType(): bool + { + return \in_array(strtolower($this->name), self::$reservedTypes, true); + } + + public function isNullable(): bool + { + return $this->nullable; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/ArgumentsAnalyzer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/ArgumentsAnalyzer.php new file mode 100644 index 00000000..94322071 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/ArgumentsAnalyzer.php @@ -0,0 +1,161 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Analyzer; + +use PhpCsFixer\Tokenizer\Analyzer\Analysis\ArgumentAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\TypeAnalysis; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + * @author Vladimir Reznichenko + * + * @internal + */ +final class ArgumentsAnalyzer +{ + /** + * Count amount of parameters in a function/method reference. + */ + public function countArguments(Tokens $tokens, int $openParenthesis, int $closeParenthesis): int + { + return \count($this->getArguments($tokens, $openParenthesis, $closeParenthesis)); + } + + /** + * Returns start and end token indices of arguments. + * + * Returns an array with each key being the first token of an + * argument and the value the last. Including non-function tokens + * such as comments and white space tokens, but without the separation + * tokens like '(', ',' and ')'. + * + * @return array + */ + public function getArguments(Tokens $tokens, int $openParenthesis, int $closeParenthesis): array + { + $arguments = []; + $firstSensibleToken = $tokens->getNextMeaningfulToken($openParenthesis); + + if ($tokens[$firstSensibleToken]->equals(')')) { + return $arguments; + } + + $paramContentIndex = $openParenthesis + 1; + $argumentsStart = $paramContentIndex; + + for (; $paramContentIndex < $closeParenthesis; ++$paramContentIndex) { + $token = $tokens[$paramContentIndex]; + + // skip nested (), [], {} constructs + $blockDefinitionProbe = Tokens::detectBlockType($token); + + if (null !== $blockDefinitionProbe && true === $blockDefinitionProbe['isStart']) { + $paramContentIndex = $tokens->findBlockEnd($blockDefinitionProbe['type'], $paramContentIndex); + + continue; + } + + // if comma matched, increase arguments counter + if ($token->equals(',')) { + if ($tokens->getNextMeaningfulToken($paramContentIndex) === $closeParenthesis) { + break; // trailing ',' in function call (PHP 7.3) + } + + $arguments[$argumentsStart] = $paramContentIndex - 1; + $argumentsStart = $paramContentIndex + 1; + } + } + + $arguments[$argumentsStart] = $paramContentIndex - 1; + + return $arguments; + } + + public function getArgumentInfo(Tokens $tokens, int $argumentStart, int $argumentEnd): ArgumentAnalysis + { + static $skipTypes = null; + + if (null === $skipTypes) { + $skipTypes = [T_ELLIPSIS, CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC, CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED, CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE]; + + if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required + $skipTypes[] = T_READONLY; + } + } + + $info = [ + 'default' => null, + 'name' => null, + 'name_index' => null, + 'type' => null, + 'type_index_start' => null, + 'type_index_end' => null, + ]; + + $sawName = false; + + for ($index = $argumentStart; $index <= $argumentEnd; ++$index) { + $token = $tokens[$index]; + + if (\defined('T_ATTRIBUTE') && $token->isGivenKind(T_ATTRIBUTE)) { + $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ATTRIBUTE, $index); + + continue; + } + + if ( + $token->isComment() + || $token->isWhitespace() + || $token->isGivenKind($skipTypes) + || $token->equals('&') + ) { + continue; + } + + if ($token->isGivenKind(T_VARIABLE)) { + $sawName = true; + $info['name_index'] = $index; + $info['name'] = $token->getContent(); + + continue; + } + + if ($token->equals('=')) { + continue; + } + + if ($sawName) { + $info['default'] .= $token->getContent(); + } else { + $info['type_index_start'] = ($info['type_index_start'] > 0) ? $info['type_index_start'] : $index; + $info['type_index_end'] = $index; + $info['type'] .= $token->getContent(); + } + } + + if (null === $info['name']) { + $info['type'] = null; + } + + return new ArgumentAnalysis( + $info['name'], + $info['name_index'], + $info['default'], + null !== $info['type'] ? new TypeAnalysis($info['type'], $info['type_index_start'], $info['type_index_end']) : null + ); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/AttributeAnalyzer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/AttributeAnalyzer.php new file mode 100644 index 00000000..c67b76bc --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/AttributeAnalyzer.php @@ -0,0 +1,70 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Analyzer; + +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @internal + */ +final class AttributeAnalyzer +{ + private const TOKEN_KINDS_NOT_ALLOWED_IN_ATTRIBUTE = [ + ';', + '{', + [T_ATTRIBUTE], + [T_FUNCTION], + [T_OPEN_TAG], + [T_OPEN_TAG_WITH_ECHO], + [T_PRIVATE], + [T_PROTECTED], + [T_PUBLIC], + [T_RETURN], + [T_VARIABLE], + [CT::T_ATTRIBUTE_CLOSE], + ]; + + /** + * Check if given index is an attribute declaration. + */ + public static function isAttribute(Tokens $tokens, int $index): bool + { + if ( + !\defined('T_ATTRIBUTE') // attributes not available, PHP version lower than 8.0 + || !$tokens[$index]->isGivenKind(T_STRING) // checked token is not a string + || !$tokens->isAnyTokenKindsFound([T_ATTRIBUTE]) // no attributes in the tokens collection + ) { + return false; + } + + $attributeStartIndex = $tokens->getPrevTokenOfKind($index, self::TOKEN_KINDS_NOT_ALLOWED_IN_ATTRIBUTE); + if (!$tokens[$attributeStartIndex]->isGivenKind(T_ATTRIBUTE)) { + return false; + } + + // now, between attribute start and the attribute candidate index cannot be more "(" than ")" + $count = 0; + for ($i = $attributeStartIndex + 1; $i < $index; ++$i) { + if ($tokens[$i]->equals('(')) { + ++$count; + } elseif ($tokens[$i]->equals(')')) { + --$count; + } + } + + return 0 === $count; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/BlocksAnalyzer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/BlocksAnalyzer.php new file mode 100644 index 00000000..9522e4cd --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/BlocksAnalyzer.php @@ -0,0 +1,59 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Analyzer; + +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Kuba Werłos + * + * @internal + */ +final class BlocksAnalyzer +{ + public function isBlock(Tokens $tokens, int $openIndex, int $closeIndex): bool + { + if (!$tokens->offsetExists($openIndex)) { + throw new \InvalidArgumentException(sprintf('Tokex index %d for potential block opening does not exist.', $openIndex)); + } + + if (!$tokens->offsetExists($closeIndex)) { + throw new \InvalidArgumentException(sprintf('Token index %d for potential block closure does not exist.', $closeIndex)); + } + + $blockType = $this->getBlockType($tokens[$openIndex]); + + if (null === $blockType) { + return false; + } + + return $closeIndex === $tokens->findBlockEnd($blockType, $openIndex); + } + + /** + * @return Tokens::BLOCK_TYPE_* + */ + private function getBlockType(Token $token): ?int + { + foreach (Tokens::getBlockEdgeDefinitions() as $blockType => $definition) { + if ($token->equals($definition['start'])) { + return $blockType; + } + } + + return null; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/ClassyAnalyzer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/ClassyAnalyzer.php new file mode 100644 index 00000000..5770d01c --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/ClassyAnalyzer.php @@ -0,0 +1,87 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Analyzer; + +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @internal + */ +final class ClassyAnalyzer +{ + public function isClassyInvocation(Tokens $tokens, int $index): bool + { + $token = $tokens[$index]; + + if (!$token->isGivenKind(T_STRING)) { + throw new \LogicException(sprintf('No T_STRING at given index %d, got "%s".', $index, $tokens[$index]->getName())); + } + + if ((new Analysis\TypeAnalysis($token->getContent()))->isReservedType()) { + return false; + } + + $next = $tokens->getNextMeaningfulToken($index); + $nextToken = $tokens[$next]; + + if ($nextToken->isGivenKind(T_NS_SEPARATOR)) { + return false; + } + + if ($nextToken->isGivenKind([T_DOUBLE_COLON, T_ELLIPSIS, CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION, T_VARIABLE])) { + return true; + } + + $prev = $tokens->getPrevMeaningfulToken($index); + + while ($tokens[$prev]->isGivenKind([CT::T_NAMESPACE_OPERATOR, T_NS_SEPARATOR, T_STRING])) { + $prev = $tokens->getPrevMeaningfulToken($prev); + } + + $prevToken = $tokens[$prev]; + + if ($prevToken->isGivenKind([T_EXTENDS, T_INSTANCEOF, T_INSTEADOF, T_IMPLEMENTS, T_NEW, CT::T_NULLABLE_TYPE, CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION, CT::T_TYPE_COLON, CT::T_USE_TRAIT])) { + return true; + } + + if (\PHP_VERSION_ID >= 8_00_00 && $nextToken->equals(')') && $prevToken->equals('(') && $tokens[$tokens->getPrevMeaningfulToken($prev)]->isGivenKind(T_CATCH)) { + return true; + } + + if (AttributeAnalyzer::isAttribute($tokens, $index)) { + return true; + } + + // `Foo & $bar` could be: + // - function reference parameter: function baz(Foo & $bar) {} + // - bit operator: $x = Foo & $bar; + if ($nextToken->equals('&') && $tokens[$tokens->getNextMeaningfulToken($next)]->isGivenKind(T_VARIABLE)) { + $checkIndex = $tokens->getPrevTokenOfKind($prev + 1, [';', '{', '}', [T_FUNCTION], [T_OPEN_TAG], [T_OPEN_TAG_WITH_ECHO]]); + + return $tokens[$checkIndex]->isGivenKind(T_FUNCTION); + } + + if (!$prevToken->equals(',')) { + return false; + } + + do { + $prev = $tokens->getPrevMeaningfulToken($prev); + } while ($tokens[$prev]->equalsAny([',', [T_NS_SEPARATOR], [T_STRING], [CT::T_NAMESPACE_OPERATOR]])); + + return $tokens[$prev]->isGivenKind([T_IMPLEMENTS, CT::T_USE_TRAIT]); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/CommentsAnalyzer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/CommentsAnalyzer.php new file mode 100644 index 00000000..ad9b05a2 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/CommentsAnalyzer.php @@ -0,0 +1,350 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Analyzer; + +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Kuba Werłos + * + * @internal + */ +final class CommentsAnalyzer +{ + private const TYPE_HASH = 1; + private const TYPE_DOUBLE_SLASH = 2; + private const TYPE_SLASH_ASTERISK = 3; + + public function isHeaderComment(Tokens $tokens, int $index): bool + { + if (!$tokens[$index]->isGivenKind([T_COMMENT, T_DOC_COMMENT])) { + throw new \InvalidArgumentException('Given index must point to a comment.'); + } + + if (null === $tokens->getNextMeaningfulToken($index)) { + return false; + } + + $prevIndex = $tokens->getPrevNonWhitespace($index); + + if ($tokens[$prevIndex]->equals(';')) { + $braceCloseIndex = $tokens->getPrevMeaningfulToken($prevIndex); + if (!$tokens[$braceCloseIndex]->equals(')')) { + return false; + } + + $braceOpenIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $braceCloseIndex); + $declareIndex = $tokens->getPrevMeaningfulToken($braceOpenIndex); + if (!$tokens[$declareIndex]->isGivenKind(T_DECLARE)) { + return false; + } + + $prevIndex = $tokens->getPrevNonWhitespace($declareIndex); + } + + return $tokens[$prevIndex]->isGivenKind(T_OPEN_TAG); + } + + /** + * Check if comment at given index precedes structural element. + * + * @see https://github.com/php-fig/fig-standards/blob/master/proposed/phpdoc.md#3-definitions + */ + public function isBeforeStructuralElement(Tokens $tokens, int $index): bool + { + $token = $tokens[$index]; + + if (!$token->isGivenKind([T_COMMENT, T_DOC_COMMENT])) { + throw new \InvalidArgumentException('Given index must point to a comment.'); + } + + $nextIndex = $this->getNextTokenIndex($tokens, $index); + + if (null === $nextIndex || $tokens[$nextIndex]->equals('}')) { + return false; + } + + if ($this->isStructuralElement($tokens, $nextIndex)) { + return true; + } + + if ($this->isValidControl($tokens, $token, $nextIndex)) { + return true; + } + + if ($this->isValidVariable($tokens, $nextIndex)) { + return true; + } + + if ($this->isValidVariableAssignment($tokens, $token, $nextIndex)) { + return true; + } + + if ($tokens[$nextIndex]->isGivenKind(CT::T_USE_TRAIT)) { + return true; + } + + return false; + } + + /** + * Check if comment at given index precedes return statement. + */ + public function isBeforeReturn(Tokens $tokens, int $index): bool + { + if (!$tokens[$index]->isGivenKind([T_COMMENT, T_DOC_COMMENT])) { + throw new \InvalidArgumentException('Given index must point to a comment.'); + } + + $nextIndex = $this->getNextTokenIndex($tokens, $index); + + if (null === $nextIndex || $tokens[$nextIndex]->equals('}')) { + return false; + } + + return $tokens[$nextIndex]->isGivenKind(T_RETURN); + } + + /** + * Return array of indices that are part of a comment started at given index. + * + * @param int $index T_COMMENT index + * + * @return list + */ + public function getCommentBlockIndices(Tokens $tokens, int $index): array + { + if (!$tokens[$index]->isGivenKind(T_COMMENT)) { + throw new \InvalidArgumentException('Given index must point to a comment.'); + } + + $commentType = $this->getCommentType($tokens[$index]->getContent()); + $indices = [$index]; + + if (self::TYPE_SLASH_ASTERISK === $commentType) { + return $indices; + } + + $count = \count($tokens); + ++$index; + + for (; $index < $count; ++$index) { + if ($tokens[$index]->isComment()) { + if ($commentType === $this->getCommentType($tokens[$index]->getContent())) { + $indices[] = $index; + + continue; + } + + break; + } + + if (!$tokens[$index]->isWhitespace() || $this->getLineBreakCount($tokens, $index, $index + 1) > 1) { + break; + } + } + + return $indices; + } + + /** + * @see https://github.com/phpDocumentor/fig-standards/blob/master/proposed/phpdoc.md#3-definitions + */ + private function isStructuralElement(Tokens $tokens, int $index): bool + { + static $skip; + + if (null === $skip) { + $skip = [ + T_PRIVATE, + T_PROTECTED, + T_PUBLIC, + T_VAR, + T_FUNCTION, + T_FN, + T_ABSTRACT, + T_CONST, + T_NAMESPACE, + T_REQUIRE, + T_REQUIRE_ONCE, + T_INCLUDE, + T_INCLUDE_ONCE, + T_FINAL, + CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC, + CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED, + CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE, + ]; + + if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required + $skip[] = T_READONLY; + } + } + + $token = $tokens[$index]; + + if ($token->isClassy() || $token->isGivenKind($skip)) { + return true; + } + + if ($token->isGivenKind(T_CASE) && \defined('T_ENUM')) { + $caseParent = $tokens->getPrevTokenOfKind($index, [[T_ENUM], [T_SWITCH]]); + + return $tokens[$caseParent]->isGivenKind([T_ENUM]); + } + + if ($token->isGivenKind(T_STATIC)) { + return !$tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind(T_DOUBLE_COLON); + } + + return false; + } + + /** + * Checks control structures (for, foreach, if, switch, while) for correct docblock usage. + * + * @param Token $docsToken docs Token + * @param int $controlIndex index of control structure Token + */ + private function isValidControl(Tokens $tokens, Token $docsToken, int $controlIndex): bool + { + static $controlStructures = [ + T_FOR, + T_FOREACH, + T_IF, + T_SWITCH, + T_WHILE, + ]; + + if (!$tokens[$controlIndex]->isGivenKind($controlStructures)) { + return false; + } + + $openParenthesisIndex = $tokens->getNextMeaningfulToken($controlIndex); + $closeParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesisIndex); + $docsContent = $docsToken->getContent(); + + for ($index = $openParenthesisIndex + 1; $index < $closeParenthesisIndex; ++$index) { + $token = $tokens[$index]; + + if ( + $token->isGivenKind(T_VARIABLE) + && str_contains($docsContent, $token->getContent()) + ) { + return true; + } + } + + return false; + } + + /** + * Checks variable assignments through `list()`, `print()` etc. calls for correct docblock usage. + * + * @param Token $docsToken docs Token + * @param int $languageConstructIndex index of variable Token + */ + private function isValidVariableAssignment(Tokens $tokens, Token $docsToken, int $languageConstructIndex): bool + { + static $languageStructures = [ + T_LIST, + T_PRINT, + T_ECHO, + CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, + ]; + + if (!$tokens[$languageConstructIndex]->isGivenKind($languageStructures)) { + return false; + } + + $endKind = $tokens[$languageConstructIndex]->isGivenKind(CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN) + ? [CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE] + : ')'; + + $endIndex = $tokens->getNextTokenOfKind($languageConstructIndex, [$endKind]); + + $docsContent = $docsToken->getContent(); + + for ($index = $languageConstructIndex + 1; $index < $endIndex; ++$index) { + $token = $tokens[$index]; + + if ($token->isGivenKind(T_VARIABLE) && str_contains($docsContent, $token->getContent())) { + return true; + } + } + + return false; + } + + /** + * Checks variable assignments for correct docblock usage. + * + * @param int $index index of variable Token + */ + private function isValidVariable(Tokens $tokens, int $index): bool + { + if (!$tokens[$index]->isGivenKind(T_VARIABLE)) { + return false; + } + + $nextIndex = $tokens->getNextMeaningfulToken($index); + + return $tokens[$nextIndex]->equals('='); + } + + private function getCommentType(string $content): int + { + if (str_starts_with($content, '#')) { + return self::TYPE_HASH; + } + + if ('*' === $content[1]) { + return self::TYPE_SLASH_ASTERISK; + } + + return self::TYPE_DOUBLE_SLASH; + } + + private function getLineBreakCount(Tokens $tokens, int $whiteStart, int $whiteEnd): int + { + $lineCount = 0; + for ($i = $whiteStart; $i < $whiteEnd; ++$i) { + $lineCount += Preg::matchAll('/\R/u', $tokens[$i]->getContent()); + } + + return $lineCount; + } + + private function getNextTokenIndex(Tokens $tokens, int $startIndex): ?int + { + $nextIndex = $startIndex; + + do { + $nextIndex = $tokens->getNextMeaningfulToken($nextIndex); + + // @TODO: drop condition when PHP 8.0+ is required + if (\defined('T_ATTRIBUTE')) { + while (null !== $nextIndex && $tokens[$nextIndex]->isGivenKind(T_ATTRIBUTE)) { + $nextIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ATTRIBUTE, $nextIndex); + $nextIndex = $tokens->getNextMeaningfulToken($nextIndex); + } + } + } while (null !== $nextIndex && $tokens[$nextIndex]->equals('(')); + + return $nextIndex; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/ControlCaseStructuresAnalyzer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/ControlCaseStructuresAnalyzer.php new file mode 100644 index 00000000..cb7cf9c4 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/ControlCaseStructuresAnalyzer.php @@ -0,0 +1,309 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Analyzer; + +use PhpCsFixer\Tokenizer\Analyzer\Analysis\AbstractControlCaseStructuresAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\CaseAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\DefaultAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\EnumAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\MatchAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\SwitchAnalysis; +use PhpCsFixer\Tokenizer\Tokens; + +final class ControlCaseStructuresAnalyzer +{ + /** + * @param list $types Token types of interest of which analyzes must be returned + * + * @return \Generator + */ + public static function findControlStructures(Tokens $tokens, array $types): \Generator + { + if (\count($types) < 1) { + return; // quick skip + } + + $typesWithCaseOrDefault = self::getTypesWithCaseOrDefault(); + + foreach ($types as $type) { + if (!\in_array($type, $typesWithCaseOrDefault, true)) { + throw new \InvalidArgumentException(sprintf('Unexpected type "%d".', $type)); + } + } + + if (!$tokens->isAnyTokenKindsFound($types)) { + return; // quick skip + } + + $depth = -1; + + /** + * @var list, + * default: array{index: int, open: int}|null, + * alternative_syntax: bool, + * }> $stack + */ + $stack = []; + $isTypeOfInterest = false; + + foreach ($tokens as $index => $token) { + if ($token->isGivenKind($typesWithCaseOrDefault)) { + ++$depth; + + $stack[$depth] = [ + 'kind' => $token->getId(), + 'index' => $index, + 'brace_count' => 0, + 'cases' => [], + 'default' => null, + 'alternative_syntax' => false, + ]; + + $isTypeOfInterest = \in_array($stack[$depth]['kind'], $types, true); + + if ($token->isGivenKind(T_SWITCH)) { + $index = $tokens->getNextMeaningfulToken($index); + $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + + $stack[$depth]['open'] = $tokens->getNextMeaningfulToken($index); + $stack[$depth]['alternative_syntax'] = $tokens[$stack[$depth]['open']]->equals(':'); + } elseif (\defined('T_MATCH') && $token->isGivenKind(T_MATCH)) { // @TODO: drop condition when PHP 8.0+ is required + $index = $tokens->getNextMeaningfulToken($index); + $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + + $stack[$depth]['open'] = $tokens->getNextMeaningfulToken($index); + } elseif (\defined('T_ENUM') && $token->isGivenKind(T_ENUM)) { + $stack[$depth]['open'] = $tokens->getNextTokenOfKind($index, ['{']); + } + + continue; + } + + if ($depth < 0) { + continue; + } + + if ($token->equals('{')) { + ++$stack[$depth]['brace_count']; + + continue; + } + + if ($token->equals('}')) { + --$stack[$depth]['brace_count']; + + if (0 === $stack[$depth]['brace_count']) { + if ($stack[$depth]['alternative_syntax']) { + continue; + } + + if ($isTypeOfInterest) { + $stack[$depth]['end'] = $index; + + yield $stack[$depth]['index'] => self::buildControlCaseStructureAnalysis($stack[$depth]); + } + + array_pop($stack); + --$depth; + + if ($depth < -1) { // @phpstan-ignore-line + throw new \RuntimeException('Analysis depth count failure.'); + } + + if (isset($stack[$depth]['kind'])) { + $isTypeOfInterest = \in_array($stack[$depth]['kind'], $types, true); + } + } + + continue; + } + + if ($tokens[$index]->isGivenKind(T_ENDSWITCH)) { + if (!$stack[$depth]['alternative_syntax']) { + throw new \RuntimeException('Analysis syntax failure, unexpected "T_ENDSWITCH".'); + } + + if (T_SWITCH !== $stack[$depth]['kind']) { + throw new \RuntimeException('Analysis type failure, unexpected "T_ENDSWITCH".'); + } + + if (0 !== $stack[$depth]['brace_count']) { + throw new \RuntimeException('Analysis count failure, unexpected "T_ENDSWITCH".'); + } + + $index = $tokens->getNextTokenOfKind($index, [';', [T_CLOSE_TAG]]); + + if ($isTypeOfInterest) { + $stack[$depth]['end'] = $index; + + yield $stack[$depth]['index'] => self::buildControlCaseStructureAnalysis($stack[$depth]); + } + + array_pop($stack); + --$depth; + + if ($depth < -1) { // @phpstan-ignore-line + throw new \RuntimeException('Analysis depth count failure ("T_ENDSWITCH").'); + } + + if (isset($stack[$depth]['kind'])) { + $isTypeOfInterest = \in_array($stack[$depth]['kind'], $types, true); + } + } + + if (!$isTypeOfInterest) { + continue; // don't bother to analyze stuff that caller is not interested in + } + + if ($token->isGivenKind(T_CASE)) { + $stack[$depth]['cases'][] = ['index' => $index, 'open' => self::findCaseOpen($tokens, $stack[$depth]['kind'], $index)]; + } elseif ($token->isGivenKind(T_DEFAULT)) { + if (null !== $stack[$depth]['default']) { + throw new \RuntimeException('Analysis multiple "default" found.'); + } + + $stack[$depth]['default'] = ['index' => $index, 'open' => self::findDefaultOpen($tokens, $stack[$depth]['kind'], $index)]; + } + } + } + + /** + * @param array{ + * kind: int, + * index: int, + * open: int, + * end: int, + * cases: list, + * default: null|array{index: int, open: int}, + * } $analysis + */ + private static function buildControlCaseStructureAnalysis(array $analysis): AbstractControlCaseStructuresAnalysis + { + $default = null === $analysis['default'] + ? null + : new DefaultAnalysis($analysis['default']['index'], $analysis['default']['open']); + + $cases = []; + + foreach ($analysis['cases'] as $case) { + $cases[$case['index']] = new CaseAnalysis($case['index'], $case['open']); + } + + sort($cases); + + if (T_SWITCH === $analysis['kind']) { + return new SwitchAnalysis( + $analysis['index'], + $analysis['open'], + $analysis['end'], + $cases, + $default + ); + } + + if (\defined('T_ENUM') && T_ENUM === $analysis['kind']) { + return new EnumAnalysis( + $analysis['index'], + $analysis['open'], + $analysis['end'], + $cases + ); + } + + if (\defined('T_MATCH') && T_MATCH === $analysis['kind']) { // @TODO: drop condition when PHP 8.0+ is required + return new MatchAnalysis( + $analysis['index'], + $analysis['open'], + $analysis['end'], + $default + ); + } + + throw new \InvalidArgumentException(sprintf('Unexpected type "%d".', $analysis['kind'])); + } + + private static function findCaseOpen(Tokens $tokens, int $kind, int $index): int + { + if (T_SWITCH === $kind) { + $ternariesCount = 0; + + do { + if ($tokens[$index]->equalsAny(['(', '{'])) { // skip constructs + $type = Tokens::detectBlockType($tokens[$index]); + $index = $tokens->findBlockEnd($type['type'], $index); + + continue; + } + + if ($tokens[$index]->equals('?')) { + ++$ternariesCount; + + continue; + } + + if ($tokens[$index]->equalsAny([':', ';'])) { + if (0 === $ternariesCount) { + break; + } + + --$ternariesCount; + } + } while (++$index); + + return $index; + } + + if (\defined('T_ENUM') && T_ENUM === $kind) { + return $tokens->getNextTokenOfKind($index, ['=', ';']); + } + + throw new \InvalidArgumentException(sprintf('Unexpected case for type "%d".', $kind)); + } + + private static function findDefaultOpen(Tokens $tokens, int $kind, int $index): int + { + if (T_SWITCH === $kind) { + return $tokens->getNextTokenOfKind($index, [':', ';']); + } + + if (\defined('T_MATCH') && T_MATCH === $kind) { // @TODO: drop condition when PHP 8.0+ is required + return $tokens->getNextTokenOfKind($index, [[T_DOUBLE_ARROW]]); + } + + throw new \InvalidArgumentException(sprintf('Unexpected default for type "%d".', $kind)); + } + + /** + * @return list + */ + private static function getTypesWithCaseOrDefault(): array + { + $supportedTypes = [T_SWITCH]; + + if (\defined('T_MATCH')) { // @TODO: drop condition when PHP 8.0+ is required + $supportedTypes[] = T_MATCH; + } + + if (\defined('T_ENUM')) { // @TODO: drop condition when PHP 8.1+ is required + $supportedTypes[] = T_ENUM; + } + + return $supportedTypes; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/DataProviderAnalyzer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/DataProviderAnalyzer.php new file mode 100644 index 00000000..f1a29444 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/DataProviderAnalyzer.php @@ -0,0 +1,101 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Analyzer; + +use PhpCsFixer\DocBlock\TypeExpression; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\DataProviderAnalysis; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Kuba Werłos + * + * @internal + */ +final class DataProviderAnalyzer +{ + private const REGEX_CLASS = '(?:\\\\?+'.TypeExpression::REGEX_IDENTIFIER + .'(\\\\'.TypeExpression::REGEX_IDENTIFIER.')*+)'; + + /** + * @return list + */ + public function getDataProviders(Tokens $tokens, int $startIndex, int $endIndex): array + { + $methods = $this->getMethods($tokens, $startIndex, $endIndex); + + $dataProviders = []; + foreach ($methods as $methodIndex) { + $docCommentIndex = $tokens->getTokenNotOfKindSibling( + $methodIndex, + -1, + [[T_ABSTRACT], [T_COMMENT], [T_FINAL], [T_FUNCTION], [T_PRIVATE], [T_PROTECTED], [T_PUBLIC], [T_STATIC], [T_WHITESPACE]] + ); + + if (!$tokens[$docCommentIndex]->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + Preg::matchAll('/@dataProvider\h+(('.self::REGEX_CLASS.'::)?'.TypeExpression::REGEX_IDENTIFIER.')/', $tokens[$docCommentIndex]->getContent(), $matches); + + /** @var list $matches */ + $matches = $matches[1]; + + foreach ($matches as $dataProviderName) { + $dataProviders[$dataProviderName][] = $docCommentIndex; + } + } + + $dataProviderAnalyses = []; + foreach ($dataProviders as $dataProviderName => $dataProviderUsages) { + $lowercaseDataProviderName = strtolower($dataProviderName); + if (!\array_key_exists($lowercaseDataProviderName, $methods)) { + continue; + } + $dataProviderAnalyses[$methods[$lowercaseDataProviderName]] = new DataProviderAnalysis( + $tokens[$methods[$lowercaseDataProviderName]]->getContent(), + $methods[$lowercaseDataProviderName], + $dataProviderUsages, + ); + } + + ksort($dataProviderAnalyses); + + return array_values($dataProviderAnalyses); + } + + /** + * @return array + */ + private function getMethods(Tokens $tokens, int $startIndex, int $endIndex): array + { + $functions = []; + for ($index = $startIndex; $index < $endIndex; ++$index) { + if (!$tokens[$index]->isGivenKind(T_FUNCTION)) { + continue; + } + + $functionNameIndex = $tokens->getNextNonWhitespace($index); + + if (!$tokens[$functionNameIndex]->isGivenKind(T_STRING)) { + continue; + } + + $functions[strtolower($tokens[$functionNameIndex]->getContent())] = $functionNameIndex; + } + + return $functions; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/FunctionsAnalyzer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/FunctionsAnalyzer.php new file mode 100644 index 00000000..bcdbe15d --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/FunctionsAnalyzer.php @@ -0,0 +1,273 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Analyzer; + +use PhpCsFixer\Tokenizer\Analyzer\Analysis\ArgumentAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceUseAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\TypeAnalysis; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @internal + */ +final class FunctionsAnalyzer +{ + /** + * @var array{tokens: string, imports: list, declarations: list} + */ + private array $functionsAnalysis = ['tokens' => '', 'imports' => [], 'declarations' => []]; + + /** + * Important: risky because of the limited (file) scope of the tool. + */ + public function isGlobalFunctionCall(Tokens $tokens, int $index): bool + { + if (!$tokens[$index]->isGivenKind(T_STRING)) { + return false; + } + + $nextIndex = $tokens->getNextMeaningfulToken($index); + + if (!$tokens[$nextIndex]->equals('(')) { + return false; + } + + $previousIsNamespaceSeparator = false; + $prevIndex = $tokens->getPrevMeaningfulToken($index); + + if ($tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) { + $previousIsNamespaceSeparator = true; + $prevIndex = $tokens->getPrevMeaningfulToken($prevIndex); + } + + $possibleKind = [ + T_DOUBLE_COLON, T_FUNCTION, CT::T_NAMESPACE_OPERATOR, T_NEW, CT::T_RETURN_REF, T_STRING, + ...Token::getObjectOperatorKinds(), + ]; + + // @TODO: drop condition when PHP 8.0+ is required + if (\defined('T_ATTRIBUTE')) { + $possibleKind[] = T_ATTRIBUTE; + } + + if ($tokens[$prevIndex]->isGivenKind($possibleKind)) { + return false; + } + + if ($tokens[$tokens->getNextMeaningfulToken($nextIndex)]->isGivenKind(CT::T_FIRST_CLASS_CALLABLE)) { + return false; + } + + if ($previousIsNamespaceSeparator) { + return true; + } + + if ($tokens->isChanged() || $tokens->getCodeHash() !== $this->functionsAnalysis['tokens']) { + $this->buildFunctionsAnalysis($tokens); + } + + // figure out in which namespace we are + $scopeStartIndex = 0; + $scopeEndIndex = \count($tokens) - 1; + $inGlobalNamespace = false; + + foreach ($tokens->getNamespaceDeclarations() as $declaration) { + $scopeStartIndex = $declaration->getScopeStartIndex(); + $scopeEndIndex = $declaration->getScopeEndIndex(); + + if ($index >= $scopeStartIndex && $index <= $scopeEndIndex) { + $inGlobalNamespace = $declaration->isGlobalNamespace(); + + break; + } + } + + $call = strtolower($tokens[$index]->getContent()); + + // check if the call is to a function declared in the same namespace as the call is done, + // if the call is already in the global namespace than declared functions are in the same + // global namespace and don't need checking + + if (!$inGlobalNamespace) { + /** @var int $functionNameIndex */ + foreach ($this->functionsAnalysis['declarations'] as $functionNameIndex) { + if ($functionNameIndex < $scopeStartIndex || $functionNameIndex > $scopeEndIndex) { + continue; + } + + if (strtolower($tokens[$functionNameIndex]->getContent()) === $call) { + return false; + } + } + } + + /** @var NamespaceUseAnalysis $functionUse */ + foreach ($this->functionsAnalysis['imports'] as $functionUse) { + if ($functionUse->getStartIndex() < $scopeStartIndex || $functionUse->getEndIndex() > $scopeEndIndex) { + continue; + } + + if ($call !== strtolower($functionUse->getShortName())) { + continue; + } + + // global import like `use function \str_repeat;` + return $functionUse->getShortName() === ltrim($functionUse->getFullName(), '\\'); + } + + if (AttributeAnalyzer::isAttribute($tokens, $index)) { + return false; + } + + return true; + } + + /** + * @return array + */ + public function getFunctionArguments(Tokens $tokens, int $functionIndex): array + { + $argumentsStart = $tokens->getNextTokenOfKind($functionIndex, ['(']); + $argumentsEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $argumentsStart); + $argumentAnalyzer = new ArgumentsAnalyzer(); + $arguments = []; + + foreach ($argumentAnalyzer->getArguments($tokens, $argumentsStart, $argumentsEnd) as $start => $end) { + $argumentInfo = $argumentAnalyzer->getArgumentInfo($tokens, $start, $end); + $arguments[$argumentInfo->getName()] = $argumentInfo; + } + + return $arguments; + } + + public function getFunctionReturnType(Tokens $tokens, int $methodIndex): ?TypeAnalysis + { + $argumentsStart = $tokens->getNextTokenOfKind($methodIndex, ['(']); + $argumentsEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $argumentsStart); + $typeColonIndex = $tokens->getNextMeaningfulToken($argumentsEnd); + + if (!$tokens[$typeColonIndex]->isGivenKind(CT::T_TYPE_COLON)) { + return null; + } + + $type = ''; + $typeStartIndex = $tokens->getNextMeaningfulToken($typeColonIndex); + $typeEndIndex = $typeStartIndex; + $functionBodyStart = $tokens->getNextTokenOfKind($typeColonIndex, ['{', ';', [T_DOUBLE_ARROW]]); + + for ($i = $typeStartIndex; $i < $functionBodyStart; ++$i) { + if ($tokens[$i]->isWhitespace() || $tokens[$i]->isComment()) { + continue; + } + + $type .= $tokens[$i]->getContent(); + $typeEndIndex = $i; + } + + return new TypeAnalysis($type, $typeStartIndex, $typeEndIndex); + } + + public function isTheSameClassCall(Tokens $tokens, int $index): bool + { + if (!$tokens->offsetExists($index)) { + throw new \InvalidArgumentException(sprintf('Token index %d does not exist.', $index)); + } + + $operatorIndex = $tokens->getPrevMeaningfulToken($index); + + if (null === $operatorIndex) { + return false; + } + + if (!$tokens[$operatorIndex]->isObjectOperator() && !$tokens[$operatorIndex]->isGivenKind(T_DOUBLE_COLON)) { + return false; + } + + $referenceIndex = $tokens->getPrevMeaningfulToken($operatorIndex); + + if (null === $referenceIndex) { + return false; + } + + if (!$tokens[$referenceIndex]->equalsAny([[T_VARIABLE, '$this'], [T_STRING, 'self'], [T_STATIC, 'static']], false)) { + return false; + } + + return $tokens[$tokens->getNextMeaningfulToken($index)]->equals('('); + } + + private function buildFunctionsAnalysis(Tokens $tokens): void + { + $this->functionsAnalysis = [ + 'tokens' => $tokens->getCodeHash(), + 'imports' => [], + 'declarations' => [], + ]; + + // find declarations + + if ($tokens->isTokenKindFound(T_FUNCTION)) { + $end = \count($tokens); + + for ($i = 0; $i < $end; ++$i) { + // skip classy, we are looking for functions not methods + if ($tokens[$i]->isGivenKind(Token::getClassyTokenKinds())) { + $i = $tokens->getNextTokenOfKind($i, ['(', '{']); + + if ($tokens[$i]->equals('(')) { // anonymous class + $i = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $i); + $i = $tokens->getNextTokenOfKind($i, ['{']); + } + + $i = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $i); + + continue; + } + + if (!$tokens[$i]->isGivenKind(T_FUNCTION)) { + continue; + } + + $i = $tokens->getNextMeaningfulToken($i); + + if ($tokens[$i]->isGivenKind(CT::T_RETURN_REF)) { + $i = $tokens->getNextMeaningfulToken($i); + } + + if (!$tokens[$i]->isGivenKind(T_STRING)) { + continue; + } + + $this->functionsAnalysis['declarations'][] = $i; + } + } + + // find imported functions + + $namespaceUsesAnalyzer = new NamespaceUsesAnalyzer(); + + if ($tokens->isTokenKindFound(CT::T_FUNCTION_IMPORT)) { + $declarations = $namespaceUsesAnalyzer->getDeclarationsFromTokens($tokens); + + foreach ($declarations as $declaration) { + if ($declaration->isFunction()) { + $this->functionsAnalysis['imports'][] = $declaration; + } + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/GotoLabelAnalyzer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/GotoLabelAnalyzer.php new file mode 100644 index 00000000..15d5b066 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/GotoLabelAnalyzer.php @@ -0,0 +1,40 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Analyzer; + +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @internal + */ +final class GotoLabelAnalyzer +{ + public function belongsToGoToLabel(Tokens $tokens, int $index): bool + { + if (!$tokens[$index]->equals(':')) { + return false; + } + + $prevMeaningfulTokenIndex = $tokens->getPrevMeaningfulToken($index); + + if (!$tokens[$prevMeaningfulTokenIndex]->isGivenKind(T_STRING)) { + return false; + } + + $prevMeaningfulTokenIndex = $tokens->getPrevMeaningfulToken($prevMeaningfulTokenIndex); + + return $tokens[$prevMeaningfulTokenIndex]->equalsAny([':', ';', '{', '}', [T_OPEN_TAG]]); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/NamespaceUsesAnalyzer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/NamespaceUsesAnalyzer.php new file mode 100644 index 00000000..f220ccaf --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/NamespaceUsesAnalyzer.php @@ -0,0 +1,213 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Analyzer; + +use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceUseAnalysis; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author VeeWee + * @author Greg Korba + * + * @internal + * + * @TODO Drop `allowMultiUses` opt-in flag when all fixers are updated and can handle multi-use statements. + */ +final class NamespaceUsesAnalyzer +{ + /** + * @return list + */ + public function getDeclarationsFromTokens(Tokens $tokens, bool $allowMultiUses = false): array + { + $tokenAnalyzer = new TokensAnalyzer($tokens); + $useIndices = $tokenAnalyzer->getImportUseIndexes(); + + return $this->getDeclarations($tokens, $useIndices, $allowMultiUses); + } + + /** + * @return list + */ + public function getDeclarationsInNamespace(Tokens $tokens, NamespaceAnalysis $namespace, bool $allowMultiUses = false): array + { + $namespaceUses = []; + + foreach ($this->getDeclarationsFromTokens($tokens, $allowMultiUses) as $namespaceUse) { + if ($namespaceUse->getStartIndex() >= $namespace->getScopeStartIndex() && $namespaceUse->getStartIndex() <= $namespace->getScopeEndIndex()) { + $namespaceUses[] = $namespaceUse; + } + } + + return $namespaceUses; + } + + /** + * @param list $useIndices + * + * @return list + */ + private function getDeclarations(Tokens $tokens, array $useIndices, bool $allowMultiUses = false): array + { + $uses = []; + + foreach ($useIndices as $index) { + $endIndex = $tokens->getNextTokenOfKind($index, [';', [T_CLOSE_TAG]]); + + $declarations = $this->parseDeclarations($index, $endIndex, $tokens); + if (false === $allowMultiUses) { + $declarations = array_filter($declarations, static fn (NamespaceUseAnalysis $declaration) => !$declaration->isInMulti()); + } + + if ([] !== $declarations) { + $uses = array_merge($uses, $declarations); + } + } + + return $uses; + } + + /** + * @return list + */ + private function parseDeclarations(int $startIndex, int $endIndex, Tokens $tokens): array + { + $type = $this->determineImportType($tokens, $startIndex); + $potentialMulti = $tokens->getNextTokenOfKind($startIndex, [',', [CT::T_GROUP_IMPORT_BRACE_OPEN]]); + $multi = null !== $potentialMulti && $potentialMulti < $endIndex; + $index = $tokens->getNextTokenOfKind($startIndex, [[T_STRING], [T_NS_SEPARATOR]]); + $imports = []; + + while (null !== $index && $index <= $endIndex) { + $qualifiedName = $this->getNearestQualifiedName($tokens, $index); + $token = $tokens[$qualifiedName['afterIndex']]; + + if ($token->isGivenKind(CT::T_GROUP_IMPORT_BRACE_OPEN)) { + $groupStart = $groupIndex = $qualifiedName['afterIndex']; + $groupEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_GROUP_IMPORT_BRACE, $groupStart); + + while ($groupIndex < $groupEnd) { + $chunkStart = $tokens->getNextMeaningfulToken($groupIndex); + + // Finish parsing on trailing comma (no more chunks there) + if ($tokens[$chunkStart]->isGivenKind(CT::T_GROUP_IMPORT_BRACE_CLOSE)) { + break; + } + + $groupQualifiedName = $this->getNearestQualifiedName($tokens, $chunkStart); + $imports[] = new NamespaceUseAnalysis( + $type, + $qualifiedName['fullName'].$groupQualifiedName['fullName'], + $groupQualifiedName['shortName'], + $groupQualifiedName['aliased'], + true, + $startIndex, + $endIndex, + $chunkStart, + $tokens->getPrevMeaningfulToken($groupQualifiedName['afterIndex']) + ); + + $groupIndex = $groupQualifiedName['afterIndex']; + } + + $index = $groupIndex; + } elseif ($token->equalsAny([',', ';', [T_CLOSE_TAG]])) { + $previousToken = $tokens->getPrevMeaningfulToken($qualifiedName['afterIndex']); + + if (!$tokens[$previousToken]->isGivenKind(CT::T_GROUP_IMPORT_BRACE_CLOSE)) { + $imports[] = new NamespaceUseAnalysis( + $type, + $qualifiedName['fullName'], + $qualifiedName['shortName'], + $qualifiedName['aliased'], + $multi, + $startIndex, + $endIndex, + $multi ? $index : null, + $multi ? $previousToken : null + ); + } + + $index = $qualifiedName['afterIndex']; + } + + $index = $tokens->getNextMeaningfulToken($index); + } + + return $imports; + } + + /** + * @return NamespaceUseAnalysis::TYPE_* + */ + private function determineImportType(Tokens $tokens, int $startIndex): int + { + $potentialType = $tokens[$tokens->getNextMeaningfulToken($startIndex)]; + + if ($potentialType->isGivenKind(CT::T_FUNCTION_IMPORT)) { + return NamespaceUseAnalysis::TYPE_FUNCTION; + } + + if ($potentialType->isGivenKind(CT::T_CONST_IMPORT)) { + return NamespaceUseAnalysis::TYPE_CONSTANT; + } + + return NamespaceUseAnalysis::TYPE_CLASS; + } + + /** + * @return array{fullName: string, shortName: string, aliased: bool, afterIndex: int} + */ + private function getNearestQualifiedName(Tokens $tokens, int $index): array + { + $fullName = $shortName = ''; + $aliased = false; + + while (null !== $index) { + $token = $tokens[$index]; + + if ($token->isGivenKind(T_STRING)) { + $shortName = $token->getContent(); + if (!$aliased) { + $fullName .= $shortName; + } + } elseif ($token->isGivenKind(T_NS_SEPARATOR)) { + $fullName .= $token->getContent(); + } elseif ($token->isGivenKind(T_AS)) { + $aliased = true; + } elseif ($token->equalsAny([ + ',', + ';', + [CT::T_GROUP_IMPORT_BRACE_OPEN], + [CT::T_GROUP_IMPORT_BRACE_CLOSE], + [T_CLOSE_TAG], + ])) { + break; + } + + $index = $tokens->getNextMeaningfulToken($index); + } + + return [ + 'fullName' => $fullName, + 'shortName' => $shortName, + 'aliased' => $aliased, + 'afterIndex' => $index, + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/NamespacesAnalyzer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/NamespacesAnalyzer.php new file mode 100644 index 00000000..3890523a --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/NamespacesAnalyzer.php @@ -0,0 +1,88 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Analyzer; + +use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceAnalysis; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @internal + */ +final class NamespacesAnalyzer +{ + /** + * @return list + */ + public function getDeclarations(Tokens $tokens): array + { + $namespaces = []; + + for ($index = 1, $count = \count($tokens); $index < $count; ++$index) { + $token = $tokens[$index]; + + if (!$token->isGivenKind(T_NAMESPACE)) { + continue; + } + + $declarationEndIndex = $tokens->getNextTokenOfKind($index, [';', '{']); + $namespace = trim($tokens->generatePartialCode($index + 1, $declarationEndIndex - 1)); + $declarationParts = explode('\\', $namespace); + $shortName = end($declarationParts); + + if ($tokens[$declarationEndIndex]->equals('{')) { + $scopeEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $declarationEndIndex); + } else { + $scopeEndIndex = $tokens->getNextTokenOfKind($declarationEndIndex, [[T_NAMESPACE]]); + if (null === $scopeEndIndex) { + $scopeEndIndex = \count($tokens); + } + --$scopeEndIndex; + } + + $namespaces[] = new NamespaceAnalysis( + $namespace, + $shortName, + $index, + $declarationEndIndex, + $index, + $scopeEndIndex + ); + + // Continue the analysis after the end of this namespace to find the next one + $index = $scopeEndIndex; + } + + if (0 === \count($namespaces)) { + $namespaces[] = new NamespaceAnalysis('', '', 0, 0, 0, \count($tokens) - 1); + } + + return $namespaces; + } + + public function getNamespaceAt(Tokens $tokens, int $index): NamespaceAnalysis + { + if (!$tokens->offsetExists($index)) { + throw new \InvalidArgumentException(sprintf('Token index %d does not exist.', $index)); + } + + foreach ($this->getDeclarations($tokens) as $namespace) { + if ($namespace->getScopeStartIndex() <= $index && $namespace->getScopeEndIndex() >= $index) { + return $namespace; + } + } + + throw new \LogicException(sprintf('Unable to get the namespace at index %d.', $index)); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/RangeAnalyzer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/RangeAnalyzer.php new file mode 100644 index 00000000..51a2abde --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/RangeAnalyzer.php @@ -0,0 +1,90 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Analyzer; + +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @internal + */ +final class RangeAnalyzer +{ + private function __construct() + { + // cannot create instance of util. class + } + + /** + * Meaningful compare of tokens within ranges. + * + * @param array{start: int, end: int} $range1 + * @param array{start: int, end: int} $range2 + */ + public static function rangeEqualsRange(Tokens $tokens, array $range1, array $range2): bool + { + $leftStart = $range1['start']; + $leftEnd = $range1['end']; + + if ($tokens[$leftStart]->isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) { + $leftStart = $tokens->getNextMeaningfulToken($leftStart); + } + + while ($tokens[$leftStart]->equals('(') && $tokens[$leftEnd]->equals(')')) { + $leftStart = $tokens->getNextMeaningfulToken($leftStart); + $leftEnd = $tokens->getPrevMeaningfulToken($leftEnd); + } + + $rightStart = $range2['start']; + $rightEnd = $range2['end']; + + if ($tokens[$rightStart]->isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) { + $rightStart = $tokens->getNextMeaningfulToken($rightStart); + } + + while ($tokens[$rightStart]->equals('(') && $tokens[$rightEnd]->equals(')')) { + $rightStart = $tokens->getNextMeaningfulToken($rightStart); + $rightEnd = $tokens->getPrevMeaningfulToken($rightEnd); + } + + $arrayOpenTypes = ['[', [CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN]]; + $arrayCloseTypes = [']', [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE]]; + + while (true) { + $leftToken = $tokens[$leftStart]; + $rightToken = $tokens[$rightStart]; + + if ( + !$leftToken->equals($rightToken) + && !($leftToken->equalsAny($arrayOpenTypes) && $rightToken->equalsAny($arrayOpenTypes)) + && !($leftToken->equalsAny($arrayCloseTypes) && $rightToken->equalsAny($arrayCloseTypes)) + ) { + return false; + } + + $leftStart = $tokens->getNextMeaningfulToken($leftStart); + $rightStart = $tokens->getNextMeaningfulToken($rightStart); + + $reachedLeftEnd = null === $leftStart || $leftStart > $leftEnd; // reached end left or moved over + $reachedRightEnd = null === $rightStart || $rightStart > $rightEnd; // reached end right or moved over + + if (!$reachedLeftEnd && !$reachedRightEnd) { + continue; + } + + return $reachedLeftEnd && $reachedRightEnd; + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/ReferenceAnalyzer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/ReferenceAnalyzer.php new file mode 100644 index 00000000..0c7d4bd9 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/ReferenceAnalyzer.php @@ -0,0 +1,49 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Analyzer; + +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Kuba Werłos + * + * @internal + */ +final class ReferenceAnalyzer +{ + public function isReference(Tokens $tokens, int $index): bool + { + if ($tokens[$index]->isGivenKind(CT::T_RETURN_REF)) { + return true; + } + + if (!$tokens[$index]->equals('&')) { + return false; + } + + /** @var int $index */ + $index = $tokens->getPrevMeaningfulToken($index); + if ($tokens[$index]->equalsAny(['=', [T_AS], [T_CALLABLE], [T_DOUBLE_ARROW], [CT::T_ARRAY_TYPEHINT]])) { + return true; + } + + if ($tokens[$index]->isGivenKind(T_STRING)) { + $index = $tokens->getPrevMeaningfulToken($index); + } + + return $tokens[$index]->equalsAny(['(', ',', [T_NS_SEPARATOR], [CT::T_NULLABLE_TYPE]]); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/SwitchAnalyzer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/SwitchAnalyzer.php new file mode 100644 index 00000000..fce1f9e3 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/SwitchAnalyzer.php @@ -0,0 +1,69 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Analyzer; + +use PhpCsFixer\Tokenizer\Analyzer\Analysis\SwitchAnalysis; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @internal + */ +final class SwitchAnalyzer +{ + /** @var array> */ + private static array $cache = []; + + public static function belongsToSwitch(Tokens $tokens, int $index): bool + { + if (!$tokens[$index]->equals(':')) { + return false; + } + + $tokensHash = md5(serialize($tokens->toArray())); + + if (!\array_key_exists($tokensHash, self::$cache)) { + self::$cache[$tokensHash] = self::getColonIndicesForSwitch(clone $tokens); + } + + return \in_array($index, self::$cache[$tokensHash], true); + } + + /** + * @return list + */ + private static function getColonIndicesForSwitch(Tokens $tokens): array + { + $colonIndices = []; + + /** @var SwitchAnalysis $analysis */ + foreach (ControlCaseStructuresAnalyzer::findControlStructures($tokens, [T_SWITCH]) as $analysis) { + if ($tokens[$analysis->getOpenIndex()]->equals(':')) { + $colonIndices[] = $analysis->getOpenIndex(); + } + + foreach ($analysis->getCases() as $case) { + $colonIndices[] = $case->getColonIndex(); + } + + $defaultAnalysis = $analysis->getDefaultAnalysis(); + + if (null !== $defaultAnalysis) { + $colonIndices[] = $defaultAnalysis->getColonIndex(); + } + } + + return $colonIndices; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/WhitespacesAnalyzer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/WhitespacesAnalyzer.php new file mode 100644 index 00000000..0845ce40 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/WhitespacesAnalyzer.php @@ -0,0 +1,52 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Analyzer; + +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @internal + */ +final class WhitespacesAnalyzer +{ + public static function detectIndent(Tokens $tokens, int $index): string + { + while (true) { + $whitespaceIndex = $tokens->getPrevTokenOfKind($index, [[T_WHITESPACE]]); + + if (null === $whitespaceIndex) { + return ''; + } + + $whitespaceToken = $tokens[$whitespaceIndex]; + + if (str_contains($whitespaceToken->getContent(), "\n")) { + break; + } + + $prevToken = $tokens[$whitespaceIndex - 1]; + + if ($prevToken->isGivenKind([T_OPEN_TAG, T_COMMENT]) && "\n" === substr($prevToken->getContent(), -1)) { + break; + } + + $index = $whitespaceIndex; + } + + $explodedContent = explode("\n", $whitespaceToken->getContent()); + + return end($explodedContent); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/CT.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/CT.php new file mode 100644 index 00000000..6eae7cca --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/CT.php @@ -0,0 +1,106 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer; + +/** + * @author Dariusz Rumiński + */ +final class CT +{ + public const T_ARRAY_INDEX_CURLY_BRACE_CLOSE = 10_001; + public const T_ARRAY_INDEX_CURLY_BRACE_OPEN = 10_002; + public const T_ARRAY_SQUARE_BRACE_CLOSE = 10_003; + public const T_ARRAY_SQUARE_BRACE_OPEN = 10_004; + public const T_ARRAY_TYPEHINT = 10_005; + public const T_BRACE_CLASS_INSTANTIATION_CLOSE = 10_006; + public const T_BRACE_CLASS_INSTANTIATION_OPEN = 10_007; + public const T_CLASS_CONSTANT = 10_008; + public const T_CONST_IMPORT = 10_009; + public const T_CURLY_CLOSE = 10_010; + public const T_DESTRUCTURING_SQUARE_BRACE_CLOSE = 10_011; + public const T_DESTRUCTURING_SQUARE_BRACE_OPEN = 10_012; + public const T_DOLLAR_CLOSE_CURLY_BRACES = 10_013; + public const T_DYNAMIC_PROP_BRACE_CLOSE = 10_014; + public const T_DYNAMIC_PROP_BRACE_OPEN = 10_015; + public const T_DYNAMIC_VAR_BRACE_CLOSE = 10_016; + public const T_DYNAMIC_VAR_BRACE_OPEN = 10_017; + public const T_FUNCTION_IMPORT = 10_018; + public const T_GROUP_IMPORT_BRACE_CLOSE = 10_019; + public const T_GROUP_IMPORT_BRACE_OPEN = 10_020; + public const T_NAMESPACE_OPERATOR = 10_021; + public const T_NULLABLE_TYPE = 10_022; + public const T_RETURN_REF = 10_023; + public const T_TYPE_ALTERNATION = 10_024; + public const T_TYPE_COLON = 10_025; + public const T_USE_LAMBDA = 10_026; + public const T_USE_TRAIT = 10_027; + public const T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC = 10_028; + public const T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED = 10_029; + public const T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE = 10_030; + public const T_ATTRIBUTE_CLOSE = 10_031; + public const T_NAMED_ARGUMENT_NAME = 10_032; + public const T_NAMED_ARGUMENT_COLON = 10_033; + public const T_FIRST_CLASS_CALLABLE = 10_034; + public const T_TYPE_INTERSECTION = 10_035; + public const T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_OPEN = 10_036; + public const T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE = 10_037; + public const T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN = 10_038; + public const T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE = 10_039; + + private function __construct() {} + + /** + * Get name for custom token. + * + * @param int $value custom token value + */ + public static function getName(int $value): string + { + if (!self::has($value)) { + throw new \InvalidArgumentException(sprintf('No custom token was found for "%s".', $value)); + } + + $tokens = self::getMapById(); + + return 'CT::'.$tokens[$value]; + } + + /** + * Check if given custom token exists. + * + * @param int $value custom token value + */ + public static function has(int $value): bool + { + $tokens = self::getMapById(); + + return isset($tokens[$value]); + } + + /** + * @return array + */ + private static function getMapById(): array + { + static $constants; + + if (null === $constants) { + $reflection = new \ReflectionClass(self::class); + $constants = array_flip($reflection->getConstants()); + } + + return $constants; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/CodeHasher.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/CodeHasher.php new file mode 100644 index 00000000..86a4a68a --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/CodeHasher.php @@ -0,0 +1,38 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class CodeHasher +{ + private function __construct() + { + // cannot create instance of util. class + } + + /** + * Calculate hash for code. + * + * @return non-empty-string + */ + public static function calculateCodeHash(string $code): string + { + return md5($code); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Processor/ImportProcessor.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Processor/ImportProcessor.php new file mode 100644 index 00000000..35fb0f9d --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Processor/ImportProcessor.php @@ -0,0 +1,101 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Processor; + +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\WhitespacesFixerConfig; + +/** + * @author Greg Korba + */ +final class ImportProcessor +{ + private WhitespacesFixerConfig $whitespacesConfig; + + public function __construct(WhitespacesFixerConfig $whitespacesConfig) + { + $this->whitespacesConfig = $whitespacesConfig; + } + + /** + * @param array{ + * const?: array, + * class?: array, + * function?: array + * } $imports + */ + public function insertImports(Tokens $tokens, array $imports, int $atIndex): void + { + $lineEnding = $this->whitespacesConfig->getLineEnding(); + + if (!$tokens[$atIndex]->isWhitespace() || !str_contains($tokens[$atIndex]->getContent(), "\n")) { + $tokens->insertAt($atIndex, new Token([T_WHITESPACE, $lineEnding])); + } + + foreach ($imports as $type => $typeImports) { + sort($typeImports); + + $items = []; + + foreach ($typeImports as $name) { + $items = array_merge($items, [ + new Token([T_WHITESPACE, $lineEnding]), + new Token([T_USE, 'use']), + new Token([T_WHITESPACE, ' ']), + ]); + + if ('const' === $type) { + $items[] = new Token([CT::T_CONST_IMPORT, 'const']); + $items[] = new Token([T_WHITESPACE, ' ']); + } elseif ('function' === $type) { + $items[] = new Token([CT::T_FUNCTION_IMPORT, 'function']); + $items[] = new Token([T_WHITESPACE, ' ']); + } + + $items = array_merge($items, self::tokenizeName($name)); + $items[] = new Token(';'); + } + + $tokens->insertAt($atIndex, $items); + } + } + + /** + * @param class-string $name + * + * @return list + */ + public static function tokenizeName(string $name): array + { + $parts = explode('\\', $name); + $newTokens = []; + + if ('' === $parts[0]) { + $newTokens[] = new Token([T_NS_SEPARATOR, '\\']); + array_shift($parts); + } + + foreach ($parts as $part) { + $newTokens[] = new Token([T_STRING, $part]); + $newTokens[] = new Token([T_NS_SEPARATOR, '\\']); + } + + array_pop($newTokens); + + return $newTokens; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Token.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Token.php new file mode 100644 index 00000000..213644c6 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Token.php @@ -0,0 +1,519 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer; + +use PhpCsFixer\Utils; + +/** + * Representation of single token. + * As a token prototype you should understand a single element generated by token_get_all. + * + * @author Dariusz Rumiński + */ +final class Token +{ + /** + * Content of token prototype. + */ + private string $content; + + /** + * ID of token prototype, if available. + */ + private ?int $id = null; + + /** + * If token prototype is an array. + */ + private bool $isArray; + + /** + * Flag is token was changed. + */ + private bool $changed = false; + + /** + * @param array{int, string}|string $token token prototype + */ + public function __construct($token) + { + if (\is_array($token)) { + if (!\is_int($token[0])) { + throw new \InvalidArgumentException(sprintf( + 'Id must be an int, got "%s".', + get_debug_type($token[0]) + )); + } + + if (!\is_string($token[1])) { + throw new \InvalidArgumentException(sprintf( + 'Content must be a string, got "%s".', + get_debug_type($token[1]) + )); + } + + if ('' === $token[1]) { + throw new \InvalidArgumentException('Cannot set empty content for id-based Token.'); + } + + $this->isArray = true; + $this->id = $token[0]; + $this->content = $token[1]; + } elseif (\is_string($token)) { + $this->isArray = false; + $this->content = $token; + } else { + throw new \InvalidArgumentException(sprintf('Cannot recognize input value as valid Token prototype, got "%s".', get_debug_type($token))); + } + } + + /** + * @return list + */ + public static function getCastTokenKinds(): array + { + static $castTokens = [T_ARRAY_CAST, T_BOOL_CAST, T_DOUBLE_CAST, T_INT_CAST, T_OBJECT_CAST, T_STRING_CAST, T_UNSET_CAST]; + + return $castTokens; + } + + /** + * Get classy tokens kinds: T_CLASS, T_INTERFACE and T_TRAIT. + * + * @return list + */ + public static function getClassyTokenKinds(): array + { + static $classTokens; + + if (null === $classTokens) { + $classTokens = [T_CLASS, T_TRAIT, T_INTERFACE]; + + if (\defined('T_ENUM')) { // @TODO: drop condition when PHP 8.1+ is required + $classTokens[] = T_ENUM; + } + } + + return $classTokens; + } + + /** + * Get object operator tokens kinds: T_OBJECT_OPERATOR and (if available) T_NULLSAFE_OBJECT_OPERATOR. + * + * @return list + */ + public static function getObjectOperatorKinds(): array + { + static $objectOperators = null; + + if (null === $objectOperators) { + $objectOperators = [T_OBJECT_OPERATOR]; + if (\defined('T_NULLSAFE_OBJECT_OPERATOR')) { + $objectOperators[] = T_NULLSAFE_OBJECT_OPERATOR; + } + } + + return $objectOperators; + } + + /** + * Check if token is equals to given one. + * + * If tokens are arrays, then only keys defined in parameter token are checked. + * + * @param array{0: int, 1?: string}|string|Token $other token or it's prototype + * @param bool $caseSensitive perform a case sensitive comparison + */ + public function equals($other, bool $caseSensitive = true): bool + { + if (\defined('T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG')) { // @TODO: drop condition when PHP 8.1+ is required + if ('&' === $other) { + return '&' === $this->content && (null === $this->id || $this->isGivenKind([T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG, T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG])); + } + if (null === $this->id && '&' === $this->content) { + return $other instanceof self && '&' === $other->content && (null === $other->id || $other->isGivenKind([T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG, T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG])); + } + } + + if ($other instanceof self) { + // Inlined getPrototype() on this very hot path. + // We access the private properties of $other directly to save function call overhead. + // This is only possible because $other is of the same class as `self`. + if (!$other->isArray) { + $otherPrototype = $other->content; + } else { + $otherPrototype = [ + $other->id, + $other->content, + ]; + } + } else { + $otherPrototype = $other; + } + + if ($this->isArray !== \is_array($otherPrototype)) { + return false; + } + + if (!$this->isArray) { + return $this->content === $otherPrototype; + } + + if ($this->id !== $otherPrototype[0]) { + return false; + } + + if (isset($otherPrototype[1])) { + if ($caseSensitive) { + if ($this->content !== $otherPrototype[1]) { + return false; + } + } elseif (0 !== strcasecmp($this->content, $otherPrototype[1])) { + return false; + } + } + + // detect unknown keys + unset($otherPrototype[0], $otherPrototype[1]); + + return [] === $otherPrototype; + } + + /** + * Check if token is equals to one of given. + * + * @param list $others array of tokens or token prototypes + * @param bool $caseSensitive perform a case sensitive comparison + */ + public function equalsAny(array $others, bool $caseSensitive = true): bool + { + foreach ($others as $other) { + if ($this->equals($other, $caseSensitive)) { + return true; + } + } + + return false; + } + + /** + * A helper method used to find out whether a certain input token has to be case-sensitively matched. + * + * @param array|bool $caseSensitive global case sensitiveness or an array of booleans, whose keys should match + * the ones used in $sequence. If any is missing, the default case-sensitive + * comparison is used + * @param int $key the key of the token that has to be looked up + * + * @deprecated + */ + public static function isKeyCaseSensitive($caseSensitive, int $key): bool + { + Utils::triggerDeprecation(new \InvalidArgumentException(sprintf( + 'Method "%s" is deprecated and will be removed in the next major version.', + __METHOD__ + ))); + + if (\is_array($caseSensitive)) { + return $caseSensitive[$key] ?? true; + } + + return $caseSensitive; + } + + /** + * @return array{int, string}|string + */ + public function getPrototype() + { + if (!$this->isArray) { + return $this->content; + } + + return [ + $this->id, + $this->content, + ]; + } + + /** + * Get token's content. + * + * It shall be used only for getting the content of token, not for checking it against excepted value. + * + * @return non-empty-string + */ + public function getContent(): string + { + return $this->content; + } + + /** + * Get token's id. + * + * It shall be used only for getting the internal id of token, not for checking it against excepted value. + */ + public function getId(): ?int + { + return $this->id; + } + + /** + * Get token's name. + * + * It shall be used only for getting the name of token, not for checking it against excepted value. + * + * @return null|string token name + */ + public function getName(): ?string + { + if (null === $this->id) { + return null; + } + + return self::getNameForId($this->id); + } + + /** + * Get token's name. + * + * It shall be used only for getting the name of token, not for checking it against excepted value. + * + * @return null|string token name + */ + public static function getNameForId(int $id): ?string + { + if (CT::has($id)) { + return CT::getName($id); + } + + $name = token_name($id); + + return 'UNKNOWN' === $name ? null : $name; + } + + /** + * Generate array containing all keywords that exists in PHP version in use. + * + * @return array + */ + public static function getKeywords(): array + { + static $keywords = null; + + if (null === $keywords) { + $keywords = self::getTokenKindsForNames(['T_ABSTRACT', 'T_ARRAY', 'T_AS', 'T_BREAK', 'T_CALLABLE', 'T_CASE', + 'T_CATCH', 'T_CLASS', 'T_CLONE', 'T_CONST', 'T_CONTINUE', 'T_DECLARE', 'T_DEFAULT', 'T_DO', + 'T_ECHO', 'T_ELSE', 'T_ELSEIF', 'T_EMPTY', 'T_ENDDECLARE', 'T_ENDFOR', 'T_ENDFOREACH', + 'T_ENDIF', 'T_ENDSWITCH', 'T_ENDWHILE', 'T_EVAL', 'T_EXIT', 'T_EXTENDS', 'T_FINAL', + 'T_FINALLY', 'T_FN', 'T_FOR', 'T_FOREACH', 'T_FUNCTION', 'T_GLOBAL', 'T_GOTO', 'T_HALT_COMPILER', + 'T_IF', 'T_IMPLEMENTS', 'T_INCLUDE', 'T_INCLUDE_ONCE', 'T_INSTANCEOF', 'T_INSTEADOF', + 'T_INTERFACE', 'T_ISSET', 'T_LIST', 'T_LOGICAL_AND', 'T_LOGICAL_OR', 'T_LOGICAL_XOR', + 'T_NAMESPACE', 'T_MATCH', 'T_NEW', 'T_PRINT', 'T_PRIVATE', 'T_PROTECTED', 'T_PUBLIC', 'T_REQUIRE', + 'T_REQUIRE_ONCE', 'T_RETURN', 'T_STATIC', 'T_SWITCH', 'T_THROW', 'T_TRAIT', 'T_TRY', + 'T_UNSET', 'T_USE', 'T_VAR', 'T_WHILE', 'T_YIELD', 'T_YIELD_FROM', 'T_READONLY', 'T_ENUM', + ]) + [ + CT::T_ARRAY_TYPEHINT => CT::T_ARRAY_TYPEHINT, + CT::T_CLASS_CONSTANT => CT::T_CLASS_CONSTANT, + CT::T_CONST_IMPORT => CT::T_CONST_IMPORT, + CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE => CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE, + CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED => CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED, + CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC => CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC, + CT::T_FUNCTION_IMPORT => CT::T_FUNCTION_IMPORT, + CT::T_NAMESPACE_OPERATOR => CT::T_NAMESPACE_OPERATOR, + CT::T_USE_LAMBDA => CT::T_USE_LAMBDA, + CT::T_USE_TRAIT => CT::T_USE_TRAIT, + ]; + } + + return $keywords; + } + + /** + * Generate array containing all predefined constants that exists in PHP version in use. + * + * @see https://php.net/manual/en/language.constants.predefined.php + * + * @return array + */ + public static function getMagicConstants(): array + { + static $magicConstants = null; + + if (null === $magicConstants) { + $magicConstants = self::getTokenKindsForNames(['T_CLASS_C', 'T_DIR', 'T_FILE', 'T_FUNC_C', 'T_LINE', 'T_METHOD_C', 'T_NS_C', 'T_TRAIT_C']); + } + + return $magicConstants; + } + + /** + * Check if token prototype is an array. + * + * @return bool is array + */ + public function isArray(): bool + { + return $this->isArray; + } + + /** + * Check if token is one of type cast tokens. + */ + public function isCast(): bool + { + return $this->isGivenKind(self::getCastTokenKinds()); + } + + /** + * Check if token is one of classy tokens: T_CLASS, T_INTERFACE, T_TRAIT or T_ENUM. + */ + public function isClassy(): bool + { + return $this->isGivenKind(self::getClassyTokenKinds()); + } + + /** + * Check if token is one of comment tokens: T_COMMENT or T_DOC_COMMENT. + */ + public function isComment(): bool + { + static $commentTokens = [T_COMMENT, T_DOC_COMMENT]; + + return $this->isGivenKind($commentTokens); + } + + /** + * Check if token is one of object operator tokens: T_OBJECT_OPERATOR or T_NULLSAFE_OBJECT_OPERATOR. + */ + public function isObjectOperator(): bool + { + return $this->isGivenKind(self::getObjectOperatorKinds()); + } + + /** + * Check if token is one of given kind. + * + * @param int|list $possibleKind kind or array of kinds + */ + public function isGivenKind($possibleKind): bool + { + return $this->isArray && (\is_array($possibleKind) ? \in_array($this->id, $possibleKind, true) : $this->id === $possibleKind); + } + + /** + * Check if token is a keyword. + */ + public function isKeyword(): bool + { + $keywords = self::getKeywords(); + + return $this->isArray && isset($keywords[$this->id]); + } + + /** + * Check if token is a native PHP constant: true, false or null. + */ + public function isNativeConstant(): bool + { + static $nativeConstantStrings = ['true', 'false', 'null']; + + return $this->isArray && \in_array(strtolower($this->content), $nativeConstantStrings, true); + } + + /** + * Returns if the token is of a Magic constants type. + * + * @see https://php.net/manual/en/language.constants.predefined.php + */ + public function isMagicConstant(): bool + { + $magicConstants = self::getMagicConstants(); + + return $this->isArray && isset($magicConstants[$this->id]); + } + + /** + * Check if token is whitespace. + * + * @param null|string $whitespaces whitespace characters, default is " \t\n\r\0\x0B" + */ + public function isWhitespace(?string $whitespaces = " \t\n\r\0\x0B"): bool + { + if (null === $whitespaces) { + $whitespaces = " \t\n\r\0\x0B"; + } + + if ($this->isArray && !$this->isGivenKind(T_WHITESPACE)) { + return false; + } + + return '' === trim($this->content, $whitespaces); + } + + /** + * @return array{ + * id: int|null, + * name: string|null, + * content: string, + * isArray: bool, + * changed: bool, + * } + */ + public function toArray(): array + { + return [ + 'id' => $this->id, + 'name' => $this->getName(), + 'content' => $this->content, + 'isArray' => $this->isArray, + 'changed' => $this->changed, + ]; + } + + public function toJson(): string + { + $jsonResult = json_encode($this->toArray(), JSON_PRETTY_PRINT | JSON_NUMERIC_CHECK); + + if (JSON_ERROR_NONE !== json_last_error()) { + $jsonResult = json_encode( + [ + 'errorDescription' => 'Cannot encode Tokens to JSON.', + 'rawErrorMessage' => json_last_error_msg(), + ], + JSON_PRETTY_PRINT | JSON_NUMERIC_CHECK + ); + } + + return $jsonResult; + } + + /** + * @param list $tokenNames + * + * @return array + */ + private static function getTokenKindsForNames(array $tokenNames): array + { + $keywords = []; + foreach ($tokenNames as $keywordName) { + if (\defined($keywordName)) { + $keyword = \constant($keywordName); + $keywords[$keyword] = $keyword; + } + } + + return $keywords; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Tokens.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Tokens.php new file mode 100644 index 00000000..a4f9b3a2 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Tokens.php @@ -0,0 +1,1479 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer; + +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\NamespacesAnalyzer; + +/** + * Collection of code tokens. + * + * Its role is to provide the ability to manage collection and navigate through it. + * + * As a token prototype you should understand a single element generated by token_get_all. + * + * @author Dariusz Rumiński + * + * @extends \SplFixedArray + * + * @method Token offsetGet($offset) + * + * @final + */ +class Tokens extends \SplFixedArray +{ + public const BLOCK_TYPE_PARENTHESIS_BRACE = 1; + public const BLOCK_TYPE_CURLY_BRACE = 2; + public const BLOCK_TYPE_INDEX_SQUARE_BRACE = 3; + public const BLOCK_TYPE_ARRAY_SQUARE_BRACE = 4; + public const BLOCK_TYPE_DYNAMIC_PROP_BRACE = 5; + public const BLOCK_TYPE_DYNAMIC_VAR_BRACE = 6; + public const BLOCK_TYPE_ARRAY_INDEX_CURLY_BRACE = 7; + public const BLOCK_TYPE_GROUP_IMPORT_BRACE = 8; + public const BLOCK_TYPE_DESTRUCTURING_SQUARE_BRACE = 9; + public const BLOCK_TYPE_BRACE_CLASS_INSTANTIATION = 10; + public const BLOCK_TYPE_ATTRIBUTE = 11; + public const BLOCK_TYPE_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS = 12; + public const BLOCK_TYPE_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE = 13; + + /** + * Static class cache. + * + * @var array + */ + private static array $cache = []; + + /** + * Cache of block starts. Any change in collection will invalidate it. + * + * @var array + */ + private array $blockStartCache = []; + + /** + * Cache of block ends. Any change in collection will invalidate it. + * + * @var array + */ + private array $blockEndCache = []; + + /** + * A MD5 hash of the code string. + * + * @var ?non-empty-string + */ + private ?string $codeHash = null; + + /** + * Flag is collection was changed. + * + * It doesn't know about change of collection's items. To check it run `isChanged` method. + */ + private bool $changed = false; + + /** + * Set of found token kinds. + * + * When the token kind is present in this set it means that given token kind + * was ever seen inside the collection (but may not be part of it any longer). + * The key is token kind and the value is always true. + * + * @var array + */ + private array $foundTokenKinds = []; + + /** + * @var null|list + */ + private ?array $namespaceDeclarations = null; + + /** + * Clone tokens collection. + */ + public function __clone() + { + foreach ($this as $key => $val) { + $this[$key] = clone $val; + } + } + + /** + * Clear cache - one position or all of them. + * + * @param null|non-empty-string $key position to clear, when null clear all + */ + public static function clearCache(?string $key = null): void + { + if (null === $key) { + self::$cache = []; + + return; + } + + unset(self::$cache[$key]); + } + + /** + * Detect type of block. + * + * @return null|array{type: self::BLOCK_TYPE_*, isStart: bool} + */ + public static function detectBlockType(Token $token): ?array + { + static $blockEdgeKinds = null; + + if (null === $blockEdgeKinds) { + $blockEdgeKinds = []; + foreach (self::getBlockEdgeDefinitions() as $type => $definition) { + $blockEdgeKinds[ + \is_string($definition['start']) ? $definition['start'] : $definition['start'][0] + ] = ['type' => $type, 'isStart' => true]; + $blockEdgeKinds[ + \is_string($definition['end']) ? $definition['end'] : $definition['end'][0] + ] = ['type' => $type, 'isStart' => false]; + } + } + + // inlined extractTokenKind() call on the hot path + /** @var int|non-empty-string */ + $tokenKind = $token->isArray() ? $token->getId() : $token->getContent(); + + return $blockEdgeKinds[$tokenKind] ?? null; + } + + /** + * Create token collection from array. + * + * @param array $array the array to import + * @param ?bool $saveIndices save the numeric indices used in the original array, default is yes + */ + public static function fromArray($array, $saveIndices = null): self + { + $tokens = new self(\count($array)); + + if ($saveIndices ?? true) { + foreach ($array as $key => $val) { + $tokens[$key] = $val; + } + } else { + $index = 0; + + foreach ($array as $val) { + $tokens[$index++] = $val; + } + } + + $tokens->generateCode(); // regenerate code to calculate code hash + $tokens->clearChanged(); + + return $tokens; + } + + /** + * Create token collection directly from code. + * + * @param string $code PHP code + */ + public static function fromCode(string $code): self + { + $codeHash = self::calculateCodeHash($code); + + if (self::hasCache($codeHash)) { + $tokens = self::getCache($codeHash); + + // generate the code to recalculate the hash + $tokens->generateCode(); + + if ($codeHash === $tokens->codeHash) { + $tokens->clearEmptyTokens(); + $tokens->clearChanged(); + + return $tokens; + } + } + + $tokens = new self(); + $tokens->setCode($code); + $tokens->clearChanged(); + + return $tokens; + } + + /** + * @return array + */ + public static function getBlockEdgeDefinitions(): array + { + static $definitions = null; + if (null === $definitions) { + $definitions = [ + self::BLOCK_TYPE_CURLY_BRACE => [ + 'start' => '{', + 'end' => '}', + ], + self::BLOCK_TYPE_PARENTHESIS_BRACE => [ + 'start' => '(', + 'end' => ')', + ], + self::BLOCK_TYPE_INDEX_SQUARE_BRACE => [ + 'start' => '[', + 'end' => ']', + ], + self::BLOCK_TYPE_ARRAY_SQUARE_BRACE => [ + 'start' => [CT::T_ARRAY_SQUARE_BRACE_OPEN, '['], + 'end' => [CT::T_ARRAY_SQUARE_BRACE_CLOSE, ']'], + ], + self::BLOCK_TYPE_DYNAMIC_PROP_BRACE => [ + 'start' => [CT::T_DYNAMIC_PROP_BRACE_OPEN, '{'], + 'end' => [CT::T_DYNAMIC_PROP_BRACE_CLOSE, '}'], + ], + self::BLOCK_TYPE_DYNAMIC_VAR_BRACE => [ + 'start' => [CT::T_DYNAMIC_VAR_BRACE_OPEN, '{'], + 'end' => [CT::T_DYNAMIC_VAR_BRACE_CLOSE, '}'], + ], + self::BLOCK_TYPE_ARRAY_INDEX_CURLY_BRACE => [ + 'start' => [CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN, '{'], + 'end' => [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE, '}'], + ], + self::BLOCK_TYPE_GROUP_IMPORT_BRACE => [ + 'start' => [CT::T_GROUP_IMPORT_BRACE_OPEN, '{'], + 'end' => [CT::T_GROUP_IMPORT_BRACE_CLOSE, '}'], + ], + self::BLOCK_TYPE_DESTRUCTURING_SQUARE_BRACE => [ + 'start' => [CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, '['], + 'end' => [CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, ']'], + ], + self::BLOCK_TYPE_BRACE_CLASS_INSTANTIATION => [ + 'start' => [CT::T_BRACE_CLASS_INSTANTIATION_OPEN, '('], + 'end' => [CT::T_BRACE_CLASS_INSTANTIATION_CLOSE, ')'], + ], + self::BLOCK_TYPE_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS => [ + 'start' => [CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_OPEN, '('], + 'end' => [CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE, ')'], + ], + self::BLOCK_TYPE_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE => [ + 'start' => [CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN, '{'], + 'end' => [CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE, '}'], + ], + ]; + + // @TODO: drop condition when PHP 8.0+ is required + if (\defined('T_ATTRIBUTE')) { + $definitions[self::BLOCK_TYPE_ATTRIBUTE] = [ + 'start' => [T_ATTRIBUTE, '#['], + 'end' => [CT::T_ATTRIBUTE_CLOSE, ']'], + ]; + } + } + + return $definitions; + } + + /** + * Set new size of collection. + * + * @param int $size + */ + public function setSize($size): bool + { + if ($this->getSize() !== $size) { + $this->changed = true; + $this->namespaceDeclarations = null; + + return parent::setSize($size); + } + + return true; + } + + /** + * Unset collection item. + * + * @param int $index + */ + public function offsetUnset($index): void + { + if (isset($this[$index])) { + if (isset($this->blockStartCache[$index])) { + unset($this->blockEndCache[$this->blockStartCache[$index]], $this->blockStartCache[$index]); + } + if (isset($this->blockEndCache[$index])) { + unset($this->blockStartCache[$this->blockEndCache[$index]], $this->blockEndCache[$index]); + } + + $this->unregisterFoundToken($this[$index]); + + $this->changed = true; + $this->namespaceDeclarations = null; + } + + parent::offsetUnset($index); + } + + /** + * Set collection item. + * + * Warning! `$newval` must not be typehinted to be compatible with `ArrayAccess::offsetSet` method. + * + * @param int $index + * @param Token $newval + */ + public function offsetSet($index, $newval): void + { + if (!isset($this[$index]) || !$this[$index]->equals($newval)) { + if (isset($this[$index])) { + if (isset($this->blockStartCache[$index])) { + unset($this->blockEndCache[$this->blockStartCache[$index]], $this->blockStartCache[$index]); + } + if (isset($this->blockEndCache[$index])) { + unset($this->blockStartCache[$this->blockEndCache[$index]], $this->blockEndCache[$index]); + } + + $this->unregisterFoundToken($this[$index]); + } + + $this->changed = true; + $this->namespaceDeclarations = null; + + $this->registerFoundToken($newval); + } + + parent::offsetSet($index, $newval); + } + + /** + * Clear internal flag if collection was changed and flag for all collection's items. + */ + public function clearChanged(): void + { + $this->changed = false; + } + + /** + * Clear empty tokens. + * + * Empty tokens can occur e.g. after calling clear on item of collection. + */ + public function clearEmptyTokens(): void + { + $limit = $this->count(); + + for ($index = 0; $index < $limit; ++$index) { + if ($this->isEmptyAt($index)) { + break; + } + } + + // no empty token found, therefore there is no need to override collection + if ($limit === $index) { + return; + } + + for ($count = $index; $index < $limit; ++$index) { + if (!$this->isEmptyAt($index)) { + // use directly for speed, skip the register of token kinds found etc. + parent::offsetSet($count++, $this[$index]); + } + } + + // should already be true + if (!$this->changed) { + // must never happen + throw new \LogicException('Unexpected non-changed collection with _EMPTY_ Tokens. Fix the code!'); + } + + // we are moving the tokens, we need to clear the index-based Cache + $this->namespaceDeclarations = null; + $this->blockStartCache = []; + $this->blockEndCache = []; + + $this->setSize($count); + } + + /** + * Ensure that on given index is a whitespace with given kind. + * + * If there is a whitespace then it's content will be modified. + * If not - the new Token will be added. + * + * @param int $index index + * @param int $indexOffset index offset for Token insertion + * @param string $whitespace whitespace to set + * + * @return bool if new Token was added + */ + public function ensureWhitespaceAtIndex(int $index, int $indexOffset, string $whitespace): bool + { + $removeLastCommentLine = static function (self $tokens, int $index, int $indexOffset, string $whitespace): string { + $token = $tokens[$index]; + + if (1 === $indexOffset && $token->isGivenKind(T_OPEN_TAG)) { + if (str_starts_with($whitespace, "\r\n")) { + $tokens[$index] = new Token([T_OPEN_TAG, rtrim($token->getContent())."\r\n"]); + + return \strlen($whitespace) > 2 // @TODO: can be removed on PHP 8; https://php.net/manual/en/function.substr.php + ? substr($whitespace, 2) + : ''; + } + + $tokens[$index] = new Token([T_OPEN_TAG, rtrim($token->getContent()).$whitespace[0]]); + + return \strlen($whitespace) > 1 // @TODO: can be removed on PHP 8; https://php.net/manual/en/function.substr.php + ? substr($whitespace, 1) + : ''; + } + + return $whitespace; + }; + + if ($this[$index]->isWhitespace()) { + $whitespace = $removeLastCommentLine($this, $index - 1, $indexOffset, $whitespace); + + if ('' === $whitespace) { + $this->clearAt($index); + } else { + $this[$index] = new Token([T_WHITESPACE, $whitespace]); + } + + return false; + } + + $whitespace = $removeLastCommentLine($this, $index, $indexOffset, $whitespace); + + if ('' === $whitespace) { + return false; + } + + $this->insertAt( + $index + $indexOffset, + [new Token([T_WHITESPACE, $whitespace])] + ); + + return true; + } + + /** + * @param self::BLOCK_TYPE_* $type type of block + * @param int $searchIndex index of opening brace + * + * @return int index of closing brace + */ + public function findBlockEnd(int $type, int $searchIndex): int + { + return $this->findOppositeBlockEdge($type, $searchIndex, true); + } + + /** + * @param self::BLOCK_TYPE_* $type type of block + * @param int $searchIndex index of closing brace + * + * @return int index of opening brace + */ + public function findBlockStart(int $type, int $searchIndex): int + { + return $this->findOppositeBlockEdge($type, $searchIndex, false); + } + + /** + * @param int|non-empty-list $possibleKind kind or array of kinds + * @param int $start optional offset + * @param null|int $end optional limit + * + * @return ($possibleKind is int ? array : array>) + */ + public function findGivenKind($possibleKind, int $start = 0, ?int $end = null): array + { + if (null === $end) { + $end = $this->count(); + } + + $elements = []; + $possibleKinds = (array) $possibleKind; + + foreach ($possibleKinds as $kind) { + $elements[$kind] = []; + } + + $possibleKinds = array_filter($possibleKinds, fn ($kind): bool => $this->isTokenKindFound($kind)); + + if (\count($possibleKinds) > 0) { + for ($i = $start; $i < $end; ++$i) { + $token = $this[$i]; + if ($token->isGivenKind($possibleKinds)) { + $elements[$token->getId()][$i] = $token; + } + } + } + + return \is_array($possibleKind) ? $elements : $elements[$possibleKind]; + } + + public function generateCode(): string + { + $code = $this->generatePartialCode(0, \count($this) - 1); + $this->changeCodeHash(self::calculateCodeHash($code)); + + return $code; + } + + /** + * Generate code from tokens between given indices. + * + * @param int $start start index + * @param int $end end index + */ + public function generatePartialCode(int $start, int $end): string + { + $code = ''; + + for ($i = $start; $i <= $end; ++$i) { + $code .= $this[$i]->getContent(); + } + + return $code; + } + + /** + * Get hash of code. + */ + public function getCodeHash(): string + { + return $this->codeHash; + } + + /** + * Get index for closest next token which is non whitespace. + * + * This method is shorthand for getNonWhitespaceSibling method. + * + * @param int $index token index + * @param null|string $whitespaces whitespaces characters for Token::isWhitespace + */ + public function getNextNonWhitespace(int $index, ?string $whitespaces = null): ?int + { + return $this->getNonWhitespaceSibling($index, 1, $whitespaces); + } + + /** + * Get index for closest next token of given kind. + * + * This method is shorthand for getTokenOfKindSibling method. + * + * @param int $index token index + * @param list $tokens possible tokens + * @param bool $caseSensitive perform a case sensitive comparison + */ + public function getNextTokenOfKind(int $index, array $tokens = [], bool $caseSensitive = true): ?int + { + return $this->getTokenOfKindSibling($index, 1, $tokens, $caseSensitive); + } + + /** + * Get index for closest sibling token which is non whitespace. + * + * @param int $index token index + * @param -1|1 $direction + * @param null|string $whitespaces whitespaces characters for Token::isWhitespace + */ + public function getNonWhitespaceSibling(int $index, int $direction, ?string $whitespaces = null): ?int + { + while (true) { + $index += $direction; + + if (!$this->offsetExists($index)) { + return null; + } + + if (!$this[$index]->isWhitespace($whitespaces)) { + return $index; + } + } + } + + /** + * Get index for closest previous token which is non whitespace. + * + * This method is shorthand for getNonWhitespaceSibling method. + * + * @param int $index token index + * @param null|string $whitespaces whitespaces characters for Token::isWhitespace + */ + public function getPrevNonWhitespace(int $index, ?string $whitespaces = null): ?int + { + return $this->getNonWhitespaceSibling($index, -1, $whitespaces); + } + + /** + * Get index for closest previous token of given kind. + * This method is shorthand for getTokenOfKindSibling method. + * + * @param int $index token index + * @param list $tokens possible tokens + * @param bool $caseSensitive perform a case sensitive comparison + */ + public function getPrevTokenOfKind(int $index, array $tokens = [], bool $caseSensitive = true): ?int + { + return $this->getTokenOfKindSibling($index, -1, $tokens, $caseSensitive); + } + + /** + * Get index for closest sibling token of given kind. + * + * @param int $index token index + * @param -1|1 $direction + * @param list $tokens possible tokens + * @param bool $caseSensitive perform a case sensitive comparison + */ + public function getTokenOfKindSibling(int $index, int $direction, array $tokens = [], bool $caseSensitive = true): ?int + { + $tokens = array_filter($tokens, fn ($token): bool => $this->isTokenKindFound($this->extractTokenKind($token))); + + if (0 === \count($tokens)) { + return null; + } + + while (true) { + $index += $direction; + + if (!$this->offsetExists($index)) { + return null; + } + + if ($this[$index]->equalsAny($tokens, $caseSensitive)) { + return $index; + } + } + } + + /** + * Get index for closest sibling token not of given kind. + * + * @param int $index token index + * @param -1|1 $direction + * @param list $tokens possible tokens + */ + public function getTokenNotOfKindSibling(int $index, int $direction, array $tokens = []): ?int + { + return $this->getTokenNotOfKind( + $index, + $direction, + fn (int $a): bool => $this[$a]->equalsAny($tokens), + ); + } + + /** + * Get index for closest sibling token not of given kind. + * + * @param int $index token index + * @param -1|1 $direction + * @param list $kinds possible tokens kinds + */ + public function getTokenNotOfKindsSibling(int $index, int $direction, array $kinds = []): ?int + { + return $this->getTokenNotOfKind( + $index, + $direction, + fn (int $index): bool => $this[$index]->isGivenKind($kinds), + ); + } + + /** + * Get index for closest sibling token that is not a whitespace, comment or attribute. + * + * @param int $index token index + * @param -1|1 $direction + */ + public function getMeaningfulTokenSibling(int $index, int $direction): ?int + { + return $this->getTokenNotOfKindsSibling( + $index, + $direction, + [T_WHITESPACE, T_COMMENT, T_DOC_COMMENT] + ); + } + + /** + * Get index for closest sibling token which is not empty. + * + * @param int $index token index + * @param -1|1 $direction + */ + public function getNonEmptySibling(int $index, int $direction): ?int + { + while (true) { + $index += $direction; + + if (!$this->offsetExists($index)) { + return null; + } + + if (!$this->isEmptyAt($index)) { + return $index; + } + } + } + + /** + * Get index for closest next token that is not a whitespace or comment. + * + * @param int $index token index + */ + public function getNextMeaningfulToken(int $index): ?int + { + return $this->getMeaningfulTokenSibling($index, 1); + } + + /** + * Get index for closest previous token that is not a whitespace or comment. + * + * @param int $index token index + */ + public function getPrevMeaningfulToken(int $index): ?int + { + return $this->getMeaningfulTokenSibling($index, -1); + } + + /** + * Find a sequence of meaningful tokens and returns the array of their locations. + * + * @param non-empty-list $sequence an array of token (kinds) + * @param int $start start index, defaulting to the start of the file + * @param null|int $end end index, defaulting to the end of the file + * @param array|bool $caseSensitive global case sensitiveness or a list of booleans, whose keys should match + * the ones used in $sequence. If any is missing, the default case-sensitive + * comparison is used + * + * @return null|non-empty-array an array containing the tokens matching the sequence elements, indexed by their position + */ + public function findSequence(array $sequence, int $start = 0, ?int $end = null, $caseSensitive = true): ?array + { + $sequenceCount = \count($sequence); + if (0 === $sequenceCount) { + throw new \InvalidArgumentException('Invalid sequence.'); + } + + // $end defaults to the end of the collection + $end = null === $end ? \count($this) - 1 : min($end, \count($this) - 1); + + if ($start + $sequenceCount - 1 > $end) { + return null; + } + + $nonMeaningFullKind = [T_COMMENT, T_DOC_COMMENT, T_WHITESPACE]; + + // make sure the sequence content is "meaningful" + foreach ($sequence as $key => $token) { + // if not a Token instance already, we convert it to verify the meaningfulness + if (!$token instanceof Token) { + if (\is_array($token) && !isset($token[1])) { + // fake some content as it is required by the Token constructor, + // although optional for search purposes + $token[1] = 'DUMMY'; + } + + $token = new Token($token); + } + + if ($token->isGivenKind($nonMeaningFullKind)) { + throw new \InvalidArgumentException(sprintf('Non-meaningful token at position: "%s".', $key)); + } + + if ('' === $token->getContent()) { + throw new \InvalidArgumentException(sprintf('Non-meaningful (empty) token at position: "%s".', $key)); + } + } + + foreach ($sequence as $token) { + if (!$this->isTokenKindFound($this->extractTokenKind($token))) { + return null; + } + } + + // remove the first token from the sequence, so we can freely iterate through the sequence after a match to + // the first one is found + $firstKey = array_key_first($sequence); + $firstCs = self::isKeyCaseSensitive($caseSensitive, $firstKey); + $firstToken = $sequence[$firstKey]; + unset($sequence[$firstKey]); + + // begin searching for the first token in the sequence (start included) + $index = $start - 1; + while ($index <= $end) { + $index = $this->getNextTokenOfKind($index, [$firstToken], $firstCs); + + // ensure we found a match and didn't get past the end index + if (null === $index || $index > $end) { + return null; + } + + // initialise the result array with the current index + $result = [$index => $this[$index]]; + + // advance cursor to the current position + $currIdx = $index; + + // iterate through the remaining tokens in the sequence + foreach ($sequence as $key => $token) { + $currIdx = $this->getNextMeaningfulToken($currIdx); + + // ensure we didn't go too far + if (null === $currIdx || $currIdx > $end) { + return null; + } + + if (!$this[$currIdx]->equals($token, self::isKeyCaseSensitive($caseSensitive, $key))) { + // not a match, restart the outer loop + continue 2; + } + + // append index to the result array + $result[$currIdx] = $this[$currIdx]; + } + + // do we have a complete match? + // hint: $result is bigger than $sequence since the first token has been removed from the latter + if (\count($sequence) < \count($result)) { + return $result; + } + } + + return null; + } + + /** + * Insert instances of Token inside collection. + * + * @param int $index start inserting index + * @param list|Token|Tokens $items instances of Token to insert + */ + public function insertAt(int $index, $items): void + { + $this->insertSlices([$index => $items]); + } + + /** + * Insert a slices or individual Tokens into multiple places in a single run. + * + * This approach is kind-of an experiment - it's proven to improve performance a lot for big files that needs plenty of new tickets to be inserted, + * like edge case example of 3.7h vs 4s (https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues/3996#issuecomment-455617637), + * yet at same time changing a logic of fixers in not-always easy way. + * + * To be discussed: + * - should we always aim to use this method? + * - should we deprecate `insertAt` method ? + * + * The `$slices` parameter is an assoc array, in which: + * - index: starting point for inserting of individual slice, with indices being relatives to original array collection before any Token inserted + * - value under index: a slice of Tokens to be inserted + * + * @internal + * + * @param array|Token|Tokens> $slices + */ + public function insertSlices(array $slices): void + { + $itemsCount = 0; + + foreach ($slices as $slice) { + $itemsCount += \is_array($slice) || $slice instanceof self ? \count($slice) : 1; + } + + if (0 === $itemsCount) { + return; + } + + $oldSize = \count($this); + $this->changed = true; + $this->namespaceDeclarations = null; + $this->blockStartCache = []; + $this->blockEndCache = []; + $this->setSize($oldSize + $itemsCount); + + krsort($slices); + $farthestSliceIndex = array_key_first($slices); + + // We check only the farthest index, if it's within the size of collection, other indices will be valid too. + if (!\is_int($farthestSliceIndex) || $farthestSliceIndex > $oldSize) { + throw new \OutOfBoundsException(sprintf('Cannot insert index "%s" outside of collection.', $farthestSliceIndex)); + } + + $previousSliceIndex = $oldSize; + + // since we only move already existing items around, we directly call into SplFixedArray::offset* methods. + // that way we get around additional overhead this class adds with overridden offset* methods. + foreach ($slices as $index => $slice) { + if (!\is_int($index) || $index < 0) { + throw new \OutOfBoundsException(sprintf('Invalid index "%s".', $index)); + } + + $slice = \is_array($slice) || $slice instanceof self ? $slice : [$slice]; + $sliceCount = \count($slice); + + for ($i = $previousSliceIndex - 1; $i >= $index; --$i) { + parent::offsetSet($i + $itemsCount, parent::offsetGet($i)); + } + + $previousSliceIndex = $index; + $itemsCount -= $sliceCount; + + foreach ($slice as $indexItem => $item) { + if ('' === $item->getContent()) { + throw new \InvalidArgumentException('Must not add empty token to collection.'); + } + + $this->registerFoundToken($item); + + parent::offsetSet($index + $itemsCount + $indexItem, $item); + } + } + } + + /** + * Check if collection was change: collection itself (like insert new tokens) or any of collection's elements. + */ + public function isChanged(): bool + { + return $this->changed; + } + + public function isEmptyAt(int $index): bool + { + $token = $this[$index]; + + return null === $token->getId() && '' === $token->getContent(); + } + + public function clearAt(int $index): void + { + $this[$index] = new Token(''); + } + + /** + * Override tokens at given range. + * + * @param int $indexStart start overriding index + * @param int $indexEnd end overriding index + * @param array|Tokens $items tokens to insert + */ + public function overrideRange(int $indexStart, int $indexEnd, iterable $items): void + { + $indexToChange = $indexEnd - $indexStart + 1; + $itemsCount = \count($items); + + // If we want to add more items than passed range contains we need to + // add placeholders for overhead items. + if ($itemsCount > $indexToChange) { + $placeholders = []; + + while ($itemsCount > $indexToChange) { + $placeholders[] = new Token('__PLACEHOLDER__'); + ++$indexToChange; + } + + $this->insertAt($indexEnd + 1, $placeholders); + } + + // Override each items. + foreach ($items as $itemIndex => $item) { + $this[$indexStart + $itemIndex] = $item; + } + + // If we want to add fewer tokens than passed range contains then clear + // not needed tokens. + if ($itemsCount < $indexToChange) { + $this->clearRange($indexStart + $itemsCount, $indexEnd); + } + } + + /** + * @param null|string $whitespaces optional whitespaces characters for Token::isWhitespace + */ + public function removeLeadingWhitespace(int $index, ?string $whitespaces = null): void + { + $this->removeWhitespaceSafely($index, -1, $whitespaces); + } + + /** + * @param null|string $whitespaces optional whitespaces characters for Token::isWhitespace + */ + public function removeTrailingWhitespace(int $index, ?string $whitespaces = null): void + { + $this->removeWhitespaceSafely($index, 1, $whitespaces); + } + + /** + * Set code. Clear all current content and replace it by new Token items generated from code directly. + * + * @param string $code PHP code + */ + public function setCode(string $code): void + { + // No need to work when the code is the same. + // That is how we avoid a lot of work and setting changed flag. + if ($code === $this->generateCode()) { + return; + } + + // clear memory + $this->setSize(0); + + $tokens = token_get_all($code, TOKEN_PARSE); + + $this->setSize(\count($tokens)); + + foreach ($tokens as $index => $token) { + $this[$index] = new Token($token); + } + + $this->applyTransformers(); + + $this->foundTokenKinds = []; + + foreach ($this as $token) { + $this->registerFoundToken($token); + } + + if (\PHP_VERSION_ID < 8_00_00) { + $this->rewind(); + } + + $this->changeCodeHash(self::calculateCodeHash($code)); + $this->changed = true; + $this->namespaceDeclarations = null; + } + + public function toJson(): string + { + $output = new \SplFixedArray(\count($this)); + + foreach ($this as $index => $token) { + $output[$index] = $token->toArray(); + } + + if (\PHP_VERSION_ID < 8_00_00) { + $this->rewind(); + } + + return json_encode($output, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT | JSON_NUMERIC_CHECK); + } + + /** + * Check if all token kinds given as argument are found. + * + * @param list $tokenKinds + */ + public function isAllTokenKindsFound(array $tokenKinds): bool + { + foreach ($tokenKinds as $tokenKind) { + if (!isset($this->foundTokenKinds[$tokenKind])) { + return false; + } + } + + return true; + } + + /** + * Check if any token kind given as argument is found. + * + * @param list $tokenKinds + */ + public function isAnyTokenKindsFound(array $tokenKinds): bool + { + foreach ($tokenKinds as $tokenKind) { + if (isset($this->foundTokenKinds[$tokenKind])) { + return true; + } + } + + return false; + } + + /** + * Check if token kind given as argument is found. + * + * @param int|string $tokenKind + */ + public function isTokenKindFound($tokenKind): bool + { + return isset($this->foundTokenKinds[$tokenKind]); + } + + /** + * @param int|string $tokenKind + */ + public function countTokenKind($tokenKind): int + { + return $this->foundTokenKinds[$tokenKind] ?? 0; + } + + /** + * Clear tokens in the given range. + */ + public function clearRange(int $indexStart, int $indexEnd): void + { + for ($i = $indexStart; $i <= $indexEnd; ++$i) { + $this->clearAt($i); + } + } + + /** + * Checks for monolithic PHP code. + * + * Checks that the code is pure PHP code, in a single code block, starting + * with an open tag. + */ + public function isMonolithicPhp(): bool + { + if (1 !== ($this->countTokenKind(T_OPEN_TAG) + $this->countTokenKind(T_OPEN_TAG_WITH_ECHO))) { + return false; + } + + return 0 === $this->countTokenKind(T_INLINE_HTML) + || (1 === $this->countTokenKind(T_INLINE_HTML) && Preg::match('/^#!.+$/', $this[0]->getContent())); + } + + /** + * @param int $start start index + * @param int $end end index + */ + public function isPartialCodeMultiline(int $start, int $end): bool + { + for ($i = $start; $i <= $end; ++$i) { + if (str_contains($this[$i]->getContent(), "\n")) { + return true; + } + } + + return false; + } + + public function hasAlternativeSyntax(): bool + { + return $this->isAnyTokenKindsFound([ + T_ENDDECLARE, + T_ENDFOR, + T_ENDFOREACH, + T_ENDIF, + T_ENDSWITCH, + T_ENDWHILE, + ]); + } + + public function clearTokenAndMergeSurroundingWhitespace(int $index): void + { + $count = \count($this); + $this->clearAt($index); + + if ($index === $count - 1) { + return; + } + + $nextIndex = $this->getNonEmptySibling($index, 1); + + if (null === $nextIndex || !$this[$nextIndex]->isWhitespace()) { + return; + } + + $prevIndex = $this->getNonEmptySibling($index, -1); + + if ($this[$prevIndex]->isWhitespace()) { + $this[$prevIndex] = new Token([T_WHITESPACE, $this[$prevIndex]->getContent().$this[$nextIndex]->getContent()]); + } elseif ($this->isEmptyAt($prevIndex + 1)) { + $this[$prevIndex + 1] = new Token([T_WHITESPACE, $this[$nextIndex]->getContent()]); + } + + $this->clearAt($nextIndex); + } + + /** + * @internal This is performance-related workaround for lack of proper DI, may be removed at some point + * + * @return list + */ + public function getNamespaceDeclarations(): array + { + if (null === $this->namespaceDeclarations) { + $this->namespaceDeclarations = (new NamespacesAnalyzer())->getDeclarations($this); + } + + return $this->namespaceDeclarations; + } + + /** + * @internal + */ + protected function applyTransformers(): void + { + $transformers = Transformers::createSingleton(); + $transformers->transform($this); + } + + /** + * @param -1|1 $direction + */ + private function removeWhitespaceSafely(int $index, int $direction, ?string $whitespaces = null): void + { + $whitespaceIndex = $this->getNonEmptySibling($index, $direction); + if (isset($this[$whitespaceIndex]) && $this[$whitespaceIndex]->isWhitespace()) { + $newContent = ''; + $tokenToCheck = $this[$whitespaceIndex]; + + // if the token candidate to remove is preceded by single line comment we do not consider the new line after this comment as part of T_WHITESPACE + if (isset($this[$whitespaceIndex - 1]) && $this[$whitespaceIndex - 1]->isComment() && !str_starts_with($this[$whitespaceIndex - 1]->getContent(), '/*')) { + [, $newContent, $whitespacesToCheck] = Preg::split('/^(\R)/', $this[$whitespaceIndex]->getContent(), -1, PREG_SPLIT_DELIM_CAPTURE); + + if ('' === $whitespacesToCheck) { + return; + } + + $tokenToCheck = new Token([T_WHITESPACE, $whitespacesToCheck]); + } + + if (!$tokenToCheck->isWhitespace($whitespaces)) { + return; + } + + if ('' === $newContent) { + $this->clearAt($whitespaceIndex); + } else { + $this[$whitespaceIndex] = new Token([T_WHITESPACE, $newContent]); + } + } + } + + /** + * @param self::BLOCK_TYPE_* $type type of block + * @param int $searchIndex index of starting brace + * @param bool $findEnd if method should find block's end or start + * + * @return int index of opposite brace + */ + private function findOppositeBlockEdge(int $type, int $searchIndex, bool $findEnd): int + { + $blockEdgeDefinitions = self::getBlockEdgeDefinitions(); + + if (!isset($blockEdgeDefinitions[$type])) { + throw new \InvalidArgumentException(sprintf('Invalid param type: "%s".', $type)); + } + + if ($findEnd && isset($this->blockStartCache[$searchIndex])) { + return $this->blockStartCache[$searchIndex]; + } + + if (!$findEnd && isset($this->blockEndCache[$searchIndex])) { + return $this->blockEndCache[$searchIndex]; + } + + $startEdge = $blockEdgeDefinitions[$type]['start']; + $endEdge = $blockEdgeDefinitions[$type]['end']; + $startIndex = $searchIndex; + $endIndex = $this->count() - 1; + $indexOffset = 1; + + if (!$findEnd) { + [$startEdge, $endEdge] = [$endEdge, $startEdge]; + $indexOffset = -1; + $endIndex = 0; + } + + if (!$this[$startIndex]->equals($startEdge)) { + throw new \InvalidArgumentException(sprintf('Invalid param $startIndex - not a proper block "%s".', $findEnd ? 'start' : 'end')); + } + + $blockLevel = 0; + + for ($index = $startIndex; $index !== $endIndex; $index += $indexOffset) { + $token = $this[$index]; + + if ($token->equals($startEdge)) { + ++$blockLevel; + + continue; + } + + if ($token->equals($endEdge)) { + --$blockLevel; + + if (0 === $blockLevel) { + break; + } + } + } + + if (!$this[$index]->equals($endEdge)) { + throw new \UnexpectedValueException(sprintf('Missing block "%s".', $findEnd ? 'end' : 'start')); + } + + if ($startIndex < $index) { + $this->blockStartCache[$startIndex] = $index; + $this->blockEndCache[$index] = $startIndex; + } else { + $this->blockStartCache[$index] = $startIndex; + $this->blockEndCache[$startIndex] = $index; + } + + return $index; + } + + /** + * Calculate hash for code. + * + * @return non-empty-string + */ + private static function calculateCodeHash(string $code): string + { + return CodeHasher::calculateCodeHash($code); + } + + /** + * Get cache value for given key. + * + * @param non-empty-string $key item key + */ + private static function getCache(string $key): self + { + if (!self::hasCache($key)) { + throw new \OutOfBoundsException(sprintf('Unknown cache key: "%s".', $key)); + } + + return self::$cache[$key]; + } + + /** + * Check if given key exists in cache. + * + * @param non-empty-string $key item key + */ + private static function hasCache(string $key): bool + { + return isset(self::$cache[$key]); + } + + /** + * @param non-empty-string $key item key + * @param Tokens $value item value + */ + private static function setCache(string $key, self $value): void + { + self::$cache[$key] = $value; + } + + /** + * Change code hash. + * + * Remove old cache and set new one. + * + * @param non-empty-string $codeHash new code hash + */ + private function changeCodeHash(string $codeHash): void + { + if (null !== $this->codeHash) { + self::clearCache($this->codeHash); + } + + $this->codeHash = $codeHash; + self::setCache($this->codeHash, $this); + } + + /** + * Register token as found. + * + * @param array{int}|string|Token $token token prototype + */ + private function registerFoundToken($token): void + { + // inlined extractTokenKind() call on the hot path + /** @var int|non-empty-string */ + $tokenKind = $token instanceof Token + ? ($token->isArray() ? $token->getId() : $token->getContent()) + : (\is_array($token) ? $token[0] : $token); + + $this->foundTokenKinds[$tokenKind] ??= 0; + ++$this->foundTokenKinds[$tokenKind]; + } + + /** + * Unregister token as not found. + * + * @param array{int}|string|Token $token token prototype + */ + private function unregisterFoundToken($token): void + { + // inlined extractTokenKind() call on the hot path + /** @var int|non-empty-string */ + $tokenKind = $token instanceof Token + ? ($token->isArray() ? $token->getId() : $token->getContent()) + : (\is_array($token) ? $token[0] : $token); + + if (1 === $this->foundTokenKinds[$tokenKind]) { + unset($this->foundTokenKinds[$tokenKind]); + } else { + --$this->foundTokenKinds[$tokenKind]; + } + } + + /** + * @param array{int}|string|Token $token token prototype + * + * @return int|non-empty-string + */ + private function extractTokenKind($token) + { + return $token instanceof Token + ? ($token->isArray() ? $token->getId() : $token->getContent()) + : (\is_array($token) ? $token[0] : $token); + } + + /** + * @param int $index token index + * @param -1|1 $direction + * @param callable(int): bool $filter + */ + private function getTokenNotOfKind(int $index, int $direction, callable $filter): ?int + { + while (true) { + $index += $direction; + + if (!$this->offsetExists($index)) { + return null; + } + + if ($this->isEmptyAt($index) || $filter($index)) { + continue; + } + + return $index; + } + } + + /** + * A helper method used to find out whether a certain input token has to be case-sensitively matched. + * + * @param array|bool $caseSensitive global case sensitiveness or an array of booleans, whose keys should match + * the ones used in $sequence. If any is missing, the default case-sensitive + * comparison is used + * @param int $key the key of the token that has to be looked up + */ + private static function isKeyCaseSensitive($caseSensitive, int $key): bool + { + if (\is_array($caseSensitive)) { + return $caseSensitive[$key] ?? true; + } + + return $caseSensitive; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/TokensAnalyzer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/TokensAnalyzer.php new file mode 100644 index 00000000..2a129341 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/TokensAnalyzer.php @@ -0,0 +1,862 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer; + +use PhpCsFixer\Tokenizer\Analyzer\AttributeAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\GotoLabelAnalyzer; + +/** + * Analyzer of Tokens collection. + * + * Its role is to provide the ability to analyze collection. + * + * @author Dariusz Rumiński + * @author Gregor Harlan + * + * @internal + * + * @phpstan-type _ClassyElementType 'case'|'const'|'method'|'property'|'trait_import' + */ +final class TokensAnalyzer +{ + /** + * Tokens collection instance. + */ + private Tokens $tokens; + + private ?GotoLabelAnalyzer $gotoLabelAnalyzer = null; + + public function __construct(Tokens $tokens) + { + $this->tokens = $tokens; + } + + /** + * Get indices of methods and properties in classy code (classes, interfaces and traits). + * + * @return array + */ + public function getClassyElements(): array + { + $elements = []; + + for ($index = 1, $count = \count($this->tokens) - 2; $index < $count; ++$index) { + if ($this->tokens[$index]->isClassy()) { + [$index, $newElements] = $this->findClassyElements($index, $index); + $elements += $newElements; + } + } + + ksort($elements); + + return $elements; + } + + /** + * Get indices of modifiers of a classy code (classes, interfaces and traits). + * + * @return array{ + * final: int|null, + * abstract: int|null, + * readonly: int|null + * } + */ + public function getClassyModifiers(int $index): array + { + if (!$this->tokens[$index]->isClassy()) { + throw new \InvalidArgumentException(sprintf('Not an "classy" at given index %d.', $index)); + } + + $readOnlyPossible = \defined('T_READONLY'); // @TODO: drop condition when PHP 8.2+ is required + $modifiers = ['final' => null, 'abstract' => null, 'readonly' => null]; + + while (true) { + $index = $this->tokens->getPrevMeaningfulToken($index); + + if ($this->tokens[$index]->isGivenKind(T_FINAL)) { + $modifiers['final'] = $index; + } elseif ($this->tokens[$index]->isGivenKind(T_ABSTRACT)) { + $modifiers['abstract'] = $index; + } elseif ($readOnlyPossible && $this->tokens[$index]->isGivenKind(T_READONLY)) { + $modifiers['readonly'] = $index; + } else { // no need to skip attributes as it is not possible on PHP8.2 + break; + } + } + + return $modifiers; + } + + /** + * Get indices of namespace uses. + * + * @param bool $perNamespace Return namespace uses per namespace + * + * @return ($perNamespace is true ? array> : list) + */ + public function getImportUseIndexes(bool $perNamespace = false): array + { + $tokens = $this->tokens; + + $uses = []; + $namespaceIndex = 0; + + for ($index = 0, $limit = $tokens->count(); $index < $limit; ++$index) { + $token = $tokens[$index]; + + if ($token->isGivenKind(T_NAMESPACE)) { + $nextTokenIndex = $tokens->getNextTokenOfKind($index, [';', '{']); + $nextToken = $tokens[$nextTokenIndex]; + + if ($nextToken->equals('{')) { + $index = $nextTokenIndex; + } + + if ($perNamespace) { + ++$namespaceIndex; + } + + continue; + } + + if ($token->isGivenKind(T_USE)) { + $uses[$namespaceIndex][] = $index; + } + } + + if (!$perNamespace && isset($uses[$namespaceIndex])) { + return $uses[$namespaceIndex]; + } + + return $uses; + } + + /** + * Check if there is an array at given index. + */ + public function isArray(int $index): bool + { + return $this->tokens[$index]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN]); + } + + /** + * Check if the array at index is multiline. + * + * This only checks the root-level of the array. + */ + public function isArrayMultiLine(int $index): bool + { + if (!$this->isArray($index)) { + throw new \InvalidArgumentException(sprintf('Not an array at given index %d.', $index)); + } + + $tokens = $this->tokens; + + // Skip only when it's an array, for short arrays we need the brace for correct + // level counting + if ($tokens[$index]->isGivenKind(T_ARRAY)) { + $index = $tokens->getNextMeaningfulToken($index); + } + + return $this->isBlockMultiline($tokens, $index); + } + + public function isBlockMultiline(Tokens $tokens, int $index): bool + { + $blockType = Tokens::detectBlockType($tokens[$index]); + + if (null === $blockType || !$blockType['isStart']) { + throw new \InvalidArgumentException(sprintf('Not an block start at given index %d.', $index)); + } + + $endIndex = $tokens->findBlockEnd($blockType['type'], $index); + + for (++$index; $index < $endIndex; ++$index) { + $token = $tokens[$index]; + $blockType = Tokens::detectBlockType($token); + + if (null !== $blockType && $blockType['isStart']) { + $index = $tokens->findBlockEnd($blockType['type'], $index); + + continue; + } + + if ( + $token->isWhitespace() + && !$tokens[$index - 1]->isGivenKind(T_END_HEREDOC) + && str_contains($token->getContent(), "\n") + ) { + return true; + } + } + + return false; + } + + /** + * @param int $index Index of the T_FUNCTION token + * + * @return array{visibility: null|T_PRIVATE|T_PROTECTED|T_PUBLIC, static: bool, abstract: bool, final: bool} + */ + public function getMethodAttributes(int $index): array + { + if (!$this->tokens[$index]->isGivenKind(T_FUNCTION)) { + throw new \LogicException(sprintf('No T_FUNCTION at given index %d, got "%s".', $index, $this->tokens[$index]->getName())); + } + + $attributes = [ + 'visibility' => null, + 'static' => false, + 'abstract' => false, + 'final' => false, + ]; + + for ($i = $index; $i >= 0; --$i) { + $i = $this->tokens->getPrevMeaningfulToken($i); + $token = $this->tokens[$i]; + + if ($token->isGivenKind(T_STATIC)) { + $attributes['static'] = true; + + continue; + } + + if ($token->isGivenKind(T_FINAL)) { + $attributes['final'] = true; + + continue; + } + + if ($token->isGivenKind(T_ABSTRACT)) { + $attributes['abstract'] = true; + + continue; + } + + // visibility + + if ($token->isGivenKind(T_PRIVATE)) { + $attributes['visibility'] = T_PRIVATE; + + continue; + } + + if ($token->isGivenKind(T_PROTECTED)) { + $attributes['visibility'] = T_PROTECTED; + + continue; + } + + if ($token->isGivenKind(T_PUBLIC)) { + $attributes['visibility'] = T_PUBLIC; + + continue; + } + + // found a meaningful token that is not part of + // the function signature; stop looking + break; + } + + return $attributes; + } + + /** + * Check if there is an anonymous class under given index. + */ + public function isAnonymousClass(int $index): bool + { + if (!$this->tokens[$index]->isClassy()) { + throw new \LogicException(sprintf('No classy token at given index %d.', $index)); + } + + if (!$this->tokens[$index]->isGivenKind(T_CLASS)) { + return false; + } + + $index = $this->tokens->getPrevMeaningfulToken($index); + + if (\defined('T_READONLY') && $this->tokens[$index]->isGivenKind(T_READONLY)) { // @TODO: drop condition when PHP 8.1+ is required + $index = $this->tokens->getPrevMeaningfulToken($index); + } + + while ($this->tokens[$index]->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) { + $index = $this->tokens->findBlockStart(Tokens::BLOCK_TYPE_ATTRIBUTE, $index); + $index = $this->tokens->getPrevMeaningfulToken($index); + } + + return $this->tokens[$index]->isGivenKind(T_NEW); + } + + /** + * Check if the function under given index is a lambda. + */ + public function isLambda(int $index): bool + { + if (!$this->tokens[$index]->isGivenKind([T_FUNCTION, T_FN])) { + throw new \LogicException(sprintf('No T_FUNCTION or T_FN at given index %d, got "%s".', $index, $this->tokens[$index]->getName())); + } + + $startParenthesisIndex = $this->tokens->getNextMeaningfulToken($index); + $startParenthesisToken = $this->tokens[$startParenthesisIndex]; + + // skip & for `function & () {}` syntax + if ($startParenthesisToken->isGivenKind(CT::T_RETURN_REF)) { + $startParenthesisIndex = $this->tokens->getNextMeaningfulToken($startParenthesisIndex); + $startParenthesisToken = $this->tokens[$startParenthesisIndex]; + } + + return $startParenthesisToken->equals('('); + } + + public function getLastTokenIndexOfArrowFunction(int $index): int + { + if (!$this->tokens[$index]->isGivenKind(T_FN)) { + throw new \InvalidArgumentException(sprintf('Not an "arrow function" at given index %d.', $index)); + } + + $stopTokens = [')', ']', ',', ';', [T_CLOSE_TAG]]; + $index = $this->tokens->getNextTokenOfKind($index, [[T_DOUBLE_ARROW]]); + + while (true) { + $index = $this->tokens->getNextMeaningfulToken($index); + + if ($this->tokens[$index]->equalsAny($stopTokens)) { + break; + } + + $blockType = Tokens::detectBlockType($this->tokens[$index]); + + if (null === $blockType) { + continue; + } + + if ($blockType['isStart']) { + $index = $this->tokens->findBlockEnd($blockType['type'], $index); + + continue; + } + + break; + } + + return $this->tokens->getPrevMeaningfulToken($index); + } + + /** + * Check if the T_STRING under given index is a constant invocation. + */ + public function isConstantInvocation(int $index): bool + { + if (!$this->tokens[$index]->isGivenKind(T_STRING)) { + throw new \LogicException(sprintf('No T_STRING at given index %d, got "%s".', $index, $this->tokens[$index]->getName())); + } + + $nextIndex = $this->tokens->getNextMeaningfulToken($index); + + if ( + $this->tokens[$nextIndex]->equalsAny(['(', '{']) + || $this->tokens[$nextIndex]->isGivenKind([T_AS, T_DOUBLE_COLON, T_ELLIPSIS, T_NS_SEPARATOR, CT::T_RETURN_REF, CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION, T_VARIABLE]) + ) { + return false; + } + + $prevIndex = $this->tokens->getPrevMeaningfulToken($index); + + if ($this->tokens[$prevIndex]->isGivenKind([T_AS, T_CLASS, T_CONST, T_DOUBLE_COLON, T_FUNCTION, T_GOTO, CT::T_GROUP_IMPORT_BRACE_OPEN, T_INTERFACE, T_TRAIT, CT::T_TYPE_COLON, CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION]) || $this->tokens[$prevIndex]->isObjectOperator()) { + return false; + } + + while ($this->tokens[$prevIndex]->isGivenKind([CT::T_NAMESPACE_OPERATOR, T_NS_SEPARATOR, T_STRING])) { + $prevIndex = $this->tokens->getPrevMeaningfulToken($prevIndex); + } + + if ($this->tokens[$prevIndex]->isGivenKind([CT::T_CONST_IMPORT, T_EXTENDS, CT::T_FUNCTION_IMPORT, T_IMPLEMENTS, T_INSTANCEOF, T_INSTEADOF, T_NAMESPACE, T_NEW, CT::T_NULLABLE_TYPE, CT::T_TYPE_COLON, T_USE, CT::T_USE_TRAIT])) { + return false; + } + + // `FOO & $bar` could be: + // - function reference parameter: function baz(Foo & $bar) {} + // - bit operator: $x = FOO & $bar; + if ($this->tokens[$nextIndex]->equals('&') && $this->tokens[$this->tokens->getNextMeaningfulToken($nextIndex)]->isGivenKind(T_VARIABLE)) { + $checkIndex = $this->tokens->getPrevTokenOfKind($prevIndex, [';', '{', '}', [T_FUNCTION], [T_OPEN_TAG], [T_OPEN_TAG_WITH_ECHO]]); + + if ($this->tokens[$checkIndex]->isGivenKind(T_FUNCTION)) { + return false; + } + } + + // check for `extends`/`implements`/`use` list + if ($this->tokens[$prevIndex]->equals(',')) { + $checkIndex = $prevIndex; + + while ($this->tokens[$checkIndex]->equalsAny([',', [T_AS], [CT::T_NAMESPACE_OPERATOR], [T_NS_SEPARATOR], [T_STRING]])) { + $checkIndex = $this->tokens->getPrevMeaningfulToken($checkIndex); + } + + if ($this->tokens[$checkIndex]->isGivenKind([T_EXTENDS, CT::T_GROUP_IMPORT_BRACE_OPEN, T_IMPLEMENTS, T_USE, CT::T_USE_TRAIT])) { + return false; + } + } + + // check for array in double quoted string: `"..$foo[bar].."` + if ($this->tokens[$prevIndex]->equals('[') && $this->tokens[$nextIndex]->equals(']')) { + $checkToken = $this->tokens[$this->tokens->getNextMeaningfulToken($nextIndex)]; + + if ($checkToken->equals('"') || $checkToken->isGivenKind([T_CURLY_OPEN, T_DOLLAR_OPEN_CURLY_BRACES, T_ENCAPSED_AND_WHITESPACE, T_VARIABLE])) { + return false; + } + } + + // check for attribute: `#[Foo]` + if (AttributeAnalyzer::isAttribute($this->tokens, $index)) { + return false; + } + + // check for goto label + if ($this->tokens[$nextIndex]->equals(':')) { + if (null === $this->gotoLabelAnalyzer) { + $this->gotoLabelAnalyzer = new GotoLabelAnalyzer(); + } + + if ($this->gotoLabelAnalyzer->belongsToGoToLabel($this->tokens, $nextIndex)) { + return false; + } + } + + // check for non-capturing catches + + while ($this->tokens[$prevIndex]->isGivenKind([CT::T_NAMESPACE_OPERATOR, T_NS_SEPARATOR, T_STRING, CT::T_TYPE_ALTERNATION])) { + $prevIndex = $this->tokens->getPrevMeaningfulToken($prevIndex); + } + + if ($this->tokens[$prevIndex]->equals('(')) { + $prevPrevIndex = $this->tokens->getPrevMeaningfulToken($prevIndex); + + if ($this->tokens[$prevPrevIndex]->isGivenKind(T_CATCH)) { + return false; + } + } + + return true; + } + + /** + * Checks if there is a unary successor operator under given index. + */ + public function isUnarySuccessorOperator(int $index): bool + { + static $allowedPrevToken = [ + ']', + [T_STRING], + [T_VARIABLE], + [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE], + [CT::T_DYNAMIC_PROP_BRACE_CLOSE], + [CT::T_DYNAMIC_VAR_BRACE_CLOSE], + ]; + + $tokens = $this->tokens; + $token = $tokens[$index]; + + if (!$token->isGivenKind([T_INC, T_DEC])) { + return false; + } + + $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; + + return $prevToken->equalsAny($allowedPrevToken); + } + + /** + * Checks if there is a unary predecessor operator under given index. + */ + public function isUnaryPredecessorOperator(int $index): bool + { + static $potentialSuccessorOperator = [T_INC, T_DEC]; + + static $potentialBinaryOperator = ['+', '-', '&', [CT::T_RETURN_REF]]; + + static $otherOperators; + + if (null === $otherOperators) { + $otherOperators = ['!', '~', '@', [T_ELLIPSIS]]; + } + + static $disallowedPrevTokens; + + if (null === $disallowedPrevTokens) { + $disallowedPrevTokens = [ + ']', + '}', + ')', + '"', + '`', + [CT::T_ARRAY_SQUARE_BRACE_CLOSE], + [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE], + [CT::T_DYNAMIC_PROP_BRACE_CLOSE], + [CT::T_DYNAMIC_VAR_BRACE_CLOSE], + [T_CLASS_C], + [T_CONSTANT_ENCAPSED_STRING], + [T_DEC], + [T_DIR], + [T_DNUMBER], + [T_FILE], + [T_FUNC_C], + [T_INC], + [T_LINE], + [T_LNUMBER], + [T_METHOD_C], + [T_NS_C], + [T_STRING], + [T_TRAIT_C], + [T_VARIABLE], + ]; + } + + $tokens = $this->tokens; + $token = $tokens[$index]; + + if ($token->isGivenKind($potentialSuccessorOperator)) { + return !$this->isUnarySuccessorOperator($index); + } + + if ($token->equalsAny($otherOperators)) { + return true; + } + + if (!$token->equalsAny($potentialBinaryOperator)) { + return false; + } + + $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; + + if (!$prevToken->equalsAny($disallowedPrevTokens)) { + return true; + } + + if (!$token->equals('&') || !$prevToken->isGivenKind(T_STRING)) { + return false; + } + + static $searchTokens = [ + ';', + '{', + '}', + [T_FN], + [T_FUNCTION], + [T_OPEN_TAG], + [T_OPEN_TAG_WITH_ECHO], + ]; + $prevToken = $tokens[$tokens->getPrevTokenOfKind($index, $searchTokens)]; + + return $prevToken->isGivenKind([T_FN, T_FUNCTION]); + } + + /** + * Checks if there is a binary operator under given index. + */ + public function isBinaryOperator(int $index): bool + { + static $nonArrayOperators = [ + '=' => true, + '*' => true, + '/' => true, + '%' => true, + '<' => true, + '>' => true, + '|' => true, + '^' => true, + '.' => true, + ]; + + static $potentialUnaryNonArrayOperators = [ + '+' => true, + '-' => true, + '&' => true, + ]; + + static $arrayOperators; + + if (null === $arrayOperators) { + $arrayOperators = [ + T_AND_EQUAL => true, // &= + T_BOOLEAN_AND => true, // && + T_BOOLEAN_OR => true, // || + T_CONCAT_EQUAL => true, // .= + T_DIV_EQUAL => true, // /= + T_DOUBLE_ARROW => true, // => + T_IS_EQUAL => true, // == + T_IS_GREATER_OR_EQUAL => true, // >= + T_IS_IDENTICAL => true, // === + T_IS_NOT_EQUAL => true, // !=, <> + T_IS_NOT_IDENTICAL => true, // !== + T_IS_SMALLER_OR_EQUAL => true, // <= + T_LOGICAL_AND => true, // and + T_LOGICAL_OR => true, // or + T_LOGICAL_XOR => true, // xor + T_MINUS_EQUAL => true, // -= + T_MOD_EQUAL => true, // %= + T_MUL_EQUAL => true, // *= + T_OR_EQUAL => true, // |= + T_PLUS_EQUAL => true, // += + T_POW => true, // ** + T_POW_EQUAL => true, // **= + T_SL => true, // << + T_SL_EQUAL => true, // <<= + T_SR => true, // >> + T_SR_EQUAL => true, // >>= + T_XOR_EQUAL => true, // ^= + T_SPACESHIP => true, // <=> + T_COALESCE => true, // ?? + T_COALESCE_EQUAL => true, // ??= + ]; + } + + $tokens = $this->tokens; + $token = $tokens[$index]; + + if ($token->isGivenKind([T_INLINE_HTML, T_ENCAPSED_AND_WHITESPACE, CT::T_TYPE_INTERSECTION])) { + return false; + } + + if (isset($potentialUnaryNonArrayOperators[$token->getContent()])) { + return !$this->isUnaryPredecessorOperator($index); + } + + if ($token->isArray()) { + return isset($arrayOperators[$token->getId()]); + } + + if (isset($nonArrayOperators[$token->getContent()])) { + return true; + } + + return false; + } + + /** + * Check if `T_WHILE` token at given index is `do { ... } while ();` syntax + * and not `while () { ...}`. + */ + public function isWhilePartOfDoWhile(int $index): bool + { + $tokens = $this->tokens; + $token = $tokens[$index]; + + if (!$token->isGivenKind(T_WHILE)) { + throw new \LogicException(sprintf('No T_WHILE at given index %d, got "%s".', $index, $token->getName())); + } + + $endIndex = $tokens->getPrevMeaningfulToken($index); + if (!$tokens[$endIndex]->equals('}')) { + return false; + } + + $startIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $endIndex); + $beforeStartIndex = $tokens->getPrevMeaningfulToken($startIndex); + + return $tokens[$beforeStartIndex]->isGivenKind(T_DO); + } + + /** + * @throws \LogicException when provided index does not point to token containing T_CASE + */ + public function isEnumCase(int $caseIndex): bool + { + $tokens = $this->tokens; + $token = $tokens[$caseIndex]; + + if (!$token->isGivenKind(T_CASE)) { + throw new \LogicException(sprintf( + 'No T_CASE given at index %d, got %s instead.', + $caseIndex, + $token->getName() ?? $token->getContent() + )); + } + + if (!\defined('T_ENUM') || !$tokens->isTokenKindFound(T_ENUM)) { + return false; + } + + $prevIndex = $tokens->getPrevTokenOfKind($caseIndex, [[T_ENUM], [T_SWITCH]]); + + return null !== $prevIndex && $tokens[$prevIndex]->isGivenKind(T_ENUM); + } + + public function isSuperGlobal(int $index): bool + { + static $superNames = [ + '$_COOKIE' => true, + '$_ENV' => true, + '$_FILES' => true, + '$_GET' => true, + '$_POST' => true, + '$_REQUEST' => true, + '$_SERVER' => true, + '$_SESSION' => true, + '$GLOBALS' => true, + ]; + + $token = $this->tokens[$index]; + + if (!$token->isGivenKind(T_VARIABLE)) { + return false; + } + + return isset($superNames[strtoupper($token->getContent())]); + } + + /** + * Find classy elements. + * + * Searches in tokens from the classy (start) index till the end (index) of the classy. + * Returns an array; first value is the index until the method has analysed (int), second the found classy elements (array). + * + * @param int $classIndex classy index + * + * @return array{int, array} + */ + private function findClassyElements(int $classIndex, int $index): array + { + $elements = []; + $curlyBracesLevel = 0; + $bracesLevel = 0; + ++$index; // skip the classy index itself + + for ($count = \count($this->tokens); $index < $count; ++$index) { + $token = $this->tokens[$index]; + + if ($token->isGivenKind(T_ENCAPSED_AND_WHITESPACE)) { + continue; + } + + if ($token->isGivenKind(T_CLASS)) { // anonymous class in class + // check for nested anonymous classes inside the new call of an anonymous class, + // for example `new class(function (){new class(function (){new class(function (){}){};}){};}){};` etc. + // if class(XYZ) {} skip till `(` as XYZ might contain functions etc. + + $nestedClassIndex = $index; + $index = $this->tokens->getNextMeaningfulToken($index); + + if ($this->tokens[$index]->equals('(')) { + ++$index; // move after `(` + + for ($nestedBracesLevel = 1; $index < $count; ++$index) { + $token = $this->tokens[$index]; + + if ($token->equals('(')) { + ++$nestedBracesLevel; + + continue; + } + + if ($token->equals(')')) { + --$nestedBracesLevel; + + if (0 === $nestedBracesLevel) { + [$index, $newElements] = $this->findClassyElements($nestedClassIndex, $index); + $elements += $newElements; + + break; + } + + continue; + } + + if ($token->isGivenKind(T_CLASS)) { // anonymous class in class + [$index, $newElements] = $this->findClassyElements($index, $index); + $elements += $newElements; + } + } + } else { + [$index, $newElements] = $this->findClassyElements($nestedClassIndex, $nestedClassIndex); + $elements += $newElements; + } + + continue; + } + + if ($token->equals('(')) { + ++$bracesLevel; + + continue; + } + + if ($token->equals(')')) { + --$bracesLevel; + + continue; + } + + if ($token->equals('{')) { + ++$curlyBracesLevel; + + continue; + } + + if ($token->equals('}')) { + --$curlyBracesLevel; + + if (0 === $curlyBracesLevel) { + break; + } + + continue; + } + + if (1 !== $curlyBracesLevel || !$token->isArray()) { + continue; + } + + if (0 === $bracesLevel && $token->isGivenKind(T_VARIABLE)) { + $elements[$index] = [ + 'classIndex' => $classIndex, + 'token' => $token, + 'type' => 'property', + ]; + + continue; + } + + if ($token->isGivenKind(T_FUNCTION)) { + $elements[$index] = [ + 'classIndex' => $classIndex, + 'token' => $token, + 'type' => 'method', + ]; + } elseif ($token->isGivenKind(T_CONST)) { + $elements[$index] = [ + 'classIndex' => $classIndex, + 'token' => $token, + 'type' => 'const', + ]; + } elseif ($token->isGivenKind(CT::T_USE_TRAIT)) { + $elements[$index] = [ + 'classIndex' => $classIndex, + 'token' => $token, + 'type' => 'trait_import', + ]; + } elseif ($token->isGivenKind(T_CASE)) { + $elements[$index] = [ + 'classIndex' => $classIndex, + 'token' => $token, + 'type' => 'case', + ]; + } + } + + return [$index, $elements]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ArrayTypehintTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ArrayTypehintTransformer.php new file mode 100644 index 00000000..8e5b9bf1 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ArrayTypehintTransformer.php @@ -0,0 +1,54 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Transformer; + +use PhpCsFixer\Tokenizer\AbstractTransformer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Transform `array` typehint from T_ARRAY into CT::T_ARRAY_TYPEHINT. + * + * @author Dariusz Rumiński + * + * @internal + */ +final class ArrayTypehintTransformer extends AbstractTransformer +{ + public function getRequiredPhpVersionId(): int + { + return 5_00_00; + } + + public function process(Tokens $tokens, Token $token, int $index): void + { + if (!$token->isGivenKind(T_ARRAY)) { + return; + } + + $nextIndex = $tokens->getNextMeaningfulToken($index); + $nextToken = $tokens[$nextIndex]; + + if (!$nextToken->equals('(')) { + $tokens[$index] = new Token([CT::T_ARRAY_TYPEHINT, $token->getContent()]); + } + } + + public function getCustomTokens(): array + { + return [CT::T_ARRAY_TYPEHINT]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/AttributeTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/AttributeTransformer.php new file mode 100644 index 00000000..d19d3b88 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/AttributeTransformer.php @@ -0,0 +1,67 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Transformer; + +use PhpCsFixer\Tokenizer\AbstractTransformer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Transforms attribute related Tokens. + * + * @internal + */ +final class AttributeTransformer extends AbstractTransformer +{ + public function getPriority(): int + { + // must run before all other transformers that might touch attributes + return 200; + } + + public function getRequiredPhpVersionId(): int + { + return 8_00_00; + } + + public function process(Tokens $tokens, Token $token, int $index): void + { + if (!$tokens[$index]->isGivenKind(T_ATTRIBUTE)) { + return; + } + + $level = 1; + + do { + ++$index; + + if ($tokens[$index]->equals('[')) { + ++$level; + } elseif ($tokens[$index]->equals(']')) { + --$level; + } + } while (0 < $level); + + $tokens[$index] = new Token([CT::T_ATTRIBUTE_CLOSE, ']']); + } + + public function getCustomTokens(): array + { + return [ + CT::T_ATTRIBUTE_CLOSE, + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/BraceClassInstantiationTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/BraceClassInstantiationTransformer.php new file mode 100644 index 00000000..1a690b89 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/BraceClassInstantiationTransformer.php @@ -0,0 +1,80 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Transformer; + +use PhpCsFixer\Tokenizer\AbstractTransformer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Transform braced class instantiation braces in `(new Foo())` into CT::T_BRACE_CLASS_INSTANTIATION_OPEN + * and CT::T_BRACE_CLASS_INSTANTIATION_CLOSE. + * + * @author Sebastiaans Stok + * + * @internal + */ +final class BraceClassInstantiationTransformer extends AbstractTransformer +{ + public function getPriority(): int + { + // must run after CurlyBraceTransformer and SquareBraceTransformer + return -2; + } + + public function getRequiredPhpVersionId(): int + { + return 5_00_00; + } + + public function process(Tokens $tokens, Token $token, int $index): void + { + if (!$tokens[$index]->equals('(') || !$tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind(T_NEW)) { + return; + } + + if ($tokens[$tokens->getPrevMeaningfulToken($index)]->equalsAny([ + ')', + ']', + [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE], + [CT::T_ARRAY_SQUARE_BRACE_CLOSE], + [CT::T_BRACE_CLASS_INSTANTIATION_CLOSE], + [T_ARRAY], + [T_CLASS], + [T_ELSEIF], + [T_FOR], + [T_FOREACH], + [T_IF], + [T_STATIC], + [T_STRING], + [T_SWITCH], + [T_VARIABLE], + [T_WHILE], + ])) { + return; + } + + $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + + $tokens[$index] = new Token([CT::T_BRACE_CLASS_INSTANTIATION_OPEN, '(']); + $tokens[$closeIndex] = new Token([CT::T_BRACE_CLASS_INSTANTIATION_CLOSE, ')']); + } + + public function getCustomTokens(): array + { + return [CT::T_BRACE_CLASS_INSTANTIATION_OPEN, CT::T_BRACE_CLASS_INSTANTIATION_CLOSE]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/BraceTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/BraceTransformer.php new file mode 100644 index 00000000..e4f374ad --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/BraceTransformer.php @@ -0,0 +1,286 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Transformer; + +use PhpCsFixer\Tokenizer\AbstractTransformer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Transform discriminate overloaded curly braces tokens. + * + * Performed transformations: + * - closing `}` for T_CURLY_OPEN into CT::T_CURLY_CLOSE, + * - closing `}` for T_DOLLAR_OPEN_CURLY_BRACES into CT::T_DOLLAR_CLOSE_CURLY_BRACES, + * - in `$foo->{$bar}` into CT::T_DYNAMIC_PROP_BRACE_OPEN and CT::T_DYNAMIC_PROP_BRACE_CLOSE, + * - in `${$foo}` into CT::T_DYNAMIC_VAR_BRACE_OPEN and CT::T_DYNAMIC_VAR_BRACE_CLOSE, + * - in `$array{$index}` into CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN and CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE, + * - in `use some\a\{ClassA, ClassB, ClassC as C}` into CT::T_GROUP_IMPORT_BRACE_OPEN, CT::T_GROUP_IMPORT_BRACE_CLOSE. + * + * @author Dariusz Rumiński + * + * @internal + */ +final class BraceTransformer extends AbstractTransformer +{ + public function getRequiredPhpVersionId(): int + { + return 5_00_00; + } + + public function process(Tokens $tokens, Token $token, int $index): void + { + $this->transformIntoCurlyCloseBrace($tokens, $token, $index); + $this->transformIntoDollarCloseBrace($tokens, $token, $index); + $this->transformIntoDynamicPropBraces($tokens, $token, $index); + $this->transformIntoDynamicVarBraces($tokens, $token, $index); + $this->transformIntoCurlyIndexBraces($tokens, $token, $index); + $this->transformIntoGroupUseBraces($tokens, $token, $index); + $this->transformIntoDynamicClassConstantFetchBraces($tokens, $token, $index); + } + + public function getCustomTokens(): array + { + return [ + CT::T_CURLY_CLOSE, + CT::T_DOLLAR_CLOSE_CURLY_BRACES, + CT::T_DYNAMIC_PROP_BRACE_OPEN, + CT::T_DYNAMIC_PROP_BRACE_CLOSE, + CT::T_DYNAMIC_VAR_BRACE_OPEN, + CT::T_DYNAMIC_VAR_BRACE_CLOSE, + CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN, + CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE, + CT::T_GROUP_IMPORT_BRACE_OPEN, + CT::T_GROUP_IMPORT_BRACE_CLOSE, + CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN, + CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE, + ]; + } + + /** + * Transform closing `}` for T_CURLY_OPEN into CT::T_CURLY_CLOSE. + * + * This should be done at very beginning of curly braces transformations. + */ + private function transformIntoCurlyCloseBrace(Tokens $tokens, Token $token, int $index): void + { + if (!$token->isGivenKind(T_CURLY_OPEN)) { + return; + } + + $level = 1; + + do { + ++$index; + + if ($tokens[$index]->equals('{') || $tokens[$index]->isGivenKind(T_CURLY_OPEN)) { // we count all kind of { + ++$level; + } elseif ($tokens[$index]->equals('}')) { // we count all kind of } + --$level; + } + } while (0 < $level); + + $tokens[$index] = new Token([CT::T_CURLY_CLOSE, '}']); + } + + private function transformIntoDollarCloseBrace(Tokens $tokens, Token $token, int $index): void + { + if ($token->isGivenKind(T_DOLLAR_OPEN_CURLY_BRACES)) { + $nextIndex = $tokens->getNextTokenOfKind($index, ['}']); + $tokens[$nextIndex] = new Token([CT::T_DOLLAR_CLOSE_CURLY_BRACES, '}']); + } + } + + private function transformIntoDynamicPropBraces(Tokens $tokens, Token $token, int $index): void + { + if (!$token->isObjectOperator()) { + return; + } + + if (!$tokens[$index + 1]->equals('{')) { + return; + } + + $openIndex = $index + 1; + $closeIndex = $this->naivelyFindCurlyBlockEnd($tokens, $openIndex); + + $tokens[$openIndex] = new Token([CT::T_DYNAMIC_PROP_BRACE_OPEN, '{']); + $tokens[$closeIndex] = new Token([CT::T_DYNAMIC_PROP_BRACE_CLOSE, '}']); + } + + private function transformIntoDynamicVarBraces(Tokens $tokens, Token $token, int $index): void + { + if (!$token->equals('$')) { + return; + } + + $openIndex = $tokens->getNextMeaningfulToken($index); + + if (null === $openIndex) { + return; + } + + $openToken = $tokens[$openIndex]; + + if (!$openToken->equals('{')) { + return; + } + + $closeIndex = $this->naivelyFindCurlyBlockEnd($tokens, $openIndex); + + $tokens[$openIndex] = new Token([CT::T_DYNAMIC_VAR_BRACE_OPEN, '{']); + $tokens[$closeIndex] = new Token([CT::T_DYNAMIC_VAR_BRACE_CLOSE, '}']); + } + + private function transformIntoCurlyIndexBraces(Tokens $tokens, Token $token, int $index): void + { + if (!$token->equals('{')) { + return; + } + + $prevIndex = $tokens->getPrevMeaningfulToken($index); + + if (!$tokens[$prevIndex]->equalsAny([ + [T_STRING], + [T_VARIABLE], + [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE], + ']', + ')', + ])) { + return; + } + + if ( + $tokens[$prevIndex]->isGivenKind(T_STRING) + && !$tokens[$tokens->getPrevMeaningfulToken($prevIndex)]->isObjectOperator() + ) { + return; + } + + if ( + $tokens[$prevIndex]->equals(')') + && !$tokens[$tokens->getPrevMeaningfulToken( + $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $prevIndex) + )]->isGivenKind(T_ARRAY) + ) { + return; + } + + $closeIndex = $this->naivelyFindCurlyBlockEnd($tokens, $index); + + $tokens[$index] = new Token([CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN, '{']); + $tokens[$closeIndex] = new Token([CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE, '}']); + } + + private function transformIntoGroupUseBraces(Tokens $tokens, Token $token, int $index): void + { + if (!$token->equals('{')) { + return; + } + + $prevIndex = $tokens->getPrevMeaningfulToken($index); + + if (!$tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) { + return; + } + + $closeIndex = $this->naivelyFindCurlyBlockEnd($tokens, $index); + + $tokens[$index] = new Token([CT::T_GROUP_IMPORT_BRACE_OPEN, '{']); + $tokens[$closeIndex] = new Token([CT::T_GROUP_IMPORT_BRACE_CLOSE, '}']); + } + + private function transformIntoDynamicClassConstantFetchBraces(Tokens $tokens, Token $token, int $index): void + { + if (\PHP_VERSION_ID < 8_03_00) { + return; // @TODO: drop condition when PHP 8.3+ is required or majority of the users are using 8.3+ + } + + if (!$token->equals('{')) { + return; + } + + $prevMeaningfulTokenIndex = $tokens->getPrevMeaningfulToken($index); + + while (!$tokens[$prevMeaningfulTokenIndex]->isGivenKind(T_DOUBLE_COLON)) { + if (!$tokens[$prevMeaningfulTokenIndex]->equals(')')) { + return; + } + + $prevMeaningfulTokenIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $prevMeaningfulTokenIndex); + $prevMeaningfulTokenIndex = $tokens->getPrevMeaningfulToken($prevMeaningfulTokenIndex); + + if (!$tokens[$prevMeaningfulTokenIndex]->equals('}')) { + return; + } + + $prevMeaningfulTokenIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $prevMeaningfulTokenIndex); + $prevMeaningfulTokenIndex = $tokens->getPrevMeaningfulToken($prevMeaningfulTokenIndex); + } + + $closeIndex = $this->naivelyFindCurlyBlockEnd($tokens, $index); + $nextMeaningfulTokenIndexAfterCloseIndex = $tokens->getNextMeaningfulToken($closeIndex); + + if (!$tokens[$nextMeaningfulTokenIndexAfterCloseIndex]->equalsAny([';', [T_CLOSE_TAG], [T_DOUBLE_COLON]])) { + return; + } + + $tokens[$index] = new Token([CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN, '{']); + $tokens[$closeIndex] = new Token([CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE, '}']); + } + + /** + * We do not want to rely on `$tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index)` here, + * as it relies on block types that are assuming that `}` tokens are already transformed to Custom Tokens that are allowing to distinguish different block types. + * As we are just about to transform `{` and `}` into Custom Tokens by this transformer, thus we need to compare those tokens manually by content without using `Tokens::findBlockEnd`. + */ + private function naivelyFindCurlyBlockEnd(Tokens $tokens, int $startIndex): int + { + if (!$tokens->offsetExists($startIndex)) { + throw new \OutOfBoundsException(sprintf('Unavailable index: "%s".', $startIndex)); + } + + if ('{' !== $tokens[$startIndex]->getContent()) { + throw new \InvalidArgumentException(sprintf('Wrong start index: "%s".', $startIndex)); + } + + $blockLevel = 1; + $endIndex = $tokens->count() - 1; + for ($index = $startIndex + 1; $index !== $endIndex; ++$index) { + $token = $tokens[$index]; + + if ('{' === $token->getContent()) { + ++$blockLevel; + + continue; + } + + if ('}' === $token->getContent()) { + --$blockLevel; + + if (0 === $blockLevel) { + if (!$token->equals('}')) { + throw new \UnexpectedValueException(sprintf('Detected block end for index: "%s" was already transformed into other token type: "%s".', $startIndex, $token->getName())); + } + + return $index; + } + } + } + + throw new \UnexpectedValueException(sprintf('Missing block end for index: "%s".', $startIndex)); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ClassConstantTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ClassConstantTransformer.php new file mode 100644 index 00000000..bb75b727 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ClassConstantTransformer.php @@ -0,0 +1,57 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Transformer; + +use PhpCsFixer\Tokenizer\AbstractTransformer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Transform `class` class' constant from T_CLASS into CT::T_CLASS_CONSTANT. + * + * @author Dariusz Rumiński + * + * @internal + */ +final class ClassConstantTransformer extends AbstractTransformer +{ + public function getRequiredPhpVersionId(): int + { + return 5_05_00; + } + + public function process(Tokens $tokens, Token $token, int $index): void + { + if (!$token->equalsAny([ + [T_CLASS, 'class'], + [T_STRING, 'class'], + ], false)) { + return; + } + + $prevIndex = $tokens->getPrevMeaningfulToken($index); + $prevToken = $tokens[$prevIndex]; + + if ($prevToken->isGivenKind(T_DOUBLE_COLON)) { + $tokens[$index] = new Token([CT::T_CLASS_CONSTANT, $token->getContent()]); + } + } + + public function getCustomTokens(): array + { + return [CT::T_CLASS_CONSTANT]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ConstructorPromotionTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ConstructorPromotionTransformer.php new file mode 100644 index 00000000..cdb282d3 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ConstructorPromotionTransformer.php @@ -0,0 +1,71 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Transformer; + +use PhpCsFixer\Tokenizer\AbstractTransformer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Transforms for Constructor Property Promotion. + * + * Transform T_PUBLIC, T_PROTECTED and T_PRIVATE of Constructor Property Promotion into custom tokens. + * + * @internal + */ +final class ConstructorPromotionTransformer extends AbstractTransformer +{ + public function getRequiredPhpVersionId(): int + { + return 8_00_00; + } + + public function process(Tokens $tokens, Token $token, int $index): void + { + if (!$tokens[$index]->isGivenKind(T_FUNCTION)) { + return; + } + + $functionNameIndex = $tokens->getNextMeaningfulToken($index); + + if (!$tokens[$functionNameIndex]->isGivenKind(T_STRING) || '__construct' !== strtolower($tokens[$functionNameIndex]->getContent())) { + return; + } + + /** @var int $openParenthesisIndex */ + $openParenthesisIndex = $tokens->getNextMeaningfulToken($functionNameIndex); // we are @ '(' now + $closeParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesisIndex); + + for ($argsIndex = $openParenthesisIndex; $argsIndex < $closeParenthesisIndex; ++$argsIndex) { + if ($tokens[$argsIndex]->isGivenKind(T_PUBLIC)) { + $tokens[$argsIndex] = new Token([CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC, $tokens[$argsIndex]->getContent()]); + } elseif ($tokens[$argsIndex]->isGivenKind(T_PROTECTED)) { + $tokens[$argsIndex] = new Token([CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED, $tokens[$argsIndex]->getContent()]); + } elseif ($tokens[$argsIndex]->isGivenKind(T_PRIVATE)) { + $tokens[$argsIndex] = new Token([CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE, $tokens[$argsIndex]->getContent()]); + } + } + } + + public function getCustomTokens(): array + { + return [ + CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC, + CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED, + CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE, + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/DisjunctiveNormalFormTypeParenthesisTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/DisjunctiveNormalFormTypeParenthesisTransformer.php new file mode 100644 index 00000000..21d062b0 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/DisjunctiveNormalFormTypeParenthesisTransformer.php @@ -0,0 +1,65 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Transformer; + +use PhpCsFixer\Tokenizer\AbstractTransformer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Transform DNF parentheses into CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_OPEN and CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE. + * + * @see https://wiki.php.net/rfc/dnf_types + * + * @internal + */ +final class DisjunctiveNormalFormTypeParenthesisTransformer extends AbstractTransformer +{ + public function getPriority(): int + { + // needs to run after TypeAlternationTransformer + return -16; + } + + public function getRequiredPhpVersionId(): int + { + return 8_02_00; + } + + public function process(Tokens $tokens, Token $token, int $index): void + { + if ($token->equals('(') && $tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(CT::T_TYPE_ALTERNATION)) { + $openIndex = $index; + $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + } elseif ($token->equals(')') && $tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind(CT::T_TYPE_ALTERNATION)) { + $openIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + $closeIndex = $index; + } else { + return; + } + + $tokens[$openIndex] = new Token([CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_OPEN, '(']); + $tokens[$closeIndex] = new Token([CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE, ')']); + } + + public function getCustomTokens(): array + { + return [ + CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_OPEN, + CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE, + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/FirstClassCallableTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/FirstClassCallableTransformer.php new file mode 100644 index 00000000..4781e7b8 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/FirstClassCallableTransformer.php @@ -0,0 +1,49 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Transformer; + +use PhpCsFixer\Tokenizer\AbstractTransformer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @internal + */ +final class FirstClassCallableTransformer extends AbstractTransformer +{ + public function getRequiredPhpVersionId(): int + { + return 8_01_00; + } + + public function process(Tokens $tokens, Token $token, int $index): void + { + if ( + $token->isGivenKind(T_ELLIPSIS) + && $tokens[$tokens->getPrevMeaningfulToken($index)]->equals('(') + && $tokens[$tokens->getNextMeaningfulToken($index)]->equals(')') + ) { + $tokens[$index] = new Token([CT::T_FIRST_CLASS_CALLABLE, '...']); + } + } + + public function getCustomTokens(): array + { + return [ + CT::T_FIRST_CLASS_CALLABLE, + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ImportTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ImportTransformer.php new file mode 100644 index 00000000..1a5b0107 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ImportTransformer.php @@ -0,0 +1,72 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Transformer; + +use PhpCsFixer\Tokenizer\AbstractTransformer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Transform const/function import tokens. + * + * Performed transformations: + * - T_CONST into CT::T_CONST_IMPORT + * - T_FUNCTION into CT::T_FUNCTION_IMPORT + * + * @author Gregor Harlan + * + * @internal + */ +final class ImportTransformer extends AbstractTransformer +{ + public function getPriority(): int + { + // Should run after CurlyBraceTransformer and ReturnRefTransformer + return -1; + } + + public function getRequiredPhpVersionId(): int + { + return 5_06_00; + } + + public function process(Tokens $tokens, Token $token, int $index): void + { + if (!$token->isGivenKind([T_CONST, T_FUNCTION])) { + return; + } + + $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; + + if (!$prevToken->isGivenKind(T_USE)) { + $nextToken = $tokens[$tokens->getNextTokenOfKind($index, ['=', '(', [CT::T_RETURN_REF], [CT::T_GROUP_IMPORT_BRACE_CLOSE]])]; + + if (!$nextToken->isGivenKind(CT::T_GROUP_IMPORT_BRACE_CLOSE)) { + return; + } + } + + $tokens[$index] = new Token([ + $token->isGivenKind(T_FUNCTION) ? CT::T_FUNCTION_IMPORT : CT::T_CONST_IMPORT, + $token->getContent(), + ]); + } + + public function getCustomTokens(): array + { + return [CT::T_CONST_IMPORT, CT::T_FUNCTION_IMPORT]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NameQualifiedTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NameQualifiedTransformer.php new file mode 100644 index 00000000..ed8353c8 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NameQualifiedTransformer.php @@ -0,0 +1,67 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Transformer; + +use PhpCsFixer\Tokenizer\AbstractTransformer; +use PhpCsFixer\Tokenizer\Processor\ImportProcessor; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Transform NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED and T_NAME_RELATIVE into T_NAMESPACE T_NS_SEPARATOR T_STRING. + * + * @internal + */ +final class NameQualifiedTransformer extends AbstractTransformer +{ + public function getPriority(): int + { + return 1; // must run before NamespaceOperatorTransformer + } + + public function getRequiredPhpVersionId(): int + { + return 8_00_00; + } + + public function process(Tokens $tokens, Token $token, int $index): void + { + if ($token->isGivenKind([T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED])) { + $this->transformQualified($tokens, $token, $index); + } elseif ($token->isGivenKind(T_NAME_RELATIVE)) { + $this->transformRelative($tokens, $token, $index); + } + } + + public function getCustomTokens(): array + { + return []; + } + + private function transformQualified(Tokens $tokens, Token $token, int $index): void + { + $newTokens = ImportProcessor::tokenizeName($token->getContent()); + + $tokens->overrideRange($index, $index, $newTokens); + } + + private function transformRelative(Tokens $tokens, Token $token, int $index): void + { + $newTokens = ImportProcessor::tokenizeName($token->getContent()); + $newTokens[0] = new Token([T_NAMESPACE, 'namespace']); + + $tokens->overrideRange($index, $index, $newTokens); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NamedArgumentTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NamedArgumentTransformer.php new file mode 100644 index 00000000..0f64717b --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NamedArgumentTransformer.php @@ -0,0 +1,73 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Transformer; + +use PhpCsFixer\Tokenizer\AbstractTransformer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Transform named argument tokens. + * + * @internal + */ +final class NamedArgumentTransformer extends AbstractTransformer +{ + public function getPriority(): int + { + // needs to run after TypeColonTransformer + return -15; + } + + public function getRequiredPhpVersionId(): int + { + return 8_00_00; + } + + public function process(Tokens $tokens, Token $token, int $index): void + { + if (!$tokens[$index]->equals(':')) { + return; + } + + $stringIndex = $tokens->getPrevMeaningfulToken($index); + + if (!$tokens[$stringIndex]->isGivenKind(T_STRING)) { + return; + } + + $preStringIndex = $tokens->getPrevMeaningfulToken($stringIndex); + + // if equals any [';', '{', '}', [T_OPEN_TAG]] than it is a goto label + // if equals ')' than likely it is a type colon, but sure not a name argument + // if equals '?' than it is part of ternary statement + + if (!$tokens[$preStringIndex]->equalsAny([',', '('])) { + return; + } + + $tokens[$stringIndex] = new Token([CT::T_NAMED_ARGUMENT_NAME, $tokens[$stringIndex]->getContent()]); + $tokens[$index] = new Token([CT::T_NAMED_ARGUMENT_COLON, ':']); + } + + public function getCustomTokens(): array + { + return [ + CT::T_NAMED_ARGUMENT_COLON, + CT::T_NAMED_ARGUMENT_NAME, + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NamespaceOperatorTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NamespaceOperatorTransformer.php new file mode 100644 index 00000000..5244481a --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NamespaceOperatorTransformer.php @@ -0,0 +1,53 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Transformer; + +use PhpCsFixer\Tokenizer\AbstractTransformer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Transform `namespace` operator from T_NAMESPACE into CT::T_NAMESPACE_OPERATOR. + * + * @author Gregor Harlan + * + * @internal + */ +final class NamespaceOperatorTransformer extends AbstractTransformer +{ + public function getRequiredPhpVersionId(): int + { + return 5_03_00; + } + + public function process(Tokens $tokens, Token $token, int $index): void + { + if (!$token->isGivenKind(T_NAMESPACE)) { + return; + } + + $nextIndex = $tokens->getNextMeaningfulToken($index); + + if ($tokens[$nextIndex]->isGivenKind(T_NS_SEPARATOR)) { + $tokens[$index] = new Token([CT::T_NAMESPACE_OPERATOR, $token->getContent()]); + } + } + + public function getCustomTokens(): array + { + return [CT::T_NAMESPACE_OPERATOR]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NullableTypeTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NullableTypeTransformer.php new file mode 100644 index 00000000..07f481f4 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NullableTypeTransformer.php @@ -0,0 +1,83 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Transformer; + +use PhpCsFixer\Tokenizer\AbstractTransformer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Transform `?` operator into CT::T_NULLABLE_TYPE in `function foo(?Bar $b) {}`. + * + * @author Dariusz Rumiński + * + * @internal + */ +final class NullableTypeTransformer extends AbstractTransformer +{ + public function getPriority(): int + { + // needs to run after TypeColonTransformer + return -20; + } + + public function getRequiredPhpVersionId(): int + { + return 7_01_00; + } + + public function process(Tokens $tokens, Token $token, int $index): void + { + if (!$token->equals('?')) { + return; + } + + static $types; + + if (null === $types) { + $types = [ + '(', + ',', + [CT::T_TYPE_COLON], + [CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC], + [CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED], + [CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE], + [CT::T_ATTRIBUTE_CLOSE], + [T_PRIVATE], + [T_PROTECTED], + [T_PUBLIC], + [T_VAR], + [T_STATIC], + [T_CONST], + ]; + + if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required + $types[] = [T_READONLY]; + } + } + + $prevIndex = $tokens->getPrevMeaningfulToken($index); + + if ($tokens[$prevIndex]->equalsAny($types)) { + $tokens[$index] = new Token([CT::T_NULLABLE_TYPE, '?']); + } + } + + public function getCustomTokens(): array + { + return [CT::T_NULLABLE_TYPE]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ReturnRefTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ReturnRefTransformer.php new file mode 100644 index 00000000..13f26d05 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ReturnRefTransformer.php @@ -0,0 +1,47 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Transformer; + +use PhpCsFixer\Tokenizer\AbstractTransformer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Transform `&` operator into CT::T_RETURN_REF in `function & foo() {}`. + * + * @author Dariusz Rumiński + * + * @internal + */ +final class ReturnRefTransformer extends AbstractTransformer +{ + public function getRequiredPhpVersionId(): int + { + return 5_00_00; + } + + public function process(Tokens $tokens, Token $token, int $index): void + { + if ($token->equals('&') && $tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind([T_FUNCTION, T_FN])) { + $tokens[$index] = new Token([CT::T_RETURN_REF, '&']); + } + } + + public function getCustomTokens(): array + { + return [CT::T_RETURN_REF]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/SquareBraceTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/SquareBraceTransformer.php new file mode 100644 index 00000000..46e5b12d --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/SquareBraceTransformer.php @@ -0,0 +1,187 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Transformer; + +use PhpCsFixer\Tokenizer\AbstractTransformer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Transform discriminate overloaded square braces tokens. + * + * Performed transformations: + * - in `[1, 2, 3]` into CT::T_ARRAY_SQUARE_BRACE_OPEN and CT::T_ARRAY_SQUARE_BRACE_CLOSE, + * - in `[$a, &$b, [$c]] = array(1, 2, array(3))` into CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN and CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE. + * + * @author Dariusz Rumiński + * + * @internal + */ +final class SquareBraceTransformer extends AbstractTransformer +{ + public function getPriority(): int + { + // must run after CurlyBraceTransformer and AttributeTransformer + return -1; + } + + public function getRequiredPhpVersionId(): int + { + // Short array syntax was introduced in PHP 5.4, but the fixer is smart + // enough to handle it even before 5.4. + // Same for array destructing syntax sugar `[` introduced in PHP 7.1. + return 5_00_00; + } + + public function process(Tokens $tokens, Token $token, int $index): void + { + if ($this->isArrayDestructing($tokens, $index)) { + $this->transformIntoDestructuringSquareBrace($tokens, $index); + + return; + } + + if ($this->isShortArray($tokens, $index)) { + $this->transformIntoArraySquareBrace($tokens, $index); + } + } + + public function getCustomTokens(): array + { + return [ + CT::T_ARRAY_SQUARE_BRACE_OPEN, + CT::T_ARRAY_SQUARE_BRACE_CLOSE, + CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, + CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, + ]; + } + + private function transformIntoArraySquareBrace(Tokens $tokens, int $index): void + { + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE, $index); + + $tokens[$index] = new Token([CT::T_ARRAY_SQUARE_BRACE_OPEN, '[']); + $tokens[$endIndex] = new Token([CT::T_ARRAY_SQUARE_BRACE_CLOSE, ']']); + } + + private function transformIntoDestructuringSquareBrace(Tokens $tokens, int $index): void + { + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE, $index); + + $tokens[$index] = new Token([CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, '[']); + $tokens[$endIndex] = new Token([CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, ']']); + + $previousMeaningfulIndex = $index; + $index = $tokens->getNextMeaningfulToken($index); + + while ($index < $endIndex) { + if ($tokens[$index]->equals('[') && $tokens[$previousMeaningfulIndex]->equalsAny([[CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN], ','])) { + $tokens[$tokens->findBlockEnd(Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE, $index)] = new Token([CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, ']']); + $tokens[$index] = new Token([CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, '[']); + } + + $previousMeaningfulIndex = $index; + $index = $tokens->getNextMeaningfulToken($index); + } + } + + /** + * Check if token under given index is short array opening. + */ + private function isShortArray(Tokens $tokens, int $index): bool + { + if (!$tokens[$index]->equals('[')) { + return false; + } + + static $disallowedPrevTokens = [ + ')', + ']', + '}', + '"', + [T_CONSTANT_ENCAPSED_STRING], + [T_STRING], + [T_STRING_VARNAME], + [T_VARIABLE], + [CT::T_ARRAY_SQUARE_BRACE_CLOSE], + [CT::T_DYNAMIC_PROP_BRACE_CLOSE], + [CT::T_DYNAMIC_VAR_BRACE_CLOSE], + [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE], + ]; + + $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; + if ($prevToken->equalsAny($disallowedPrevTokens)) { + return false; + } + + $nextToken = $tokens[$tokens->getNextMeaningfulToken($index)]; + if ($nextToken->equals(']')) { + return true; + } + + return !$this->isArrayDestructing($tokens, $index); + } + + private function isArrayDestructing(Tokens $tokens, int $index): bool + { + if (!$tokens[$index]->equals('[')) { + return false; + } + + static $disallowedPrevTokens = [ + ')', + ']', + '"', + [T_CONSTANT_ENCAPSED_STRING], + [T_STRING], + [T_STRING_VARNAME], + [T_VARIABLE], + [CT::T_ARRAY_SQUARE_BRACE_CLOSE], + [CT::T_DYNAMIC_PROP_BRACE_CLOSE], + [CT::T_DYNAMIC_VAR_BRACE_CLOSE], + [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE], + ]; + + $prevIndex = $tokens->getPrevMeaningfulToken($index); + $prevToken = $tokens[$prevIndex]; + if ($prevToken->equalsAny($disallowedPrevTokens)) { + return false; + } + + if ($prevToken->isGivenKind(T_AS)) { + return true; + } + + if ($prevToken->isGivenKind(T_DOUBLE_ARROW)) { + $variableIndex = $tokens->getPrevMeaningfulToken($prevIndex); + if (!$tokens[$variableIndex]->isGivenKind(T_VARIABLE)) { + return false; + } + + $prevVariableIndex = $tokens->getPrevMeaningfulToken($variableIndex); + if ($tokens[$prevVariableIndex]->isGivenKind(T_AS)) { + return true; + } + } + + $type = Tokens::detectBlockType($tokens[$index]); + $end = $tokens->findBlockEnd($type['type'], $index); + + $nextToken = $tokens[$tokens->getNextMeaningfulToken($end)]; + + return $nextToken->equals('='); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/TypeAlternationTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/TypeAlternationTransformer.php new file mode 100644 index 00000000..91035f90 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/TypeAlternationTransformer.php @@ -0,0 +1,57 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Transformer; + +use PhpCsFixer\Tokenizer\AbstractTypeTransformer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Transform `|` operator into CT::T_TYPE_ALTERNATION in `function foo(Type1 | Type2 $x) {` + * or `} catch (ExceptionType1 | ExceptionType2 $e) {`. + * + * @author Dariusz Rumiński + * + * @internal + */ +final class TypeAlternationTransformer extends AbstractTypeTransformer +{ + public function getPriority(): int + { + // needs to run after ArrayTypehintTransformer, TypeColonTransformer and AttributeTransformer + return -15; + } + + public function getRequiredPhpVersionId(): int + { + return 7_01_00; + } + + public function process(Tokens $tokens, Token $token, int $index): void + { + $this->doProcess($tokens, $index, '|'); + } + + public function getCustomTokens(): array + { + return [CT::T_TYPE_ALTERNATION]; + } + + protected function replaceToken(Tokens $tokens, int $index): void + { + $tokens[$index] = new Token([CT::T_TYPE_ALTERNATION, '|']); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/TypeColonTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/TypeColonTransformer.php new file mode 100644 index 00000000..7fed9de2 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/TypeColonTransformer.php @@ -0,0 +1,83 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Transformer; + +use PhpCsFixer\Tokenizer\AbstractTransformer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Transform `:` operator into CT::T_TYPE_COLON in `function foo() : int {}`. + * + * @author Dariusz Rumiński + * + * @internal + */ +final class TypeColonTransformer extends AbstractTransformer +{ + public function getPriority(): int + { + // needs to run after ReturnRefTransformer and UseTransformer + // and before TypeAlternationTransformer + return -10; + } + + public function getRequiredPhpVersionId(): int + { + return 7_00_00; + } + + public function process(Tokens $tokens, Token $token, int $index): void + { + if (!$token->equals(':')) { + return; + } + + $endIndex = $tokens->getPrevMeaningfulToken($index); + + if ( + \defined('T_ENUM') // @TODO: drop condition when PHP 8.1+ is required + && $tokens[$tokens->getPrevMeaningfulToken($endIndex)]->isGivenKind(T_ENUM) + ) { + $tokens[$index] = new Token([CT::T_TYPE_COLON, ':']); + + return; + } + + if (!$tokens[$endIndex]->equals(')')) { + return; + } + + $startIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $endIndex); + $prevIndex = $tokens->getPrevMeaningfulToken($startIndex); + $prevToken = $tokens[$prevIndex]; + + // if this could be a function name we need to take one more step + if ($prevToken->isGivenKind(T_STRING)) { + $prevIndex = $tokens->getPrevMeaningfulToken($prevIndex); + $prevToken = $tokens[$prevIndex]; + } + + if ($prevToken->isGivenKind([T_FUNCTION, CT::T_RETURN_REF, CT::T_USE_LAMBDA, T_FN])) { + $tokens[$index] = new Token([CT::T_TYPE_COLON, ':']); + } + } + + public function getCustomTokens(): array + { + return [CT::T_TYPE_COLON]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/TypeIntersectionTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/TypeIntersectionTransformer.php new file mode 100644 index 00000000..8018155e --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/TypeIntersectionTransformer.php @@ -0,0 +1,55 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Transformer; + +use PhpCsFixer\Tokenizer\AbstractTypeTransformer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Transform `&` operator into CT::T_TYPE_INTERSECTION in `function foo(Type1 & Type2 $x) {` + * or `} catch (ExceptionType1 & ExceptionType2 $e) {`. + * + * @internal + */ +final class TypeIntersectionTransformer extends AbstractTypeTransformer +{ + public function getPriority(): int + { + // needs to run after ArrayTypehintTransformer, TypeColonTransformer and AttributeTransformer + return -15; + } + + public function getRequiredPhpVersionId(): int + { + return 8_01_00; + } + + public function process(Tokens $tokens, Token $token, int $index): void + { + $this->doProcess($tokens, $index, [T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG, '&']); + } + + public function getCustomTokens(): array + { + return [CT::T_TYPE_INTERSECTION]; + } + + protected function replaceToken(Tokens $tokens, int $index): void + { + $tokens[$index] = new Token([CT::T_TYPE_INTERSECTION, '&']); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/UseTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/UseTransformer.php new file mode 100644 index 00000000..5aa0c027 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/UseTransformer.php @@ -0,0 +1,102 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Transformer; + +use PhpCsFixer\Tokenizer\AbstractTransformer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Transform T_USE into: + * - CT::T_USE_TRAIT for imports, + * - CT::T_USE_LAMBDA for lambda variable uses. + * + * @author Dariusz Rumiński + * + * @internal + */ +final class UseTransformer extends AbstractTransformer +{ + public function getPriority(): int + { + // Should run after CurlyBraceTransformer and before TypeColonTransformer + return -5; + } + + public function getRequiredPhpVersionId(): int + { + return 5_03_00; + } + + public function process(Tokens $tokens, Token $token, int $index): void + { + if ($token->isGivenKind(T_USE) && $this->isUseForLambda($tokens, $index)) { + $tokens[$index] = new Token([CT::T_USE_LAMBDA, $token->getContent()]); + + return; + } + + // Only search inside class/trait body for `T_USE` for traits. + // Cannot import traits inside interfaces or anywhere else + + $classTypes = [T_TRAIT]; + + if (\defined('T_ENUM')) { // @TODO: drop condition when PHP 8.1+ is required + $classTypes[] = T_ENUM; + } + + if ($token->isGivenKind(T_CLASS)) { + if ($tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_DOUBLE_COLON)) { + return; + } + } elseif (!$token->isGivenKind($classTypes)) { + return; + } + + $index = $tokens->getNextTokenOfKind($index, ['{']); + $innerLimit = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); + + while ($index < $innerLimit) { + $token = $tokens[++$index]; + + if (!$token->isGivenKind(T_USE)) { + continue; + } + + if ($this->isUseForLambda($tokens, $index)) { + $tokens[$index] = new Token([CT::T_USE_LAMBDA, $token->getContent()]); + } else { + $tokens[$index] = new Token([CT::T_USE_TRAIT, $token->getContent()]); + } + } + } + + public function getCustomTokens(): array + { + return [CT::T_USE_TRAIT, CT::T_USE_LAMBDA]; + } + + /** + * Check if token under given index is `use` statement for lambda function. + */ + private function isUseForLambda(Tokens $tokens, int $index): bool + { + $nextToken = $tokens[$tokens->getNextMeaningfulToken($index)]; + + // test `function () use ($foo) {}` case + return $nextToken->equals('('); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/WhitespacyCommentTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/WhitespacyCommentTransformer.php new file mode 100644 index 00000000..91ab4cf2 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/WhitespacyCommentTransformer.php @@ -0,0 +1,64 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Transformer; + +use PhpCsFixer\Tokenizer\AbstractTransformer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Move trailing whitespaces from comments and docs into following T_WHITESPACE token. + * + * @author Dariusz Rumiński + * + * @internal + */ +final class WhitespacyCommentTransformer extends AbstractTransformer +{ + public function getRequiredPhpVersionId(): int + { + return 5_00_00; + } + + public function process(Tokens $tokens, Token $token, int $index): void + { + if (!$token->isComment()) { + return; + } + + $content = $token->getContent(); + $trimmedContent = rtrim($content); + + // nothing trimmed, nothing to do + if ($content === $trimmedContent) { + return; + } + + $whitespaces = substr($content, \strlen($trimmedContent)); + + $tokens[$index] = new Token([$token->getId(), $trimmedContent]); + + if (isset($tokens[$index + 1]) && $tokens[$index + 1]->isWhitespace()) { + $tokens[$index + 1] = new Token([T_WHITESPACE, $whitespaces.$tokens[$index + 1]->getContent()]); + } else { + $tokens->insertAt($index + 1, new Token([T_WHITESPACE, $whitespaces])); + } + } + + public function getCustomTokens(): array + { + return []; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/TransformerInterface.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/TransformerInterface.php new file mode 100644 index 00000000..9d7f8600 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/TransformerInterface.php @@ -0,0 +1,68 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer; + +/** + * Interface for Transformer class. + * + * Transformer role is to register custom tokens and transform Tokens collection to use them. + * + * Custom token is a user defined token type and is used to separate different meaning of original token type. + * For example T_ARRAY is a token for both creating new array and typehinting a parameter. This two meaning should have two token types. + * + * @author Dariusz Rumiński + * + * @internal + */ +interface TransformerInterface +{ + /** + * Get tokens created by Transformer. + * + * @return list + */ + public function getCustomTokens(): array; + + /** + * Return the name of the transformer. + * + * The name must be all lowercase and without any spaces. + * + * @return string The name of the fixer + */ + public function getName(): string; + + /** + * Returns the priority of the transformer. + * + * The default priority is 0 and higher priorities are executed first. + */ + public function getPriority(): int; + + /** + * Return minimal required PHP version id to transform the code. + * + * Custom Token kinds from Transformers are always registered, but sometimes + * there is no need to analyse the Tokens if for sure we cannot find examined + * token kind, e.g. transforming `T_FUNCTION` in ` + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer; + +use Symfony\Component\Finder\Finder; +use Symfony\Component\Finder\SplFileInfo; + +/** + * Collection of Transformer classes. + * + * @author Dariusz Rumiński + * + * @internal + */ +final class Transformers +{ + /** + * The registered transformers. + * + * @var list + */ + private array $items = []; + + /** + * Register built in Transformers. + */ + private function __construct() + { + $this->registerBuiltInTransformers(); + + usort($this->items, static fn (TransformerInterface $a, TransformerInterface $b): int => $b->getPriority() <=> $a->getPriority()); + } + + public static function createSingleton(): self + { + static $instance = null; + + if (!$instance) { + $instance = new self(); + } + + return $instance; + } + + /** + * Transform given Tokens collection through all Transformer classes. + * + * @param Tokens $tokens Tokens collection + */ + public function transform(Tokens $tokens): void + { + foreach ($this->items as $transformer) { + foreach ($tokens as $index => $token) { + $transformer->process($tokens, $token, $index); + } + } + } + + /** + * @param TransformerInterface $transformer Transformer + */ + private function registerTransformer(TransformerInterface $transformer): void + { + if (\PHP_VERSION_ID >= $transformer->getRequiredPhpVersionId()) { + $this->items[] = $transformer; + } + } + + private function registerBuiltInTransformers(): void + { + static $registered = false; + + if ($registered) { + return; + } + + $registered = true; + + foreach ($this->findBuiltInTransformers() as $transformer) { + $this->registerTransformer($transformer); + } + } + + /** + * @return \Generator + */ + private function findBuiltInTransformers(): iterable + { + /** @var SplFileInfo $file */ + foreach (Finder::create()->files()->in(__DIR__.'/Transformer') as $file) { + $relativeNamespace = $file->getRelativePath(); + $class = __NAMESPACE__.'\\Transformer\\'.('' !== $relativeNamespace ? $relativeNamespace.'\\' : '').$file->getBasename('.php'); + + yield new $class(); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/ToolInfo.php b/vendor/friendsofphp/php-cs-fixer/src/ToolInfo.php new file mode 100644 index 00000000..fe5bdcea --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/ToolInfo.php @@ -0,0 +1,122 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer; + +use PhpCsFixer\Console\Application; + +/** + * Obtain information about using version of tool. + * + * @author Dariusz Rumiński + * + * @internal + */ +final class ToolInfo implements ToolInfoInterface +{ + public const COMPOSER_PACKAGE_NAME = 'friendsofphp/php-cs-fixer'; + + public const COMPOSER_LEGACY_PACKAGE_NAME = 'fabpot/php-cs-fixer'; + + /** + * @var null|array{name: string, version: string, dist: array{reference?: string}} + */ + private $composerInstallationDetails; + + /** + * @var null|bool + */ + private $isInstalledByComposer; + + public function getComposerInstallationDetails(): array + { + if (!$this->isInstalledByComposer()) { + throw new \LogicException('Cannot get composer version for tool not installed by composer.'); + } + + if (null === $this->composerInstallationDetails) { + $composerInstalled = json_decode(file_get_contents($this->getComposerInstalledFile()), true, 512, JSON_THROW_ON_ERROR); + + $packages = $composerInstalled['packages'] ?? $composerInstalled; + + foreach ($packages as $package) { + if (\in_array($package['name'], [self::COMPOSER_PACKAGE_NAME, self::COMPOSER_LEGACY_PACKAGE_NAME], true)) { + $this->composerInstallationDetails = $package; + + break; + } + } + } + + return $this->composerInstallationDetails; + } + + public function getComposerVersion(): string + { + $package = $this->getComposerInstallationDetails(); + + $versionSuffix = ''; + + if (isset($package['dist']['reference'])) { + $versionSuffix = '#'.$package['dist']['reference']; + } + + return $package['version'].$versionSuffix; + } + + public function getVersion(): string + { + if ($this->isInstalledByComposer()) { + return Application::VERSION.':'.$this->getComposerVersion(); + } + + return Application::VERSION; + } + + public function isInstalledAsPhar(): bool + { + return str_starts_with(__DIR__, 'phar://'); + } + + public function isInstalledByComposer(): bool + { + if (null === $this->isInstalledByComposer) { + $this->isInstalledByComposer = !$this->isInstalledAsPhar() && file_exists($this->getComposerInstalledFile()); + } + + return $this->isInstalledByComposer; + } + + /** + * Determines if the tool is run inside our pre-built Docker image. + * The `/fixer/` path comes from our Dockerfile, tool is installed there and added to global PATH via symlinked binary. + */ + public function isRunInsideDocker(): bool + { + return is_file('/.dockerenv') && str_starts_with(__FILE__, '/fixer/'); + } + + public function getPharDownloadUri(string $version): string + { + return sprintf( + 'https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/releases/download/%s/php-cs-fixer.phar', + $version + ); + } + + private function getComposerInstalledFile(): string + { + return __DIR__.'/../../../composer/installed.json'; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/ToolInfoInterface.php b/vendor/friendsofphp/php-cs-fixer/src/ToolInfoInterface.php new file mode 100644 index 00000000..bc2a5e0b --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/ToolInfoInterface.php @@ -0,0 +1,38 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer; + +/** + * @internal + */ +interface ToolInfoInterface +{ + /** + * @return array{name: string, version: string, dist: array{reference?: string}} + */ + public function getComposerInstallationDetails(): array; + + public function getComposerVersion(): string; + + public function getVersion(): string; + + public function isInstalledAsPhar(): bool; + + public function isInstalledByComposer(): bool; + + public function isRunInsideDocker(): bool; + + public function getPharDownloadUri(string $version): string; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Utils.php b/vendor/friendsofphp/php-cs-fixer/src/Utils.php new file mode 100644 index 00000000..21e52fd7 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Utils.php @@ -0,0 +1,242 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer; + +use PhpCsFixer\Fixer\FixerInterface; +use PhpCsFixer\Tokenizer\Token; + +/** + * @author Dariusz Rumiński + * @author Graham Campbell + * @author Odín del Río + * + * @internal + * + * @deprecated This is a God Class anti-pattern. Don't expand it. It is fine to use logic that is already here (that's why we don't trigger deprecation warnings), but over time logic should be moved to dedicated, single-responsibility classes. + */ +final class Utils +{ + /** + * @var array + */ + private static array $deprecations = []; + + private function __construct() + { + // cannot create instance of util. class + } + + /** + * Converts a camel cased string to a snake cased string. + */ + public static function camelCaseToUnderscore(string $string): string + { + return mb_strtolower(Preg::replace('/(?isWhitespace()) { + throw new \InvalidArgumentException(sprintf('The given token must be whitespace, got "%s".', $token->getName())); + } + + $str = strrchr( + str_replace(["\r\n", "\r"], "\n", $token->getContent()), + "\n" + ); + + if (false === $str) { + return ''; + } + + return ltrim($str, "\n"); + } + + /** + * Perform stable sorting using provided comparison function. + * + * Stability is ensured by using Schwartzian transform. + * + * @template T + * @template R + * + * @param list $elements + * @param callable(T): R $getComparedValue a callable that takes a single element and returns the value to compare + * @param callable(R, R): int $compareValues a callable that compares two values + * + * @return list + */ + public static function stableSort(array $elements, callable $getComparedValue, callable $compareValues): array + { + array_walk($elements, static function (&$element, int $index) use ($getComparedValue): void { + $element = [$element, $index, $getComparedValue($element)]; + }); + + usort($elements, static function ($a, $b) use ($compareValues): int { + $comparison = $compareValues($a[2], $b[2]); + + if (0 !== $comparison) { + return $comparison; + } + + return $a[1] <=> $b[1]; + }); + + return array_map(static fn (array $item) => $item[0], $elements); + } + + /** + * Sort fixers by their priorities. + * + * @param list $fixers + * + * @return list + */ + public static function sortFixers(array $fixers): array + { + // Schwartzian transform is used to improve the efficiency and avoid + // `usort(): Array was modified by the user comparison function` warning for mocked objects. + return self::stableSort( + $fixers, + static fn (FixerInterface $fixer): int => $fixer->getPriority(), + static fn (int $a, int $b): int => $b <=> $a + ); + } + + /** + * Join names in natural language using specified wrapper (double quote by default). + * + * @param string[] $names + * + * @throws \InvalidArgumentException + */ + public static function naturalLanguageJoin(array $names, string $wrapper = '"'): string + { + if (0 === \count($names)) { + throw new \InvalidArgumentException('Array of names cannot be empty.'); + } + + if (\strlen($wrapper) > 1) { + throw new \InvalidArgumentException('Wrapper should be a single-char string or empty.'); + } + + $names = array_map(static fn (string $name): string => sprintf('%2$s%1$s%2$s', $name, $wrapper), $names); + + $last = array_pop($names); + + if (\count($names) > 0) { + return implode(', ', $names).' and '.$last; + } + + return $last; + } + + /** + * Join names in natural language wrapped in backticks, e.g. `a`, `b` and `c`. + * + * @param string[] $names + * + * @throws \InvalidArgumentException + */ + public static function naturalLanguageJoinWithBackticks(array $names): string + { + return self::naturalLanguageJoin($names, '`'); + } + + public static function isFutureModeEnabled(): bool + { + return filter_var( + getenv('PHP_CS_FIXER_FUTURE_MODE'), + FILTER_VALIDATE_BOOL + ); + } + + public static function triggerDeprecation(\Exception $futureException): void + { + if (self::isFutureModeEnabled()) { + throw new \RuntimeException( + 'Your are using something deprecated, see previous exception. Aborting execution because `PHP_CS_FIXER_FUTURE_MODE` environment variable is set.', + 0, + $futureException + ); + } + + $message = $futureException->getMessage(); + + self::$deprecations[$message] = true; + @trigger_error($message, E_USER_DEPRECATED); + } + + /** + * @return list + */ + public static function getTriggeredDeprecations(): array + { + $triggeredDeprecations = array_keys(self::$deprecations); + sort($triggeredDeprecations); + + return $triggeredDeprecations; + } + + /** + * @param mixed $value + */ + public static function toString($value): string + { + return \is_array($value) + ? self::arrayToString($value) + : self::scalarToString($value); + } + + /** + * @param mixed $value + */ + private static function scalarToString($value): string + { + $str = var_export($value, true); + + return Preg::replace('/\bNULL\b/', 'null', $str); + } + + /** + * @param array $value + */ + private static function arrayToString(array $value): string + { + if (0 === \count($value)) { + return '[]'; + } + + $isHash = !array_is_list($value); + $str = '['; + + foreach ($value as $k => $v) { + if ($isHash) { + $str .= self::scalarToString($k).' => '; + } + + $str .= \is_array($v) + ? self::arrayToString($v).', ' + : self::scalarToString($v).', '; + } + + return substr($str, 0, -2).']'; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/WhitespacesFixerConfig.php b/vendor/friendsofphp/php-cs-fixer/src/WhitespacesFixerConfig.php new file mode 100644 index 00000000..e558fd6b --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/WhitespacesFixerConfig.php @@ -0,0 +1,49 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer; + +/** + * @author Dariusz Rumiński + */ +final class WhitespacesFixerConfig +{ + private string $indent; + + private string $lineEnding; + + public function __construct(string $indent = ' ', string $lineEnding = "\n") + { + if (!\in_array($indent, [' ', ' ', "\t"], true)) { + throw new \InvalidArgumentException('Invalid "indent" param, expected tab or two or four spaces.'); + } + + if (!\in_array($lineEnding, ["\n", "\r\n"], true)) { + throw new \InvalidArgumentException('Invalid "lineEnding" param, expected "\n" or "\r\n".'); + } + + $this->indent = $indent; + $this->lineEnding = $lineEnding; + } + + public function getIndent(): string + { + return $this->indent; + } + + public function getLineEnding(): string + { + return $this->lineEnding; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/WordMatcher.php b/vendor/friendsofphp/php-cs-fixer/src/WordMatcher.php new file mode 100644 index 00000000..36265a88 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/WordMatcher.php @@ -0,0 +1,53 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class WordMatcher +{ + /** + * @var string[] + */ + private array $candidates; + + /** + * @param string[] $candidates + */ + public function __construct(array $candidates) + { + $this->candidates = $candidates; + } + + public function match(string $needle): ?string + { + $word = null; + $distance = ceil(\strlen($needle) * 0.35); + + foreach ($this->candidates as $candidate) { + $candidateDistance = levenshtein($needle, $candidate); + + if ($candidateDistance < $distance) { + $word = $candidate; + $distance = $candidateDistance; + } + } + + return $word; + } +} diff --git a/vendor/guzzlehttp/guzzle/CHANGELOG.md b/vendor/guzzlehttp/guzzle/CHANGELOG.md new file mode 100644 index 00000000..13709d1b --- /dev/null +++ b/vendor/guzzlehttp/guzzle/CHANGELOG.md @@ -0,0 +1,1633 @@ +# Change Log + +Please refer to [UPGRADING](UPGRADING.md) guide for upgrading to a major version. + + +## 7.8.1 - 2023-12-03 + +### Changed + +- Updated links in docs to their canonical versions +- Replaced `call_user_func*` with native calls + + +## 7.8.0 - 2023-08-27 + +### Added + +- Support for PHP 8.3 +- Added automatic closing of handles on `CurlFactory` object destruction + + +## 7.7.1 - 2023-08-27 + +### Changed + +- Remove the need for `AllowDynamicProperties` in `CurlMultiHandler` + + +## 7.7.0 - 2023-05-21 + +### Added + +- Support `guzzlehttp/promises` v2 + + +## 7.6.1 - 2023-05-15 + +### Fixed + +- Fix `SetCookie::fromString` MaxAge deprecation warning and skip invalid MaxAge values + + +## 7.6.0 - 2023-05-14 + +### Added + +- Support for setting the minimum TLS version in a unified way +- Apply on request the version set in options parameters + + +## 7.5.2 - 2023-05-14 + +### Fixed + +- Fixed set cookie constructor validation +- Fixed handling of files with `'0'` body + +### Changed + +- Corrected docs and default connect timeout value to 300 seconds + + +## 7.5.1 - 2023-04-17 + +### Fixed + +- Fixed `NO_PROXY` settings so that setting the `proxy` option to `no` overrides the env variable + +### Changed + +- Adjusted `guzzlehttp/psr7` version constraint to `^1.9.1 || ^2.4.5` + + +## 7.5.0 - 2022-08-28 + +### Added + +- Support PHP 8.2 +- Add request to delay closure params + + +## 7.4.5 - 2022-06-20 + +### Fixed + +* Fix change in port should be considered a change in origin +* Fix `CURLOPT_HTTPAUTH` option not cleared on change of origin + + +## 7.4.4 - 2022-06-09 + +### Fixed + +* Fix failure to strip Authorization header on HTTP downgrade +* Fix failure to strip the Cookie header on change in host or HTTP downgrade + + +## 7.4.3 - 2022-05-25 + +### Fixed + +* Fix cross-domain cookie leakage + + +## 7.4.2 - 2022-03-20 + +### Fixed + +- Remove curl auth on cross-domain redirects to align with the Authorization HTTP header +- Reject non-HTTP schemes in StreamHandler +- Set a default ssl.peer_name context in StreamHandler to allow `force_ip_resolve` + + +## 7.4.1 - 2021-12-06 + +### Changed + +- Replaced implicit URI to string coercion [#2946](https://github.com/guzzle/guzzle/pull/2946) +- Allow `symfony/deprecation-contracts` version 3 [#2961](https://github.com/guzzle/guzzle/pull/2961) + +### Fixed + +- Only close curl handle if it's done [#2950](https://github.com/guzzle/guzzle/pull/2950) + + +## 7.4.0 - 2021-10-18 + +### Added + +- Support PHP 8.1 [#2929](https://github.com/guzzle/guzzle/pull/2929), [#2939](https://github.com/guzzle/guzzle/pull/2939) +- Support `psr/log` version 2 and 3 [#2943](https://github.com/guzzle/guzzle/pull/2943) + +### Fixed + +- Make sure we always call `restore_error_handler()` [#2915](https://github.com/guzzle/guzzle/pull/2915) +- Fix progress parameter type compatibility between the cURL and stream handlers [#2936](https://github.com/guzzle/guzzle/pull/2936) +- Throw `InvalidArgumentException` when an incorrect `headers` array is provided [#2916](https://github.com/guzzle/guzzle/pull/2916), [#2942](https://github.com/guzzle/guzzle/pull/2942) + +### Changed + +- Be more strict with types [#2914](https://github.com/guzzle/guzzle/pull/2914), [#2917](https://github.com/guzzle/guzzle/pull/2917), [#2919](https://github.com/guzzle/guzzle/pull/2919), [#2945](https://github.com/guzzle/guzzle/pull/2945) + + +## 7.3.0 - 2021-03-23 + +### Added + +- Support for DER and P12 certificates [#2413](https://github.com/guzzle/guzzle/pull/2413) +- Support the cURL (http://) scheme for StreamHandler proxies [#2850](https://github.com/guzzle/guzzle/pull/2850) +- Support for `guzzlehttp/psr7:^2.0` [#2878](https://github.com/guzzle/guzzle/pull/2878) + +### Fixed + +- Handle exceptions on invalid header consistently between PHP versions and handlers [#2872](https://github.com/guzzle/guzzle/pull/2872) + + +## 7.2.0 - 2020-10-10 + +### Added + +- Support for PHP 8 [#2712](https://github.com/guzzle/guzzle/pull/2712), [#2715](https://github.com/guzzle/guzzle/pull/2715), [#2789](https://github.com/guzzle/guzzle/pull/2789) +- Support passing a body summarizer to the http errors middleware [#2795](https://github.com/guzzle/guzzle/pull/2795) + +### Fixed + +- Handle exceptions during response creation [#2591](https://github.com/guzzle/guzzle/pull/2591) +- Fix CURLOPT_ENCODING not to be overwritten [#2595](https://github.com/guzzle/guzzle/pull/2595) +- Make sure the Request always has a body object [#2804](https://github.com/guzzle/guzzle/pull/2804) + +### Changed + +- The `TooManyRedirectsException` has a response [#2660](https://github.com/guzzle/guzzle/pull/2660) +- Avoid "functions" from dependencies [#2712](https://github.com/guzzle/guzzle/pull/2712) + +### Deprecated + +- Using environment variable GUZZLE_CURL_SELECT_TIMEOUT [#2786](https://github.com/guzzle/guzzle/pull/2786) + + +## 7.1.1 - 2020-09-30 + +### Fixed + +- Incorrect EOF detection for response body streams on Windows. + +### Changed + +- We dont connect curl `sink` on HEAD requests. +- Removed some PHP 5 workarounds + + +## 7.1.0 - 2020-09-22 + +### Added + +- `GuzzleHttp\MessageFormatterInterface` + +### Fixed + +- Fixed issue that caused cookies with no value not to be stored. +- On redirects, we allow all safe methods like GET, HEAD and OPTIONS. +- Fixed logging on empty responses. +- Make sure MessageFormatter::format returns string + +### Deprecated + +- All functions in `GuzzleHttp` has been deprecated. Use static methods on `Utils` instead. +- `ClientInterface::getConfig()` +- `Client::getConfig()` +- `Client::__call()` +- `Utils::defaultCaBundle()` +- `CurlFactory::LOW_CURL_VERSION_NUMBER` + + +## 7.0.1 - 2020-06-27 + +* Fix multiply defined functions fatal error [#2699](https://github.com/guzzle/guzzle/pull/2699) + + +## 7.0.0 - 2020-06-27 + +No changes since 7.0.0-rc1. + + +## 7.0.0-rc1 - 2020-06-15 + +### Changed + +* Use error level for logging errors in Middleware [#2629](https://github.com/guzzle/guzzle/pull/2629) +* Disabled IDN support by default and require ext-intl to use it [#2675](https://github.com/guzzle/guzzle/pull/2675) + + +## 7.0.0-beta2 - 2020-05-25 + +### Added + +* Using `Utils` class instead of functions in the `GuzzleHttp` namespace. [#2546](https://github.com/guzzle/guzzle/pull/2546) +* `ClientInterface::MAJOR_VERSION` [#2583](https://github.com/guzzle/guzzle/pull/2583) + +### Changed + +* Avoid the `getenv` function when unsafe [#2531](https://github.com/guzzle/guzzle/pull/2531) +* Added real client methods [#2529](https://github.com/guzzle/guzzle/pull/2529) +* Avoid functions due to global install conflicts [#2546](https://github.com/guzzle/guzzle/pull/2546) +* Use Symfony intl-idn polyfill [#2550](https://github.com/guzzle/guzzle/pull/2550) +* Adding methods for HTTP verbs like `Client::get()`, `Client::head()`, `Client::patch()` etc [#2529](https://github.com/guzzle/guzzle/pull/2529) +* `ConnectException` extends `TransferException` [#2541](https://github.com/guzzle/guzzle/pull/2541) +* Updated the default User Agent to "GuzzleHttp/7" [#2654](https://github.com/guzzle/guzzle/pull/2654) + +### Fixed + +* Various intl icu issues [#2626](https://github.com/guzzle/guzzle/pull/2626) + +### Removed + +* Pool option `pool_size` [#2528](https://github.com/guzzle/guzzle/pull/2528) + + +## 7.0.0-beta1 - 2019-12-30 + +The diff might look very big but 95% of Guzzle users will be able to upgrade without modification. +Please see [the upgrade document](UPGRADING.md) that describes all BC breaking changes. + +### Added + +* Implement PSR-18 and dropped PHP 5 support [#2421](https://github.com/guzzle/guzzle/pull/2421) [#2474](https://github.com/guzzle/guzzle/pull/2474) +* PHP 7 types [#2442](https://github.com/guzzle/guzzle/pull/2442) [#2449](https://github.com/guzzle/guzzle/pull/2449) [#2466](https://github.com/guzzle/guzzle/pull/2466) [#2497](https://github.com/guzzle/guzzle/pull/2497) [#2499](https://github.com/guzzle/guzzle/pull/2499) +* IDN support for redirects [2424](https://github.com/guzzle/guzzle/pull/2424) + +### Changed + +* Dont allow passing null as third argument to `BadResponseException::__construct()` [#2427](https://github.com/guzzle/guzzle/pull/2427) +* Use SAPI constant instead of method call [#2450](https://github.com/guzzle/guzzle/pull/2450) +* Use native function invocation [#2444](https://github.com/guzzle/guzzle/pull/2444) +* Better defaults for PHP installations with old ICU lib [2454](https://github.com/guzzle/guzzle/pull/2454) +* Added visibility to all constants [#2462](https://github.com/guzzle/guzzle/pull/2462) +* Dont allow passing `null` as URI to `Client::request()` and `Client::requestAsync()` [#2461](https://github.com/guzzle/guzzle/pull/2461) +* Widen the exception argument to throwable [#2495](https://github.com/guzzle/guzzle/pull/2495) + +### Fixed + +* Logging when Promise rejected with a string [#2311](https://github.com/guzzle/guzzle/pull/2311) + +### Removed + +* Class `SeekException` [#2162](https://github.com/guzzle/guzzle/pull/2162) +* `RequestException::getResponseBodySummary()` [#2425](https://github.com/guzzle/guzzle/pull/2425) +* `CookieJar::getCookieValue()` [#2433](https://github.com/guzzle/guzzle/pull/2433) +* `uri_template()` and `UriTemplate` [#2440](https://github.com/guzzle/guzzle/pull/2440) +* Request options `save_to` and `exceptions` [#2464](https://github.com/guzzle/guzzle/pull/2464) + + +## 6.5.2 - 2019-12-23 + +* idn_to_ascii() fix for old PHP versions [#2489](https://github.com/guzzle/guzzle/pull/2489) + + +## 6.5.1 - 2019-12-21 + +* Better defaults for PHP installations with old ICU lib [#2454](https://github.com/guzzle/guzzle/pull/2454) +* IDN support for redirects [#2424](https://github.com/guzzle/guzzle/pull/2424) + + +## 6.5.0 - 2019-12-07 + +* Improvement: Added support for reset internal queue in MockHandler. [#2143](https://github.com/guzzle/guzzle/pull/2143) +* Improvement: Added support to pass arbitrary options to `curl_multi_init`. [#2287](https://github.com/guzzle/guzzle/pull/2287) +* Fix: Gracefully handle passing `null` to the `header` option. [#2132](https://github.com/guzzle/guzzle/pull/2132) +* Fix: `RetryMiddleware` did not do exponential delay between retires due unit mismatch. [#2132](https://github.com/guzzle/guzzle/pull/2132) +* Fix: Prevent undefined offset when using array for ssl_key options. [#2348](https://github.com/guzzle/guzzle/pull/2348) +* Deprecated `ClientInterface::VERSION` + + +## 6.4.1 - 2019-10-23 + +* No `guzzle.phar` was created in 6.4.0 due expired API token. This release will fix that +* Added `parent::__construct()` to `FileCookieJar` and `SessionCookieJar` + + +## 6.4.0 - 2019-10-23 + +* Improvement: Improved error messages when using curl < 7.21.2 [#2108](https://github.com/guzzle/guzzle/pull/2108) +* Fix: Test if response is readable before returning a summary in `RequestException::getResponseBodySummary()` [#2081](https://github.com/guzzle/guzzle/pull/2081) +* Fix: Add support for GUZZLE_CURL_SELECT_TIMEOUT environment variable [#2161](https://github.com/guzzle/guzzle/pull/2161) +* Improvement: Added `GuzzleHttp\Exception\InvalidArgumentException` [#2163](https://github.com/guzzle/guzzle/pull/2163) +* Improvement: Added `GuzzleHttp\_current_time()` to use `hrtime()` if that function exists. [#2242](https://github.com/guzzle/guzzle/pull/2242) +* Improvement: Added curl's `appconnect_time` in `TransferStats` [#2284](https://github.com/guzzle/guzzle/pull/2284) +* Improvement: Make GuzzleException extend Throwable wherever it's available [#2273](https://github.com/guzzle/guzzle/pull/2273) +* Fix: Prevent concurrent writes to file when saving `CookieJar` [#2335](https://github.com/guzzle/guzzle/pull/2335) +* Improvement: Update `MockHandler` so we can test transfer time [#2362](https://github.com/guzzle/guzzle/pull/2362) + + +## 6.3.3 - 2018-04-22 + +* Fix: Default headers when decode_content is specified + + +## 6.3.2 - 2018-03-26 + +* Fix: Release process + + +## 6.3.1 - 2018-03-26 + +* Bug fix: Parsing 0 epoch expiry times in cookies [#2014](https://github.com/guzzle/guzzle/pull/2014) +* Improvement: Better ConnectException detection [#2012](https://github.com/guzzle/guzzle/pull/2012) +* Bug fix: Malformed domain that contains a "/" [#1999](https://github.com/guzzle/guzzle/pull/1999) +* Bug fix: Undefined offset when a cookie has no first key-value pair [#1998](https://github.com/guzzle/guzzle/pull/1998) +* Improvement: Support PHPUnit 6 [#1953](https://github.com/guzzle/guzzle/pull/1953) +* Bug fix: Support empty headers [#1915](https://github.com/guzzle/guzzle/pull/1915) +* Bug fix: Ignore case during header modifications [#1916](https://github.com/guzzle/guzzle/pull/1916) + ++ Minor code cleanups, documentation fixes and clarifications. + + +## 6.3.0 - 2017-06-22 + +* Feature: force IP resolution (ipv4 or ipv6) [#1608](https://github.com/guzzle/guzzle/pull/1608), [#1659](https://github.com/guzzle/guzzle/pull/1659) +* Improvement: Don't include summary in exception message when body is empty [#1621](https://github.com/guzzle/guzzle/pull/1621) +* Improvement: Handle `on_headers` option in MockHandler [#1580](https://github.com/guzzle/guzzle/pull/1580) +* Improvement: Added SUSE Linux CA path [#1609](https://github.com/guzzle/guzzle/issues/1609) +* Improvement: Use class reference for getting the name of the class instead of using hardcoded strings [#1641](https://github.com/guzzle/guzzle/pull/1641) +* Feature: Added `read_timeout` option [#1611](https://github.com/guzzle/guzzle/pull/1611) +* Bug fix: PHP 7.x fixes [#1685](https://github.com/guzzle/guzzle/pull/1685), [#1686](https://github.com/guzzle/guzzle/pull/1686), [#1811](https://github.com/guzzle/guzzle/pull/1811) +* Deprecation: BadResponseException instantiation without a response [#1642](https://github.com/guzzle/guzzle/pull/1642) +* Feature: Added NTLM auth [#1569](https://github.com/guzzle/guzzle/pull/1569) +* Feature: Track redirect HTTP status codes [#1711](https://github.com/guzzle/guzzle/pull/1711) +* Improvement: Check handler type during construction [#1745](https://github.com/guzzle/guzzle/pull/1745) +* Improvement: Always include the Content-Length if there's a body [#1721](https://github.com/guzzle/guzzle/pull/1721) +* Feature: Added convenience method to access a cookie by name [#1318](https://github.com/guzzle/guzzle/pull/1318) +* Bug fix: Fill `CURLOPT_CAPATH` and `CURLOPT_CAINFO` properly [#1684](https://github.com/guzzle/guzzle/pull/1684) +* Improvement: Use `\GuzzleHttp\Promise\rejection_for` function instead of object init [#1827](https://github.com/guzzle/guzzle/pull/1827) + ++ Minor code cleanups, documentation fixes and clarifications. + + +## 6.2.3 - 2017-02-28 + +* Fix deprecations with guzzle/psr7 version 1.4 + + +## 6.2.2 - 2016-10-08 + +* Allow to pass nullable Response to delay callable +* Only add scheme when host is present +* Fix drain case where content-length is the literal string zero +* Obfuscate in-URL credentials in exceptions + + +## 6.2.1 - 2016-07-18 + +* Address HTTP_PROXY security vulnerability, CVE-2016-5385: + https://httpoxy.org/ +* Fixing timeout bug with StreamHandler: + https://github.com/guzzle/guzzle/pull/1488 +* Only read up to `Content-Length` in PHP StreamHandler to avoid timeouts when + a server does not honor `Connection: close`. +* Ignore URI fragment when sending requests. + + +## 6.2.0 - 2016-03-21 + +* Feature: added `GuzzleHttp\json_encode` and `GuzzleHttp\json_decode`. + https://github.com/guzzle/guzzle/pull/1389 +* Bug fix: Fix sleep calculation when waiting for delayed requests. + https://github.com/guzzle/guzzle/pull/1324 +* Feature: More flexible history containers. + https://github.com/guzzle/guzzle/pull/1373 +* Bug fix: defer sink stream opening in StreamHandler. + https://github.com/guzzle/guzzle/pull/1377 +* Bug fix: do not attempt to escape cookie values. + https://github.com/guzzle/guzzle/pull/1406 +* Feature: report original content encoding and length on decoded responses. + https://github.com/guzzle/guzzle/pull/1409 +* Bug fix: rewind seekable request bodies before dispatching to cURL. + https://github.com/guzzle/guzzle/pull/1422 +* Bug fix: provide an empty string to `http_build_query` for HHVM workaround. + https://github.com/guzzle/guzzle/pull/1367 + + +## 6.1.1 - 2015-11-22 + +* Bug fix: Proxy::wrapSync() now correctly proxies to the appropriate handler + https://github.com/guzzle/guzzle/commit/911bcbc8b434adce64e223a6d1d14e9a8f63e4e4 +* Feature: HandlerStack is now more generic. + https://github.com/guzzle/guzzle/commit/f2102941331cda544745eedd97fc8fd46e1ee33e +* Bug fix: setting verify to false in the StreamHandler now disables peer + verification. https://github.com/guzzle/guzzle/issues/1256 +* Feature: Middleware now uses an exception factory, including more error + context. https://github.com/guzzle/guzzle/pull/1282 +* Feature: better support for disabled functions. + https://github.com/guzzle/guzzle/pull/1287 +* Bug fix: fixed regression where MockHandler was not using `sink`. + https://github.com/guzzle/guzzle/pull/1292 + + +## 6.1.0 - 2015-09-08 + +* Feature: Added the `on_stats` request option to provide access to transfer + statistics for requests. https://github.com/guzzle/guzzle/pull/1202 +* Feature: Added the ability to persist session cookies in CookieJars. + https://github.com/guzzle/guzzle/pull/1195 +* Feature: Some compatibility updates for Google APP Engine + https://github.com/guzzle/guzzle/pull/1216 +* Feature: Added support for NO_PROXY to prevent the use of a proxy based on + a simple set of rules. https://github.com/guzzle/guzzle/pull/1197 +* Feature: Cookies can now contain square brackets. + https://github.com/guzzle/guzzle/pull/1237 +* Bug fix: Now correctly parsing `=` inside of quotes in Cookies. + https://github.com/guzzle/guzzle/pull/1232 +* Bug fix: Cusotm cURL options now correctly override curl options of the + same name. https://github.com/guzzle/guzzle/pull/1221 +* Bug fix: Content-Type header is now added when using an explicitly provided + multipart body. https://github.com/guzzle/guzzle/pull/1218 +* Bug fix: Now ignoring Set-Cookie headers that have no name. +* Bug fix: Reason phrase is no longer cast to an int in some cases in the + cURL handler. https://github.com/guzzle/guzzle/pull/1187 +* Bug fix: Remove the Authorization header when redirecting if the Host + header changes. https://github.com/guzzle/guzzle/pull/1207 +* Bug fix: Cookie path matching fixes + https://github.com/guzzle/guzzle/issues/1129 +* Bug fix: Fixing the cURL `body_as_string` setting + https://github.com/guzzle/guzzle/pull/1201 +* Bug fix: quotes are no longer stripped when parsing cookies. + https://github.com/guzzle/guzzle/issues/1172 +* Bug fix: `form_params` and `query` now always uses the `&` separator. + https://github.com/guzzle/guzzle/pull/1163 +* Bug fix: Adding a Content-Length to PHP stream wrapper requests if not set. + https://github.com/guzzle/guzzle/pull/1189 + + +## 6.0.2 - 2015-07-04 + +* Fixed a memory leak in the curl handlers in which references to callbacks + were not being removed by `curl_reset`. +* Cookies are now extracted properly before redirects. +* Cookies now allow more character ranges. +* Decoded Content-Encoding responses are now modified to correctly reflect + their state if the encoding was automatically removed by a handler. This + means that the `Content-Encoding` header may be removed an the + `Content-Length` modified to reflect the message size after removing the + encoding. +* Added a more explicit error message when trying to use `form_params` and + `multipart` in the same request. +* Several fixes for HHVM support. +* Functions are now conditionally required using an additional level of + indirection to help with global Composer installations. + + +## 6.0.1 - 2015-05-27 + +* Fixed a bug with serializing the `query` request option where the `&` + separator was missing. +* Added a better error message for when `body` is provided as an array. Please + use `form_params` or `multipart` instead. +* Various doc fixes. + + +## 6.0.0 - 2015-05-26 + +* See the UPGRADING.md document for more information. +* Added `multipart` and `form_params` request options. +* Added `synchronous` request option. +* Added the `on_headers` request option. +* Fixed `expect` handling. +* No longer adding default middlewares in the client ctor. These need to be + present on the provided handler in order to work. +* Requests are no longer initiated when sending async requests with the + CurlMultiHandler. This prevents unexpected recursion from requests completing + while ticking the cURL loop. +* Removed the semantics of setting `default` to `true`. This is no longer + required now that the cURL loop is not ticked for async requests. +* Added request and response logging middleware. +* No longer allowing self signed certificates when using the StreamHandler. +* Ensuring that `sink` is valid if saving to a file. +* Request exceptions now include a "handler context" which provides handler + specific contextual information. +* Added `GuzzleHttp\RequestOptions` to allow request options to be applied + using constants. +* `$maxHandles` has been removed from CurlMultiHandler. +* `MultipartPostBody` is now part of the `guzzlehttp/psr7` package. + + +## 5.3.0 - 2015-05-19 + +* Mock now supports `save_to` +* Marked `AbstractRequestEvent::getTransaction()` as public. +* Fixed a bug in which multiple headers using different casing would overwrite + previous headers in the associative array. +* Added `Utils::getDefaultHandler()` +* Marked `GuzzleHttp\Client::getDefaultUserAgent` as deprecated. +* URL scheme is now always lowercased. + + +## 6.0.0-beta.1 + +* Requires PHP >= 5.5 +* Updated to use PSR-7 + * Requires immutable messages, which basically means an event based system + owned by a request instance is no longer possible. + * Utilizing the [Guzzle PSR-7 package](https://github.com/guzzle/psr7). + * Removed the dependency on `guzzlehttp/streams`. These stream abstractions + are available in the `guzzlehttp/psr7` package under the `GuzzleHttp\Psr7` + namespace. +* Added middleware and handler system + * Replaced the Guzzle event and subscriber system with a middleware system. + * No longer depends on RingPHP, but rather places the HTTP handlers directly + in Guzzle, operating on PSR-7 messages. + * Retry logic is now encapsulated in `GuzzleHttp\Middleware::retry`, which + means the `guzzlehttp/retry-subscriber` is now obsolete. + * Mocking responses is now handled using `GuzzleHttp\Handler\MockHandler`. +* Asynchronous responses + * No longer supports the `future` request option to send an async request. + Instead, use one of the `*Async` methods of a client (e.g., `requestAsync`, + `getAsync`, etc.). + * Utilizing `GuzzleHttp\Promise` instead of React's promise library to avoid + recursion required by chaining and forwarding react promises. See + https://github.com/guzzle/promises + * Added `requestAsync` and `sendAsync` to send request asynchronously. + * Added magic methods for `getAsync()`, `postAsync()`, etc. to send requests + asynchronously. +* Request options + * POST and form updates + * Added the `form_fields` and `form_files` request options. + * Removed the `GuzzleHttp\Post` namespace. + * The `body` request option no longer accepts an array for POST requests. + * The `exceptions` request option has been deprecated in favor of the + `http_errors` request options. + * The `save_to` request option has been deprecated in favor of `sink` request + option. +* Clients no longer accept an array of URI template string and variables for + URI variables. You will need to expand URI templates before passing them + into a client constructor or request method. +* Client methods `get()`, `post()`, `put()`, `patch()`, `options()`, etc. are + now magic methods that will send synchronous requests. +* Replaced `Utils.php` with plain functions in `functions.php`. +* Removed `GuzzleHttp\Collection`. +* Removed `GuzzleHttp\BatchResults`. Batched pool results are now returned as + an array. +* Removed `GuzzleHttp\Query`. Query string handling is now handled using an + associative array passed into the `query` request option. The query string + is serialized using PHP's `http_build_query`. If you need more control, you + can pass the query string in as a string. +* `GuzzleHttp\QueryParser` has been replaced with the + `GuzzleHttp\Psr7\parse_query`. + + +## 5.2.0 - 2015-01-27 + +* Added `AppliesHeadersInterface` to make applying headers to a request based + on the body more generic and not specific to `PostBodyInterface`. +* Reduced the number of stack frames needed to send requests. +* Nested futures are now resolved in the client rather than the RequestFsm +* Finishing state transitions is now handled in the RequestFsm rather than the + RingBridge. +* Added a guard in the Pool class to not use recursion for request retries. + + +## 5.1.0 - 2014-12-19 + +* Pool class no longer uses recursion when a request is intercepted. +* The size of a Pool can now be dynamically adjusted using a callback. + See https://github.com/guzzle/guzzle/pull/943. +* Setting a request option to `null` when creating a request with a client will + ensure that the option is not set. This allows you to overwrite default + request options on a per-request basis. + See https://github.com/guzzle/guzzle/pull/937. +* Added the ability to limit which protocols are allowed for redirects by + specifying a `protocols` array in the `allow_redirects` request option. +* Nested futures due to retries are now resolved when waiting for synchronous + responses. See https://github.com/guzzle/guzzle/pull/947. +* `"0"` is now an allowed URI path. See + https://github.com/guzzle/guzzle/pull/935. +* `Query` no longer typehints on the `$query` argument in the constructor, + allowing for strings and arrays. +* Exceptions thrown in the `end` event are now correctly wrapped with Guzzle + specific exceptions if necessary. + + +## 5.0.3 - 2014-11-03 + +This change updates query strings so that they are treated as un-encoded values +by default where the value represents an un-encoded value to send over the +wire. A Query object then encodes the value before sending over the wire. This +means that even value query string values (e.g., ":") are url encoded. This +makes the Query class match PHP's http_build_query function. However, if you +want to send requests over the wire using valid query string characters that do +not need to be encoded, then you can provide a string to Url::setQuery() and +pass true as the second argument to specify that the query string is a raw +string that should not be parsed or encoded (unless a call to getQuery() is +subsequently made, forcing the query-string to be converted into a Query +object). + + +## 5.0.2 - 2014-10-30 + +* Added a trailing `\r\n` to multipart/form-data payloads. See + https://github.com/guzzle/guzzle/pull/871 +* Added a `GuzzleHttp\Pool::send()` convenience method to match the docs. +* Status codes are now returned as integers. See + https://github.com/guzzle/guzzle/issues/881 +* No longer overwriting an existing `application/x-www-form-urlencoded` header + when sending POST requests, allowing for customized headers. See + https://github.com/guzzle/guzzle/issues/877 +* Improved path URL serialization. + + * No longer double percent-encoding characters in the path or query string if + they are already encoded. + * Now properly encoding the supplied path to a URL object, instead of only + encoding ' ' and '?'. + * Note: This has been changed in 5.0.3 to now encode query string values by + default unless the `rawString` argument is provided when setting the query + string on a URL: Now allowing many more characters to be present in the + query string without being percent encoded. See + https://datatracker.ietf.org/doc/html/rfc3986#appendix-A + + +## 5.0.1 - 2014-10-16 + +Bugfix release. + +* Fixed an issue where connection errors still returned response object in + error and end events event though the response is unusable. This has been + corrected so that a response is not returned in the `getResponse` method of + these events if the response did not complete. https://github.com/guzzle/guzzle/issues/867 +* Fixed an issue where transfer statistics were not being populated in the + RingBridge. https://github.com/guzzle/guzzle/issues/866 + + +## 5.0.0 - 2014-10-12 + +Adding support for non-blocking responses and some minor API cleanup. + +### New Features + +* Added support for non-blocking responses based on `guzzlehttp/guzzle-ring`. +* Added a public API for creating a default HTTP adapter. +* Updated the redirect plugin to be non-blocking so that redirects are sent + concurrently. Other plugins like this can now be updated to be non-blocking. +* Added a "progress" event so that you can get upload and download progress + events. +* Added `GuzzleHttp\Pool` which implements FutureInterface and transfers + requests concurrently using a capped pool size as efficiently as possible. +* Added `hasListeners()` to EmitterInterface. +* Removed `GuzzleHttp\ClientInterface::sendAll` and marked + `GuzzleHttp\Client::sendAll` as deprecated (it's still there, just not the + recommended way). + +### Breaking changes + +The breaking changes in this release are relatively minor. The biggest thing to +look out for is that request and response objects no longer implement fluent +interfaces. + +* Removed the fluent interfaces (i.e., `return $this`) from requests, + responses, `GuzzleHttp\Collection`, `GuzzleHttp\Url`, + `GuzzleHttp\Query`, `GuzzleHttp\Post\PostBody`, and + `GuzzleHttp\Cookie\SetCookie`. This blog post provides a good outline of + why I did this: https://ocramius.github.io/blog/fluent-interfaces-are-evil/. + This also makes the Guzzle message interfaces compatible with the current + PSR-7 message proposal. +* Removed "functions.php", so that Guzzle is truly PSR-4 compliant. Except + for the HTTP request functions from function.php, these functions are now + implemented in `GuzzleHttp\Utils` using camelCase. `GuzzleHttp\json_decode` + moved to `GuzzleHttp\Utils::jsonDecode`. `GuzzleHttp\get_path` moved to + `GuzzleHttp\Utils::getPath`. `GuzzleHttp\set_path` moved to + `GuzzleHttp\Utils::setPath`. `GuzzleHttp\batch` should now be + `GuzzleHttp\Pool::batch`, which returns an `objectStorage`. Using functions.php + caused problems for many users: they aren't PSR-4 compliant, require an + explicit include, and needed an if-guard to ensure that the functions are not + declared multiple times. +* Rewrote adapter layer. + * Removing all classes from `GuzzleHttp\Adapter`, these are now + implemented as callables that are stored in `GuzzleHttp\Ring\Client`. + * Removed the concept of "parallel adapters". Sending requests serially or + concurrently is now handled using a single adapter. + * Moved `GuzzleHttp\Adapter\Transaction` to `GuzzleHttp\Transaction`. The + Transaction object now exposes the request, response, and client as public + properties. The getters and setters have been removed. +* Removed the "headers" event. This event was only useful for changing the + body a response once the headers of the response were known. You can implement + a similar behavior in a number of ways. One example might be to use a + FnStream that has access to the transaction being sent. For example, when the + first byte is written, you could check if the response headers match your + expectations, and if so, change the actual stream body that is being + written to. +* Removed the `asArray` parameter from + `GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header + value as an array, then use the newly added `getHeaderAsArray()` method of + `MessageInterface`. This change makes the Guzzle interfaces compatible with + the PSR-7 interfaces. +* `GuzzleHttp\Message\MessageFactory` no longer allows subclasses to add + custom request options using double-dispatch (this was an implementation + detail). Instead, you should now provide an associative array to the + constructor which is a mapping of the request option name mapping to a + function that applies the option value to a request. +* Removed the concept of "throwImmediately" from exceptions and error events. + This control mechanism was used to stop a transfer of concurrent requests + from completing. This can now be handled by throwing the exception or by + cancelling a pool of requests or each outstanding future request individually. +* Updated to "GuzzleHttp\Streams" 3.0. + * `GuzzleHttp\Stream\StreamInterface::getContents()` no longer accepts a + `maxLen` parameter. This update makes the Guzzle streams project + compatible with the current PSR-7 proposal. + * `GuzzleHttp\Stream\Stream::__construct`, + `GuzzleHttp\Stream\Stream::factory`, and + `GuzzleHttp\Stream\Utils::create` no longer accept a size in the second + argument. They now accept an associative array of options, including the + "size" key and "metadata" key which can be used to provide custom metadata. + + +## 4.2.2 - 2014-09-08 + +* Fixed a memory leak in the CurlAdapter when reusing cURL handles. +* No longer using `request_fulluri` in stream adapter proxies. +* Relative redirects are now based on the last response, not the first response. + +## 4.2.1 - 2014-08-19 + +* Ensuring that the StreamAdapter does not always add a Content-Type header +* Adding automated github releases with a phar and zip + +## 4.2.0 - 2014-08-17 + +* Now merging in default options using a case-insensitive comparison. + Closes https://github.com/guzzle/guzzle/issues/767 +* Added the ability to automatically decode `Content-Encoding` response bodies + using the `decode_content` request option. This is set to `true` by default + to decode the response body if it comes over the wire with a + `Content-Encoding`. Set this value to `false` to disable decoding the + response content, and pass a string to provide a request `Accept-Encoding` + header and turn on automatic response decoding. This feature now allows you + to pass an `Accept-Encoding` header in the headers of a request but still + disable automatic response decoding. + Closes https://github.com/guzzle/guzzle/issues/764 +* Added the ability to throw an exception immediately when transferring + requests in parallel. Closes https://github.com/guzzle/guzzle/issues/760 +* Updating guzzlehttp/streams dependency to ~2.1 +* No longer utilizing the now deprecated namespaced methods from the stream + package. + +## 4.1.8 - 2014-08-14 + +* Fixed an issue in the CurlFactory that caused setting the `stream=false` + request option to throw an exception. + See: https://github.com/guzzle/guzzle/issues/769 +* TransactionIterator now calls rewind on the inner iterator. + See: https://github.com/guzzle/guzzle/pull/765 +* You can now set the `Content-Type` header to `multipart/form-data` + when creating POST requests to force multipart bodies. + See https://github.com/guzzle/guzzle/issues/768 + +## 4.1.7 - 2014-08-07 + +* Fixed an error in the HistoryPlugin that caused the same request and response + to be logged multiple times when an HTTP protocol error occurs. +* Ensuring that cURL does not add a default Content-Type when no Content-Type + has been supplied by the user. This prevents the adapter layer from modifying + the request that is sent over the wire after any listeners may have already + put the request in a desired state (e.g., signed the request). +* Throwing an exception when you attempt to send requests that have the + "stream" set to true in parallel using the MultiAdapter. +* Only calling curl_multi_select when there are active cURL handles. This was + previously changed and caused performance problems on some systems due to PHP + always selecting until the maximum select timeout. +* Fixed a bug where multipart/form-data POST fields were not correctly + aggregated (e.g., values with "&"). + +## 4.1.6 - 2014-08-03 + +* Added helper methods to make it easier to represent messages as strings, + including getting the start line and getting headers as a string. + +## 4.1.5 - 2014-08-02 + +* Automatically retrying cURL "Connection died, retrying a fresh connect" + errors when possible. +* cURL implementation cleanup +* Allowing multiple event subscriber listeners to be registered per event by + passing an array of arrays of listener configuration. + +## 4.1.4 - 2014-07-22 + +* Fixed a bug that caused multi-part POST requests with more than one field to + serialize incorrectly. +* Paths can now be set to "0" +* `ResponseInterface::xml` now accepts a `libxml_options` option and added a + missing default argument that was required when parsing XML response bodies. +* A `save_to` stream is now created lazily, which means that files are not + created on disk unless a request succeeds. + +## 4.1.3 - 2014-07-15 + +* Various fixes to multipart/form-data POST uploads +* Wrapping function.php in an if-statement to ensure Guzzle can be used + globally and in a Composer install +* Fixed an issue with generating and merging in events to an event array +* POST headers are only applied before sending a request to allow you to change + the query aggregator used before uploading +* Added much more robust query string parsing +* Fixed various parsing and normalization issues with URLs +* Fixing an issue where multi-valued headers were not being utilized correctly + in the StreamAdapter + +## 4.1.2 - 2014-06-18 + +* Added support for sending payloads with GET requests + +## 4.1.1 - 2014-06-08 + +* Fixed an issue related to using custom message factory options in subclasses +* Fixed an issue with nested form fields in a multi-part POST +* Fixed an issue with using the `json` request option for POST requests +* Added `ToArrayInterface` to `GuzzleHttp\Cookie\CookieJar` + +## 4.1.0 - 2014-05-27 + +* Added a `json` request option to easily serialize JSON payloads. +* Added a `GuzzleHttp\json_decode()` wrapper to safely parse JSON. +* Added `setPort()` and `getPort()` to `GuzzleHttp\Message\RequestInterface`. +* Added the ability to provide an emitter to a client in the client constructor. +* Added the ability to persist a cookie session using $_SESSION. +* Added a trait that can be used to add event listeners to an iterator. +* Removed request method constants from RequestInterface. +* Fixed warning when invalid request start-lines are received. +* Updated MessageFactory to work with custom request option methods. +* Updated cacert bundle to latest build. + +4.0.2 (2014-04-16) +------------------ + +* Proxy requests using the StreamAdapter now properly use request_fulluri (#632) +* Added the ability to set scalars as POST fields (#628) + +## 4.0.1 - 2014-04-04 + +* The HTTP status code of a response is now set as the exception code of + RequestException objects. +* 303 redirects will now correctly switch from POST to GET requests. +* The default parallel adapter of a client now correctly uses the MultiAdapter. +* HasDataTrait now initializes the internal data array as an empty array so + that the toArray() method always returns an array. + +## 4.0.0 - 2014-03-29 + +* For information on changes and upgrading, see: + https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40 +* Added `GuzzleHttp\batch()` as a convenience function for sending requests in + parallel without needing to write asynchronous code. +* Restructured how events are added to `GuzzleHttp\ClientInterface::sendAll()`. + You can now pass a callable or an array of associative arrays where each + associative array contains the "fn", "priority", and "once" keys. + +## 4.0.0.rc-2 - 2014-03-25 + +* Removed `getConfig()` and `setConfig()` from clients to avoid confusion + around whether things like base_url, message_factory, etc. should be able to + be retrieved or modified. +* Added `getDefaultOption()` and `setDefaultOption()` to ClientInterface +* functions.php functions were renamed using snake_case to match PHP idioms +* Added support for `HTTP_PROXY`, `HTTPS_PROXY`, and + `GUZZLE_CURL_SELECT_TIMEOUT` environment variables +* Added the ability to specify custom `sendAll()` event priorities +* Added the ability to specify custom stream context options to the stream + adapter. +* Added a functions.php function for `get_path()` and `set_path()` +* CurlAdapter and MultiAdapter now use a callable to generate curl resources +* MockAdapter now properly reads a body and emits a `headers` event +* Updated Url class to check if a scheme and host are set before adding ":" + and "//". This allows empty Url (e.g., "") to be serialized as "". +* Parsing invalid XML no longer emits warnings +* Curl classes now properly throw AdapterExceptions +* Various performance optimizations +* Streams are created with the faster `Stream\create()` function +* Marked deprecation_proxy() as internal +* Test server is now a collection of static methods on a class + +## 4.0.0-rc.1 - 2014-03-15 + +* See https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40 + +## 3.8.1 - 2014-01-28 + +* Bug: Always using GET requests when redirecting from a 303 response +* Bug: CURLOPT_SSL_VERIFYHOST is now correctly set to false when setting `$certificateAuthority` to false in + `Guzzle\Http\ClientInterface::setSslVerification()` +* Bug: RedirectPlugin now uses strict RFC 3986 compliance when combining a base URL with a relative URL +* Bug: The body of a request can now be set to `"0"` +* Sending PHP stream requests no longer forces `HTTP/1.0` +* Adding more information to ExceptionCollection exceptions so that users have more context, including a stack trace of + each sub-exception +* Updated the `$ref` attribute in service descriptions to merge over any existing parameters of a schema (rather than + clobbering everything). +* Merging URLs will now use the query string object from the relative URL (thus allowing custom query aggregators) +* Query strings are now parsed in a way that they do no convert empty keys with no value to have a dangling `=`. + For example `foo&bar=baz` is now correctly parsed and recognized as `foo&bar=baz` rather than `foo=&bar=baz`. +* Now properly escaping the regular expression delimiter when matching Cookie domains. +* Network access is now disabled when loading XML documents + +## 3.8.0 - 2013-12-05 + +* Added the ability to define a POST name for a file +* JSON response parsing now properly walks additionalProperties +* cURL error code 18 is now retried automatically in the BackoffPlugin +* Fixed a cURL error when URLs contain fragments +* Fixed an issue in the BackoffPlugin retry event where it was trying to access all exceptions as if they were + CurlExceptions +* CURLOPT_PROGRESS function fix for PHP 5.5 (69fcc1e) +* Added the ability for Guzzle to work with older versions of cURL that do not support `CURLOPT_TIMEOUT_MS` +* Fixed a bug that was encountered when parsing empty header parameters +* UriTemplate now has a `setRegex()` method to match the docs +* The `debug` request parameter now checks if it is truthy rather than if it exists +* Setting the `debug` request parameter to true shows verbose cURL output instead of using the LogPlugin +* Added the ability to combine URLs using strict RFC 3986 compliance +* Command objects can now return the validation errors encountered by the command +* Various fixes to cache revalidation (#437 and 29797e5) +* Various fixes to the AsyncPlugin +* Cleaned up build scripts + +## 3.7.4 - 2013-10-02 + +* Bug fix: 0 is now an allowed value in a description parameter that has a default value (#430) +* Bug fix: SchemaFormatter now returns an integer when formatting to a Unix timestamp + (see https://github.com/aws/aws-sdk-php/issues/147) +* Bug fix: Cleaned up and fixed URL dot segment removal to properly resolve internal dots +* Minimum PHP version is now properly specified as 5.3.3 (up from 5.3.2) (#420) +* Updated the bundled cacert.pem (#419) +* OauthPlugin now supports adding authentication to headers or query string (#425) + +## 3.7.3 - 2013-09-08 + +* Added the ability to get the exception associated with a request/command when using `MultiTransferException` and + `CommandTransferException`. +* Setting `additionalParameters` of a response to false is now honored when parsing responses with a service description +* Schemas are only injected into response models when explicitly configured. +* No longer guessing Content-Type based on the path of a request. Content-Type is now only guessed based on the path of + an EntityBody. +* Bug fix: ChunkedIterator can now properly chunk a \Traversable as well as an \Iterator. +* Bug fix: FilterIterator now relies on `\Iterator` instead of `\Traversable`. +* Bug fix: Gracefully handling malformed responses in RequestMediator::writeResponseBody() +* Bug fix: Replaced call to canCache with canCacheRequest in the CallbackCanCacheStrategy of the CachePlugin +* Bug fix: Visiting XML attributes first before visiting XML children when serializing requests +* Bug fix: Properly parsing headers that contain commas contained in quotes +* Bug fix: mimetype guessing based on a filename is now case-insensitive + +## 3.7.2 - 2013-08-02 + +* Bug fix: Properly URL encoding paths when using the PHP-only version of the UriTemplate expander + See https://github.com/guzzle/guzzle/issues/371 +* Bug fix: Cookie domains are now matched correctly according to RFC 6265 + See https://github.com/guzzle/guzzle/issues/377 +* Bug fix: GET parameters are now used when calculating an OAuth signature +* Bug fix: Fixed an issue with cache revalidation where the If-None-Match header was being double quoted +* `Guzzle\Common\AbstractHasDispatcher::dispatch()` now returns the event that was dispatched +* `Guzzle\Http\QueryString::factory()` now guesses the most appropriate query aggregator to used based on the input. + See https://github.com/guzzle/guzzle/issues/379 +* Added a way to add custom domain objects to service description parsing using the `operation.parse_class` event. See + https://github.com/guzzle/guzzle/pull/380 +* cURL multi cleanup and optimizations + +## 3.7.1 - 2013-07-05 + +* Bug fix: Setting default options on a client now works +* Bug fix: Setting options on HEAD requests now works. See #352 +* Bug fix: Moving stream factory before send event to before building the stream. See #353 +* Bug fix: Cookies no longer match on IP addresses per RFC 6265 +* Bug fix: Correctly parsing header parameters that are in `<>` and quotes +* Added `cert` and `ssl_key` as request options +* `Host` header can now diverge from the host part of a URL if the header is set manually +* `Guzzle\Service\Command\LocationVisitor\Request\XmlVisitor` was rewritten to change from using SimpleXML to XMLWriter +* OAuth parameters are only added via the plugin if they aren't already set +* Exceptions are now thrown when a URL cannot be parsed +* Returning `false` if `Guzzle\Http\EntityBody::getContentMd5()` fails +* Not setting a `Content-MD5` on a command if calculating the Content-MD5 fails via the CommandContentMd5Plugin + +## 3.7.0 - 2013-06-10 + +* See UPGRADING.md for more information on how to upgrade. +* Requests now support the ability to specify an array of $options when creating a request to more easily modify a + request. You can pass a 'request.options' configuration setting to a client to apply default request options to + every request created by a client (e.g. default query string variables, headers, curl options, etc.). +* Added a static facade class that allows you to use Guzzle with static methods and mount the class to `\Guzzle`. + See `Guzzle\Http\StaticClient::mount`. +* Added `command.request_options` to `Guzzle\Service\Command\AbstractCommand` to pass request options to requests + created by a command (e.g. custom headers, query string variables, timeout settings, etc.). +* Stream size in `Guzzle\Stream\PhpStreamRequestFactory` will now be set if Content-Length is returned in the + headers of a response +* Added `Guzzle\Common\Collection::setPath($path, $value)` to set a value into an array using a nested key + (e.g. `$collection->setPath('foo/baz/bar', 'test'); echo $collection['foo']['bar']['bar'];`) +* ServiceBuilders now support storing and retrieving arbitrary data +* CachePlugin can now purge all resources for a given URI +* CachePlugin can automatically purge matching cached items when a non-idempotent request is sent to a resource +* CachePlugin now uses the Vary header to determine if a resource is a cache hit +* `Guzzle\Http\Message\Response` now implements `\Serializable` +* Added `Guzzle\Cache\CacheAdapterFactory::fromCache()` to more easily create cache adapters +* `Guzzle\Service\ClientInterface::execute()` now accepts an array, single command, or Traversable +* Fixed a bug in `Guzzle\Http\Message\Header\Link::addLink()` +* Better handling of calculating the size of a stream in `Guzzle\Stream\Stream` using fstat() and caching the size +* `Guzzle\Common\Exception\ExceptionCollection` now creates a more readable exception message +* Fixing BC break: Added back the MonologLogAdapter implementation rather than extending from PsrLog so that older + Symfony users can still use the old version of Monolog. +* Fixing BC break: Added the implementation back in for `Guzzle\Http\Message\AbstractMessage::getTokenizedHeader()`. + Now triggering an E_USER_DEPRECATED warning when used. Use `$message->getHeader()->parseParams()`. +* Several performance improvements to `Guzzle\Common\Collection` +* Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`: + createRequest, head, delete, put, patch, post, options, prepareRequest +* Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()` +* Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface` +* Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to + `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a + resource, string, or EntityBody into the $options parameter to specify the download location of the response. +* Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a + default `array()` +* Added `Guzzle\Stream\StreamInterface::isRepeatable` +* Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use + $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or + $client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))`. +* Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use $client->getConfig()->getPath('request.options/headers')`. +* Removed `Guzzle\Http\ClientInterface::expandTemplate()` +* Removed `Guzzle\Http\ClientInterface::setRequestFactory()` +* Removed `Guzzle\Http\ClientInterface::getCurlMulti()` +* Removed `Guzzle\Http\Message\RequestInterface::canCache` +* Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect` +* Removed `Guzzle\Http\Message\RequestInterface::isRedirect` +* Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods. +* You can now enable E_USER_DEPRECATED warnings to see if you are using a deprecated method by setting + `Guzzle\Common\Version::$emitWarnings` to true. +* Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use + `$request->getResponseBody()->isRepeatable()` instead. +* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use + `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use + `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +* Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead. +* Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead. +* Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated +* Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand. + These will work through Guzzle 4.0 +* Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use [request.options][params]. +* Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client. +* Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use $client->getConfig()->getPath('request.options/headers')`. +* Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. +* Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8. +* Marked `Guzzle\Common\Collection::inject()` as deprecated. +* Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest');` +* CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a + CacheStorageInterface. These two objects and interface will be removed in a future version. +* Always setting X-cache headers on cached responses +* Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin +* `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface + $request, Response $response);` +* `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);` +* `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);` +* Added `CacheStorageInterface::purge($url)` +* `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin + $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache, + CanCacheStrategyInterface $canCache = null)` +* Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)` + +## 3.6.0 - 2013-05-29 + +* ServiceDescription now implements ToArrayInterface +* Added command.hidden_params to blacklist certain headers from being treated as additionalParameters +* Guzzle can now correctly parse incomplete URLs +* Mixed casing of headers are now forced to be a single consistent casing across all values for that header. +* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution +* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader(). +* Specific header implementations can be created for complex headers. When a message creates a header, it uses a + HeaderFactory which can map specific headers to specific header classes. There is now a Link header and + CacheControl header implementation. +* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate +* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti() +* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in + Guzzle\Http\Curl\RequestMediator +* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string. +* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface +* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders() +* Removed Guzzle\Parser\ParserRegister::get(). Use getParser() +* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser(). +* All response header helper functions return a string rather than mixing Header objects and strings inconsistently +* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle + directly via interfaces +* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist + but are a no-op until removed. +* Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a + `Guzzle\Service\Command\ArrayCommandInterface`. +* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response + on a request while the request is still being transferred +* The ability to case-insensitively search for header values +* Guzzle\Http\Message\Header::hasExactHeader +* Guzzle\Http\Message\Header::raw. Use getAll() +* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object + instead. +* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess +* Added the ability to cast Model objects to a string to view debug information. + +## 3.5.0 - 2013-05-13 + +* Bug: Fixed a regression so that request responses are parsed only once per oncomplete event rather than multiple times +* Bug: Better cleanup of one-time events across the board (when an event is meant to fire once, it will now remove + itself from the EventDispatcher) +* Bug: `Guzzle\Log\MessageFormatter` now properly writes "total_time" and "connect_time" values +* Bug: Cloning an EntityEnclosingRequest now clones the EntityBody too +* Bug: Fixed an undefined index error when parsing nested JSON responses with a sentAs parameter that reference a + non-existent key +* Bug: All __call() method arguments are now required (helps with mocking frameworks) +* Deprecating Response::getRequest() and now using a shallow clone of a request object to remove a circular reference + to help with refcount based garbage collection of resources created by sending a request +* Deprecating ZF1 cache and log adapters. These will be removed in the next major version. +* Deprecating `Response::getPreviousResponse()` (method signature still exists, but it's deprecated). Use the + HistoryPlugin for a history. +* Added a `responseBody` alias for the `response_body` location +* Refactored internals to no longer rely on Response::getRequest() +* HistoryPlugin can now be cast to a string +* HistoryPlugin now logs transactions rather than requests and responses to more accurately keep track of the requests + and responses that are sent over the wire +* Added `getEffectiveUrl()` and `getRedirectCount()` to Response objects + +## 3.4.3 - 2013-04-30 + +* Bug fix: Fixing bug introduced in 3.4.2 where redirect responses are duplicated on the final redirected response +* Added a check to re-extract the temp cacert bundle from the phar before sending each request + +## 3.4.2 - 2013-04-29 + +* Bug fix: Stream objects now work correctly with "a" and "a+" modes +* Bug fix: Removing `Transfer-Encoding: chunked` header when a Content-Length is present +* Bug fix: AsyncPlugin no longer forces HEAD requests +* Bug fix: DateTime timezones are now properly handled when using the service description schema formatter +* Bug fix: CachePlugin now properly handles stale-if-error directives when a request to the origin server fails +* Setting a response on a request will write to the custom request body from the response body if one is specified +* LogPlugin now writes to php://output when STDERR is undefined +* Added the ability to set multiple POST files for the same key in a single call +* application/x-www-form-urlencoded POSTs now use the utf-8 charset by default +* Added the ability to queue CurlExceptions to the MockPlugin +* Cleaned up how manual responses are queued on requests (removed "queued_response" and now using request.before_send) +* Configuration loading now allows remote files + +## 3.4.1 - 2013-04-16 + +* Large refactoring to how CurlMulti handles work. There is now a proxy that sits in front of a pool of CurlMulti + handles. This greatly simplifies the implementation, fixes a couple bugs, and provides a small performance boost. +* Exceptions are now properly grouped when sending requests in parallel +* Redirects are now properly aggregated when a multi transaction fails +* Redirects now set the response on the original object even in the event of a failure +* Bug fix: Model names are now properly set even when using $refs +* Added support for PHP 5.5's CurlFile to prevent warnings with the deprecated @ syntax +* Added support for oauth_callback in OAuth signatures +* Added support for oauth_verifier in OAuth signatures +* Added support to attempt to retrieve a command first literally, then ucfirst, the with inflection + +## 3.4.0 - 2013-04-11 + +* Bug fix: URLs are now resolved correctly based on https://datatracker.ietf.org/doc/html/rfc3986#section-5.2. #289 +* Bug fix: Absolute URLs with a path in a service description will now properly override the base URL. #289 +* Bug fix: Parsing a query string with a single PHP array value will now result in an array. #263 +* Bug fix: Better normalization of the User-Agent header to prevent duplicate headers. #264. +* Bug fix: Added `number` type to service descriptions. +* Bug fix: empty parameters are removed from an OAuth signature +* Bug fix: Revalidating a cache entry prefers the Last-Modified over the Date header +* Bug fix: Fixed "array to string" error when validating a union of types in a service description +* Bug fix: Removed code that attempted to determine the size of a stream when data is written to the stream +* Bug fix: Not including an `oauth_token` if the value is null in the OauthPlugin. +* Bug fix: Now correctly aggregating successful requests and failed requests in CurlMulti when a redirect occurs. +* The new default CURLOPT_TIMEOUT setting has been increased to 150 seconds so that Guzzle works on poor connections. +* Added a feature to EntityEnclosingRequest::setBody() that will automatically set the Content-Type of the request if + the Content-Type can be determined based on the entity body or the path of the request. +* Added the ability to overwrite configuration settings in a client when grabbing a throwaway client from a builder. +* Added support for a PSR-3 LogAdapter. +* Added a `command.after_prepare` event +* Added `oauth_callback` parameter to the OauthPlugin +* Added the ability to create a custom stream class when using a stream factory +* Added a CachingEntityBody decorator +* Added support for `additionalParameters` in service descriptions to define how custom parameters are serialized. +* The bundled SSL certificate is now provided in the phar file and extracted when running Guzzle from a phar. +* You can now send any EntityEnclosingRequest with POST fields or POST files and cURL will handle creating bodies +* POST requests using a custom entity body are now treated exactly like PUT requests but with a custom cURL method. This + means that the redirect behavior of POST requests with custom bodies will not be the same as POST requests that use + POST fields or files (the latter is only used when emulating a form POST in the browser). +* Lots of cleanup to CurlHandle::factory and RequestFactory::createRequest + +## 3.3.1 - 2013-03-10 + +* Added the ability to create PHP streaming responses from HTTP requests +* Bug fix: Running any filters when parsing response headers with service descriptions +* Bug fix: OauthPlugin fixes to allow for multi-dimensional array signing, and sorting parameters before signing +* Bug fix: Removed the adding of default empty arrays and false Booleans to responses in order to be consistent across + response location visitors. +* Bug fix: Removed the possibility of creating configuration files with circular dependencies +* RequestFactory::create() now uses the key of a POST file when setting the POST file name +* Added xmlAllowEmpty to serialize an XML body even if no XML specific parameters are set + +## 3.3.0 - 2013-03-03 + +* A large number of performance optimizations have been made +* Bug fix: Added 'wb' as a valid write mode for streams +* Bug fix: `Guzzle\Http\Message\Response::json()` now allows scalar values to be returned +* Bug fix: Fixed bug in `Guzzle\Http\Message\Response` where wrapping quotes were stripped from `getEtag()` +* BC: Removed `Guzzle\Http\Utils` class +* BC: Setting a service description on a client will no longer modify the client's command factories. +* BC: Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using + the 'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io' +* BC: `Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getSteamType()` are no longer converted to + lowercase +* Operation parameter objects are now lazy loaded internally +* Added ErrorResponsePlugin that can throw errors for responses defined in service description operations' errorResponses +* Added support for instantiating responseType=class responseClass classes. Classes must implement + `Guzzle\Service\Command\ResponseClassInterface` +* Added support for additionalProperties for top-level parameters in responseType=model responseClasses. These + additional properties also support locations and can be used to parse JSON responses where the outermost part of the + JSON is an array +* Added support for nested renaming of JSON models (rename sentAs to name) +* CachePlugin + * Added support for stale-if-error so that the CachePlugin can now serve stale content from the cache on error + * Debug headers can now added to cached response in the CachePlugin + +## 3.2.0 - 2013-02-14 + +* CurlMulti is no longer reused globally. A new multi object is created per-client. This helps to isolate clients. +* URLs with no path no longer contain a "/" by default +* Guzzle\Http\QueryString does no longer manages the leading "?". This is now handled in Guzzle\Http\Url. +* BadResponseException no longer includes the full request and response message +* Adding setData() to Guzzle\Service\Description\ServiceDescriptionInterface +* Adding getResponseBody() to Guzzle\Http\Message\RequestInterface +* Various updates to classes to use ServiceDescriptionInterface type hints rather than ServiceDescription +* Header values can now be normalized into distinct values when multiple headers are combined with a comma separated list +* xmlEncoding can now be customized for the XML declaration of a XML service description operation +* Guzzle\Http\QueryString now uses Guzzle\Http\QueryAggregator\QueryAggregatorInterface objects to add custom value + aggregation and no longer uses callbacks +* The URL encoding implementation of Guzzle\Http\QueryString can now be customized +* Bug fix: Filters were not always invoked for array service description parameters +* Bug fix: Redirects now use a target response body rather than a temporary response body +* Bug fix: The default exponential backoff BackoffPlugin was not giving when the request threshold was exceeded +* Bug fix: Guzzle now takes the first found value when grabbing Cache-Control directives + +## 3.1.2 - 2013-01-27 + +* Refactored how operation responses are parsed. Visitors now include a before() method responsible for parsing the + response body. For example, the XmlVisitor now parses the XML response into an array in the before() method. +* Fixed an issue where cURL would not automatically decompress responses when the Accept-Encoding header was sent +* CURLOPT_SSL_VERIFYHOST is never set to 1 because it is deprecated (see 5e0ff2ef20f839e19d1eeb298f90ba3598784444) +* Fixed a bug where redirect responses were not chained correctly using getPreviousResponse() +* Setting default headers on a client after setting the user-agent will not erase the user-agent setting + +## 3.1.1 - 2013-01-20 + +* Adding wildcard support to Guzzle\Common\Collection::getPath() +* Adding alias support to ServiceBuilder configs +* Adding Guzzle\Service\Resource\CompositeResourceIteratorFactory and cleaning up factory interface + +## 3.1.0 - 2013-01-12 + +* BC: CurlException now extends from RequestException rather than BadResponseException +* BC: Renamed Guzzle\Plugin\Cache\CanCacheStrategyInterface::canCache() to canCacheRequest() and added CanCacheResponse() +* Added getData to ServiceDescriptionInterface +* Added context array to RequestInterface::setState() +* Bug: Removing hard dependency on the BackoffPlugin from Guzzle\Http +* Bug: Adding required content-type when JSON request visitor adds JSON to a command +* Bug: Fixing the serialization of a service description with custom data +* Made it easier to deal with exceptions thrown when transferring commands or requests in parallel by providing + an array of successful and failed responses +* Moved getPath from Guzzle\Service\Resource\Model to Guzzle\Common\Collection +* Added Guzzle\Http\IoEmittingEntityBody +* Moved command filtration from validators to location visitors +* Added `extends` attributes to service description parameters +* Added getModels to ServiceDescriptionInterface + +## 3.0.7 - 2012-12-19 + +* Fixing phar detection when forcing a cacert to system if null or true +* Allowing filename to be passed to `Guzzle\Http\Message\Request::setResponseBody()` +* Cleaning up `Guzzle\Common\Collection::inject` method +* Adding a response_body location to service descriptions + +## 3.0.6 - 2012-12-09 + +* CurlMulti performance improvements +* Adding setErrorResponses() to Operation +* composer.json tweaks + +## 3.0.5 - 2012-11-18 + +* Bug: Fixing an infinite recursion bug caused from revalidating with the CachePlugin +* Bug: Response body can now be a string containing "0" +* Bug: Using Guzzle inside of a phar uses system by default but now allows for a custom cacert +* Bug: QueryString::fromString now properly parses query string parameters that contain equal signs +* Added support for XML attributes in service description responses +* DefaultRequestSerializer now supports array URI parameter values for URI template expansion +* Added better mimetype guessing to requests and post files + +## 3.0.4 - 2012-11-11 + +* Bug: Fixed a bug when adding multiple cookies to a request to use the correct glue value +* Bug: Cookies can now be added that have a name, domain, or value set to "0" +* Bug: Using the system cacert bundle when using the Phar +* Added json and xml methods to Response to make it easier to parse JSON and XML response data into data structures +* Enhanced cookie jar de-duplication +* Added the ability to enable strict cookie jars that throw exceptions when invalid cookies are added +* Added setStream to StreamInterface to actually make it possible to implement custom rewind behavior for entity bodies +* Added the ability to create any sort of hash for a stream rather than just an MD5 hash + +## 3.0.3 - 2012-11-04 + +* Implementing redirects in PHP rather than cURL +* Added PECL URI template extension and using as default parser if available +* Bug: Fixed Content-Length parsing of Response factory +* Adding rewind() method to entity bodies and streams. Allows for custom rewinding of non-repeatable streams. +* Adding ToArrayInterface throughout library +* Fixing OauthPlugin to create unique nonce values per request + +## 3.0.2 - 2012-10-25 + +* Magic methods are enabled by default on clients +* Magic methods return the result of a command +* Service clients no longer require a base_url option in the factory +* Bug: Fixed an issue with URI templates where null template variables were being expanded + +## 3.0.1 - 2012-10-22 + +* Models can now be used like regular collection objects by calling filter, map, etc. +* Models no longer require a Parameter structure or initial data in the constructor +* Added a custom AppendIterator to get around a PHP bug with the `\AppendIterator` + +## 3.0.0 - 2012-10-15 + +* Rewrote service description format to be based on Swagger + * Now based on JSON schema + * Added nested input structures and nested response models + * Support for JSON and XML input and output models + * Renamed `commands` to `operations` + * Removed dot class notation + * Removed custom types +* Broke the project into smaller top-level namespaces to be more component friendly +* Removed support for XML configs and descriptions. Use arrays or JSON files. +* Removed the Validation component and Inspector +* Moved all cookie code to Guzzle\Plugin\Cookie +* Magic methods on a Guzzle\Service\Client now return the command un-executed. +* Calling getResult() or getResponse() on a command will lazily execute the command if needed. +* Now shipping with cURL's CA certs and using it by default +* Added previousResponse() method to response objects +* No longer sending Accept and Accept-Encoding headers on every request +* Only sending an Expect header by default when a payload is greater than 1MB +* Added/moved client options: + * curl.blacklist to curl.option.blacklist + * Added ssl.certificate_authority +* Added a Guzzle\Iterator component +* Moved plugins from Guzzle\Http\Plugin to Guzzle\Plugin +* Added a more robust backoff retry strategy (replaced the ExponentialBackoffPlugin) +* Added a more robust caching plugin +* Added setBody to response objects +* Updating LogPlugin to use a more flexible MessageFormatter +* Added a completely revamped build process +* Cleaning up Collection class and removing default values from the get method +* Fixed ZF2 cache adapters + +## 2.8.8 - 2012-10-15 + +* Bug: Fixed a cookie issue that caused dot prefixed domains to not match where popular browsers did + +## 2.8.7 - 2012-09-30 + +* Bug: Fixed config file aliases for JSON includes +* Bug: Fixed cookie bug on a request object by using CookieParser to parse cookies on requests +* Bug: Removing the path to a file when sending a Content-Disposition header on a POST upload +* Bug: Hardening request and response parsing to account for missing parts +* Bug: Fixed PEAR packaging +* Bug: Fixed Request::getInfo +* Bug: Fixed cases where CURLM_CALL_MULTI_PERFORM return codes were causing curl transactions to fail +* Adding the ability for the namespace Iterator factory to look in multiple directories +* Added more getters/setters/removers from service descriptions +* Added the ability to remove POST fields from OAuth signatures +* OAuth plugin now supports 2-legged OAuth + +## 2.8.6 - 2012-09-05 + +* Added the ability to modify and build service descriptions +* Added the use of visitors to apply parameters to locations in service descriptions using the dynamic command +* Added a `json` parameter location +* Now allowing dot notation for classes in the CacheAdapterFactory +* Using the union of two arrays rather than an array_merge when extending service builder services and service params +* Ensuring that a service is a string before doing strpos() checks on it when substituting services for references + in service builder config files. +* Services defined in two different config files that include one another will by default replace the previously + defined service, but you can now create services that extend themselves and merge their settings over the previous +* The JsonLoader now supports aliasing filenames with different filenames. This allows you to alias something like + '_default' with a default JSON configuration file. + +## 2.8.5 - 2012-08-29 + +* Bug: Suppressed empty arrays from URI templates +* Bug: Added the missing $options argument from ServiceDescription::factory to enable caching +* Added support for HTTP responses that do not contain a reason phrase in the start-line +* AbstractCommand commands are now invokable +* Added a way to get the data used when signing an Oauth request before a request is sent + +## 2.8.4 - 2012-08-15 + +* Bug: Custom delay time calculations are no longer ignored in the ExponentialBackoffPlugin +* Added the ability to transfer entity bodies as a string rather than streamed. This gets around curl error 65. Set `body_as_string` in a request's curl options to enable. +* Added a StreamInterface, EntityBodyInterface, and added ftell() to Guzzle\Common\Stream +* Added an AbstractEntityBodyDecorator and a ReadLimitEntityBody decorator to transfer only a subset of a decorated stream +* Stream and EntityBody objects will now return the file position to the previous position after a read required operation (e.g. getContentMd5()) +* Added additional response status codes +* Removed SSL information from the default User-Agent header +* DELETE requests can now send an entity body +* Added an EventDispatcher to the ExponentialBackoffPlugin and added an ExponentialBackoffLogger to log backoff retries +* Added the ability of the MockPlugin to consume mocked request bodies +* LogPlugin now exposes request and response objects in the extras array + +## 2.8.3 - 2012-07-30 + +* Bug: Fixed a case where empty POST requests were sent as GET requests +* Bug: Fixed a bug in ExponentialBackoffPlugin that caused fatal errors when retrying an EntityEnclosingRequest that does not have a body +* Bug: Setting the response body of a request to null after completing a request, not when setting the state of a request to new +* Added multiple inheritance to service description commands +* Added an ApiCommandInterface and added `getParamNames()` and `hasParam()` +* Removed the default 2mb size cutoff from the Md5ValidatorPlugin so that it now defaults to validating everything +* Changed CurlMulti::perform to pass a smaller timeout to CurlMulti::executeHandles + +## 2.8.2 - 2012-07-24 + +* Bug: Query string values set to 0 are no longer dropped from the query string +* Bug: A Collection object is no longer created each time a call is made to `Guzzle\Service\Command\AbstractCommand::getRequestHeaders()` +* Bug: `+` is now treated as an encoded space when parsing query strings +* QueryString and Collection performance improvements +* Allowing dot notation for class paths in filters attribute of a service descriptions + +## 2.8.1 - 2012-07-16 + +* Loosening Event Dispatcher dependency +* POST redirects can now be customized using CURLOPT_POSTREDIR + +## 2.8.0 - 2012-07-15 + +* BC: Guzzle\Http\Query + * Query strings with empty variables will always show an equal sign unless the variable is set to QueryString::BLANK (e.g. ?acl= vs ?acl) + * Changed isEncodingValues() and isEncodingFields() to isUrlEncoding() + * Changed setEncodeValues(bool) and setEncodeFields(bool) to useUrlEncoding(bool) + * Changed the aggregation functions of QueryString to be static methods + * Can now use fromString() with querystrings that have a leading ? +* cURL configuration values can be specified in service descriptions using `curl.` prefixed parameters +* Content-Length is set to 0 before emitting the request.before_send event when sending an empty request body +* Cookies are no longer URL decoded by default +* Bug: URI template variables set to null are no longer expanded + +## 2.7.2 - 2012-07-02 + +* BC: Moving things to get ready for subtree splits. Moving Inflection into Common. Moving Guzzle\Http\Parser to Guzzle\Parser. +* BC: Removing Guzzle\Common\Batch\Batch::count() and replacing it with isEmpty() +* CachePlugin now allows for a custom request parameter function to check if a request can be cached +* Bug fix: CachePlugin now only caches GET and HEAD requests by default +* Bug fix: Using header glue when transferring headers over the wire +* Allowing deeply nested arrays for composite variables in URI templates +* Batch divisors can now return iterators or arrays + +## 2.7.1 - 2012-06-26 + +* Minor patch to update version number in UA string +* Updating build process + +## 2.7.0 - 2012-06-25 + +* BC: Inflection classes moved to Guzzle\Inflection. No longer static methods. Can now inject custom inflectors into classes. +* BC: Removed magic setX methods from commands +* BC: Magic methods mapped to service description commands are now inflected in the command factory rather than the client __call() method +* Verbose cURL options are no longer enabled by default. Set curl.debug to true on a client to enable. +* Bug: Now allowing colons in a response start-line (e.g. HTTP/1.1 503 Service Unavailable: Back-end server is at capacity) +* Guzzle\Service\Resource\ResourceIteratorApplyBatched now internally uses the Guzzle\Common\Batch namespace +* Added Guzzle\Service\Plugin namespace and a PluginCollectionPlugin +* Added the ability to set POST fields and files in a service description +* Guzzle\Http\EntityBody::factory() now accepts objects with a __toString() method +* Adding a command.before_prepare event to clients +* Added BatchClosureTransfer and BatchClosureDivisor +* BatchTransferException now includes references to the batch divisor and transfer strategies +* Fixed some tests so that they pass more reliably +* Added Guzzle\Common\Log\ArrayLogAdapter + +## 2.6.6 - 2012-06-10 + +* BC: Removing Guzzle\Http\Plugin\BatchQueuePlugin +* BC: Removing Guzzle\Service\Command\CommandSet +* Adding generic batching system (replaces the batch queue plugin and command set) +* Updating ZF cache and log adapters and now using ZF's composer repository +* Bug: Setting the name of each ApiParam when creating through an ApiCommand +* Adding result_type, result_doc, deprecated, and doc_url to service descriptions +* Bug: Changed the default cookie header casing back to 'Cookie' + +## 2.6.5 - 2012-06-03 + +* BC: Renaming Guzzle\Http\Message\RequestInterface::getResourceUri() to getResource() +* BC: Removing unused AUTH_BASIC and AUTH_DIGEST constants from +* BC: Guzzle\Http\Cookie is now used to manage Set-Cookie data, not Cookie data +* BC: Renaming methods in the CookieJarInterface +* Moving almost all cookie logic out of the CookiePlugin and into the Cookie or CookieJar implementations +* Making the default glue for HTTP headers ';' instead of ',' +* Adding a removeValue to Guzzle\Http\Message\Header +* Adding getCookies() to request interface. +* Making it easier to add event subscribers to HasDispatcherInterface classes. Can now directly call addSubscriber() + +## 2.6.4 - 2012-05-30 + +* BC: Cleaning up how POST files are stored in EntityEnclosingRequest objects. Adding PostFile class. +* BC: Moving ApiCommand specific functionality from the Inspector and on to the ApiCommand +* Bug: Fixing magic method command calls on clients +* Bug: Email constraint only validates strings +* Bug: Aggregate POST fields when POST files are present in curl handle +* Bug: Fixing default User-Agent header +* Bug: Only appending or prepending parameters in commands if they are specified +* Bug: Not requiring response reason phrases or status codes to match a predefined list of codes +* Allowing the use of dot notation for class namespaces when using instance_of constraint +* Added any_match validation constraint +* Added an AsyncPlugin +* Passing request object to the calculateWait method of the ExponentialBackoffPlugin +* Allowing the result of a command object to be changed +* Parsing location and type sub values when instantiating a service description rather than over and over at runtime + +## 2.6.3 - 2012-05-23 + +* [BC] Guzzle\Common\FromConfigInterface no longer requires any config options. +* [BC] Refactoring how POST files are stored on an EntityEnclosingRequest. They are now separate from POST fields. +* You can now use an array of data when creating PUT request bodies in the request factory. +* Removing the requirement that HTTPS requests needed a Cache-Control: public directive to be cacheable. +* [Http] Adding support for Content-Type in multipart POST uploads per upload +* [Http] Added support for uploading multiple files using the same name (foo[0], foo[1]) +* Adding more POST data operations for easier manipulation of POST data. +* You can now set empty POST fields. +* The body of a request is only shown on EntityEnclosingRequest objects that do not use POST files. +* Split the Guzzle\Service\Inspector::validateConfig method into two methods. One to initialize when a command is created, and one to validate. +* CS updates + +## 2.6.2 - 2012-05-19 + +* [Http] Better handling of nested scope requests in CurlMulti. Requests are now always prepares in the send() method rather than the addRequest() method. + +## 2.6.1 - 2012-05-19 + +* [BC] Removing 'path' support in service descriptions. Use 'uri'. +* [BC] Guzzle\Service\Inspector::parseDocBlock is now protected. Adding getApiParamsForClass() with cache. +* [BC] Removing Guzzle\Common\NullObject. Use https://github.com/mtdowling/NullObject if you need it. +* [BC] Removing Guzzle\Common\XmlElement. +* All commands, both dynamic and concrete, have ApiCommand objects. +* Adding a fix for CurlMulti so that if all of the connections encounter some sort of curl error, then the loop exits. +* Adding checks to EntityEnclosingRequest so that empty POST files and fields are ignored. +* Making the method signature of Guzzle\Service\Builder\ServiceBuilder::factory more flexible. + +## 2.6.0 - 2012-05-15 + +* [BC] Moving Guzzle\Service\Builder to Guzzle\Service\Builder\ServiceBuilder +* [BC] Executing a Command returns the result of the command rather than the command +* [BC] Moving all HTTP parsing logic to Guzzle\Http\Parsers. Allows for faster C implementations if needed. +* [BC] Changing the Guzzle\Http\Message\Response::setProtocol() method to accept a protocol and version in separate args. +* [BC] Moving ResourceIterator* to Guzzle\Service\Resource +* [BC] Completely refactored ResourceIterators to iterate over a cloned command object +* [BC] Moved Guzzle\Http\UriTemplate to Guzzle\Http\Parser\UriTemplate\UriTemplate +* [BC] Guzzle\Guzzle is now deprecated +* Moving Guzzle\Common\Guzzle::inject to Guzzle\Common\Collection::inject +* Adding Guzzle\Version class to give version information about Guzzle +* Adding Guzzle\Http\Utils class to provide getDefaultUserAgent() and getHttpDate() +* Adding Guzzle\Curl\CurlVersion to manage caching curl_version() data +* ServiceDescription and ServiceBuilder are now cacheable using similar configs +* Changing the format of XML and JSON service builder configs. Backwards compatible. +* Cleaned up Cookie parsing +* Trimming the default Guzzle User-Agent header +* Adding a setOnComplete() method to Commands that is called when a command completes +* Keeping track of requests that were mocked in the MockPlugin +* Fixed a caching bug in the CacheAdapterFactory +* Inspector objects can be injected into a Command object +* Refactoring a lot of code and tests to be case insensitive when dealing with headers +* Adding Guzzle\Http\Message\HeaderComparison for easy comparison of HTTP headers using a DSL +* Adding the ability to set global option overrides to service builder configs +* Adding the ability to include other service builder config files from within XML and JSON files +* Moving the parseQuery method out of Url and on to QueryString::fromString() as a static factory method. + +## 2.5.0 - 2012-05-08 + +* Major performance improvements +* [BC] Simplifying Guzzle\Common\Collection. Please check to see if you are using features that are now deprecated. +* [BC] Using a custom validation system that allows a flyweight implementation for much faster validation. No longer using Symfony2 Validation component. +* [BC] No longer supporting "{{ }}" for injecting into command or UriTemplates. Use "{}" +* Added the ability to passed parameters to all requests created by a client +* Added callback functionality to the ExponentialBackoffPlugin +* Using microtime in ExponentialBackoffPlugin to allow more granular backoff strategies. +* Rewinding request stream bodies when retrying requests +* Exception is thrown when JSON response body cannot be decoded +* Added configurable magic method calls to clients and commands. This is off by default. +* Fixed a defect that added a hash to every parsed URL part +* Fixed duplicate none generation for OauthPlugin. +* Emitting an event each time a client is generated by a ServiceBuilder +* Using an ApiParams object instead of a Collection for parameters of an ApiCommand +* cache.* request parameters should be renamed to params.cache.* +* Added the ability to set arbitrary curl options on requests (disable_wire, progress, etc.). See CurlHandle. +* Added the ability to disable type validation of service descriptions +* ServiceDescriptions and ServiceBuilders are now Serializable diff --git a/vendor/guzzlehttp/guzzle/LICENSE b/vendor/guzzlehttp/guzzle/LICENSE new file mode 100644 index 00000000..fd2375d8 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/LICENSE @@ -0,0 +1,27 @@ +The MIT License (MIT) + +Copyright (c) 2011 Michael Dowling +Copyright (c) 2012 Jeremy Lindblom +Copyright (c) 2014 Graham Campbell +Copyright (c) 2015 Márk Sági-Kazár +Copyright (c) 2015 Tobias Schultze +Copyright (c) 2016 Tobias Nyholm +Copyright (c) 2016 George Mponos + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/guzzlehttp/guzzle/README.md b/vendor/guzzlehttp/guzzle/README.md new file mode 100644 index 00000000..6d78a930 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/README.md @@ -0,0 +1,94 @@ +![Guzzle](.github/logo.png?raw=true) + +# Guzzle, PHP HTTP client + +[![Latest Version](https://img.shields.io/github/release/guzzle/guzzle.svg?style=flat-square)](https://github.com/guzzle/guzzle/releases) +[![Build Status](https://img.shields.io/github/actions/workflow/status/guzzle/guzzle/ci.yml?label=ci%20build&style=flat-square)](https://github.com/guzzle/guzzle/actions?query=workflow%3ACI) +[![Total Downloads](https://img.shields.io/packagist/dt/guzzlehttp/guzzle.svg?style=flat-square)](https://packagist.org/packages/guzzlehttp/guzzle) + +Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and +trivial to integrate with web services. + +- Simple interface for building query strings, POST requests, streaming large + uploads, streaming large downloads, using HTTP cookies, uploading JSON data, + etc... +- Can send both synchronous and asynchronous requests using the same interface. +- Uses PSR-7 interfaces for requests, responses, and streams. This allows you + to utilize other PSR-7 compatible libraries with Guzzle. +- Supports PSR-18 allowing interoperability between other PSR-18 HTTP Clients. +- Abstracts away the underlying HTTP transport, allowing you to write + environment and transport agnostic code; i.e., no hard dependency on cURL, + PHP streams, sockets, or non-blocking event loops. +- Middleware system allows you to augment and compose client behavior. + +```php +$client = new \GuzzleHttp\Client(); +$response = $client->request('GET', 'https://api.github.com/repos/guzzle/guzzle'); + +echo $response->getStatusCode(); // 200 +echo $response->getHeaderLine('content-type'); // 'application/json; charset=utf8' +echo $response->getBody(); // '{"id": 1420053, "name": "guzzle", ...}' + +// Send an asynchronous request. +$request = new \GuzzleHttp\Psr7\Request('GET', 'http://httpbin.org'); +$promise = $client->sendAsync($request)->then(function ($response) { + echo 'I completed! ' . $response->getBody(); +}); + +$promise->wait(); +``` + +## Help and docs + +We use GitHub issues only to discuss bugs and new features. For support please refer to: + +- [Documentation](https://docs.guzzlephp.org) +- [Stack Overflow](https://stackoverflow.com/questions/tagged/guzzle) +- [#guzzle](https://app.slack.com/client/T0D2S9JCT/CE6UAAKL4) channel on [PHP-HTTP Slack](https://slack.httplug.io/) +- [Gitter](https://gitter.im/guzzle/guzzle) + + +## Installing Guzzle + +The recommended way to install Guzzle is through +[Composer](https://getcomposer.org/). + +```bash +composer require guzzlehttp/guzzle +``` + + +## Version Guidance + +| Version | Status | Packagist | Namespace | Repo | Docs | PSR-7 | PHP Version | +|---------|---------------------|---------------------|--------------|---------------------|---------------------|-------|--------------| +| 3.x | EOL | `guzzle/guzzle` | `Guzzle` | [v3][guzzle-3-repo] | [v3][guzzle-3-docs] | No | >=5.3.3,<7.0 | +| 4.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v4][guzzle-4-repo] | N/A | No | >=5.4,<7.0 | +| 5.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v5][guzzle-5-repo] | [v5][guzzle-5-docs] | No | >=5.4,<7.4 | +| 6.x | Security fixes only | `guzzlehttp/guzzle` | `GuzzleHttp` | [v6][guzzle-6-repo] | [v6][guzzle-6-docs] | Yes | >=5.5,<8.0 | +| 7.x | Latest | `guzzlehttp/guzzle` | `GuzzleHttp` | [v7][guzzle-7-repo] | [v7][guzzle-7-docs] | Yes | >=7.2.5,<8.4 | + +[guzzle-3-repo]: https://github.com/guzzle/guzzle3 +[guzzle-4-repo]: https://github.com/guzzle/guzzle/tree/4.x +[guzzle-5-repo]: https://github.com/guzzle/guzzle/tree/5.3 +[guzzle-6-repo]: https://github.com/guzzle/guzzle/tree/6.5 +[guzzle-7-repo]: https://github.com/guzzle/guzzle +[guzzle-3-docs]: https://guzzle3.readthedocs.io/ +[guzzle-5-docs]: https://docs.guzzlephp.org/en/5.3/ +[guzzle-6-docs]: https://docs.guzzlephp.org/en/6.5/ +[guzzle-7-docs]: https://docs.guzzlephp.org/en/latest/ + + +## Security + +If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see [Security Policy](https://github.com/guzzle/guzzle/security/policy) for more information. + +## License + +Guzzle is made available under the MIT License (MIT). Please see [License File](LICENSE) for more information. + +## For Enterprise + +Available as part of the Tidelift Subscription + +The maintainers of Guzzle and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-guzzlehttp-guzzle?utm_source=packagist-guzzlehttp-guzzle&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) diff --git a/vendor/guzzlehttp/guzzle/UPGRADING.md b/vendor/guzzlehttp/guzzle/UPGRADING.md new file mode 100644 index 00000000..4efbb596 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/UPGRADING.md @@ -0,0 +1,1253 @@ +Guzzle Upgrade Guide +==================== + +6.0 to 7.0 +---------- + +In order to take advantage of the new features of PHP, Guzzle dropped the support +of PHP 5. The minimum supported PHP version is now PHP 7.2. Type hints and return +types for functions and methods have been added wherever possible. + +Please make sure: +- You are calling a function or a method with the correct type. +- If you extend a class of Guzzle; update all signatures on methods you override. + +#### Other backwards compatibility breaking changes + +- Class `GuzzleHttp\UriTemplate` is removed. +- Class `GuzzleHttp\Exception\SeekException` is removed. +- Classes `GuzzleHttp\Exception\BadResponseException`, `GuzzleHttp\Exception\ClientException`, + `GuzzleHttp\Exception\ServerException` can no longer be initialized with an empty + Response as argument. +- Class `GuzzleHttp\Exception\ConnectException` now extends `GuzzleHttp\Exception\TransferException` + instead of `GuzzleHttp\Exception\RequestException`. +- Function `GuzzleHttp\Exception\ConnectException::getResponse()` is removed. +- Function `GuzzleHttp\Exception\ConnectException::hasResponse()` is removed. +- Constant `GuzzleHttp\ClientInterface::VERSION` is removed. Added `GuzzleHttp\ClientInterface::MAJOR_VERSION` instead. +- Function `GuzzleHttp\Exception\RequestException::getResponseBodySummary` is removed. + Use `\GuzzleHttp\Psr7\get_message_body_summary` as an alternative. +- Function `GuzzleHttp\Cookie\CookieJar::getCookieValue` is removed. +- Request option `exceptions` is removed. Please use `http_errors`. +- Request option `save_to` is removed. Please use `sink`. +- Pool option `pool_size` is removed. Please use `concurrency`. +- We now look for environment variables in the `$_SERVER` super global, due to thread safety issues with `getenv`. We continue to fallback to `getenv` in CLI environments, for maximum compatibility. +- The `get`, `head`, `put`, `post`, `patch`, `delete`, `getAsync`, `headAsync`, `putAsync`, `postAsync`, `patchAsync`, and `deleteAsync` methods are now implemented as genuine methods on `GuzzleHttp\Client`, with strong typing. The original `__call` implementation remains unchanged for now, for maximum backwards compatibility, but won't be invoked under normal operation. +- The `log` middleware will log the errors with level `error` instead of `notice` +- Support for international domain names (IDN) is now disabled by default, and enabling it requires installing ext-intl, linked against a modern version of the C library (ICU 4.6 or higher). + +#### Native functions calls + +All internal native functions calls of Guzzle are now prefixed with a slash. This +change makes it impossible for method overloading by other libraries or applications. +Example: + +```php +// Before: +curl_version(); + +// After: +\curl_version(); +``` + +For the full diff you can check [here](https://github.com/guzzle/guzzle/compare/6.5.4..master). + +5.0 to 6.0 +---------- + +Guzzle now uses [PSR-7](https://www.php-fig.org/psr/psr-7/) for HTTP messages. +Due to the fact that these messages are immutable, this prompted a refactoring +of Guzzle to use a middleware based system rather than an event system. Any +HTTP message interaction (e.g., `GuzzleHttp\Message\Request`) need to be +updated to work with the new immutable PSR-7 request and response objects. Any +event listeners or subscribers need to be updated to become middleware +functions that wrap handlers (or are injected into a +`GuzzleHttp\HandlerStack`). + +- Removed `GuzzleHttp\BatchResults` +- Removed `GuzzleHttp\Collection` +- Removed `GuzzleHttp\HasDataTrait` +- Removed `GuzzleHttp\ToArrayInterface` +- The `guzzlehttp/streams` dependency has been removed. Stream functionality + is now present in the `GuzzleHttp\Psr7` namespace provided by the + `guzzlehttp/psr7` package. +- Guzzle no longer uses ReactPHP promises and now uses the + `guzzlehttp/promises` library. We use a custom promise library for three + significant reasons: + 1. React promises (at the time of writing this) are recursive. Promise + chaining and promise resolution will eventually blow the stack. Guzzle + promises are not recursive as they use a sort of trampolining technique. + Note: there has been movement in the React project to modify promises to + no longer utilize recursion. + 2. Guzzle needs to have the ability to synchronously block on a promise to + wait for a result. Guzzle promises allows this functionality (and does + not require the use of recursion). + 3. Because we need to be able to wait on a result, doing so using React + promises requires wrapping react promises with RingPHP futures. This + overhead is no longer needed, reducing stack sizes, reducing complexity, + and improving performance. +- `GuzzleHttp\Mimetypes` has been moved to a function in + `GuzzleHttp\Psr7\mimetype_from_extension` and + `GuzzleHttp\Psr7\mimetype_from_filename`. +- `GuzzleHttp\Query` and `GuzzleHttp\QueryParser` have been removed. Query + strings must now be passed into request objects as strings, or provided to + the `query` request option when creating requests with clients. The `query` + option uses PHP's `http_build_query` to convert an array to a string. If you + need a different serialization technique, you will need to pass the query + string in as a string. There are a couple helper functions that will make + working with query strings easier: `GuzzleHttp\Psr7\parse_query` and + `GuzzleHttp\Psr7\build_query`. +- Guzzle no longer has a dependency on RingPHP. Due to the use of a middleware + system based on PSR-7, using RingPHP and it's middleware system as well adds + more complexity than the benefits it provides. All HTTP handlers that were + present in RingPHP have been modified to work directly with PSR-7 messages + and placed in the `GuzzleHttp\Handler` namespace. This significantly reduces + complexity in Guzzle, removes a dependency, and improves performance. RingPHP + will be maintained for Guzzle 5 support, but will no longer be a part of + Guzzle 6. +- As Guzzle now uses a middleware based systems the event system and RingPHP + integration has been removed. Note: while the event system has been removed, + it is possible to add your own type of event system that is powered by the + middleware system. + - Removed the `Event` namespace. + - Removed the `Subscriber` namespace. + - Removed `Transaction` class + - Removed `RequestFsm` + - Removed `RingBridge` + - `GuzzleHttp\Subscriber\Cookie` is now provided by + `GuzzleHttp\Middleware::cookies` + - `GuzzleHttp\Subscriber\HttpError` is now provided by + `GuzzleHttp\Middleware::httpError` + - `GuzzleHttp\Subscriber\History` is now provided by + `GuzzleHttp\Middleware::history` + - `GuzzleHttp\Subscriber\Mock` is now provided by + `GuzzleHttp\Handler\MockHandler` + - `GuzzleHttp\Subscriber\Prepare` is now provided by + `GuzzleHttp\PrepareBodyMiddleware` + - `GuzzleHttp\Subscriber\Redirect` is now provided by + `GuzzleHttp\RedirectMiddleware` +- Guzzle now uses `Psr\Http\Message\UriInterface` (implements in + `GuzzleHttp\Psr7\Uri`) for URI support. `GuzzleHttp\Url` is now gone. +- Static functions in `GuzzleHttp\Utils` have been moved to namespaced + functions under the `GuzzleHttp` namespace. This requires either a Composer + based autoloader or you to include functions.php. +- `GuzzleHttp\ClientInterface::getDefaultOption` has been renamed to + `GuzzleHttp\ClientInterface::getConfig`. +- `GuzzleHttp\ClientInterface::setDefaultOption` has been removed. +- The `json` and `xml` methods of response objects has been removed. With the + migration to strictly adhering to PSR-7 as the interface for Guzzle messages, + adding methods to message interfaces would actually require Guzzle messages + to extend from PSR-7 messages rather then work with them directly. + +## Migrating to middleware + +The change to PSR-7 unfortunately required significant refactoring to Guzzle +due to the fact that PSR-7 messages are immutable. Guzzle 5 relied on an event +system from plugins. The event system relied on mutability of HTTP messages and +side effects in order to work. With immutable messages, you have to change your +workflow to become more about either returning a value (e.g., functional +middlewares) or setting a value on an object. Guzzle v6 has chosen the +functional middleware approach. + +Instead of using the event system to listen for things like the `before` event, +you now create a stack based middleware function that intercepts a request on +the way in and the promise of the response on the way out. This is a much +simpler and more predictable approach than the event system and works nicely +with PSR-7 middleware. Due to the use of promises, the middleware system is +also asynchronous. + +v5: + +```php +use GuzzleHttp\Event\BeforeEvent; +$client = new GuzzleHttp\Client(); +// Get the emitter and listen to the before event. +$client->getEmitter()->on('before', function (BeforeEvent $e) { + // Guzzle v5 events relied on mutation + $e->getRequest()->setHeader('X-Foo', 'Bar'); +}); +``` + +v6: + +In v6, you can modify the request before it is sent using the `mapRequest` +middleware. The idiomatic way in v6 to modify the request/response lifecycle is +to setup a handler middleware stack up front and inject the handler into a +client. + +```php +use GuzzleHttp\Middleware; +// Create a handler stack that has all of the default middlewares attached +$handler = GuzzleHttp\HandlerStack::create(); +// Push the handler onto the handler stack +$handler->push(Middleware::mapRequest(function (RequestInterface $request) { + // Notice that we have to return a request object + return $request->withHeader('X-Foo', 'Bar'); +})); +// Inject the handler into the client +$client = new GuzzleHttp\Client(['handler' => $handler]); +``` + +## POST Requests + +This version added the [`form_params`](https://docs.guzzlephp.org/en/latest/request-options.html#form_params) +and `multipart` request options. `form_params` is an associative array of +strings or array of strings and is used to serialize an +`application/x-www-form-urlencoded` POST request. The +[`multipart`](https://docs.guzzlephp.org/en/latest/request-options.html#multipart) +option is now used to send a multipart/form-data POST request. + +`GuzzleHttp\Post\PostFile` has been removed. Use the `multipart` option to add +POST files to a multipart/form-data request. + +The `body` option no longer accepts an array to send POST requests. Please use +`multipart` or `form_params` instead. + +The `base_url` option has been renamed to `base_uri`. + +4.x to 5.0 +---------- + +## Rewritten Adapter Layer + +Guzzle now uses [RingPHP](https://ringphp.readthedocs.org/en/latest) to send +HTTP requests. The `adapter` option in a `GuzzleHttp\Client` constructor +is still supported, but it has now been renamed to `handler`. Instead of +passing a `GuzzleHttp\Adapter\AdapterInterface`, you must now pass a PHP +`callable` that follows the RingPHP specification. + +## Removed Fluent Interfaces + +[Fluent interfaces were removed](https://ocramius.github.io/blog/fluent-interfaces-are-evil/) +from the following classes: + +- `GuzzleHttp\Collection` +- `GuzzleHttp\Url` +- `GuzzleHttp\Query` +- `GuzzleHttp\Post\PostBody` +- `GuzzleHttp\Cookie\SetCookie` + +## Removed functions.php + +Removed "functions.php", so that Guzzle is truly PSR-4 compliant. The following +functions can be used as replacements. + +- `GuzzleHttp\json_decode` -> `GuzzleHttp\Utils::jsonDecode` +- `GuzzleHttp\get_path` -> `GuzzleHttp\Utils::getPath` +- `GuzzleHttp\Utils::setPath` -> `GuzzleHttp\set_path` +- `GuzzleHttp\Pool::batch` -> `GuzzleHttp\batch`. This function is, however, + deprecated in favor of using `GuzzleHttp\Pool::batch()`. + +The "procedural" global client has been removed with no replacement (e.g., +`GuzzleHttp\get()`, `GuzzleHttp\post()`, etc.). Use a `GuzzleHttp\Client` +object as a replacement. + +## `throwImmediately` has been removed + +The concept of "throwImmediately" has been removed from exceptions and error +events. This control mechanism was used to stop a transfer of concurrent +requests from completing. This can now be handled by throwing the exception or +by cancelling a pool of requests or each outstanding future request +individually. + +## headers event has been removed + +Removed the "headers" event. This event was only useful for changing the +body a response once the headers of the response were known. You can implement +a similar behavior in a number of ways. One example might be to use a +FnStream that has access to the transaction being sent. For example, when the +first byte is written, you could check if the response headers match your +expectations, and if so, change the actual stream body that is being +written to. + +## Updates to HTTP Messages + +Removed the `asArray` parameter from +`GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header +value as an array, then use the newly added `getHeaderAsArray()` method of +`MessageInterface`. This change makes the Guzzle interfaces compatible with +the PSR-7 interfaces. + +3.x to 4.0 +---------- + +## Overarching changes: + +- Now requires PHP 5.4 or greater. +- No longer requires cURL to send requests. +- Guzzle no longer wraps every exception it throws. Only exceptions that are + recoverable are now wrapped by Guzzle. +- Various namespaces have been removed or renamed. +- No longer requiring the Symfony EventDispatcher. A custom event dispatcher + based on the Symfony EventDispatcher is + now utilized in `GuzzleHttp\Event\EmitterInterface` (resulting in significant + speed and functionality improvements). + +Changes per Guzzle 3.x namespace are described below. + +## Batch + +The `Guzzle\Batch` namespace has been removed. This is best left to +third-parties to implement on top of Guzzle's core HTTP library. + +## Cache + +The `Guzzle\Cache` namespace has been removed. (Todo: No suitable replacement +has been implemented yet, but hoping to utilize a PSR cache interface). + +## Common + +- Removed all of the wrapped exceptions. It's better to use the standard PHP + library for unrecoverable exceptions. +- `FromConfigInterface` has been removed. +- `Guzzle\Common\Version` has been removed. The VERSION constant can be found + at `GuzzleHttp\ClientInterface::VERSION`. + +### Collection + +- `getAll` has been removed. Use `toArray` to convert a collection to an array. +- `inject` has been removed. +- `keySearch` has been removed. +- `getPath` no longer supports wildcard expressions. Use something better like + JMESPath for this. +- `setPath` now supports appending to an existing array via the `[]` notation. + +### Events + +Guzzle no longer requires Symfony's EventDispatcher component. Guzzle now uses +`GuzzleHttp\Event\Emitter`. + +- `Symfony\Component\EventDispatcher\EventDispatcherInterface` is replaced by + `GuzzleHttp\Event\EmitterInterface`. +- `Symfony\Component\EventDispatcher\EventDispatcher` is replaced by + `GuzzleHttp\Event\Emitter`. +- `Symfony\Component\EventDispatcher\Event` is replaced by + `GuzzleHttp\Event\Event`, and Guzzle now has an EventInterface in + `GuzzleHttp\Event\EventInterface`. +- `AbstractHasDispatcher` has moved to a trait, `HasEmitterTrait`, and + `HasDispatcherInterface` has moved to `HasEmitterInterface`. Retrieving the + event emitter of a request, client, etc. now uses the `getEmitter` method + rather than the `getDispatcher` method. + +#### Emitter + +- Use the `once()` method to add a listener that automatically removes itself + the first time it is invoked. +- Use the `listeners()` method to retrieve a list of event listeners rather than + the `getListeners()` method. +- Use `emit()` instead of `dispatch()` to emit an event from an emitter. +- Use `attach()` instead of `addSubscriber()` and `detach()` instead of + `removeSubscriber()`. + +```php +$mock = new Mock(); +// 3.x +$request->getEventDispatcher()->addSubscriber($mock); +$request->getEventDispatcher()->removeSubscriber($mock); +// 4.x +$request->getEmitter()->attach($mock); +$request->getEmitter()->detach($mock); +``` + +Use the `on()` method to add a listener rather than the `addListener()` method. + +```php +// 3.x +$request->getEventDispatcher()->addListener('foo', function (Event $event) { /* ... */ } ); +// 4.x +$request->getEmitter()->on('foo', function (Event $event, $name) { /* ... */ } ); +``` + +## Http + +### General changes + +- The cacert.pem certificate has been moved to `src/cacert.pem`. +- Added the concept of adapters that are used to transfer requests over the + wire. +- Simplified the event system. +- Sending requests in parallel is still possible, but batching is no longer a + concept of the HTTP layer. Instead, you must use the `complete` and `error` + events to asynchronously manage parallel request transfers. +- `Guzzle\Http\Url` has moved to `GuzzleHttp\Url`. +- `Guzzle\Http\QueryString` has moved to `GuzzleHttp\Query`. +- QueryAggregators have been rewritten so that they are simply callable + functions. +- `GuzzleHttp\StaticClient` has been removed. Use the functions provided in + `functions.php` for an easy to use static client instance. +- Exceptions in `GuzzleHttp\Exception` have been updated to all extend from + `GuzzleHttp\Exception\TransferException`. + +### Client + +Calling methods like `get()`, `post()`, `head()`, etc. no longer create and +return a request, but rather creates a request, sends the request, and returns +the response. + +```php +// 3.0 +$request = $client->get('/'); +$response = $request->send(); + +// 4.0 +$response = $client->get('/'); + +// or, to mirror the previous behavior +$request = $client->createRequest('GET', '/'); +$response = $client->send($request); +``` + +`GuzzleHttp\ClientInterface` has changed. + +- The `send` method no longer accepts more than one request. Use `sendAll` to + send multiple requests in parallel. +- `setUserAgent()` has been removed. Use a default request option instead. You + could, for example, do something like: + `$client->setConfig('defaults/headers/User-Agent', 'Foo/Bar ' . $client::getDefaultUserAgent())`. +- `setSslVerification()` has been removed. Use default request options instead, + like `$client->setConfig('defaults/verify', true)`. + +`GuzzleHttp\Client` has changed. + +- The constructor now accepts only an associative array. You can include a + `base_url` string or array to use a URI template as the base URL of a client. + You can also specify a `defaults` key that is an associative array of default + request options. You can pass an `adapter` to use a custom adapter, + `batch_adapter` to use a custom adapter for sending requests in parallel, or + a `message_factory` to change the factory used to create HTTP requests and + responses. +- The client no longer emits a `client.create_request` event. +- Creating requests with a client no longer automatically utilize a URI + template. You must pass an array into a creational method (e.g., + `createRequest`, `get`, `put`, etc.) in order to expand a URI template. + +### Messages + +Messages no longer have references to their counterparts (i.e., a request no +longer has a reference to it's response, and a response no loger has a +reference to its request). This association is now managed through a +`GuzzleHttp\Adapter\TransactionInterface` object. You can get references to +these transaction objects using request events that are emitted over the +lifecycle of a request. + +#### Requests with a body + +- `GuzzleHttp\Message\EntityEnclosingRequest` and + `GuzzleHttp\Message\EntityEnclosingRequestInterface` have been removed. The + separation between requests that contain a body and requests that do not + contain a body has been removed, and now `GuzzleHttp\Message\RequestInterface` + handles both use cases. +- Any method that previously accepts a `GuzzleHttp\Response` object now accept a + `GuzzleHttp\Message\ResponseInterface`. +- `GuzzleHttp\Message\RequestFactoryInterface` has been renamed to + `GuzzleHttp\Message\MessageFactoryInterface`. This interface is used to create + both requests and responses and is implemented in + `GuzzleHttp\Message\MessageFactory`. +- POST field and file methods have been removed from the request object. You + must now use the methods made available to `GuzzleHttp\Post\PostBodyInterface` + to control the format of a POST body. Requests that are created using a + standard `GuzzleHttp\Message\MessageFactoryInterface` will automatically use + a `GuzzleHttp\Post\PostBody` body if the body was passed as an array or if + the method is POST and no body is provided. + +```php +$request = $client->createRequest('POST', '/'); +$request->getBody()->setField('foo', 'bar'); +$request->getBody()->addFile(new PostFile('file_key', fopen('/path/to/content', 'r'))); +``` + +#### Headers + +- `GuzzleHttp\Message\Header` has been removed. Header values are now simply + represented by an array of values or as a string. Header values are returned + as a string by default when retrieving a header value from a message. You can + pass an optional argument of `true` to retrieve a header value as an array + of strings instead of a single concatenated string. +- `GuzzleHttp\PostFile` and `GuzzleHttp\PostFileInterface` have been moved to + `GuzzleHttp\Post`. This interface has been simplified and now allows the + addition of arbitrary headers. +- Custom headers like `GuzzleHttp\Message\Header\Link` have been removed. Most + of the custom headers are now handled separately in specific + subscribers/plugins, and `GuzzleHttp\Message\HeaderValues::parseParams()` has + been updated to properly handle headers that contain parameters (like the + `Link` header). + +#### Responses + +- `GuzzleHttp\Message\Response::getInfo()` and + `GuzzleHttp\Message\Response::setInfo()` have been removed. Use the event + system to retrieve this type of information. +- `GuzzleHttp\Message\Response::getRawHeaders()` has been removed. +- `GuzzleHttp\Message\Response::getMessage()` has been removed. +- `GuzzleHttp\Message\Response::calculateAge()` and other cache specific + methods have moved to the CacheSubscriber. +- Header specific helper functions like `getContentMd5()` have been removed. + Just use `getHeader('Content-MD5')` instead. +- `GuzzleHttp\Message\Response::setRequest()` and + `GuzzleHttp\Message\Response::getRequest()` have been removed. Use the event + system to work with request and response objects as a transaction. +- `GuzzleHttp\Message\Response::getRedirectCount()` has been removed. Use the + Redirect subscriber instead. +- `GuzzleHttp\Message\Response::isSuccessful()` and other related methods have + been removed. Use `getStatusCode()` instead. + +#### Streaming responses + +Streaming requests can now be created by a client directly, returning a +`GuzzleHttp\Message\ResponseInterface` object that contains a body stream +referencing an open PHP HTTP stream. + +```php +// 3.0 +use Guzzle\Stream\PhpStreamRequestFactory; +$request = $client->get('/'); +$factory = new PhpStreamRequestFactory(); +$stream = $factory->fromRequest($request); +$data = $stream->read(1024); + +// 4.0 +$response = $client->get('/', ['stream' => true]); +// Read some data off of the stream in the response body +$data = $response->getBody()->read(1024); +``` + +#### Redirects + +The `configureRedirects()` method has been removed in favor of a +`allow_redirects` request option. + +```php +// Standard redirects with a default of a max of 5 redirects +$request = $client->createRequest('GET', '/', ['allow_redirects' => true]); + +// Strict redirects with a custom number of redirects +$request = $client->createRequest('GET', '/', [ + 'allow_redirects' => ['max' => 5, 'strict' => true] +]); +``` + +#### EntityBody + +EntityBody interfaces and classes have been removed or moved to +`GuzzleHttp\Stream`. All classes and interfaces that once required +`GuzzleHttp\EntityBodyInterface` now require +`GuzzleHttp\Stream\StreamInterface`. Creating a new body for a request no +longer uses `GuzzleHttp\EntityBody::factory` but now uses +`GuzzleHttp\Stream\Stream::factory` or even better: +`GuzzleHttp\Stream\create()`. + +- `Guzzle\Http\EntityBodyInterface` is now `GuzzleHttp\Stream\StreamInterface` +- `Guzzle\Http\EntityBody` is now `GuzzleHttp\Stream\Stream` +- `Guzzle\Http\CachingEntityBody` is now `GuzzleHttp\Stream\CachingStream` +- `Guzzle\Http\ReadLimitEntityBody` is now `GuzzleHttp\Stream\LimitStream` +- `Guzzle\Http\IoEmittyinEntityBody` has been removed. + +#### Request lifecycle events + +Requests previously submitted a large number of requests. The number of events +emitted over the lifecycle of a request has been significantly reduced to make +it easier to understand how to extend the behavior of a request. All events +emitted during the lifecycle of a request now emit a custom +`GuzzleHttp\Event\EventInterface` object that contains context providing +methods and a way in which to modify the transaction at that specific point in +time (e.g., intercept the request and set a response on the transaction). + +- `request.before_send` has been renamed to `before` and now emits a + `GuzzleHttp\Event\BeforeEvent` +- `request.complete` has been renamed to `complete` and now emits a + `GuzzleHttp\Event\CompleteEvent`. +- `request.sent` has been removed. Use `complete`. +- `request.success` has been removed. Use `complete`. +- `error` is now an event that emits a `GuzzleHttp\Event\ErrorEvent`. +- `request.exception` has been removed. Use `error`. +- `request.receive.status_line` has been removed. +- `curl.callback.progress` has been removed. Use a custom `StreamInterface` to + maintain a status update. +- `curl.callback.write` has been removed. Use a custom `StreamInterface` to + intercept writes. +- `curl.callback.read` has been removed. Use a custom `StreamInterface` to + intercept reads. + +`headers` is a new event that is emitted after the response headers of a +request have been received before the body of the response is downloaded. This +event emits a `GuzzleHttp\Event\HeadersEvent`. + +You can intercept a request and inject a response using the `intercept()` event +of a `GuzzleHttp\Event\BeforeEvent`, `GuzzleHttp\Event\CompleteEvent`, and +`GuzzleHttp\Event\ErrorEvent` event. + +See: https://docs.guzzlephp.org/en/latest/events.html + +## Inflection + +The `Guzzle\Inflection` namespace has been removed. This is not a core concern +of Guzzle. + +## Iterator + +The `Guzzle\Iterator` namespace has been removed. + +- `Guzzle\Iterator\AppendIterator`, `Guzzle\Iterator\ChunkedIterator`, and + `Guzzle\Iterator\MethodProxyIterator` are nice, but not a core requirement of + Guzzle itself. +- `Guzzle\Iterator\FilterIterator` is no longer needed because an equivalent + class is shipped with PHP 5.4. +- `Guzzle\Iterator\MapIterator` is not really needed when using PHP 5.5 because + it's easier to just wrap an iterator in a generator that maps values. + +For a replacement of these iterators, see https://github.com/nikic/iter + +## Log + +The LogPlugin has moved to https://github.com/guzzle/log-subscriber. The +`Guzzle\Log` namespace has been removed. Guzzle now relies on +`Psr\Log\LoggerInterface` for all logging. The MessageFormatter class has been +moved to `GuzzleHttp\Subscriber\Log\Formatter`. + +## Parser + +The `Guzzle\Parser` namespace has been removed. This was previously used to +make it possible to plug in custom parsers for cookies, messages, URI +templates, and URLs; however, this level of complexity is not needed in Guzzle +so it has been removed. + +- Cookie: Cookie parsing logic has been moved to + `GuzzleHttp\Cookie\SetCookie::fromString`. +- Message: Message parsing logic for both requests and responses has been moved + to `GuzzleHttp\Message\MessageFactory::fromMessage`. Message parsing is only + used in debugging or deserializing messages, so it doesn't make sense for + Guzzle as a library to add this level of complexity to parsing messages. +- UriTemplate: URI template parsing has been moved to + `GuzzleHttp\UriTemplate`. The Guzzle library will automatically use the PECL + URI template library if it is installed. +- Url: URL parsing is now performed in `GuzzleHttp\Url::fromString` (previously + it was `Guzzle\Http\Url::factory()`). If custom URL parsing is necessary, + then developers are free to subclass `GuzzleHttp\Url`. + +## Plugin + +The `Guzzle\Plugin` namespace has been renamed to `GuzzleHttp\Subscriber`. +Several plugins are shipping with the core Guzzle library under this namespace. + +- `GuzzleHttp\Subscriber\Cookie`: Replaces the old CookiePlugin. Cookie jar + code has moved to `GuzzleHttp\Cookie`. +- `GuzzleHttp\Subscriber\History`: Replaces the old HistoryPlugin. +- `GuzzleHttp\Subscriber\HttpError`: Throws errors when a bad HTTP response is + received. +- `GuzzleHttp\Subscriber\Mock`: Replaces the old MockPlugin. +- `GuzzleHttp\Subscriber\Prepare`: Prepares the body of a request just before + sending. This subscriber is attached to all requests by default. +- `GuzzleHttp\Subscriber\Redirect`: Replaces the RedirectPlugin. + +The following plugins have been removed (third-parties are free to re-implement +these if needed): + +- `GuzzleHttp\Plugin\Async` has been removed. +- `GuzzleHttp\Plugin\CurlAuth` has been removed. +- `GuzzleHttp\Plugin\ErrorResponse\ErrorResponsePlugin` has been removed. This + functionality should instead be implemented with event listeners that occur + after normal response parsing occurs in the guzzle/command package. + +The following plugins are not part of the core Guzzle package, but are provided +in separate repositories: + +- `Guzzle\Http\Plugin\BackoffPlugin` has been rewritten to be much simpler + to build custom retry policies using simple functions rather than various + chained classes. See: https://github.com/guzzle/retry-subscriber +- `Guzzle\Http\Plugin\Cache\CachePlugin` has moved to + https://github.com/guzzle/cache-subscriber +- `Guzzle\Http\Plugin\Log\LogPlugin` has moved to + https://github.com/guzzle/log-subscriber +- `Guzzle\Http\Plugin\Md5\Md5Plugin` has moved to + https://github.com/guzzle/message-integrity-subscriber +- `Guzzle\Http\Plugin\Mock\MockPlugin` has moved to + `GuzzleHttp\Subscriber\MockSubscriber`. +- `Guzzle\Http\Plugin\Oauth\OauthPlugin` has moved to + https://github.com/guzzle/oauth-subscriber + +## Service + +The service description layer of Guzzle has moved into two separate packages: + +- https://github.com/guzzle/command Provides a high level abstraction over web + services by representing web service operations using commands. +- https://github.com/guzzle/guzzle-services Provides an implementation of + guzzle/command that provides request serialization and response parsing using + Guzzle service descriptions. + +## Stream + +Stream have moved to a separate package available at +https://github.com/guzzle/streams. + +`Guzzle\Stream\StreamInterface` has been given a large update to cleanly take +on the responsibilities of `Guzzle\Http\EntityBody` and +`Guzzle\Http\EntityBodyInterface` now that they have been removed. The number +of methods implemented by the `StreamInterface` has been drastically reduced to +allow developers to more easily extend and decorate stream behavior. + +## Removed methods from StreamInterface + +- `getStream` and `setStream` have been removed to better encapsulate streams. +- `getMetadata` and `setMetadata` have been removed in favor of + `GuzzleHttp\Stream\MetadataStreamInterface`. +- `getWrapper`, `getWrapperData`, `getStreamType`, and `getUri` have all been + removed. This data is accessible when + using streams that implement `GuzzleHttp\Stream\MetadataStreamInterface`. +- `rewind` has been removed. Use `seek(0)` for a similar behavior. + +## Renamed methods + +- `detachStream` has been renamed to `detach`. +- `feof` has been renamed to `eof`. +- `ftell` has been renamed to `tell`. +- `readLine` has moved from an instance method to a static class method of + `GuzzleHttp\Stream\Stream`. + +## Metadata streams + +`GuzzleHttp\Stream\MetadataStreamInterface` has been added to denote streams +that contain additional metadata accessible via `getMetadata()`. +`GuzzleHttp\Stream\StreamInterface::getMetadata` and +`GuzzleHttp\Stream\StreamInterface::setMetadata` have been removed. + +## StreamRequestFactory + +The entire concept of the StreamRequestFactory has been removed. The way this +was used in Guzzle 3 broke the actual interface of sending streaming requests +(instead of getting back a Response, you got a StreamInterface). Streaming +PHP requests are now implemented through the `GuzzleHttp\Adapter\StreamAdapter`. + +3.6 to 3.7 +---------- + +### Deprecations + +- You can now enable E_USER_DEPRECATED warnings to see if you are using any deprecated methods.: + +```php +\Guzzle\Common\Version::$emitWarnings = true; +``` + +The following APIs and options have been marked as deprecated: + +- Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use `$request->getResponseBody()->isRepeatable()` instead. +- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +- Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead. +- Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead. +- Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated +- Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client. +- Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8. +- Marked `Guzzle\Common\Collection::inject()` as deprecated. +- Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use + `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));` or + `$client->setDefaultOption('auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));` + +3.7 introduces `request.options` as a parameter for a client configuration and as an optional argument to all creational +request methods. When paired with a client's configuration settings, these options allow you to specify default settings +for various aspects of a request. Because these options make other previous configuration options redundant, several +configuration options and methods of a client and AbstractCommand have been deprecated. + +- Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use `$client->getDefaultOption('headers')`. +- Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use `$client->setDefaultOption('headers/{header_name}', 'value')`. +- Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use `$client->setDefaultOption('params/{param_name}', 'value')` +- Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand. These will work through Guzzle 4.0 + + $command = $client->getCommand('foo', array( + 'command.headers' => array('Test' => '123'), + 'command.response_body' => '/path/to/file' + )); + + // Should be changed to: + + $command = $client->getCommand('foo', array( + 'command.request_options' => array( + 'headers' => array('Test' => '123'), + 'save_as' => '/path/to/file' + ) + )); + +### Interface changes + +Additions and changes (you will need to update any implementations or subclasses you may have created): + +- Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`: + createRequest, head, delete, put, patch, post, options, prepareRequest +- Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()` +- Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface` +- Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to + `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a + resource, string, or EntityBody into the $options parameter to specify the download location of the response. +- Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a + default `array()` +- Added `Guzzle\Stream\StreamInterface::isRepeatable` +- Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods. + +The following methods were removed from interfaces. All of these methods are still available in the concrete classes +that implement them, but you should update your code to use alternative methods: + +- Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use + `$client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or + `$client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))` or + `$client->setDefaultOption('headers/{header_name}', 'value')`. or + `$client->setDefaultOption('headers', array('header_name' => 'value'))`. +- Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use `$client->getConfig()->getPath('request.options/headers')`. +- Removed `Guzzle\Http\ClientInterface::expandTemplate()`. This is an implementation detail. +- Removed `Guzzle\Http\ClientInterface::setRequestFactory()`. This is an implementation detail. +- Removed `Guzzle\Http\ClientInterface::getCurlMulti()`. This is a very specific implementation detail. +- Removed `Guzzle\Http\Message\RequestInterface::canCache`. Use the CachePlugin. +- Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect`. Use the HistoryPlugin. +- Removed `Guzzle\Http\Message\RequestInterface::isRedirect`. Use the HistoryPlugin. + +### Cache plugin breaking changes + +- CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a + CacheStorageInterface. These two objects and interface will be removed in a future version. +- Always setting X-cache headers on cached responses +- Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin +- `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface + $request, Response $response);` +- `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);` +- `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);` +- Added `CacheStorageInterface::purge($url)` +- `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin + $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache, + CanCacheStrategyInterface $canCache = null)` +- Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)` + +3.5 to 3.6 +---------- + +* Mixed casing of headers are now forced to be a single consistent casing across all values for that header. +* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution +* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader(). + For example, setHeader() first removes the header using unset on a HeaderCollection and then calls addHeader(). + Keeping the Host header and URL host in sync is now handled by overriding the addHeader method in Request. +* Specific header implementations can be created for complex headers. When a message creates a header, it uses a + HeaderFactory which can map specific headers to specific header classes. There is now a Link header and + CacheControl header implementation. +* Moved getLinks() from Response to just be used on a Link header object. + +If you previously relied on Guzzle\Http\Message\Header::raw(), then you will need to update your code to use the +HeaderInterface (e.g. toArray(), getAll(), etc.). + +### Interface changes + +* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate +* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti() +* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in + Guzzle\Http\Curl\RequestMediator +* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string. +* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface +* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders() + +### Removed deprecated functions + +* Removed Guzzle\Parser\ParserRegister::get(). Use getParser() +* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser(). + +### Deprecations + +* The ability to case-insensitively search for header values +* Guzzle\Http\Message\Header::hasExactHeader +* Guzzle\Http\Message\Header::raw. Use getAll() +* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object + instead. + +### Other changes + +* All response header helper functions return a string rather than mixing Header objects and strings inconsistently +* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle + directly via interfaces +* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist + but are a no-op until removed. +* Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a + `Guzzle\Service\Command\ArrayCommandInterface`. +* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response + on a request while the request is still being transferred +* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess + +3.3 to 3.4 +---------- + +Base URLs of a client now follow the rules of https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.2 when merging URLs. + +3.2 to 3.3 +---------- + +### Response::getEtag() quote stripping removed + +`Guzzle\Http\Message\Response::getEtag()` no longer strips quotes around the ETag response header + +### Removed `Guzzle\Http\Utils` + +The `Guzzle\Http\Utils` class was removed. This class was only used for testing. + +### Stream wrapper and type + +`Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getStreamType()` are no longer converted to lowercase. + +### curl.emit_io became emit_io + +Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using the +'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io' + +3.1 to 3.2 +---------- + +### CurlMulti is no longer reused globally + +Before 3.2, the same CurlMulti object was reused globally for each client. This can cause issue where plugins added +to a single client can pollute requests dispatched from other clients. + +If you still wish to reuse the same CurlMulti object with each client, then you can add a listener to the +ServiceBuilder's `service_builder.create_client` event to inject a custom CurlMulti object into each client as it is +created. + +```php +$multi = new Guzzle\Http\Curl\CurlMulti(); +$builder = Guzzle\Service\Builder\ServiceBuilder::factory('/path/to/config.json'); +$builder->addListener('service_builder.create_client', function ($event) use ($multi) { + $event['client']->setCurlMulti($multi); +} +}); +``` + +### No default path + +URLs no longer have a default path value of '/' if no path was specified. + +Before: + +```php +$request = $client->get('http://www.foo.com'); +echo $request->getUrl(); +// >> http://www.foo.com/ +``` + +After: + +```php +$request = $client->get('http://www.foo.com'); +echo $request->getUrl(); +// >> http://www.foo.com +``` + +### Less verbose BadResponseException + +The exception message for `Guzzle\Http\Exception\BadResponseException` no longer contains the full HTTP request and +response information. You can, however, get access to the request and response object by calling `getRequest()` or +`getResponse()` on the exception object. + +### Query parameter aggregation + +Multi-valued query parameters are no longer aggregated using a callback function. `Guzzle\Http\Query` now has a +setAggregator() method that accepts a `Guzzle\Http\QueryAggregator\QueryAggregatorInterface` object. This object is +responsible for handling the aggregation of multi-valued query string variables into a flattened hash. + +2.8 to 3.x +---------- + +### Guzzle\Service\Inspector + +Change `\Guzzle\Service\Inspector::fromConfig` to `\Guzzle\Common\Collection::fromConfig` + +**Before** + +```php +use Guzzle\Service\Inspector; + +class YourClient extends \Guzzle\Service\Client +{ + public static function factory($config = array()) + { + $default = array(); + $required = array('base_url', 'username', 'api_key'); + $config = Inspector::fromConfig($config, $default, $required); + + $client = new self( + $config->get('base_url'), + $config->get('username'), + $config->get('api_key') + ); + $client->setConfig($config); + + $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json')); + + return $client; + } +``` + +**After** + +```php +use Guzzle\Common\Collection; + +class YourClient extends \Guzzle\Service\Client +{ + public static function factory($config = array()) + { + $default = array(); + $required = array('base_url', 'username', 'api_key'); + $config = Collection::fromConfig($config, $default, $required); + + $client = new self( + $config->get('base_url'), + $config->get('username'), + $config->get('api_key') + ); + $client->setConfig($config); + + $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json')); + + return $client; + } +``` + +### Convert XML Service Descriptions to JSON + +**Before** + +```xml + + + + + + Get a list of groups + + + Uses a search query to get a list of groups + + + + Create a group + + + + + Delete a group by ID + + + + + + + Update a group + + + + + + +``` + +**After** + +```json +{ + "name": "Zendesk REST API v2", + "apiVersion": "2012-12-31", + "description":"Provides access to Zendesk views, groups, tickets, ticket fields, and users", + "operations": { + "list_groups": { + "httpMethod":"GET", + "uri": "groups.json", + "summary": "Get a list of groups" + }, + "search_groups":{ + "httpMethod":"GET", + "uri": "search.json?query=\"{query} type:group\"", + "summary": "Uses a search query to get a list of groups", + "parameters":{ + "query":{ + "location": "uri", + "description":"Zendesk Search Query", + "type": "string", + "required": true + } + } + }, + "create_group": { + "httpMethod":"POST", + "uri": "groups.json", + "summary": "Create a group", + "parameters":{ + "data": { + "type": "array", + "location": "body", + "description":"Group JSON", + "filters": "json_encode", + "required": true + }, + "Content-Type":{ + "type": "string", + "location":"header", + "static": "application/json" + } + } + }, + "delete_group": { + "httpMethod":"DELETE", + "uri": "groups/{id}.json", + "summary": "Delete a group", + "parameters":{ + "id":{ + "location": "uri", + "description":"Group to delete by ID", + "type": "integer", + "required": true + } + } + }, + "get_group": { + "httpMethod":"GET", + "uri": "groups/{id}.json", + "summary": "Get a ticket", + "parameters":{ + "id":{ + "location": "uri", + "description":"Group to get by ID", + "type": "integer", + "required": true + } + } + }, + "update_group": { + "httpMethod":"PUT", + "uri": "groups/{id}.json", + "summary": "Update a group", + "parameters":{ + "id": { + "location": "uri", + "description":"Group to update by ID", + "type": "integer", + "required": true + }, + "data": { + "type": "array", + "location": "body", + "description":"Group JSON", + "filters": "json_encode", + "required": true + }, + "Content-Type":{ + "type": "string", + "location":"header", + "static": "application/json" + } + } + } +} +``` + +### Guzzle\Service\Description\ServiceDescription + +Commands are now called Operations + +**Before** + +```php +use Guzzle\Service\Description\ServiceDescription; + +$sd = new ServiceDescription(); +$sd->getCommands(); // @returns ApiCommandInterface[] +$sd->hasCommand($name); +$sd->getCommand($name); // @returns ApiCommandInterface|null +$sd->addCommand($command); // @param ApiCommandInterface $command +``` + +**After** + +```php +use Guzzle\Service\Description\ServiceDescription; + +$sd = new ServiceDescription(); +$sd->getOperations(); // @returns OperationInterface[] +$sd->hasOperation($name); +$sd->getOperation($name); // @returns OperationInterface|null +$sd->addOperation($operation); // @param OperationInterface $operation +``` + +### Guzzle\Common\Inflection\Inflector + +Namespace is now `Guzzle\Inflection\Inflector` + +### Guzzle\Http\Plugin + +Namespace is now `Guzzle\Plugin`. Many other changes occur within this namespace and are detailed in their own sections below. + +### Guzzle\Http\Plugin\LogPlugin and Guzzle\Common\Log + +Now `Guzzle\Plugin\Log\LogPlugin` and `Guzzle\Log` respectively. + +**Before** + +```php +use Guzzle\Common\Log\ClosureLogAdapter; +use Guzzle\Http\Plugin\LogPlugin; + +/** @var \Guzzle\Http\Client */ +$client; + +// $verbosity is an integer indicating desired message verbosity level +$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $verbosity = LogPlugin::LOG_VERBOSE); +``` + +**After** + +```php +use Guzzle\Log\ClosureLogAdapter; +use Guzzle\Log\MessageFormatter; +use Guzzle\Plugin\Log\LogPlugin; + +/** @var \Guzzle\Http\Client */ +$client; + +// $format is a string indicating desired message format -- @see MessageFormatter +$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $format = MessageFormatter::DEBUG_FORMAT); +``` + +### Guzzle\Http\Plugin\CurlAuthPlugin + +Now `Guzzle\Plugin\CurlAuth\CurlAuthPlugin`. + +### Guzzle\Http\Plugin\ExponentialBackoffPlugin + +Now `Guzzle\Plugin\Backoff\BackoffPlugin`, and other changes. + +**Before** + +```php +use Guzzle\Http\Plugin\ExponentialBackoffPlugin; + +$backoffPlugin = new ExponentialBackoffPlugin($maxRetries, array_merge( + ExponentialBackoffPlugin::getDefaultFailureCodes(), array(429) + )); + +$client->addSubscriber($backoffPlugin); +``` + +**After** + +```php +use Guzzle\Plugin\Backoff\BackoffPlugin; +use Guzzle\Plugin\Backoff\HttpBackoffStrategy; + +// Use convenient factory method instead -- see implementation for ideas of what +// you can do with chaining backoff strategies +$backoffPlugin = BackoffPlugin::getExponentialBackoff($maxRetries, array_merge( + HttpBackoffStrategy::getDefaultFailureCodes(), array(429) + )); +$client->addSubscriber($backoffPlugin); +``` + +### Known Issues + +#### [BUG] Accept-Encoding header behavior changed unintentionally. + +(See #217) (Fixed in 09daeb8c666fb44499a0646d655a8ae36456575e) + +In version 2.8 setting the `Accept-Encoding` header would set the CURLOPT_ENCODING option, which permitted cURL to +properly handle gzip/deflate compressed responses from the server. In versions affected by this bug this does not happen. +See issue #217 for a workaround, or use a version containing the fix. diff --git a/vendor/guzzlehttp/guzzle/composer.json b/vendor/guzzlehttp/guzzle/composer.json new file mode 100644 index 00000000..69583d7c --- /dev/null +++ b/vendor/guzzlehttp/guzzle/composer.json @@ -0,0 +1,103 @@ +{ + "name": "guzzlehttp/guzzle", + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "framework", + "http", + "rest", + "web service", + "curl", + "client", + "HTTP client", + "PSR-7", + "PSR-18" + ], + "license": "MIT", + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "require": { + "php": "^7.2.5 || ^8.0", + "ext-json": "*", + "guzzlehttp/promises": "^1.5.3 || ^2.0.1", + "guzzlehttp/psr7": "^1.9.1 || ^2.5.1", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "bamarni/composer-bin-plugin": "^1.8.2", + "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.36 || ^9.6.15", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "config": { + "allow-plugins": { + "bamarni/composer-bin-plugin": true + }, + "preferred-install": "dist", + "sort-packages": true + }, + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "autoload-dev": { + "psr-4": { + "GuzzleHttp\\Tests\\": "tests/" + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/BodySummarizer.php b/vendor/guzzlehttp/guzzle/src/BodySummarizer.php new file mode 100644 index 00000000..6eca94ef --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/BodySummarizer.php @@ -0,0 +1,28 @@ +truncateAt = $truncateAt; + } + + /** + * Returns a summarized message body. + */ + public function summarize(MessageInterface $message): ?string + { + return $this->truncateAt === null + ? \GuzzleHttp\Psr7\Message::bodySummary($message) + : \GuzzleHttp\Psr7\Message::bodySummary($message, $this->truncateAt); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/BodySummarizerInterface.php b/vendor/guzzlehttp/guzzle/src/BodySummarizerInterface.php new file mode 100644 index 00000000..3e02e036 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/BodySummarizerInterface.php @@ -0,0 +1,13 @@ + 'http://www.foo.com/1.0/', + * 'timeout' => 0, + * 'allow_redirects' => false, + * 'proxy' => '192.168.16.1:10' + * ]); + * + * Client configuration settings include the following options: + * + * - handler: (callable) Function that transfers HTTP requests over the + * wire. The function is called with a Psr7\Http\Message\RequestInterface + * and array of transfer options, and must return a + * GuzzleHttp\Promise\PromiseInterface that is fulfilled with a + * Psr7\Http\Message\ResponseInterface on success. + * If no handler is provided, a default handler will be created + * that enables all of the request options below by attaching all of the + * default middleware to the handler. + * - base_uri: (string|UriInterface) Base URI of the client that is merged + * into relative URIs. Can be a string or instance of UriInterface. + * - **: any request option + * + * @param array $config Client configuration settings. + * + * @see \GuzzleHttp\RequestOptions for a list of available request options. + */ + public function __construct(array $config = []) + { + if (!isset($config['handler'])) { + $config['handler'] = HandlerStack::create(); + } elseif (!\is_callable($config['handler'])) { + throw new InvalidArgumentException('handler must be a callable'); + } + + // Convert the base_uri to a UriInterface + if (isset($config['base_uri'])) { + $config['base_uri'] = Psr7\Utils::uriFor($config['base_uri']); + } + + $this->configureDefaults($config); + } + + /** + * @param string $method + * @param array $args + * + * @return PromiseInterface|ResponseInterface + * + * @deprecated Client::__call will be removed in guzzlehttp/guzzle:8.0. + */ + public function __call($method, $args) + { + if (\count($args) < 1) { + throw new InvalidArgumentException('Magic request methods require a URI and optional options array'); + } + + $uri = $args[0]; + $opts = $args[1] ?? []; + + return \substr($method, -5) === 'Async' + ? $this->requestAsync(\substr($method, 0, -5), $uri, $opts) + : $this->request($method, $uri, $opts); + } + + /** + * Asynchronously send an HTTP request. + * + * @param array $options Request options to apply to the given + * request and to the transfer. See \GuzzleHttp\RequestOptions. + */ + public function sendAsync(RequestInterface $request, array $options = []): PromiseInterface + { + // Merge the base URI into the request URI if needed. + $options = $this->prepareDefaults($options); + + return $this->transfer( + $request->withUri($this->buildUri($request->getUri(), $options), $request->hasHeader('Host')), + $options + ); + } + + /** + * Send an HTTP request. + * + * @param array $options Request options to apply to the given + * request and to the transfer. See \GuzzleHttp\RequestOptions. + * + * @throws GuzzleException + */ + public function send(RequestInterface $request, array $options = []): ResponseInterface + { + $options[RequestOptions::SYNCHRONOUS] = true; + + return $this->sendAsync($request, $options)->wait(); + } + + /** + * The HttpClient PSR (PSR-18) specify this method. + * + * {@inheritDoc} + */ + public function sendRequest(RequestInterface $request): ResponseInterface + { + $options[RequestOptions::SYNCHRONOUS] = true; + $options[RequestOptions::ALLOW_REDIRECTS] = false; + $options[RequestOptions::HTTP_ERRORS] = false; + + return $this->sendAsync($request, $options)->wait(); + } + + /** + * Create and send an asynchronous HTTP request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string $method HTTP method + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. See \GuzzleHttp\RequestOptions. + */ + public function requestAsync(string $method, $uri = '', array $options = []): PromiseInterface + { + $options = $this->prepareDefaults($options); + // Remove request modifying parameter because it can be done up-front. + $headers = $options['headers'] ?? []; + $body = $options['body'] ?? null; + $version = $options['version'] ?? '1.1'; + // Merge the URI into the base URI. + $uri = $this->buildUri(Psr7\Utils::uriFor($uri), $options); + if (\is_array($body)) { + throw $this->invalidBody(); + } + $request = new Psr7\Request($method, $uri, $headers, $body, $version); + // Remove the option so that they are not doubly-applied. + unset($options['headers'], $options['body'], $options['version']); + + return $this->transfer($request, $options); + } + + /** + * Create and send an HTTP request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. + * + * @param string $method HTTP method. + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. See \GuzzleHttp\RequestOptions. + * + * @throws GuzzleException + */ + public function request(string $method, $uri = '', array $options = []): ResponseInterface + { + $options[RequestOptions::SYNCHRONOUS] = true; + + return $this->requestAsync($method, $uri, $options)->wait(); + } + + /** + * Get a client configuration option. + * + * These options include default request options of the client, a "handler" + * (if utilized by the concrete client), and a "base_uri" if utilized by + * the concrete client. + * + * @param string|null $option The config option to retrieve. + * + * @return mixed + * + * @deprecated Client::getConfig will be removed in guzzlehttp/guzzle:8.0. + */ + public function getConfig(string $option = null) + { + return $option === null + ? $this->config + : ($this->config[$option] ?? null); + } + + private function buildUri(UriInterface $uri, array $config): UriInterface + { + if (isset($config['base_uri'])) { + $uri = Psr7\UriResolver::resolve(Psr7\Utils::uriFor($config['base_uri']), $uri); + } + + if (isset($config['idn_conversion']) && ($config['idn_conversion'] !== false)) { + $idnOptions = ($config['idn_conversion'] === true) ? \IDNA_DEFAULT : $config['idn_conversion']; + $uri = Utils::idnUriConvert($uri, $idnOptions); + } + + return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri; + } + + /** + * Configures the default options for a client. + */ + private function configureDefaults(array $config): void + { + $defaults = [ + 'allow_redirects' => RedirectMiddleware::$defaultSettings, + 'http_errors' => true, + 'decode_content' => true, + 'verify' => true, + 'cookies' => false, + 'idn_conversion' => false, + ]; + + // Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set. + + // We can only trust the HTTP_PROXY environment variable in a CLI + // process due to the fact that PHP has no reliable mechanism to + // get environment variables that start with "HTTP_". + if (\PHP_SAPI === 'cli' && ($proxy = Utils::getenv('HTTP_PROXY'))) { + $defaults['proxy']['http'] = $proxy; + } + + if ($proxy = Utils::getenv('HTTPS_PROXY')) { + $defaults['proxy']['https'] = $proxy; + } + + if ($noProxy = Utils::getenv('NO_PROXY')) { + $cleanedNoProxy = \str_replace(' ', '', $noProxy); + $defaults['proxy']['no'] = \explode(',', $cleanedNoProxy); + } + + $this->config = $config + $defaults; + + if (!empty($config['cookies']) && $config['cookies'] === true) { + $this->config['cookies'] = new CookieJar(); + } + + // Add the default user-agent header. + if (!isset($this->config['headers'])) { + $this->config['headers'] = ['User-Agent' => Utils::defaultUserAgent()]; + } else { + // Add the User-Agent header if one was not already set. + foreach (\array_keys($this->config['headers']) as $name) { + if (\strtolower($name) === 'user-agent') { + return; + } + } + $this->config['headers']['User-Agent'] = Utils::defaultUserAgent(); + } + } + + /** + * Merges default options into the array. + * + * @param array $options Options to modify by reference + */ + private function prepareDefaults(array $options): array + { + $defaults = $this->config; + + if (!empty($defaults['headers'])) { + // Default headers are only added if they are not present. + $defaults['_conditional'] = $defaults['headers']; + unset($defaults['headers']); + } + + // Special handling for headers is required as they are added as + // conditional headers and as headers passed to a request ctor. + if (\array_key_exists('headers', $options)) { + // Allows default headers to be unset. + if ($options['headers'] === null) { + $defaults['_conditional'] = []; + unset($options['headers']); + } elseif (!\is_array($options['headers'])) { + throw new InvalidArgumentException('headers must be an array'); + } + } + + // Shallow merge defaults underneath options. + $result = $options + $defaults; + + // Remove null values. + foreach ($result as $k => $v) { + if ($v === null) { + unset($result[$k]); + } + } + + return $result; + } + + /** + * Transfers the given request and applies request options. + * + * The URI of the request is not modified and the request options are used + * as-is without merging in default options. + * + * @param array $options See \GuzzleHttp\RequestOptions. + */ + private function transfer(RequestInterface $request, array $options): PromiseInterface + { + $request = $this->applyOptions($request, $options); + /** @var HandlerStack $handler */ + $handler = $options['handler']; + + try { + return P\Create::promiseFor($handler($request, $options)); + } catch (\Exception $e) { + return P\Create::rejectionFor($e); + } + } + + /** + * Applies the array of request options to a request. + */ + private function applyOptions(RequestInterface $request, array &$options): RequestInterface + { + $modify = [ + 'set_headers' => [], + ]; + + if (isset($options['headers'])) { + if (array_keys($options['headers']) === range(0, count($options['headers']) - 1)) { + throw new InvalidArgumentException('The headers array must have header name as keys.'); + } + $modify['set_headers'] = $options['headers']; + unset($options['headers']); + } + + if (isset($options['form_params'])) { + if (isset($options['multipart'])) { + throw new InvalidArgumentException('You cannot use ' + .'form_params and multipart at the same time. Use the ' + .'form_params option if you want to send application/' + .'x-www-form-urlencoded requests, and the multipart ' + .'option to send multipart/form-data requests.'); + } + $options['body'] = \http_build_query($options['form_params'], '', '&'); + unset($options['form_params']); + // Ensure that we don't have the header in different case and set the new value. + $options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']); + $options['_conditional']['Content-Type'] = 'application/x-www-form-urlencoded'; + } + + if (isset($options['multipart'])) { + $options['body'] = new Psr7\MultipartStream($options['multipart']); + unset($options['multipart']); + } + + if (isset($options['json'])) { + $options['body'] = Utils::jsonEncode($options['json']); + unset($options['json']); + // Ensure that we don't have the header in different case and set the new value. + $options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']); + $options['_conditional']['Content-Type'] = 'application/json'; + } + + if (!empty($options['decode_content']) + && $options['decode_content'] !== true + ) { + // Ensure that we don't have the header in different case and set the new value. + $options['_conditional'] = Psr7\Utils::caselessRemove(['Accept-Encoding'], $options['_conditional']); + $modify['set_headers']['Accept-Encoding'] = $options['decode_content']; + } + + if (isset($options['body'])) { + if (\is_array($options['body'])) { + throw $this->invalidBody(); + } + $modify['body'] = Psr7\Utils::streamFor($options['body']); + unset($options['body']); + } + + if (!empty($options['auth']) && \is_array($options['auth'])) { + $value = $options['auth']; + $type = isset($value[2]) ? \strtolower($value[2]) : 'basic'; + switch ($type) { + case 'basic': + // Ensure that we don't have the header in different case and set the new value. + $modify['set_headers'] = Psr7\Utils::caselessRemove(['Authorization'], $modify['set_headers']); + $modify['set_headers']['Authorization'] = 'Basic ' + .\base64_encode("$value[0]:$value[1]"); + break; + case 'digest': + // @todo: Do not rely on curl + $options['curl'][\CURLOPT_HTTPAUTH] = \CURLAUTH_DIGEST; + $options['curl'][\CURLOPT_USERPWD] = "$value[0]:$value[1]"; + break; + case 'ntlm': + $options['curl'][\CURLOPT_HTTPAUTH] = \CURLAUTH_NTLM; + $options['curl'][\CURLOPT_USERPWD] = "$value[0]:$value[1]"; + break; + } + } + + if (isset($options['query'])) { + $value = $options['query']; + if (\is_array($value)) { + $value = \http_build_query($value, '', '&', \PHP_QUERY_RFC3986); + } + if (!\is_string($value)) { + throw new InvalidArgumentException('query must be a string or array'); + } + $modify['query'] = $value; + unset($options['query']); + } + + // Ensure that sink is not an invalid value. + if (isset($options['sink'])) { + // TODO: Add more sink validation? + if (\is_bool($options['sink'])) { + throw new InvalidArgumentException('sink must not be a boolean'); + } + } + + if (isset($options['version'])) { + $modify['version'] = $options['version']; + } + + $request = Psr7\Utils::modifyRequest($request, $modify); + if ($request->getBody() instanceof Psr7\MultipartStream) { + // Use a multipart/form-data POST if a Content-Type is not set. + // Ensure that we don't have the header in different case and set the new value. + $options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']); + $options['_conditional']['Content-Type'] = 'multipart/form-data; boundary=' + .$request->getBody()->getBoundary(); + } + + // Merge in conditional headers if they are not present. + if (isset($options['_conditional'])) { + // Build up the changes so it's in a single clone of the message. + $modify = []; + foreach ($options['_conditional'] as $k => $v) { + if (!$request->hasHeader($k)) { + $modify['set_headers'][$k] = $v; + } + } + $request = Psr7\Utils::modifyRequest($request, $modify); + // Don't pass this internal value along to middleware/handlers. + unset($options['_conditional']); + } + + return $request; + } + + /** + * Return an InvalidArgumentException with pre-set message. + */ + private function invalidBody(): InvalidArgumentException + { + return new InvalidArgumentException('Passing in the "body" request ' + .'option as an array to send a request is not supported. ' + .'Please use the "form_params" request option to send a ' + .'application/x-www-form-urlencoded request, or the "multipart" ' + .'request option to send a multipart/form-data request.'); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/ClientInterface.php b/vendor/guzzlehttp/guzzle/src/ClientInterface.php new file mode 100644 index 00000000..1788e16a --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/ClientInterface.php @@ -0,0 +1,84 @@ +request('GET', $uri, $options); + } + + /** + * Create and send an HTTP HEAD request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + * + * @throws GuzzleException + */ + public function head($uri, array $options = []): ResponseInterface + { + return $this->request('HEAD', $uri, $options); + } + + /** + * Create and send an HTTP PUT request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + * + * @throws GuzzleException + */ + public function put($uri, array $options = []): ResponseInterface + { + return $this->request('PUT', $uri, $options); + } + + /** + * Create and send an HTTP POST request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + * + * @throws GuzzleException + */ + public function post($uri, array $options = []): ResponseInterface + { + return $this->request('POST', $uri, $options); + } + + /** + * Create and send an HTTP PATCH request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + * + * @throws GuzzleException + */ + public function patch($uri, array $options = []): ResponseInterface + { + return $this->request('PATCH', $uri, $options); + } + + /** + * Create and send an HTTP DELETE request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + * + * @throws GuzzleException + */ + public function delete($uri, array $options = []): ResponseInterface + { + return $this->request('DELETE', $uri, $options); + } + + /** + * Create and send an asynchronous HTTP request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string $method HTTP method + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + abstract public function requestAsync(string $method, $uri, array $options = []): PromiseInterface; + + /** + * Create and send an asynchronous HTTP GET request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function getAsync($uri, array $options = []): PromiseInterface + { + return $this->requestAsync('GET', $uri, $options); + } + + /** + * Create and send an asynchronous HTTP HEAD request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function headAsync($uri, array $options = []): PromiseInterface + { + return $this->requestAsync('HEAD', $uri, $options); + } + + /** + * Create and send an asynchronous HTTP PUT request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function putAsync($uri, array $options = []): PromiseInterface + { + return $this->requestAsync('PUT', $uri, $options); + } + + /** + * Create and send an asynchronous HTTP POST request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function postAsync($uri, array $options = []): PromiseInterface + { + return $this->requestAsync('POST', $uri, $options); + } + + /** + * Create and send an asynchronous HTTP PATCH request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function patchAsync($uri, array $options = []): PromiseInterface + { + return $this->requestAsync('PATCH', $uri, $options); + } + + /** + * Create and send an asynchronous HTTP DELETE request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function deleteAsync($uri, array $options = []): PromiseInterface + { + return $this->requestAsync('DELETE', $uri, $options); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php b/vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php new file mode 100644 index 00000000..c29b4b7e --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php @@ -0,0 +1,307 @@ +strictMode = $strictMode; + + foreach ($cookieArray as $cookie) { + if (!($cookie instanceof SetCookie)) { + $cookie = new SetCookie($cookie); + } + $this->setCookie($cookie); + } + } + + /** + * Create a new Cookie jar from an associative array and domain. + * + * @param array $cookies Cookies to create the jar from + * @param string $domain Domain to set the cookies to + */ + public static function fromArray(array $cookies, string $domain): self + { + $cookieJar = new self(); + foreach ($cookies as $name => $value) { + $cookieJar->setCookie(new SetCookie([ + 'Domain' => $domain, + 'Name' => $name, + 'Value' => $value, + 'Discard' => true, + ])); + } + + return $cookieJar; + } + + /** + * Evaluate if this cookie should be persisted to storage + * that survives between requests. + * + * @param SetCookie $cookie Being evaluated. + * @param bool $allowSessionCookies If we should persist session cookies + */ + public static function shouldPersist(SetCookie $cookie, bool $allowSessionCookies = false): bool + { + if ($cookie->getExpires() || $allowSessionCookies) { + if (!$cookie->getDiscard()) { + return true; + } + } + + return false; + } + + /** + * Finds and returns the cookie based on the name + * + * @param string $name cookie name to search for + * + * @return SetCookie|null cookie that was found or null if not found + */ + public function getCookieByName(string $name): ?SetCookie + { + foreach ($this->cookies as $cookie) { + if ($cookie->getName() !== null && \strcasecmp($cookie->getName(), $name) === 0) { + return $cookie; + } + } + + return null; + } + + public function toArray(): array + { + return \array_map(static function (SetCookie $cookie): array { + return $cookie->toArray(); + }, $this->getIterator()->getArrayCopy()); + } + + public function clear(string $domain = null, string $path = null, string $name = null): void + { + if (!$domain) { + $this->cookies = []; + + return; + } elseif (!$path) { + $this->cookies = \array_filter( + $this->cookies, + static function (SetCookie $cookie) use ($domain): bool { + return !$cookie->matchesDomain($domain); + } + ); + } elseif (!$name) { + $this->cookies = \array_filter( + $this->cookies, + static function (SetCookie $cookie) use ($path, $domain): bool { + return !($cookie->matchesPath($path) + && $cookie->matchesDomain($domain)); + } + ); + } else { + $this->cookies = \array_filter( + $this->cookies, + static function (SetCookie $cookie) use ($path, $domain, $name) { + return !($cookie->getName() == $name + && $cookie->matchesPath($path) + && $cookie->matchesDomain($domain)); + } + ); + } + } + + public function clearSessionCookies(): void + { + $this->cookies = \array_filter( + $this->cookies, + static function (SetCookie $cookie): bool { + return !$cookie->getDiscard() && $cookie->getExpires(); + } + ); + } + + public function setCookie(SetCookie $cookie): bool + { + // If the name string is empty (but not 0), ignore the set-cookie + // string entirely. + $name = $cookie->getName(); + if (!$name && $name !== '0') { + return false; + } + + // Only allow cookies with set and valid domain, name, value + $result = $cookie->validate(); + if ($result !== true) { + if ($this->strictMode) { + throw new \RuntimeException('Invalid cookie: '.$result); + } + $this->removeCookieIfEmpty($cookie); + + return false; + } + + // Resolve conflicts with previously set cookies + foreach ($this->cookies as $i => $c) { + // Two cookies are identical, when their path, and domain are + // identical. + if ($c->getPath() != $cookie->getPath() + || $c->getDomain() != $cookie->getDomain() + || $c->getName() != $cookie->getName() + ) { + continue; + } + + // The previously set cookie is a discard cookie and this one is + // not so allow the new cookie to be set + if (!$cookie->getDiscard() && $c->getDiscard()) { + unset($this->cookies[$i]); + continue; + } + + // If the new cookie's expiration is further into the future, then + // replace the old cookie + if ($cookie->getExpires() > $c->getExpires()) { + unset($this->cookies[$i]); + continue; + } + + // If the value has changed, we better change it + if ($cookie->getValue() !== $c->getValue()) { + unset($this->cookies[$i]); + continue; + } + + // The cookie exists, so no need to continue + return false; + } + + $this->cookies[] = $cookie; + + return true; + } + + public function count(): int + { + return \count($this->cookies); + } + + /** + * @return \ArrayIterator + */ + public function getIterator(): \ArrayIterator + { + return new \ArrayIterator(\array_values($this->cookies)); + } + + public function extractCookies(RequestInterface $request, ResponseInterface $response): void + { + if ($cookieHeader = $response->getHeader('Set-Cookie')) { + foreach ($cookieHeader as $cookie) { + $sc = SetCookie::fromString($cookie); + if (!$sc->getDomain()) { + $sc->setDomain($request->getUri()->getHost()); + } + if (0 !== \strpos($sc->getPath(), '/')) { + $sc->setPath($this->getCookiePathFromRequest($request)); + } + if (!$sc->matchesDomain($request->getUri()->getHost())) { + continue; + } + // Note: At this point `$sc->getDomain()` being a public suffix should + // be rejected, but we don't want to pull in the full PSL dependency. + $this->setCookie($sc); + } + } + } + + /** + * Computes cookie path following RFC 6265 section 5.1.4 + * + * @see https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.4 + */ + private function getCookiePathFromRequest(RequestInterface $request): string + { + $uriPath = $request->getUri()->getPath(); + if ('' === $uriPath) { + return '/'; + } + if (0 !== \strpos($uriPath, '/')) { + return '/'; + } + if ('/' === $uriPath) { + return '/'; + } + $lastSlashPos = \strrpos($uriPath, '/'); + if (0 === $lastSlashPos || false === $lastSlashPos) { + return '/'; + } + + return \substr($uriPath, 0, $lastSlashPos); + } + + public function withCookieHeader(RequestInterface $request): RequestInterface + { + $values = []; + $uri = $request->getUri(); + $scheme = $uri->getScheme(); + $host = $uri->getHost(); + $path = $uri->getPath() ?: '/'; + + foreach ($this->cookies as $cookie) { + if ($cookie->matchesPath($path) + && $cookie->matchesDomain($host) + && !$cookie->isExpired() + && (!$cookie->getSecure() || $scheme === 'https') + ) { + $values[] = $cookie->getName().'=' + .$cookie->getValue(); + } + } + + return $values + ? $request->withHeader('Cookie', \implode('; ', $values)) + : $request; + } + + /** + * If a cookie already exists and the server asks to set it again with a + * null value, the cookie must be deleted. + */ + private function removeCookieIfEmpty(SetCookie $cookie): void + { + $cookieValue = $cookie->getValue(); + if ($cookieValue === null || $cookieValue === '') { + $this->clear( + $cookie->getDomain(), + $cookie->getPath(), + $cookie->getName() + ); + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php b/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php new file mode 100644 index 00000000..8c55cc6f --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php @@ -0,0 +1,80 @@ + + */ +interface CookieJarInterface extends \Countable, \IteratorAggregate +{ + /** + * Create a request with added cookie headers. + * + * If no matching cookies are found in the cookie jar, then no Cookie + * header is added to the request and the same request is returned. + * + * @param RequestInterface $request Request object to modify. + * + * @return RequestInterface returns the modified request. + */ + public function withCookieHeader(RequestInterface $request): RequestInterface; + + /** + * Extract cookies from an HTTP response and store them in the CookieJar. + * + * @param RequestInterface $request Request that was sent + * @param ResponseInterface $response Response that was received + */ + public function extractCookies(RequestInterface $request, ResponseInterface $response): void; + + /** + * Sets a cookie in the cookie jar. + * + * @param SetCookie $cookie Cookie to set. + * + * @return bool Returns true on success or false on failure + */ + public function setCookie(SetCookie $cookie): bool; + + /** + * Remove cookies currently held in the cookie jar. + * + * Invoking this method without arguments will empty the whole cookie jar. + * If given a $domain argument only cookies belonging to that domain will + * be removed. If given a $domain and $path argument, cookies belonging to + * the specified path within that domain are removed. If given all three + * arguments, then the cookie with the specified name, path and domain is + * removed. + * + * @param string|null $domain Clears cookies matching a domain + * @param string|null $path Clears cookies matching a domain and path + * @param string|null $name Clears cookies matching a domain, path, and name + */ + public function clear(string $domain = null, string $path = null, string $name = null): void; + + /** + * Discard all sessions cookies. + * + * Removes cookies that don't have an expire field or a have a discard + * field set to true. To be called when the user agent shuts down according + * to RFC 2965. + */ + public function clearSessionCookies(): void; + + /** + * Converts the cookie jar to an array. + */ + public function toArray(): array; +} diff --git a/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php b/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php new file mode 100644 index 00000000..290236d5 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php @@ -0,0 +1,101 @@ +filename = $cookieFile; + $this->storeSessionCookies = $storeSessionCookies; + + if (\file_exists($cookieFile)) { + $this->load($cookieFile); + } + } + + /** + * Saves the file when shutting down + */ + public function __destruct() + { + $this->save($this->filename); + } + + /** + * Saves the cookies to a file. + * + * @param string $filename File to save + * + * @throws \RuntimeException if the file cannot be found or created + */ + public function save(string $filename): void + { + $json = []; + /** @var SetCookie $cookie */ + foreach ($this as $cookie) { + if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) { + $json[] = $cookie->toArray(); + } + } + + $jsonStr = Utils::jsonEncode($json); + if (false === \file_put_contents($filename, $jsonStr, \LOCK_EX)) { + throw new \RuntimeException("Unable to save file {$filename}"); + } + } + + /** + * Load cookies from a JSON formatted file. + * + * Old cookies are kept unless overwritten by newly loaded ones. + * + * @param string $filename Cookie file to load. + * + * @throws \RuntimeException if the file cannot be loaded. + */ + public function load(string $filename): void + { + $json = \file_get_contents($filename); + if (false === $json) { + throw new \RuntimeException("Unable to load file {$filename}"); + } + if ($json === '') { + return; + } + + $data = Utils::jsonDecode($json, true); + if (\is_array($data)) { + foreach ($data as $cookie) { + $this->setCookie(new SetCookie($cookie)); + } + } elseif (\is_scalar($data) && !empty($data)) { + throw new \RuntimeException("Invalid cookie file: {$filename}"); + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php b/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php new file mode 100644 index 00000000..cb3e67c6 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php @@ -0,0 +1,77 @@ +sessionKey = $sessionKey; + $this->storeSessionCookies = $storeSessionCookies; + $this->load(); + } + + /** + * Saves cookies to session when shutting down + */ + public function __destruct() + { + $this->save(); + } + + /** + * Save cookies to the client session + */ + public function save(): void + { + $json = []; + /** @var SetCookie $cookie */ + foreach ($this as $cookie) { + if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) { + $json[] = $cookie->toArray(); + } + } + + $_SESSION[$this->sessionKey] = \json_encode($json); + } + + /** + * Load the contents of the client session into the data array + */ + protected function load(): void + { + if (!isset($_SESSION[$this->sessionKey])) { + return; + } + $data = \json_decode($_SESSION[$this->sessionKey], true); + if (\is_array($data)) { + foreach ($data as $cookie) { + $this->setCookie(new SetCookie($cookie)); + } + } elseif (\strlen($data)) { + throw new \RuntimeException('Invalid cookie data'); + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php b/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php new file mode 100644 index 00000000..c9806da8 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php @@ -0,0 +1,488 @@ + null, + 'Value' => null, + 'Domain' => null, + 'Path' => '/', + 'Max-Age' => null, + 'Expires' => null, + 'Secure' => false, + 'Discard' => false, + 'HttpOnly' => false, + ]; + + /** + * @var array Cookie data + */ + private $data; + + /** + * Create a new SetCookie object from a string. + * + * @param string $cookie Set-Cookie header string + */ + public static function fromString(string $cookie): self + { + // Create the default return array + $data = self::$defaults; + // Explode the cookie string using a series of semicolons + $pieces = \array_filter(\array_map('trim', \explode(';', $cookie))); + // The name of the cookie (first kvp) must exist and include an equal sign. + if (!isset($pieces[0]) || \strpos($pieces[0], '=') === false) { + return new self($data); + } + + // Add the cookie pieces into the parsed data array + foreach ($pieces as $part) { + $cookieParts = \explode('=', $part, 2); + $key = \trim($cookieParts[0]); + $value = isset($cookieParts[1]) + ? \trim($cookieParts[1], " \n\r\t\0\x0B") + : true; + + // Only check for non-cookies when cookies have been found + if (!isset($data['Name'])) { + $data['Name'] = $key; + $data['Value'] = $value; + } else { + foreach (\array_keys(self::$defaults) as $search) { + if (!\strcasecmp($search, $key)) { + if ($search === 'Max-Age') { + if (is_numeric($value)) { + $data[$search] = (int) $value; + } + } else { + $data[$search] = $value; + } + continue 2; + } + } + $data[$key] = $value; + } + } + + return new self($data); + } + + /** + * @param array $data Array of cookie data provided by a Cookie parser + */ + public function __construct(array $data = []) + { + $this->data = self::$defaults; + + if (isset($data['Name'])) { + $this->setName($data['Name']); + } + + if (isset($data['Value'])) { + $this->setValue($data['Value']); + } + + if (isset($data['Domain'])) { + $this->setDomain($data['Domain']); + } + + if (isset($data['Path'])) { + $this->setPath($data['Path']); + } + + if (isset($data['Max-Age'])) { + $this->setMaxAge($data['Max-Age']); + } + + if (isset($data['Expires'])) { + $this->setExpires($data['Expires']); + } + + if (isset($data['Secure'])) { + $this->setSecure($data['Secure']); + } + + if (isset($data['Discard'])) { + $this->setDiscard($data['Discard']); + } + + if (isset($data['HttpOnly'])) { + $this->setHttpOnly($data['HttpOnly']); + } + + // Set the remaining values that don't have extra validation logic + foreach (array_diff(array_keys($data), array_keys(self::$defaults)) as $key) { + $this->data[$key] = $data[$key]; + } + + // Extract the Expires value and turn it into a UNIX timestamp if needed + if (!$this->getExpires() && $this->getMaxAge()) { + // Calculate the Expires date + $this->setExpires(\time() + $this->getMaxAge()); + } elseif (null !== ($expires = $this->getExpires()) && !\is_numeric($expires)) { + $this->setExpires($expires); + } + } + + public function __toString() + { + $str = $this->data['Name'].'='.($this->data['Value'] ?? '').'; '; + foreach ($this->data as $k => $v) { + if ($k !== 'Name' && $k !== 'Value' && $v !== null && $v !== false) { + if ($k === 'Expires') { + $str .= 'Expires='.\gmdate('D, d M Y H:i:s \G\M\T', $v).'; '; + } else { + $str .= ($v === true ? $k : "{$k}={$v}").'; '; + } + } + } + + return \rtrim($str, '; '); + } + + public function toArray(): array + { + return $this->data; + } + + /** + * Get the cookie name. + * + * @return string + */ + public function getName() + { + return $this->data['Name']; + } + + /** + * Set the cookie name. + * + * @param string $name Cookie name + */ + public function setName($name): void + { + if (!is_string($name)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Name'] = (string) $name; + } + + /** + * Get the cookie value. + * + * @return string|null + */ + public function getValue() + { + return $this->data['Value']; + } + + /** + * Set the cookie value. + * + * @param string $value Cookie value + */ + public function setValue($value): void + { + if (!is_string($value)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Value'] = (string) $value; + } + + /** + * Get the domain. + * + * @return string|null + */ + public function getDomain() + { + return $this->data['Domain']; + } + + /** + * Set the domain of the cookie. + * + * @param string|null $domain + */ + public function setDomain($domain): void + { + if (!is_string($domain) && null !== $domain) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string or null to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Domain'] = null === $domain ? null : (string) $domain; + } + + /** + * Get the path. + * + * @return string + */ + public function getPath() + { + return $this->data['Path']; + } + + /** + * Set the path of the cookie. + * + * @param string $path Path of the cookie + */ + public function setPath($path): void + { + if (!is_string($path)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Path'] = (string) $path; + } + + /** + * Maximum lifetime of the cookie in seconds. + * + * @return int|null + */ + public function getMaxAge() + { + return null === $this->data['Max-Age'] ? null : (int) $this->data['Max-Age']; + } + + /** + * Set the max-age of the cookie. + * + * @param int|null $maxAge Max age of the cookie in seconds + */ + public function setMaxAge($maxAge): void + { + if (!is_int($maxAge) && null !== $maxAge) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an int or null to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Max-Age'] = $maxAge === null ? null : (int) $maxAge; + } + + /** + * The UNIX timestamp when the cookie Expires. + * + * @return string|int|null + */ + public function getExpires() + { + return $this->data['Expires']; + } + + /** + * Set the unix timestamp for which the cookie will expire. + * + * @param int|string|null $timestamp Unix timestamp or any English textual datetime description. + */ + public function setExpires($timestamp): void + { + if (!is_int($timestamp) && !is_string($timestamp) && null !== $timestamp) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an int, string or null to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Expires'] = null === $timestamp ? null : (\is_numeric($timestamp) ? (int) $timestamp : \strtotime((string) $timestamp)); + } + + /** + * Get whether or not this is a secure cookie. + * + * @return bool + */ + public function getSecure() + { + return $this->data['Secure']; + } + + /** + * Set whether or not the cookie is secure. + * + * @param bool $secure Set to true or false if secure + */ + public function setSecure($secure): void + { + if (!is_bool($secure)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a bool to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Secure'] = (bool) $secure; + } + + /** + * Get whether or not this is a session cookie. + * + * @return bool|null + */ + public function getDiscard() + { + return $this->data['Discard']; + } + + /** + * Set whether or not this is a session cookie. + * + * @param bool $discard Set to true or false if this is a session cookie + */ + public function setDiscard($discard): void + { + if (!is_bool($discard)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a bool to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Discard'] = (bool) $discard; + } + + /** + * Get whether or not this is an HTTP only cookie. + * + * @return bool + */ + public function getHttpOnly() + { + return $this->data['HttpOnly']; + } + + /** + * Set whether or not this is an HTTP only cookie. + * + * @param bool $httpOnly Set to true or false if this is HTTP only + */ + public function setHttpOnly($httpOnly): void + { + if (!is_bool($httpOnly)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a bool to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['HttpOnly'] = (bool) $httpOnly; + } + + /** + * Check if the cookie matches a path value. + * + * A request-path path-matches a given cookie-path if at least one of + * the following conditions holds: + * + * - The cookie-path and the request-path are identical. + * - The cookie-path is a prefix of the request-path, and the last + * character of the cookie-path is %x2F ("/"). + * - The cookie-path is a prefix of the request-path, and the first + * character of the request-path that is not included in the cookie- + * path is a %x2F ("/") character. + * + * @param string $requestPath Path to check against + */ + public function matchesPath(string $requestPath): bool + { + $cookiePath = $this->getPath(); + + // Match on exact matches or when path is the default empty "/" + if ($cookiePath === '/' || $cookiePath == $requestPath) { + return true; + } + + // Ensure that the cookie-path is a prefix of the request path. + if (0 !== \strpos($requestPath, $cookiePath)) { + return false; + } + + // Match if the last character of the cookie-path is "/" + if (\substr($cookiePath, -1, 1) === '/') { + return true; + } + + // Match if the first character not included in cookie path is "/" + return \substr($requestPath, \strlen($cookiePath), 1) === '/'; + } + + /** + * Check if the cookie matches a domain value. + * + * @param string $domain Domain to check against + */ + public function matchesDomain(string $domain): bool + { + $cookieDomain = $this->getDomain(); + if (null === $cookieDomain) { + return true; + } + + // Remove the leading '.' as per spec in RFC 6265. + // https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.3 + $cookieDomain = \ltrim(\strtolower($cookieDomain), '.'); + + $domain = \strtolower($domain); + + // Domain not set or exact match. + if ('' === $cookieDomain || $domain === $cookieDomain) { + return true; + } + + // Matching the subdomain according to RFC 6265. + // https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.3 + if (\filter_var($domain, \FILTER_VALIDATE_IP)) { + return false; + } + + return (bool) \preg_match('/\.'.\preg_quote($cookieDomain, '/').'$/', $domain); + } + + /** + * Check if the cookie is expired. + */ + public function isExpired(): bool + { + return $this->getExpires() !== null && \time() > $this->getExpires(); + } + + /** + * Check if the cookie is valid according to RFC 6265. + * + * @return bool|string Returns true if valid or an error message if invalid + */ + public function validate() + { + $name = $this->getName(); + if ($name === '') { + return 'The cookie name must not be empty'; + } + + // Check if any of the invalid characters are present in the cookie name + if (\preg_match( + '/[\x00-\x20\x22\x28-\x29\x2c\x2f\x3a-\x40\x5c\x7b\x7d\x7f]/', + $name + )) { + return 'Cookie name must not contain invalid characters: ASCII ' + .'Control characters (0-31;127), space, tab and the ' + .'following characters: ()<>@,;:\"/?={}'; + } + + // Value must not be null. 0 and empty string are valid. Empty strings + // are technically against RFC 6265, but known to happen in the wild. + $value = $this->getValue(); + if ($value === null) { + return 'The cookie value must not be empty'; + } + + // Domains must not be empty, but can be 0. "0" is not a valid internet + // domain, but may be used as server name in a private network. + $domain = $this->getDomain(); + if ($domain === null || $domain === '') { + return 'The cookie domain must not be empty'; + } + + return true; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php b/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php new file mode 100644 index 00000000..a80956c9 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php @@ -0,0 +1,39 @@ +request = $request; + $this->handlerContext = $handlerContext; + } + + /** + * Get the request that caused the exception + */ + public function getRequest(): RequestInterface + { + return $this->request; + } + + /** + * Get contextual information about the error from the underlying handler. + * + * The contents of this array will vary depending on which handler you are + * using. It may also be just an empty array. Relying on this data will + * couple you to a specific handler, but can give more debug information + * when needed. + */ + public function getHandlerContext(): array + { + return $this->handlerContext; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Exception/GuzzleException.php b/vendor/guzzlehttp/guzzle/src/Exception/GuzzleException.php new file mode 100644 index 00000000..fa3ed699 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Exception/GuzzleException.php @@ -0,0 +1,9 @@ +getStatusCode() : 0; + parent::__construct($message, $code, $previous); + $this->request = $request; + $this->response = $response; + $this->handlerContext = $handlerContext; + } + + /** + * Wrap non-RequestExceptions with a RequestException + */ + public static function wrapException(RequestInterface $request, \Throwable $e): RequestException + { + return $e instanceof RequestException ? $e : new RequestException($e->getMessage(), $request, null, $e); + } + + /** + * Factory method to create a new exception with a normalized error message + * + * @param RequestInterface $request Request sent + * @param ResponseInterface $response Response received + * @param \Throwable|null $previous Previous exception + * @param array $handlerContext Optional handler context + * @param BodySummarizerInterface|null $bodySummarizer Optional body summarizer + */ + public static function create( + RequestInterface $request, + ResponseInterface $response = null, + \Throwable $previous = null, + array $handlerContext = [], + BodySummarizerInterface $bodySummarizer = null + ): self { + if (!$response) { + return new self( + 'Error completing request', + $request, + null, + $previous, + $handlerContext + ); + } + + $level = (int) \floor($response->getStatusCode() / 100); + if ($level === 4) { + $label = 'Client error'; + $className = ClientException::class; + } elseif ($level === 5) { + $label = 'Server error'; + $className = ServerException::class; + } else { + $label = 'Unsuccessful request'; + $className = __CLASS__; + } + + $uri = $request->getUri(); + $uri = static::obfuscateUri($uri); + + // Client Error: `GET /` resulted in a `404 Not Found` response: + // ... (truncated) + $message = \sprintf( + '%s: `%s %s` resulted in a `%s %s` response', + $label, + $request->getMethod(), + $uri->__toString(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + + $summary = ($bodySummarizer ?? new BodySummarizer())->summarize($response); + + if ($summary !== null) { + $message .= ":\n{$summary}\n"; + } + + return new $className($message, $request, $response, $previous, $handlerContext); + } + + /** + * Obfuscates URI if there is a username and a password present + */ + private static function obfuscateUri(UriInterface $uri): UriInterface + { + $userInfo = $uri->getUserInfo(); + + if (false !== ($pos = \strpos($userInfo, ':'))) { + return $uri->withUserInfo(\substr($userInfo, 0, $pos), '***'); + } + + return $uri; + } + + /** + * Get the request that caused the exception + */ + public function getRequest(): RequestInterface + { + return $this->request; + } + + /** + * Get the associated response + */ + public function getResponse(): ?ResponseInterface + { + return $this->response; + } + + /** + * Check if a response was received + */ + public function hasResponse(): bool + { + return $this->response !== null; + } + + /** + * Get contextual information about the error from the underlying handler. + * + * The contents of this array will vary depending on which handler you are + * using. It may also be just an empty array. Relying on this data will + * couple you to a specific handler, but can give more debug information + * when needed. + */ + public function getHandlerContext(): array + { + return $this->handlerContext; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php b/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php new file mode 100644 index 00000000..8055e067 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php @@ -0,0 +1,10 @@ +maxHandles = $maxHandles; + } + + public function create(RequestInterface $request, array $options): EasyHandle + { + if (isset($options['curl']['body_as_string'])) { + $options['_body_as_string'] = $options['curl']['body_as_string']; + unset($options['curl']['body_as_string']); + } + + $easy = new EasyHandle(); + $easy->request = $request; + $easy->options = $options; + $conf = $this->getDefaultConf($easy); + $this->applyMethod($easy, $conf); + $this->applyHandlerOptions($easy, $conf); + $this->applyHeaders($easy, $conf); + unset($conf['_headers']); + + // Add handler options from the request configuration options + if (isset($options['curl'])) { + $conf = \array_replace($conf, $options['curl']); + } + + $conf[\CURLOPT_HEADERFUNCTION] = $this->createHeaderFn($easy); + $easy->handle = $this->handles ? \array_pop($this->handles) : \curl_init(); + curl_setopt_array($easy->handle, $conf); + + return $easy; + } + + public function release(EasyHandle $easy): void + { + $resource = $easy->handle; + unset($easy->handle); + + if (\count($this->handles) >= $this->maxHandles) { + \curl_close($resource); + } else { + // Remove all callback functions as they can hold onto references + // and are not cleaned up by curl_reset. Using curl_setopt_array + // does not work for some reason, so removing each one + // individually. + \curl_setopt($resource, \CURLOPT_HEADERFUNCTION, null); + \curl_setopt($resource, \CURLOPT_READFUNCTION, null); + \curl_setopt($resource, \CURLOPT_WRITEFUNCTION, null); + \curl_setopt($resource, \CURLOPT_PROGRESSFUNCTION, null); + \curl_reset($resource); + $this->handles[] = $resource; + } + } + + /** + * Completes a cURL transaction, either returning a response promise or a + * rejected promise. + * + * @param callable(RequestInterface, array): PromiseInterface $handler + * @param CurlFactoryInterface $factory Dictates how the handle is released + */ + public static function finish(callable $handler, EasyHandle $easy, CurlFactoryInterface $factory): PromiseInterface + { + if (isset($easy->options['on_stats'])) { + self::invokeStats($easy); + } + + if (!$easy->response || $easy->errno) { + return self::finishError($handler, $easy, $factory); + } + + // Return the response if it is present and there is no error. + $factory->release($easy); + + // Rewind the body of the response if possible. + $body = $easy->response->getBody(); + if ($body->isSeekable()) { + $body->rewind(); + } + + return new FulfilledPromise($easy->response); + } + + private static function invokeStats(EasyHandle $easy): void + { + $curlStats = \curl_getinfo($easy->handle); + $curlStats['appconnect_time'] = \curl_getinfo($easy->handle, \CURLINFO_APPCONNECT_TIME); + $stats = new TransferStats( + $easy->request, + $easy->response, + $curlStats['total_time'], + $easy->errno, + $curlStats + ); + ($easy->options['on_stats'])($stats); + } + + /** + * @param callable(RequestInterface, array): PromiseInterface $handler + */ + private static function finishError(callable $handler, EasyHandle $easy, CurlFactoryInterface $factory): PromiseInterface + { + // Get error information and release the handle to the factory. + $ctx = [ + 'errno' => $easy->errno, + 'error' => \curl_error($easy->handle), + 'appconnect_time' => \curl_getinfo($easy->handle, \CURLINFO_APPCONNECT_TIME), + ] + \curl_getinfo($easy->handle); + $ctx[self::CURL_VERSION_STR] = \curl_version()['version']; + $factory->release($easy); + + // Retry when nothing is present or when curl failed to rewind. + if (empty($easy->options['_err_message']) && (!$easy->errno || $easy->errno == 65)) { + return self::retryFailedRewind($handler, $easy, $ctx); + } + + return self::createRejection($easy, $ctx); + } + + private static function createRejection(EasyHandle $easy, array $ctx): PromiseInterface + { + static $connectionErrors = [ + \CURLE_OPERATION_TIMEOUTED => true, + \CURLE_COULDNT_RESOLVE_HOST => true, + \CURLE_COULDNT_CONNECT => true, + \CURLE_SSL_CONNECT_ERROR => true, + \CURLE_GOT_NOTHING => true, + ]; + + if ($easy->createResponseException) { + return P\Create::rejectionFor( + new RequestException( + 'An error was encountered while creating the response', + $easy->request, + $easy->response, + $easy->createResponseException, + $ctx + ) + ); + } + + // If an exception was encountered during the onHeaders event, then + // return a rejected promise that wraps that exception. + if ($easy->onHeadersException) { + return P\Create::rejectionFor( + new RequestException( + 'An error was encountered during the on_headers event', + $easy->request, + $easy->response, + $easy->onHeadersException, + $ctx + ) + ); + } + + $message = \sprintf( + 'cURL error %s: %s (%s)', + $ctx['errno'], + $ctx['error'], + 'see https://curl.haxx.se/libcurl/c/libcurl-errors.html' + ); + $uriString = (string) $easy->request->getUri(); + if ($uriString !== '' && false === \strpos($ctx['error'], $uriString)) { + $message .= \sprintf(' for %s', $uriString); + } + + // Create a connection exception if it was a specific error code. + $error = isset($connectionErrors[$easy->errno]) + ? new ConnectException($message, $easy->request, null, $ctx) + : new RequestException($message, $easy->request, $easy->response, null, $ctx); + + return P\Create::rejectionFor($error); + } + + /** + * @return array + */ + private function getDefaultConf(EasyHandle $easy): array + { + $conf = [ + '_headers' => $easy->request->getHeaders(), + \CURLOPT_CUSTOMREQUEST => $easy->request->getMethod(), + \CURLOPT_URL => (string) $easy->request->getUri()->withFragment(''), + \CURLOPT_RETURNTRANSFER => false, + \CURLOPT_HEADER => false, + \CURLOPT_CONNECTTIMEOUT => 300, + ]; + + if (\defined('CURLOPT_PROTOCOLS')) { + $conf[\CURLOPT_PROTOCOLS] = \CURLPROTO_HTTP | \CURLPROTO_HTTPS; + } + + $version = $easy->request->getProtocolVersion(); + if ($version == 1.1) { + $conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_1; + } elseif ($version == 2.0) { + $conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_2_0; + } else { + $conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_0; + } + + return $conf; + } + + private function applyMethod(EasyHandle $easy, array &$conf): void + { + $body = $easy->request->getBody(); + $size = $body->getSize(); + + if ($size === null || $size > 0) { + $this->applyBody($easy->request, $easy->options, $conf); + + return; + } + + $method = $easy->request->getMethod(); + if ($method === 'PUT' || $method === 'POST') { + // See https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.2 + if (!$easy->request->hasHeader('Content-Length')) { + $conf[\CURLOPT_HTTPHEADER][] = 'Content-Length: 0'; + } + } elseif ($method === 'HEAD') { + $conf[\CURLOPT_NOBODY] = true; + unset( + $conf[\CURLOPT_WRITEFUNCTION], + $conf[\CURLOPT_READFUNCTION], + $conf[\CURLOPT_FILE], + $conf[\CURLOPT_INFILE] + ); + } + } + + private function applyBody(RequestInterface $request, array $options, array &$conf): void + { + $size = $request->hasHeader('Content-Length') + ? (int) $request->getHeaderLine('Content-Length') + : null; + + // Send the body as a string if the size is less than 1MB OR if the + // [curl][body_as_string] request value is set. + if (($size !== null && $size < 1000000) || !empty($options['_body_as_string'])) { + $conf[\CURLOPT_POSTFIELDS] = (string) $request->getBody(); + // Don't duplicate the Content-Length header + $this->removeHeader('Content-Length', $conf); + $this->removeHeader('Transfer-Encoding', $conf); + } else { + $conf[\CURLOPT_UPLOAD] = true; + if ($size !== null) { + $conf[\CURLOPT_INFILESIZE] = $size; + $this->removeHeader('Content-Length', $conf); + } + $body = $request->getBody(); + if ($body->isSeekable()) { + $body->rewind(); + } + $conf[\CURLOPT_READFUNCTION] = static function ($ch, $fd, $length) use ($body) { + return $body->read($length); + }; + } + + // If the Expect header is not present, prevent curl from adding it + if (!$request->hasHeader('Expect')) { + $conf[\CURLOPT_HTTPHEADER][] = 'Expect:'; + } + + // cURL sometimes adds a content-type by default. Prevent this. + if (!$request->hasHeader('Content-Type')) { + $conf[\CURLOPT_HTTPHEADER][] = 'Content-Type:'; + } + } + + private function applyHeaders(EasyHandle $easy, array &$conf): void + { + foreach ($conf['_headers'] as $name => $values) { + foreach ($values as $value) { + $value = (string) $value; + if ($value === '') { + // cURL requires a special format for empty headers. + // See https://github.com/guzzle/guzzle/issues/1882 for more details. + $conf[\CURLOPT_HTTPHEADER][] = "$name;"; + } else { + $conf[\CURLOPT_HTTPHEADER][] = "$name: $value"; + } + } + } + + // Remove the Accept header if one was not set + if (!$easy->request->hasHeader('Accept')) { + $conf[\CURLOPT_HTTPHEADER][] = 'Accept:'; + } + } + + /** + * Remove a header from the options array. + * + * @param string $name Case-insensitive header to remove + * @param array $options Array of options to modify + */ + private function removeHeader(string $name, array &$options): void + { + foreach (\array_keys($options['_headers']) as $key) { + if (!\strcasecmp($key, $name)) { + unset($options['_headers'][$key]); + + return; + } + } + } + + private function applyHandlerOptions(EasyHandle $easy, array &$conf): void + { + $options = $easy->options; + if (isset($options['verify'])) { + if ($options['verify'] === false) { + unset($conf[\CURLOPT_CAINFO]); + $conf[\CURLOPT_SSL_VERIFYHOST] = 0; + $conf[\CURLOPT_SSL_VERIFYPEER] = false; + } else { + $conf[\CURLOPT_SSL_VERIFYHOST] = 2; + $conf[\CURLOPT_SSL_VERIFYPEER] = true; + if (\is_string($options['verify'])) { + // Throw an error if the file/folder/link path is not valid or doesn't exist. + if (!\file_exists($options['verify'])) { + throw new \InvalidArgumentException("SSL CA bundle not found: {$options['verify']}"); + } + // If it's a directory or a link to a directory use CURLOPT_CAPATH. + // If not, it's probably a file, or a link to a file, so use CURLOPT_CAINFO. + if ( + \is_dir($options['verify']) + || ( + \is_link($options['verify']) === true + && ($verifyLink = \readlink($options['verify'])) !== false + && \is_dir($verifyLink) + ) + ) { + $conf[\CURLOPT_CAPATH] = $options['verify']; + } else { + $conf[\CURLOPT_CAINFO] = $options['verify']; + } + } + } + } + + if (!isset($options['curl'][\CURLOPT_ENCODING]) && !empty($options['decode_content'])) { + $accept = $easy->request->getHeaderLine('Accept-Encoding'); + if ($accept) { + $conf[\CURLOPT_ENCODING] = $accept; + } else { + // The empty string enables all available decoders and implicitly + // sets a matching 'Accept-Encoding' header. + $conf[\CURLOPT_ENCODING] = ''; + // But as the user did not specify any acceptable encodings we need + // to overwrite this implicit header with an empty one. + $conf[\CURLOPT_HTTPHEADER][] = 'Accept-Encoding:'; + } + } + + if (!isset($options['sink'])) { + // Use a default temp stream if no sink was set. + $options['sink'] = \GuzzleHttp\Psr7\Utils::tryFopen('php://temp', 'w+'); + } + $sink = $options['sink']; + if (!\is_string($sink)) { + $sink = \GuzzleHttp\Psr7\Utils::streamFor($sink); + } elseif (!\is_dir(\dirname($sink))) { + // Ensure that the directory exists before failing in curl. + throw new \RuntimeException(\sprintf('Directory %s does not exist for sink value of %s', \dirname($sink), $sink)); + } else { + $sink = new LazyOpenStream($sink, 'w+'); + } + $easy->sink = $sink; + $conf[\CURLOPT_WRITEFUNCTION] = static function ($ch, $write) use ($sink): int { + return $sink->write($write); + }; + + $timeoutRequiresNoSignal = false; + if (isset($options['timeout'])) { + $timeoutRequiresNoSignal |= $options['timeout'] < 1; + $conf[\CURLOPT_TIMEOUT_MS] = $options['timeout'] * 1000; + } + + // CURL default value is CURL_IPRESOLVE_WHATEVER + if (isset($options['force_ip_resolve'])) { + if ('v4' === $options['force_ip_resolve']) { + $conf[\CURLOPT_IPRESOLVE] = \CURL_IPRESOLVE_V4; + } elseif ('v6' === $options['force_ip_resolve']) { + $conf[\CURLOPT_IPRESOLVE] = \CURL_IPRESOLVE_V6; + } + } + + if (isset($options['connect_timeout'])) { + $timeoutRequiresNoSignal |= $options['connect_timeout'] < 1; + $conf[\CURLOPT_CONNECTTIMEOUT_MS] = $options['connect_timeout'] * 1000; + } + + if ($timeoutRequiresNoSignal && \strtoupper(\substr(\PHP_OS, 0, 3)) !== 'WIN') { + $conf[\CURLOPT_NOSIGNAL] = true; + } + + if (isset($options['proxy'])) { + if (!\is_array($options['proxy'])) { + $conf[\CURLOPT_PROXY] = $options['proxy']; + } else { + $scheme = $easy->request->getUri()->getScheme(); + if (isset($options['proxy'][$scheme])) { + $host = $easy->request->getUri()->getHost(); + if (isset($options['proxy']['no']) && Utils::isHostInNoProxy($host, $options['proxy']['no'])) { + unset($conf[\CURLOPT_PROXY]); + } else { + $conf[\CURLOPT_PROXY] = $options['proxy'][$scheme]; + } + } + } + } + + if (isset($options['crypto_method'])) { + if (\STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT === $options['crypto_method']) { + if (!defined('CURL_SSLVERSION_TLSv1_0')) { + throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.0 not supported by your version of cURL'); + } + $conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_0; + } elseif (\STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT === $options['crypto_method']) { + if (!defined('CURL_SSLVERSION_TLSv1_1')) { + throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.1 not supported by your version of cURL'); + } + $conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_1; + } elseif (\STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT === $options['crypto_method']) { + if (!defined('CURL_SSLVERSION_TLSv1_2')) { + throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.2 not supported by your version of cURL'); + } + $conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_2; + } elseif (defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT') && \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT === $options['crypto_method']) { + if (!defined('CURL_SSLVERSION_TLSv1_3')) { + throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.3 not supported by your version of cURL'); + } + $conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_3; + } else { + throw new \InvalidArgumentException('Invalid crypto_method request option: unknown version provided'); + } + } + + if (isset($options['cert'])) { + $cert = $options['cert']; + if (\is_array($cert)) { + $conf[\CURLOPT_SSLCERTPASSWD] = $cert[1]; + $cert = $cert[0]; + } + if (!\file_exists($cert)) { + throw new \InvalidArgumentException("SSL certificate not found: {$cert}"); + } + // OpenSSL (versions 0.9.3 and later) also support "P12" for PKCS#12-encoded files. + // see https://curl.se/libcurl/c/CURLOPT_SSLCERTTYPE.html + $ext = pathinfo($cert, \PATHINFO_EXTENSION); + if (preg_match('#^(der|p12)$#i', $ext)) { + $conf[\CURLOPT_SSLCERTTYPE] = strtoupper($ext); + } + $conf[\CURLOPT_SSLCERT] = $cert; + } + + if (isset($options['ssl_key'])) { + if (\is_array($options['ssl_key'])) { + if (\count($options['ssl_key']) === 2) { + [$sslKey, $conf[\CURLOPT_SSLKEYPASSWD]] = $options['ssl_key']; + } else { + [$sslKey] = $options['ssl_key']; + } + } + + $sslKey = $sslKey ?? $options['ssl_key']; + + if (!\file_exists($sslKey)) { + throw new \InvalidArgumentException("SSL private key not found: {$sslKey}"); + } + $conf[\CURLOPT_SSLKEY] = $sslKey; + } + + if (isset($options['progress'])) { + $progress = $options['progress']; + if (!\is_callable($progress)) { + throw new \InvalidArgumentException('progress client option must be callable'); + } + $conf[\CURLOPT_NOPROGRESS] = false; + $conf[\CURLOPT_PROGRESSFUNCTION] = static function ($resource, int $downloadSize, int $downloaded, int $uploadSize, int $uploaded) use ($progress) { + $progress($downloadSize, $downloaded, $uploadSize, $uploaded); + }; + } + + if (!empty($options['debug'])) { + $conf[\CURLOPT_STDERR] = Utils::debugResource($options['debug']); + $conf[\CURLOPT_VERBOSE] = true; + } + } + + /** + * This function ensures that a response was set on a transaction. If one + * was not set, then the request is retried if possible. This error + * typically means you are sending a payload, curl encountered a + * "Connection died, retrying a fresh connect" error, tried to rewind the + * stream, and then encountered a "necessary data rewind wasn't possible" + * error, causing the request to be sent through curl_multi_info_read() + * without an error status. + * + * @param callable(RequestInterface, array): PromiseInterface $handler + */ + private static function retryFailedRewind(callable $handler, EasyHandle $easy, array $ctx): PromiseInterface + { + try { + // Only rewind if the body has been read from. + $body = $easy->request->getBody(); + if ($body->tell() > 0) { + $body->rewind(); + } + } catch (\RuntimeException $e) { + $ctx['error'] = 'The connection unexpectedly failed without ' + .'providing an error. The request would have been retried, ' + .'but attempting to rewind the request body failed. ' + .'Exception: '.$e; + + return self::createRejection($easy, $ctx); + } + + // Retry no more than 3 times before giving up. + if (!isset($easy->options['_curl_retries'])) { + $easy->options['_curl_retries'] = 1; + } elseif ($easy->options['_curl_retries'] == 2) { + $ctx['error'] = 'The cURL request was retried 3 times ' + .'and did not succeed. The most likely reason for the failure ' + .'is that cURL was unable to rewind the body of the request ' + .'and subsequent retries resulted in the same error. Turn on ' + .'the debug option to see what went wrong. See ' + .'https://bugs.php.net/bug.php?id=47204 for more information.'; + + return self::createRejection($easy, $ctx); + } else { + ++$easy->options['_curl_retries']; + } + + return $handler($easy->request, $easy->options); + } + + private function createHeaderFn(EasyHandle $easy): callable + { + if (isset($easy->options['on_headers'])) { + $onHeaders = $easy->options['on_headers']; + + if (!\is_callable($onHeaders)) { + throw new \InvalidArgumentException('on_headers must be callable'); + } + } else { + $onHeaders = null; + } + + return static function ($ch, $h) use ( + $onHeaders, + $easy, + &$startingResponse + ) { + $value = \trim($h); + if ($value === '') { + $startingResponse = true; + try { + $easy->createResponse(); + } catch (\Exception $e) { + $easy->createResponseException = $e; + + return -1; + } + if ($onHeaders !== null) { + try { + $onHeaders($easy->response); + } catch (\Exception $e) { + // Associate the exception with the handle and trigger + // a curl header write error by returning 0. + $easy->onHeadersException = $e; + + return -1; + } + } + } elseif ($startingResponse) { + $startingResponse = false; + $easy->headers = [$value]; + } else { + $easy->headers[] = $value; + } + + return \strlen($h); + }; + } + + public function __destruct() + { + foreach ($this->handles as $id => $handle) { + \curl_close($handle); + unset($this->handles[$id]); + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php b/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php new file mode 100644 index 00000000..fe57ed5d --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php @@ -0,0 +1,25 @@ +factory = $options['handle_factory'] + ?? new CurlFactory(3); + } + + public function __invoke(RequestInterface $request, array $options): PromiseInterface + { + if (isset($options['delay'])) { + \usleep($options['delay'] * 1000); + } + + $easy = $this->factory->create($request, $options); + \curl_exec($easy->handle); + $easy->errno = \curl_errno($easy->handle); + + return CurlFactory::finish($this, $easy, $this->factory); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php b/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php new file mode 100644 index 00000000..a64e1821 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php @@ -0,0 +1,267 @@ + An array of delay times, indexed by handle id in `addRequest`. + * + * @see CurlMultiHandler::addRequest + */ + private $delays = []; + + /** + * @var array An associative array of CURLMOPT_* options and corresponding values for curl_multi_setopt() + */ + private $options = []; + + /** @var resource|\CurlMultiHandle */ + private $_mh; + + /** + * This handler accepts the following options: + * + * - handle_factory: An optional factory used to create curl handles + * - select_timeout: Optional timeout (in seconds) to block before timing + * out while selecting curl handles. Defaults to 1 second. + * - options: An associative array of CURLMOPT_* options and + * corresponding values for curl_multi_setopt() + */ + public function __construct(array $options = []) + { + $this->factory = $options['handle_factory'] ?? new CurlFactory(50); + + if (isset($options['select_timeout'])) { + $this->selectTimeout = $options['select_timeout']; + } elseif ($selectTimeout = Utils::getenv('GUZZLE_CURL_SELECT_TIMEOUT')) { + @trigger_error('Since guzzlehttp/guzzle 7.2.0: Using environment variable GUZZLE_CURL_SELECT_TIMEOUT is deprecated. Use option "select_timeout" instead.', \E_USER_DEPRECATED); + $this->selectTimeout = (int) $selectTimeout; + } else { + $this->selectTimeout = 1; + } + + $this->options = $options['options'] ?? []; + + // unsetting the property forces the first access to go through + // __get(). + unset($this->_mh); + } + + /** + * @param string $name + * + * @return resource|\CurlMultiHandle + * + * @throws \BadMethodCallException when another field as `_mh` will be gotten + * @throws \RuntimeException when curl can not initialize a multi handle + */ + public function __get($name) + { + if ($name !== '_mh') { + throw new \BadMethodCallException("Can not get other property as '_mh'."); + } + + $multiHandle = \curl_multi_init(); + + if (false === $multiHandle) { + throw new \RuntimeException('Can not initialize curl multi handle.'); + } + + $this->_mh = $multiHandle; + + foreach ($this->options as $option => $value) { + // A warning is raised in case of a wrong option. + curl_multi_setopt($this->_mh, $option, $value); + } + + return $this->_mh; + } + + public function __destruct() + { + if (isset($this->_mh)) { + \curl_multi_close($this->_mh); + unset($this->_mh); + } + } + + public function __invoke(RequestInterface $request, array $options): PromiseInterface + { + $easy = $this->factory->create($request, $options); + $id = (int) $easy->handle; + + $promise = new Promise( + [$this, 'execute'], + function () use ($id) { + return $this->cancel($id); + } + ); + + $this->addRequest(['easy' => $easy, 'deferred' => $promise]); + + return $promise; + } + + /** + * Ticks the curl event loop. + */ + public function tick(): void + { + // Add any delayed handles if needed. + if ($this->delays) { + $currentTime = Utils::currentTime(); + foreach ($this->delays as $id => $delay) { + if ($currentTime >= $delay) { + unset($this->delays[$id]); + \curl_multi_add_handle( + $this->_mh, + $this->handles[$id]['easy']->handle + ); + } + } + } + + // Step through the task queue which may add additional requests. + P\Utils::queue()->run(); + + if ($this->active && \curl_multi_select($this->_mh, $this->selectTimeout) === -1) { + // Perform a usleep if a select returns -1. + // See: https://bugs.php.net/bug.php?id=61141 + \usleep(250); + } + + while (\curl_multi_exec($this->_mh, $this->active) === \CURLM_CALL_MULTI_PERFORM) { + } + + $this->processMessages(); + } + + /** + * Runs until all outstanding connections have completed. + */ + public function execute(): void + { + $queue = P\Utils::queue(); + + while ($this->handles || !$queue->isEmpty()) { + // If there are no transfers, then sleep for the next delay + if (!$this->active && $this->delays) { + \usleep($this->timeToNext()); + } + $this->tick(); + } + } + + private function addRequest(array $entry): void + { + $easy = $entry['easy']; + $id = (int) $easy->handle; + $this->handles[$id] = $entry; + if (empty($easy->options['delay'])) { + \curl_multi_add_handle($this->_mh, $easy->handle); + } else { + $this->delays[$id] = Utils::currentTime() + ($easy->options['delay'] / 1000); + } + } + + /** + * Cancels a handle from sending and removes references to it. + * + * @param int $id Handle ID to cancel and remove. + * + * @return bool True on success, false on failure. + */ + private function cancel($id): bool + { + if (!is_int($id)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an integer to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + // Cannot cancel if it has been processed. + if (!isset($this->handles[$id])) { + return false; + } + + $handle = $this->handles[$id]['easy']->handle; + unset($this->delays[$id], $this->handles[$id]); + \curl_multi_remove_handle($this->_mh, $handle); + \curl_close($handle); + + return true; + } + + private function processMessages(): void + { + while ($done = \curl_multi_info_read($this->_mh)) { + if ($done['msg'] !== \CURLMSG_DONE) { + // if it's not done, then it would be premature to remove the handle. ref https://github.com/guzzle/guzzle/pull/2892#issuecomment-945150216 + continue; + } + $id = (int) $done['handle']; + \curl_multi_remove_handle($this->_mh, $done['handle']); + + if (!isset($this->handles[$id])) { + // Probably was cancelled. + continue; + } + + $entry = $this->handles[$id]; + unset($this->handles[$id], $this->delays[$id]); + $entry['easy']->errno = $done['result']; + $entry['deferred']->resolve( + CurlFactory::finish($this, $entry['easy'], $this->factory) + ); + } + } + + private function timeToNext(): int + { + $currentTime = Utils::currentTime(); + $nextTime = \PHP_INT_MAX; + foreach ($this->delays as $time) { + if ($time < $nextTime) { + $nextTime = $time; + } + } + + return ((int) \max(0, $nextTime - $currentTime)) * 1000000; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php b/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php new file mode 100644 index 00000000..1bc39f4b --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php @@ -0,0 +1,112 @@ +headers); + + $normalizedKeys = Utils::normalizeHeaderKeys($headers); + + if (!empty($this->options['decode_content']) && isset($normalizedKeys['content-encoding'])) { + $headers['x-encoded-content-encoding'] = $headers[$normalizedKeys['content-encoding']]; + unset($headers[$normalizedKeys['content-encoding']]); + if (isset($normalizedKeys['content-length'])) { + $headers['x-encoded-content-length'] = $headers[$normalizedKeys['content-length']]; + + $bodyLength = (int) $this->sink->getSize(); + if ($bodyLength) { + $headers[$normalizedKeys['content-length']] = $bodyLength; + } else { + unset($headers[$normalizedKeys['content-length']]); + } + } + } + + // Attach a response to the easy handle with the parsed headers. + $this->response = new Response( + $status, + $headers, + $this->sink, + $ver, + $reason + ); + } + + /** + * @param string $name + * + * @return void + * + * @throws \BadMethodCallException + */ + public function __get($name) + { + $msg = $name === 'handle' ? 'The EasyHandle has been released' : 'Invalid property: '.$name; + throw new \BadMethodCallException($msg); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php b/vendor/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php new file mode 100644 index 00000000..5554b8fa --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php @@ -0,0 +1,42 @@ +|null $queue The parameters to be passed to the append function, as an indexed array. + * @param callable|null $onFulfilled Callback to invoke when the return value is fulfilled. + * @param callable|null $onRejected Callback to invoke when the return value is rejected. + */ + public function __construct(array $queue = null, callable $onFulfilled = null, callable $onRejected = null) + { + $this->onFulfilled = $onFulfilled; + $this->onRejected = $onRejected; + + if ($queue) { + // array_values included for BC + $this->append(...array_values($queue)); + } + } + + public function __invoke(RequestInterface $request, array $options): PromiseInterface + { + if (!$this->queue) { + throw new \OutOfBoundsException('Mock queue is empty'); + } + + if (isset($options['delay']) && \is_numeric($options['delay'])) { + \usleep((int) $options['delay'] * 1000); + } + + $this->lastRequest = $request; + $this->lastOptions = $options; + $response = \array_shift($this->queue); + + if (isset($options['on_headers'])) { + if (!\is_callable($options['on_headers'])) { + throw new \InvalidArgumentException('on_headers must be callable'); + } + try { + $options['on_headers']($response); + } catch (\Exception $e) { + $msg = 'An error was encountered during the on_headers event'; + $response = new RequestException($msg, $request, $response, $e); + } + } + + if (\is_callable($response)) { + $response = $response($request, $options); + } + + $response = $response instanceof \Throwable + ? P\Create::rejectionFor($response) + : P\Create::promiseFor($response); + + return $response->then( + function (?ResponseInterface $value) use ($request, $options) { + $this->invokeStats($request, $options, $value); + if ($this->onFulfilled) { + ($this->onFulfilled)($value); + } + + if ($value !== null && isset($options['sink'])) { + $contents = (string) $value->getBody(); + $sink = $options['sink']; + + if (\is_resource($sink)) { + \fwrite($sink, $contents); + } elseif (\is_string($sink)) { + \file_put_contents($sink, $contents); + } elseif ($sink instanceof StreamInterface) { + $sink->write($contents); + } + } + + return $value; + }, + function ($reason) use ($request, $options) { + $this->invokeStats($request, $options, null, $reason); + if ($this->onRejected) { + ($this->onRejected)($reason); + } + + return P\Create::rejectionFor($reason); + } + ); + } + + /** + * Adds one or more variadic requests, exceptions, callables, or promises + * to the queue. + * + * @param mixed ...$values + */ + public function append(...$values): void + { + foreach ($values as $value) { + if ($value instanceof ResponseInterface + || $value instanceof \Throwable + || $value instanceof PromiseInterface + || \is_callable($value) + ) { + $this->queue[] = $value; + } else { + throw new \TypeError('Expected a Response, Promise, Throwable or callable. Found '.Utils::describeType($value)); + } + } + } + + /** + * Get the last received request. + */ + public function getLastRequest(): ?RequestInterface + { + return $this->lastRequest; + } + + /** + * Get the last received request options. + */ + public function getLastOptions(): array + { + return $this->lastOptions; + } + + /** + * Returns the number of remaining items in the queue. + */ + public function count(): int + { + return \count($this->queue); + } + + public function reset(): void + { + $this->queue = []; + } + + /** + * @param mixed $reason Promise or reason. + */ + private function invokeStats( + RequestInterface $request, + array $options, + ResponseInterface $response = null, + $reason = null + ): void { + if (isset($options['on_stats'])) { + $transferTime = $options['transfer_time'] ?? 0; + $stats = new TransferStats($request, $response, $transferTime, $reason); + ($options['on_stats'])($stats); + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php b/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php new file mode 100644 index 00000000..f045b526 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php @@ -0,0 +1,51 @@ +withoutHeader('Expect'); + + // Append a content-length header if body size is zero to match + // cURL's behavior. + if (0 === $request->getBody()->getSize()) { + $request = $request->withHeader('Content-Length', '0'); + } + + return $this->createResponse( + $request, + $options, + $this->createStream($request, $options), + $startTime + ); + } catch (\InvalidArgumentException $e) { + throw $e; + } catch (\Exception $e) { + // Determine if the error was a networking error. + $message = $e->getMessage(); + // This list can probably get more comprehensive. + if (false !== \strpos($message, 'getaddrinfo') // DNS lookup failed + || false !== \strpos($message, 'Connection refused') + || false !== \strpos($message, "couldn't connect to host") // error on HHVM + || false !== \strpos($message, 'connection attempt failed') + ) { + $e = new ConnectException($e->getMessage(), $request, $e); + } else { + $e = RequestException::wrapException($request, $e); + } + $this->invokeStats($options, $request, $startTime, null, $e); + + return P\Create::rejectionFor($e); + } + } + + private function invokeStats( + array $options, + RequestInterface $request, + ?float $startTime, + ResponseInterface $response = null, + \Throwable $error = null + ): void { + if (isset($options['on_stats'])) { + $stats = new TransferStats($request, $response, Utils::currentTime() - $startTime, $error, []); + ($options['on_stats'])($stats); + } + } + + /** + * @param resource $stream + */ + private function createResponse(RequestInterface $request, array $options, $stream, ?float $startTime): PromiseInterface + { + $hdrs = $this->lastHeaders; + $this->lastHeaders = []; + + try { + [$ver, $status, $reason, $headers] = HeaderProcessor::parseHeaders($hdrs); + } catch (\Exception $e) { + return P\Create::rejectionFor( + new RequestException('An error was encountered while creating the response', $request, null, $e) + ); + } + + [$stream, $headers] = $this->checkDecode($options, $headers, $stream); + $stream = Psr7\Utils::streamFor($stream); + $sink = $stream; + + if (\strcasecmp('HEAD', $request->getMethod())) { + $sink = $this->createSink($stream, $options); + } + + try { + $response = new Psr7\Response($status, $headers, $sink, $ver, $reason); + } catch (\Exception $e) { + return P\Create::rejectionFor( + new RequestException('An error was encountered while creating the response', $request, null, $e) + ); + } + + if (isset($options['on_headers'])) { + try { + $options['on_headers']($response); + } catch (\Exception $e) { + return P\Create::rejectionFor( + new RequestException('An error was encountered during the on_headers event', $request, $response, $e) + ); + } + } + + // Do not drain when the request is a HEAD request because they have + // no body. + if ($sink !== $stream) { + $this->drain($stream, $sink, $response->getHeaderLine('Content-Length')); + } + + $this->invokeStats($options, $request, $startTime, $response, null); + + return new FulfilledPromise($response); + } + + private function createSink(StreamInterface $stream, array $options): StreamInterface + { + if (!empty($options['stream'])) { + return $stream; + } + + $sink = $options['sink'] ?? Psr7\Utils::tryFopen('php://temp', 'r+'); + + return \is_string($sink) ? new Psr7\LazyOpenStream($sink, 'w+') : Psr7\Utils::streamFor($sink); + } + + /** + * @param resource $stream + */ + private function checkDecode(array $options, array $headers, $stream): array + { + // Automatically decode responses when instructed. + if (!empty($options['decode_content'])) { + $normalizedKeys = Utils::normalizeHeaderKeys($headers); + if (isset($normalizedKeys['content-encoding'])) { + $encoding = $headers[$normalizedKeys['content-encoding']]; + if ($encoding[0] === 'gzip' || $encoding[0] === 'deflate') { + $stream = new Psr7\InflateStream(Psr7\Utils::streamFor($stream)); + $headers['x-encoded-content-encoding'] = $headers[$normalizedKeys['content-encoding']]; + + // Remove content-encoding header + unset($headers[$normalizedKeys['content-encoding']]); + + // Fix content-length header + if (isset($normalizedKeys['content-length'])) { + $headers['x-encoded-content-length'] = $headers[$normalizedKeys['content-length']]; + $length = (int) $stream->getSize(); + if ($length === 0) { + unset($headers[$normalizedKeys['content-length']]); + } else { + $headers[$normalizedKeys['content-length']] = [$length]; + } + } + } + } + } + + return [$stream, $headers]; + } + + /** + * Drains the source stream into the "sink" client option. + * + * @param string $contentLength Header specifying the amount of + * data to read. + * + * @throws \RuntimeException when the sink option is invalid. + */ + private function drain(StreamInterface $source, StreamInterface $sink, string $contentLength): StreamInterface + { + // If a content-length header is provided, then stop reading once + // that number of bytes has been read. This can prevent infinitely + // reading from a stream when dealing with servers that do not honor + // Connection: Close headers. + Psr7\Utils::copyToStream( + $source, + $sink, + (\strlen($contentLength) > 0 && (int) $contentLength > 0) ? (int) $contentLength : -1 + ); + + $sink->seek(0); + $source->close(); + + return $sink; + } + + /** + * Create a resource and check to ensure it was created successfully + * + * @param callable $callback Callable that returns stream resource + * + * @return resource + * + * @throws \RuntimeException on error + */ + private function createResource(callable $callback) + { + $errors = []; + \set_error_handler(static function ($_, $msg, $file, $line) use (&$errors): bool { + $errors[] = [ + 'message' => $msg, + 'file' => $file, + 'line' => $line, + ]; + + return true; + }); + + try { + $resource = $callback(); + } finally { + \restore_error_handler(); + } + + if (!$resource) { + $message = 'Error creating resource: '; + foreach ($errors as $err) { + foreach ($err as $key => $value) { + $message .= "[$key] $value".\PHP_EOL; + } + } + throw new \RuntimeException(\trim($message)); + } + + return $resource; + } + + /** + * @return resource + */ + private function createStream(RequestInterface $request, array $options) + { + static $methods; + if (!$methods) { + $methods = \array_flip(\get_class_methods(__CLASS__)); + } + + if (!\in_array($request->getUri()->getScheme(), ['http', 'https'])) { + throw new RequestException(\sprintf("The scheme '%s' is not supported.", $request->getUri()->getScheme()), $request); + } + + // HTTP/1.1 streams using the PHP stream wrapper require a + // Connection: close header + if ($request->getProtocolVersion() == '1.1' + && !$request->hasHeader('Connection') + ) { + $request = $request->withHeader('Connection', 'close'); + } + + // Ensure SSL is verified by default + if (!isset($options['verify'])) { + $options['verify'] = true; + } + + $params = []; + $context = $this->getDefaultContext($request); + + if (isset($options['on_headers']) && !\is_callable($options['on_headers'])) { + throw new \InvalidArgumentException('on_headers must be callable'); + } + + if (!empty($options)) { + foreach ($options as $key => $value) { + $method = "add_{$key}"; + if (isset($methods[$method])) { + $this->{$method}($request, $context, $value, $params); + } + } + } + + if (isset($options['stream_context'])) { + if (!\is_array($options['stream_context'])) { + throw new \InvalidArgumentException('stream_context must be an array'); + } + $context = \array_replace_recursive($context, $options['stream_context']); + } + + // Microsoft NTLM authentication only supported with curl handler + if (isset($options['auth'][2]) && 'ntlm' === $options['auth'][2]) { + throw new \InvalidArgumentException('Microsoft NTLM authentication only supported with curl handler'); + } + + $uri = $this->resolveHost($request, $options); + + $contextResource = $this->createResource( + static function () use ($context, $params) { + return \stream_context_create($context, $params); + } + ); + + return $this->createResource( + function () use ($uri, &$http_response_header, $contextResource, $context, $options, $request) { + $resource = @\fopen((string) $uri, 'r', false, $contextResource); + $this->lastHeaders = $http_response_header ?? []; + + if (false === $resource) { + throw new ConnectException(sprintf('Connection refused for URI %s', $uri), $request, null, $context); + } + + if (isset($options['read_timeout'])) { + $readTimeout = $options['read_timeout']; + $sec = (int) $readTimeout; + $usec = ($readTimeout - $sec) * 100000; + \stream_set_timeout($resource, $sec, $usec); + } + + return $resource; + } + ); + } + + private function resolveHost(RequestInterface $request, array $options): UriInterface + { + $uri = $request->getUri(); + + if (isset($options['force_ip_resolve']) && !\filter_var($uri->getHost(), \FILTER_VALIDATE_IP)) { + if ('v4' === $options['force_ip_resolve']) { + $records = \dns_get_record($uri->getHost(), \DNS_A); + if (false === $records || !isset($records[0]['ip'])) { + throw new ConnectException(\sprintf("Could not resolve IPv4 address for host '%s'", $uri->getHost()), $request); + } + + return $uri->withHost($records[0]['ip']); + } + if ('v6' === $options['force_ip_resolve']) { + $records = \dns_get_record($uri->getHost(), \DNS_AAAA); + if (false === $records || !isset($records[0]['ipv6'])) { + throw new ConnectException(\sprintf("Could not resolve IPv6 address for host '%s'", $uri->getHost()), $request); + } + + return $uri->withHost('['.$records[0]['ipv6'].']'); + } + } + + return $uri; + } + + private function getDefaultContext(RequestInterface $request): array + { + $headers = ''; + foreach ($request->getHeaders() as $name => $value) { + foreach ($value as $val) { + $headers .= "$name: $val\r\n"; + } + } + + $context = [ + 'http' => [ + 'method' => $request->getMethod(), + 'header' => $headers, + 'protocol_version' => $request->getProtocolVersion(), + 'ignore_errors' => true, + 'follow_location' => 0, + ], + 'ssl' => [ + 'peer_name' => $request->getUri()->getHost(), + ], + ]; + + $body = (string) $request->getBody(); + + if ('' !== $body) { + $context['http']['content'] = $body; + // Prevent the HTTP handler from adding a Content-Type header. + if (!$request->hasHeader('Content-Type')) { + $context['http']['header'] .= "Content-Type:\r\n"; + } + } + + $context['http']['header'] = \rtrim($context['http']['header']); + + return $context; + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_proxy(RequestInterface $request, array &$options, $value, array &$params): void + { + $uri = null; + + if (!\is_array($value)) { + $uri = $value; + } else { + $scheme = $request->getUri()->getScheme(); + if (isset($value[$scheme])) { + if (!isset($value['no']) || !Utils::isHostInNoProxy($request->getUri()->getHost(), $value['no'])) { + $uri = $value[$scheme]; + } + } + } + + if (!$uri) { + return; + } + + $parsed = $this->parse_proxy($uri); + $options['http']['proxy'] = $parsed['proxy']; + + if ($parsed['auth']) { + if (!isset($options['http']['header'])) { + $options['http']['header'] = []; + } + $options['http']['header'] .= "\r\nProxy-Authorization: {$parsed['auth']}"; + } + } + + /** + * Parses the given proxy URL to make it compatible with the format PHP's stream context expects. + */ + private function parse_proxy(string $url): array + { + $parsed = \parse_url($url); + + if ($parsed !== false && isset($parsed['scheme']) && $parsed['scheme'] === 'http') { + if (isset($parsed['host']) && isset($parsed['port'])) { + $auth = null; + if (isset($parsed['user']) && isset($parsed['pass'])) { + $auth = \base64_encode("{$parsed['user']}:{$parsed['pass']}"); + } + + return [ + 'proxy' => "tcp://{$parsed['host']}:{$parsed['port']}", + 'auth' => $auth ? "Basic {$auth}" : null, + ]; + } + } + + // Return proxy as-is. + return [ + 'proxy' => $url, + 'auth' => null, + ]; + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_timeout(RequestInterface $request, array &$options, $value, array &$params): void + { + if ($value > 0) { + $options['http']['timeout'] = $value; + } + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_crypto_method(RequestInterface $request, array &$options, $value, array &$params): void + { + if ( + $value === \STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT + || $value === \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT + || $value === \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT + || (defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT') && $value === \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT) + ) { + $options['http']['crypto_method'] = $value; + + return; + } + + throw new \InvalidArgumentException('Invalid crypto_method request option: unknown version provided'); + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_verify(RequestInterface $request, array &$options, $value, array &$params): void + { + if ($value === false) { + $options['ssl']['verify_peer'] = false; + $options['ssl']['verify_peer_name'] = false; + + return; + } + + if (\is_string($value)) { + $options['ssl']['cafile'] = $value; + if (!\file_exists($value)) { + throw new \RuntimeException("SSL CA bundle not found: $value"); + } + } elseif ($value !== true) { + throw new \InvalidArgumentException('Invalid verify request option'); + } + + $options['ssl']['verify_peer'] = true; + $options['ssl']['verify_peer_name'] = true; + $options['ssl']['allow_self_signed'] = false; + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_cert(RequestInterface $request, array &$options, $value, array &$params): void + { + if (\is_array($value)) { + $options['ssl']['passphrase'] = $value[1]; + $value = $value[0]; + } + + if (!\file_exists($value)) { + throw new \RuntimeException("SSL certificate not found: {$value}"); + } + + $options['ssl']['local_cert'] = $value; + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_progress(RequestInterface $request, array &$options, $value, array &$params): void + { + self::addNotification( + $params, + static function ($code, $a, $b, $c, $transferred, $total) use ($value) { + if ($code == \STREAM_NOTIFY_PROGRESS) { + // The upload progress cannot be determined. Use 0 for cURL compatibility: + // https://curl.se/libcurl/c/CURLOPT_PROGRESSFUNCTION.html + $value($total, $transferred, 0, 0); + } + } + ); + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_debug(RequestInterface $request, array &$options, $value, array &$params): void + { + if ($value === false) { + return; + } + + static $map = [ + \STREAM_NOTIFY_CONNECT => 'CONNECT', + \STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED', + \STREAM_NOTIFY_AUTH_RESULT => 'AUTH_RESULT', + \STREAM_NOTIFY_MIME_TYPE_IS => 'MIME_TYPE_IS', + \STREAM_NOTIFY_FILE_SIZE_IS => 'FILE_SIZE_IS', + \STREAM_NOTIFY_REDIRECTED => 'REDIRECTED', + \STREAM_NOTIFY_PROGRESS => 'PROGRESS', + \STREAM_NOTIFY_FAILURE => 'FAILURE', + \STREAM_NOTIFY_COMPLETED => 'COMPLETED', + \STREAM_NOTIFY_RESOLVE => 'RESOLVE', + ]; + static $args = ['severity', 'message', 'message_code', 'bytes_transferred', 'bytes_max']; + + $value = Utils::debugResource($value); + $ident = $request->getMethod().' '.$request->getUri()->withFragment(''); + self::addNotification( + $params, + static function (int $code, ...$passed) use ($ident, $value, $map, $args): void { + \fprintf($value, '<%s> [%s] ', $ident, $map[$code]); + foreach (\array_filter($passed) as $i => $v) { + \fwrite($value, $args[$i].': "'.$v.'" '); + } + \fwrite($value, "\n"); + } + ); + } + + private static function addNotification(array &$params, callable $notify): void + { + // Wrap the existing function if needed. + if (!isset($params['notification'])) { + $params['notification'] = $notify; + } else { + $params['notification'] = self::callArray([ + $params['notification'], + $notify, + ]); + } + } + + private static function callArray(array $functions): callable + { + return static function (...$args) use ($functions) { + foreach ($functions as $fn) { + $fn(...$args); + } + }; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/HandlerStack.php b/vendor/guzzlehttp/guzzle/src/HandlerStack.php new file mode 100644 index 00000000..6cb12f07 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/HandlerStack.php @@ -0,0 +1,275 @@ +push(Middleware::httpErrors(), 'http_errors'); + $stack->push(Middleware::redirect(), 'allow_redirects'); + $stack->push(Middleware::cookies(), 'cookies'); + $stack->push(Middleware::prepareBody(), 'prepare_body'); + + return $stack; + } + + /** + * @param (callable(RequestInterface, array): PromiseInterface)|null $handler Underlying HTTP handler. + */ + public function __construct(callable $handler = null) + { + $this->handler = $handler; + } + + /** + * Invokes the handler stack as a composed handler + * + * @return ResponseInterface|PromiseInterface + */ + public function __invoke(RequestInterface $request, array $options) + { + $handler = $this->resolve(); + + return $handler($request, $options); + } + + /** + * Dumps a string representation of the stack. + * + * @return string + */ + public function __toString() + { + $depth = 0; + $stack = []; + + if ($this->handler !== null) { + $stack[] = '0) Handler: '.$this->debugCallable($this->handler); + } + + $result = ''; + foreach (\array_reverse($this->stack) as $tuple) { + ++$depth; + $str = "{$depth}) Name: '{$tuple[1]}', "; + $str .= 'Function: '.$this->debugCallable($tuple[0]); + $result = "> {$str}\n{$result}"; + $stack[] = $str; + } + + foreach (\array_keys($stack) as $k) { + $result .= "< {$stack[$k]}\n"; + } + + return $result; + } + + /** + * Set the HTTP handler that actually returns a promise. + * + * @param callable(RequestInterface, array): PromiseInterface $handler Accepts a request and array of options and + * returns a Promise. + */ + public function setHandler(callable $handler): void + { + $this->handler = $handler; + $this->cached = null; + } + + /** + * Returns true if the builder has a handler. + */ + public function hasHandler(): bool + { + return $this->handler !== null; + } + + /** + * Unshift a middleware to the bottom of the stack. + * + * @param callable(callable): callable $middleware Middleware function + * @param string $name Name to register for this middleware. + */ + public function unshift(callable $middleware, string $name = null): void + { + \array_unshift($this->stack, [$middleware, $name]); + $this->cached = null; + } + + /** + * Push a middleware to the top of the stack. + * + * @param callable(callable): callable $middleware Middleware function + * @param string $name Name to register for this middleware. + */ + public function push(callable $middleware, string $name = ''): void + { + $this->stack[] = [$middleware, $name]; + $this->cached = null; + } + + /** + * Add a middleware before another middleware by name. + * + * @param string $findName Middleware to find + * @param callable(callable): callable $middleware Middleware function + * @param string $withName Name to register for this middleware. + */ + public function before(string $findName, callable $middleware, string $withName = ''): void + { + $this->splice($findName, $withName, $middleware, true); + } + + /** + * Add a middleware after another middleware by name. + * + * @param string $findName Middleware to find + * @param callable(callable): callable $middleware Middleware function + * @param string $withName Name to register for this middleware. + */ + public function after(string $findName, callable $middleware, string $withName = ''): void + { + $this->splice($findName, $withName, $middleware, false); + } + + /** + * Remove a middleware by instance or name from the stack. + * + * @param callable|string $remove Middleware to remove by instance or name. + */ + public function remove($remove): void + { + if (!is_string($remove) && !is_callable($remove)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a callable or string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->cached = null; + $idx = \is_callable($remove) ? 0 : 1; + $this->stack = \array_values(\array_filter( + $this->stack, + static function ($tuple) use ($idx, $remove) { + return $tuple[$idx] !== $remove; + } + )); + } + + /** + * Compose the middleware and handler into a single callable function. + * + * @return callable(RequestInterface, array): PromiseInterface + */ + public function resolve(): callable + { + if ($this->cached === null) { + if (($prev = $this->handler) === null) { + throw new \LogicException('No handler has been specified'); + } + + foreach (\array_reverse($this->stack) as $fn) { + /** @var callable(RequestInterface, array): PromiseInterface $prev */ + $prev = $fn[0]($prev); + } + + $this->cached = $prev; + } + + return $this->cached; + } + + private function findByName(string $name): int + { + foreach ($this->stack as $k => $v) { + if ($v[1] === $name) { + return $k; + } + } + + throw new \InvalidArgumentException("Middleware not found: $name"); + } + + /** + * Splices a function into the middleware list at a specific position. + */ + private function splice(string $findName, string $withName, callable $middleware, bool $before): void + { + $this->cached = null; + $idx = $this->findByName($findName); + $tuple = [$middleware, $withName]; + + if ($before) { + if ($idx === 0) { + \array_unshift($this->stack, $tuple); + } else { + $replacement = [$tuple, $this->stack[$idx]]; + \array_splice($this->stack, $idx, 1, $replacement); + } + } elseif ($idx === \count($this->stack) - 1) { + $this->stack[] = $tuple; + } else { + $replacement = [$this->stack[$idx], $tuple]; + \array_splice($this->stack, $idx, 1, $replacement); + } + } + + /** + * Provides a debug string for a given callable. + * + * @param callable|string $fn Function to write as a string. + */ + private function debugCallable($fn): string + { + if (\is_string($fn)) { + return "callable({$fn})"; + } + + if (\is_array($fn)) { + return \is_string($fn[0]) + ? "callable({$fn[0]}::{$fn[1]})" + : "callable(['".\get_class($fn[0])."', '{$fn[1]}'])"; + } + + /** @var object $fn */ + return 'callable('.\spl_object_hash($fn).')'; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/MessageFormatter.php b/vendor/guzzlehttp/guzzle/src/MessageFormatter.php new file mode 100644 index 00000000..04e9eb37 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/MessageFormatter.php @@ -0,0 +1,199 @@ +>>>>>>>\n{request}\n<<<<<<<<\n{response}\n--------\n{error}"; + public const SHORT = '[{ts}] "{method} {target} HTTP/{version}" {code}'; + + /** + * @var string Template used to format log messages + */ + private $template; + + /** + * @param string $template Log message template + */ + public function __construct(?string $template = self::CLF) + { + $this->template = $template ?: self::CLF; + } + + /** + * Returns a formatted message string. + * + * @param RequestInterface $request Request that was sent + * @param ResponseInterface|null $response Response that was received + * @param \Throwable|null $error Exception that was received + */ + public function format(RequestInterface $request, ResponseInterface $response = null, \Throwable $error = null): string + { + $cache = []; + + /** @var string */ + return \preg_replace_callback( + '/{\s*([A-Za-z_\-\.0-9]+)\s*}/', + function (array $matches) use ($request, $response, $error, &$cache) { + if (isset($cache[$matches[1]])) { + return $cache[$matches[1]]; + } + + $result = ''; + switch ($matches[1]) { + case 'request': + $result = Psr7\Message::toString($request); + break; + case 'response': + $result = $response ? Psr7\Message::toString($response) : ''; + break; + case 'req_headers': + $result = \trim($request->getMethod() + .' '.$request->getRequestTarget()) + .' HTTP/'.$request->getProtocolVersion()."\r\n" + .$this->headers($request); + break; + case 'res_headers': + $result = $response ? + \sprintf( + 'HTTP/%s %d %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + )."\r\n".$this->headers($response) + : 'NULL'; + break; + case 'req_body': + $result = $request->getBody()->__toString(); + break; + case 'res_body': + if (!$response instanceof ResponseInterface) { + $result = 'NULL'; + break; + } + + $body = $response->getBody(); + + if (!$body->isSeekable()) { + $result = 'RESPONSE_NOT_LOGGEABLE'; + break; + } + + $result = $response->getBody()->__toString(); + break; + case 'ts': + case 'date_iso_8601': + $result = \gmdate('c'); + break; + case 'date_common_log': + $result = \date('d/M/Y:H:i:s O'); + break; + case 'method': + $result = $request->getMethod(); + break; + case 'version': + $result = $request->getProtocolVersion(); + break; + case 'uri': + case 'url': + $result = $request->getUri()->__toString(); + break; + case 'target': + $result = $request->getRequestTarget(); + break; + case 'req_version': + $result = $request->getProtocolVersion(); + break; + case 'res_version': + $result = $response + ? $response->getProtocolVersion() + : 'NULL'; + break; + case 'host': + $result = $request->getHeaderLine('Host'); + break; + case 'hostname': + $result = \gethostname(); + break; + case 'code': + $result = $response ? $response->getStatusCode() : 'NULL'; + break; + case 'phrase': + $result = $response ? $response->getReasonPhrase() : 'NULL'; + break; + case 'error': + $result = $error ? $error->getMessage() : 'NULL'; + break; + default: + // handle prefixed dynamic headers + if (\strpos($matches[1], 'req_header_') === 0) { + $result = $request->getHeaderLine(\substr($matches[1], 11)); + } elseif (\strpos($matches[1], 'res_header_') === 0) { + $result = $response + ? $response->getHeaderLine(\substr($matches[1], 11)) + : 'NULL'; + } + } + + $cache[$matches[1]] = $result; + + return $result; + }, + $this->template + ); + } + + /** + * Get headers from message as string + */ + private function headers(MessageInterface $message): string + { + $result = ''; + foreach ($message->getHeaders() as $name => $values) { + $result .= $name.': '.\implode(', ', $values)."\r\n"; + } + + return \trim($result); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/MessageFormatterInterface.php b/vendor/guzzlehttp/guzzle/src/MessageFormatterInterface.php new file mode 100644 index 00000000..47934614 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/MessageFormatterInterface.php @@ -0,0 +1,18 @@ +withCookieHeader($request); + + return $handler($request, $options) + ->then( + static function (ResponseInterface $response) use ($cookieJar, $request): ResponseInterface { + $cookieJar->extractCookies($request, $response); + + return $response; + } + ); + }; + }; + } + + /** + * Middleware that throws exceptions for 4xx or 5xx responses when the + * "http_errors" request option is set to true. + * + * @param BodySummarizerInterface|null $bodySummarizer The body summarizer to use in exception messages. + * + * @return callable(callable): callable Returns a function that accepts the next handler. + */ + public static function httpErrors(BodySummarizerInterface $bodySummarizer = null): callable + { + return static function (callable $handler) use ($bodySummarizer): callable { + return static function ($request, array $options) use ($handler, $bodySummarizer) { + if (empty($options['http_errors'])) { + return $handler($request, $options); + } + + return $handler($request, $options)->then( + static function (ResponseInterface $response) use ($request, $bodySummarizer) { + $code = $response->getStatusCode(); + if ($code < 400) { + return $response; + } + throw RequestException::create($request, $response, null, [], $bodySummarizer); + } + ); + }; + }; + } + + /** + * Middleware that pushes history data to an ArrayAccess container. + * + * @param array|\ArrayAccess $container Container to hold the history (by reference). + * + * @return callable(callable): callable Returns a function that accepts the next handler. + * + * @throws \InvalidArgumentException if container is not an array or ArrayAccess. + */ + public static function history(&$container): callable + { + if (!\is_array($container) && !$container instanceof \ArrayAccess) { + throw new \InvalidArgumentException('history container must be an array or object implementing ArrayAccess'); + } + + return static function (callable $handler) use (&$container): callable { + return static function (RequestInterface $request, array $options) use ($handler, &$container) { + return $handler($request, $options)->then( + static function ($value) use ($request, &$container, $options) { + $container[] = [ + 'request' => $request, + 'response' => $value, + 'error' => null, + 'options' => $options, + ]; + + return $value; + }, + static function ($reason) use ($request, &$container, $options) { + $container[] = [ + 'request' => $request, + 'response' => null, + 'error' => $reason, + 'options' => $options, + ]; + + return P\Create::rejectionFor($reason); + } + ); + }; + }; + } + + /** + * Middleware that invokes a callback before and after sending a request. + * + * The provided listener cannot modify or alter the response. It simply + * "taps" into the chain to be notified before returning the promise. The + * before listener accepts a request and options array, and the after + * listener accepts a request, options array, and response promise. + * + * @param callable $before Function to invoke before forwarding the request. + * @param callable $after Function invoked after forwarding. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function tap(callable $before = null, callable $after = null): callable + { + return static function (callable $handler) use ($before, $after): callable { + return static function (RequestInterface $request, array $options) use ($handler, $before, $after) { + if ($before) { + $before($request, $options); + } + $response = $handler($request, $options); + if ($after) { + $after($request, $options, $response); + } + + return $response; + }; + }; + } + + /** + * Middleware that handles request redirects. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function redirect(): callable + { + return static function (callable $handler): RedirectMiddleware { + return new RedirectMiddleware($handler); + }; + } + + /** + * Middleware that retries requests based on the boolean result of + * invoking the provided "decider" function. + * + * If no delay function is provided, a simple implementation of exponential + * backoff will be utilized. + * + * @param callable $decider Function that accepts the number of retries, + * a request, [response], and [exception] and + * returns true if the request is to be retried. + * @param callable $delay Function that accepts the number of retries and + * returns the number of milliseconds to delay. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function retry(callable $decider, callable $delay = null): callable + { + return static function (callable $handler) use ($decider, $delay): RetryMiddleware { + return new RetryMiddleware($decider, $handler, $delay); + }; + } + + /** + * Middleware that logs requests, responses, and errors using a message + * formatter. + * + * @phpstan-param \Psr\Log\LogLevel::* $logLevel Level at which to log requests. + * + * @param LoggerInterface $logger Logs messages. + * @param MessageFormatterInterface|MessageFormatter $formatter Formatter used to create message strings. + * @param string $logLevel Level at which to log requests. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function log(LoggerInterface $logger, $formatter, string $logLevel = 'info'): callable + { + // To be compatible with Guzzle 7.1.x we need to allow users to pass a MessageFormatter + if (!$formatter instanceof MessageFormatter && !$formatter instanceof MessageFormatterInterface) { + throw new \LogicException(sprintf('Argument 2 to %s::log() must be of type %s', self::class, MessageFormatterInterface::class)); + } + + return static function (callable $handler) use ($logger, $formatter, $logLevel): callable { + return static function (RequestInterface $request, array $options = []) use ($handler, $logger, $formatter, $logLevel) { + return $handler($request, $options)->then( + static function ($response) use ($logger, $request, $formatter, $logLevel): ResponseInterface { + $message = $formatter->format($request, $response); + $logger->log($logLevel, $message); + + return $response; + }, + static function ($reason) use ($logger, $request, $formatter): PromiseInterface { + $response = $reason instanceof RequestException ? $reason->getResponse() : null; + $message = $formatter->format($request, $response, P\Create::exceptionFor($reason)); + $logger->error($message); + + return P\Create::rejectionFor($reason); + } + ); + }; + }; + } + + /** + * This middleware adds a default content-type if possible, a default + * content-length or transfer-encoding header, and the expect header. + */ + public static function prepareBody(): callable + { + return static function (callable $handler): PrepareBodyMiddleware { + return new PrepareBodyMiddleware($handler); + }; + } + + /** + * Middleware that applies a map function to the request before passing to + * the next handler. + * + * @param callable $fn Function that accepts a RequestInterface and returns + * a RequestInterface. + */ + public static function mapRequest(callable $fn): callable + { + return static function (callable $handler) use ($fn): callable { + return static function (RequestInterface $request, array $options) use ($handler, $fn) { + return $handler($fn($request), $options); + }; + }; + } + + /** + * Middleware that applies a map function to the resolved promise's + * response. + * + * @param callable $fn Function that accepts a ResponseInterface and + * returns a ResponseInterface. + */ + public static function mapResponse(callable $fn): callable + { + return static function (callable $handler) use ($fn): callable { + return static function (RequestInterface $request, array $options) use ($handler, $fn) { + return $handler($request, $options)->then($fn); + }; + }; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Pool.php b/vendor/guzzlehttp/guzzle/src/Pool.php new file mode 100644 index 00000000..6277c61f --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Pool.php @@ -0,0 +1,125 @@ + $rfn) { + if ($rfn instanceof RequestInterface) { + yield $key => $client->sendAsync($rfn, $opts); + } elseif (\is_callable($rfn)) { + yield $key => $rfn($opts); + } else { + throw new \InvalidArgumentException('Each value yielded by the iterator must be a Psr7\Http\Message\RequestInterface or a callable that returns a promise that fulfills with a Psr7\Message\Http\ResponseInterface object.'); + } + } + }; + + $this->each = new EachPromise($requests(), $config); + } + + /** + * Get promise + */ + public function promise(): PromiseInterface + { + return $this->each->promise(); + } + + /** + * Sends multiple requests concurrently and returns an array of responses + * and exceptions that uses the same ordering as the provided requests. + * + * IMPORTANT: This method keeps every request and response in memory, and + * as such, is NOT recommended when sending a large number or an + * indeterminate number of requests concurrently. + * + * @param ClientInterface $client Client used to send the requests + * @param array|\Iterator $requests Requests to send concurrently. + * @param array $options Passes through the options available in + * {@see \GuzzleHttp\Pool::__construct} + * + * @return array Returns an array containing the response or an exception + * in the same order that the requests were sent. + * + * @throws \InvalidArgumentException if the event format is incorrect. + */ + public static function batch(ClientInterface $client, $requests, array $options = []): array + { + $res = []; + self::cmpCallback($options, 'fulfilled', $res); + self::cmpCallback($options, 'rejected', $res); + $pool = new static($client, $requests, $options); + $pool->promise()->wait(); + \ksort($res); + + return $res; + } + + /** + * Execute callback(s) + */ + private static function cmpCallback(array &$options, string $name, array &$results): void + { + if (!isset($options[$name])) { + $options[$name] = static function ($v, $k) use (&$results) { + $results[$k] = $v; + }; + } else { + $currentFn = $options[$name]; + $options[$name] = static function ($v, $k) use (&$results, $currentFn) { + $currentFn($v, $k); + $results[$k] = $v; + }; + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php b/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php new file mode 100644 index 00000000..0a8de812 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php @@ -0,0 +1,105 @@ +nextHandler = $nextHandler; + } + + public function __invoke(RequestInterface $request, array $options): PromiseInterface + { + $fn = $this->nextHandler; + + // Don't do anything if the request has no body. + if ($request->getBody()->getSize() === 0) { + return $fn($request, $options); + } + + $modify = []; + + // Add a default content-type if possible. + if (!$request->hasHeader('Content-Type')) { + if ($uri = $request->getBody()->getMetadata('uri')) { + if (is_string($uri) && $type = Psr7\MimeType::fromFilename($uri)) { + $modify['set_headers']['Content-Type'] = $type; + } + } + } + + // Add a default content-length or transfer-encoding header. + if (!$request->hasHeader('Content-Length') + && !$request->hasHeader('Transfer-Encoding') + ) { + $size = $request->getBody()->getSize(); + if ($size !== null) { + $modify['set_headers']['Content-Length'] = $size; + } else { + $modify['set_headers']['Transfer-Encoding'] = 'chunked'; + } + } + + // Add the expect header if needed. + $this->addExpectHeader($request, $options, $modify); + + return $fn(Psr7\Utils::modifyRequest($request, $modify), $options); + } + + /** + * Add expect header + */ + private function addExpectHeader(RequestInterface $request, array $options, array &$modify): void + { + // Determine if the Expect header should be used + if ($request->hasHeader('Expect')) { + return; + } + + $expect = $options['expect'] ?? null; + + // Return if disabled or if you're not using HTTP/1.1 or HTTP/2.0 + if ($expect === false || $request->getProtocolVersion() < 1.1) { + return; + } + + // The expect header is unconditionally enabled + if ($expect === true) { + $modify['set_headers']['Expect'] = '100-Continue'; + + return; + } + + // By default, send the expect header when the payload is > 1mb + if ($expect === null) { + $expect = 1048576; + } + + // Always add if the body cannot be rewound, the size cannot be + // determined, or the size is greater than the cutoff threshold + $body = $request->getBody(); + $size = $body->getSize(); + + if ($size === null || $size >= (int) $expect || !$body->isSeekable()) { + $modify['set_headers']['Expect'] = '100-Continue'; + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php b/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php new file mode 100644 index 00000000..7aa21a62 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php @@ -0,0 +1,228 @@ + 5, + 'protocols' => ['http', 'https'], + 'strict' => false, + 'referer' => false, + 'track_redirects' => false, + ]; + + /** + * @var callable(RequestInterface, array): PromiseInterface + */ + private $nextHandler; + + /** + * @param callable(RequestInterface, array): PromiseInterface $nextHandler Next handler to invoke. + */ + public function __construct(callable $nextHandler) + { + $this->nextHandler = $nextHandler; + } + + public function __invoke(RequestInterface $request, array $options): PromiseInterface + { + $fn = $this->nextHandler; + + if (empty($options['allow_redirects'])) { + return $fn($request, $options); + } + + if ($options['allow_redirects'] === true) { + $options['allow_redirects'] = self::$defaultSettings; + } elseif (!\is_array($options['allow_redirects'])) { + throw new \InvalidArgumentException('allow_redirects must be true, false, or array'); + } else { + // Merge the default settings with the provided settings + $options['allow_redirects'] += self::$defaultSettings; + } + + if (empty($options['allow_redirects']['max'])) { + return $fn($request, $options); + } + + return $fn($request, $options) + ->then(function (ResponseInterface $response) use ($request, $options) { + return $this->checkRedirect($request, $options, $response); + }); + } + + /** + * @return ResponseInterface|PromiseInterface + */ + public function checkRedirect(RequestInterface $request, array $options, ResponseInterface $response) + { + if (\strpos((string) $response->getStatusCode(), '3') !== 0 + || !$response->hasHeader('Location') + ) { + return $response; + } + + $this->guardMax($request, $response, $options); + $nextRequest = $this->modifyRequest($request, $options, $response); + + // If authorization is handled by curl, unset it if URI is cross-origin. + if (Psr7\UriComparator::isCrossOrigin($request->getUri(), $nextRequest->getUri()) && defined('\CURLOPT_HTTPAUTH')) { + unset( + $options['curl'][\CURLOPT_HTTPAUTH], + $options['curl'][\CURLOPT_USERPWD] + ); + } + + if (isset($options['allow_redirects']['on_redirect'])) { + ($options['allow_redirects']['on_redirect'])( + $request, + $response, + $nextRequest->getUri() + ); + } + + $promise = $this($nextRequest, $options); + + // Add headers to be able to track history of redirects. + if (!empty($options['allow_redirects']['track_redirects'])) { + return $this->withTracking( + $promise, + (string) $nextRequest->getUri(), + $response->getStatusCode() + ); + } + + return $promise; + } + + /** + * Enable tracking on promise. + */ + private function withTracking(PromiseInterface $promise, string $uri, int $statusCode): PromiseInterface + { + return $promise->then( + static function (ResponseInterface $response) use ($uri, $statusCode) { + // Note that we are pushing to the front of the list as this + // would be an earlier response than what is currently present + // in the history header. + $historyHeader = $response->getHeader(self::HISTORY_HEADER); + $statusHeader = $response->getHeader(self::STATUS_HISTORY_HEADER); + \array_unshift($historyHeader, $uri); + \array_unshift($statusHeader, (string) $statusCode); + + return $response->withHeader(self::HISTORY_HEADER, $historyHeader) + ->withHeader(self::STATUS_HISTORY_HEADER, $statusHeader); + } + ); + } + + /** + * Check for too many redirects. + * + * @throws TooManyRedirectsException Too many redirects. + */ + private function guardMax(RequestInterface $request, ResponseInterface $response, array &$options): void + { + $current = $options['__redirect_count'] + ?? 0; + $options['__redirect_count'] = $current + 1; + $max = $options['allow_redirects']['max']; + + if ($options['__redirect_count'] > $max) { + throw new TooManyRedirectsException("Will not follow more than {$max} redirects", $request, $response); + } + } + + public function modifyRequest(RequestInterface $request, array $options, ResponseInterface $response): RequestInterface + { + // Request modifications to apply. + $modify = []; + $protocols = $options['allow_redirects']['protocols']; + + // Use a GET request if this is an entity enclosing request and we are + // not forcing RFC compliance, but rather emulating what all browsers + // would do. + $statusCode = $response->getStatusCode(); + if ($statusCode == 303 + || ($statusCode <= 302 && !$options['allow_redirects']['strict']) + ) { + $safeMethods = ['GET', 'HEAD', 'OPTIONS']; + $requestMethod = $request->getMethod(); + + $modify['method'] = in_array($requestMethod, $safeMethods) ? $requestMethod : 'GET'; + $modify['body'] = ''; + } + + $uri = self::redirectUri($request, $response, $protocols); + if (isset($options['idn_conversion']) && ($options['idn_conversion'] !== false)) { + $idnOptions = ($options['idn_conversion'] === true) ? \IDNA_DEFAULT : $options['idn_conversion']; + $uri = Utils::idnUriConvert($uri, $idnOptions); + } + + $modify['uri'] = $uri; + Psr7\Message::rewindBody($request); + + // Add the Referer header if it is told to do so and only + // add the header if we are not redirecting from https to http. + if ($options['allow_redirects']['referer'] + && $modify['uri']->getScheme() === $request->getUri()->getScheme() + ) { + $uri = $request->getUri()->withUserInfo(''); + $modify['set_headers']['Referer'] = (string) $uri; + } else { + $modify['remove_headers'][] = 'Referer'; + } + + // Remove Authorization and Cookie headers if URI is cross-origin. + if (Psr7\UriComparator::isCrossOrigin($request->getUri(), $modify['uri'])) { + $modify['remove_headers'][] = 'Authorization'; + $modify['remove_headers'][] = 'Cookie'; + } + + return Psr7\Utils::modifyRequest($request, $modify); + } + + /** + * Set the appropriate URL on the request based on the location header. + */ + private static function redirectUri( + RequestInterface $request, + ResponseInterface $response, + array $protocols + ): UriInterface { + $location = Psr7\UriResolver::resolve( + $request->getUri(), + new Psr7\Uri($response->getHeaderLine('Location')) + ); + + // Ensure that the redirect URI is allowed based on the protocols. + if (!\in_array($location->getScheme(), $protocols)) { + throw new BadResponseException(\sprintf('Redirect URI, %s, does not use one of the allowed redirect protocols: %s', $location, \implode(', ', $protocols)), $request, $response); + } + + return $location; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/RequestOptions.php b/vendor/guzzlehttp/guzzle/src/RequestOptions.php new file mode 100644 index 00000000..a38768c0 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/RequestOptions.php @@ -0,0 +1,274 @@ +decider = $decider; + $this->nextHandler = $nextHandler; + $this->delay = $delay ?: __CLASS__.'::exponentialDelay'; + } + + /** + * Default exponential backoff delay function. + * + * @return int milliseconds. + */ + public static function exponentialDelay(int $retries): int + { + return (int) 2 ** ($retries - 1) * 1000; + } + + public function __invoke(RequestInterface $request, array $options): PromiseInterface + { + if (!isset($options['retries'])) { + $options['retries'] = 0; + } + + $fn = $this->nextHandler; + + return $fn($request, $options) + ->then( + $this->onFulfilled($request, $options), + $this->onRejected($request, $options) + ); + } + + /** + * Execute fulfilled closure + */ + private function onFulfilled(RequestInterface $request, array $options): callable + { + return function ($value) use ($request, $options) { + if (!($this->decider)( + $options['retries'], + $request, + $value, + null + )) { + return $value; + } + + return $this->doRetry($request, $options, $value); + }; + } + + /** + * Execute rejected closure + */ + private function onRejected(RequestInterface $req, array $options): callable + { + return function ($reason) use ($req, $options) { + if (!($this->decider)( + $options['retries'], + $req, + null, + $reason + )) { + return P\Create::rejectionFor($reason); + } + + return $this->doRetry($req, $options); + }; + } + + private function doRetry(RequestInterface $request, array $options, ResponseInterface $response = null): PromiseInterface + { + $options['delay'] = ($this->delay)(++$options['retries'], $response, $request); + + return $this($request, $options); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/TransferStats.php b/vendor/guzzlehttp/guzzle/src/TransferStats.php new file mode 100644 index 00000000..2ce9e38f --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/TransferStats.php @@ -0,0 +1,133 @@ +request = $request; + $this->response = $response; + $this->transferTime = $transferTime; + $this->handlerErrorData = $handlerErrorData; + $this->handlerStats = $handlerStats; + } + + public function getRequest(): RequestInterface + { + return $this->request; + } + + /** + * Returns the response that was received (if any). + */ + public function getResponse(): ?ResponseInterface + { + return $this->response; + } + + /** + * Returns true if a response was received. + */ + public function hasResponse(): bool + { + return $this->response !== null; + } + + /** + * Gets handler specific error data. + * + * This might be an exception, a integer representing an error code, or + * anything else. Relying on this value assumes that you know what handler + * you are using. + * + * @return mixed + */ + public function getHandlerErrorData() + { + return $this->handlerErrorData; + } + + /** + * Get the effective URI the request was sent to. + */ + public function getEffectiveUri(): UriInterface + { + return $this->request->getUri(); + } + + /** + * Get the estimated time the request was being transferred by the handler. + * + * @return float|null Time in seconds. + */ + public function getTransferTime(): ?float + { + return $this->transferTime; + } + + /** + * Gets an array of all of the handler specific transfer data. + */ + public function getHandlerStats(): array + { + return $this->handlerStats; + } + + /** + * Get a specific handler statistic from the handler by name. + * + * @param string $stat Handler specific transfer stat to retrieve. + * + * @return mixed|null + */ + public function getHandlerStat(string $stat) + { + return $this->handlerStats[$stat] ?? null; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Utils.php b/vendor/guzzlehttp/guzzle/src/Utils.php new file mode 100644 index 00000000..93d6d39c --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Utils.php @@ -0,0 +1,384 @@ +getHost()) { + $asciiHost = self::idnToAsci($uri->getHost(), $options, $info); + if ($asciiHost === false) { + $errorBitSet = $info['errors'] ?? 0; + + $errorConstants = array_filter(array_keys(get_defined_constants()), static function (string $name): bool { + return substr($name, 0, 11) === 'IDNA_ERROR_'; + }); + + $errors = []; + foreach ($errorConstants as $errorConstant) { + if ($errorBitSet & constant($errorConstant)) { + $errors[] = $errorConstant; + } + } + + $errorMessage = 'IDN conversion failed'; + if ($errors) { + $errorMessage .= ' (errors: '.implode(', ', $errors).')'; + } + + throw new InvalidArgumentException($errorMessage); + } + if ($uri->getHost() !== $asciiHost) { + // Replace URI only if the ASCII version is different + $uri = $uri->withHost($asciiHost); + } + } + + return $uri; + } + + /** + * @internal + */ + public static function getenv(string $name): ?string + { + if (isset($_SERVER[$name])) { + return (string) $_SERVER[$name]; + } + + if (\PHP_SAPI === 'cli' && ($value = \getenv($name)) !== false && $value !== null) { + return (string) $value; + } + + return null; + } + + /** + * @return string|false + */ + private static function idnToAsci(string $domain, int $options, ?array &$info = []) + { + if (\function_exists('idn_to_ascii') && \defined('INTL_IDNA_VARIANT_UTS46')) { + return \idn_to_ascii($domain, $options, \INTL_IDNA_VARIANT_UTS46, $info); + } + + throw new \Error('ext-idn or symfony/polyfill-intl-idn not loaded or too old'); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/functions.php b/vendor/guzzlehttp/guzzle/src/functions.php new file mode 100644 index 00000000..5edc66ab --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/functions.php @@ -0,0 +1,167 @@ + +Copyright (c) 2015 Graham Campbell +Copyright (c) 2017 Tobias Schultze +Copyright (c) 2020 Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/guzzlehttp/promises/README.md b/vendor/guzzlehttp/promises/README.md new file mode 100644 index 00000000..a32d3d29 --- /dev/null +++ b/vendor/guzzlehttp/promises/README.md @@ -0,0 +1,559 @@ +# Guzzle Promises + +[Promises/A+](https://promisesaplus.com/) implementation that handles promise +chaining and resolution iteratively, allowing for "infinite" promise chaining +while keeping the stack size constant. Read [this blog post](https://blog.domenic.me/youre-missing-the-point-of-promises/) +for a general introduction to promises. + +- [Features](#features) +- [Quick start](#quick-start) +- [Synchronous wait](#synchronous-wait) +- [Cancellation](#cancellation) +- [API](#api) + - [Promise](#promise) + - [FulfilledPromise](#fulfilledpromise) + - [RejectedPromise](#rejectedpromise) +- [Promise interop](#promise-interop) +- [Implementation notes](#implementation-notes) + + +## Features + +- [Promises/A+](https://promisesaplus.com/) implementation. +- Promise resolution and chaining is handled iteratively, allowing for + "infinite" promise chaining. +- Promises have a synchronous `wait` method. +- Promises can be cancelled. +- Works with any object that has a `then` function. +- C# style async/await coroutine promises using + `GuzzleHttp\Promise\Coroutine::of()`. + + +## Installation + +```shell +composer require guzzlehttp/promises +``` + + +## Version Guidance + +| Version | Status | PHP Version | +|---------|------------------------|--------------| +| 1.x | Bug and security fixes | >=5.5,<8.3 | +| 2.x | Latest | >=7.2.5,<8.4 | + + +## Quick Start + +A *promise* represents the eventual result of an asynchronous operation. The +primary way of interacting with a promise is through its `then` method, which +registers callbacks to receive either a promise's eventual value or the reason +why the promise cannot be fulfilled. + +### Callbacks + +Callbacks are registered with the `then` method by providing an optional +`$onFulfilled` followed by an optional `$onRejected` function. + + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise->then( + // $onFulfilled + function ($value) { + echo 'The promise was fulfilled.'; + }, + // $onRejected + function ($reason) { + echo 'The promise was rejected.'; + } +); +``` + +*Resolving* a promise means that you either fulfill a promise with a *value* or +reject a promise with a *reason*. Resolving a promise triggers callbacks +registered with the promise's `then` method. These callbacks are triggered +only once and in the order in which they were added. + +### Resolving a Promise + +Promises are fulfilled using the `resolve($value)` method. Resolving a promise +with any value other than a `GuzzleHttp\Promise\RejectedPromise` will trigger +all of the onFulfilled callbacks (resolving a promise with a rejected promise +will reject the promise and trigger the `$onRejected` callbacks). + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise + ->then(function ($value) { + // Return a value and don't break the chain + return "Hello, " . $value; + }) + // This then is executed after the first then and receives the value + // returned from the first then. + ->then(function ($value) { + echo $value; + }); + +// Resolving the promise triggers the $onFulfilled callbacks and outputs +// "Hello, reader." +$promise->resolve('reader.'); +``` + +### Promise Forwarding + +Promises can be chained one after the other. Each then in the chain is a new +promise. The return value of a promise is what's forwarded to the next +promise in the chain. Returning a promise in a `then` callback will cause the +subsequent promises in the chain to only be fulfilled when the returned promise +has been fulfilled. The next promise in the chain will be invoked with the +resolved value of the promise. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$nextPromise = new Promise(); + +$promise + ->then(function ($value) use ($nextPromise) { + echo $value; + return $nextPromise; + }) + ->then(function ($value) { + echo $value; + }); + +// Triggers the first callback and outputs "A" +$promise->resolve('A'); +// Triggers the second callback and outputs "B" +$nextPromise->resolve('B'); +``` + +### Promise Rejection + +When a promise is rejected, the `$onRejected` callbacks are invoked with the +rejection reason. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise->then(null, function ($reason) { + echo $reason; +}); + +$promise->reject('Error!'); +// Outputs "Error!" +``` + +### Rejection Forwarding + +If an exception is thrown in an `$onRejected` callback, subsequent +`$onRejected` callbacks are invoked with the thrown exception as the reason. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise->then(null, function ($reason) { + throw new Exception($reason); +})->then(null, function ($reason) { + assert($reason->getMessage() === 'Error!'); +}); + +$promise->reject('Error!'); +``` + +You can also forward a rejection down the promise chain by returning a +`GuzzleHttp\Promise\RejectedPromise` in either an `$onFulfilled` or +`$onRejected` callback. + +```php +use GuzzleHttp\Promise\Promise; +use GuzzleHttp\Promise\RejectedPromise; + +$promise = new Promise(); +$promise->then(null, function ($reason) { + return new RejectedPromise($reason); +})->then(null, function ($reason) { + assert($reason === 'Error!'); +}); + +$promise->reject('Error!'); +``` + +If an exception is not thrown in a `$onRejected` callback and the callback +does not return a rejected promise, downstream `$onFulfilled` callbacks are +invoked using the value returned from the `$onRejected` callback. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise + ->then(null, function ($reason) { + return "It's ok"; + }) + ->then(function ($value) { + assert($value === "It's ok"); + }); + +$promise->reject('Error!'); +``` + + +## Synchronous Wait + +You can synchronously force promises to complete using a promise's `wait` +method. When creating a promise, you can provide a wait function that is used +to synchronously force a promise to complete. When a wait function is invoked +it is expected to deliver a value to the promise or reject the promise. If the +wait function does not deliver a value, then an exception is thrown. The wait +function provided to a promise constructor is invoked when the `wait` function +of the promise is called. + +```php +$promise = new Promise(function () use (&$promise) { + $promise->resolve('foo'); +}); + +// Calling wait will return the value of the promise. +echo $promise->wait(); // outputs "foo" +``` + +If an exception is encountered while invoking the wait function of a promise, +the promise is rejected with the exception and the exception is thrown. + +```php +$promise = new Promise(function () use (&$promise) { + throw new Exception('foo'); +}); + +$promise->wait(); // throws the exception. +``` + +Calling `wait` on a promise that has been fulfilled will not trigger the wait +function. It will simply return the previously resolved value. + +```php +$promise = new Promise(function () { die('this is not called!'); }); +$promise->resolve('foo'); +echo $promise->wait(); // outputs "foo" +``` + +Calling `wait` on a promise that has been rejected will throw an exception. If +the rejection reason is an instance of `\Exception` the reason is thrown. +Otherwise, a `GuzzleHttp\Promise\RejectionException` is thrown and the reason +can be obtained by calling the `getReason` method of the exception. + +```php +$promise = new Promise(); +$promise->reject('foo'); +$promise->wait(); +``` + +> PHP Fatal error: Uncaught exception 'GuzzleHttp\Promise\RejectionException' with message 'The promise was rejected with value: foo' + +### Unwrapping a Promise + +When synchronously waiting on a promise, you are joining the state of the +promise into the current state of execution (i.e., return the value of the +promise if it was fulfilled or throw an exception if it was rejected). This is +called "unwrapping" the promise. Waiting on a promise will by default unwrap +the promise state. + +You can force a promise to resolve and *not* unwrap the state of the promise +by passing `false` to the first argument of the `wait` function: + +```php +$promise = new Promise(); +$promise->reject('foo'); +// This will not throw an exception. It simply ensures the promise has +// been resolved. +$promise->wait(false); +``` + +When unwrapping a promise, the resolved value of the promise will be waited +upon until the unwrapped value is not a promise. This means that if you resolve +promise A with a promise B and unwrap promise A, the value returned by the +wait function will be the value delivered to promise B. + +**Note**: when you do not unwrap the promise, no value is returned. + + +## Cancellation + +You can cancel a promise that has not yet been fulfilled using the `cancel()` +method of a promise. When creating a promise you can provide an optional +cancel function that when invoked cancels the action of computing a resolution +of the promise. + + +## API + +### Promise + +When creating a promise object, you can provide an optional `$waitFn` and +`$cancelFn`. `$waitFn` is a function that is invoked with no arguments and is +expected to resolve the promise. `$cancelFn` is a function with no arguments +that is expected to cancel the computation of a promise. It is invoked when the +`cancel()` method of a promise is called. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise( + function () use (&$promise) { + $promise->resolve('waited'); + }, + function () { + // do something that will cancel the promise computation (e.g., close + // a socket, cancel a database query, etc...) + } +); + +assert('waited' === $promise->wait()); +``` + +A promise has the following methods: + +- `then(callable $onFulfilled, callable $onRejected) : PromiseInterface` + + Appends fulfillment and rejection handlers to the promise, and returns a new promise resolving to the return value of the called handler. + +- `otherwise(callable $onRejected) : PromiseInterface` + + Appends a rejection handler callback to the promise, and returns a new promise resolving to the return value of the callback if it is called, or to its original fulfillment value if the promise is instead fulfilled. + +- `wait($unwrap = true) : mixed` + + Synchronously waits on the promise to complete. + + `$unwrap` controls whether or not the value of the promise is returned for a + fulfilled promise or if an exception is thrown if the promise is rejected. + This is set to `true` by default. + +- `cancel()` + + Attempts to cancel the promise if possible. The promise being cancelled and + the parent most ancestor that has not yet been resolved will also be + cancelled. Any promises waiting on the cancelled promise to resolve will also + be cancelled. + +- `getState() : string` + + Returns the state of the promise. One of `pending`, `fulfilled`, or + `rejected`. + +- `resolve($value)` + + Fulfills the promise with the given `$value`. + +- `reject($reason)` + + Rejects the promise with the given `$reason`. + + +### FulfilledPromise + +A fulfilled promise can be created to represent a promise that has been +fulfilled. + +```php +use GuzzleHttp\Promise\FulfilledPromise; + +$promise = new FulfilledPromise('value'); + +// Fulfilled callbacks are immediately invoked. +$promise->then(function ($value) { + echo $value; +}); +``` + + +### RejectedPromise + +A rejected promise can be created to represent a promise that has been +rejected. + +```php +use GuzzleHttp\Promise\RejectedPromise; + +$promise = new RejectedPromise('Error'); + +// Rejected callbacks are immediately invoked. +$promise->then(null, function ($reason) { + echo $reason; +}); +``` + + +## Promise Interoperability + +This library works with foreign promises that have a `then` method. This means +you can use Guzzle promises with [React promises](https://github.com/reactphp/promise) +for example. When a foreign promise is returned inside of a then method +callback, promise resolution will occur recursively. + +```php +// Create a React promise +$deferred = new React\Promise\Deferred(); +$reactPromise = $deferred->promise(); + +// Create a Guzzle promise that is fulfilled with a React promise. +$guzzlePromise = new GuzzleHttp\Promise\Promise(); +$guzzlePromise->then(function ($value) use ($reactPromise) { + // Do something something with the value... + // Return the React promise + return $reactPromise; +}); +``` + +Please note that wait and cancel chaining is no longer possible when forwarding +a foreign promise. You will need to wrap a third-party promise with a Guzzle +promise in order to utilize wait and cancel functions with foreign promises. + + +### Event Loop Integration + +In order to keep the stack size constant, Guzzle promises are resolved +asynchronously using a task queue. When waiting on promises synchronously, the +task queue will be automatically run to ensure that the blocking promise and +any forwarded promises are resolved. When using promises asynchronously in an +event loop, you will need to run the task queue on each tick of the loop. If +you do not run the task queue, then promises will not be resolved. + +You can run the task queue using the `run()` method of the global task queue +instance. + +```php +// Get the global task queue +$queue = GuzzleHttp\Promise\Utils::queue(); +$queue->run(); +``` + +For example, you could use Guzzle promises with React using a periodic timer: + +```php +$loop = React\EventLoop\Factory::create(); +$loop->addPeriodicTimer(0, [$queue, 'run']); +``` + + +## Implementation Notes + +### Promise Resolution and Chaining is Handled Iteratively + +By shuffling pending handlers from one owner to another, promises are +resolved iteratively, allowing for "infinite" then chaining. + +```php +then(function ($v) { + // The stack size remains constant (a good thing) + echo xdebug_get_stack_depth() . ', '; + return $v + 1; + }); +} + +$parent->resolve(0); +var_dump($p->wait()); // int(1000) + +``` + +When a promise is fulfilled or rejected with a non-promise value, the promise +then takes ownership of the handlers of each child promise and delivers values +down the chain without using recursion. + +When a promise is resolved with another promise, the original promise transfers +all of its pending handlers to the new promise. When the new promise is +eventually resolved, all of the pending handlers are delivered the forwarded +value. + +### A Promise is the Deferred + +Some promise libraries implement promises using a deferred object to represent +a computation and a promise object to represent the delivery of the result of +the computation. This is a nice separation of computation and delivery because +consumers of the promise cannot modify the value that will be eventually +delivered. + +One side effect of being able to implement promise resolution and chaining +iteratively is that you need to be able for one promise to reach into the state +of another promise to shuffle around ownership of handlers. In order to achieve +this without making the handlers of a promise publicly mutable, a promise is +also the deferred value, allowing promises of the same parent class to reach +into and modify the private properties of promises of the same type. While this +does allow consumers of the value to modify the resolution or rejection of the +deferred, it is a small price to pay for keeping the stack size constant. + +```php +$promise = new Promise(); +$promise->then(function ($value) { echo $value; }); +// The promise is the deferred value, so you can deliver a value to it. +$promise->resolve('foo'); +// prints "foo" +``` + + +## Upgrading from Function API + +A static API was first introduced in 1.4.0, in order to mitigate problems with +functions conflicting between global and local copies of the package. The +function API was removed in 2.0.0. A migration table has been provided here for +your convenience: + +| Original Function | Replacement Method | +|----------------|----------------| +| `queue` | `Utils::queue` | +| `task` | `Utils::task` | +| `promise_for` | `Create::promiseFor` | +| `rejection_for` | `Create::rejectionFor` | +| `exception_for` | `Create::exceptionFor` | +| `iter_for` | `Create::iterFor` | +| `inspect` | `Utils::inspect` | +| `inspect_all` | `Utils::inspectAll` | +| `unwrap` | `Utils::unwrap` | +| `all` | `Utils::all` | +| `some` | `Utils::some` | +| `any` | `Utils::any` | +| `settle` | `Utils::settle` | +| `each` | `Each::of` | +| `each_limit` | `Each::ofLimit` | +| `each_limit_all` | `Each::ofLimitAll` | +| `!is_fulfilled` | `Is::pending` | +| `is_fulfilled` | `Is::fulfilled` | +| `is_rejected` | `Is::rejected` | +| `is_settled` | `Is::settled` | +| `coroutine` | `Coroutine::of` | + + +## Security + +If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see [Security Policy](https://github.com/guzzle/promises/security/policy) for more information. + + +## License + +Guzzle is made available under the MIT License (MIT). Please see [License File](LICENSE) for more information. + + +## For Enterprise + +Available as part of the Tidelift Subscription + +The maintainers of Guzzle and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-guzzlehttp-promises?utm_source=packagist-guzzlehttp-promises&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) diff --git a/vendor/guzzlehttp/promises/composer.json b/vendor/guzzlehttp/promises/composer.json new file mode 100644 index 00000000..6c5bdd66 --- /dev/null +++ b/vendor/guzzlehttp/promises/composer.json @@ -0,0 +1,58 @@ +{ + "name": "guzzlehttp/promises", + "description": "Guzzle promises library", + "keywords": ["promise"], + "license": "MIT", + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.36 || ^9.6.15" + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "GuzzleHttp\\Promise\\Tests\\": "tests/" + } + }, + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "config": { + "allow-plugins": { + "bamarni/composer-bin-plugin": true + }, + "preferred-install": "dist", + "sort-packages": true + } +} diff --git a/vendor/guzzlehttp/promises/src/AggregateException.php b/vendor/guzzlehttp/promises/src/AggregateException.php new file mode 100644 index 00000000..40ffdbcf --- /dev/null +++ b/vendor/guzzlehttp/promises/src/AggregateException.php @@ -0,0 +1,19 @@ +then(function ($v) { echo $v; }); + * + * @param callable $generatorFn Generator function to wrap into a promise. + * + * @return Promise + * + * @see https://github.com/petkaantonov/bluebird/blob/master/API.md#generators inspiration + */ +final class Coroutine implements PromiseInterface +{ + /** + * @var PromiseInterface|null + */ + private $currentPromise; + + /** + * @var Generator + */ + private $generator; + + /** + * @var Promise + */ + private $result; + + public function __construct(callable $generatorFn) + { + $this->generator = $generatorFn(); + $this->result = new Promise(function (): void { + while (isset($this->currentPromise)) { + $this->currentPromise->wait(); + } + }); + try { + $this->nextCoroutine($this->generator->current()); + } catch (Throwable $throwable) { + $this->result->reject($throwable); + } + } + + /** + * Create a new coroutine. + */ + public static function of(callable $generatorFn): self + { + return new self($generatorFn); + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ): PromiseInterface { + return $this->result->then($onFulfilled, $onRejected); + } + + public function otherwise(callable $onRejected): PromiseInterface + { + return $this->result->otherwise($onRejected); + } + + public function wait(bool $unwrap = true) + { + return $this->result->wait($unwrap); + } + + public function getState(): string + { + return $this->result->getState(); + } + + public function resolve($value): void + { + $this->result->resolve($value); + } + + public function reject($reason): void + { + $this->result->reject($reason); + } + + public function cancel(): void + { + $this->currentPromise->cancel(); + $this->result->cancel(); + } + + private function nextCoroutine($yielded): void + { + $this->currentPromise = Create::promiseFor($yielded) + ->then([$this, '_handleSuccess'], [$this, '_handleFailure']); + } + + /** + * @internal + */ + public function _handleSuccess($value): void + { + unset($this->currentPromise); + try { + $next = $this->generator->send($value); + if ($this->generator->valid()) { + $this->nextCoroutine($next); + } else { + $this->result->resolve($value); + } + } catch (Throwable $throwable) { + $this->result->reject($throwable); + } + } + + /** + * @internal + */ + public function _handleFailure($reason): void + { + unset($this->currentPromise); + try { + $nextYield = $this->generator->throw(Create::exceptionFor($reason)); + // The throw was caught, so keep iterating on the coroutine + $this->nextCoroutine($nextYield); + } catch (Throwable $throwable) { + $this->result->reject($throwable); + } + } +} diff --git a/vendor/guzzlehttp/promises/src/Create.php b/vendor/guzzlehttp/promises/src/Create.php new file mode 100644 index 00000000..9d3fc4a1 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/Create.php @@ -0,0 +1,79 @@ +then([$promise, 'resolve'], [$promise, 'reject']); + + return $promise; + } + + return new FulfilledPromise($value); + } + + /** + * Creates a rejected promise for a reason if the reason is not a promise. + * If the provided reason is a promise, then it is returned as-is. + * + * @param mixed $reason Promise or reason. + */ + public static function rejectionFor($reason): PromiseInterface + { + if ($reason instanceof PromiseInterface) { + return $reason; + } + + return new RejectedPromise($reason); + } + + /** + * Create an exception for a rejected promise value. + * + * @param mixed $reason + */ + public static function exceptionFor($reason): \Throwable + { + if ($reason instanceof \Throwable) { + return $reason; + } + + return new RejectionException($reason); + } + + /** + * Returns an iterator for the given value. + * + * @param mixed $value + */ + public static function iterFor($value): \Iterator + { + if ($value instanceof \Iterator) { + return $value; + } + + if (is_array($value)) { + return new \ArrayIterator($value); + } + + return new \ArrayIterator([$value]); + } +} diff --git a/vendor/guzzlehttp/promises/src/Each.php b/vendor/guzzlehttp/promises/src/Each.php new file mode 100644 index 00000000..c09d23c6 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/Each.php @@ -0,0 +1,81 @@ + $onFulfilled, + 'rejected' => $onRejected, + ]))->promise(); + } + + /** + * Like of, but only allows a certain number of outstanding promises at any + * given time. + * + * $concurrency may be an integer or a function that accepts the number of + * pending promises and returns a numeric concurrency limit value to allow + * for dynamic a concurrency size. + * + * @param mixed $iterable + * @param int|callable $concurrency + */ + public static function ofLimit( + $iterable, + $concurrency, + callable $onFulfilled = null, + callable $onRejected = null + ): PromiseInterface { + return (new EachPromise($iterable, [ + 'fulfilled' => $onFulfilled, + 'rejected' => $onRejected, + 'concurrency' => $concurrency, + ]))->promise(); + } + + /** + * Like limit, but ensures that no promise in the given $iterable argument + * is rejected. If any promise is rejected, then the aggregate promise is + * rejected with the encountered rejection. + * + * @param mixed $iterable + * @param int|callable $concurrency + */ + public static function ofLimitAll( + $iterable, + $concurrency, + callable $onFulfilled = null + ): PromiseInterface { + return self::ofLimit( + $iterable, + $concurrency, + $onFulfilled, + function ($reason, $idx, PromiseInterface $aggregate): void { + $aggregate->reject($reason); + } + ); + } +} diff --git a/vendor/guzzlehttp/promises/src/EachPromise.php b/vendor/guzzlehttp/promises/src/EachPromise.php new file mode 100644 index 00000000..e1238981 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/EachPromise.php @@ -0,0 +1,248 @@ +iterable = Create::iterFor($iterable); + + if (isset($config['concurrency'])) { + $this->concurrency = $config['concurrency']; + } + + if (isset($config['fulfilled'])) { + $this->onFulfilled = $config['fulfilled']; + } + + if (isset($config['rejected'])) { + $this->onRejected = $config['rejected']; + } + } + + /** @psalm-suppress InvalidNullableReturnType */ + public function promise(): PromiseInterface + { + if ($this->aggregate) { + return $this->aggregate; + } + + try { + $this->createPromise(); + /** @psalm-assert Promise $this->aggregate */ + $this->iterable->rewind(); + $this->refillPending(); + } catch (\Throwable $e) { + $this->aggregate->reject($e); + } + + /** + * @psalm-suppress NullableReturnStatement + */ + return $this->aggregate; + } + + private function createPromise(): void + { + $this->mutex = false; + $this->aggregate = new Promise(function (): void { + if ($this->checkIfFinished()) { + return; + } + reset($this->pending); + // Consume a potentially fluctuating list of promises while + // ensuring that indexes are maintained (precluding array_shift). + while ($promise = current($this->pending)) { + next($this->pending); + $promise->wait(); + if (Is::settled($this->aggregate)) { + return; + } + } + }); + + // Clear the references when the promise is resolved. + $clearFn = function (): void { + $this->iterable = $this->concurrency = $this->pending = null; + $this->onFulfilled = $this->onRejected = null; + $this->nextPendingIndex = 0; + }; + + $this->aggregate->then($clearFn, $clearFn); + } + + private function refillPending(): void + { + if (!$this->concurrency) { + // Add all pending promises. + while ($this->addPending() && $this->advanceIterator()) { + } + + return; + } + + // Add only up to N pending promises. + $concurrency = is_callable($this->concurrency) + ? ($this->concurrency)(count($this->pending)) + : $this->concurrency; + $concurrency = max($concurrency - count($this->pending), 0); + // Concurrency may be set to 0 to disallow new promises. + if (!$concurrency) { + return; + } + // Add the first pending promise. + $this->addPending(); + // Note this is special handling for concurrency=1 so that we do + // not advance the iterator after adding the first promise. This + // helps work around issues with generators that might not have the + // next value to yield until promise callbacks are called. + while (--$concurrency + && $this->advanceIterator() + && $this->addPending()) { + } + } + + private function addPending(): bool + { + if (!$this->iterable || !$this->iterable->valid()) { + return false; + } + + $promise = Create::promiseFor($this->iterable->current()); + $key = $this->iterable->key(); + + // Iterable keys may not be unique, so we use a counter to + // guarantee uniqueness + $idx = $this->nextPendingIndex++; + + $this->pending[$idx] = $promise->then( + function ($value) use ($idx, $key): void { + if ($this->onFulfilled) { + ($this->onFulfilled)( + $value, + $key, + $this->aggregate + ); + } + $this->step($idx); + }, + function ($reason) use ($idx, $key): void { + if ($this->onRejected) { + ($this->onRejected)( + $reason, + $key, + $this->aggregate + ); + } + $this->step($idx); + } + ); + + return true; + } + + private function advanceIterator(): bool + { + // Place a lock on the iterator so that we ensure to not recurse, + // preventing fatal generator errors. + if ($this->mutex) { + return false; + } + + $this->mutex = true; + + try { + $this->iterable->next(); + $this->mutex = false; + + return true; + } catch (\Throwable $e) { + $this->aggregate->reject($e); + $this->mutex = false; + + return false; + } + } + + private function step(int $idx): void + { + // If the promise was already resolved, then ignore this step. + if (Is::settled($this->aggregate)) { + return; + } + + unset($this->pending[$idx]); + + // Only refill pending promises if we are not locked, preventing the + // EachPromise to recursively invoke the provided iterator, which + // cause a fatal error: "Cannot resume an already running generator" + if ($this->advanceIterator() && !$this->checkIfFinished()) { + // Add more pending promises if possible. + $this->refillPending(); + } + } + + private function checkIfFinished(): bool + { + if (!$this->pending && !$this->iterable->valid()) { + // Resolve the promise if there's nothing left to do. + $this->aggregate->resolve(null); + + return true; + } + + return false; + } +} diff --git a/vendor/guzzlehttp/promises/src/FulfilledPromise.php b/vendor/guzzlehttp/promises/src/FulfilledPromise.php new file mode 100644 index 00000000..ab712965 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/FulfilledPromise.php @@ -0,0 +1,89 @@ +value = $value; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ): PromiseInterface { + // Return itself if there is no onFulfilled function. + if (!$onFulfilled) { + return $this; + } + + $queue = Utils::queue(); + $p = new Promise([$queue, 'run']); + $value = $this->value; + $queue->add(static function () use ($p, $value, $onFulfilled): void { + if (Is::pending($p)) { + try { + $p->resolve($onFulfilled($value)); + } catch (\Throwable $e) { + $p->reject($e); + } + } + }); + + return $p; + } + + public function otherwise(callable $onRejected): PromiseInterface + { + return $this->then(null, $onRejected); + } + + public function wait(bool $unwrap = true) + { + return $unwrap ? $this->value : null; + } + + public function getState(): string + { + return self::FULFILLED; + } + + public function resolve($value): void + { + if ($value !== $this->value) { + throw new \LogicException('Cannot resolve a fulfilled promise'); + } + } + + public function reject($reason): void + { + throw new \LogicException('Cannot reject a fulfilled promise'); + } + + public function cancel(): void + { + // pass + } +} diff --git a/vendor/guzzlehttp/promises/src/Is.php b/vendor/guzzlehttp/promises/src/Is.php new file mode 100644 index 00000000..f3f05038 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/Is.php @@ -0,0 +1,40 @@ +getState() === PromiseInterface::PENDING; + } + + /** + * Returns true if a promise is fulfilled or rejected. + */ + public static function settled(PromiseInterface $promise): bool + { + return $promise->getState() !== PromiseInterface::PENDING; + } + + /** + * Returns true if a promise is fulfilled. + */ + public static function fulfilled(PromiseInterface $promise): bool + { + return $promise->getState() === PromiseInterface::FULFILLED; + } + + /** + * Returns true if a promise is rejected. + */ + public static function rejected(PromiseInterface $promise): bool + { + return $promise->getState() === PromiseInterface::REJECTED; + } +} diff --git a/vendor/guzzlehttp/promises/src/Promise.php b/vendor/guzzlehttp/promises/src/Promise.php new file mode 100644 index 00000000..1b07bdc9 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/Promise.php @@ -0,0 +1,281 @@ +waitFn = $waitFn; + $this->cancelFn = $cancelFn; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ): PromiseInterface { + if ($this->state === self::PENDING) { + $p = new Promise(null, [$this, 'cancel']); + $this->handlers[] = [$p, $onFulfilled, $onRejected]; + $p->waitList = $this->waitList; + $p->waitList[] = $this; + + return $p; + } + + // Return a fulfilled promise and immediately invoke any callbacks. + if ($this->state === self::FULFILLED) { + $promise = Create::promiseFor($this->result); + + return $onFulfilled ? $promise->then($onFulfilled) : $promise; + } + + // It's either cancelled or rejected, so return a rejected promise + // and immediately invoke any callbacks. + $rejection = Create::rejectionFor($this->result); + + return $onRejected ? $rejection->then(null, $onRejected) : $rejection; + } + + public function otherwise(callable $onRejected): PromiseInterface + { + return $this->then(null, $onRejected); + } + + public function wait(bool $unwrap = true) + { + $this->waitIfPending(); + + if ($this->result instanceof PromiseInterface) { + return $this->result->wait($unwrap); + } + if ($unwrap) { + if ($this->state === self::FULFILLED) { + return $this->result; + } + // It's rejected so "unwrap" and throw an exception. + throw Create::exceptionFor($this->result); + } + } + + public function getState(): string + { + return $this->state; + } + + public function cancel(): void + { + if ($this->state !== self::PENDING) { + return; + } + + $this->waitFn = $this->waitList = null; + + if ($this->cancelFn) { + $fn = $this->cancelFn; + $this->cancelFn = null; + try { + $fn(); + } catch (\Throwable $e) { + $this->reject($e); + } + } + + // Reject the promise only if it wasn't rejected in a then callback. + /** @psalm-suppress RedundantCondition */ + if ($this->state === self::PENDING) { + $this->reject(new CancellationException('Promise has been cancelled')); + } + } + + public function resolve($value): void + { + $this->settle(self::FULFILLED, $value); + } + + public function reject($reason): void + { + $this->settle(self::REJECTED, $reason); + } + + private function settle(string $state, $value): void + { + if ($this->state !== self::PENDING) { + // Ignore calls with the same resolution. + if ($state === $this->state && $value === $this->result) { + return; + } + throw $this->state === $state + ? new \LogicException("The promise is already {$state}.") + : new \LogicException("Cannot change a {$this->state} promise to {$state}"); + } + + if ($value === $this) { + throw new \LogicException('Cannot fulfill or reject a promise with itself'); + } + + // Clear out the state of the promise but stash the handlers. + $this->state = $state; + $this->result = $value; + $handlers = $this->handlers; + $this->handlers = null; + $this->waitList = $this->waitFn = null; + $this->cancelFn = null; + + if (!$handlers) { + return; + } + + // If the value was not a settled promise or a thenable, then resolve + // it in the task queue using the correct ID. + if (!is_object($value) || !method_exists($value, 'then')) { + $id = $state === self::FULFILLED ? 1 : 2; + // It's a success, so resolve the handlers in the queue. + Utils::queue()->add(static function () use ($id, $value, $handlers): void { + foreach ($handlers as $handler) { + self::callHandler($id, $value, $handler); + } + }); + } elseif ($value instanceof Promise && Is::pending($value)) { + // We can just merge our handlers onto the next promise. + $value->handlers = array_merge($value->handlers, $handlers); + } else { + // Resolve the handlers when the forwarded promise is resolved. + $value->then( + static function ($value) use ($handlers): void { + foreach ($handlers as $handler) { + self::callHandler(1, $value, $handler); + } + }, + static function ($reason) use ($handlers): void { + foreach ($handlers as $handler) { + self::callHandler(2, $reason, $handler); + } + } + ); + } + } + + /** + * Call a stack of handlers using a specific callback index and value. + * + * @param int $index 1 (resolve) or 2 (reject). + * @param mixed $value Value to pass to the callback. + * @param array $handler Array of handler data (promise and callbacks). + */ + private static function callHandler(int $index, $value, array $handler): void + { + /** @var PromiseInterface $promise */ + $promise = $handler[0]; + + // The promise may have been cancelled or resolved before placing + // this thunk in the queue. + if (Is::settled($promise)) { + return; + } + + try { + if (isset($handler[$index])) { + /* + * If $f throws an exception, then $handler will be in the exception + * stack trace. Since $handler contains a reference to the callable + * itself we get a circular reference. We clear the $handler + * here to avoid that memory leak. + */ + $f = $handler[$index]; + unset($handler); + $promise->resolve($f($value)); + } elseif ($index === 1) { + // Forward resolution values as-is. + $promise->resolve($value); + } else { + // Forward rejections down the chain. + $promise->reject($value); + } + } catch (\Throwable $reason) { + $promise->reject($reason); + } + } + + private function waitIfPending(): void + { + if ($this->state !== self::PENDING) { + return; + } elseif ($this->waitFn) { + $this->invokeWaitFn(); + } elseif ($this->waitList) { + $this->invokeWaitList(); + } else { + // If there's no wait function, then reject the promise. + $this->reject('Cannot wait on a promise that has ' + .'no internal wait function. You must provide a wait ' + .'function when constructing the promise to be able to ' + .'wait on a promise.'); + } + + Utils::queue()->run(); + + /** @psalm-suppress RedundantCondition */ + if ($this->state === self::PENDING) { + $this->reject('Invoking the wait callback did not resolve the promise'); + } + } + + private function invokeWaitFn(): void + { + try { + $wfn = $this->waitFn; + $this->waitFn = null; + $wfn(true); + } catch (\Throwable $reason) { + if ($this->state === self::PENDING) { + // The promise has not been resolved yet, so reject the promise + // with the exception. + $this->reject($reason); + } else { + // The promise was already resolved, so there's a problem in + // the application. + throw $reason; + } + } + } + + private function invokeWaitList(): void + { + $waitList = $this->waitList; + $this->waitList = null; + + foreach ($waitList as $result) { + do { + $result->waitIfPending(); + $result = $result->result; + } while ($result instanceof Promise); + + if ($result instanceof PromiseInterface) { + $result->wait(false); + } + } + } +} diff --git a/vendor/guzzlehttp/promises/src/PromiseInterface.php b/vendor/guzzlehttp/promises/src/PromiseInterface.php new file mode 100644 index 00000000..2824802b --- /dev/null +++ b/vendor/guzzlehttp/promises/src/PromiseInterface.php @@ -0,0 +1,91 @@ +reason = $reason; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ): PromiseInterface { + // If there's no onRejected callback then just return self. + if (!$onRejected) { + return $this; + } + + $queue = Utils::queue(); + $reason = $this->reason; + $p = new Promise([$queue, 'run']); + $queue->add(static function () use ($p, $reason, $onRejected): void { + if (Is::pending($p)) { + try { + // Return a resolved promise if onRejected does not throw. + $p->resolve($onRejected($reason)); + } catch (\Throwable $e) { + // onRejected threw, so return a rejected promise. + $p->reject($e); + } + } + }); + + return $p; + } + + public function otherwise(callable $onRejected): PromiseInterface + { + return $this->then(null, $onRejected); + } + + public function wait(bool $unwrap = true) + { + if ($unwrap) { + throw Create::exceptionFor($this->reason); + } + + return null; + } + + public function getState(): string + { + return self::REJECTED; + } + + public function resolve($value): void + { + throw new \LogicException('Cannot resolve a rejected promise'); + } + + public function reject($reason): void + { + if ($reason !== $this->reason) { + throw new \LogicException('Cannot reject a rejected promise'); + } + } + + public function cancel(): void + { + // pass + } +} diff --git a/vendor/guzzlehttp/promises/src/RejectionException.php b/vendor/guzzlehttp/promises/src/RejectionException.php new file mode 100644 index 00000000..72a81ba2 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/RejectionException.php @@ -0,0 +1,49 @@ +reason = $reason; + + $message = 'The promise was rejected'; + + if ($description) { + $message .= ' with reason: '.$description; + } elseif (is_string($reason) + || (is_object($reason) && method_exists($reason, '__toString')) + ) { + $message .= ' with reason: '.$this->reason; + } elseif ($reason instanceof \JsonSerializable) { + $message .= ' with reason: '.json_encode($this->reason, JSON_PRETTY_PRINT); + } + + parent::__construct($message); + } + + /** + * Returns the rejection reason. + * + * @return mixed + */ + public function getReason() + { + return $this->reason; + } +} diff --git a/vendor/guzzlehttp/promises/src/TaskQueue.php b/vendor/guzzlehttp/promises/src/TaskQueue.php new file mode 100644 index 00000000..503e0b2d --- /dev/null +++ b/vendor/guzzlehttp/promises/src/TaskQueue.php @@ -0,0 +1,71 @@ +run(); + * + * @final + */ +class TaskQueue implements TaskQueueInterface +{ + private $enableShutdown = true; + private $queue = []; + + public function __construct(bool $withShutdown = true) + { + if ($withShutdown) { + register_shutdown_function(function (): void { + if ($this->enableShutdown) { + // Only run the tasks if an E_ERROR didn't occur. + $err = error_get_last(); + if (!$err || ($err['type'] ^ E_ERROR)) { + $this->run(); + } + } + }); + } + } + + public function isEmpty(): bool + { + return !$this->queue; + } + + public function add(callable $task): void + { + $this->queue[] = $task; + } + + public function run(): void + { + while ($task = array_shift($this->queue)) { + /** @var callable $task */ + $task(); + } + } + + /** + * The task queue will be run and exhausted by default when the process + * exits IFF the exit is not the result of a PHP E_ERROR error. + * + * You can disable running the automatic shutdown of the queue by calling + * this function. If you disable the task queue shutdown process, then you + * MUST either run the task queue (as a result of running your event loop + * or manually using the run() method) or wait on each outstanding promise. + * + * Note: This shutdown will occur before any destructors are triggered. + */ + public function disableShutdown(): void + { + $this->enableShutdown = false; + } +} diff --git a/vendor/guzzlehttp/promises/src/TaskQueueInterface.php b/vendor/guzzlehttp/promises/src/TaskQueueInterface.php new file mode 100644 index 00000000..34c561a4 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/TaskQueueInterface.php @@ -0,0 +1,24 @@ + + * while ($eventLoop->isRunning()) { + * GuzzleHttp\Promise\Utils::queue()->run(); + * } + * + * + * @param TaskQueueInterface|null $assign Optionally specify a new queue instance. + */ + public static function queue(TaskQueueInterface $assign = null): TaskQueueInterface + { + static $queue; + + if ($assign) { + $queue = $assign; + } elseif (!$queue) { + $queue = new TaskQueue(); + } + + return $queue; + } + + /** + * Adds a function to run in the task queue when it is next `run()` and + * returns a promise that is fulfilled or rejected with the result. + * + * @param callable $task Task function to run. + */ + public static function task(callable $task): PromiseInterface + { + $queue = self::queue(); + $promise = new Promise([$queue, 'run']); + $queue->add(function () use ($task, $promise): void { + try { + if (Is::pending($promise)) { + $promise->resolve($task()); + } + } catch (\Throwable $e) { + $promise->reject($e); + } + }); + + return $promise; + } + + /** + * Synchronously waits on a promise to resolve and returns an inspection + * state array. + * + * Returns a state associative array containing a "state" key mapping to a + * valid promise state. If the state of the promise is "fulfilled", the + * array will contain a "value" key mapping to the fulfilled value of the + * promise. If the promise is rejected, the array will contain a "reason" + * key mapping to the rejection reason of the promise. + * + * @param PromiseInterface $promise Promise or value. + */ + public static function inspect(PromiseInterface $promise): array + { + try { + return [ + 'state' => PromiseInterface::FULFILLED, + 'value' => $promise->wait(), + ]; + } catch (RejectionException $e) { + return ['state' => PromiseInterface::REJECTED, 'reason' => $e->getReason()]; + } catch (\Throwable $e) { + return ['state' => PromiseInterface::REJECTED, 'reason' => $e]; + } + } + + /** + * Waits on all of the provided promises, but does not unwrap rejected + * promises as thrown exception. + * + * Returns an array of inspection state arrays. + * + * @see inspect for the inspection state array format. + * + * @param PromiseInterface[] $promises Traversable of promises to wait upon. + */ + public static function inspectAll($promises): array + { + $results = []; + foreach ($promises as $key => $promise) { + $results[$key] = self::inspect($promise); + } + + return $results; + } + + /** + * Waits on all of the provided promises and returns the fulfilled values. + * + * Returns an array that contains the value of each promise (in the same + * order the promises were provided). An exception is thrown if any of the + * promises are rejected. + * + * @param iterable $promises Iterable of PromiseInterface objects to wait on. + * + * @throws \Throwable on error + */ + public static function unwrap($promises): array + { + $results = []; + foreach ($promises as $key => $promise) { + $results[$key] = $promise->wait(); + } + + return $results; + } + + /** + * Given an array of promises, return a promise that is fulfilled when all + * the items in the array are fulfilled. + * + * The promise's fulfillment value is an array with fulfillment values at + * respective positions to the original array. If any promise in the array + * rejects, the returned promise is rejected with the rejection reason. + * + * @param mixed $promises Promises or values. + * @param bool $recursive If true, resolves new promises that might have been added to the stack during its own resolution. + */ + public static function all($promises, bool $recursive = false): PromiseInterface + { + $results = []; + $promise = Each::of( + $promises, + function ($value, $idx) use (&$results): void { + $results[$idx] = $value; + }, + function ($reason, $idx, Promise $aggregate): void { + $aggregate->reject($reason); + } + )->then(function () use (&$results) { + ksort($results); + + return $results; + }); + + if (true === $recursive) { + $promise = $promise->then(function ($results) use ($recursive, &$promises) { + foreach ($promises as $promise) { + if (Is::pending($promise)) { + return self::all($promises, $recursive); + } + } + + return $results; + }); + } + + return $promise; + } + + /** + * Initiate a competitive race between multiple promises or values (values + * will become immediately fulfilled promises). + * + * When count amount of promises have been fulfilled, the returned promise + * is fulfilled with an array that contains the fulfillment values of the + * winners in order of resolution. + * + * This promise is rejected with a {@see AggregateException} if the number + * of fulfilled promises is less than the desired $count. + * + * @param int $count Total number of promises. + * @param mixed $promises Promises or values. + */ + public static function some(int $count, $promises): PromiseInterface + { + $results = []; + $rejections = []; + + return Each::of( + $promises, + function ($value, $idx, PromiseInterface $p) use (&$results, $count): void { + if (Is::settled($p)) { + return; + } + $results[$idx] = $value; + if (count($results) >= $count) { + $p->resolve(null); + } + }, + function ($reason) use (&$rejections): void { + $rejections[] = $reason; + } + )->then( + function () use (&$results, &$rejections, $count) { + if (count($results) !== $count) { + throw new AggregateException( + 'Not enough promises to fulfill count', + $rejections + ); + } + ksort($results); + + return array_values($results); + } + ); + } + + /** + * Like some(), with 1 as count. However, if the promise fulfills, the + * fulfillment value is not an array of 1 but the value directly. + * + * @param mixed $promises Promises or values. + */ + public static function any($promises): PromiseInterface + { + return self::some(1, $promises)->then(function ($values) { + return $values[0]; + }); + } + + /** + * Returns a promise that is fulfilled when all of the provided promises have + * been fulfilled or rejected. + * + * The returned promise is fulfilled with an array of inspection state arrays. + * + * @see inspect for the inspection state array format. + * + * @param mixed $promises Promises or values. + */ + public static function settle($promises): PromiseInterface + { + $results = []; + + return Each::of( + $promises, + function ($value, $idx) use (&$results): void { + $results[$idx] = ['state' => PromiseInterface::FULFILLED, 'value' => $value]; + }, + function ($reason, $idx) use (&$results): void { + $results[$idx] = ['state' => PromiseInterface::REJECTED, 'reason' => $reason]; + } + )->then(function () use (&$results) { + ksort($results); + + return $results; + }); + } +} diff --git a/vendor/guzzlehttp/psr7/CHANGELOG.md b/vendor/guzzlehttp/psr7/CHANGELOG.md new file mode 100644 index 00000000..fe3eda70 --- /dev/null +++ b/vendor/guzzlehttp/psr7/CHANGELOG.md @@ -0,0 +1,448 @@ +# Change Log + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## 2.6.2 - 2023-12-03 + +### Fixed + +- Fixed another issue with the fact that PHP transforms numeric strings in array keys to ints + +### Changed + +- Updated links in docs to their canonical versions +- Replaced `call_user_func*` with native calls + +## 2.6.1 - 2023-08-27 + +### Fixed + +- Properly handle the fact that PHP transforms numeric strings in array keys to ints + +## 2.6.0 - 2023-08-03 + +### Changed + +- Updated the mime type map to add some new entries, fix a couple of invalid entries, and remove an invalid entry +- Fallback to `application/octet-stream` if we are unable to guess the content type for a multipart file upload + +## 2.5.1 - 2023-08-03 + +### Fixed + +- Corrected mime type for `.acc` files to `audio/aac` + +### Changed + +- PHP 8.3 support + +## 2.5.0 - 2023-04-17 + +### Changed + +- Adjusted `psr/http-message` version constraint to `^1.1 || ^2.0` + +## 2.4.5 - 2023-04-17 + +### Fixed + +- Prevent possible warnings on unset variables in `ServerRequest::normalizeNestedFileSpec` +- Fixed `Message::bodySummary` when `preg_match` fails +- Fixed header validation issue + +## 2.4.4 - 2023-03-09 + +### Changed + +- Removed the need for `AllowDynamicProperties` in `LazyOpenStream` + +## 2.4.3 - 2022-10-26 + +### Changed + +- Replaced `sha1(uniqid())` by `bin2hex(random_bytes(20))` + +## 2.4.2 - 2022-10-25 + +### Fixed + +- Fixed erroneous behaviour when combining host and relative path + +## 2.4.1 - 2022-08-28 + +### Fixed + +- Rewind body before reading in `Message::bodySummary` + +## 2.4.0 - 2022-06-20 + +### Added + +- Added provisional PHP 8.2 support +- Added `UriComparator::isCrossOrigin` method + +## 2.3.0 - 2022-06-09 + +### Fixed + +- Added `Header::splitList` method +- Added `Utils::tryGetContents` method +- Improved `Stream::getContents` method +- Updated mimetype mappings + +## 2.2.2 - 2022-06-08 + +### Fixed + +- Fix `Message::parseRequestUri` for numeric headers +- Re-wrap exceptions thrown in `fread` into runtime exceptions +- Throw an exception when multipart options is misformatted + +## 2.2.1 - 2022-03-20 + +### Fixed + +- Correct header value validation + +## 2.2.0 - 2022-03-20 + +### Added + +- A more compressive list of mime types +- Add JsonSerializable to Uri +- Missing return types + +### Fixed + +- Bug MultipartStream no `uri` metadata +- Bug MultipartStream with filename for `data://` streams +- Fixed new line handling in MultipartStream +- Reduced RAM usage when copying streams +- Updated parsing in `Header::normalize()` + +## 2.1.1 - 2022-03-20 + +### Fixed + +- Validate header values properly + +## 2.1.0 - 2021-10-06 + +### Changed + +- Attempting to create a `Uri` object from a malformed URI will no longer throw a generic + `InvalidArgumentException`, but rather a `MalformedUriException`, which inherits from the former + for backwards compatibility. Callers relying on the exception being thrown to detect invalid + URIs should catch the new exception. + +### Fixed + +- Return `null` in caching stream size if remote size is `null` + +## 2.0.0 - 2021-06-30 + +Identical to the RC release. + +## 2.0.0@RC-1 - 2021-04-29 + +### Fixed + +- Handle possibly unset `url` in `stream_get_meta_data` + +## 2.0.0@beta-1 - 2021-03-21 + +### Added + +- PSR-17 factories +- Made classes final +- PHP7 type hints + +### Changed + +- When building a query string, booleans are represented as 1 and 0. + +### Removed + +- PHP < 7.2 support +- All functions in the `GuzzleHttp\Psr7` namespace + +## 1.8.1 - 2021-03-21 + +### Fixed + +- Issue parsing IPv6 URLs +- Issue modifying ServerRequest lost all its attributes + +## 1.8.0 - 2021-03-21 + +### Added + +- Locale independent URL parsing +- Most classes got a `@final` annotation to prepare for 2.0 + +### Fixed + +- Issue when creating stream from `php://input` and curl-ext is not installed +- Broken `Utils::tryFopen()` on PHP 8 + +## 1.7.0 - 2020-09-30 + +### Added + +- Replaced functions by static methods + +### Fixed + +- Converting a non-seekable stream to a string +- Handle multiple Set-Cookie correctly +- Ignore array keys in header values when merging +- Allow multibyte characters to be parsed in `Message:bodySummary()` + +### Changed + +- Restored partial HHVM 3 support + + +## [1.6.1] - 2019-07-02 + +### Fixed + +- Accept null and bool header values again + + +## [1.6.0] - 2019-06-30 + +### Added + +- Allowed version `^3.0` of `ralouphie/getallheaders` dependency (#244) +- Added MIME type for WEBP image format (#246) +- Added more validation of values according to PSR-7 and RFC standards, e.g. status code range (#250, #272) + +### Changed + +- Tests don't pass with HHVM 4.0, so HHVM support got dropped. Other libraries like composer have done the same. (#262) +- Accept port number 0 to be valid (#270) + +### Fixed + +- Fixed subsequent reads from `php://input` in ServerRequest (#247) +- Fixed readable/writable detection for certain stream modes (#248) +- Fixed encoding of special characters in the `userInfo` component of an URI (#253) + + +## [1.5.2] - 2018-12-04 + +### Fixed + +- Check body size when getting the message summary + + +## [1.5.1] - 2018-12-04 + +### Fixed + +- Get the summary of a body only if it is readable + + +## [1.5.0] - 2018-12-03 + +### Added + +- Response first-line to response string exception (fixes #145) +- A test for #129 behavior +- `get_message_body_summary` function in order to get the message summary +- `3gp` and `mkv` mime types + +### Changed + +- Clarify exception message when stream is detached + +### Deprecated + +- Deprecated parsing folded header lines as per RFC 7230 + +### Fixed + +- Fix `AppendStream::detach` to not close streams +- `InflateStream` preserves `isSeekable` attribute of the underlying stream +- `ServerRequest::getUriFromGlobals` to support URLs in query parameters + + +Several other fixes and improvements. + + +## [1.4.2] - 2017-03-20 + +### Fixed + +- Reverted BC break to `Uri::resolve` and `Uri::removeDotSegments` by removing + calls to `trigger_error` when deprecated methods are invoked. + + +## [1.4.1] - 2017-02-27 + +### Added + +- Rriggering of silenced deprecation warnings. + +### Fixed + +- Reverted BC break by reintroducing behavior to automagically fix a URI with a + relative path and an authority by adding a leading slash to the path. It's only + deprecated now. + + +## [1.4.0] - 2017-02-21 + +### Added + +- Added common URI utility methods based on RFC 3986 (see documentation in the readme): + - `Uri::isDefaultPort` + - `Uri::isAbsolute` + - `Uri::isNetworkPathReference` + - `Uri::isAbsolutePathReference` + - `Uri::isRelativePathReference` + - `Uri::isSameDocumentReference` + - `Uri::composeComponents` + - `UriNormalizer::normalize` + - `UriNormalizer::isEquivalent` + - `UriResolver::relativize` + +### Changed + +- Ensure `ServerRequest::getUriFromGlobals` returns a URI in absolute form. +- Allow `parse_response` to parse a response without delimiting space and reason. +- Ensure each URI modification results in a valid URI according to PSR-7 discussions. + Invalid modifications will throw an exception instead of returning a wrong URI or + doing some magic. + - `(new Uri)->withPath('foo')->withHost('example.com')` will throw an exception + because the path of a URI with an authority must start with a slash "/" or be empty + - `(new Uri())->withScheme('http')` will return `'http://localhost'` + +### Deprecated + +- `Uri::resolve` in favor of `UriResolver::resolve` +- `Uri::removeDotSegments` in favor of `UriResolver::removeDotSegments` + +### Fixed + +- `Stream::read` when length parameter <= 0. +- `copy_to_stream` reads bytes in chunks instead of `maxLen` into memory. +- `ServerRequest::getUriFromGlobals` when `Host` header contains port. +- Compatibility of URIs with `file` scheme and empty host. + + +## [1.3.1] - 2016-06-25 + +### Fixed + +- `Uri::__toString` for network path references, e.g. `//example.org`. +- Missing lowercase normalization for host. +- Handling of URI components in case they are `'0'` in a lot of places, + e.g. as a user info password. +- `Uri::withAddedHeader` to correctly merge headers with different case. +- Trimming of header values in `Uri::withAddedHeader`. Header values may + be surrounded by whitespace which should be ignored according to RFC 7230 + Section 3.2.4. This does not apply to header names. +- `Uri::withAddedHeader` with an array of header values. +- `Uri::resolve` when base path has no slash and handling of fragment. +- Handling of encoding in `Uri::with(out)QueryValue` so one can pass the + key/value both in encoded as well as decoded form to those methods. This is + consistent with withPath, withQuery etc. +- `ServerRequest::withoutAttribute` when attribute value is null. + + +## [1.3.0] - 2016-04-13 + +### Added + +- Remaining interfaces needed for full PSR7 compatibility + (ServerRequestInterface, UploadedFileInterface, etc.). +- Support for stream_for from scalars. + +### Changed + +- Can now extend Uri. + +### Fixed +- A bug in validating request methods by making it more permissive. + + +## [1.2.3] - 2016-02-18 + +### Fixed + +- Support in `GuzzleHttp\Psr7\CachingStream` for seeking forward on remote + streams, which can sometimes return fewer bytes than requested with `fread`. +- Handling of gzipped responses with FNAME headers. + + +## [1.2.2] - 2016-01-22 + +### Added + +- Support for URIs without any authority. +- Support for HTTP 451 'Unavailable For Legal Reasons.' +- Support for using '0' as a filename. +- Support for including non-standard ports in Host headers. + + +## [1.2.1] - 2015-11-02 + +### Changes + +- Now supporting negative offsets when seeking to SEEK_END. + + +## [1.2.0] - 2015-08-15 + +### Changed + +- Body as `"0"` is now properly added to a response. +- Now allowing forward seeking in CachingStream. +- Now properly parsing HTTP requests that contain proxy targets in + `parse_request`. +- functions.php is now conditionally required. +- user-info is no longer dropped when resolving URIs. + + +## [1.1.0] - 2015-06-24 + +### Changed + +- URIs can now be relative. +- `multipart/form-data` headers are now overridden case-insensitively. +- URI paths no longer encode the following characters because they are allowed + in URIs: "(", ")", "*", "!", "'" +- A port is no longer added to a URI when the scheme is missing and no port is + present. + + +## 1.0.0 - 2015-05-19 + +Initial release. + +Currently unsupported: + +- `Psr\Http\Message\ServerRequestInterface` +- `Psr\Http\Message\UploadedFileInterface` + + + +[1.6.0]: https://github.com/guzzle/psr7/compare/1.5.2...1.6.0 +[1.5.2]: https://github.com/guzzle/psr7/compare/1.5.1...1.5.2 +[1.5.1]: https://github.com/guzzle/psr7/compare/1.5.0...1.5.1 +[1.5.0]: https://github.com/guzzle/psr7/compare/1.4.2...1.5.0 +[1.4.2]: https://github.com/guzzle/psr7/compare/1.4.1...1.4.2 +[1.4.1]: https://github.com/guzzle/psr7/compare/1.4.0...1.4.1 +[1.4.0]: https://github.com/guzzle/psr7/compare/1.3.1...1.4.0 +[1.3.1]: https://github.com/guzzle/psr7/compare/1.3.0...1.3.1 +[1.3.0]: https://github.com/guzzle/psr7/compare/1.2.3...1.3.0 +[1.2.3]: https://github.com/guzzle/psr7/compare/1.2.2...1.2.3 +[1.2.2]: https://github.com/guzzle/psr7/compare/1.2.1...1.2.2 +[1.2.1]: https://github.com/guzzle/psr7/compare/1.2.0...1.2.1 +[1.2.0]: https://github.com/guzzle/psr7/compare/1.1.0...1.2.0 +[1.1.0]: https://github.com/guzzle/psr7/compare/1.0.0...1.1.0 diff --git a/vendor/guzzlehttp/psr7/LICENSE b/vendor/guzzlehttp/psr7/LICENSE new file mode 100644 index 00000000..51c7ec81 --- /dev/null +++ b/vendor/guzzlehttp/psr7/LICENSE @@ -0,0 +1,26 @@ +The MIT License (MIT) + +Copyright (c) 2015 Michael Dowling +Copyright (c) 2015 Márk Sági-Kazár +Copyright (c) 2015 Graham Campbell +Copyright (c) 2016 Tobias Schultze +Copyright (c) 2016 George Mponos +Copyright (c) 2018 Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/guzzlehttp/psr7/README.md b/vendor/guzzlehttp/psr7/README.md new file mode 100644 index 00000000..850fa9d7 --- /dev/null +++ b/vendor/guzzlehttp/psr7/README.md @@ -0,0 +1,880 @@ +# PSR-7 Message Implementation + +This repository contains a full [PSR-7](https://www.php-fig.org/psr/psr-7/) +message implementation, several stream decorators, and some helpful +functionality like query string parsing. + +![CI](https://github.com/guzzle/psr7/workflows/CI/badge.svg) +![Static analysis](https://github.com/guzzle/psr7/workflows/Static%20analysis/badge.svg) + + +## Features + +This package comes with a number of stream implementations and stream +decorators. + + +## Installation + +```shell +composer require guzzlehttp/psr7 +``` + +## Version Guidance + +| Version | Status | PHP Version | +|---------|---------------------|--------------| +| 1.x | Security fixes only | >=5.4,<8.1 | +| 2.x | Latest | >=7.2.5,<8.4 | + + +## AppendStream + +`GuzzleHttp\Psr7\AppendStream` + +Reads from multiple streams, one after the other. + +```php +use GuzzleHttp\Psr7; + +$a = Psr7\Utils::streamFor('abc, '); +$b = Psr7\Utils::streamFor('123.'); +$composed = new Psr7\AppendStream([$a, $b]); + +$composed->addStream(Psr7\Utils::streamFor(' Above all listen to me')); + +echo $composed; // abc, 123. Above all listen to me. +``` + + +## BufferStream + +`GuzzleHttp\Psr7\BufferStream` + +Provides a buffer stream that can be written to fill a buffer, and read +from to remove bytes from the buffer. + +This stream returns a "hwm" metadata value that tells upstream consumers +what the configured high water mark of the stream is, or the maximum +preferred size of the buffer. + +```php +use GuzzleHttp\Psr7; + +// When more than 1024 bytes are in the buffer, it will begin returning +// false to writes. This is an indication that writers should slow down. +$buffer = new Psr7\BufferStream(1024); +``` + + +## CachingStream + +The CachingStream is used to allow seeking over previously read bytes on +non-seekable streams. This can be useful when transferring a non-seekable +entity body fails due to needing to rewind the stream (for example, resulting +from a redirect). Data that is read from the remote stream will be buffered in +a PHP temp stream so that previously read bytes are cached first in memory, +then on disk. + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\Utils::streamFor(fopen('http://www.google.com', 'r')); +$stream = new Psr7\CachingStream($original); + +$stream->read(1024); +echo $stream->tell(); +// 1024 + +$stream->seek(0); +echo $stream->tell(); +// 0 +``` + + +## DroppingStream + +`GuzzleHttp\Psr7\DroppingStream` + +Stream decorator that begins dropping data once the size of the underlying +stream becomes too full. + +```php +use GuzzleHttp\Psr7; + +// Create an empty stream +$stream = Psr7\Utils::streamFor(); + +// Start dropping data when the stream has more than 10 bytes +$dropping = new Psr7\DroppingStream($stream, 10); + +$dropping->write('01234567890123456789'); +echo $stream; // 0123456789 +``` + + +## FnStream + +`GuzzleHttp\Psr7\FnStream` + +Compose stream implementations based on a hash of functions. + +Allows for easy testing and extension of a provided stream without needing +to create a concrete class for a simple extension point. + +```php + +use GuzzleHttp\Psr7; + +$stream = Psr7\Utils::streamFor('hi'); +$fnStream = Psr7\FnStream::decorate($stream, [ + 'rewind' => function () use ($stream) { + echo 'About to rewind - '; + $stream->rewind(); + echo 'rewound!'; + } +]); + +$fnStream->rewind(); +// Outputs: About to rewind - rewound! +``` + + +## InflateStream + +`GuzzleHttp\Psr7\InflateStream` + +Uses PHP's zlib.inflate filter to inflate zlib (HTTP deflate, RFC1950) or gzipped (RFC1952) content. + +This stream decorator converts the provided stream to a PHP stream resource, +then appends the zlib.inflate filter. The stream is then converted back +to a Guzzle stream resource to be used as a Guzzle stream. + + +## LazyOpenStream + +`GuzzleHttp\Psr7\LazyOpenStream` + +Lazily reads or writes to a file that is opened only after an IO operation +take place on the stream. + +```php +use GuzzleHttp\Psr7; + +$stream = new Psr7\LazyOpenStream('/path/to/file', 'r'); +// The file has not yet been opened... + +echo $stream->read(10); +// The file is opened and read from only when needed. +``` + + +## LimitStream + +`GuzzleHttp\Psr7\LimitStream` + +LimitStream can be used to read a subset or slice of an existing stream object. +This can be useful for breaking a large file into smaller pieces to be sent in +chunks (e.g. Amazon S3's multipart upload API). + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\Utils::streamFor(fopen('/tmp/test.txt', 'r+')); +echo $original->getSize(); +// >>> 1048576 + +// Limit the size of the body to 1024 bytes and start reading from byte 2048 +$stream = new Psr7\LimitStream($original, 1024, 2048); +echo $stream->getSize(); +// >>> 1024 +echo $stream->tell(); +// >>> 0 +``` + + +## MultipartStream + +`GuzzleHttp\Psr7\MultipartStream` + +Stream that when read returns bytes for a streaming multipart or +multipart/form-data stream. + + +## NoSeekStream + +`GuzzleHttp\Psr7\NoSeekStream` + +NoSeekStream wraps a stream and does not allow seeking. + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\Utils::streamFor('foo'); +$noSeek = new Psr7\NoSeekStream($original); + +echo $noSeek->read(3); +// foo +var_export($noSeek->isSeekable()); +// false +$noSeek->seek(0); +var_export($noSeek->read(3)); +// NULL +``` + + +## PumpStream + +`GuzzleHttp\Psr7\PumpStream` + +Provides a read only stream that pumps data from a PHP callable. + +When invoking the provided callable, the PumpStream will pass the amount of +data requested to read to the callable. The callable can choose to ignore +this value and return fewer or more bytes than requested. Any extra data +returned by the provided callable is buffered internally until drained using +the read() function of the PumpStream. The provided callable MUST return +false when there is no more data to read. + + +## Implementing stream decorators + +Creating a stream decorator is very easy thanks to the +`GuzzleHttp\Psr7\StreamDecoratorTrait`. This trait provides methods that +implement `Psr\Http\Message\StreamInterface` by proxying to an underlying +stream. Just `use` the `StreamDecoratorTrait` and implement your custom +methods. + +For example, let's say we wanted to call a specific function each time the last +byte is read from a stream. This could be implemented by overriding the +`read()` method. + +```php +use Psr\Http\Message\StreamInterface; +use GuzzleHttp\Psr7\StreamDecoratorTrait; + +class EofCallbackStream implements StreamInterface +{ + use StreamDecoratorTrait; + + private $callback; + + private $stream; + + public function __construct(StreamInterface $stream, callable $cb) + { + $this->stream = $stream; + $this->callback = $cb; + } + + public function read($length) + { + $result = $this->stream->read($length); + + // Invoke the callback when EOF is hit. + if ($this->eof()) { + ($this->callback)(); + } + + return $result; + } +} +``` + +This decorator could be added to any existing stream and used like so: + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\Utils::streamFor('foo'); + +$eofStream = new EofCallbackStream($original, function () { + echo 'EOF!'; +}); + +$eofStream->read(2); +$eofStream->read(1); +// echoes "EOF!" +$eofStream->seek(0); +$eofStream->read(3); +// echoes "EOF!" +``` + + +## PHP StreamWrapper + +You can use the `GuzzleHttp\Psr7\StreamWrapper` class if you need to use a +PSR-7 stream as a PHP stream resource. + +Use the `GuzzleHttp\Psr7\StreamWrapper::getResource()` method to create a PHP +stream from a PSR-7 stream. + +```php +use GuzzleHttp\Psr7\StreamWrapper; + +$stream = GuzzleHttp\Psr7\Utils::streamFor('hello!'); +$resource = StreamWrapper::getResource($stream); +echo fread($resource, 6); // outputs hello! +``` + + +# Static API + +There are various static methods available under the `GuzzleHttp\Psr7` namespace. + + +## `GuzzleHttp\Psr7\Message::toString` + +`public static function toString(MessageInterface $message): string` + +Returns the string representation of an HTTP message. + +```php +$request = new GuzzleHttp\Psr7\Request('GET', 'http://example.com'); +echo GuzzleHttp\Psr7\Message::toString($request); +``` + + +## `GuzzleHttp\Psr7\Message::bodySummary` + +`public static function bodySummary(MessageInterface $message, int $truncateAt = 120): string|null` + +Get a short summary of the message body. + +Will return `null` if the response is not printable. + + +## `GuzzleHttp\Psr7\Message::rewindBody` + +`public static function rewindBody(MessageInterface $message): void` + +Attempts to rewind a message body and throws an exception on failure. + +The body of the message will only be rewound if a call to `tell()` +returns a value other than `0`. + + +## `GuzzleHttp\Psr7\Message::parseMessage` + +`public static function parseMessage(string $message): array` + +Parses an HTTP message into an associative array. + +The array contains the "start-line" key containing the start line of +the message, "headers" key containing an associative array of header +array values, and a "body" key containing the body of the message. + + +## `GuzzleHttp\Psr7\Message::parseRequestUri` + +`public static function parseRequestUri(string $path, array $headers): string` + +Constructs a URI for an HTTP request message. + + +## `GuzzleHttp\Psr7\Message::parseRequest` + +`public static function parseRequest(string $message): Request` + +Parses a request message string into a request object. + + +## `GuzzleHttp\Psr7\Message::parseResponse` + +`public static function parseResponse(string $message): Response` + +Parses a response message string into a response object. + + +## `GuzzleHttp\Psr7\Header::parse` + +`public static function parse(string|array $header): array` + +Parse an array of header values containing ";" separated data into an +array of associative arrays representing the header key value pair data +of the header. When a parameter does not contain a value, but just +contains a key, this function will inject a key with a '' string value. + + +## `GuzzleHttp\Psr7\Header::splitList` + +`public static function splitList(string|string[] $header): string[]` + +Splits a HTTP header defined to contain a comma-separated list into +each individual value: + +``` +$knownEtags = Header::splitList($request->getHeader('if-none-match')); +``` + +Example headers include `accept`, `cache-control` and `if-none-match`. + + +## `GuzzleHttp\Psr7\Header::normalize` (deprecated) + +`public static function normalize(string|array $header): array` + +`Header::normalize()` is deprecated in favor of [`Header::splitList()`](README.md#guzzlehttppsr7headersplitlist) +which performs the same operation with a cleaned up API and improved +documentation. + +Converts an array of header values that may contain comma separated +headers into an array of headers with no comma separated values. + + +## `GuzzleHttp\Psr7\Query::parse` + +`public static function parse(string $str, int|bool $urlEncoding = true): array` + +Parse a query string into an associative array. + +If multiple values are found for the same key, the value of that key +value pair will become an array. This function does not parse nested +PHP style arrays into an associative array (e.g., `foo[a]=1&foo[b]=2` +will be parsed into `['foo[a]' => '1', 'foo[b]' => '2'])`. + + +## `GuzzleHttp\Psr7\Query::build` + +`public static function build(array $params, int|false $encoding = PHP_QUERY_RFC3986): string` + +Build a query string from an array of key value pairs. + +This function can use the return value of `parse()` to build a query +string. This function does not modify the provided keys when an array is +encountered (like `http_build_query()` would). + + +## `GuzzleHttp\Psr7\Utils::caselessRemove` + +`public static function caselessRemove(iterable $keys, $keys, array $data): array` + +Remove the items given by the keys, case insensitively from the data. + + +## `GuzzleHttp\Psr7\Utils::copyToStream` + +`public static function copyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen = -1): void` + +Copy the contents of a stream into another stream until the given number +of bytes have been read. + + +## `GuzzleHttp\Psr7\Utils::copyToString` + +`public static function copyToString(StreamInterface $stream, int $maxLen = -1): string` + +Copy the contents of a stream into a string until the given number of +bytes have been read. + + +## `GuzzleHttp\Psr7\Utils::hash` + +`public static function hash(StreamInterface $stream, string $algo, bool $rawOutput = false): string` + +Calculate a hash of a stream. + +This method reads the entire stream to calculate a rolling hash, based on +PHP's `hash_init` functions. + + +## `GuzzleHttp\Psr7\Utils::modifyRequest` + +`public static function modifyRequest(RequestInterface $request, array $changes): RequestInterface` + +Clone and modify a request with the given changes. + +This method is useful for reducing the number of clones needed to mutate +a message. + +- method: (string) Changes the HTTP method. +- set_headers: (array) Sets the given headers. +- remove_headers: (array) Remove the given headers. +- body: (mixed) Sets the given body. +- uri: (UriInterface) Set the URI. +- query: (string) Set the query string value of the URI. +- version: (string) Set the protocol version. + + +## `GuzzleHttp\Psr7\Utils::readLine` + +`public static function readLine(StreamInterface $stream, int $maxLength = null): string` + +Read a line from the stream up to the maximum allowed buffer length. + + +## `GuzzleHttp\Psr7\Utils::streamFor` + +`public static function streamFor(resource|string|null|int|float|bool|StreamInterface|callable|\Iterator $resource = '', array $options = []): StreamInterface` + +Create a new stream based on the input type. + +Options is an associative array that can contain the following keys: + +- metadata: Array of custom metadata. +- size: Size of the stream. + +This method accepts the following `$resource` types: + +- `Psr\Http\Message\StreamInterface`: Returns the value as-is. +- `string`: Creates a stream object that uses the given string as the contents. +- `resource`: Creates a stream object that wraps the given PHP stream resource. +- `Iterator`: If the provided value implements `Iterator`, then a read-only + stream object will be created that wraps the given iterable. Each time the + stream is read from, data from the iterator will fill a buffer and will be + continuously called until the buffer is equal to the requested read size. + Subsequent read calls will first read from the buffer and then call `next` + on the underlying iterator until it is exhausted. +- `object` with `__toString()`: If the object has the `__toString()` method, + the object will be cast to a string and then a stream will be returned that + uses the string value. +- `NULL`: When `null` is passed, an empty stream object is returned. +- `callable` When a callable is passed, a read-only stream object will be + created that invokes the given callable. The callable is invoked with the + number of suggested bytes to read. The callable can return any number of + bytes, but MUST return `false` when there is no more data to return. The + stream object that wraps the callable will invoke the callable until the + number of requested bytes are available. Any additional bytes will be + buffered and used in subsequent reads. + +```php +$stream = GuzzleHttp\Psr7\Utils::streamFor('foo'); +$stream = GuzzleHttp\Psr7\Utils::streamFor(fopen('/path/to/file', 'r')); + +$generator = function ($bytes) { + for ($i = 0; $i < $bytes; $i++) { + yield ' '; + } +} + +$stream = GuzzleHttp\Psr7\Utils::streamFor($generator(100)); +``` + + +## `GuzzleHttp\Psr7\Utils::tryFopen` + +`public static function tryFopen(string $filename, string $mode): resource` + +Safely opens a PHP stream resource using a filename. + +When fopen fails, PHP normally raises a warning. This function adds an +error handler that checks for errors and throws an exception instead. + + +## `GuzzleHttp\Psr7\Utils::tryGetContents` + +`public static function tryGetContents(resource $stream): string` + +Safely gets the contents of a given stream. + +When stream_get_contents fails, PHP normally raises a warning. This +function adds an error handler that checks for errors and throws an +exception instead. + + +## `GuzzleHttp\Psr7\Utils::uriFor` + +`public static function uriFor(string|UriInterface $uri): UriInterface` + +Returns a UriInterface for the given value. + +This function accepts a string or UriInterface and returns a +UriInterface for the given value. If the value is already a +UriInterface, it is returned as-is. + + +## `GuzzleHttp\Psr7\MimeType::fromFilename` + +`public static function fromFilename(string $filename): string|null` + +Determines the mimetype of a file by looking at its extension. + + +## `GuzzleHttp\Psr7\MimeType::fromExtension` + +`public static function fromExtension(string $extension): string|null` + +Maps a file extensions to a mimetype. + + +## Upgrading from Function API + +The static API was first introduced in 1.7.0, in order to mitigate problems with functions conflicting between global and local copies of the package. The function API was removed in 2.0.0. A migration table has been provided here for your convenience: + +| Original Function | Replacement Method | +|----------------|----------------| +| `str` | `Message::toString` | +| `uri_for` | `Utils::uriFor` | +| `stream_for` | `Utils::streamFor` | +| `parse_header` | `Header::parse` | +| `normalize_header` | `Header::normalize` | +| `modify_request` | `Utils::modifyRequest` | +| `rewind_body` | `Message::rewindBody` | +| `try_fopen` | `Utils::tryFopen` | +| `copy_to_string` | `Utils::copyToString` | +| `copy_to_stream` | `Utils::copyToStream` | +| `hash` | `Utils::hash` | +| `readline` | `Utils::readLine` | +| `parse_request` | `Message::parseRequest` | +| `parse_response` | `Message::parseResponse` | +| `parse_query` | `Query::parse` | +| `build_query` | `Query::build` | +| `mimetype_from_filename` | `MimeType::fromFilename` | +| `mimetype_from_extension` | `MimeType::fromExtension` | +| `_parse_message` | `Message::parseMessage` | +| `_parse_request_uri` | `Message::parseRequestUri` | +| `get_message_body_summary` | `Message::bodySummary` | +| `_caseless_remove` | `Utils::caselessRemove` | + + +# Additional URI Methods + +Aside from the standard `Psr\Http\Message\UriInterface` implementation in form of the `GuzzleHttp\Psr7\Uri` class, +this library also provides additional functionality when working with URIs as static methods. + +## URI Types + +An instance of `Psr\Http\Message\UriInterface` can either be an absolute URI or a relative reference. +An absolute URI has a scheme. A relative reference is used to express a URI relative to another URI, +the base URI. Relative references can be divided into several forms according to +[RFC 3986 Section 4.2](https://datatracker.ietf.org/doc/html/rfc3986#section-4.2): + +- network-path references, e.g. `//example.com/path` +- absolute-path references, e.g. `/path` +- relative-path references, e.g. `subpath` + +The following methods can be used to identify the type of the URI. + +### `GuzzleHttp\Psr7\Uri::isAbsolute` + +`public static function isAbsolute(UriInterface $uri): bool` + +Whether the URI is absolute, i.e. it has a scheme. + +### `GuzzleHttp\Psr7\Uri::isNetworkPathReference` + +`public static function isNetworkPathReference(UriInterface $uri): bool` + +Whether the URI is a network-path reference. A relative reference that begins with two slash characters is +termed an network-path reference. + +### `GuzzleHttp\Psr7\Uri::isAbsolutePathReference` + +`public static function isAbsolutePathReference(UriInterface $uri): bool` + +Whether the URI is a absolute-path reference. A relative reference that begins with a single slash character is +termed an absolute-path reference. + +### `GuzzleHttp\Psr7\Uri::isRelativePathReference` + +`public static function isRelativePathReference(UriInterface $uri): bool` + +Whether the URI is a relative-path reference. A relative reference that does not begin with a slash character is +termed a relative-path reference. + +### `GuzzleHttp\Psr7\Uri::isSameDocumentReference` + +`public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null): bool` + +Whether the URI is a same-document reference. A same-document reference refers to a URI that is, aside from its +fragment component, identical to the base URI. When no base URI is given, only an empty URI reference +(apart from its fragment) is considered a same-document reference. + +## URI Components + +Additional methods to work with URI components. + +### `GuzzleHttp\Psr7\Uri::isDefaultPort` + +`public static function isDefaultPort(UriInterface $uri): bool` + +Whether the URI has the default port of the current scheme. `Psr\Http\Message\UriInterface::getPort` may return null +or the standard port. This method can be used independently of the implementation. + +### `GuzzleHttp\Psr7\Uri::composeComponents` + +`public static function composeComponents($scheme, $authority, $path, $query, $fragment): string` + +Composes a URI reference string from its various components according to +[RFC 3986 Section 5.3](https://datatracker.ietf.org/doc/html/rfc3986#section-5.3). Usually this method does not need +to be called manually but instead is used indirectly via `Psr\Http\Message\UriInterface::__toString`. + +### `GuzzleHttp\Psr7\Uri::fromParts` + +`public static function fromParts(array $parts): UriInterface` + +Creates a URI from a hash of [`parse_url`](https://www.php.net/manual/en/function.parse-url.php) components. + + +### `GuzzleHttp\Psr7\Uri::withQueryValue` + +`public static function withQueryValue(UriInterface $uri, $key, $value): UriInterface` + +Creates a new URI with a specific query string value. Any existing query string values that exactly match the +provided key are removed and replaced with the given key value pair. A value of null will set the query string +key without a value, e.g. "key" instead of "key=value". + +### `GuzzleHttp\Psr7\Uri::withQueryValues` + +`public static function withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface` + +Creates a new URI with multiple query string values. It has the same behavior as `withQueryValue()` but for an +associative array of key => value. + +### `GuzzleHttp\Psr7\Uri::withoutQueryValue` + +`public static function withoutQueryValue(UriInterface $uri, $key): UriInterface` + +Creates a new URI with a specific query string value removed. Any existing query string values that exactly match the +provided key are removed. + +## Cross-Origin Detection + +`GuzzleHttp\Psr7\UriComparator` provides methods to determine if a modified URL should be considered cross-origin. + +### `GuzzleHttp\Psr7\UriComparator::isCrossOrigin` + +`public static function isCrossOrigin(UriInterface $original, UriInterface $modified): bool` + +Determines if a modified URL should be considered cross-origin with respect to an original URL. + +## Reference Resolution + +`GuzzleHttp\Psr7\UriResolver` provides methods to resolve a URI reference in the context of a base URI according +to [RFC 3986 Section 5](https://datatracker.ietf.org/doc/html/rfc3986#section-5). This is for example also what web +browsers do when resolving a link in a website based on the current request URI. + +### `GuzzleHttp\Psr7\UriResolver::resolve` + +`public static function resolve(UriInterface $base, UriInterface $rel): UriInterface` + +Converts the relative URI into a new URI that is resolved against the base URI. + +### `GuzzleHttp\Psr7\UriResolver::removeDotSegments` + +`public static function removeDotSegments(string $path): string` + +Removes dot segments from a path and returns the new path according to +[RFC 3986 Section 5.2.4](https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4). + +### `GuzzleHttp\Psr7\UriResolver::relativize` + +`public static function relativize(UriInterface $base, UriInterface $target): UriInterface` + +Returns the target URI as a relative reference from the base URI. This method is the counterpart to resolve(): + +```php +(string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target)) +``` + +One use-case is to use the current request URI as base URI and then generate relative links in your documents +to reduce the document size or offer self-contained downloadable document archives. + +```php +$base = new Uri('http://example.com/a/b/'); +echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'. +echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'. +echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'. +echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'. +``` + +## Normalization and Comparison + +`GuzzleHttp\Psr7\UriNormalizer` provides methods to normalize and compare URIs according to +[RFC 3986 Section 6](https://datatracker.ietf.org/doc/html/rfc3986#section-6). + +### `GuzzleHttp\Psr7\UriNormalizer::normalize` + +`public static function normalize(UriInterface $uri, $flags = self::PRESERVING_NORMALIZATIONS): UriInterface` + +Returns a normalized URI. The scheme and host component are already normalized to lowercase per PSR-7 UriInterface. +This methods adds additional normalizations that can be configured with the `$flags` parameter which is a bitmask +of normalizations to apply. The following normalizations are available: + +- `UriNormalizer::PRESERVING_NORMALIZATIONS` + + Default normalizations which only include the ones that preserve semantics. + +- `UriNormalizer::CAPITALIZE_PERCENT_ENCODING` + + All letters within a percent-encoding triplet (e.g., "%3A") are case-insensitive, and should be capitalized. + + Example: `http://example.org/a%c2%b1b` → `http://example.org/a%C2%B1b` + +- `UriNormalizer::DECODE_UNRESERVED_CHARACTERS` + + Decodes percent-encoded octets of unreserved characters. For consistency, percent-encoded octets in the ranges of + ALPHA (%41–%5A and %61–%7A), DIGIT (%30–%39), hyphen (%2D), period (%2E), underscore (%5F), or tilde (%7E) should + not be created by URI producers and, when found in a URI, should be decoded to their corresponding unreserved + characters by URI normalizers. + + Example: `http://example.org/%7Eusern%61me/` → `http://example.org/~username/` + +- `UriNormalizer::CONVERT_EMPTY_PATH` + + Converts the empty path to "/" for http and https URIs. + + Example: `http://example.org` → `http://example.org/` + +- `UriNormalizer::REMOVE_DEFAULT_HOST` + + Removes the default host of the given URI scheme from the URI. Only the "file" scheme defines the default host + "localhost". All of `file:/myfile`, `file:///myfile`, and `file://localhost/myfile` are equivalent according to + RFC 3986. + + Example: `file://localhost/myfile` → `file:///myfile` + +- `UriNormalizer::REMOVE_DEFAULT_PORT` + + Removes the default port of the given URI scheme from the URI. + + Example: `http://example.org:80/` → `http://example.org/` + +- `UriNormalizer::REMOVE_DOT_SEGMENTS` + + Removes unnecessary dot-segments. Dot-segments in relative-path references are not removed as it would + change the semantics of the URI reference. + + Example: `http://example.org/../a/b/../c/./d.html` → `http://example.org/a/c/d.html` + +- `UriNormalizer::REMOVE_DUPLICATE_SLASHES` + + Paths which include two or more adjacent slashes are converted to one. Webservers usually ignore duplicate slashes + and treat those URIs equivalent. But in theory those URIs do not need to be equivalent. So this normalization + may change the semantics. Encoded slashes (%2F) are not removed. + + Example: `http://example.org//foo///bar.html` → `http://example.org/foo/bar.html` + +- `UriNormalizer::SORT_QUERY_PARAMETERS` + + Sort query parameters with their values in alphabetical order. However, the order of parameters in a URI may be + significant (this is not defined by the standard). So this normalization is not safe and may change the semantics + of the URI. + + Example: `?lang=en&article=fred` → `?article=fred&lang=en` + +### `GuzzleHttp\Psr7\UriNormalizer::isEquivalent` + +`public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS): bool` + +Whether two URIs can be considered equivalent. Both URIs are normalized automatically before comparison with the given +`$normalizations` bitmask. The method also accepts relative URI references and returns true when they are equivalent. +This of course assumes they will be resolved against the same base URI. If this is not the case, determination of +equivalence or difference of relative references does not mean anything. + + +## Security + +If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see [Security Policy](https://github.com/guzzle/psr7/security/policy) for more information. + + +## License + +Guzzle is made available under the MIT License (MIT). Please see [License File](LICENSE) for more information. + + +## For Enterprise + +Available as part of the Tidelift Subscription + +The maintainers of Guzzle and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-guzzlehttp-psr7?utm_source=packagist-guzzlehttp-psr7&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) diff --git a/vendor/guzzlehttp/psr7/composer.json b/vendor/guzzlehttp/psr7/composer.json new file mode 100644 index 00000000..70293fc4 --- /dev/null +++ b/vendor/guzzlehttp/psr7/composer.json @@ -0,0 +1,93 @@ +{ + "name": "guzzlehttp/psr7", + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "request", + "response", + "message", + "stream", + "http", + "uri", + "url", + "psr-7" + ], + "license": "MIT", + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.36 || ^9.6.15" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "GuzzleHttp\\Tests\\Psr7\\": "tests/" + } + }, + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "config": { + "allow-plugins": { + "bamarni/composer-bin-plugin": true + }, + "preferred-install": "dist", + "sort-packages": true + } +} diff --git a/vendor/guzzlehttp/psr7/src/AppendStream.php b/vendor/guzzlehttp/psr7/src/AppendStream.php new file mode 100644 index 00000000..ee8f3788 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/AppendStream.php @@ -0,0 +1,248 @@ +addStream($stream); + } + } + + public function __toString(): string + { + try { + $this->rewind(); + + return $this->getContents(); + } catch (\Throwable $e) { + if (\PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR); + + return ''; + } + } + + /** + * Add a stream to the AppendStream + * + * @param StreamInterface $stream Stream to append. Must be readable. + * + * @throws \InvalidArgumentException if the stream is not readable + */ + public function addStream(StreamInterface $stream): void + { + if (!$stream->isReadable()) { + throw new \InvalidArgumentException('Each stream must be readable'); + } + + // The stream is only seekable if all streams are seekable + if (!$stream->isSeekable()) { + $this->seekable = false; + } + + $this->streams[] = $stream; + } + + public function getContents(): string + { + return Utils::copyToString($this); + } + + /** + * Closes each attached stream. + */ + public function close(): void + { + $this->pos = $this->current = 0; + $this->seekable = true; + + foreach ($this->streams as $stream) { + $stream->close(); + } + + $this->streams = []; + } + + /** + * Detaches each attached stream. + * + * Returns null as it's not clear which underlying stream resource to return. + */ + public function detach() + { + $this->pos = $this->current = 0; + $this->seekable = true; + + foreach ($this->streams as $stream) { + $stream->detach(); + } + + $this->streams = []; + + return null; + } + + public function tell(): int + { + return $this->pos; + } + + /** + * Tries to calculate the size by adding the size of each stream. + * + * If any of the streams do not return a valid number, then the size of the + * append stream cannot be determined and null is returned. + */ + public function getSize(): ?int + { + $size = 0; + + foreach ($this->streams as $stream) { + $s = $stream->getSize(); + if ($s === null) { + return null; + } + $size += $s; + } + + return $size; + } + + public function eof(): bool + { + return !$this->streams + || ($this->current >= count($this->streams) - 1 + && $this->streams[$this->current]->eof()); + } + + public function rewind(): void + { + $this->seek(0); + } + + /** + * Attempts to seek to the given position. Only supports SEEK_SET. + */ + public function seek($offset, $whence = SEEK_SET): void + { + if (!$this->seekable) { + throw new \RuntimeException('This AppendStream is not seekable'); + } elseif ($whence !== SEEK_SET) { + throw new \RuntimeException('The AppendStream can only seek with SEEK_SET'); + } + + $this->pos = $this->current = 0; + + // Rewind each stream + foreach ($this->streams as $i => $stream) { + try { + $stream->rewind(); + } catch (\Exception $e) { + throw new \RuntimeException('Unable to seek stream ' + .$i.' of the AppendStream', 0, $e); + } + } + + // Seek to the actual position by reading from each stream + while ($this->pos < $offset && !$this->eof()) { + $result = $this->read(min(8096, $offset - $this->pos)); + if ($result === '') { + break; + } + } + } + + /** + * Reads from all of the appended streams until the length is met or EOF. + */ + public function read($length): string + { + $buffer = ''; + $total = count($this->streams) - 1; + $remaining = $length; + $progressToNext = false; + + while ($remaining > 0) { + // Progress to the next stream if needed. + if ($progressToNext || $this->streams[$this->current]->eof()) { + $progressToNext = false; + if ($this->current === $total) { + break; + } + ++$this->current; + } + + $result = $this->streams[$this->current]->read($remaining); + + if ($result === '') { + $progressToNext = true; + continue; + } + + $buffer .= $result; + $remaining = $length - strlen($buffer); + } + + $this->pos += strlen($buffer); + + return $buffer; + } + + public function isReadable(): bool + { + return true; + } + + public function isWritable(): bool + { + return false; + } + + public function isSeekable(): bool + { + return $this->seekable; + } + + public function write($string): int + { + throw new \RuntimeException('Cannot write to an AppendStream'); + } + + /** + * @return mixed + */ + public function getMetadata($key = null) + { + return $key ? null : []; + } +} diff --git a/vendor/guzzlehttp/psr7/src/BufferStream.php b/vendor/guzzlehttp/psr7/src/BufferStream.php new file mode 100644 index 00000000..2b0eb77b --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/BufferStream.php @@ -0,0 +1,147 @@ +hwm = $hwm; + } + + public function __toString(): string + { + return $this->getContents(); + } + + public function getContents(): string + { + $buffer = $this->buffer; + $this->buffer = ''; + + return $buffer; + } + + public function close(): void + { + $this->buffer = ''; + } + + public function detach() + { + $this->close(); + + return null; + } + + public function getSize(): ?int + { + return strlen($this->buffer); + } + + public function isReadable(): bool + { + return true; + } + + public function isWritable(): bool + { + return true; + } + + public function isSeekable(): bool + { + return false; + } + + public function rewind(): void + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET): void + { + throw new \RuntimeException('Cannot seek a BufferStream'); + } + + public function eof(): bool + { + return strlen($this->buffer) === 0; + } + + public function tell(): int + { + throw new \RuntimeException('Cannot determine the position of a BufferStream'); + } + + /** + * Reads data from the buffer. + */ + public function read($length): string + { + $currentLength = strlen($this->buffer); + + if ($length >= $currentLength) { + // No need to slice the buffer because we don't have enough data. + $result = $this->buffer; + $this->buffer = ''; + } else { + // Slice up the result to provide a subset of the buffer. + $result = substr($this->buffer, 0, $length); + $this->buffer = substr($this->buffer, $length); + } + + return $result; + } + + /** + * Writes data to the buffer. + */ + public function write($string): int + { + $this->buffer .= $string; + + if (strlen($this->buffer) >= $this->hwm) { + return 0; + } + + return strlen($string); + } + + /** + * @return mixed + */ + public function getMetadata($key = null) + { + if ($key === 'hwm') { + return $this->hwm; + } + + return $key ? null : []; + } +} diff --git a/vendor/guzzlehttp/psr7/src/CachingStream.php b/vendor/guzzlehttp/psr7/src/CachingStream.php new file mode 100644 index 00000000..f34722cf --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/CachingStream.php @@ -0,0 +1,153 @@ +remoteStream = $stream; + $this->stream = $target ?: new Stream(Utils::tryFopen('php://temp', 'r+')); + } + + public function getSize(): ?int + { + $remoteSize = $this->remoteStream->getSize(); + + if (null === $remoteSize) { + return null; + } + + return max($this->stream->getSize(), $remoteSize); + } + + public function rewind(): void + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET): void + { + if ($whence === SEEK_SET) { + $byte = $offset; + } elseif ($whence === SEEK_CUR) { + $byte = $offset + $this->tell(); + } elseif ($whence === SEEK_END) { + $size = $this->remoteStream->getSize(); + if ($size === null) { + $size = $this->cacheEntireStream(); + } + $byte = $size + $offset; + } else { + throw new \InvalidArgumentException('Invalid whence'); + } + + $diff = $byte - $this->stream->getSize(); + + if ($diff > 0) { + // Read the remoteStream until we have read in at least the amount + // of bytes requested, or we reach the end of the file. + while ($diff > 0 && !$this->remoteStream->eof()) { + $this->read($diff); + $diff = $byte - $this->stream->getSize(); + } + } else { + // We can just do a normal seek since we've already seen this byte. + $this->stream->seek($byte); + } + } + + public function read($length): string + { + // Perform a regular read on any previously read data from the buffer + $data = $this->stream->read($length); + $remaining = $length - strlen($data); + + // More data was requested so read from the remote stream + if ($remaining) { + // If data was written to the buffer in a position that would have + // been filled from the remote stream, then we must skip bytes on + // the remote stream to emulate overwriting bytes from that + // position. This mimics the behavior of other PHP stream wrappers. + $remoteData = $this->remoteStream->read( + $remaining + $this->skipReadBytes + ); + + if ($this->skipReadBytes) { + $len = strlen($remoteData); + $remoteData = substr($remoteData, $this->skipReadBytes); + $this->skipReadBytes = max(0, $this->skipReadBytes - $len); + } + + $data .= $remoteData; + $this->stream->write($remoteData); + } + + return $data; + } + + public function write($string): int + { + // When appending to the end of the currently read stream, you'll want + // to skip bytes from being read from the remote stream to emulate + // other stream wrappers. Basically replacing bytes of data of a fixed + // length. + $overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell(); + if ($overflow > 0) { + $this->skipReadBytes += $overflow; + } + + return $this->stream->write($string); + } + + public function eof(): bool + { + return $this->stream->eof() && $this->remoteStream->eof(); + } + + /** + * Close both the remote stream and buffer stream + */ + public function close(): void + { + $this->remoteStream->close(); + $this->stream->close(); + } + + private function cacheEntireStream(): int + { + $target = new FnStream(['write' => 'strlen']); + Utils::copyToStream($this, $target); + + return $this->tell(); + } +} diff --git a/vendor/guzzlehttp/psr7/src/DroppingStream.php b/vendor/guzzlehttp/psr7/src/DroppingStream.php new file mode 100644 index 00000000..6e3d209d --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/DroppingStream.php @@ -0,0 +1,49 @@ +stream = $stream; + $this->maxLength = $maxLength; + } + + public function write($string): int + { + $diff = $this->maxLength - $this->stream->getSize(); + + // Begin returning 0 when the underlying stream is too large. + if ($diff <= 0) { + return 0; + } + + // Write the stream or a subset of the stream if needed. + if (strlen($string) < $diff) { + return $this->stream->write($string); + } + + return $this->stream->write(substr($string, 0, $diff)); + } +} diff --git a/vendor/guzzlehttp/psr7/src/Exception/MalformedUriException.php b/vendor/guzzlehttp/psr7/src/Exception/MalformedUriException.php new file mode 100644 index 00000000..3a084779 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Exception/MalformedUriException.php @@ -0,0 +1,14 @@ + */ + private $methods; + + /** + * @param array $methods Hash of method name to a callable. + */ + public function __construct(array $methods) + { + $this->methods = $methods; + + // Create the functions on the class + foreach ($methods as $name => $fn) { + $this->{'_fn_'.$name} = $fn; + } + } + + /** + * Lazily determine which methods are not implemented. + * + * @throws \BadMethodCallException + */ + public function __get(string $name): void + { + throw new \BadMethodCallException(str_replace('_fn_', '', $name) + .'() is not implemented in the FnStream'); + } + + /** + * The close method is called on the underlying stream only if possible. + */ + public function __destruct() + { + if (isset($this->_fn_close)) { + ($this->_fn_close)(); + } + } + + /** + * An unserialize would allow the __destruct to run when the unserialized value goes out of scope. + * + * @throws \LogicException + */ + public function __wakeup(): void + { + throw new \LogicException('FnStream should never be unserialized'); + } + + /** + * Adds custom functionality to an underlying stream by intercepting + * specific method calls. + * + * @param StreamInterface $stream Stream to decorate + * @param array $methods Hash of method name to a closure + * + * @return FnStream + */ + public static function decorate(StreamInterface $stream, array $methods) + { + // If any of the required methods were not provided, then simply + // proxy to the decorated stream. + foreach (array_diff(self::SLOTS, array_keys($methods)) as $diff) { + /** @var callable $callable */ + $callable = [$stream, $diff]; + $methods[$diff] = $callable; + } + + return new self($methods); + } + + public function __toString(): string + { + try { + /** @var string */ + return ($this->_fn___toString)(); + } catch (\Throwable $e) { + if (\PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR); + + return ''; + } + } + + public function close(): void + { + ($this->_fn_close)(); + } + + public function detach() + { + return ($this->_fn_detach)(); + } + + public function getSize(): ?int + { + return ($this->_fn_getSize)(); + } + + public function tell(): int + { + return ($this->_fn_tell)(); + } + + public function eof(): bool + { + return ($this->_fn_eof)(); + } + + public function isSeekable(): bool + { + return ($this->_fn_isSeekable)(); + } + + public function rewind(): void + { + ($this->_fn_rewind)(); + } + + public function seek($offset, $whence = SEEK_SET): void + { + ($this->_fn_seek)($offset, $whence); + } + + public function isWritable(): bool + { + return ($this->_fn_isWritable)(); + } + + public function write($string): int + { + return ($this->_fn_write)($string); + } + + public function isReadable(): bool + { + return ($this->_fn_isReadable)(); + } + + public function read($length): string + { + return ($this->_fn_read)($length); + } + + public function getContents(): string + { + return ($this->_fn_getContents)(); + } + + /** + * @return mixed + */ + public function getMetadata($key = null) + { + return ($this->_fn_getMetadata)($key); + } +} diff --git a/vendor/guzzlehttp/psr7/src/Header.php b/vendor/guzzlehttp/psr7/src/Header.php new file mode 100644 index 00000000..bbce8b03 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Header.php @@ -0,0 +1,134 @@ +]+>|[^=]+/', $kvp, $matches)) { + $m = $matches[0]; + if (isset($m[1])) { + $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed); + } else { + $part[] = trim($m[0], $trimmed); + } + } + } + if ($part) { + $params[] = $part; + } + } + } + + return $params; + } + + /** + * Converts an array of header values that may contain comma separated + * headers into an array of headers with no comma separated values. + * + * @param string|array $header Header to normalize. + * + * @deprecated Use self::splitList() instead. + */ + public static function normalize($header): array + { + $result = []; + foreach ((array) $header as $value) { + foreach (self::splitList($value) as $parsed) { + $result[] = $parsed; + } + } + + return $result; + } + + /** + * Splits a HTTP header defined to contain a comma-separated list into + * each individual value. Empty values will be removed. + * + * Example headers include 'accept', 'cache-control' and 'if-none-match'. + * + * This method must not be used to parse headers that are not defined as + * a list, such as 'user-agent' or 'set-cookie'. + * + * @param string|string[] $values Header value as returned by MessageInterface::getHeader() + * + * @return string[] + */ + public static function splitList($values): array + { + if (!\is_array($values)) { + $values = [$values]; + } + + $result = []; + foreach ($values as $value) { + if (!\is_string($value)) { + throw new \TypeError('$header must either be a string or an array containing strings.'); + } + + $v = ''; + $isQuoted = false; + $isEscaped = false; + for ($i = 0, $max = \strlen($value); $i < $max; ++$i) { + if ($isEscaped) { + $v .= $value[$i]; + $isEscaped = false; + + continue; + } + + if (!$isQuoted && $value[$i] === ',') { + $v = \trim($v); + if ($v !== '') { + $result[] = $v; + } + + $v = ''; + continue; + } + + if ($isQuoted && $value[$i] === '\\') { + $isEscaped = true; + $v .= $value[$i]; + + continue; + } + if ($value[$i] === '"') { + $isQuoted = !$isQuoted; + $v .= $value[$i]; + + continue; + } + + $v .= $value[$i]; + } + + $v = \trim($v); + if ($v !== '') { + $result[] = $v; + } + } + + return $result; + } +} diff --git a/vendor/guzzlehttp/psr7/src/HttpFactory.php b/vendor/guzzlehttp/psr7/src/HttpFactory.php new file mode 100644 index 00000000..73d17e33 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/HttpFactory.php @@ -0,0 +1,94 @@ +getSize(); + } + + return new UploadedFile($stream, $size, $error, $clientFilename, $clientMediaType); + } + + public function createStream(string $content = ''): StreamInterface + { + return Utils::streamFor($content); + } + + public function createStreamFromFile(string $file, string $mode = 'r'): StreamInterface + { + try { + $resource = Utils::tryFopen($file, $mode); + } catch (\RuntimeException $e) { + if ('' === $mode || false === \in_array($mode[0], ['r', 'w', 'a', 'x', 'c'], true)) { + throw new \InvalidArgumentException(sprintf('Invalid file opening mode "%s"', $mode), 0, $e); + } + + throw $e; + } + + return Utils::streamFor($resource); + } + + public function createStreamFromResource($resource): StreamInterface + { + return Utils::streamFor($resource); + } + + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + if (empty($method)) { + if (!empty($serverParams['REQUEST_METHOD'])) { + $method = $serverParams['REQUEST_METHOD']; + } else { + throw new \InvalidArgumentException('Cannot determine HTTP method'); + } + } + + return new ServerRequest($method, $uri, [], null, '1.1', $serverParams); + } + + public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface + { + return new Response($code, [], null, '1.1', $reasonPhrase); + } + + public function createRequest(string $method, $uri): RequestInterface + { + return new Request($method, $uri); + } + + public function createUri(string $uri = ''): UriInterface + { + return new Uri($uri); + } +} diff --git a/vendor/guzzlehttp/psr7/src/InflateStream.php b/vendor/guzzlehttp/psr7/src/InflateStream.php new file mode 100644 index 00000000..e674c9ab --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/InflateStream.php @@ -0,0 +1,37 @@ + 15 + 32]); + $this->stream = $stream->isSeekable() ? new Stream($resource) : new NoSeekStream(new Stream($resource)); + } +} diff --git a/vendor/guzzlehttp/psr7/src/LazyOpenStream.php b/vendor/guzzlehttp/psr7/src/LazyOpenStream.php new file mode 100644 index 00000000..f6c84904 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/LazyOpenStream.php @@ -0,0 +1,49 @@ +filename = $filename; + $this->mode = $mode; + + // unsetting the property forces the first access to go through + // __get(). + unset($this->stream); + } + + /** + * Creates the underlying stream lazily when required. + */ + protected function createStream(): StreamInterface + { + return Utils::streamFor(Utils::tryFopen($this->filename, $this->mode)); + } +} diff --git a/vendor/guzzlehttp/psr7/src/LimitStream.php b/vendor/guzzlehttp/psr7/src/LimitStream.php new file mode 100644 index 00000000..fb223255 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/LimitStream.php @@ -0,0 +1,157 @@ +stream = $stream; + $this->setLimit($limit); + $this->setOffset($offset); + } + + public function eof(): bool + { + // Always return true if the underlying stream is EOF + if ($this->stream->eof()) { + return true; + } + + // No limit and the underlying stream is not at EOF + if ($this->limit === -1) { + return false; + } + + return $this->stream->tell() >= $this->offset + $this->limit; + } + + /** + * Returns the size of the limited subset of data + */ + public function getSize(): ?int + { + if (null === ($length = $this->stream->getSize())) { + return null; + } elseif ($this->limit === -1) { + return $length - $this->offset; + } else { + return min($this->limit, $length - $this->offset); + } + } + + /** + * Allow for a bounded seek on the read limited stream + */ + public function seek($offset, $whence = SEEK_SET): void + { + if ($whence !== SEEK_SET || $offset < 0) { + throw new \RuntimeException(sprintf( + 'Cannot seek to offset %s with whence %s', + $offset, + $whence + )); + } + + $offset += $this->offset; + + if ($this->limit !== -1) { + if ($offset > $this->offset + $this->limit) { + $offset = $this->offset + $this->limit; + } + } + + $this->stream->seek($offset); + } + + /** + * Give a relative tell() + */ + public function tell(): int + { + return $this->stream->tell() - $this->offset; + } + + /** + * Set the offset to start limiting from + * + * @param int $offset Offset to seek to and begin byte limiting from + * + * @throws \RuntimeException if the stream cannot be seeked. + */ + public function setOffset(int $offset): void + { + $current = $this->stream->tell(); + + if ($current !== $offset) { + // If the stream cannot seek to the offset position, then read to it + if ($this->stream->isSeekable()) { + $this->stream->seek($offset); + } elseif ($current > $offset) { + throw new \RuntimeException("Could not seek to stream offset $offset"); + } else { + $this->stream->read($offset - $current); + } + } + + $this->offset = $offset; + } + + /** + * Set the limit of bytes that the decorator allows to be read from the + * stream. + * + * @param int $limit Number of bytes to allow to be read from the stream. + * Use -1 for no limit. + */ + public function setLimit(int $limit): void + { + $this->limit = $limit; + } + + public function read($length): string + { + if ($this->limit === -1) { + return $this->stream->read($length); + } + + // Check if the current position is less than the total allowed + // bytes + original offset + $remaining = ($this->offset + $this->limit) - $this->stream->tell(); + if ($remaining > 0) { + // Only return the amount of requested data, ensuring that the byte + // limit is not exceeded + return $this->stream->read(min($remaining, $length)); + } + + return ''; + } +} diff --git a/vendor/guzzlehttp/psr7/src/Message.php b/vendor/guzzlehttp/psr7/src/Message.php new file mode 100644 index 00000000..5561a513 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Message.php @@ -0,0 +1,246 @@ +getMethod().' ' + .$message->getRequestTarget()) + .' HTTP/'.$message->getProtocolVersion(); + if (!$message->hasHeader('host')) { + $msg .= "\r\nHost: ".$message->getUri()->getHost(); + } + } elseif ($message instanceof ResponseInterface) { + $msg = 'HTTP/'.$message->getProtocolVersion().' ' + .$message->getStatusCode().' ' + .$message->getReasonPhrase(); + } else { + throw new \InvalidArgumentException('Unknown message type'); + } + + foreach ($message->getHeaders() as $name => $values) { + if (is_string($name) && strtolower($name) === 'set-cookie') { + foreach ($values as $value) { + $msg .= "\r\n{$name}: ".$value; + } + } else { + $msg .= "\r\n{$name}: ".implode(', ', $values); + } + } + + return "{$msg}\r\n\r\n".$message->getBody(); + } + + /** + * Get a short summary of the message body. + * + * Will return `null` if the response is not printable. + * + * @param MessageInterface $message The message to get the body summary + * @param int $truncateAt The maximum allowed size of the summary + */ + public static function bodySummary(MessageInterface $message, int $truncateAt = 120): ?string + { + $body = $message->getBody(); + + if (!$body->isSeekable() || !$body->isReadable()) { + return null; + } + + $size = $body->getSize(); + + if ($size === 0) { + return null; + } + + $body->rewind(); + $summary = $body->read($truncateAt); + $body->rewind(); + + if ($size > $truncateAt) { + $summary .= ' (truncated...)'; + } + + // Matches any printable character, including unicode characters: + // letters, marks, numbers, punctuation, spacing, and separators. + if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/u', $summary) !== 0) { + return null; + } + + return $summary; + } + + /** + * Attempts to rewind a message body and throws an exception on failure. + * + * The body of the message will only be rewound if a call to `tell()` + * returns a value other than `0`. + * + * @param MessageInterface $message Message to rewind + * + * @throws \RuntimeException + */ + public static function rewindBody(MessageInterface $message): void + { + $body = $message->getBody(); + + if ($body->tell()) { + $body->rewind(); + } + } + + /** + * Parses an HTTP message into an associative array. + * + * The array contains the "start-line" key containing the start line of + * the message, "headers" key containing an associative array of header + * array values, and a "body" key containing the body of the message. + * + * @param string $message HTTP request or response to parse. + */ + public static function parseMessage(string $message): array + { + if (!$message) { + throw new \InvalidArgumentException('Invalid message'); + } + + $message = ltrim($message, "\r\n"); + + $messageParts = preg_split("/\r?\n\r?\n/", $message, 2); + + if ($messageParts === false || count($messageParts) !== 2) { + throw new \InvalidArgumentException('Invalid message: Missing header delimiter'); + } + + [$rawHeaders, $body] = $messageParts; + $rawHeaders .= "\r\n"; // Put back the delimiter we split previously + $headerParts = preg_split("/\r?\n/", $rawHeaders, 2); + + if ($headerParts === false || count($headerParts) !== 2) { + throw new \InvalidArgumentException('Invalid message: Missing status line'); + } + + [$startLine, $rawHeaders] = $headerParts; + + if (preg_match("/(?:^HTTP\/|^[A-Z]+ \S+ HTTP\/)(\d+(?:\.\d+)?)/i", $startLine, $matches) && $matches[1] === '1.0') { + // Header folding is deprecated for HTTP/1.1, but allowed in HTTP/1.0 + $rawHeaders = preg_replace(Rfc7230::HEADER_FOLD_REGEX, ' ', $rawHeaders); + } + + /** @var array[] $headerLines */ + $count = preg_match_all(Rfc7230::HEADER_REGEX, $rawHeaders, $headerLines, PREG_SET_ORDER); + + // If these aren't the same, then one line didn't match and there's an invalid header. + if ($count !== substr_count($rawHeaders, "\n")) { + // Folding is deprecated, see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.4 + if (preg_match(Rfc7230::HEADER_FOLD_REGEX, $rawHeaders)) { + throw new \InvalidArgumentException('Invalid header syntax: Obsolete line folding'); + } + + throw new \InvalidArgumentException('Invalid header syntax'); + } + + $headers = []; + + foreach ($headerLines as $headerLine) { + $headers[$headerLine[1]][] = $headerLine[2]; + } + + return [ + 'start-line' => $startLine, + 'headers' => $headers, + 'body' => $body, + ]; + } + + /** + * Constructs a URI for an HTTP request message. + * + * @param string $path Path from the start-line + * @param array $headers Array of headers (each value an array). + */ + public static function parseRequestUri(string $path, array $headers): string + { + $hostKey = array_filter(array_keys($headers), function ($k) { + // Numeric array keys are converted to int by PHP. + $k = (string) $k; + + return strtolower($k) === 'host'; + }); + + // If no host is found, then a full URI cannot be constructed. + if (!$hostKey) { + return $path; + } + + $host = $headers[reset($hostKey)][0]; + $scheme = substr($host, -4) === ':443' ? 'https' : 'http'; + + return $scheme.'://'.$host.'/'.ltrim($path, '/'); + } + + /** + * Parses a request message string into a request object. + * + * @param string $message Request message string. + */ + public static function parseRequest(string $message): RequestInterface + { + $data = self::parseMessage($message); + $matches = []; + if (!preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) { + throw new \InvalidArgumentException('Invalid request string'); + } + $parts = explode(' ', $data['start-line'], 3); + $version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1'; + + $request = new Request( + $parts[0], + $matches[1] === '/' ? self::parseRequestUri($parts[1], $data['headers']) : $parts[1], + $data['headers'], + $data['body'], + $version + ); + + return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]); + } + + /** + * Parses a response message string into a response object. + * + * @param string $message Response message string. + */ + public static function parseResponse(string $message): ResponseInterface + { + $data = self::parseMessage($message); + // According to https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2 + // the space between status-code and reason-phrase is required. But + // browsers accept responses without space and reason as well. + if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) { + throw new \InvalidArgumentException('Invalid response string: '.$data['start-line']); + } + $parts = explode(' ', $data['start-line'], 3); + + return new Response( + (int) $parts[1], + $data['headers'], + $data['body'], + explode('/', $parts[0])[1], + $parts[2] ?? null + ); + } +} diff --git a/vendor/guzzlehttp/psr7/src/MessageTrait.php b/vendor/guzzlehttp/psr7/src/MessageTrait.php new file mode 100644 index 00000000..65dbc4ba --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/MessageTrait.php @@ -0,0 +1,265 @@ + array of values */ + private $headers = []; + + /** @var string[] Map of lowercase header name => original name at registration */ + private $headerNames = []; + + /** @var string */ + private $protocol = '1.1'; + + /** @var StreamInterface|null */ + private $stream; + + public function getProtocolVersion(): string + { + return $this->protocol; + } + + public function withProtocolVersion($version): MessageInterface + { + if ($this->protocol === $version) { + return $this; + } + + $new = clone $this; + $new->protocol = $version; + + return $new; + } + + public function getHeaders(): array + { + return $this->headers; + } + + public function hasHeader($header): bool + { + return isset($this->headerNames[strtolower($header)]); + } + + public function getHeader($header): array + { + $header = strtolower($header); + + if (!isset($this->headerNames[$header])) { + return []; + } + + $header = $this->headerNames[$header]; + + return $this->headers[$header]; + } + + public function getHeaderLine($header): string + { + return implode(', ', $this->getHeader($header)); + } + + public function withHeader($header, $value): MessageInterface + { + $this->assertHeader($header); + $value = $this->normalizeHeaderValue($value); + $normalized = strtolower($header); + + $new = clone $this; + if (isset($new->headerNames[$normalized])) { + unset($new->headers[$new->headerNames[$normalized]]); + } + $new->headerNames[$normalized] = $header; + $new->headers[$header] = $value; + + return $new; + } + + public function withAddedHeader($header, $value): MessageInterface + { + $this->assertHeader($header); + $value = $this->normalizeHeaderValue($value); + $normalized = strtolower($header); + + $new = clone $this; + if (isset($new->headerNames[$normalized])) { + $header = $this->headerNames[$normalized]; + $new->headers[$header] = array_merge($this->headers[$header], $value); + } else { + $new->headerNames[$normalized] = $header; + $new->headers[$header] = $value; + } + + return $new; + } + + public function withoutHeader($header): MessageInterface + { + $normalized = strtolower($header); + + if (!isset($this->headerNames[$normalized])) { + return $this; + } + + $header = $this->headerNames[$normalized]; + + $new = clone $this; + unset($new->headers[$header], $new->headerNames[$normalized]); + + return $new; + } + + public function getBody(): StreamInterface + { + if (!$this->stream) { + $this->stream = Utils::streamFor(''); + } + + return $this->stream; + } + + public function withBody(StreamInterface $body): MessageInterface + { + if ($body === $this->stream) { + return $this; + } + + $new = clone $this; + $new->stream = $body; + + return $new; + } + + /** + * @param (string|string[])[] $headers + */ + private function setHeaders(array $headers): void + { + $this->headerNames = $this->headers = []; + foreach ($headers as $header => $value) { + // Numeric array keys are converted to int by PHP. + $header = (string) $header; + + $this->assertHeader($header); + $value = $this->normalizeHeaderValue($value); + $normalized = strtolower($header); + if (isset($this->headerNames[$normalized])) { + $header = $this->headerNames[$normalized]; + $this->headers[$header] = array_merge($this->headers[$header], $value); + } else { + $this->headerNames[$normalized] = $header; + $this->headers[$header] = $value; + } + } + } + + /** + * @param mixed $value + * + * @return string[] + */ + private function normalizeHeaderValue($value): array + { + if (!is_array($value)) { + return $this->trimAndValidateHeaderValues([$value]); + } + + if (count($value) === 0) { + throw new \InvalidArgumentException('Header value can not be an empty array.'); + } + + return $this->trimAndValidateHeaderValues($value); + } + + /** + * Trims whitespace from the header values. + * + * Spaces and tabs ought to be excluded by parsers when extracting the field value from a header field. + * + * header-field = field-name ":" OWS field-value OWS + * OWS = *( SP / HTAB ) + * + * @param mixed[] $values Header values + * + * @return string[] Trimmed header values + * + * @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.4 + */ + private function trimAndValidateHeaderValues(array $values): array + { + return array_map(function ($value) { + if (!is_scalar($value) && null !== $value) { + throw new \InvalidArgumentException(sprintf( + 'Header value must be scalar or null but %s provided.', + is_object($value) ? get_class($value) : gettype($value) + )); + } + + $trimmed = trim((string) $value, " \t"); + $this->assertValue($trimmed); + + return $trimmed; + }, array_values($values)); + } + + /** + * @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2 + * + * @param mixed $header + */ + private function assertHeader($header): void + { + if (!is_string($header)) { + throw new \InvalidArgumentException(sprintf( + 'Header name must be a string but %s provided.', + is_object($header) ? get_class($header) : gettype($header) + )); + } + + if (!preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/D', $header)) { + throw new \InvalidArgumentException( + sprintf('"%s" is not valid header name.', $header) + ); + } + } + + /** + * @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2 + * + * field-value = *( field-content / obs-fold ) + * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] + * field-vchar = VCHAR / obs-text + * VCHAR = %x21-7E + * obs-text = %x80-FF + * obs-fold = CRLF 1*( SP / HTAB ) + */ + private function assertValue(string $value): void + { + // The regular expression intentionally does not support the obs-fold production, because as + // per RFC 7230#3.2.4: + // + // A sender MUST NOT generate a message that includes + // line folding (i.e., that has any field-value that contains a match to + // the obs-fold rule) unless the message is intended for packaging + // within the message/http media type. + // + // Clients must not send a request with line folding and a server sending folded headers is + // likely very rare. Line folding is a fairly obscure feature of HTTP/1.1 and thus not accepting + // folding is not likely to break any legitimate use case. + if (!preg_match('/^[\x20\x09\x21-\x7E\x80-\xFF]*$/D', $value)) { + throw new \InvalidArgumentException( + sprintf('"%s" is not valid header value.', $value) + ); + } + } +} diff --git a/vendor/guzzlehttp/psr7/src/MimeType.php b/vendor/guzzlehttp/psr7/src/MimeType.php new file mode 100644 index 00000000..b131bdbe --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/MimeType.php @@ -0,0 +1,1259 @@ + 'application/vnd.1000minds.decision-model+xml', + '3dml' => 'text/vnd.in3d.3dml', + '3ds' => 'image/x-3ds', + '3g2' => 'video/3gpp2', + '3gp' => 'video/3gp', + '3gpp' => 'video/3gpp', + '3mf' => 'model/3mf', + '7z' => 'application/x-7z-compressed', + '7zip' => 'application/x-7z-compressed', + '123' => 'application/vnd.lotus-1-2-3', + 'aab' => 'application/x-authorware-bin', + 'aac' => 'audio/aac', + 'aam' => 'application/x-authorware-map', + 'aas' => 'application/x-authorware-seg', + 'abw' => 'application/x-abiword', + 'ac' => 'application/vnd.nokia.n-gage.ac+xml', + 'ac3' => 'audio/ac3', + 'acc' => 'application/vnd.americandynamics.acc', + 'ace' => 'application/x-ace-compressed', + 'acu' => 'application/vnd.acucobol', + 'acutc' => 'application/vnd.acucorp', + 'adp' => 'audio/adpcm', + 'adts' => 'audio/aac', + 'aep' => 'application/vnd.audiograph', + 'afm' => 'application/x-font-type1', + 'afp' => 'application/vnd.ibm.modcap', + 'age' => 'application/vnd.age', + 'ahead' => 'application/vnd.ahead.space', + 'ai' => 'application/pdf', + 'aif' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'air' => 'application/vnd.adobe.air-application-installer-package+zip', + 'ait' => 'application/vnd.dvb.ait', + 'ami' => 'application/vnd.amiga.ami', + 'aml' => 'application/automationml-aml+xml', + 'amlx' => 'application/automationml-amlx+zip', + 'amr' => 'audio/amr', + 'apk' => 'application/vnd.android.package-archive', + 'apng' => 'image/apng', + 'appcache' => 'text/cache-manifest', + 'appinstaller' => 'application/appinstaller', + 'application' => 'application/x-ms-application', + 'appx' => 'application/appx', + 'appxbundle' => 'application/appxbundle', + 'apr' => 'application/vnd.lotus-approach', + 'arc' => 'application/x-freearc', + 'arj' => 'application/x-arj', + 'asc' => 'application/pgp-signature', + 'asf' => 'video/x-ms-asf', + 'asm' => 'text/x-asm', + 'aso' => 'application/vnd.accpac.simply.aso', + 'asx' => 'video/x-ms-asf', + 'atc' => 'application/vnd.acucorp', + 'atom' => 'application/atom+xml', + 'atomcat' => 'application/atomcat+xml', + 'atomdeleted' => 'application/atomdeleted+xml', + 'atomsvc' => 'application/atomsvc+xml', + 'atx' => 'application/vnd.antix.game-component', + 'au' => 'audio/x-au', + 'avci' => 'image/avci', + 'avcs' => 'image/avcs', + 'avi' => 'video/x-msvideo', + 'avif' => 'image/avif', + 'aw' => 'application/applixware', + 'azf' => 'application/vnd.airzip.filesecure.azf', + 'azs' => 'application/vnd.airzip.filesecure.azs', + 'azv' => 'image/vnd.airzip.accelerator.azv', + 'azw' => 'application/vnd.amazon.ebook', + 'b16' => 'image/vnd.pco.b16', + 'bat' => 'application/x-msdownload', + 'bcpio' => 'application/x-bcpio', + 'bdf' => 'application/x-font-bdf', + 'bdm' => 'application/vnd.syncml.dm+wbxml', + 'bdoc' => 'application/x-bdoc', + 'bed' => 'application/vnd.realvnc.bed', + 'bh2' => 'application/vnd.fujitsu.oasysprs', + 'bin' => 'application/octet-stream', + 'blb' => 'application/x-blorb', + 'blorb' => 'application/x-blorb', + 'bmi' => 'application/vnd.bmi', + 'bmml' => 'application/vnd.balsamiq.bmml+xml', + 'bmp' => 'image/bmp', + 'book' => 'application/vnd.framemaker', + 'box' => 'application/vnd.previewsystems.box', + 'boz' => 'application/x-bzip2', + 'bpk' => 'application/octet-stream', + 'bpmn' => 'application/octet-stream', + 'bsp' => 'model/vnd.valve.source.compiled-map', + 'btf' => 'image/prs.btif', + 'btif' => 'image/prs.btif', + 'buffer' => 'application/octet-stream', + 'bz' => 'application/x-bzip', + 'bz2' => 'application/x-bzip2', + 'c' => 'text/x-c', + 'c4d' => 'application/vnd.clonk.c4group', + 'c4f' => 'application/vnd.clonk.c4group', + 'c4g' => 'application/vnd.clonk.c4group', + 'c4p' => 'application/vnd.clonk.c4group', + 'c4u' => 'application/vnd.clonk.c4group', + 'c11amc' => 'application/vnd.cluetrust.cartomobile-config', + 'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg', + 'cab' => 'application/vnd.ms-cab-compressed', + 'caf' => 'audio/x-caf', + 'cap' => 'application/vnd.tcpdump.pcap', + 'car' => 'application/vnd.curl.car', + 'cat' => 'application/vnd.ms-pki.seccat', + 'cb7' => 'application/x-cbr', + 'cba' => 'application/x-cbr', + 'cbr' => 'application/x-cbr', + 'cbt' => 'application/x-cbr', + 'cbz' => 'application/x-cbr', + 'cc' => 'text/x-c', + 'cco' => 'application/x-cocoa', + 'cct' => 'application/x-director', + 'ccxml' => 'application/ccxml+xml', + 'cdbcmsg' => 'application/vnd.contact.cmsg', + 'cdf' => 'application/x-netcdf', + 'cdfx' => 'application/cdfx+xml', + 'cdkey' => 'application/vnd.mediastation.cdkey', + 'cdmia' => 'application/cdmi-capability', + 'cdmic' => 'application/cdmi-container', + 'cdmid' => 'application/cdmi-domain', + 'cdmio' => 'application/cdmi-object', + 'cdmiq' => 'application/cdmi-queue', + 'cdr' => 'application/cdr', + 'cdx' => 'chemical/x-cdx', + 'cdxml' => 'application/vnd.chemdraw+xml', + 'cdy' => 'application/vnd.cinderella', + 'cer' => 'application/pkix-cert', + 'cfs' => 'application/x-cfs-compressed', + 'cgm' => 'image/cgm', + 'chat' => 'application/x-chat', + 'chm' => 'application/vnd.ms-htmlhelp', + 'chrt' => 'application/vnd.kde.kchart', + 'cif' => 'chemical/x-cif', + 'cii' => 'application/vnd.anser-web-certificate-issue-initiation', + 'cil' => 'application/vnd.ms-artgalry', + 'cjs' => 'application/node', + 'cla' => 'application/vnd.claymore', + 'class' => 'application/octet-stream', + 'cld' => 'model/vnd.cld', + 'clkk' => 'application/vnd.crick.clicker.keyboard', + 'clkp' => 'application/vnd.crick.clicker.palette', + 'clkt' => 'application/vnd.crick.clicker.template', + 'clkw' => 'application/vnd.crick.clicker.wordbank', + 'clkx' => 'application/vnd.crick.clicker', + 'clp' => 'application/x-msclip', + 'cmc' => 'application/vnd.cosmocaller', + 'cmdf' => 'chemical/x-cmdf', + 'cml' => 'chemical/x-cml', + 'cmp' => 'application/vnd.yellowriver-custom-menu', + 'cmx' => 'image/x-cmx', + 'cod' => 'application/vnd.rim.cod', + 'coffee' => 'text/coffeescript', + 'com' => 'application/x-msdownload', + 'conf' => 'text/plain', + 'cpio' => 'application/x-cpio', + 'cpl' => 'application/cpl+xml', + 'cpp' => 'text/x-c', + 'cpt' => 'application/mac-compactpro', + 'crd' => 'application/x-mscardfile', + 'crl' => 'application/pkix-crl', + 'crt' => 'application/x-x509-ca-cert', + 'crx' => 'application/x-chrome-extension', + 'cryptonote' => 'application/vnd.rig.cryptonote', + 'csh' => 'application/x-csh', + 'csl' => 'application/vnd.citationstyles.style+xml', + 'csml' => 'chemical/x-csml', + 'csp' => 'application/vnd.commonspace', + 'csr' => 'application/octet-stream', + 'css' => 'text/css', + 'cst' => 'application/x-director', + 'csv' => 'text/csv', + 'cu' => 'application/cu-seeme', + 'curl' => 'text/vnd.curl', + 'cwl' => 'application/cwl', + 'cww' => 'application/prs.cww', + 'cxt' => 'application/x-director', + 'cxx' => 'text/x-c', + 'dae' => 'model/vnd.collada+xml', + 'daf' => 'application/vnd.mobius.daf', + 'dart' => 'application/vnd.dart', + 'dataless' => 'application/vnd.fdsn.seed', + 'davmount' => 'application/davmount+xml', + 'dbf' => 'application/vnd.dbf', + 'dbk' => 'application/docbook+xml', + 'dcr' => 'application/x-director', + 'dcurl' => 'text/vnd.curl.dcurl', + 'dd2' => 'application/vnd.oma.dd2+xml', + 'ddd' => 'application/vnd.fujixerox.ddd', + 'ddf' => 'application/vnd.syncml.dmddf+xml', + 'dds' => 'image/vnd.ms-dds', + 'deb' => 'application/x-debian-package', + 'def' => 'text/plain', + 'deploy' => 'application/octet-stream', + 'der' => 'application/x-x509-ca-cert', + 'dfac' => 'application/vnd.dreamfactory', + 'dgc' => 'application/x-dgc-compressed', + 'dib' => 'image/bmp', + 'dic' => 'text/x-c', + 'dir' => 'application/x-director', + 'dis' => 'application/vnd.mobius.dis', + 'disposition-notification' => 'message/disposition-notification', + 'dist' => 'application/octet-stream', + 'distz' => 'application/octet-stream', + 'djv' => 'image/vnd.djvu', + 'djvu' => 'image/vnd.djvu', + 'dll' => 'application/octet-stream', + 'dmg' => 'application/x-apple-diskimage', + 'dmn' => 'application/octet-stream', + 'dmp' => 'application/vnd.tcpdump.pcap', + 'dms' => 'application/octet-stream', + 'dna' => 'application/vnd.dna', + 'doc' => 'application/msword', + 'docm' => 'application/vnd.ms-word.template.macroEnabled.12', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dot' => 'application/msword', + 'dotm' => 'application/vnd.ms-word.template.macroEnabled.12', + 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 'dp' => 'application/vnd.osgi.dp', + 'dpg' => 'application/vnd.dpgraph', + 'dpx' => 'image/dpx', + 'dra' => 'audio/vnd.dra', + 'drle' => 'image/dicom-rle', + 'dsc' => 'text/prs.lines.tag', + 'dssc' => 'application/dssc+der', + 'dtb' => 'application/x-dtbook+xml', + 'dtd' => 'application/xml-dtd', + 'dts' => 'audio/vnd.dts', + 'dtshd' => 'audio/vnd.dts.hd', + 'dump' => 'application/octet-stream', + 'dvb' => 'video/vnd.dvb.file', + 'dvi' => 'application/x-dvi', + 'dwd' => 'application/atsc-dwd+xml', + 'dwf' => 'model/vnd.dwf', + 'dwg' => 'image/vnd.dwg', + 'dxf' => 'image/vnd.dxf', + 'dxp' => 'application/vnd.spotfire.dxp', + 'dxr' => 'application/x-director', + 'ear' => 'application/java-archive', + 'ecelp4800' => 'audio/vnd.nuera.ecelp4800', + 'ecelp7470' => 'audio/vnd.nuera.ecelp7470', + 'ecelp9600' => 'audio/vnd.nuera.ecelp9600', + 'ecma' => 'application/ecmascript', + 'edm' => 'application/vnd.novadigm.edm', + 'edx' => 'application/vnd.novadigm.edx', + 'efif' => 'application/vnd.picsel', + 'ei6' => 'application/vnd.pg.osasli', + 'elc' => 'application/octet-stream', + 'emf' => 'image/emf', + 'eml' => 'message/rfc822', + 'emma' => 'application/emma+xml', + 'emotionml' => 'application/emotionml+xml', + 'emz' => 'application/x-msmetafile', + 'eol' => 'audio/vnd.digital-winds', + 'eot' => 'application/vnd.ms-fontobject', + 'eps' => 'application/postscript', + 'epub' => 'application/epub+zip', + 'es3' => 'application/vnd.eszigno3+xml', + 'esa' => 'application/vnd.osgi.subsystem', + 'esf' => 'application/vnd.epson.esf', + 'et3' => 'application/vnd.eszigno3+xml', + 'etx' => 'text/x-setext', + 'eva' => 'application/x-eva', + 'evy' => 'application/x-envoy', + 'exe' => 'application/octet-stream', + 'exi' => 'application/exi', + 'exp' => 'application/express', + 'exr' => 'image/aces', + 'ext' => 'application/vnd.novadigm.ext', + 'ez' => 'application/andrew-inset', + 'ez2' => 'application/vnd.ezpix-album', + 'ez3' => 'application/vnd.ezpix-package', + 'f' => 'text/x-fortran', + 'f4v' => 'video/mp4', + 'f77' => 'text/x-fortran', + 'f90' => 'text/x-fortran', + 'fbs' => 'image/vnd.fastbidsheet', + 'fcdt' => 'application/vnd.adobe.formscentral.fcdt', + 'fcs' => 'application/vnd.isac.fcs', + 'fdf' => 'application/vnd.fdf', + 'fdt' => 'application/fdt+xml', + 'fe_launch' => 'application/vnd.denovo.fcselayout-link', + 'fg5' => 'application/vnd.fujitsu.oasysgp', + 'fgd' => 'application/x-director', + 'fh' => 'image/x-freehand', + 'fh4' => 'image/x-freehand', + 'fh5' => 'image/x-freehand', + 'fh7' => 'image/x-freehand', + 'fhc' => 'image/x-freehand', + 'fig' => 'application/x-xfig', + 'fits' => 'image/fits', + 'flac' => 'audio/x-flac', + 'fli' => 'video/x-fli', + 'flo' => 'application/vnd.micrografx.flo', + 'flv' => 'video/x-flv', + 'flw' => 'application/vnd.kde.kivio', + 'flx' => 'text/vnd.fmi.flexstor', + 'fly' => 'text/vnd.fly', + 'fm' => 'application/vnd.framemaker', + 'fnc' => 'application/vnd.frogans.fnc', + 'fo' => 'application/vnd.software602.filler.form+xml', + 'for' => 'text/x-fortran', + 'fpx' => 'image/vnd.fpx', + 'frame' => 'application/vnd.framemaker', + 'fsc' => 'application/vnd.fsc.weblaunch', + 'fst' => 'image/vnd.fst', + 'ftc' => 'application/vnd.fluxtime.clip', + 'fti' => 'application/vnd.anser-web-funds-transfer-initiation', + 'fvt' => 'video/vnd.fvt', + 'fxp' => 'application/vnd.adobe.fxp', + 'fxpl' => 'application/vnd.adobe.fxp', + 'fzs' => 'application/vnd.fuzzysheet', + 'g2w' => 'application/vnd.geoplan', + 'g3' => 'image/g3fax', + 'g3w' => 'application/vnd.geospace', + 'gac' => 'application/vnd.groove-account', + 'gam' => 'application/x-tads', + 'gbr' => 'application/rpki-ghostbusters', + 'gca' => 'application/x-gca-compressed', + 'gdl' => 'model/vnd.gdl', + 'gdoc' => 'application/vnd.google-apps.document', + 'ged' => 'text/vnd.familysearch.gedcom', + 'geo' => 'application/vnd.dynageo', + 'geojson' => 'application/geo+json', + 'gex' => 'application/vnd.geometry-explorer', + 'ggb' => 'application/vnd.geogebra.file', + 'ggt' => 'application/vnd.geogebra.tool', + 'ghf' => 'application/vnd.groove-help', + 'gif' => 'image/gif', + 'gim' => 'application/vnd.groove-identity-message', + 'glb' => 'model/gltf-binary', + 'gltf' => 'model/gltf+json', + 'gml' => 'application/gml+xml', + 'gmx' => 'application/vnd.gmx', + 'gnumeric' => 'application/x-gnumeric', + 'gpg' => 'application/gpg-keys', + 'gph' => 'application/vnd.flographit', + 'gpx' => 'application/gpx+xml', + 'gqf' => 'application/vnd.grafeq', + 'gqs' => 'application/vnd.grafeq', + 'gram' => 'application/srgs', + 'gramps' => 'application/x-gramps-xml', + 'gre' => 'application/vnd.geometry-explorer', + 'grv' => 'application/vnd.groove-injector', + 'grxml' => 'application/srgs+xml', + 'gsf' => 'application/x-font-ghostscript', + 'gsheet' => 'application/vnd.google-apps.spreadsheet', + 'gslides' => 'application/vnd.google-apps.presentation', + 'gtar' => 'application/x-gtar', + 'gtm' => 'application/vnd.groove-tool-message', + 'gtw' => 'model/vnd.gtw', + 'gv' => 'text/vnd.graphviz', + 'gxf' => 'application/gxf', + 'gxt' => 'application/vnd.geonext', + 'gz' => 'application/gzip', + 'gzip' => 'application/gzip', + 'h' => 'text/x-c', + 'h261' => 'video/h261', + 'h263' => 'video/h263', + 'h264' => 'video/h264', + 'hal' => 'application/vnd.hal+xml', + 'hbci' => 'application/vnd.hbci', + 'hbs' => 'text/x-handlebars-template', + 'hdd' => 'application/x-virtualbox-hdd', + 'hdf' => 'application/x-hdf', + 'heic' => 'image/heic', + 'heics' => 'image/heic-sequence', + 'heif' => 'image/heif', + 'heifs' => 'image/heif-sequence', + 'hej2' => 'image/hej2k', + 'held' => 'application/atsc-held+xml', + 'hh' => 'text/x-c', + 'hjson' => 'application/hjson', + 'hlp' => 'application/winhlp', + 'hpgl' => 'application/vnd.hp-hpgl', + 'hpid' => 'application/vnd.hp-hpid', + 'hps' => 'application/vnd.hp-hps', + 'hqx' => 'application/mac-binhex40', + 'hsj2' => 'image/hsj2', + 'htc' => 'text/x-component', + 'htke' => 'application/vnd.kenameaapp', + 'htm' => 'text/html', + 'html' => 'text/html', + 'hvd' => 'application/vnd.yamaha.hv-dic', + 'hvp' => 'application/vnd.yamaha.hv-voice', + 'hvs' => 'application/vnd.yamaha.hv-script', + 'i2g' => 'application/vnd.intergeo', + 'icc' => 'application/vnd.iccprofile', + 'ice' => 'x-conference/x-cooltalk', + 'icm' => 'application/vnd.iccprofile', + 'ico' => 'image/x-icon', + 'ics' => 'text/calendar', + 'ief' => 'image/ief', + 'ifb' => 'text/calendar', + 'ifm' => 'application/vnd.shana.informed.formdata', + 'iges' => 'model/iges', + 'igl' => 'application/vnd.igloader', + 'igm' => 'application/vnd.insors.igm', + 'igs' => 'model/iges', + 'igx' => 'application/vnd.micrografx.igx', + 'iif' => 'application/vnd.shana.informed.interchange', + 'img' => 'application/octet-stream', + 'imp' => 'application/vnd.accpac.simply.imp', + 'ims' => 'application/vnd.ms-ims', + 'in' => 'text/plain', + 'ini' => 'text/plain', + 'ink' => 'application/inkml+xml', + 'inkml' => 'application/inkml+xml', + 'install' => 'application/x-install-instructions', + 'iota' => 'application/vnd.astraea-software.iota', + 'ipfix' => 'application/ipfix', + 'ipk' => 'application/vnd.shana.informed.package', + 'irm' => 'application/vnd.ibm.rights-management', + 'irp' => 'application/vnd.irepository.package+xml', + 'iso' => 'application/x-iso9660-image', + 'itp' => 'application/vnd.shana.informed.formtemplate', + 'its' => 'application/its+xml', + 'ivp' => 'application/vnd.immervision-ivp', + 'ivu' => 'application/vnd.immervision-ivu', + 'jad' => 'text/vnd.sun.j2me.app-descriptor', + 'jade' => 'text/jade', + 'jam' => 'application/vnd.jam', + 'jar' => 'application/java-archive', + 'jardiff' => 'application/x-java-archive-diff', + 'java' => 'text/x-java-source', + 'jhc' => 'image/jphc', + 'jisp' => 'application/vnd.jisp', + 'jls' => 'image/jls', + 'jlt' => 'application/vnd.hp-jlyt', + 'jng' => 'image/x-jng', + 'jnlp' => 'application/x-java-jnlp-file', + 'joda' => 'application/vnd.joost.joda-archive', + 'jp2' => 'image/jp2', + 'jpe' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpf' => 'image/jpx', + 'jpg' => 'image/jpeg', + 'jpg2' => 'image/jp2', + 'jpgm' => 'video/jpm', + 'jpgv' => 'video/jpeg', + 'jph' => 'image/jph', + 'jpm' => 'video/jpm', + 'jpx' => 'image/jpx', + 'js' => 'application/javascript', + 'json' => 'application/json', + 'json5' => 'application/json5', + 'jsonld' => 'application/ld+json', + 'jsonml' => 'application/jsonml+json', + 'jsx' => 'text/jsx', + 'jt' => 'model/jt', + 'jxr' => 'image/jxr', + 'jxra' => 'image/jxra', + 'jxrs' => 'image/jxrs', + 'jxs' => 'image/jxs', + 'jxsc' => 'image/jxsc', + 'jxsi' => 'image/jxsi', + 'jxss' => 'image/jxss', + 'kar' => 'audio/midi', + 'karbon' => 'application/vnd.kde.karbon', + 'kdb' => 'application/octet-stream', + 'kdbx' => 'application/x-keepass2', + 'key' => 'application/x-iwork-keynote-sffkey', + 'kfo' => 'application/vnd.kde.kformula', + 'kia' => 'application/vnd.kidspiration', + 'kml' => 'application/vnd.google-earth.kml+xml', + 'kmz' => 'application/vnd.google-earth.kmz', + 'kne' => 'application/vnd.kinar', + 'knp' => 'application/vnd.kinar', + 'kon' => 'application/vnd.kde.kontour', + 'kpr' => 'application/vnd.kde.kpresenter', + 'kpt' => 'application/vnd.kde.kpresenter', + 'kpxx' => 'application/vnd.ds-keypoint', + 'ksp' => 'application/vnd.kde.kspread', + 'ktr' => 'application/vnd.kahootz', + 'ktx' => 'image/ktx', + 'ktx2' => 'image/ktx2', + 'ktz' => 'application/vnd.kahootz', + 'kwd' => 'application/vnd.kde.kword', + 'kwt' => 'application/vnd.kde.kword', + 'lasxml' => 'application/vnd.las.las+xml', + 'latex' => 'application/x-latex', + 'lbd' => 'application/vnd.llamagraphics.life-balance.desktop', + 'lbe' => 'application/vnd.llamagraphics.life-balance.exchange+xml', + 'les' => 'application/vnd.hhe.lesson-player', + 'less' => 'text/less', + 'lgr' => 'application/lgr+xml', + 'lha' => 'application/octet-stream', + 'link66' => 'application/vnd.route66.link66+xml', + 'list' => 'text/plain', + 'list3820' => 'application/vnd.ibm.modcap', + 'listafp' => 'application/vnd.ibm.modcap', + 'litcoffee' => 'text/coffeescript', + 'lnk' => 'application/x-ms-shortcut', + 'log' => 'text/plain', + 'lostxml' => 'application/lost+xml', + 'lrf' => 'application/octet-stream', + 'lrm' => 'application/vnd.ms-lrm', + 'ltf' => 'application/vnd.frogans.ltf', + 'lua' => 'text/x-lua', + 'luac' => 'application/x-lua-bytecode', + 'lvp' => 'audio/vnd.lucent.voice', + 'lwp' => 'application/vnd.lotus-wordpro', + 'lzh' => 'application/octet-stream', + 'm1v' => 'video/mpeg', + 'm2a' => 'audio/mpeg', + 'm2v' => 'video/mpeg', + 'm3a' => 'audio/mpeg', + 'm3u' => 'text/plain', + 'm3u8' => 'application/vnd.apple.mpegurl', + 'm4a' => 'audio/x-m4a', + 'm4p' => 'application/mp4', + 'm4s' => 'video/iso.segment', + 'm4u' => 'application/vnd.mpegurl', + 'm4v' => 'video/x-m4v', + 'm13' => 'application/x-msmediaview', + 'm14' => 'application/x-msmediaview', + 'm21' => 'application/mp21', + 'ma' => 'application/mathematica', + 'mads' => 'application/mads+xml', + 'maei' => 'application/mmt-aei+xml', + 'mag' => 'application/vnd.ecowin.chart', + 'maker' => 'application/vnd.framemaker', + 'man' => 'text/troff', + 'manifest' => 'text/cache-manifest', + 'map' => 'application/json', + 'mar' => 'application/octet-stream', + 'markdown' => 'text/markdown', + 'mathml' => 'application/mathml+xml', + 'mb' => 'application/mathematica', + 'mbk' => 'application/vnd.mobius.mbk', + 'mbox' => 'application/mbox', + 'mc1' => 'application/vnd.medcalcdata', + 'mcd' => 'application/vnd.mcd', + 'mcurl' => 'text/vnd.curl.mcurl', + 'md' => 'text/markdown', + 'mdb' => 'application/x-msaccess', + 'mdi' => 'image/vnd.ms-modi', + 'mdx' => 'text/mdx', + 'me' => 'text/troff', + 'mesh' => 'model/mesh', + 'meta4' => 'application/metalink4+xml', + 'metalink' => 'application/metalink+xml', + 'mets' => 'application/mets+xml', + 'mfm' => 'application/vnd.mfmp', + 'mft' => 'application/rpki-manifest', + 'mgp' => 'application/vnd.osgeo.mapguide.package', + 'mgz' => 'application/vnd.proteus.magazine', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mie' => 'application/x-mie', + 'mif' => 'application/vnd.mif', + 'mime' => 'message/rfc822', + 'mj2' => 'video/mj2', + 'mjp2' => 'video/mj2', + 'mjs' => 'text/javascript', + 'mk3d' => 'video/x-matroska', + 'mka' => 'audio/x-matroska', + 'mkd' => 'text/x-markdown', + 'mks' => 'video/x-matroska', + 'mkv' => 'video/x-matroska', + 'mlp' => 'application/vnd.dolby.mlp', + 'mmd' => 'application/vnd.chipnuts.karaoke-mmd', + 'mmf' => 'application/vnd.smaf', + 'mml' => 'text/mathml', + 'mmr' => 'image/vnd.fujixerox.edmics-mmr', + 'mng' => 'video/x-mng', + 'mny' => 'application/x-msmoney', + 'mobi' => 'application/x-mobipocket-ebook', + 'mods' => 'application/mods+xml', + 'mov' => 'video/quicktime', + 'movie' => 'video/x-sgi-movie', + 'mp2' => 'audio/mpeg', + 'mp2a' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'mp4' => 'video/mp4', + 'mp4a' => 'audio/mp4', + 'mp4s' => 'application/mp4', + 'mp4v' => 'video/mp4', + 'mp21' => 'application/mp21', + 'mpc' => 'application/vnd.mophun.certificate', + 'mpd' => 'application/dash+xml', + 'mpe' => 'video/mpeg', + 'mpeg' => 'video/mpeg', + 'mpf' => 'application/media-policy-dataset+xml', + 'mpg' => 'video/mpeg', + 'mpg4' => 'video/mp4', + 'mpga' => 'audio/mpeg', + 'mpkg' => 'application/vnd.apple.installer+xml', + 'mpm' => 'application/vnd.blueice.multipass', + 'mpn' => 'application/vnd.mophun.application', + 'mpp' => 'application/vnd.ms-project', + 'mpt' => 'application/vnd.ms-project', + 'mpy' => 'application/vnd.ibm.minipay', + 'mqy' => 'application/vnd.mobius.mqy', + 'mrc' => 'application/marc', + 'mrcx' => 'application/marcxml+xml', + 'ms' => 'text/troff', + 'mscml' => 'application/mediaservercontrol+xml', + 'mseed' => 'application/vnd.fdsn.mseed', + 'mseq' => 'application/vnd.mseq', + 'msf' => 'application/vnd.epson.msf', + 'msg' => 'application/vnd.ms-outlook', + 'msh' => 'model/mesh', + 'msi' => 'application/x-msdownload', + 'msix' => 'application/msix', + 'msixbundle' => 'application/msixbundle', + 'msl' => 'application/vnd.mobius.msl', + 'msm' => 'application/octet-stream', + 'msp' => 'application/octet-stream', + 'msty' => 'application/vnd.muvee.style', + 'mtl' => 'model/mtl', + 'mts' => 'model/vnd.mts', + 'mus' => 'application/vnd.musician', + 'musd' => 'application/mmt-usd+xml', + 'musicxml' => 'application/vnd.recordare.musicxml+xml', + 'mvb' => 'application/x-msmediaview', + 'mvt' => 'application/vnd.mapbox-vector-tile', + 'mwf' => 'application/vnd.mfer', + 'mxf' => 'application/mxf', + 'mxl' => 'application/vnd.recordare.musicxml', + 'mxmf' => 'audio/mobile-xmf', + 'mxml' => 'application/xv+xml', + 'mxs' => 'application/vnd.triscape.mxs', + 'mxu' => 'video/vnd.mpegurl', + 'n-gage' => 'application/vnd.nokia.n-gage.symbian.install', + 'n3' => 'text/n3', + 'nb' => 'application/mathematica', + 'nbp' => 'application/vnd.wolfram.player', + 'nc' => 'application/x-netcdf', + 'ncx' => 'application/x-dtbncx+xml', + 'nfo' => 'text/x-nfo', + 'ngdat' => 'application/vnd.nokia.n-gage.data', + 'nitf' => 'application/vnd.nitf', + 'nlu' => 'application/vnd.neurolanguage.nlu', + 'nml' => 'application/vnd.enliven', + 'nnd' => 'application/vnd.noblenet-directory', + 'nns' => 'application/vnd.noblenet-sealer', + 'nnw' => 'application/vnd.noblenet-web', + 'npx' => 'image/vnd.net-fpx', + 'nq' => 'application/n-quads', + 'nsc' => 'application/x-conference', + 'nsf' => 'application/vnd.lotus-notes', + 'nt' => 'application/n-triples', + 'ntf' => 'application/vnd.nitf', + 'numbers' => 'application/x-iwork-numbers-sffnumbers', + 'nzb' => 'application/x-nzb', + 'oa2' => 'application/vnd.fujitsu.oasys2', + 'oa3' => 'application/vnd.fujitsu.oasys3', + 'oas' => 'application/vnd.fujitsu.oasys', + 'obd' => 'application/x-msbinder', + 'obgx' => 'application/vnd.openblox.game+xml', + 'obj' => 'model/obj', + 'oda' => 'application/oda', + 'odb' => 'application/vnd.oasis.opendocument.database', + 'odc' => 'application/vnd.oasis.opendocument.chart', + 'odf' => 'application/vnd.oasis.opendocument.formula', + 'odft' => 'application/vnd.oasis.opendocument.formula-template', + 'odg' => 'application/vnd.oasis.opendocument.graphics', + 'odi' => 'application/vnd.oasis.opendocument.image', + 'odm' => 'application/vnd.oasis.opendocument.text-master', + 'odp' => 'application/vnd.oasis.opendocument.presentation', + 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', + 'odt' => 'application/vnd.oasis.opendocument.text', + 'oga' => 'audio/ogg', + 'ogex' => 'model/vnd.opengex', + 'ogg' => 'audio/ogg', + 'ogv' => 'video/ogg', + 'ogx' => 'application/ogg', + 'omdoc' => 'application/omdoc+xml', + 'onepkg' => 'application/onenote', + 'onetmp' => 'application/onenote', + 'onetoc' => 'application/onenote', + 'onetoc2' => 'application/onenote', + 'opf' => 'application/oebps-package+xml', + 'opml' => 'text/x-opml', + 'oprc' => 'application/vnd.palm', + 'opus' => 'audio/ogg', + 'org' => 'text/x-org', + 'osf' => 'application/vnd.yamaha.openscoreformat', + 'osfpvg' => 'application/vnd.yamaha.openscoreformat.osfpvg+xml', + 'osm' => 'application/vnd.openstreetmap.data+xml', + 'otc' => 'application/vnd.oasis.opendocument.chart-template', + 'otf' => 'font/otf', + 'otg' => 'application/vnd.oasis.opendocument.graphics-template', + 'oth' => 'application/vnd.oasis.opendocument.text-web', + 'oti' => 'application/vnd.oasis.opendocument.image-template', + 'otp' => 'application/vnd.oasis.opendocument.presentation-template', + 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', + 'ott' => 'application/vnd.oasis.opendocument.text-template', + 'ova' => 'application/x-virtualbox-ova', + 'ovf' => 'application/x-virtualbox-ovf', + 'owl' => 'application/rdf+xml', + 'oxps' => 'application/oxps', + 'oxt' => 'application/vnd.openofficeorg.extension', + 'p' => 'text/x-pascal', + 'p7a' => 'application/x-pkcs7-signature', + 'p7b' => 'application/x-pkcs7-certificates', + 'p7c' => 'application/pkcs7-mime', + 'p7m' => 'application/pkcs7-mime', + 'p7r' => 'application/x-pkcs7-certreqresp', + 'p7s' => 'application/pkcs7-signature', + 'p8' => 'application/pkcs8', + 'p10' => 'application/x-pkcs10', + 'p12' => 'application/x-pkcs12', + 'pac' => 'application/x-ns-proxy-autoconfig', + 'pages' => 'application/x-iwork-pages-sffpages', + 'pas' => 'text/x-pascal', + 'paw' => 'application/vnd.pawaafile', + 'pbd' => 'application/vnd.powerbuilder6', + 'pbm' => 'image/x-portable-bitmap', + 'pcap' => 'application/vnd.tcpdump.pcap', + 'pcf' => 'application/x-font-pcf', + 'pcl' => 'application/vnd.hp-pcl', + 'pclxl' => 'application/vnd.hp-pclxl', + 'pct' => 'image/x-pict', + 'pcurl' => 'application/vnd.curl.pcurl', + 'pcx' => 'image/x-pcx', + 'pdb' => 'application/x-pilot', + 'pde' => 'text/x-processing', + 'pdf' => 'application/pdf', + 'pem' => 'application/x-x509-user-cert', + 'pfa' => 'application/x-font-type1', + 'pfb' => 'application/x-font-type1', + 'pfm' => 'application/x-font-type1', + 'pfr' => 'application/font-tdpfr', + 'pfx' => 'application/x-pkcs12', + 'pgm' => 'image/x-portable-graymap', + 'pgn' => 'application/x-chess-pgn', + 'pgp' => 'application/pgp', + 'phar' => 'application/octet-stream', + 'php' => 'application/x-httpd-php', + 'php3' => 'application/x-httpd-php', + 'php4' => 'application/x-httpd-php', + 'phps' => 'application/x-httpd-php-source', + 'phtml' => 'application/x-httpd-php', + 'pic' => 'image/x-pict', + 'pkg' => 'application/octet-stream', + 'pki' => 'application/pkixcmp', + 'pkipath' => 'application/pkix-pkipath', + 'pkpass' => 'application/vnd.apple.pkpass', + 'pl' => 'application/x-perl', + 'plb' => 'application/vnd.3gpp.pic-bw-large', + 'plc' => 'application/vnd.mobius.plc', + 'plf' => 'application/vnd.pocketlearn', + 'pls' => 'application/pls+xml', + 'pm' => 'application/x-perl', + 'pml' => 'application/vnd.ctc-posml', + 'png' => 'image/png', + 'pnm' => 'image/x-portable-anymap', + 'portpkg' => 'application/vnd.macports.portpkg', + 'pot' => 'application/vnd.ms-powerpoint', + 'potm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', + 'ppa' => 'application/vnd.ms-powerpoint', + 'ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12', + 'ppd' => 'application/vnd.cups-ppd', + 'ppm' => 'image/x-portable-pixmap', + 'pps' => 'application/vnd.ms-powerpoint', + 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', + 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 'ppt' => 'application/powerpoint', + 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'pqa' => 'application/vnd.palm', + 'prc' => 'model/prc', + 'pre' => 'application/vnd.lotus-freelance', + 'prf' => 'application/pics-rules', + 'provx' => 'application/provenance+xml', + 'ps' => 'application/postscript', + 'psb' => 'application/vnd.3gpp.pic-bw-small', + 'psd' => 'application/x-photoshop', + 'psf' => 'application/x-font-linux-psf', + 'pskcxml' => 'application/pskc+xml', + 'pti' => 'image/prs.pti', + 'ptid' => 'application/vnd.pvi.ptid1', + 'pub' => 'application/x-mspublisher', + 'pvb' => 'application/vnd.3gpp.pic-bw-var', + 'pwn' => 'application/vnd.3m.post-it-notes', + 'pya' => 'audio/vnd.ms-playready.media.pya', + 'pyo' => 'model/vnd.pytha.pyox', + 'pyox' => 'model/vnd.pytha.pyox', + 'pyv' => 'video/vnd.ms-playready.media.pyv', + 'qam' => 'application/vnd.epson.quickanime', + 'qbo' => 'application/vnd.intu.qbo', + 'qfx' => 'application/vnd.intu.qfx', + 'qps' => 'application/vnd.publishare-delta-tree', + 'qt' => 'video/quicktime', + 'qwd' => 'application/vnd.quark.quarkxpress', + 'qwt' => 'application/vnd.quark.quarkxpress', + 'qxb' => 'application/vnd.quark.quarkxpress', + 'qxd' => 'application/vnd.quark.quarkxpress', + 'qxl' => 'application/vnd.quark.quarkxpress', + 'qxt' => 'application/vnd.quark.quarkxpress', + 'ra' => 'audio/x-realaudio', + 'ram' => 'audio/x-pn-realaudio', + 'raml' => 'application/raml+yaml', + 'rapd' => 'application/route-apd+xml', + 'rar' => 'application/x-rar', + 'ras' => 'image/x-cmu-raster', + 'rcprofile' => 'application/vnd.ipunplugged.rcprofile', + 'rdf' => 'application/rdf+xml', + 'rdz' => 'application/vnd.data-vision.rdz', + 'relo' => 'application/p2p-overlay+xml', + 'rep' => 'application/vnd.businessobjects', + 'res' => 'application/x-dtbresource+xml', + 'rgb' => 'image/x-rgb', + 'rif' => 'application/reginfo+xml', + 'rip' => 'audio/vnd.rip', + 'ris' => 'application/x-research-info-systems', + 'rl' => 'application/resource-lists+xml', + 'rlc' => 'image/vnd.fujixerox.edmics-rlc', + 'rld' => 'application/resource-lists-diff+xml', + 'rm' => 'audio/x-pn-realaudio', + 'rmi' => 'audio/midi', + 'rmp' => 'audio/x-pn-realaudio-plugin', + 'rms' => 'application/vnd.jcp.javame.midlet-rms', + 'rmvb' => 'application/vnd.rn-realmedia-vbr', + 'rnc' => 'application/relax-ng-compact-syntax', + 'rng' => 'application/xml', + 'roa' => 'application/rpki-roa', + 'roff' => 'text/troff', + 'rp9' => 'application/vnd.cloanto.rp9', + 'rpm' => 'audio/x-pn-realaudio-plugin', + 'rpss' => 'application/vnd.nokia.radio-presets', + 'rpst' => 'application/vnd.nokia.radio-preset', + 'rq' => 'application/sparql-query', + 'rs' => 'application/rls-services+xml', + 'rsa' => 'application/x-pkcs7', + 'rsat' => 'application/atsc-rsat+xml', + 'rsd' => 'application/rsd+xml', + 'rsheet' => 'application/urc-ressheet+xml', + 'rss' => 'application/rss+xml', + 'rtf' => 'text/rtf', + 'rtx' => 'text/richtext', + 'run' => 'application/x-makeself', + 'rusd' => 'application/route-usd+xml', + 'rv' => 'video/vnd.rn-realvideo', + 's' => 'text/x-asm', + 's3m' => 'audio/s3m', + 'saf' => 'application/vnd.yamaha.smaf-audio', + 'sass' => 'text/x-sass', + 'sbml' => 'application/sbml+xml', + 'sc' => 'application/vnd.ibm.secure-container', + 'scd' => 'application/x-msschedule', + 'scm' => 'application/vnd.lotus-screencam', + 'scq' => 'application/scvp-cv-request', + 'scs' => 'application/scvp-cv-response', + 'scss' => 'text/x-scss', + 'scurl' => 'text/vnd.curl.scurl', + 'sda' => 'application/vnd.stardivision.draw', + 'sdc' => 'application/vnd.stardivision.calc', + 'sdd' => 'application/vnd.stardivision.impress', + 'sdkd' => 'application/vnd.solent.sdkm+xml', + 'sdkm' => 'application/vnd.solent.sdkm+xml', + 'sdp' => 'application/sdp', + 'sdw' => 'application/vnd.stardivision.writer', + 'sea' => 'application/octet-stream', + 'see' => 'application/vnd.seemail', + 'seed' => 'application/vnd.fdsn.seed', + 'sema' => 'application/vnd.sema', + 'semd' => 'application/vnd.semd', + 'semf' => 'application/vnd.semf', + 'senmlx' => 'application/senml+xml', + 'sensmlx' => 'application/sensml+xml', + 'ser' => 'application/java-serialized-object', + 'setpay' => 'application/set-payment-initiation', + 'setreg' => 'application/set-registration-initiation', + 'sfd-hdstx' => 'application/vnd.hydrostatix.sof-data', + 'sfs' => 'application/vnd.spotfire.sfs', + 'sfv' => 'text/x-sfv', + 'sgi' => 'image/sgi', + 'sgl' => 'application/vnd.stardivision.writer-global', + 'sgm' => 'text/sgml', + 'sgml' => 'text/sgml', + 'sh' => 'application/x-sh', + 'shar' => 'application/x-shar', + 'shex' => 'text/shex', + 'shf' => 'application/shf+xml', + 'shtml' => 'text/html', + 'sid' => 'image/x-mrsid-image', + 'sieve' => 'application/sieve', + 'sig' => 'application/pgp-signature', + 'sil' => 'audio/silk', + 'silo' => 'model/mesh', + 'sis' => 'application/vnd.symbian.install', + 'sisx' => 'application/vnd.symbian.install', + 'sit' => 'application/x-stuffit', + 'sitx' => 'application/x-stuffitx', + 'siv' => 'application/sieve', + 'skd' => 'application/vnd.koan', + 'skm' => 'application/vnd.koan', + 'skp' => 'application/vnd.koan', + 'skt' => 'application/vnd.koan', + 'sldm' => 'application/vnd.ms-powerpoint.slide.macroenabled.12', + 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', + 'slim' => 'text/slim', + 'slm' => 'text/slim', + 'sls' => 'application/route-s-tsid+xml', + 'slt' => 'application/vnd.epson.salt', + 'sm' => 'application/vnd.stepmania.stepchart', + 'smf' => 'application/vnd.stardivision.math', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + 'smv' => 'video/x-smv', + 'smzip' => 'application/vnd.stepmania.package', + 'snd' => 'audio/basic', + 'snf' => 'application/x-font-snf', + 'so' => 'application/octet-stream', + 'spc' => 'application/x-pkcs7-certificates', + 'spdx' => 'text/spdx', + 'spf' => 'application/vnd.yamaha.smaf-phrase', + 'spl' => 'application/x-futuresplash', + 'spot' => 'text/vnd.in3d.spot', + 'spp' => 'application/scvp-vp-response', + 'spq' => 'application/scvp-vp-request', + 'spx' => 'audio/ogg', + 'sql' => 'application/x-sql', + 'src' => 'application/x-wais-source', + 'srt' => 'application/x-subrip', + 'sru' => 'application/sru+xml', + 'srx' => 'application/sparql-results+xml', + 'ssdl' => 'application/ssdl+xml', + 'sse' => 'application/vnd.kodak-descriptor', + 'ssf' => 'application/vnd.epson.ssf', + 'ssml' => 'application/ssml+xml', + 'sst' => 'application/octet-stream', + 'st' => 'application/vnd.sailingtracker.track', + 'stc' => 'application/vnd.sun.xml.calc.template', + 'std' => 'application/vnd.sun.xml.draw.template', + 'step' => 'application/STEP', + 'stf' => 'application/vnd.wt.stf', + 'sti' => 'application/vnd.sun.xml.impress.template', + 'stk' => 'application/hyperstudio', + 'stl' => 'model/stl', + 'stp' => 'application/STEP', + 'stpx' => 'model/step+xml', + 'stpxz' => 'model/step-xml+zip', + 'stpz' => 'model/step+zip', + 'str' => 'application/vnd.pg.format', + 'stw' => 'application/vnd.sun.xml.writer.template', + 'styl' => 'text/stylus', + 'stylus' => 'text/stylus', + 'sub' => 'text/vnd.dvb.subtitle', + 'sus' => 'application/vnd.sus-calendar', + 'susp' => 'application/vnd.sus-calendar', + 'sv4cpio' => 'application/x-sv4cpio', + 'sv4crc' => 'application/x-sv4crc', + 'svc' => 'application/vnd.dvb.service', + 'svd' => 'application/vnd.svd', + 'svg' => 'image/svg+xml', + 'svgz' => 'image/svg+xml', + 'swa' => 'application/x-director', + 'swf' => 'application/x-shockwave-flash', + 'swi' => 'application/vnd.aristanetworks.swi', + 'swidtag' => 'application/swid+xml', + 'sxc' => 'application/vnd.sun.xml.calc', + 'sxd' => 'application/vnd.sun.xml.draw', + 'sxg' => 'application/vnd.sun.xml.writer.global', + 'sxi' => 'application/vnd.sun.xml.impress', + 'sxm' => 'application/vnd.sun.xml.math', + 'sxw' => 'application/vnd.sun.xml.writer', + 't' => 'text/troff', + 't3' => 'application/x-t3vm-image', + 't38' => 'image/t38', + 'taglet' => 'application/vnd.mynfc', + 'tao' => 'application/vnd.tao.intent-module-archive', + 'tap' => 'image/vnd.tencent.tap', + 'tar' => 'application/x-tar', + 'tcap' => 'application/vnd.3gpp2.tcap', + 'tcl' => 'application/x-tcl', + 'td' => 'application/urc-targetdesc+xml', + 'teacher' => 'application/vnd.smart.teacher', + 'tei' => 'application/tei+xml', + 'teicorpus' => 'application/tei+xml', + 'tex' => 'application/x-tex', + 'texi' => 'application/x-texinfo', + 'texinfo' => 'application/x-texinfo', + 'text' => 'text/plain', + 'tfi' => 'application/thraud+xml', + 'tfm' => 'application/x-tex-tfm', + 'tfx' => 'image/tiff-fx', + 'tga' => 'image/x-tga', + 'tgz' => 'application/x-tar', + 'thmx' => 'application/vnd.ms-officetheme', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'tk' => 'application/x-tcl', + 'tmo' => 'application/vnd.tmobile-livetv', + 'toml' => 'application/toml', + 'torrent' => 'application/x-bittorrent', + 'tpl' => 'application/vnd.groove-tool-template', + 'tpt' => 'application/vnd.trid.tpt', + 'tr' => 'text/troff', + 'tra' => 'application/vnd.trueapp', + 'trig' => 'application/trig', + 'trm' => 'application/x-msterminal', + 'ts' => 'video/mp2t', + 'tsd' => 'application/timestamped-data', + 'tsv' => 'text/tab-separated-values', + 'ttc' => 'font/collection', + 'ttf' => 'font/ttf', + 'ttl' => 'text/turtle', + 'ttml' => 'application/ttml+xml', + 'twd' => 'application/vnd.simtech-mindmapper', + 'twds' => 'application/vnd.simtech-mindmapper', + 'txd' => 'application/vnd.genomatix.tuxedo', + 'txf' => 'application/vnd.mobius.txf', + 'txt' => 'text/plain', + 'u3d' => 'model/u3d', + 'u8dsn' => 'message/global-delivery-status', + 'u8hdr' => 'message/global-headers', + 'u8mdn' => 'message/global-disposition-notification', + 'u8msg' => 'message/global', + 'u32' => 'application/x-authorware-bin', + 'ubj' => 'application/ubjson', + 'udeb' => 'application/x-debian-package', + 'ufd' => 'application/vnd.ufdl', + 'ufdl' => 'application/vnd.ufdl', + 'ulx' => 'application/x-glulx', + 'umj' => 'application/vnd.umajin', + 'unityweb' => 'application/vnd.unity', + 'uo' => 'application/vnd.uoml+xml', + 'uoml' => 'application/vnd.uoml+xml', + 'uri' => 'text/uri-list', + 'uris' => 'text/uri-list', + 'urls' => 'text/uri-list', + 'usda' => 'model/vnd.usda', + 'usdz' => 'model/vnd.usdz+zip', + 'ustar' => 'application/x-ustar', + 'utz' => 'application/vnd.uiq.theme', + 'uu' => 'text/x-uuencode', + 'uva' => 'audio/vnd.dece.audio', + 'uvd' => 'application/vnd.dece.data', + 'uvf' => 'application/vnd.dece.data', + 'uvg' => 'image/vnd.dece.graphic', + 'uvh' => 'video/vnd.dece.hd', + 'uvi' => 'image/vnd.dece.graphic', + 'uvm' => 'video/vnd.dece.mobile', + 'uvp' => 'video/vnd.dece.pd', + 'uvs' => 'video/vnd.dece.sd', + 'uvt' => 'application/vnd.dece.ttml+xml', + 'uvu' => 'video/vnd.uvvu.mp4', + 'uvv' => 'video/vnd.dece.video', + 'uvva' => 'audio/vnd.dece.audio', + 'uvvd' => 'application/vnd.dece.data', + 'uvvf' => 'application/vnd.dece.data', + 'uvvg' => 'image/vnd.dece.graphic', + 'uvvh' => 'video/vnd.dece.hd', + 'uvvi' => 'image/vnd.dece.graphic', + 'uvvm' => 'video/vnd.dece.mobile', + 'uvvp' => 'video/vnd.dece.pd', + 'uvvs' => 'video/vnd.dece.sd', + 'uvvt' => 'application/vnd.dece.ttml+xml', + 'uvvu' => 'video/vnd.uvvu.mp4', + 'uvvv' => 'video/vnd.dece.video', + 'uvvx' => 'application/vnd.dece.unspecified', + 'uvvz' => 'application/vnd.dece.zip', + 'uvx' => 'application/vnd.dece.unspecified', + 'uvz' => 'application/vnd.dece.zip', + 'vbox' => 'application/x-virtualbox-vbox', + 'vbox-extpack' => 'application/x-virtualbox-vbox-extpack', + 'vcard' => 'text/vcard', + 'vcd' => 'application/x-cdlink', + 'vcf' => 'text/x-vcard', + 'vcg' => 'application/vnd.groove-vcard', + 'vcs' => 'text/x-vcalendar', + 'vcx' => 'application/vnd.vcx', + 'vdi' => 'application/x-virtualbox-vdi', + 'vds' => 'model/vnd.sap.vds', + 'vhd' => 'application/x-virtualbox-vhd', + 'vis' => 'application/vnd.visionary', + 'viv' => 'video/vnd.vivo', + 'vlc' => 'application/videolan', + 'vmdk' => 'application/x-virtualbox-vmdk', + 'vob' => 'video/x-ms-vob', + 'vor' => 'application/vnd.stardivision.writer', + 'vox' => 'application/x-authorware-bin', + 'vrml' => 'model/vrml', + 'vsd' => 'application/vnd.visio', + 'vsf' => 'application/vnd.vsf', + 'vss' => 'application/vnd.visio', + 'vst' => 'application/vnd.visio', + 'vsw' => 'application/vnd.visio', + 'vtf' => 'image/vnd.valve.source.texture', + 'vtt' => 'text/vtt', + 'vtu' => 'model/vnd.vtu', + 'vxml' => 'application/voicexml+xml', + 'w3d' => 'application/x-director', + 'wad' => 'application/x-doom', + 'wadl' => 'application/vnd.sun.wadl+xml', + 'war' => 'application/java-archive', + 'wasm' => 'application/wasm', + 'wav' => 'audio/x-wav', + 'wax' => 'audio/x-ms-wax', + 'wbmp' => 'image/vnd.wap.wbmp', + 'wbs' => 'application/vnd.criticaltools.wbs+xml', + 'wbxml' => 'application/wbxml', + 'wcm' => 'application/vnd.ms-works', + 'wdb' => 'application/vnd.ms-works', + 'wdp' => 'image/vnd.ms-photo', + 'weba' => 'audio/webm', + 'webapp' => 'application/x-web-app-manifest+json', + 'webm' => 'video/webm', + 'webmanifest' => 'application/manifest+json', + 'webp' => 'image/webp', + 'wg' => 'application/vnd.pmi.widget', + 'wgsl' => 'text/wgsl', + 'wgt' => 'application/widget', + 'wif' => 'application/watcherinfo+xml', + 'wks' => 'application/vnd.ms-works', + 'wm' => 'video/x-ms-wm', + 'wma' => 'audio/x-ms-wma', + 'wmd' => 'application/x-ms-wmd', + 'wmf' => 'image/wmf', + 'wml' => 'text/vnd.wap.wml', + 'wmlc' => 'application/wmlc', + 'wmls' => 'text/vnd.wap.wmlscript', + 'wmlsc' => 'application/vnd.wap.wmlscriptc', + 'wmv' => 'video/x-ms-wmv', + 'wmx' => 'video/x-ms-wmx', + 'wmz' => 'application/x-msmetafile', + 'woff' => 'font/woff', + 'woff2' => 'font/woff2', + 'word' => 'application/msword', + 'wpd' => 'application/vnd.wordperfect', + 'wpl' => 'application/vnd.ms-wpl', + 'wps' => 'application/vnd.ms-works', + 'wqd' => 'application/vnd.wqd', + 'wri' => 'application/x-mswrite', + 'wrl' => 'model/vrml', + 'wsc' => 'message/vnd.wfa.wsc', + 'wsdl' => 'application/wsdl+xml', + 'wspolicy' => 'application/wspolicy+xml', + 'wtb' => 'application/vnd.webturbo', + 'wvx' => 'video/x-ms-wvx', + 'x3d' => 'model/x3d+xml', + 'x3db' => 'model/x3d+fastinfoset', + 'x3dbz' => 'model/x3d+binary', + 'x3dv' => 'model/x3d-vrml', + 'x3dvz' => 'model/x3d+vrml', + 'x3dz' => 'model/x3d+xml', + 'x32' => 'application/x-authorware-bin', + 'x_b' => 'model/vnd.parasolid.transmit.binary', + 'x_t' => 'model/vnd.parasolid.transmit.text', + 'xaml' => 'application/xaml+xml', + 'xap' => 'application/x-silverlight-app', + 'xar' => 'application/vnd.xara', + 'xav' => 'application/xcap-att+xml', + 'xbap' => 'application/x-ms-xbap', + 'xbd' => 'application/vnd.fujixerox.docuworks.binder', + 'xbm' => 'image/x-xbitmap', + 'xca' => 'application/xcap-caps+xml', + 'xcs' => 'application/calendar+xml', + 'xdf' => 'application/xcap-diff+xml', + 'xdm' => 'application/vnd.syncml.dm+xml', + 'xdp' => 'application/vnd.adobe.xdp+xml', + 'xdssc' => 'application/dssc+xml', + 'xdw' => 'application/vnd.fujixerox.docuworks', + 'xel' => 'application/xcap-el+xml', + 'xenc' => 'application/xenc+xml', + 'xer' => 'application/patch-ops-error+xml', + 'xfdf' => 'application/xfdf', + 'xfdl' => 'application/vnd.xfdl', + 'xht' => 'application/xhtml+xml', + 'xhtm' => 'application/vnd.pwg-xhtml-print+xml', + 'xhtml' => 'application/xhtml+xml', + 'xhvml' => 'application/xv+xml', + 'xif' => 'image/vnd.xiff', + 'xl' => 'application/excel', + 'xla' => 'application/vnd.ms-excel', + 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12', + 'xlc' => 'application/vnd.ms-excel', + 'xlf' => 'application/xliff+xml', + 'xlm' => 'application/vnd.ms-excel', + 'xls' => 'application/vnd.ms-excel', + 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', + 'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xlt' => 'application/vnd.ms-excel', + 'xltm' => 'application/vnd.ms-excel.template.macroEnabled.12', + 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 'xlw' => 'application/vnd.ms-excel', + 'xm' => 'audio/xm', + 'xml' => 'application/xml', + 'xns' => 'application/xcap-ns+xml', + 'xo' => 'application/vnd.olpc-sugar', + 'xop' => 'application/xop+xml', + 'xpi' => 'application/x-xpinstall', + 'xpl' => 'application/xproc+xml', + 'xpm' => 'image/x-xpixmap', + 'xpr' => 'application/vnd.is-xpr', + 'xps' => 'application/vnd.ms-xpsdocument', + 'xpw' => 'application/vnd.intercon.formnet', + 'xpx' => 'application/vnd.intercon.formnet', + 'xsd' => 'application/xml', + 'xsf' => 'application/prs.xsf+xml', + 'xsl' => 'application/xml', + 'xslt' => 'application/xslt+xml', + 'xsm' => 'application/vnd.syncml+xml', + 'xspf' => 'application/xspf+xml', + 'xul' => 'application/vnd.mozilla.xul+xml', + 'xvm' => 'application/xv+xml', + 'xvml' => 'application/xv+xml', + 'xwd' => 'image/x-xwindowdump', + 'xyz' => 'chemical/x-xyz', + 'xz' => 'application/x-xz', + 'yaml' => 'text/yaml', + 'yang' => 'application/yang', + 'yin' => 'application/yin+xml', + 'yml' => 'text/yaml', + 'ymp' => 'text/x-suse-ymp', + 'z' => 'application/x-compress', + 'z1' => 'application/x-zmachine', + 'z2' => 'application/x-zmachine', + 'z3' => 'application/x-zmachine', + 'z4' => 'application/x-zmachine', + 'z5' => 'application/x-zmachine', + 'z6' => 'application/x-zmachine', + 'z7' => 'application/x-zmachine', + 'z8' => 'application/x-zmachine', + 'zaz' => 'application/vnd.zzazz.deck+xml', + 'zip' => 'application/zip', + 'zir' => 'application/vnd.zul', + 'zirz' => 'application/vnd.zul', + 'zmm' => 'application/vnd.handheld-entertainment+xml', + 'zsh' => 'text/x-scriptzsh', + ]; + + /** + * Determines the mimetype of a file by looking at its extension. + * + * @see https://raw.githubusercontent.com/jshttp/mime-db/master/db.json + */ + public static function fromFilename(string $filename): ?string + { + return self::fromExtension(pathinfo($filename, PATHINFO_EXTENSION)); + } + + /** + * Maps a file extensions to a mimetype. + * + * @see https://raw.githubusercontent.com/jshttp/mime-db/master/db.json + */ + public static function fromExtension(string $extension): ?string + { + return self::MIME_TYPES[strtolower($extension)] ?? null; + } +} diff --git a/vendor/guzzlehttp/psr7/src/MultipartStream.php b/vendor/guzzlehttp/psr7/src/MultipartStream.php new file mode 100644 index 00000000..d23fba8a --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/MultipartStream.php @@ -0,0 +1,165 @@ +boundary = $boundary ?: bin2hex(random_bytes(20)); + $this->stream = $this->createStream($elements); + } + + public function getBoundary(): string + { + return $this->boundary; + } + + public function isWritable(): bool + { + return false; + } + + /** + * Get the headers needed before transferring the content of a POST file + * + * @param string[] $headers + */ + private function getHeaders(array $headers): string + { + $str = ''; + foreach ($headers as $key => $value) { + $str .= "{$key}: {$value}\r\n"; + } + + return "--{$this->boundary}\r\n".trim($str)."\r\n\r\n"; + } + + /** + * Create the aggregate stream that will be used to upload the POST data + */ + protected function createStream(array $elements = []): StreamInterface + { + $stream = new AppendStream(); + + foreach ($elements as $element) { + if (!is_array($element)) { + throw new \UnexpectedValueException('An array is expected'); + } + $this->addElement($stream, $element); + } + + // Add the trailing boundary with CRLF + $stream->addStream(Utils::streamFor("--{$this->boundary}--\r\n")); + + return $stream; + } + + private function addElement(AppendStream $stream, array $element): void + { + foreach (['contents', 'name'] as $key) { + if (!array_key_exists($key, $element)) { + throw new \InvalidArgumentException("A '{$key}' key is required"); + } + } + + $element['contents'] = Utils::streamFor($element['contents']); + + if (empty($element['filename'])) { + $uri = $element['contents']->getMetadata('uri'); + if ($uri && \is_string($uri) && \substr($uri, 0, 6) !== 'php://' && \substr($uri, 0, 7) !== 'data://') { + $element['filename'] = $uri; + } + } + + [$body, $headers] = $this->createElement( + $element['name'], + $element['contents'], + $element['filename'] ?? null, + $element['headers'] ?? [] + ); + + $stream->addStream(Utils::streamFor($this->getHeaders($headers))); + $stream->addStream($body); + $stream->addStream(Utils::streamFor("\r\n")); + } + + /** + * @param string[] $headers + * + * @return array{0: StreamInterface, 1: string[]} + */ + private function createElement(string $name, StreamInterface $stream, ?string $filename, array $headers): array + { + // Set a default content-disposition header if one was no provided + $disposition = self::getHeader($headers, 'content-disposition'); + if (!$disposition) { + $headers['Content-Disposition'] = ($filename === '0' || $filename) + ? sprintf( + 'form-data; name="%s"; filename="%s"', + $name, + basename($filename) + ) + : "form-data; name=\"{$name}\""; + } + + // Set a default content-length header if one was no provided + $length = self::getHeader($headers, 'content-length'); + if (!$length) { + if ($length = $stream->getSize()) { + $headers['Content-Length'] = (string) $length; + } + } + + // Set a default Content-Type if one was not supplied + $type = self::getHeader($headers, 'content-type'); + if (!$type && ($filename === '0' || $filename)) { + $headers['Content-Type'] = MimeType::fromFilename($filename) ?? 'application/octet-stream'; + } + + return [$stream, $headers]; + } + + /** + * @param string[] $headers + */ + private static function getHeader(array $headers, string $key): ?string + { + $lowercaseHeader = strtolower($key); + foreach ($headers as $k => $v) { + if (strtolower((string) $k) === $lowercaseHeader) { + return $v; + } + } + + return null; + } +} diff --git a/vendor/guzzlehttp/psr7/src/NoSeekStream.php b/vendor/guzzlehttp/psr7/src/NoSeekStream.php new file mode 100644 index 00000000..161a224f --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/NoSeekStream.php @@ -0,0 +1,28 @@ +source = $source; + $this->size = $options['size'] ?? null; + $this->metadata = $options['metadata'] ?? []; + $this->buffer = new BufferStream(); + } + + public function __toString(): string + { + try { + return Utils::copyToString($this); + } catch (\Throwable $e) { + if (\PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR); + + return ''; + } + } + + public function close(): void + { + $this->detach(); + } + + public function detach() + { + $this->tellPos = 0; + $this->source = null; + + return null; + } + + public function getSize(): ?int + { + return $this->size; + } + + public function tell(): int + { + return $this->tellPos; + } + + public function eof(): bool + { + return $this->source === null; + } + + public function isSeekable(): bool + { + return false; + } + + public function rewind(): void + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET): void + { + throw new \RuntimeException('Cannot seek a PumpStream'); + } + + public function isWritable(): bool + { + return false; + } + + public function write($string): int + { + throw new \RuntimeException('Cannot write to a PumpStream'); + } + + public function isReadable(): bool + { + return true; + } + + public function read($length): string + { + $data = $this->buffer->read($length); + $readLen = strlen($data); + $this->tellPos += $readLen; + $remaining = $length - $readLen; + + if ($remaining) { + $this->pump($remaining); + $data .= $this->buffer->read($remaining); + $this->tellPos += strlen($data) - $readLen; + } + + return $data; + } + + public function getContents(): string + { + $result = ''; + while (!$this->eof()) { + $result .= $this->read(1000000); + } + + return $result; + } + + /** + * @return mixed + */ + public function getMetadata($key = null) + { + if (!$key) { + return $this->metadata; + } + + return $this->metadata[$key] ?? null; + } + + private function pump(int $length): void + { + if ($this->source !== null) { + do { + $data = ($this->source)($length); + if ($data === false || $data === null) { + $this->source = null; + + return; + } + $this->buffer->write($data); + $length -= strlen($data); + } while ($length > 0); + } + } +} diff --git a/vendor/guzzlehttp/psr7/src/Query.php b/vendor/guzzlehttp/psr7/src/Query.php new file mode 100644 index 00000000..8b949279 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Query.php @@ -0,0 +1,113 @@ + '1', 'foo[b]' => '2'])`. + * + * @param string $str Query string to parse + * @param int|bool $urlEncoding How the query string is encoded + */ + public static function parse(string $str, $urlEncoding = true): array + { + $result = []; + + if ($str === '') { + return $result; + } + + if ($urlEncoding === true) { + $decoder = function ($value) { + return rawurldecode(str_replace('+', ' ', (string) $value)); + }; + } elseif ($urlEncoding === PHP_QUERY_RFC3986) { + $decoder = 'rawurldecode'; + } elseif ($urlEncoding === PHP_QUERY_RFC1738) { + $decoder = 'urldecode'; + } else { + $decoder = function ($str) { + return $str; + }; + } + + foreach (explode('&', $str) as $kvp) { + $parts = explode('=', $kvp, 2); + $key = $decoder($parts[0]); + $value = isset($parts[1]) ? $decoder($parts[1]) : null; + if (!array_key_exists($key, $result)) { + $result[$key] = $value; + } else { + if (!is_array($result[$key])) { + $result[$key] = [$result[$key]]; + } + $result[$key][] = $value; + } + } + + return $result; + } + + /** + * Build a query string from an array of key value pairs. + * + * This function can use the return value of `parse()` to build a query + * string. This function does not modify the provided keys when an array is + * encountered (like `http_build_query()` would). + * + * @param array $params Query string parameters. + * @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986 + * to encode using RFC3986, or PHP_QUERY_RFC1738 + * to encode using RFC1738. + */ + public static function build(array $params, $encoding = PHP_QUERY_RFC3986): string + { + if (!$params) { + return ''; + } + + if ($encoding === false) { + $encoder = function (string $str): string { + return $str; + }; + } elseif ($encoding === PHP_QUERY_RFC3986) { + $encoder = 'rawurlencode'; + } elseif ($encoding === PHP_QUERY_RFC1738) { + $encoder = 'urlencode'; + } else { + throw new \InvalidArgumentException('Invalid type'); + } + + $qs = ''; + foreach ($params as $k => $v) { + $k = $encoder((string) $k); + if (!is_array($v)) { + $qs .= $k; + $v = is_bool($v) ? (int) $v : $v; + if ($v !== null) { + $qs .= '='.$encoder((string) $v); + } + $qs .= '&'; + } else { + foreach ($v as $vv) { + $qs .= $k; + $vv = is_bool($vv) ? (int) $vv : $vv; + if ($vv !== null) { + $qs .= '='.$encoder((string) $vv); + } + $qs .= '&'; + } + } + } + + return $qs ? (string) substr($qs, 0, -1) : ''; + } +} diff --git a/vendor/guzzlehttp/psr7/src/Request.php b/vendor/guzzlehttp/psr7/src/Request.php new file mode 100644 index 00000000..faafe1ad --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Request.php @@ -0,0 +1,159 @@ +assertMethod($method); + if (!($uri instanceof UriInterface)) { + $uri = new Uri($uri); + } + + $this->method = strtoupper($method); + $this->uri = $uri; + $this->setHeaders($headers); + $this->protocol = $version; + + if (!isset($this->headerNames['host'])) { + $this->updateHostFromUri(); + } + + if ($body !== '' && $body !== null) { + $this->stream = Utils::streamFor($body); + } + } + + public function getRequestTarget(): string + { + if ($this->requestTarget !== null) { + return $this->requestTarget; + } + + $target = $this->uri->getPath(); + if ($target === '') { + $target = '/'; + } + if ($this->uri->getQuery() != '') { + $target .= '?'.$this->uri->getQuery(); + } + + return $target; + } + + public function withRequestTarget($requestTarget): RequestInterface + { + if (preg_match('#\s#', $requestTarget)) { + throw new InvalidArgumentException( + 'Invalid request target provided; cannot contain whitespace' + ); + } + + $new = clone $this; + $new->requestTarget = $requestTarget; + + return $new; + } + + public function getMethod(): string + { + return $this->method; + } + + public function withMethod($method): RequestInterface + { + $this->assertMethod($method); + $new = clone $this; + $new->method = strtoupper($method); + + return $new; + } + + public function getUri(): UriInterface + { + return $this->uri; + } + + public function withUri(UriInterface $uri, $preserveHost = false): RequestInterface + { + if ($uri === $this->uri) { + return $this; + } + + $new = clone $this; + $new->uri = $uri; + + if (!$preserveHost || !isset($this->headerNames['host'])) { + $new->updateHostFromUri(); + } + + return $new; + } + + private function updateHostFromUri(): void + { + $host = $this->uri->getHost(); + + if ($host == '') { + return; + } + + if (($port = $this->uri->getPort()) !== null) { + $host .= ':'.$port; + } + + if (isset($this->headerNames['host'])) { + $header = $this->headerNames['host']; + } else { + $header = 'Host'; + $this->headerNames['host'] = 'Host'; + } + // Ensure Host is the first header. + // See: https://datatracker.ietf.org/doc/html/rfc7230#section-5.4 + $this->headers = [$header => [$host]] + $this->headers; + } + + /** + * @param mixed $method + */ + private function assertMethod($method): void + { + if (!is_string($method) || $method === '') { + throw new InvalidArgumentException('Method must be a non-empty string.'); + } + } +} diff --git a/vendor/guzzlehttp/psr7/src/Response.php b/vendor/guzzlehttp/psr7/src/Response.php new file mode 100644 index 00000000..00f16e2d --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Response.php @@ -0,0 +1,161 @@ + 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-status', + 208 => 'Already Reported', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => 'Switch Proxy', + 307 => 'Temporary Redirect', + 308 => 'Permanent Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Time-out', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Large', + 415 => 'Unsupported Media Type', + 416 => 'Requested range not satisfiable', + 417 => 'Expectation Failed', + 418 => 'I\'m a teapot', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 424 => 'Failed Dependency', + 425 => 'Unordered Collection', + 426 => 'Upgrade Required', + 428 => 'Precondition Required', + 429 => 'Too Many Requests', + 431 => 'Request Header Fields Too Large', + 451 => 'Unavailable For Legal Reasons', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Time-out', + 505 => 'HTTP Version not supported', + 506 => 'Variant Also Negotiates', + 507 => 'Insufficient Storage', + 508 => 'Loop Detected', + 510 => 'Not Extended', + 511 => 'Network Authentication Required', + ]; + + /** @var string */ + private $reasonPhrase; + + /** @var int */ + private $statusCode; + + /** + * @param int $status Status code + * @param (string|string[])[] $headers Response headers + * @param string|resource|StreamInterface|null $body Response body + * @param string $version Protocol version + * @param string|null $reason Reason phrase (when empty a default will be used based on the status code) + */ + public function __construct( + int $status = 200, + array $headers = [], + $body = null, + string $version = '1.1', + string $reason = null + ) { + $this->assertStatusCodeRange($status); + + $this->statusCode = $status; + + if ($body !== '' && $body !== null) { + $this->stream = Utils::streamFor($body); + } + + $this->setHeaders($headers); + if ($reason == '' && isset(self::PHRASES[$this->statusCode])) { + $this->reasonPhrase = self::PHRASES[$this->statusCode]; + } else { + $this->reasonPhrase = (string) $reason; + } + + $this->protocol = $version; + } + + public function getStatusCode(): int + { + return $this->statusCode; + } + + public function getReasonPhrase(): string + { + return $this->reasonPhrase; + } + + public function withStatus($code, $reasonPhrase = ''): ResponseInterface + { + $this->assertStatusCodeIsInteger($code); + $code = (int) $code; + $this->assertStatusCodeRange($code); + + $new = clone $this; + $new->statusCode = $code; + if ($reasonPhrase == '' && isset(self::PHRASES[$new->statusCode])) { + $reasonPhrase = self::PHRASES[$new->statusCode]; + } + $new->reasonPhrase = (string) $reasonPhrase; + + return $new; + } + + /** + * @param mixed $statusCode + */ + private function assertStatusCodeIsInteger($statusCode): void + { + if (filter_var($statusCode, FILTER_VALIDATE_INT) === false) { + throw new \InvalidArgumentException('Status code must be an integer value.'); + } + } + + private function assertStatusCodeRange(int $statusCode): void + { + if ($statusCode < 100 || $statusCode >= 600) { + throw new \InvalidArgumentException('Status code must be an integer value between 1xx and 5xx.'); + } + } +} diff --git a/vendor/guzzlehttp/psr7/src/Rfc7230.php b/vendor/guzzlehttp/psr7/src/Rfc7230.php new file mode 100644 index 00000000..8219dba4 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Rfc7230.php @@ -0,0 +1,23 @@ +@,;:\\\"/[\]?={}\x01-\x20\x7F]++):[ \t]*+((?:[ \t]*+[\x21-\x7E\x80-\xFF]++)*+)[ \t]*+\r?\n)m"; + public const HEADER_FOLD_REGEX = "(\r?\n[ \t]++)"; +} diff --git a/vendor/guzzlehttp/psr7/src/ServerRequest.php b/vendor/guzzlehttp/psr7/src/ServerRequest.php new file mode 100644 index 00000000..3cc95345 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/ServerRequest.php @@ -0,0 +1,340 @@ +serverParams = $serverParams; + + parent::__construct($method, $uri, $headers, $body, $version); + } + + /** + * Return an UploadedFile instance array. + * + * @param array $files An array which respect $_FILES structure + * + * @throws InvalidArgumentException for unrecognized values + */ + public static function normalizeFiles(array $files): array + { + $normalized = []; + + foreach ($files as $key => $value) { + if ($value instanceof UploadedFileInterface) { + $normalized[$key] = $value; + } elseif (is_array($value) && isset($value['tmp_name'])) { + $normalized[$key] = self::createUploadedFileFromSpec($value); + } elseif (is_array($value)) { + $normalized[$key] = self::normalizeFiles($value); + continue; + } else { + throw new InvalidArgumentException('Invalid value in files specification'); + } + } + + return $normalized; + } + + /** + * Create and return an UploadedFile instance from a $_FILES specification. + * + * If the specification represents an array of values, this method will + * delegate to normalizeNestedFileSpec() and return that return value. + * + * @param array $value $_FILES struct + * + * @return UploadedFileInterface|UploadedFileInterface[] + */ + private static function createUploadedFileFromSpec(array $value) + { + if (is_array($value['tmp_name'])) { + return self::normalizeNestedFileSpec($value); + } + + return new UploadedFile( + $value['tmp_name'], + (int) $value['size'], + (int) $value['error'], + $value['name'], + $value['type'] + ); + } + + /** + * Normalize an array of file specifications. + * + * Loops through all nested files and returns a normalized array of + * UploadedFileInterface instances. + * + * @return UploadedFileInterface[] + */ + private static function normalizeNestedFileSpec(array $files = []): array + { + $normalizedFiles = []; + + foreach (array_keys($files['tmp_name']) as $key) { + $spec = [ + 'tmp_name' => $files['tmp_name'][$key], + 'size' => $files['size'][$key] ?? null, + 'error' => $files['error'][$key] ?? null, + 'name' => $files['name'][$key] ?? null, + 'type' => $files['type'][$key] ?? null, + ]; + $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec); + } + + return $normalizedFiles; + } + + /** + * Return a ServerRequest populated with superglobals: + * $_GET + * $_POST + * $_COOKIE + * $_FILES + * $_SERVER + */ + public static function fromGlobals(): ServerRequestInterface + { + $method = $_SERVER['REQUEST_METHOD'] ?? 'GET'; + $headers = getallheaders(); + $uri = self::getUriFromGlobals(); + $body = new CachingStream(new LazyOpenStream('php://input', 'r+')); + $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL']) : '1.1'; + + $serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER); + + return $serverRequest + ->withCookieParams($_COOKIE) + ->withQueryParams($_GET) + ->withParsedBody($_POST) + ->withUploadedFiles(self::normalizeFiles($_FILES)); + } + + private static function extractHostAndPortFromAuthority(string $authority): array + { + $uri = 'http://'.$authority; + $parts = parse_url($uri); + if (false === $parts) { + return [null, null]; + } + + $host = $parts['host'] ?? null; + $port = $parts['port'] ?? null; + + return [$host, $port]; + } + + /** + * Get a Uri populated with values from $_SERVER. + */ + public static function getUriFromGlobals(): UriInterface + { + $uri = new Uri(''); + + $uri = $uri->withScheme(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http'); + + $hasPort = false; + if (isset($_SERVER['HTTP_HOST'])) { + [$host, $port] = self::extractHostAndPortFromAuthority($_SERVER['HTTP_HOST']); + if ($host !== null) { + $uri = $uri->withHost($host); + } + + if ($port !== null) { + $hasPort = true; + $uri = $uri->withPort($port); + } + } elseif (isset($_SERVER['SERVER_NAME'])) { + $uri = $uri->withHost($_SERVER['SERVER_NAME']); + } elseif (isset($_SERVER['SERVER_ADDR'])) { + $uri = $uri->withHost($_SERVER['SERVER_ADDR']); + } + + if (!$hasPort && isset($_SERVER['SERVER_PORT'])) { + $uri = $uri->withPort($_SERVER['SERVER_PORT']); + } + + $hasQuery = false; + if (isset($_SERVER['REQUEST_URI'])) { + $requestUriParts = explode('?', $_SERVER['REQUEST_URI'], 2); + $uri = $uri->withPath($requestUriParts[0]); + if (isset($requestUriParts[1])) { + $hasQuery = true; + $uri = $uri->withQuery($requestUriParts[1]); + } + } + + if (!$hasQuery && isset($_SERVER['QUERY_STRING'])) { + $uri = $uri->withQuery($_SERVER['QUERY_STRING']); + } + + return $uri; + } + + public function getServerParams(): array + { + return $this->serverParams; + } + + public function getUploadedFiles(): array + { + return $this->uploadedFiles; + } + + public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface + { + $new = clone $this; + $new->uploadedFiles = $uploadedFiles; + + return $new; + } + + public function getCookieParams(): array + { + return $this->cookieParams; + } + + public function withCookieParams(array $cookies): ServerRequestInterface + { + $new = clone $this; + $new->cookieParams = $cookies; + + return $new; + } + + public function getQueryParams(): array + { + return $this->queryParams; + } + + public function withQueryParams(array $query): ServerRequestInterface + { + $new = clone $this; + $new->queryParams = $query; + + return $new; + } + + /** + * @return array|object|null + */ + public function getParsedBody() + { + return $this->parsedBody; + } + + public function withParsedBody($data): ServerRequestInterface + { + $new = clone $this; + $new->parsedBody = $data; + + return $new; + } + + public function getAttributes(): array + { + return $this->attributes; + } + + /** + * @return mixed + */ + public function getAttribute($attribute, $default = null) + { + if (false === array_key_exists($attribute, $this->attributes)) { + return $default; + } + + return $this->attributes[$attribute]; + } + + public function withAttribute($attribute, $value): ServerRequestInterface + { + $new = clone $this; + $new->attributes[$attribute] = $value; + + return $new; + } + + public function withoutAttribute($attribute): ServerRequestInterface + { + if (false === array_key_exists($attribute, $this->attributes)) { + return $this; + } + + $new = clone $this; + unset($new->attributes[$attribute]); + + return $new; + } +} diff --git a/vendor/guzzlehttp/psr7/src/Stream.php b/vendor/guzzlehttp/psr7/src/Stream.php new file mode 100644 index 00000000..0aff9b2b --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Stream.php @@ -0,0 +1,283 @@ +size = $options['size']; + } + + $this->customMetadata = $options['metadata'] ?? []; + $this->stream = $stream; + $meta = stream_get_meta_data($this->stream); + $this->seekable = $meta['seekable']; + $this->readable = (bool) preg_match(self::READABLE_MODES, $meta['mode']); + $this->writable = (bool) preg_match(self::WRITABLE_MODES, $meta['mode']); + $this->uri = $this->getMetadata('uri'); + } + + /** + * Closes the stream when the destructed + */ + public function __destruct() + { + $this->close(); + } + + public function __toString(): string + { + try { + if ($this->isSeekable()) { + $this->seek(0); + } + + return $this->getContents(); + } catch (\Throwable $e) { + if (\PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR); + + return ''; + } + } + + public function getContents(): string + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + + if (!$this->readable) { + throw new \RuntimeException('Cannot read from non-readable stream'); + } + + return Utils::tryGetContents($this->stream); + } + + public function close(): void + { + if (isset($this->stream)) { + if (is_resource($this->stream)) { + fclose($this->stream); + } + $this->detach(); + } + } + + public function detach() + { + if (!isset($this->stream)) { + return null; + } + + $result = $this->stream; + unset($this->stream); + $this->size = $this->uri = null; + $this->readable = $this->writable = $this->seekable = false; + + return $result; + } + + public function getSize(): ?int + { + if ($this->size !== null) { + return $this->size; + } + + if (!isset($this->stream)) { + return null; + } + + // Clear the stat cache if the stream has a URI + if ($this->uri) { + clearstatcache(true, $this->uri); + } + + $stats = fstat($this->stream); + if (is_array($stats) && isset($stats['size'])) { + $this->size = $stats['size']; + + return $this->size; + } + + return null; + } + + public function isReadable(): bool + { + return $this->readable; + } + + public function isWritable(): bool + { + return $this->writable; + } + + public function isSeekable(): bool + { + return $this->seekable; + } + + public function eof(): bool + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + + return feof($this->stream); + } + + public function tell(): int + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + + $result = ftell($this->stream); + + if ($result === false) { + throw new \RuntimeException('Unable to determine stream position'); + } + + return $result; + } + + public function rewind(): void + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET): void + { + $whence = (int) $whence; + + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + if (!$this->seekable) { + throw new \RuntimeException('Stream is not seekable'); + } + if (fseek($this->stream, $offset, $whence) === -1) { + throw new \RuntimeException('Unable to seek to stream position ' + .$offset.' with whence '.var_export($whence, true)); + } + } + + public function read($length): string + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + if (!$this->readable) { + throw new \RuntimeException('Cannot read from non-readable stream'); + } + if ($length < 0) { + throw new \RuntimeException('Length parameter cannot be negative'); + } + + if (0 === $length) { + return ''; + } + + try { + $string = fread($this->stream, $length); + } catch (\Exception $e) { + throw new \RuntimeException('Unable to read from stream', 0, $e); + } + + if (false === $string) { + throw new \RuntimeException('Unable to read from stream'); + } + + return $string; + } + + public function write($string): int + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + if (!$this->writable) { + throw new \RuntimeException('Cannot write to a non-writable stream'); + } + + // We can't know the size after writing anything + $this->size = null; + $result = fwrite($this->stream, $string); + + if ($result === false) { + throw new \RuntimeException('Unable to write to stream'); + } + + return $result; + } + + /** + * @return mixed + */ + public function getMetadata($key = null) + { + if (!isset($this->stream)) { + return $key ? null : []; + } elseif (!$key) { + return $this->customMetadata + stream_get_meta_data($this->stream); + } elseif (isset($this->customMetadata[$key])) { + return $this->customMetadata[$key]; + } + + $meta = stream_get_meta_data($this->stream); + + return $meta[$key] ?? null; + } +} diff --git a/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php b/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php new file mode 100644 index 00000000..601c13af --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php @@ -0,0 +1,156 @@ +stream = $stream; + } + + /** + * Magic method used to create a new stream if streams are not added in + * the constructor of a decorator (e.g., LazyOpenStream). + * + * @return StreamInterface + */ + public function __get(string $name) + { + if ($name === 'stream') { + $this->stream = $this->createStream(); + + return $this->stream; + } + + throw new \UnexpectedValueException("$name not found on class"); + } + + public function __toString(): string + { + try { + if ($this->isSeekable()) { + $this->seek(0); + } + + return $this->getContents(); + } catch (\Throwable $e) { + if (\PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR); + + return ''; + } + } + + public function getContents(): string + { + return Utils::copyToString($this); + } + + /** + * Allow decorators to implement custom methods + * + * @return mixed + */ + public function __call(string $method, array $args) + { + /** @var callable $callable */ + $callable = [$this->stream, $method]; + $result = ($callable)(...$args); + + // Always return the wrapped object if the result is a return $this + return $result === $this->stream ? $this : $result; + } + + public function close(): void + { + $this->stream->close(); + } + + /** + * @return mixed + */ + public function getMetadata($key = null) + { + return $this->stream->getMetadata($key); + } + + public function detach() + { + return $this->stream->detach(); + } + + public function getSize(): ?int + { + return $this->stream->getSize(); + } + + public function eof(): bool + { + return $this->stream->eof(); + } + + public function tell(): int + { + return $this->stream->tell(); + } + + public function isReadable(): bool + { + return $this->stream->isReadable(); + } + + public function isWritable(): bool + { + return $this->stream->isWritable(); + } + + public function isSeekable(): bool + { + return $this->stream->isSeekable(); + } + + public function rewind(): void + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET): void + { + $this->stream->seek($offset, $whence); + } + + public function read($length): string + { + return $this->stream->read($length); + } + + public function write($string): int + { + return $this->stream->write($string); + } + + /** + * Implement in subclasses to dynamically create streams when requested. + * + * @throws \BadMethodCallException + */ + protected function createStream(): StreamInterface + { + throw new \BadMethodCallException('Not implemented'); + } +} diff --git a/vendor/guzzlehttp/psr7/src/StreamWrapper.php b/vendor/guzzlehttp/psr7/src/StreamWrapper.php new file mode 100644 index 00000000..ae853881 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/StreamWrapper.php @@ -0,0 +1,203 @@ +isReadable()) { + $mode = $stream->isWritable() ? 'r+' : 'r'; + } elseif ($stream->isWritable()) { + $mode = 'w'; + } else { + throw new \InvalidArgumentException('The stream must be readable, ' + .'writable, or both.'); + } + + return fopen('guzzle://stream', $mode, false, self::createStreamContext($stream)); + } + + /** + * Creates a stream context that can be used to open a stream as a php stream resource. + * + * @return resource + */ + public static function createStreamContext(StreamInterface $stream) + { + return stream_context_create([ + 'guzzle' => ['stream' => $stream], + ]); + } + + /** + * Registers the stream wrapper if needed + */ + public static function register(): void + { + if (!in_array('guzzle', stream_get_wrappers())) { + stream_wrapper_register('guzzle', __CLASS__); + } + } + + public function stream_open(string $path, string $mode, int $options, string &$opened_path = null): bool + { + $options = stream_context_get_options($this->context); + + if (!isset($options['guzzle']['stream'])) { + return false; + } + + $this->mode = $mode; + $this->stream = $options['guzzle']['stream']; + + return true; + } + + public function stream_read(int $count): string + { + return $this->stream->read($count); + } + + public function stream_write(string $data): int + { + return $this->stream->write($data); + } + + public function stream_tell(): int + { + return $this->stream->tell(); + } + + public function stream_eof(): bool + { + return $this->stream->eof(); + } + + public function stream_seek(int $offset, int $whence): bool + { + $this->stream->seek($offset, $whence); + + return true; + } + + /** + * @return resource|false + */ + public function stream_cast(int $cast_as) + { + $stream = clone $this->stream; + $resource = $stream->detach(); + + return $resource ?? false; + } + + /** + * @return array{ + * dev: int, + * ino: int, + * mode: int, + * nlink: int, + * uid: int, + * gid: int, + * rdev: int, + * size: int, + * atime: int, + * mtime: int, + * ctime: int, + * blksize: int, + * blocks: int + * } + */ + public function stream_stat(): array + { + static $modeMap = [ + 'r' => 33060, + 'rb' => 33060, + 'r+' => 33206, + 'w' => 33188, + 'wb' => 33188, + ]; + + return [ + 'dev' => 0, + 'ino' => 0, + 'mode' => $modeMap[$this->mode], + 'nlink' => 0, + 'uid' => 0, + 'gid' => 0, + 'rdev' => 0, + 'size' => $this->stream->getSize() ?: 0, + 'atime' => 0, + 'mtime' => 0, + 'ctime' => 0, + 'blksize' => 0, + 'blocks' => 0, + ]; + } + + /** + * @return array{ + * dev: int, + * ino: int, + * mode: int, + * nlink: int, + * uid: int, + * gid: int, + * rdev: int, + * size: int, + * atime: int, + * mtime: int, + * ctime: int, + * blksize: int, + * blocks: int + * } + */ + public function url_stat(string $path, int $flags): array + { + return [ + 'dev' => 0, + 'ino' => 0, + 'mode' => 0, + 'nlink' => 0, + 'uid' => 0, + 'gid' => 0, + 'rdev' => 0, + 'size' => 0, + 'atime' => 0, + 'mtime' => 0, + 'ctime' => 0, + 'blksize' => 0, + 'blocks' => 0, + ]; + } +} diff --git a/vendor/guzzlehttp/psr7/src/UploadedFile.php b/vendor/guzzlehttp/psr7/src/UploadedFile.php new file mode 100644 index 00000000..b2671993 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/UploadedFile.php @@ -0,0 +1,211 @@ +setError($errorStatus); + $this->size = $size; + $this->clientFilename = $clientFilename; + $this->clientMediaType = $clientMediaType; + + if ($this->isOk()) { + $this->setStreamOrFile($streamOrFile); + } + } + + /** + * Depending on the value set file or stream variable + * + * @param StreamInterface|string|resource $streamOrFile + * + * @throws InvalidArgumentException + */ + private function setStreamOrFile($streamOrFile): void + { + if (is_string($streamOrFile)) { + $this->file = $streamOrFile; + } elseif (is_resource($streamOrFile)) { + $this->stream = new Stream($streamOrFile); + } elseif ($streamOrFile instanceof StreamInterface) { + $this->stream = $streamOrFile; + } else { + throw new InvalidArgumentException( + 'Invalid stream or file provided for UploadedFile' + ); + } + } + + /** + * @throws InvalidArgumentException + */ + private function setError(int $error): void + { + if (false === in_array($error, UploadedFile::ERRORS, true)) { + throw new InvalidArgumentException( + 'Invalid error status for UploadedFile' + ); + } + + $this->error = $error; + } + + private static function isStringNotEmpty($param): bool + { + return is_string($param) && false === empty($param); + } + + /** + * Return true if there is no upload error + */ + private function isOk(): bool + { + return $this->error === UPLOAD_ERR_OK; + } + + public function isMoved(): bool + { + return $this->moved; + } + + /** + * @throws RuntimeException if is moved or not ok + */ + private function validateActive(): void + { + if (false === $this->isOk()) { + throw new RuntimeException('Cannot retrieve stream due to upload error'); + } + + if ($this->isMoved()) { + throw new RuntimeException('Cannot retrieve stream after it has already been moved'); + } + } + + public function getStream(): StreamInterface + { + $this->validateActive(); + + if ($this->stream instanceof StreamInterface) { + return $this->stream; + } + + /** @var string $file */ + $file = $this->file; + + return new LazyOpenStream($file, 'r+'); + } + + public function moveTo($targetPath): void + { + $this->validateActive(); + + if (false === self::isStringNotEmpty($targetPath)) { + throw new InvalidArgumentException( + 'Invalid path provided for move operation; must be a non-empty string' + ); + } + + if ($this->file) { + $this->moved = PHP_SAPI === 'cli' + ? rename($this->file, $targetPath) + : move_uploaded_file($this->file, $targetPath); + } else { + Utils::copyToStream( + $this->getStream(), + new LazyOpenStream($targetPath, 'w') + ); + + $this->moved = true; + } + + if (false === $this->moved) { + throw new RuntimeException( + sprintf('Uploaded file could not be moved to %s', $targetPath) + ); + } + } + + public function getSize(): ?int + { + return $this->size; + } + + public function getError(): int + { + return $this->error; + } + + public function getClientFilename(): ?string + { + return $this->clientFilename; + } + + public function getClientMediaType(): ?string + { + return $this->clientMediaType; + } +} diff --git a/vendor/guzzlehttp/psr7/src/Uri.php b/vendor/guzzlehttp/psr7/src/Uri.php new file mode 100644 index 00000000..f1feee87 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Uri.php @@ -0,0 +1,743 @@ + 80, + 'https' => 443, + 'ftp' => 21, + 'gopher' => 70, + 'nntp' => 119, + 'news' => 119, + 'telnet' => 23, + 'tn3270' => 23, + 'imap' => 143, + 'pop' => 110, + 'ldap' => 389, + ]; + + /** + * Unreserved characters for use in a regex. + * + * @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.3 + */ + private const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~'; + + /** + * Sub-delims for use in a regex. + * + * @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.2 + */ + private const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;='; + private const QUERY_SEPARATORS_REPLACEMENT = ['=' => '%3D', '&' => '%26']; + + /** @var string Uri scheme. */ + private $scheme = ''; + + /** @var string Uri user info. */ + private $userInfo = ''; + + /** @var string Uri host. */ + private $host = ''; + + /** @var int|null Uri port. */ + private $port; + + /** @var string Uri path. */ + private $path = ''; + + /** @var string Uri query string. */ + private $query = ''; + + /** @var string Uri fragment. */ + private $fragment = ''; + + /** @var string|null String representation */ + private $composedComponents; + + public function __construct(string $uri = '') + { + if ($uri !== '') { + $parts = self::parse($uri); + if ($parts === false) { + throw new MalformedUriException("Unable to parse URI: $uri"); + } + $this->applyParts($parts); + } + } + + /** + * UTF-8 aware \parse_url() replacement. + * + * The internal function produces broken output for non ASCII domain names + * (IDN) when used with locales other than "C". + * + * On the other hand, cURL understands IDN correctly only when UTF-8 locale + * is configured ("C.UTF-8", "en_US.UTF-8", etc.). + * + * @see https://bugs.php.net/bug.php?id=52923 + * @see https://www.php.net/manual/en/function.parse-url.php#114817 + * @see https://curl.haxx.se/libcurl/c/CURLOPT_URL.html#ENCODING + * + * @return array|false + */ + private static function parse(string $url) + { + // If IPv6 + $prefix = ''; + if (preg_match('%^(.*://\[[0-9:a-f]+\])(.*?)$%', $url, $matches)) { + /** @var array{0:string, 1:string, 2:string} $matches */ + $prefix = $matches[1]; + $url = $matches[2]; + } + + /** @var string */ + $encodedUrl = preg_replace_callback( + '%[^:/@?&=#]+%usD', + static function ($matches) { + return urlencode($matches[0]); + }, + $url + ); + + $result = parse_url($prefix.$encodedUrl); + + if ($result === false) { + return false; + } + + return array_map('urldecode', $result); + } + + public function __toString(): string + { + if ($this->composedComponents === null) { + $this->composedComponents = self::composeComponents( + $this->scheme, + $this->getAuthority(), + $this->path, + $this->query, + $this->fragment + ); + } + + return $this->composedComponents; + } + + /** + * Composes a URI reference string from its various components. + * + * Usually this method does not need to be called manually but instead is used indirectly via + * `Psr\Http\Message\UriInterface::__toString`. + * + * PSR-7 UriInterface treats an empty component the same as a missing component as + * getQuery(), getFragment() etc. always return a string. This explains the slight + * difference to RFC 3986 Section 5.3. + * + * Another adjustment is that the authority separator is added even when the authority is missing/empty + * for the "file" scheme. This is because PHP stream functions like `file_get_contents` only work with + * `file:///myfile` but not with `file:/myfile` although they are equivalent according to RFC 3986. But + * `file:///` is the more common syntax for the file scheme anyway (Chrome for example redirects to + * that format). + * + * @see https://datatracker.ietf.org/doc/html/rfc3986#section-5.3 + */ + public static function composeComponents(?string $scheme, ?string $authority, string $path, ?string $query, ?string $fragment): string + { + $uri = ''; + + // weak type checks to also accept null until we can add scalar type hints + if ($scheme != '') { + $uri .= $scheme.':'; + } + + if ($authority != '' || $scheme === 'file') { + $uri .= '//'.$authority; + } + + if ($authority != '' && $path != '' && $path[0] != '/') { + $path = '/'.$path; + } + + $uri .= $path; + + if ($query != '') { + $uri .= '?'.$query; + } + + if ($fragment != '') { + $uri .= '#'.$fragment; + } + + return $uri; + } + + /** + * Whether the URI has the default port of the current scheme. + * + * `Psr\Http\Message\UriInterface::getPort` may return null or the standard port. This method can be used + * independently of the implementation. + */ + public static function isDefaultPort(UriInterface $uri): bool + { + return $uri->getPort() === null + || (isset(self::DEFAULT_PORTS[$uri->getScheme()]) && $uri->getPort() === self::DEFAULT_PORTS[$uri->getScheme()]); + } + + /** + * Whether the URI is absolute, i.e. it has a scheme. + * + * An instance of UriInterface can either be an absolute URI or a relative reference. This method returns true + * if it is the former. An absolute URI has a scheme. A relative reference is used to express a URI relative + * to another URI, the base URI. Relative references can be divided into several forms: + * - network-path references, e.g. '//example.com/path' + * - absolute-path references, e.g. '/path' + * - relative-path references, e.g. 'subpath' + * + * @see Uri::isNetworkPathReference + * @see Uri::isAbsolutePathReference + * @see Uri::isRelativePathReference + * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4 + */ + public static function isAbsolute(UriInterface $uri): bool + { + return $uri->getScheme() !== ''; + } + + /** + * Whether the URI is a network-path reference. + * + * A relative reference that begins with two slash characters is termed an network-path reference. + * + * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2 + */ + public static function isNetworkPathReference(UriInterface $uri): bool + { + return $uri->getScheme() === '' && $uri->getAuthority() !== ''; + } + + /** + * Whether the URI is a absolute-path reference. + * + * A relative reference that begins with a single slash character is termed an absolute-path reference. + * + * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2 + */ + public static function isAbsolutePathReference(UriInterface $uri): bool + { + return $uri->getScheme() === '' + && $uri->getAuthority() === '' + && isset($uri->getPath()[0]) + && $uri->getPath()[0] === '/'; + } + + /** + * Whether the URI is a relative-path reference. + * + * A relative reference that does not begin with a slash character is termed a relative-path reference. + * + * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2 + */ + public static function isRelativePathReference(UriInterface $uri): bool + { + return $uri->getScheme() === '' + && $uri->getAuthority() === '' + && (!isset($uri->getPath()[0]) || $uri->getPath()[0] !== '/'); + } + + /** + * Whether the URI is a same-document reference. + * + * A same-document reference refers to a URI that is, aside from its fragment + * component, identical to the base URI. When no base URI is given, only an empty + * URI reference (apart from its fragment) is considered a same-document reference. + * + * @param UriInterface $uri The URI to check + * @param UriInterface|null $base An optional base URI to compare against + * + * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.4 + */ + public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null): bool + { + if ($base !== null) { + $uri = UriResolver::resolve($base, $uri); + + return ($uri->getScheme() === $base->getScheme()) + && ($uri->getAuthority() === $base->getAuthority()) + && ($uri->getPath() === $base->getPath()) + && ($uri->getQuery() === $base->getQuery()); + } + + return $uri->getScheme() === '' && $uri->getAuthority() === '' && $uri->getPath() === '' && $uri->getQuery() === ''; + } + + /** + * Creates a new URI with a specific query string value removed. + * + * Any existing query string values that exactly match the provided key are + * removed. + * + * @param UriInterface $uri URI to use as a base. + * @param string $key Query string key to remove. + */ + public static function withoutQueryValue(UriInterface $uri, string $key): UriInterface + { + $result = self::getFilteredQueryString($uri, [$key]); + + return $uri->withQuery(implode('&', $result)); + } + + /** + * Creates a new URI with a specific query string value. + * + * Any existing query string values that exactly match the provided key are + * removed and replaced with the given key value pair. + * + * A value of null will set the query string key without a value, e.g. "key" + * instead of "key=value". + * + * @param UriInterface $uri URI to use as a base. + * @param string $key Key to set. + * @param string|null $value Value to set + */ + public static function withQueryValue(UriInterface $uri, string $key, ?string $value): UriInterface + { + $result = self::getFilteredQueryString($uri, [$key]); + + $result[] = self::generateQueryString($key, $value); + + return $uri->withQuery(implode('&', $result)); + } + + /** + * Creates a new URI with multiple specific query string values. + * + * It has the same behavior as withQueryValue() but for an associative array of key => value. + * + * @param UriInterface $uri URI to use as a base. + * @param (string|null)[] $keyValueArray Associative array of key and values + */ + public static function withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface + { + $result = self::getFilteredQueryString($uri, array_keys($keyValueArray)); + + foreach ($keyValueArray as $key => $value) { + $result[] = self::generateQueryString((string) $key, $value !== null ? (string) $value : null); + } + + return $uri->withQuery(implode('&', $result)); + } + + /** + * Creates a URI from a hash of `parse_url` components. + * + * @see https://www.php.net/manual/en/function.parse-url.php + * + * @throws MalformedUriException If the components do not form a valid URI. + */ + public static function fromParts(array $parts): UriInterface + { + $uri = new self(); + $uri->applyParts($parts); + $uri->validateState(); + + return $uri; + } + + public function getScheme(): string + { + return $this->scheme; + } + + public function getAuthority(): string + { + $authority = $this->host; + if ($this->userInfo !== '') { + $authority = $this->userInfo.'@'.$authority; + } + + if ($this->port !== null) { + $authority .= ':'.$this->port; + } + + return $authority; + } + + public function getUserInfo(): string + { + return $this->userInfo; + } + + public function getHost(): string + { + return $this->host; + } + + public function getPort(): ?int + { + return $this->port; + } + + public function getPath(): string + { + return $this->path; + } + + public function getQuery(): string + { + return $this->query; + } + + public function getFragment(): string + { + return $this->fragment; + } + + public function withScheme($scheme): UriInterface + { + $scheme = $this->filterScheme($scheme); + + if ($this->scheme === $scheme) { + return $this; + } + + $new = clone $this; + $new->scheme = $scheme; + $new->composedComponents = null; + $new->removeDefaultPort(); + $new->validateState(); + + return $new; + } + + public function withUserInfo($user, $password = null): UriInterface + { + $info = $this->filterUserInfoComponent($user); + if ($password !== null) { + $info .= ':'.$this->filterUserInfoComponent($password); + } + + if ($this->userInfo === $info) { + return $this; + } + + $new = clone $this; + $new->userInfo = $info; + $new->composedComponents = null; + $new->validateState(); + + return $new; + } + + public function withHost($host): UriInterface + { + $host = $this->filterHost($host); + + if ($this->host === $host) { + return $this; + } + + $new = clone $this; + $new->host = $host; + $new->composedComponents = null; + $new->validateState(); + + return $new; + } + + public function withPort($port): UriInterface + { + $port = $this->filterPort($port); + + if ($this->port === $port) { + return $this; + } + + $new = clone $this; + $new->port = $port; + $new->composedComponents = null; + $new->removeDefaultPort(); + $new->validateState(); + + return $new; + } + + public function withPath($path): UriInterface + { + $path = $this->filterPath($path); + + if ($this->path === $path) { + return $this; + } + + $new = clone $this; + $new->path = $path; + $new->composedComponents = null; + $new->validateState(); + + return $new; + } + + public function withQuery($query): UriInterface + { + $query = $this->filterQueryAndFragment($query); + + if ($this->query === $query) { + return $this; + } + + $new = clone $this; + $new->query = $query; + $new->composedComponents = null; + + return $new; + } + + public function withFragment($fragment): UriInterface + { + $fragment = $this->filterQueryAndFragment($fragment); + + if ($this->fragment === $fragment) { + return $this; + } + + $new = clone $this; + $new->fragment = $fragment; + $new->composedComponents = null; + + return $new; + } + + public function jsonSerialize(): string + { + return $this->__toString(); + } + + /** + * Apply parse_url parts to a URI. + * + * @param array $parts Array of parse_url parts to apply. + */ + private function applyParts(array $parts): void + { + $this->scheme = isset($parts['scheme']) + ? $this->filterScheme($parts['scheme']) + : ''; + $this->userInfo = isset($parts['user']) + ? $this->filterUserInfoComponent($parts['user']) + : ''; + $this->host = isset($parts['host']) + ? $this->filterHost($parts['host']) + : ''; + $this->port = isset($parts['port']) + ? $this->filterPort($parts['port']) + : null; + $this->path = isset($parts['path']) + ? $this->filterPath($parts['path']) + : ''; + $this->query = isset($parts['query']) + ? $this->filterQueryAndFragment($parts['query']) + : ''; + $this->fragment = isset($parts['fragment']) + ? $this->filterQueryAndFragment($parts['fragment']) + : ''; + if (isset($parts['pass'])) { + $this->userInfo .= ':'.$this->filterUserInfoComponent($parts['pass']); + } + + $this->removeDefaultPort(); + } + + /** + * @param mixed $scheme + * + * @throws \InvalidArgumentException If the scheme is invalid. + */ + private function filterScheme($scheme): string + { + if (!is_string($scheme)) { + throw new \InvalidArgumentException('Scheme must be a string'); + } + + return \strtr($scheme, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + } + + /** + * @param mixed $component + * + * @throws \InvalidArgumentException If the user info is invalid. + */ + private function filterUserInfoComponent($component): string + { + if (!is_string($component)) { + throw new \InvalidArgumentException('User info must be a string'); + } + + return preg_replace_callback( + '/(?:[^%'.self::CHAR_UNRESERVED.self::CHAR_SUB_DELIMS.']+|%(?![A-Fa-f0-9]{2}))/', + [$this, 'rawurlencodeMatchZero'], + $component + ); + } + + /** + * @param mixed $host + * + * @throws \InvalidArgumentException If the host is invalid. + */ + private function filterHost($host): string + { + if (!is_string($host)) { + throw new \InvalidArgumentException('Host must be a string'); + } + + return \strtr($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + } + + /** + * @param mixed $port + * + * @throws \InvalidArgumentException If the port is invalid. + */ + private function filterPort($port): ?int + { + if ($port === null) { + return null; + } + + $port = (int) $port; + if (0 > $port || 0xFFFF < $port) { + throw new \InvalidArgumentException( + sprintf('Invalid port: %d. Must be between 0 and 65535', $port) + ); + } + + return $port; + } + + /** + * @param (string|int)[] $keys + * + * @return string[] + */ + private static function getFilteredQueryString(UriInterface $uri, array $keys): array + { + $current = $uri->getQuery(); + + if ($current === '') { + return []; + } + + $decodedKeys = array_map(function ($k): string { + return rawurldecode((string) $k); + }, $keys); + + return array_filter(explode('&', $current), function ($part) use ($decodedKeys) { + return !in_array(rawurldecode(explode('=', $part)[0]), $decodedKeys, true); + }); + } + + private static function generateQueryString(string $key, ?string $value): string + { + // Query string separators ("=", "&") within the key or value need to be encoded + // (while preventing double-encoding) before setting the query string. All other + // chars that need percent-encoding will be encoded by withQuery(). + $queryString = strtr($key, self::QUERY_SEPARATORS_REPLACEMENT); + + if ($value !== null) { + $queryString .= '='.strtr($value, self::QUERY_SEPARATORS_REPLACEMENT); + } + + return $queryString; + } + + private function removeDefaultPort(): void + { + if ($this->port !== null && self::isDefaultPort($this)) { + $this->port = null; + } + } + + /** + * Filters the path of a URI + * + * @param mixed $path + * + * @throws \InvalidArgumentException If the path is invalid. + */ + private function filterPath($path): string + { + if (!is_string($path)) { + throw new \InvalidArgumentException('Path must be a string'); + } + + return preg_replace_callback( + '/(?:[^'.self::CHAR_UNRESERVED.self::CHAR_SUB_DELIMS.'%:@\/]++|%(?![A-Fa-f0-9]{2}))/', + [$this, 'rawurlencodeMatchZero'], + $path + ); + } + + /** + * Filters the query string or fragment of a URI. + * + * @param mixed $str + * + * @throws \InvalidArgumentException If the query or fragment is invalid. + */ + private function filterQueryAndFragment($str): string + { + if (!is_string($str)) { + throw new \InvalidArgumentException('Query and fragment must be a string'); + } + + return preg_replace_callback( + '/(?:[^'.self::CHAR_UNRESERVED.self::CHAR_SUB_DELIMS.'%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/', + [$this, 'rawurlencodeMatchZero'], + $str + ); + } + + private function rawurlencodeMatchZero(array $match): string + { + return rawurlencode($match[0]); + } + + private function validateState(): void + { + if ($this->host === '' && ($this->scheme === 'http' || $this->scheme === 'https')) { + $this->host = self::HTTP_DEFAULT_HOST; + } + + if ($this->getAuthority() === '') { + if (0 === strpos($this->path, '//')) { + throw new MalformedUriException('The path of a URI without an authority must not start with two slashes "//"'); + } + if ($this->scheme === '' && false !== strpos(explode('/', $this->path, 2)[0], ':')) { + throw new MalformedUriException('A relative URI must not have a path beginning with a segment containing a colon'); + } + } + } +} diff --git a/vendor/guzzlehttp/psr7/src/UriComparator.php b/vendor/guzzlehttp/psr7/src/UriComparator.php new file mode 100644 index 00000000..70c582aa --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/UriComparator.php @@ -0,0 +1,52 @@ +getHost(), $modified->getHost()) !== 0) { + return true; + } + + if ($original->getScheme() !== $modified->getScheme()) { + return true; + } + + if (self::computePort($original) !== self::computePort($modified)) { + return true; + } + + return false; + } + + private static function computePort(UriInterface $uri): int + { + $port = $uri->getPort(); + + if (null !== $port) { + return $port; + } + + return 'https' === $uri->getScheme() ? 443 : 80; + } + + private function __construct() + { + // cannot be instantiated + } +} diff --git a/vendor/guzzlehttp/psr7/src/UriNormalizer.php b/vendor/guzzlehttp/psr7/src/UriNormalizer.php new file mode 100644 index 00000000..e1745573 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/UriNormalizer.php @@ -0,0 +1,220 @@ +getPath() === '' + && ($uri->getScheme() === 'http' || $uri->getScheme() === 'https') + ) { + $uri = $uri->withPath('/'); + } + + if ($flags & self::REMOVE_DEFAULT_HOST && $uri->getScheme() === 'file' && $uri->getHost() === 'localhost') { + $uri = $uri->withHost(''); + } + + if ($flags & self::REMOVE_DEFAULT_PORT && $uri->getPort() !== null && Uri::isDefaultPort($uri)) { + $uri = $uri->withPort(null); + } + + if ($flags & self::REMOVE_DOT_SEGMENTS && !Uri::isRelativePathReference($uri)) { + $uri = $uri->withPath(UriResolver::removeDotSegments($uri->getPath())); + } + + if ($flags & self::REMOVE_DUPLICATE_SLASHES) { + $uri = $uri->withPath(preg_replace('#//++#', '/', $uri->getPath())); + } + + if ($flags & self::SORT_QUERY_PARAMETERS && $uri->getQuery() !== '') { + $queryKeyValues = explode('&', $uri->getQuery()); + sort($queryKeyValues); + $uri = $uri->withQuery(implode('&', $queryKeyValues)); + } + + return $uri; + } + + /** + * Whether two URIs can be considered equivalent. + * + * Both URIs are normalized automatically before comparison with the given $normalizations bitmask. The method also + * accepts relative URI references and returns true when they are equivalent. This of course assumes they will be + * resolved against the same base URI. If this is not the case, determination of equivalence or difference of + * relative references does not mean anything. + * + * @param UriInterface $uri1 An URI to compare + * @param UriInterface $uri2 An URI to compare + * @param int $normalizations A bitmask of normalizations to apply, see constants + * + * @see https://datatracker.ietf.org/doc/html/rfc3986#section-6.1 + */ + public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, int $normalizations = self::PRESERVING_NORMALIZATIONS): bool + { + return (string) self::normalize($uri1, $normalizations) === (string) self::normalize($uri2, $normalizations); + } + + private static function capitalizePercentEncoding(UriInterface $uri): UriInterface + { + $regex = '/(?:%[A-Fa-f0-9]{2})++/'; + + $callback = function (array $match): string { + return strtoupper($match[0]); + }; + + return + $uri->withPath( + preg_replace_callback($regex, $callback, $uri->getPath()) + )->withQuery( + preg_replace_callback($regex, $callback, $uri->getQuery()) + ); + } + + private static function decodeUnreservedCharacters(UriInterface $uri): UriInterface + { + $regex = '/%(?:2D|2E|5F|7E|3[0-9]|[46][1-9A-F]|[57][0-9A])/i'; + + $callback = function (array $match): string { + return rawurldecode($match[0]); + }; + + return + $uri->withPath( + preg_replace_callback($regex, $callback, $uri->getPath()) + )->withQuery( + preg_replace_callback($regex, $callback, $uri->getQuery()) + ); + } + + private function __construct() + { + // cannot be instantiated + } +} diff --git a/vendor/guzzlehttp/psr7/src/UriResolver.php b/vendor/guzzlehttp/psr7/src/UriResolver.php new file mode 100644 index 00000000..3737be1e --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/UriResolver.php @@ -0,0 +1,211 @@ +getScheme() != '') { + return $rel->withPath(self::removeDotSegments($rel->getPath())); + } + + if ($rel->getAuthority() != '') { + $targetAuthority = $rel->getAuthority(); + $targetPath = self::removeDotSegments($rel->getPath()); + $targetQuery = $rel->getQuery(); + } else { + $targetAuthority = $base->getAuthority(); + if ($rel->getPath() === '') { + $targetPath = $base->getPath(); + $targetQuery = $rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery(); + } else { + if ($rel->getPath()[0] === '/') { + $targetPath = $rel->getPath(); + } else { + if ($targetAuthority != '' && $base->getPath() === '') { + $targetPath = '/'.$rel->getPath(); + } else { + $lastSlashPos = strrpos($base->getPath(), '/'); + if ($lastSlashPos === false) { + $targetPath = $rel->getPath(); + } else { + $targetPath = substr($base->getPath(), 0, $lastSlashPos + 1).$rel->getPath(); + } + } + } + $targetPath = self::removeDotSegments($targetPath); + $targetQuery = $rel->getQuery(); + } + } + + return new Uri(Uri::composeComponents( + $base->getScheme(), + $targetAuthority, + $targetPath, + $targetQuery, + $rel->getFragment() + )); + } + + /** + * Returns the target URI as a relative reference from the base URI. + * + * This method is the counterpart to resolve(): + * + * (string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target)) + * + * One use-case is to use the current request URI as base URI and then generate relative links in your documents + * to reduce the document size or offer self-contained downloadable document archives. + * + * $base = new Uri('http://example.com/a/b/'); + * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'. + * echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'. + * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'. + * echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'. + * + * This method also accepts a target that is already relative and will try to relativize it further. Only a + * relative-path reference will be returned as-is. + * + * echo UriResolver::relativize($base, new Uri('/a/b/c')); // prints 'c' as well + */ + public static function relativize(UriInterface $base, UriInterface $target): UriInterface + { + if ($target->getScheme() !== '' + && ($base->getScheme() !== $target->getScheme() || $target->getAuthority() === '' && $base->getAuthority() !== '') + ) { + return $target; + } + + if (Uri::isRelativePathReference($target)) { + // As the target is already highly relative we return it as-is. It would be possible to resolve + // the target with `$target = self::resolve($base, $target);` and then try make it more relative + // by removing a duplicate query. But let's not do that automatically. + return $target; + } + + if ($target->getAuthority() !== '' && $base->getAuthority() !== $target->getAuthority()) { + return $target->withScheme(''); + } + + // We must remove the path before removing the authority because if the path starts with two slashes, the URI + // would turn invalid. And we also cannot set a relative path before removing the authority, as that is also + // invalid. + $emptyPathUri = $target->withScheme('')->withPath('')->withUserInfo('')->withPort(null)->withHost(''); + + if ($base->getPath() !== $target->getPath()) { + return $emptyPathUri->withPath(self::getRelativePath($base, $target)); + } + + if ($base->getQuery() === $target->getQuery()) { + // Only the target fragment is left. And it must be returned even if base and target fragment are the same. + return $emptyPathUri->withQuery(''); + } + + // If the base URI has a query but the target has none, we cannot return an empty path reference as it would + // inherit the base query component when resolving. + if ($target->getQuery() === '') { + $segments = explode('/', $target->getPath()); + /** @var string $lastSegment */ + $lastSegment = end($segments); + + return $emptyPathUri->withPath($lastSegment === '' ? './' : $lastSegment); + } + + return $emptyPathUri; + } + + private static function getRelativePath(UriInterface $base, UriInterface $target): string + { + $sourceSegments = explode('/', $base->getPath()); + $targetSegments = explode('/', $target->getPath()); + array_pop($sourceSegments); + $targetLastSegment = array_pop($targetSegments); + foreach ($sourceSegments as $i => $segment) { + if (isset($targetSegments[$i]) && $segment === $targetSegments[$i]) { + unset($sourceSegments[$i], $targetSegments[$i]); + } else { + break; + } + } + $targetSegments[] = $targetLastSegment; + $relativePath = str_repeat('../', count($sourceSegments)).implode('/', $targetSegments); + + // A reference to am empty last segment or an empty first sub-segment must be prefixed with "./". + // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used + // as the first segment of a relative-path reference, as it would be mistaken for a scheme name. + if ('' === $relativePath || false !== strpos(explode('/', $relativePath, 2)[0], ':')) { + $relativePath = "./$relativePath"; + } elseif ('/' === $relativePath[0]) { + if ($base->getAuthority() != '' && $base->getPath() === '') { + // In this case an extra slash is added by resolve() automatically. So we must not add one here. + $relativePath = ".$relativePath"; + } else { + $relativePath = "./$relativePath"; + } + } + + return $relativePath; + } + + private function __construct() + { + // cannot be instantiated + } +} diff --git a/vendor/guzzlehttp/psr7/src/Utils.php b/vendor/guzzlehttp/psr7/src/Utils.php new file mode 100644 index 00000000..bf5ea9db --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Utils.php @@ -0,0 +1,463 @@ + $v) { + if (!in_array(strtolower((string) $k), $keys)) { + $result[$k] = $v; + } + } + + return $result; + } + + /** + * Copy the contents of a stream into another stream until the given number + * of bytes have been read. + * + * @param StreamInterface $source Stream to read from + * @param StreamInterface $dest Stream to write to + * @param int $maxLen Maximum number of bytes to read. Pass -1 + * to read the entire stream. + * + * @throws \RuntimeException on error. + */ + public static function copyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen = -1): void + { + $bufferSize = 8192; + + if ($maxLen === -1) { + while (!$source->eof()) { + if (!$dest->write($source->read($bufferSize))) { + break; + } + } + } else { + $remaining = $maxLen; + while ($remaining > 0 && !$source->eof()) { + $buf = $source->read(min($bufferSize, $remaining)); + $len = strlen($buf); + if (!$len) { + break; + } + $remaining -= $len; + $dest->write($buf); + } + } + } + + /** + * Copy the contents of a stream into a string until the given number of + * bytes have been read. + * + * @param StreamInterface $stream Stream to read + * @param int $maxLen Maximum number of bytes to read. Pass -1 + * to read the entire stream. + * + * @throws \RuntimeException on error. + */ + public static function copyToString(StreamInterface $stream, int $maxLen = -1): string + { + $buffer = ''; + + if ($maxLen === -1) { + while (!$stream->eof()) { + $buf = $stream->read(1048576); + if ($buf === '') { + break; + } + $buffer .= $buf; + } + + return $buffer; + } + + $len = 0; + while (!$stream->eof() && $len < $maxLen) { + $buf = $stream->read($maxLen - $len); + if ($buf === '') { + break; + } + $buffer .= $buf; + $len = strlen($buffer); + } + + return $buffer; + } + + /** + * Calculate a hash of a stream. + * + * This method reads the entire stream to calculate a rolling hash, based + * on PHP's `hash_init` functions. + * + * @param StreamInterface $stream Stream to calculate the hash for + * @param string $algo Hash algorithm (e.g. md5, crc32, etc) + * @param bool $rawOutput Whether or not to use raw output + * + * @throws \RuntimeException on error. + */ + public static function hash(StreamInterface $stream, string $algo, bool $rawOutput = false): string + { + $pos = $stream->tell(); + + if ($pos > 0) { + $stream->rewind(); + } + + $ctx = hash_init($algo); + while (!$stream->eof()) { + hash_update($ctx, $stream->read(1048576)); + } + + $out = hash_final($ctx, $rawOutput); + $stream->seek($pos); + + return $out; + } + + /** + * Clone and modify a request with the given changes. + * + * This method is useful for reducing the number of clones needed to mutate + * a message. + * + * The changes can be one of: + * - method: (string) Changes the HTTP method. + * - set_headers: (array) Sets the given headers. + * - remove_headers: (array) Remove the given headers. + * - body: (mixed) Sets the given body. + * - uri: (UriInterface) Set the URI. + * - query: (string) Set the query string value of the URI. + * - version: (string) Set the protocol version. + * + * @param RequestInterface $request Request to clone and modify. + * @param array $changes Changes to apply. + */ + public static function modifyRequest(RequestInterface $request, array $changes): RequestInterface + { + if (!$changes) { + return $request; + } + + $headers = $request->getHeaders(); + + if (!isset($changes['uri'])) { + $uri = $request->getUri(); + } else { + // Remove the host header if one is on the URI + if ($host = $changes['uri']->getHost()) { + $changes['set_headers']['Host'] = $host; + + if ($port = $changes['uri']->getPort()) { + $standardPorts = ['http' => 80, 'https' => 443]; + $scheme = $changes['uri']->getScheme(); + if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) { + $changes['set_headers']['Host'] .= ':'.$port; + } + } + } + $uri = $changes['uri']; + } + + if (!empty($changes['remove_headers'])) { + $headers = self::caselessRemove($changes['remove_headers'], $headers); + } + + if (!empty($changes['set_headers'])) { + $headers = self::caselessRemove(array_keys($changes['set_headers']), $headers); + $headers = $changes['set_headers'] + $headers; + } + + if (isset($changes['query'])) { + $uri = $uri->withQuery($changes['query']); + } + + if ($request instanceof ServerRequestInterface) { + $new = (new ServerRequest( + $changes['method'] ?? $request->getMethod(), + $uri, + $headers, + $changes['body'] ?? $request->getBody(), + $changes['version'] ?? $request->getProtocolVersion(), + $request->getServerParams() + )) + ->withParsedBody($request->getParsedBody()) + ->withQueryParams($request->getQueryParams()) + ->withCookieParams($request->getCookieParams()) + ->withUploadedFiles($request->getUploadedFiles()); + + foreach ($request->getAttributes() as $key => $value) { + $new = $new->withAttribute($key, $value); + } + + return $new; + } + + return new Request( + $changes['method'] ?? $request->getMethod(), + $uri, + $headers, + $changes['body'] ?? $request->getBody(), + $changes['version'] ?? $request->getProtocolVersion() + ); + } + + /** + * Read a line from the stream up to the maximum allowed buffer length. + * + * @param StreamInterface $stream Stream to read from + * @param int|null $maxLength Maximum buffer length + */ + public static function readLine(StreamInterface $stream, int $maxLength = null): string + { + $buffer = ''; + $size = 0; + + while (!$stream->eof()) { + if ('' === ($byte = $stream->read(1))) { + return $buffer; + } + $buffer .= $byte; + // Break when a new line is found or the max length - 1 is reached + if ($byte === "\n" || ++$size === $maxLength - 1) { + break; + } + } + + return $buffer; + } + + /** + * Create a new stream based on the input type. + * + * Options is an associative array that can contain the following keys: + * - metadata: Array of custom metadata. + * - size: Size of the stream. + * + * This method accepts the following `$resource` types: + * - `Psr\Http\Message\StreamInterface`: Returns the value as-is. + * - `string`: Creates a stream object that uses the given string as the contents. + * - `resource`: Creates a stream object that wraps the given PHP stream resource. + * - `Iterator`: If the provided value implements `Iterator`, then a read-only + * stream object will be created that wraps the given iterable. Each time the + * stream is read from, data from the iterator will fill a buffer and will be + * continuously called until the buffer is equal to the requested read size. + * Subsequent read calls will first read from the buffer and then call `next` + * on the underlying iterator until it is exhausted. + * - `object` with `__toString()`: If the object has the `__toString()` method, + * the object will be cast to a string and then a stream will be returned that + * uses the string value. + * - `NULL`: When `null` is passed, an empty stream object is returned. + * - `callable` When a callable is passed, a read-only stream object will be + * created that invokes the given callable. The callable is invoked with the + * number of suggested bytes to read. The callable can return any number of + * bytes, but MUST return `false` when there is no more data to return. The + * stream object that wraps the callable will invoke the callable until the + * number of requested bytes are available. Any additional bytes will be + * buffered and used in subsequent reads. + * + * @param resource|string|int|float|bool|StreamInterface|callable|\Iterator|null $resource Entity body data + * @param array{size?: int, metadata?: array} $options Additional options + * + * @throws \InvalidArgumentException if the $resource arg is not valid. + */ + public static function streamFor($resource = '', array $options = []): StreamInterface + { + if (is_scalar($resource)) { + $stream = self::tryFopen('php://temp', 'r+'); + if ($resource !== '') { + fwrite($stream, (string) $resource); + fseek($stream, 0); + } + + return new Stream($stream, $options); + } + + switch (gettype($resource)) { + case 'resource': + /* + * The 'php://input' is a special stream with quirks and inconsistencies. + * We avoid using that stream by reading it into php://temp + */ + + /** @var resource $resource */ + if ((\stream_get_meta_data($resource)['uri'] ?? '') === 'php://input') { + $stream = self::tryFopen('php://temp', 'w+'); + stream_copy_to_stream($resource, $stream); + fseek($stream, 0); + $resource = $stream; + } + + return new Stream($resource, $options); + case 'object': + /** @var object $resource */ + if ($resource instanceof StreamInterface) { + return $resource; + } elseif ($resource instanceof \Iterator) { + return new PumpStream(function () use ($resource) { + if (!$resource->valid()) { + return false; + } + $result = $resource->current(); + $resource->next(); + + return $result; + }, $options); + } elseif (method_exists($resource, '__toString')) { + return self::streamFor((string) $resource, $options); + } + break; + case 'NULL': + return new Stream(self::tryFopen('php://temp', 'r+'), $options); + } + + if (is_callable($resource)) { + return new PumpStream($resource, $options); + } + + throw new \InvalidArgumentException('Invalid resource type: '.gettype($resource)); + } + + /** + * Safely opens a PHP stream resource using a filename. + * + * When fopen fails, PHP normally raises a warning. This function adds an + * error handler that checks for errors and throws an exception instead. + * + * @param string $filename File to open + * @param string $mode Mode used to open the file + * + * @return resource + * + * @throws \RuntimeException if the file cannot be opened + */ + public static function tryFopen(string $filename, string $mode) + { + $ex = null; + set_error_handler(static function (int $errno, string $errstr) use ($filename, $mode, &$ex): bool { + $ex = new \RuntimeException(sprintf( + 'Unable to open "%s" using mode "%s": %s', + $filename, + $mode, + $errstr + )); + + return true; + }); + + try { + /** @var resource $handle */ + $handle = fopen($filename, $mode); + } catch (\Throwable $e) { + $ex = new \RuntimeException(sprintf( + 'Unable to open "%s" using mode "%s": %s', + $filename, + $mode, + $e->getMessage() + ), 0, $e); + } + + restore_error_handler(); + + if ($ex) { + /** @var $ex \RuntimeException */ + throw $ex; + } + + return $handle; + } + + /** + * Safely gets the contents of a given stream. + * + * When stream_get_contents fails, PHP normally raises a warning. This + * function adds an error handler that checks for errors and throws an + * exception instead. + * + * @param resource $stream + * + * @throws \RuntimeException if the stream cannot be read + */ + public static function tryGetContents($stream): string + { + $ex = null; + set_error_handler(static function (int $errno, string $errstr) use (&$ex): bool { + $ex = new \RuntimeException(sprintf( + 'Unable to read stream contents: %s', + $errstr + )); + + return true; + }); + + try { + /** @var string|false $contents */ + $contents = stream_get_contents($stream); + + if ($contents === false) { + $ex = new \RuntimeException('Unable to read stream contents'); + } + } catch (\Throwable $e) { + $ex = new \RuntimeException(sprintf( + 'Unable to read stream contents: %s', + $e->getMessage() + ), 0, $e); + } + + restore_error_handler(); + + if ($ex) { + /** @var $ex \RuntimeException */ + throw $ex; + } + + return $contents; + } + + /** + * Returns a UriInterface for the given value. + * + * This function accepts a string or UriInterface and returns a + * UriInterface for the given value. If the value is already a + * UriInterface, it is returned as-is. + * + * @param string|UriInterface $uri + * + * @throws \InvalidArgumentException + */ + public static function uriFor($uri): UriInterface + { + if ($uri instanceof UriInterface) { + return $uri; + } + + if (is_string($uri)) { + return new Uri($uri); + } + + throw new \InvalidArgumentException('URI must be a string or UriInterface'); + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/CHANGELOG.md b/vendor/kubawerlos/php-cs-fixer-custom-fixers/CHANGELOG.md new file mode 100644 index 00000000..c66c87d3 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/CHANGELOG.md @@ -0,0 +1,233 @@ +# CHANGELOG for PHP CS Fixer: custom fixers + +## v3.21.0 +- Deprecate PhpdocArrayStyleFixer - use "phpdoc_array_type" +- Update minimum PHP CS Fixer version to 3.50.0 + +## v3.20.0 +- Deprecate PhpdocTypeListFixer - use "phpdoc_list_type" +- Update minimum PHP CS Fixer version to 3.49.0 + +## v3.19.0 +- Deprecate NumericLiteralSeparatorFixer - use "numeric_literal_separator" +- Update minimum PHP CS Fixer version to 3.47.0 + +## v3.18.0 +- Add PhpdocTypeListFixer + +## v3.17.0 +- PhpdocNoIncorrectVarAnnotationFixer - support promoted properties + +## v3.16.0 +- Deprecate DataProviderReturnTypeFixer - use "php_unit_data_provider_return_type" +- Update minimum PHP CS Fixer version to 3.22.0 + +## v3.15.0 +- Deprecate DataProviderNameFixer - use "php_unit_data_provider_name" +- Deprecate PhpdocParamOrderFixer - use "phpdoc_param_order" +- Update minimum PHP CS Fixer version to 3.19.0 + +## v3.14.0 +- Add EmptyFunctionBodyFixer +- Deprecate DataProviderStaticFixer - use "php_unit_data_provider_static" +- Update minimum PHP CS Fixer version to 3.16.0 + +## v3.13.0 +- MultilinePromotedPropertiesFixer - add option "keep_blank_lines" + +## v3.12.0 +- MultilinePromotedPropertiesFixer - add option "minimum_number_of_parameters" +- Add `bootstrap.php` + +## v3.11.0 +- Add ReadonlyPromotedPropertiesFixer + +## v3.10.0 +- Do not require `friendsofphp/php-cs-fixer` as dependency (to allow using `php-cs-fixer/shim`) + +## v3.9.0 +- Add PhpdocTypesCommaSpacesFixer + +## v3.8.0 +- Update minimum PHP version to 7.4 +- Update minimum PHP CS Fixer version to 3.6.0 +- DataProviderStaticFixer - add option "force" +- Deprecate InternalClassCasingFixer + +## v3.7.0 +- Add NoTrailingCommaInSinglelineFixer + +## v3.6.0 +- Add IssetToArrayKeyExistsFixer +- Add PhpdocVarAnnotationToAssertFixer + +## v3.5.0 +- Add NoUselessDirnameCallFixer + +## v3.4.0 +- Add DeclareAfterOpeningTagFixer + +## v3.3.0 +- Add ConstructorEmptyBracesFixer +- PhpdocNoIncorrectVarAnnotationFixer - remove more incorrect annotations + +## v3.2.0 +- Add PhpUnitAssertArgumentsOrderFixer +- Add PhpUnitDedicatedAssertFixer +- PromotedConstructorPropertyFixer - add option "promote_only_existing_properties" +- NoUselessCommentFixer - support PHPDoc like `/** ClassName */` + +## v3.1.0 +- Add MultilinePromotedPropertiesFixer +- Add PhpdocArrayStyleFixer +- Add PromotedConstructorPropertyFixer +- Restore PhpCsFixerCustomFixers\Analyzer\SwitchAnalyzer (as PhpCsFixer\Tokenizer\Analyzer\SwitchAnalyzer got removed in PHP CS Fixer 3.2.0) + +## v3.0.0 +- Drop support for PHP CS Fixer v2 +- Add StringableInterfaceFixer +- Remove NoUselessSprintfFixer - use "no_useless_sprintf" +- Remove OperatorLinebreakFixer - use "operator_linebreak" +- NoCommentedOutCodeFixer - do not remove URLs +- NoDuplicatedArrayKeyFixer - add option "ignore_expressions" +- NoUselessParenthesisFixer - fix expressions +- PhpdocNoIncorrectVarAnnotationFixer - handle class properties when variable names are different and constants with visibility + +## v2.5.0 +- Add PHP CS Fixer v3 support + +## v2.4.0 +- Allow PHP 8 +- Update PHP CS Fixer to v2.17 +- Deprecate NoUselessSprintfFixer - use "no_useless_sprintf" +- Deprecate OperatorLinebreakFixer - use "operator_linebreak" +- Remove PhpCsFixerCustomFixers\Analyzer\ReferenceAnalyzer - use PhpCsFixer\Tokenizer\Analyzer\ReferenceAnalyzer +- Remove PhpCsFixerCustomFixers\Analyzer\SwitchAnalyzer - use PhpCsFixer\Tokenizer\Analyzer\SwitchAnalyzer + +## v2.3.0 +- Add NoUselessParenthesisFixer +- Add NoUselessStrlenFixer +- DataProviderNameFixer - handle snake_case naming + +## v2.2.0 +- Feature: DataProviderNameFixer - add options "prefix" and "suffix" + +## v2.1.0 +- Add CommentedOutFunctionFixer +- Add NoDuplicatedArrayKeyFixer +- Add NumericLiteralSeparatorFixer + +## v2.0.0 +- Drop PHP 7.1 support +- Remove ImplodeCallFixer - use "implode_call" +- Remove NoTwoConsecutiveEmptyLinesFixer - use "no_extra_blank_lines" +- Remove NoUnneededConcatenationFixer - use NoSuperfluousConcatenationFixer +- Remove NoUselessClassCommentFixer - use NoUselessCommentFixer +- Remove NoUselessConstructorCommentFixer - use NoUselessCommentFixer +- Remove NullableParamStyleFixer - use "nullable_type_declaration_for_default_null_value" +- Remove PhpdocVarAnnotationCorrectOrderFixer - use "phpdoc_var_annotation_correct_order" +- Remove SingleLineThrowFixer - use "single_line_throw" + +## v1.17.0 +- Update PHP CS Fixer to v2.16 +- Add DataProviderStaticFixer +- Add NoSuperfluousConcatenationFixer +- Add PhpdocTypesTrimFixer +- Feature: NoSuperfluousConcatenationFixer - add option "allow_preventing_trailing_spaces" +- Feature: NoSuperfluousConcatenationFixer - handle concatenation of single and double quoted strings together +- Deprecate NoUnneededConcatenationFixer +- Deprecate NullableParamStyleFixer +- Deprecate SingleLineThrowFixer +- Allow symfony/finder 5.0 +- Add Windows OS support with AppVeyor + +## v1.16.0 +- Add PhpdocOnlyAllowedAnnotationsFixer +- Feature: OperatorLinebreakFixer - handle object operators + +## v1.15.0 +- Add CommentSurroundedBySpacesFixer +- Add DataProviderReturnTypeFixer +- Add NoDuplicatedImportsFixer + +## v1.14.0 +- Add DataProviderNameFixer +- Add NoUselessSprintfFixer +- Add PhpUnitNoUselessReturnFixer +- Add SingleLineThrowFixer +- Feature: NoCommentedOutCodeFixer - handle class method + +## v1.13.0 +- Update PHP CS Fixer to v2.14 +- OperatorLinebreakFixer - respect no whitespace around operator +- OperatorLinebreakFixer - support concatenation operator +- Deprecate PhpdocVarAnnotationCorrectOrderFixer + +## v1.12.0 +- Add NoCommentedOutCodeFixer +- Add NoUselessCommentFixer +- Add NullableParamStyleFixer +- Deprecate NoUselessClassCommentFixer +- Deprecate NoUselessConstructorCommentFixer +- Feature: OperatorLinebreakFixer - handle ternary operator +- Fix: NoImportFromGlobalNamespaceFixer - class without namespace +- Fix: NoUselessClassCommentFixer - comment detection +- Fix: TokenRemover - remove last element of file +- Fix: TokenRemover - remove item in line after code +- Fix: NoImportFromGlobalNamespaceFixer - constant named the same as global imported class + +## v1.11.0 +- Add PhpdocParamOrderFixer +- Add InternalClassCasingFixer +- Add SingleSpaceAfterStatementFixer +- Add SingleSpaceBeforeStatementFixer +- Add OperatorLinebreakFixer +- Add MultilineCommentOpeningClosingAloneFixer + +## v1.10.0 +- Add NoUnneededConcatenationFixer +- Add PhpdocNoSuperfluousParamFixer +- Deprecate ImplodeCallFixer +- Deprecate NoTwoConsecutiveEmptyLinesFixer + +## v1.9.0 +- Add NoNullableBooleanTypeFixer + +## v1.8.0 +- Add PhpdocSelfAccessorFixer + +## v1.7.0 +- Add NoReferenceInFunctionDefinitionFixer +- Add NoImportFromGlobalNamespaceFixer + +## v1.6.0 +- Add ImplodeCallFixer +- Add PhpdocSingleLineVarFixer + +## v1.5.0 +- Add NoUselessDoctrineRepositoryCommentFixer + +## v1.4.0 +- Add NoDoctrineMigrationsGeneratedCommentFixer + +## v1.3.0 +- Add PhpdocVarAnnotationCorrectOrderFixer +- Remove @var without type at the beginning in PhpdocNoIncorrectVarAnnotationFixer + +## v1.2.0 +- Add PhpdocNoIncorrectVarAnnotationFixer + +## v1.1.0 +- Update PHP CS Fixer to v2.12 +- Add NoUselessConstructorCommentFixer +- Add PhpdocParamTypeFixer +- Feature: code coverage +- Feature: create Travis stages +- Feature: verify correctness for PHP CS Fixer (without smote tests) +- Fix: false positive class comment + +## v1.0.0 +- Add NoLeadingSlashInGlobalNamespaceFixer +- Add NoPhpStormGeneratedCommentFixer +- Add NoTwoConsecutiveEmptyLinesFixer +- Add NoUselessClassCommentFixer diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/LICENSE b/vendor/kubawerlos/php-cs-fixer-custom-fixers/LICENSE new file mode 100644 index 00000000..c67d51a4 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Kuba Werłos + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/README.md b/vendor/kubawerlos/php-cs-fixer-custom-fixers/README.md new file mode 100644 index 00000000..97e20842 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/README.md @@ -0,0 +1,660 @@ + + +# PHP CS Fixer: custom fixers + +[![Latest stable version](https://img.shields.io/packagist/v/kubawerlos/php-cs-fixer-custom-fixers.svg?label=current%20version)](https://packagist.org/packages/kubawerlos/php-cs-fixer-custom-fixers) +[![PHP version](https://img.shields.io/packagist/php-v/kubawerlos/php-cs-fixer-custom-fixers.svg)](https://php.net) +[![License](https://img.shields.io/github/license/kubawerlos/php-cs-fixer-custom-fixers.svg)](LICENSE) +![Tests](https://img.shields.io/badge/tests-3541-brightgreen.svg) +[![Downloads](https://img.shields.io/packagist/dt/kubawerlos/php-cs-fixer-custom-fixers.svg)](https://packagist.org/packages/kubawerlos/php-cs-fixer-custom-fixers) + +[![CI status](https://github.com/kubawerlos/php-cs-fixer-custom-fixers/actions/workflows/ci.yaml/badge.svg)](https://github.com/kubawerlos/php-cs-fixer-custom-fixers/actions/workflows/ci.yaml) +[![Code coverage](https://img.shields.io/coveralls/github/kubawerlos/php-cs-fixer-custom-fixers/main.svg)](https://coveralls.io/github/kubawerlos/php-cs-fixer-custom-fixers?branch=main) +[![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fkubawerlos%2Fphp-cs-fixer-custom-fixers%2Fmain)](https://dashboard.stryker-mutator.io/reports/github.com/kubawerlos/php-cs-fixer-custom-fixers/main) +[![Psalm type coverage](https://shepherd.dev/github/kubawerlos/php-cs-fixer-custom-fixers/coverage.svg)](https://shepherd.dev/github/kubawerlos/php-cs-fixer-custom-fixers) + +A set of custom fixers for [PHP CS Fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer). + +## Installation +PHP CS Fixer: custom fixers can be installed by running: +```bash +composer require --dev kubawerlos/php-cs-fixer-custom-fixers +``` + + +## Usage +In your PHP CS Fixer configuration register fixers and use them: +```diff + registerCustomFixers(new PhpCsFixerCustomFixers\Fixers()) + ->setRules([ + '@PSR2' => true, + 'array_syntax' => ['syntax' => 'short'], ++ PhpCsFixerCustomFixers\Fixer\NoLeadingSlashInGlobalNamespaceFixer::name() => true, ++ PhpCsFixerCustomFixers\Fixer\PhpdocNoSuperfluousParamFixer::name() => true, + ]); +``` +:warning: When PHP CS Fixer is installed via [`php-cs-fixer/shim`](https://github.com/PHP-CS-Fixer/shim) package, +requiring bootstrap may be needed to load `PhpCsFixerCustomFixers` classes: +```php +require __DIR__ . '/vendor/kubawerlos/php-cs-fixer-custom-fixers/bootstrap.php'; +``` + + +## Fixers +#### CommentSurroundedBySpacesFixer +Comments must be surrounded by spaces. +```diff + addSql("UPDATE t1 SET col1 = col1 + 1"); + } + public function down(Schema $schema) + { +- // this down() migration is auto-generated, please modify it to your needs + $this->addSql("UPDATE t1 SET col1 = col1 - 1"); + } + } +``` + +#### NoDuplicatedArrayKeyFixer +There must be no duplicate array keys. +Configuration options: +- `ignore_expressions` (`bool`): whether to keep duplicated expressions (as they might return different values) or not; defaults to `true` +```diff + 1, + "bar" => 2, + "foo" => 3, + ]; +``` + +#### NoDuplicatedImportsFixer +There must be no duplicate `use` statements. +```diff + 0; ++$isEmpty = $string === ''; ++$isNotEmpty = $string !== ''; +``` + +#### NumericLiteralSeparatorFixer +Numeric literals must have configured separators. + DEPRECATED: use `numeric_literal_separator` instead. +Configuration options: +- `binary` (`bool`, `null`): whether add, remove or ignore separators in binary numbers.; defaults to `false` +- `decimal` (`bool`, `null`): whether add, remove or ignore separators in decimal numbers.; defaults to `false` +- `float` (`bool`, `null`): whether add, remove or ignore separators in float numbers.; defaults to `false` +- `hexadecimal` (`bool`, `null`): whether add, remove or ignore separators in hexadecimal numbers.; defaults to `false` +- `octal` (`bool`, `null`): whether add, remove or ignore separators in octal numbers.; defaults to `false` +```diff + markTestSkipped(); +- return; + } + } +``` + +#### PhpdocArrayStyleFixer +Generic array style should be used in PHPDoc. + DEPRECATED: use `phpdoc_array_type` instead. +```diff + + */ + function foo() { return [1, 2]; } +``` + +#### PhpdocNoIncorrectVarAnnotationFixer +The `@var` annotations must be used correctly in code. +```diff + ++ * @param list + */ + function foo($x) {} +``` + +#### PhpdocTypesCommaSpacesFixer +PHPDoc types commas must not be preceded by a whitespace, and must be succeeded by a single whitespace. +```diff +- */ ++ */ +``` + +#### PhpdocTypesTrimFixer +PHPDoc types must be trimmed. +```diff + bar = $bar; ++ public function __construct(private string $bar) { + } + } +``` + +#### ReadonlyPromotedPropertiesFixer +Promoted properties must be declared as read-only. + *Risky: when property is written.* +```diff + bar(); ++$foo = new Foo(); ++echo $foo->bar(); +``` + +#### SingleSpaceBeforeStatementFixer +Statements not preceded by a line break must be preceded by a single space. +```diff + startIndex = $startIndex; + $this->endIndex = $endIndex; + $this->isConstant = $isConstant; + } + + public function getStartIndex(): int + { + return $this->startIndex; + } + + public function getEndIndex(): int + { + return $this->endIndex; + } + + public function isConstant(): bool + { + return $this->isConstant; + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Analyzer/Analysis/ArrayElementAnalysis.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Analyzer/Analysis/ArrayElementAnalysis.php new file mode 100644 index 00000000..c3b63593 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Analyzer/Analysis/ArrayElementAnalysis.php @@ -0,0 +1,51 @@ +keyStartIndex = $keyStartIndex; + $this->keyEndIndex = $keyEndIndex; + $this->valueStartIndex = $valueStartIndex; + $this->valueEndIndex = $valueEndIndex; + } + + public function getKeyStartIndex(): ?int + { + return $this->keyStartIndex; + } + + public function getKeyEndIndex(): ?int + { + return $this->keyEndIndex; + } + + public function getValueStartIndex(): int + { + return $this->valueStartIndex; + } + + public function getValueEndIndex(): int + { + return $this->valueEndIndex; + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Analyzer/Analysis/CaseAnalysis.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Analyzer/Analysis/CaseAnalysis.php new file mode 100644 index 00000000..615822cf --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Analyzer/Analysis/CaseAnalysis.php @@ -0,0 +1,30 @@ +colonIndex = $colonIndex; + } + + public function getColonIndex(): int + { + return $this->colonIndex; + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Analyzer/Analysis/ConstructorAnalysis.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Analyzer/Analysis/ConstructorAnalysis.php new file mode 100644 index 00000000..a6a2eb10 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Analyzer/Analysis/ConstructorAnalysis.php @@ -0,0 +1,199 @@ +tokens = $tokens; + $this->constructorIndex = $constructorIndex; + } + + public function getConstructorIndex(): int + { + return $this->constructorIndex; + } + + /** + * @return list + */ + public function getConstructorParameterNames(): array + { + $openParenthesis = $this->tokens->getNextTokenOfKind($this->constructorIndex, ['(']); + \assert(\is_int($openParenthesis)); + $closeParenthesis = $this->tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesis); + + $constructorParameterNames = []; + for ($index = $openParenthesis + 1; $index < $closeParenthesis; $index++) { + if (!$this->tokens[$index]->isGivenKind(\T_VARIABLE)) { + continue; + } + + $constructorParameterNames[] = $this->tokens[$index]->getContent(); + } + + return $constructorParameterNames; + } + + /** + * @return array + */ + public function getConstructorPromotableParameters(): array + { + $openParenthesis = $this->tokens->getNextTokenOfKind($this->constructorIndex, ['(']); + \assert(\is_int($openParenthesis)); + $closeParenthesis = $this->tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesis); + + $constructorPromotableParameters = []; + for ($index = $openParenthesis + 1; $index < $closeParenthesis; $index++) { + if (!$this->tokens[$index]->isGivenKind(\T_VARIABLE)) { + continue; + } + + $typeIndex = $this->tokens->getPrevMeaningfulToken($index); + \assert(\is_int($typeIndex)); + if ($this->tokens[$typeIndex]->equalsAny(['(', ',', [\T_CALLABLE], [\T_ELLIPSIS]])) { + continue; + } + + $visibilityIndex = $this->tokens->getPrevTokenOfKind( + $index, + [ + '(', + ',', + [CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE], + [CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED], + [CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC], + ], + ); + \assert(\is_int($visibilityIndex)); + if (!$this->tokens[$visibilityIndex]->equalsAny(['(', ','])) { + continue; + } + + $constructorPromotableParameters[$index] = $this->tokens[$index]->getContent(); + } + + return $constructorPromotableParameters; + } + + /** + * @return array + */ + public function getConstructorPromotableAssignments(): array + { + $openParenthesis = $this->tokens->getNextTokenOfKind($this->constructorIndex, ['(']); + \assert(\is_int($openParenthesis)); + $closeParenthesis = $this->tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesis); + + $openBrace = $this->tokens->getNextTokenOfKind($closeParenthesis, ['{']); + \assert(\is_int($openBrace)); + $closeBrace = $this->tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $openBrace); + + $variables = []; + $properties = []; + $propertyToVariableMap = []; + + for ($index = $openBrace + 1; $index < $closeBrace; $index++) { + if (!$this->tokens[$index]->isGivenKind(\T_VARIABLE)) { + continue; + } + + $semicolonIndex = $this->tokens->getNextMeaningfulToken($index); + \assert(\is_int($semicolonIndex)); + if (!$this->tokens[$semicolonIndex]->equals(';')) { + continue; + } + + $propertyIndex = $this->getPropertyIndex($index, $openBrace); + if ($propertyIndex === null) { + continue; + } + + $properties[$propertyIndex] = $this->tokens[$propertyIndex]->getContent(); + $variables[$index] = $this->tokens[$index]->getContent(); + $propertyToVariableMap[$propertyIndex] = $index; + } + + foreach ($this->getDuplicatesIndices($properties) as $duplicate) { + unset($variables[$propertyToVariableMap[$duplicate]]); + } + + foreach ($this->getDuplicatesIndices($variables) as $duplicate) { + unset($variables[$duplicate]); + } + + return \array_flip($variables); + } + + private function getPropertyIndex(int $index, int $openBrace): ?int + { + $assignmentIndex = $this->tokens->getPrevMeaningfulToken($index); + \assert(\is_int($assignmentIndex)); + if (!$this->tokens[$assignmentIndex]->equals('=')) { + return null; + } + + $propertyIndex = $this->tokens->getPrevMeaningfulToken($assignmentIndex); + if (!$this->tokens[$propertyIndex]->isGivenKind(\T_STRING)) { + return null; + } + \assert(\is_int($propertyIndex)); + + $objectOperatorIndex = $this->tokens->getPrevMeaningfulToken($propertyIndex); + \assert(\is_int($objectOperatorIndex)); + + $thisIndex = $this->tokens->getPrevMeaningfulToken($objectOperatorIndex); + \assert(\is_int($thisIndex)); + if (!$this->tokens[$thisIndex]->equals([\T_VARIABLE, '$this'])) { + return null; + } + + $prevThisIndex = $this->tokens->getPrevMeaningfulToken($thisIndex); + \assert(\is_int($prevThisIndex)); + if ($prevThisIndex > $openBrace && !$this->tokens[$prevThisIndex]->equalsAny(['}', ';'])) { + return null; + } + + return $propertyIndex; + } + + /** + * @param array $array + * + * @return array + */ + private function getDuplicatesIndices(array $array): array + { + $duplicates = []; + $values = []; + foreach ($array as $key => $value) { + if (\array_key_exists($value, $values)) { + $duplicates[$values[$value]] = $values[$value]; + $duplicates[$key] = $key; + } + $values[$value] = $key; + } + + return $duplicates; + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Analyzer/Analysis/SwitchAnalysis.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Analyzer/Analysis/SwitchAnalysis.php new file mode 100644 index 00000000..305976c0 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Analyzer/Analysis/SwitchAnalysis.php @@ -0,0 +1,52 @@ + */ + private array $cases = []; + + /** + * @param list $cases + */ + public function __construct(int $casesStart, int $casesEnd, array $cases) + { + $this->casesStart = $casesStart; + $this->casesEnd = $casesEnd; + $this->cases = $cases; + } + + public function getCasesStart(): int + { + return $this->casesStart; + } + + public function getCasesEnd(): int + { + return $this->casesEnd; + } + + /** + * @return list + */ + public function getCases(): array + { + return $this->cases; + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Analyzer/ArrayAnalyzer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Analyzer/ArrayAnalyzer.php new file mode 100644 index 00000000..ba6297ea --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Analyzer/ArrayAnalyzer.php @@ -0,0 +1,126 @@ + + */ + public function getElements(Tokens $tokens, int $index): array + { + if ($tokens[$index]->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) { + $arrayContentStartIndex = $tokens->getNextMeaningfulToken($index); + \assert(\is_int($arrayContentStartIndex)); + + $arrayContentEndIndex = $tokens->getPrevMeaningfulToken($tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $index)); + \assert(\is_int($arrayContentEndIndex)); + + return $this->getElementsForArrayContent($tokens, $arrayContentStartIndex, $arrayContentEndIndex); + } + + if ($tokens[$index]->isGivenKind(\T_ARRAY)) { + $arrayOpenBraceIndex = $tokens->getNextTokenOfKind($index, ['(']); + \assert(\is_int($arrayOpenBraceIndex)); + + $arrayContentStartIndex = $tokens->getNextMeaningfulToken($arrayOpenBraceIndex); + \assert(\is_int($arrayContentStartIndex)); + + $arrayContentEndIndex = $tokens->getPrevMeaningfulToken($tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $arrayOpenBraceIndex)); + \assert(\is_int($arrayContentEndIndex)); + + return $this->getElementsForArrayContent($tokens, $arrayContentStartIndex, $arrayContentEndIndex); + } + + throw new \InvalidArgumentException(\sprintf('Index %d is not an array.', $index)); + } + + /** + * @return list + */ + private function getElementsForArrayContent(Tokens $tokens, int $startIndex, int $endIndex): array + { + $elements = []; + + $index = $startIndex; + while ($endIndex >= $index = $this->nextCandidateIndex($tokens, $index)) { + if (!$tokens[$index]->equals(',')) { + continue; + } + + $elementEndIndex = $tokens->getPrevMeaningfulToken($index); + \assert(\is_int($elementEndIndex)); + + $elements[] = $this->createArrayElementAnalysis($tokens, $startIndex, $elementEndIndex); + + $startIndex = $tokens->getNextMeaningfulToken($index); + \assert(\is_int($startIndex)); + } + + if ($startIndex <= $endIndex) { + $elements[] = $this->createArrayElementAnalysis($tokens, $startIndex, $endIndex); + } + + return $elements; + } + + private function createArrayElementAnalysis(Tokens $tokens, int $startIndex, int $endIndex): ArrayElementAnalysis + { + $index = $startIndex; + while ($endIndex > $index = $this->nextCandidateIndex($tokens, $index)) { + if (!$tokens[$index]->isGivenKind(\T_DOUBLE_ARROW)) { + continue; + } + + $keyEndIndex = $tokens->getPrevMeaningfulToken($index); + \assert(\is_int($keyEndIndex)); + + $valueStartIndex = $tokens->getNextMeaningfulToken($index); + \assert(\is_int($valueStartIndex)); + + return new ArrayElementAnalysis($startIndex, $keyEndIndex, $valueStartIndex, $endIndex); + } + + return new ArrayElementAnalysis(null, null, $startIndex, $endIndex); + } + + private function nextCandidateIndex(Tokens $tokens, int $index): int + { + if ($tokens[$index]->equals('{')) { + return $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index) + 1; + } + + if ($tokens[$index]->equals('(')) { + return $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index) + 1; + } + + if ($tokens[$index]->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) { + return $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $index) + 1; + } + + if ($tokens[$index]->isGivenKind(\T_ARRAY)) { + $arrayOpenBraceIndex = $tokens->getNextTokenOfKind($index, ['(']); + \assert(\is_int($arrayOpenBraceIndex)); + + return $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $arrayOpenBraceIndex) + 1; + } + + return $index + 1; + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Analyzer/ConstructorAnalyzer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Analyzer/ConstructorAnalyzer.php new file mode 100644 index 00000000..b869087c --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Analyzer/ConstructorAnalyzer.php @@ -0,0 +1,66 @@ +isGivenKind(\T_CLASS)) { + throw new \InvalidArgumentException(\sprintf('Index %d is not a class.', $classIndex)); + } + + $tokensAnalyzer = new TokensAnalyzer($tokens); + + foreach ($tokensAnalyzer->getClassyElements() as $index => $element) { + if ($element['classIndex'] !== $classIndex) { + continue; + } + + if (!$this->isConstructor($tokens, $index, $element)) { + continue; + } + + $constructorAttributes = $tokensAnalyzer->getMethodAttributes($index); + if ($constructorAttributes['abstract']) { + return null; + } + + return new ConstructorAnalysis($tokens, $index); + } + + return null; + } + + /** + * @param array $element + */ + private function isConstructor(Tokens $tokens, int $index, array $element): bool + { + if ($element['type'] !== 'method') { + return false; + } + + $functionNameIndex = $tokens->getNextMeaningfulToken($index); + \assert(\is_int($functionNameIndex)); + + return $tokens[$functionNameIndex]->equals([\T_STRING, '__construct'], false); + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Analyzer/FunctionAnalyzer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Analyzer/FunctionAnalyzer.php new file mode 100644 index 00000000..bd51cef6 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Analyzer/FunctionAnalyzer.php @@ -0,0 +1,118 @@ + + */ + public static function getFunctionArguments(Tokens $tokens, int $index): array + { + $argumentsRange = self::getArgumentsRange($tokens, $index); + if ($argumentsRange === null) { + return []; + } + + [$argumentStartIndex, $argumentsEndIndex] = $argumentsRange; + + $arguments = []; + $index = $currentArgumentStart = $argumentStartIndex; + while ($index < $argumentsEndIndex) { + $blockType = Tokens::detectBlockType($tokens[$index]); + if ($blockType !== null && $blockType['isStart']) { + $index = $tokens->findBlockEnd($blockType['type'], $index); + continue; + } + + $index = $tokens->getNextMeaningfulToken($index); + \assert(\is_int($index)); + + if (!$tokens[$index]->equals(',')) { + continue; + } + + $currentArgumentEnd = $tokens->getPrevMeaningfulToken($index); + \assert(\is_int($currentArgumentEnd)); + + $arguments[] = self::createArgumentAnalysis($tokens, $currentArgumentStart, $currentArgumentEnd); + + $currentArgumentStart = $tokens->getNextMeaningfulToken($index); + \assert(\is_int($currentArgumentStart)); + } + + $arguments[] = self::createArgumentAnalysis($tokens, $currentArgumentStart, $argumentsEndIndex); + + return $arguments; + } + + /** + * @return null|array{int, int} + */ + private static function getArgumentsRange(Tokens $tokens, int $index): ?array + { + if (!$tokens[$index]->isGivenKind([\T_ISSET, \T_STRING])) { + throw new \InvalidArgumentException(\sprintf('Index %d is not a function.', $index)); + } + + $openParenthesis = $tokens->getNextMeaningfulToken($index); + \assert(\is_int($openParenthesis)); + if (!$tokens[$openParenthesis]->equals('(')) { + throw new \InvalidArgumentException(\sprintf('Index %d is not a function.', $index)); + } + + $closeParenthesis = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesis); + + $argumentsEndIndex = $tokens->getPrevMeaningfulToken($closeParenthesis); + \assert(\is_int($argumentsEndIndex)); + + if ($openParenthesis === $argumentsEndIndex) { + return null; + } + if ($tokens[$argumentsEndIndex]->equals(',')) { + $argumentsEndIndex = $tokens->getPrevMeaningfulToken($argumentsEndIndex); + \assert(\is_int($argumentsEndIndex)); + } + + $argumentStartIndex = $tokens->getNextMeaningfulToken($openParenthesis); + \assert(\is_int($argumentStartIndex)); + + return [$argumentStartIndex, $argumentsEndIndex]; + } + + private static function createArgumentAnalysis(Tokens $tokens, int $startIndex, int $endIndex): ArgumentAnalysis + { + $isConstant = true; + + for ($index = $startIndex; $index <= $endIndex; $index++) { + if ($tokens[$index]->isGivenKind(\T_VARIABLE)) { + $isConstant = false; + } + if ($tokens[$index]->equals('(')) { + $prevParenthesisIndex = $tokens->getPrevMeaningfulToken($index); + \assert(\is_int($prevParenthesisIndex)); + + if (!$tokens[$prevParenthesisIndex]->isGivenKind(\T_ARRAY)) { + $isConstant = false; + } + } + } + + return new ArgumentAnalysis($startIndex, $endIndex, $isConstant); + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Analyzer/SwitchAnalyzer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Analyzer/SwitchAnalyzer.php new file mode 100644 index 00000000..d2aefc5d --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Analyzer/SwitchAnalyzer.php @@ -0,0 +1,111 @@ +isGivenKind(\T_SWITCH)) { + throw new \InvalidArgumentException(\sprintf('Index %d is not "switch".', $switchIndex)); + } + + $casesStartIndex = $this->getCasesStart($tokens, $switchIndex); + $casesEndIndex = $this->getCasesEnd($tokens, $casesStartIndex); + + $cases = []; + $index = $casesStartIndex; + while ($index < $casesEndIndex) { + $index = $this->getNextSameLevelToken($tokens, $index); + + if (!$tokens[$index]->isGivenKind([\T_CASE, \T_DEFAULT])) { + continue; + } + + $caseAnalysis = $this->getCaseAnalysis($tokens, $index); + + $cases[] = $caseAnalysis; + } + + return new SwitchAnalysis($casesStartIndex, $casesEndIndex, $cases); + } + + private function getCasesStart(Tokens $tokens, int $switchIndex): int + { + $parenthesisStartIndex = $tokens->getNextMeaningfulToken($switchIndex); + \assert(\is_int($parenthesisStartIndex)); + $parenthesisEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $parenthesisStartIndex); + + $casesStartIndex = $tokens->getNextMeaningfulToken($parenthesisEndIndex); + \assert(\is_int($casesStartIndex)); + + return $casesStartIndex; + } + + private function getCasesEnd(Tokens $tokens, int $casesStartIndex): int + { + if ($tokens[$casesStartIndex]->equals('{')) { + return $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $casesStartIndex); + } + + $index = $casesStartIndex; + while ($index < $tokens->count()) { + $index = $this->getNextSameLevelToken($tokens, $index); + + if ($tokens[$index]->isGivenKind(\T_ENDSWITCH)) { + break; + } + } + + $afterEndswitchIndex = $tokens->getNextMeaningfulToken($index); + \assert(\is_int($afterEndswitchIndex)); + + return $tokens[$afterEndswitchIndex]->equals(';') ? $afterEndswitchIndex : $index; + } + + private function getCaseAnalysis(Tokens $tokens, int $index): CaseAnalysis + { + while ($index < $tokens->count()) { + $index = $this->getNextSameLevelToken($tokens, $index); + + if ($tokens[$index]->equalsAny([':', ';'])) { + break; + } + } + + return new CaseAnalysis($index); + } + + private function getNextSameLevelToken(Tokens $tokens, int $index): int + { + $index = $tokens->getNextMeaningfulToken($index); + \assert(\is_int($index)); + + if ($tokens[$index]->isGivenKind(\T_SWITCH)) { + return (new self())->getSwitchAnalysis($tokens, $index)->getCasesEnd(); + } + + $blockType = Tokens::detectBlockType($tokens[$index]); + if ($blockType !== null && $blockType['isStart']) { + return $tokens->findBlockEnd($blockType['type'], $index) + 1; + } + + return $index; + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/AbstractFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/AbstractFixer.php new file mode 100644 index 00000000..7bf72652 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/AbstractFixer.php @@ -0,0 +1,35 @@ +isTokenKindFound(\T_DOC_COMMENT); + } + + final public function isRisky(): bool + { + return false; + } + + final public function fix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index > 0; $index--) { + if (!$tokens[$index]->isGivenKind([\T_DOC_COMMENT])) { + continue; + } + + $docBlock = new DocBlock($tokens[$index]->getContent()); + + foreach ($docBlock->getAnnotations() as $annotation) { + if (!$annotation->supportTypes()) { + continue; + } + + $typeExpression = $annotation->getTypeExpression(); + if ($typeExpression === null) { + continue; + } + + $type = $typeExpression->toString(); + $type = $this->fixType($type); + $annotation->setTypes([$type]); + } + + $newContent = $docBlock->getContent(); + if ($newContent === $tokens[$index]->getContent()) { + continue; + } + + $tokens[$index] = new Token([\T_DOC_COMMENT, $newContent]); + } + } + + abstract protected function fixType(string $type): string; +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/CommentSurroundedBySpacesFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/CommentSurroundedBySpacesFixer.php new file mode 100644 index 00000000..e4380dfa --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/CommentSurroundedBySpacesFixer.php @@ -0,0 +1,81 @@ +isAnyTokenKindsFound([\T_COMMENT, \T_DOC_COMMENT]); + } + + public function isRisky(): bool + { + return false; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index > 0; $index--) { + if (!$tokens[$index]->isGivenKind([\T_COMMENT, \T_DOC_COMMENT])) { + continue; + } + + // ensure whitespace at the beginning + $newContent = Preg::replace( + '/^(\/\/|#(?!\[)|\/\*+)(?!(?:\/|\*|\s|$))/', + '$1 ', + $tokens[$index]->getContent(), + ); + + // ensure whitespace at the end + $newContent = Preg::replace( + '/(?getContent()) { + continue; + } + + $tokens[$index] = new Token([\strpos($newContent, '/** ') === 0 ? \T_DOC_COMMENT : \T_COMMENT, $newContent]); + } + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/CommentedOutFunctionFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/CommentedOutFunctionFixer.php new file mode 100644 index 00000000..8e2a4ca0 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/CommentedOutFunctionFixer.php @@ -0,0 +1,229 @@ + */ + private array $functions = ['print_r', 'var_dump', 'var_export']; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'The configured functions must be commented out.', + [new CodeSample('setDefault($this->functions) + ->setAllowedTypes(['array']) + ->getOption(), + ]); + } + + /** + * @param array> $configuration + */ + public function configure(array $configuration): void + { + if (\array_key_exists('functions', $configuration)) { + $this->functions = $configuration['functions']; + } + } + + /** + * Must run before CommentSurroundedBySpacesFixer, NoCommentedOutCodeFixer. + */ + public function getPriority(): int + { + return 57; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(\T_STRING); + } + + public function isRisky(): bool + { + return true; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index > 0; $index--) { + if (!$this->isFunctionToFix($tokens, $index)) { + continue; + } + + $startIndex = $index; + + $prevIndex = $tokens->getPrevMeaningfulToken($index); + \assert(\is_int($prevIndex)); + + if ($tokens[$prevIndex]->isGivenKind(\T_NS_SEPARATOR)) { + $startIndex = $prevIndex; + } + + if (!$this->isPreviousTokenSeparateStatement($tokens, $startIndex)) { + continue; + } + + $indexParenthesisStart = $tokens->getNextMeaningfulToken($index); + \assert(\is_int($indexParenthesisStart)); + + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $indexParenthesisStart); + + $semicolonIndex = $tokens->getNextMeaningfulToken($endIndex); + \assert(\is_int($semicolonIndex)); + + if (!$tokens[$semicolonIndex]->equalsAny([';', [\T_CLOSE_TAG]])) { + continue; + } + + if ($tokens[$semicolonIndex]->equals(';')) { + $endIndex = $semicolonIndex; + } + + $this->fixBlock($tokens, $startIndex, $endIndex); + } + } + + private function isFunctionToFix(Tokens $tokens, int $index): bool + { + if (!$tokens[$index]->isGivenKind(\T_STRING)) { + return false; + } + + if (!\in_array(\strtolower($tokens[$index]->getContent()), $this->functions, true)) { + return false; + } + + return (new FunctionsAnalyzer())->isGlobalFunctionCall($tokens, $index); + } + + private function isPreviousTokenSeparateStatement(Tokens $tokens, int $index): bool + { + $prevIndex = $tokens->getPrevMeaningfulToken($index); + \assert(\is_int($prevIndex)); + + if ($tokens[$prevIndex]->equalsAny([';', '{', '}', [\T_OPEN_TAG]])) { + return true; + } + + $switchAnalyzer = new SwitchAnalyzer(); + + if (!$tokens[$prevIndex]->equals(':')) { // can be part of ternary operator or from switch/case + return false; + } + + for ($i = $index; $i > 0; $i--) { + if (!$tokens[$i]->isGivenKind(\T_SWITCH)) { + continue; + } + foreach ($switchAnalyzer->getSwitchAnalysis($tokens, $i)->getCases() as $caseAnalysis) { + if ($caseAnalysis->getColonIndex() === $prevIndex) { + return true; + } + } + } + + return false; + } + + private function fixBlock(Tokens $tokens, int $startIndex, int $endIndex): void + { + if ($this->canUseSingleLineComment($tokens, $startIndex, $endIndex)) { + $this->fixBlockWithSingleLineComments($tokens, $startIndex, $endIndex); + + return; + } + + $tokens->overrideRange( + $startIndex, + $endIndex, + [new Token([\T_COMMENT, '/*' . $tokens->generatePartialCode($startIndex, $endIndex) . '*/'])], + ); + } + + private function canUseSingleLineComment(Tokens $tokens, int $startIndex, int $endIndex): bool + { + if (!$tokens->offsetExists($endIndex + 1)) { + return true; + } + + if (Preg::match('/^\R/', $tokens[$endIndex + 1]->getContent())) { + return true; + } + + for ($index = $startIndex; $index < $endIndex; $index++) { + if (\strpos($tokens[$index]->getContent(), '*/') !== false) { + return true; + } + } + + return false; + } + + private function fixBlockWithSingleLineComments(Tokens $tokens, int $startIndex, int $endIndex): void + { + $codeToCommentOut = $tokens->generatePartialCode($startIndex, $endIndex); + + $prefix = '//'; + if ($tokens[$startIndex - 1]->isWhitespace()) { + $startIndex--; + $prefix = Preg::replace('/(^|\R)(\h*$)/D', '$1//$2', $tokens[$startIndex]->getContent()); + } + $codeToCommentOut = $prefix . \str_replace("\n", "\n//", $codeToCommentOut); + + if ($tokens->offsetExists($endIndex + 1)) { + if (!Preg::match('/^\R/', $tokens[$endIndex + 1]->getContent())) { + $codeToCommentOut .= "\n"; + if ($tokens[$endIndex + 1]->isWhitespace()) { + $endIndex++; + $codeToCommentOut .= $tokens[$endIndex]->getContent(); + } + } + } + + $newTokens = Tokens::fromCode('clearAt(0); + $newTokens->clearEmptyTokens(); + + $tokens->overrideRange( + $startIndex, + $endIndex, + $newTokens, + ); + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/ConstructorEmptyBracesFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/ConstructorEmptyBracesFixer.php new file mode 100644 index 00000000..323025dd --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/ConstructorEmptyBracesFixer.php @@ -0,0 +1,93 @@ +isTokenKindFound('{'); + } + + public function isRisky(): bool + { + return false; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + $constructorAnalyzer = new ConstructorAnalyzer(); + + for ($index = $tokens->count() - 1; $index > 0; $index--) { + if (!$tokens[$index]->isGivenKind(\T_CLASS)) { + continue; + } + + $constructorAnalysis = $constructorAnalyzer->findNonAbstractConstructor($tokens, $index); + if ($constructorAnalysis === null) { + continue; + } + + $openParenthesisIndex = $tokens->getNextTokenOfKind($constructorAnalysis->getConstructorIndex(), ['(']); + \assert(\is_int($openParenthesisIndex)); + + $closeParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesisIndex); + + $openBraceIndex = $tokens->getNextMeaningfulToken($closeParenthesisIndex); + \assert(\is_int($openBraceIndex)); + + $closeBraceIndex = $tokens->getNextNonWhitespace($openBraceIndex); + \assert(\is_int($closeBraceIndex)); + if (!$tokens[$closeBraceIndex]->equals('}')) { + continue; + } + + $tokens->ensureWhitespaceAtIndex($openBraceIndex + 1, 0, ''); + $tokens->ensureWhitespaceAtIndex($closeParenthesisIndex + 1, 0, ' '); + } + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/DataProviderNameFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/DataProviderNameFixer.php new file mode 100644 index 00000000..cd310b5a --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/DataProviderNameFixer.php @@ -0,0 +1,110 @@ +phpUnitDataProviderNameFixer = new PhpUnitDataProviderNameFixer(); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + $this->phpUnitDataProviderNameFixer->getDefinition()->getSummary(), + [ + new CodeSample( + 'setAllowedTypes(['string']) + ->setDefault($this->prefix) + ->getOption(), + (new FixerOptionBuilder('suffix', 'suffix to be added at the end"')) + ->setAllowedTypes(['string']) + ->setDefault($this->suffix) + ->getOption(), + ]); + } + + /** + * @param array $configuration + */ + public function configure(array $configuration): void + { + $this->phpUnitDataProviderNameFixer->configure($configuration); + } + + public function getPriority(): int + { + return $this->phpUnitDataProviderNameFixer->getPriority(); + } + + public function isCandidate(Tokens $tokens): bool + { + return $this->phpUnitDataProviderNameFixer->isCandidate($tokens); + } + + public function isRisky(): bool + { + return $this->phpUnitDataProviderNameFixer->isRisky(); + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + $this->phpUnitDataProviderNameFixer->fix($file, $tokens); + } + + /** + * @return list + */ + public function getSuccessorsNames(): array + { + return [$this->phpUnitDataProviderNameFixer->getName()]; + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/DataProviderReturnTypeFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/DataProviderReturnTypeFixer.php new file mode 100644 index 00000000..19a085b6 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/DataProviderReturnTypeFixer.php @@ -0,0 +1,85 @@ +phpUnitDataProviderReturnTypeFixer = new PhpUnitDataProviderReturnTypeFixer(); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + $this->phpUnitDataProviderReturnTypeFixer->getDefinition()->getSummary(), + [ + new CodeSample( + 'phpUnitDataProviderReturnTypeFixer->getPriority(); + } + + public function isCandidate(Tokens $tokens): bool + { + return $this->phpUnitDataProviderReturnTypeFixer->isCandidate($tokens); + } + + public function isRisky(): bool + { + return $this->phpUnitDataProviderReturnTypeFixer->isRisky(); + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + $this->phpUnitDataProviderReturnTypeFixer->fix($file, $tokens); + } + + /** + * @return list + */ + public function getSuccessorsNames(): array + { + return [$this->phpUnitDataProviderReturnTypeFixer->getName()]; + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/DataProviderStaticFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/DataProviderStaticFixer.php new file mode 100644 index 00000000..e049d428 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/DataProviderStaticFixer.php @@ -0,0 +1,108 @@ +phpUnitDataProviderStaticFixer = new PhpUnitDataProviderStaticFixer(); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + $this->phpUnitDataProviderStaticFixer->getDefinition()->getSummary(), + [ + new CodeSample( + 'setAllowedTypes(['bool']) + ->setDefault($this->force) + ->getOption(), + ]); + } + + /** + * @param array $configuration + */ + public function configure(array $configuration): void + { + if (\array_key_exists('force', $configuration)) { + $this->force = $configuration['force']; + } + $this->phpUnitDataProviderStaticFixer->configure(['force' => $this->force]); + } + + public function getPriority(): int + { + return $this->phpUnitDataProviderStaticFixer->getPriority(); + } + + public function isCandidate(Tokens $tokens): bool + { + return $this->phpUnitDataProviderStaticFixer->isCandidate($tokens); + } + + public function isRisky(): bool + { + return $this->phpUnitDataProviderStaticFixer->isRisky(); + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + $this->phpUnitDataProviderStaticFixer->fix($file, $tokens); + } + + /** + * @return list + */ + public function getSuccessorsNames(): array + { + return [$this->phpUnitDataProviderStaticFixer->getName()]; + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/DeclareAfterOpeningTagFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/DeclareAfterOpeningTagFixer.php new file mode 100644 index 00000000..f3c3a171 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/DeclareAfterOpeningTagFixer.php @@ -0,0 +1,104 @@ +isTokenKindFound(\T_DECLARE); + } + + public function isRisky(): bool + { + return false; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + if (!$tokens[0]->isGivenKind(\T_OPEN_TAG)) { + return; + } + + $openingTagTokenContent = $tokens[0]->getContent(); + + $declareIndex = $tokens->getNextTokenOfKind(0, [[\T_DECLARE]]); + \assert(\is_int($declareIndex)); + + $openParenthesisIndex = $tokens->getNextMeaningfulToken($declareIndex); + \assert(\is_int($openParenthesisIndex)); + + $closeParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesisIndex); + + if (\stripos($tokens->generatePartialCode($openParenthesisIndex, $closeParenthesisIndex), 'strict_types') === false) { + return; + } + + $tokens[0] = new Token([\T_OPEN_TAG, \substr($openingTagTokenContent, 0, 5) . ' ']); + + if ($declareIndex <= 2) { + $tokens->clearRange(1, $declareIndex - 1); + + return; + } + + $semicolonIndex = $tokens->getNextMeaningfulToken($closeParenthesisIndex); + \assert(\is_int($semicolonIndex)); + + $tokensToInsert = []; + for ($index = $declareIndex; $index <= $semicolonIndex; $index++) { + $tokensToInsert[] = $tokens[$index]; + } + + if ($tokens[1]->isGivenKind(\T_WHITESPACE)) { + $tokens[1] = new Token([\T_WHITESPACE, \substr($openingTagTokenContent, 5) . $tokens[1]->getContent()]); + } else { + $tokensToInsert[] = new Token([\T_WHITESPACE, \substr($openingTagTokenContent, 5)]); + } + + if ($tokens[$semicolonIndex + 1]->isGivenKind(\T_WHITESPACE)) { + $content = Preg::replace('/^(\R?)(?=\R)/', '', $tokens[$semicolonIndex + 1]->getContent()); + + $tokens->ensureWhitespaceAtIndex($semicolonIndex + 1, 0, $content); + } + + $tokens->clearRange($declareIndex + 1, $semicolonIndex); + TokenRemover::removeWithLinesIfPossible($tokens, $declareIndex); + + $tokens->insertAt(1, $tokensToInsert); + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/EmptyFunctionBodyFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/EmptyFunctionBodyFixer.php new file mode 100644 index 00000000..849294aa --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/EmptyFunctionBodyFixer.php @@ -0,0 +1,77 @@ +isTokenKindFound(\T_FUNCTION); + } + + public function isRisky(): bool + { + return false; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index > 0; $index--) { + if (!$tokens[$index]->isGivenKind(\T_FUNCTION)) { + continue; + } + + $openBraceIndex = $tokens->getNextTokenOfKind($index, ['{', ';']); + \assert(\is_int($openBraceIndex)); + if (!$tokens[$openBraceIndex]->equals('{')) { + continue; + } + + $closeBraceIndex = $tokens->getNextNonWhitespace($openBraceIndex); + \assert(\is_int($closeBraceIndex)); + if (!$tokens[$closeBraceIndex]->equals('}')) { + continue; + } + + $tokens->ensureWhitespaceAtIndex($openBraceIndex + 1, 0, ''); + + $beforeOpenBraceIndex = $tokens->getPrevNonWhitespace($openBraceIndex); + if (!$tokens[$beforeOpenBraceIndex]->isGivenKind([\T_COMMENT, \T_DOC_COMMENT])) { + $tokens->ensureWhitespaceAtIndex($openBraceIndex - 1, 1, ' '); + } + } + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/InternalClassCasingFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/InternalClassCasingFixer.php new file mode 100644 index 00000000..776c4795 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/InternalClassCasingFixer.php @@ -0,0 +1,69 @@ +classReferenceNameCasingFixer = new ClassReferenceNameCasingFixer(); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Classes defined internally by an extension or the core must be referenced with the correct case.', + [new CodeSample("classReferenceNameCasingFixer->getPriority(); + } + + public function isCandidate(Tokens $tokens): bool + { + return $this->classReferenceNameCasingFixer->isCandidate($tokens); + } + + public function isRisky(): bool + { + return $this->classReferenceNameCasingFixer->isRisky(); + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + $this->classReferenceNameCasingFixer->fix($file, $tokens); + } + + /** + * @return list + */ + public function getSuccessorsNames(): array + { + return [$this->classReferenceNameCasingFixer->getName()]; + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/IssetToArrayKeyExistsFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/IssetToArrayKeyExistsFixer.php new file mode 100644 index 00000000..abaf71d6 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/IssetToArrayKeyExistsFixer.php @@ -0,0 +1,102 @@ +isTokenKindFound(\T_ISSET); + } + + public function isRisky(): bool + { + return true; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index > 0; $index--) { + if (!$tokens[$index]->isGivenKind(\T_ISSET)) { + continue; + } + + if (\count(FunctionAnalyzer::getFunctionArguments($tokens, $index)) !== 1) { + continue; + } + + $openParenthesis = $tokens->getNextMeaningfulToken($index); + \assert(\is_int($openParenthesis)); + + $closeParenthesis = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesis); + + $closeBrackets = $tokens->getPrevMeaningfulToken($closeParenthesis); + \assert(\is_int($closeBrackets)); + if (!$tokens[$closeBrackets]->equals(']')) { + continue; + } + + $openBrackets = $tokens->findBlockStart(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $closeBrackets); + + $keyStartIndex = $tokens->getNextMeaningfulToken($openBrackets); + \assert(\is_int($keyStartIndex)); + $keyEndIndex = $tokens->getPrevMeaningfulToken($closeBrackets); + + $keyTokens = []; + for ($i = $keyStartIndex; $i <= $keyEndIndex; $i++) { + if ($tokens[$i]->equals('')) { + continue; + } + $keyTokens[] = $tokens[$i]; + } + $keyTokens[] = new Token(','); + $keyTokens[] = new Token([\T_WHITESPACE, ' ']); + + $tokens->clearRange($openBrackets, $closeBrackets); + $tokens->insertAt($openParenthesis + 1, $keyTokens); + $tokens[$index] = new Token([\T_STRING, 'array_key_exists']); + } + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/MultilineCommentOpeningClosingAloneFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/MultilineCommentOpeningClosingAloneFixer.php new file mode 100644 index 00000000..7ad9da4e --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/MultilineCommentOpeningClosingAloneFixer.php @@ -0,0 +1,129 @@ +isAnyTokenKindsFound([\T_COMMENT, \T_DOC_COMMENT]); + } + + public function isRisky(): bool + { + return false; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index > 0; $index--) { + if (!$tokens[$index]->isGivenKind([\T_COMMENT, \T_DOC_COMMENT])) { + continue; + } + + if (!Preg::match('/\R/', $tokens[$index]->getContent())) { + continue; + } + + $this->fixOpening($tokens, $index); + $this->fixClosing($tokens, $index); + } + } + + private function fixOpening(Tokens $tokens, int $index): void + { + if (Preg::match('#^/\*+\R#', $tokens[$index]->getContent())) { + return; + } + + Preg::match('#\R(\h*)#', $tokens[$index]->getContent(), $matches); + $indent = $matches[1] . '*'; + + Preg::match('#^(?/\*+)(?.*?)(?\R)(?.*)$#s', $tokens[$index]->getContent(), $matches); + if ($matches === []) { + return; + } + + $opening = $matches['opening']; + \assert(\is_string($opening)); + + $beforeNewline = $matches['before_newline']; + \assert(\is_string($beforeNewline)); + + $newline = $matches['newline']; + \assert(\is_string($newline)); + + $afterNewline = $matches['after_newline']; + \assert(\is_string($afterNewline)); + + if ($beforeNewline[0] !== ' ') { + $indent .= ' '; + } + + if (Preg::match('#^\h+$#', $beforeNewline)) { + $insert = ''; + } else { + $insert = $newline . $indent . $beforeNewline; + } + + $newContent = $opening . $insert . $newline . $afterNewline; + + if ($newContent !== $tokens[$index]->getContent()) { + $tokens[$index] = new Token([Preg::match('~/\*{2}\s~', $newContent) ? \T_DOC_COMMENT : \T_COMMENT, $newContent]); + } + } + + private function fixClosing(Tokens $tokens, int $index): void + { + if (Preg::match('#\R\h*\*+/$#', $tokens[$index]->getContent())) { + return; + } + + Preg::match('#\R(\h*)#', $tokens[$index]->getContent(), $matches); + + $indent = $matches[1]; + \assert(\is_string($indent)); + + $newContent = Preg::replace('#(\R)(.+?)\h*(\*+/)$#', \sprintf('$1$2$1%s$3', $indent), $tokens[$index]->getContent()); + + if ($newContent !== $tokens[$index]->getContent()) { + $id = $tokens[$index]->getId(); + \assert(\is_int($id)); + + $tokens[$index] = new Token([$id, $newContent]); + } + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/MultilinePromotedPropertiesFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/MultilinePromotedPropertiesFixer.php new file mode 100644 index 00000000..3c92b608 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/MultilinePromotedPropertiesFixer.php @@ -0,0 +1,198 @@ +setAllowedTypes(['bool']) + ->setDefault($this->keepBlankLines) + ->getOption(), + (new FixerOptionBuilder('minimum_number_of_parameters', 'minimum number of parameters in the constructor to fix')) + ->setAllowedTypes(['int']) + ->setDefault($this->minimumNumberOfParameters) + ->getOption(), + ]); + } + + /** + * @param array{minimum_number_of_parameters?: int, keep_blank_lines?: bool} $configuration + */ + public function configure(array $configuration): void + { + if (\array_key_exists('minimum_number_of_parameters', $configuration)) { + $this->minimumNumberOfParameters = $configuration['minimum_number_of_parameters']; + } + if (\array_key_exists('keep_blank_lines', $configuration)) { + $this->keepBlankLines = $configuration['keep_blank_lines']; + } + } + + public function setWhitespacesConfig(WhitespacesFixerConfig $config): void + { + $this->whitespacesConfig = $config; + } + + /** + * Must run before BracesFixer. + * Must run after PromotedConstructorPropertyFixer. + */ + public function getPriority(): int + { + return 36; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound([ + CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE, + CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED, + CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC, + ]); + } + + public function isRisky(): bool + { + return false; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + $constructorAnalyzer = new ConstructorAnalyzer(); + + for ($index = $tokens->count() - 1; $index > 0; $index--) { + if (!$tokens[$index]->isGivenKind(\T_CLASS)) { + continue; + } + + $constructorAnalysis = $constructorAnalyzer->findNonAbstractConstructor($tokens, $index); + if ($constructorAnalysis === null) { + continue; + } + + $openParenthesis = $tokens->getNextTokenOfKind($constructorAnalysis->getConstructorIndex(), ['(']); + \assert(\is_int($openParenthesis)); + $closeParenthesis = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesis); + + if (!$this->shouldBeFixed($tokens, $openParenthesis, $closeParenthesis)) { + continue; + } + + $this->fixParameters($tokens, $openParenthesis, $closeParenthesis); + } + } + + private function shouldBeFixed(Tokens $tokens, int $openParenthesis, int $closeParenthesis): bool + { + $promotedParameterFound = false; + $minimumNumberOfParameters = 0; + for ($index = $openParenthesis + 1; $index < $closeParenthesis; $index++) { + if ($tokens[$index]->isGivenKind(\T_VARIABLE)) { + $minimumNumberOfParameters++; + } + if ( + $tokens[$index]->isGivenKind([ + CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE, + CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED, + CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC, + ]) + ) { + $promotedParameterFound = true; + } + } + + return $promotedParameterFound && $minimumNumberOfParameters >= $this->minimumNumberOfParameters; + } + + private function fixParameters(Tokens $tokens, int $openParenthesis, int $closeParenthesis): void + { + $indent = WhitespacesAnalyzer::detectIndent($tokens, $openParenthesis); + + $tokens->ensureWhitespaceAtIndex( + $closeParenthesis - 1, + 1, + $this->whitespacesConfig->getLineEnding() . $indent, + ); + + $index = $tokens->getPrevMeaningfulToken($closeParenthesis); + \assert(\is_int($index)); + + while ($index > $openParenthesis) { + $index = $tokens->getPrevMeaningfulToken($index); + \assert(\is_int($index)); + + $blockType = Tokens::detectBlockType($tokens[$index]); + if ($blockType !== null && !$blockType['isStart']) { + $index = $tokens->findBlockStart($blockType['type'], $index); + continue; + } + + if (!$tokens[$index]->equalsAny(['(', ','])) { + continue; + } + + $this->fixParameter($tokens, $index + 1, $indent); + } + } + + private function fixParameter(Tokens $tokens, int $index, string $indent): void + { + if ($this->keepBlankLines && $tokens[$index]->isWhitespace() && \str_contains($tokens[$index]->getContent(), "\n")) { + return; + } + + $tokens->ensureWhitespaceAtIndex( + $index, + 0, + $this->whitespacesConfig->getLineEnding() . $indent . $this->whitespacesConfig->getIndent(), + ); + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoCommentedOutCodeFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoCommentedOutCodeFixer.php new file mode 100644 index 00000000..39f91d7c --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoCommentedOutCodeFixer.php @@ -0,0 +1,140 @@ +isAnyTokenKindsFound([\T_COMMENT, \T_DOC_COMMENT]); + } + + public function isRisky(): bool + { + return false; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + $commentsAnalyzer = new CommentsAnalyzer(); + + $index = 0; + while (++$index < $tokens->count()) { + if (!$tokens[$index]->isGivenKind([\T_COMMENT, \T_DOC_COMMENT])) { + continue; + } + + if (\strpos($tokens[$index]->getContent(), '/*') === 0) { + $commentIndices = [$index]; + } else { + $commentIndices = $commentsAnalyzer->getCommentBlockIndices($tokens, $index); + } + + $indicesToRemove = $this->getIndicesToRemove($tokens, $commentIndices); + + if ($indicesToRemove === []) { + continue; + } + + foreach ($indicesToRemove as $indexToRemove) { + TokenRemover::removeWithLinesIfPossible($tokens, $indexToRemove); + } + + $index = \max($indicesToRemove); + } + } + + /** + * @param list $commentIndices + * + * @return list + */ + private function getIndicesToRemove(Tokens $tokens, array $commentIndices): array + { + $indicesToRemove = []; + $testedIndices = []; + $content = ''; + + foreach ($commentIndices as $index) { + $content .= \PHP_EOL . $this->getContent($tokens[$index]); + $testedIndices[] = $index; + + if (\rtrim($content) === '') { + continue; + } + + foreach (Preg::split('/\s+/u', $content) as $line) { + if (\filter_var($line, \FILTER_VALIDATE_URL) !== false) { + return []; + } + } + + if ( + $this->isCorrectSyntax('isCorrectSyntax('getContent(); + + if (\strpos($content, '/*') === 0) { + $content = Preg::replace('~^/\*+|\R\s*\*\s+|\*+/$~', \PHP_EOL, $content); + } + + return \ltrim($content, '#/'); + } + + private function isCorrectSyntax(string $content): bool + { + try { + @Tokens::fromCode($content); + } catch (\ParseError $error) { + return false; + } + + return true; + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoDoctrineMigrationsGeneratedCommentFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoDoctrineMigrationsGeneratedCommentFixer.php new file mode 100644 index 00000000..0f0dbdd3 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoDoctrineMigrationsGeneratedCommentFixer.php @@ -0,0 +1,86 @@ +addSql("UPDATE t1 SET col1 = col1 + 1"); + } + public function down(Schema $schema) + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql("UPDATE t1 SET col1 = col1 - 1"); + } +} +')], + '', + ); + } + + /** + * Must run before BracesFixer. + */ + public function getPriority(): int + { + return 36; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound([\T_COMMENT, \T_DOC_COMMENT]); + } + + public function isRisky(): bool + { + return false; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index > 0; $index--) { + if (!$tokens[$index]->isGivenKind([\T_COMMENT, \T_DOC_COMMENT])) { + continue; + } + + if ( + \strpos($tokens[$index]->getContent(), 'Auto-generated Migration: Please modify to your needs!') === false + && \strpos($tokens[$index]->getContent(), 'this up() migration is auto-generated, please modify it to your needs') === false + && \strpos($tokens[$index]->getContent(), 'this down() migration is auto-generated, please modify it to your needs') === false + ) { + continue; + } + + TokenRemover::removeWithLinesIfPossible($tokens, $index); + } + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoDuplicatedArrayKeyFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoDuplicatedArrayKeyFixer.php new file mode 100644 index 00000000..67edd7c9 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoDuplicatedArrayKeyFixer.php @@ -0,0 +1,145 @@ + 1, + "bar" => 2, + "foo" => 3, +]; +')], + '', + ); + } + + public function getConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('ignore_expressions', 'whether to keep duplicated expressions (as they might return different values) or not')) + ->setAllowedTypes(['bool']) + ->setDefault($this->ignoreExpressions) + ->getOption(), + ]); + } + + /** + * @param array $configuration + */ + public function configure(array $configuration): void + { + if (\array_key_exists('ignore_expressions', $configuration)) { + $this->ignoreExpressions = $configuration['ignore_expressions']; + } + } + + public function getPriority(): int + { + return 0; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound([\T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN]); + } + + public function isRisky(): bool + { + return false; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index > 0; $index--) { + if (!$tokens[$index]->isGivenKind([\T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN])) { + continue; + } + + $this->fixArray($tokens, $index); + } + } + + private function fixArray(Tokens $tokens, int $index): void + { + $arrayAnalyzer = new ArrayAnalyzer(); + + $foundKeys = []; + foreach (\array_reverse($arrayAnalyzer->getElements($tokens, $index)) as $arrayElementAnalysis) { + $key = $this->getKeyContentIfPossible($tokens, $arrayElementAnalysis); + if ($key === null) { + continue; + } + if (\in_array($key, $foundKeys, true)) { + $startIndex = $arrayElementAnalysis->getKeyStartIndex(); + \assert(\is_int($startIndex)); + + $endIndex = $tokens->getNextMeaningfulToken($arrayElementAnalysis->getValueEndIndex()); + \assert(\is_int($endIndex)); + + if ($tokens[$endIndex + 1]->isWhitespace() && Preg::match('/^\h+$/', $tokens[$endIndex + 1]->getContent())) { + $endIndex++; + } + + $tokens->clearRange($startIndex + 1, $endIndex); + TokenRemover::removeWithLinesIfPossible($tokens, $startIndex); + } + $foundKeys[] = $key; + } + } + + private function getKeyContentIfPossible(Tokens $tokens, ArrayElementAnalysis $arrayElementAnalysis): ?string + { + if ($arrayElementAnalysis->getKeyStartIndex() === null) { + return null; + } + + $keyEndIndex = $arrayElementAnalysis->getKeyEndIndex(); + \assert(\is_int($keyEndIndex)); + + $content = ''; + for ($index = $keyEndIndex; $index >= $arrayElementAnalysis->getKeyStartIndex(); $index--) { + if ($tokens[$index]->isWhitespace() || $tokens[$index]->isComment()) { + continue; + } + + if ($this->ignoreExpressions && $tokens[$index]->equalsAny([[\T_VARIABLE], '('])) { + return null; + } + + $content .= $tokens[$index]->getContent(); + } + + return $content; + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoDuplicatedImportsFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoDuplicatedImportsFixer.php new file mode 100644 index 00000000..758d7365 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoDuplicatedImportsFixer.php @@ -0,0 +1,94 @@ +isTokenKindFound(\T_USE); + } + + public function isRisky(): bool + { + return false; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + $useDeclarations = (new NamespaceUsesAnalyzer())->getDeclarationsFromTokens($tokens); + + foreach ((new NamespacesAnalyzer())->getDeclarations($tokens) as $namespace) { + $currentNamespaceUseDeclarations = \array_filter( + $useDeclarations, + static function (NamespaceUseAnalysis $useDeclaration) use ($namespace): bool { + return $useDeclaration->getStartIndex() >= $namespace->getScopeStartIndex() + && $useDeclaration->getEndIndex() <= $namespace->getScopeEndIndex(); + }, + ); + + $foundDeclarations = []; + foreach ($currentNamespaceUseDeclarations as $useDeclaration) { + $key = \sprintf('key_%d_%s', $useDeclaration->getType(), $useDeclaration->getShortName()); + if (\in_array($key, $foundDeclarations, true)) { + $this->removeUseDeclaration($tokens, $useDeclaration); + } + $foundDeclarations[] = $key; + } + } + } + + private function removeUseDeclaration(Tokens $tokens, NamespaceUseAnalysis $useDeclaration): void + { + $removeUseDeclaration = static function ( + NoUnusedImportsFixer $noUnusedImportsFixer, + Tokens $tokens, + NamespaceUseAnalysis $useDeclaration + ): void { + $noUnusedImportsFixer->removeUseDeclaration($tokens, $useDeclaration); + }; + + $noUnusedImportsFixer = new NoUnusedImportsFixer(); + + $removeUseDeclaration = \Closure::bind($removeUseDeclaration, null, $noUnusedImportsFixer); + + $removeUseDeclaration($noUnusedImportsFixer, $tokens, $useDeclaration); + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoImportFromGlobalNamespaceFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoImportFromGlobalNamespaceFixer.php new file mode 100644 index 00000000..d4b66b98 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoImportFromGlobalNamespaceFixer.php @@ -0,0 +1,190 @@ +isTokenKindFound(\T_USE); + } + + public function isRisky(): bool + { + return false; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + foreach (\array_reverse((new NamespacesAnalyzer())->getDeclarations($tokens)) as $namespace) { + $this->fixImports($tokens, $namespace->getScopeStartIndex(), $namespace->getScopeEndIndex(), $namespace->getFullName() === ''); + } + } + + private function fixImports(Tokens $tokens, int $startIndex, int $endIndex, bool $isInGlobalNamespace): void + { + $importedClassesIndices = self::getImportCandidateIndices($tokens, $startIndex, $endIndex); + + if (!$isInGlobalNamespace) { + for ($index = $endIndex; $index > $startIndex; $index--) { + if ($tokens[$index]->isGivenKind(\T_DOC_COMMENT)) { + $importedClassesIndices = $this->updateComment($tokens, $importedClassesIndices, $index); + continue; + } + + if (!$tokens[$index]->isGivenKind(\T_STRING)) { + continue; + } + + $importedClassesIndices = $this->updateUsage($tokens, $importedClassesIndices, $index); + } + } + + self::clearImports($tokens, $importedClassesIndices); + } + + /** + * @return array + */ + private static function getImportCandidateIndices(Tokens $tokens, int $startIndex, int $endIndex): array + { + $importedClassesIndices = []; + + foreach (\array_keys($tokens->findGivenKind(\T_USE, $startIndex, $endIndex)) as $index) { + $classNameIndex = $tokens->getNextMeaningfulToken($index); + \assert(\is_int($classNameIndex)); + + if ($tokens[$classNameIndex]->isGivenKind(\T_NS_SEPARATOR)) { + $classNameIndex = $tokens->getNextMeaningfulToken($classNameIndex); + \assert(\is_int($classNameIndex)); + } + + $semicolonIndex = $tokens->getNextMeaningfulToken($classNameIndex); + \assert(\is_int($semicolonIndex)); + + if (!$tokens[$semicolonIndex]->equals(';')) { + continue; + } + + $importedClassesIndices[$tokens[$classNameIndex]->getContent()] = $classNameIndex; + } + + return $importedClassesIndices; + } + + /** + * @param array $importedClassesIndices + * + * @return array + */ + private function updateComment(Tokens $tokens, array $importedClassesIndices, int $index): array + { + $content = $tokens[$index]->getContent(); + + foreach ($importedClassesIndices as $importedClassName => $importedClassIndex) { + $content = Preg::replace(\sprintf('/\b(?getContent()) { + $tokens[$index] = new Token([\T_DOC_COMMENT, $content]); + } + + return $importedClassesIndices; + } + + /** + * @param array $importedClassesIndices + * + * @return array + */ + private function updateUsage(Tokens $tokens, array $importedClassesIndices, int $index): array + { + if (!\in_array($tokens[$index]->getContent(), \array_keys($importedClassesIndices), true)) { + return $importedClassesIndices; + } + + $prevIndex = $tokens->getPrevMeaningfulToken($index); + \assert(\is_int($prevIndex)); + + if ($tokens[$prevIndex]->isGivenKind([\T_CONST, \T_DOUBLE_COLON, \T_FUNCTION, \T_NS_SEPARATOR, \T_OBJECT_OPERATOR, \T_USE])) { + return $importedClassesIndices; + } + + $nextIndex = $tokens->getNextMeaningfulToken($index); + \assert(\is_int($nextIndex)); + + if ($tokens[$nextIndex]->isGivenKind(\T_NS_SEPARATOR)) { + $importedClassesIndices[$tokens[$index]->getContent()] = null; + + return $importedClassesIndices; + } + + $tokens->insertAt($index, new Token([\T_NS_SEPARATOR, '\\'])); + + return $importedClassesIndices; + } + + /** + * @param array $importedClassesIndices + */ + private static function clearImports(Tokens $tokens, array $importedClassesIndices): void + { + foreach ($importedClassesIndices as $importedClassIndex) { + if ($importedClassIndex === null) { + continue; + } + $useIndex = $tokens->getPrevTokenOfKind($importedClassIndex, [[\T_USE]]); + \assert(\is_int($useIndex)); + + $semicolonIndex = $tokens->getNextTokenOfKind($importedClassIndex, [';']); + \assert(\is_int($semicolonIndex)); + + $tokens->clearRange($useIndex, $semicolonIndex); + TokenRemover::removeWithLinesIfPossible($tokens, $useIndex); + } + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoLeadingSlashInGlobalNamespaceFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoLeadingSlashInGlobalNamespaceFixer.php new file mode 100644 index 00000000..a306d334 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoLeadingSlashInGlobalNamespaceFixer.php @@ -0,0 +1,112 @@ +isTokenKindFound(\T_NS_SEPARATOR); + } + + public function isRisky(): bool + { + return false; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + $index = 0; + while (++$index < $tokens->count()) { + $index = $this->skipNamespacedCode($tokens, $index); + + if (!$this->isToRemove($tokens, $index)) { + continue; + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } + } + + private function isToRemove(Tokens $tokens, int $index): bool + { + if (!$tokens[$index]->isGivenKind(\T_NS_SEPARATOR)) { + return false; + } + + $prevIndex = $tokens->getPrevMeaningfulToken($index); + \assert(\is_int($prevIndex)); + + if ($tokens[$prevIndex]->isGivenKind(\T_STRING)) { + return false; + } + if ($tokens[$prevIndex]->isGivenKind([\T_NEW, CT::T_NULLABLE_TYPE, CT::T_TYPE_COLON])) { + return true; + } + + $nextIndex = $tokens->getTokenNotOfKindSibling($index, 1, [[\T_COMMENT], [\T_DOC_COMMENT], [\T_NS_SEPARATOR], [\T_STRING], [\T_WHITESPACE]]); + \assert(\is_int($nextIndex)); + + if ($tokens[$nextIndex]->isGivenKind(\T_DOUBLE_COLON)) { + return true; + } + + return $tokens[$prevIndex]->equalsAny(['(', ',', [CT::T_TYPE_ALTERNATION]]) && $tokens[$nextIndex]->isGivenKind([\T_VARIABLE, CT::T_TYPE_ALTERNATION]); + } + + private function skipNamespacedCode(Tokens $tokens, int $index): int + { + if (!$tokens[$index]->isGivenKind(\T_NAMESPACE)) { + return $index; + } + + $nextIndex = $tokens->getNextMeaningfulToken($index); + \assert(\is_int($nextIndex)); + + if ($tokens[$nextIndex]->equals('{')) { + return $nextIndex; + } + + $nextIndex = $tokens->getNextTokenOfKind($index, ['{', ';']); + \assert(\is_int($nextIndex)); + + if ($tokens[$nextIndex]->equals(';')) { + return $tokens->count() - 1; + } + + return $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $nextIndex); + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoNullableBooleanTypeFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoNullableBooleanTypeFixer.php new file mode 100644 index 00000000..effc8e15 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoNullableBooleanTypeFixer.php @@ -0,0 +1,75 @@ +isTokenKindFound(\T_STRING); + } + + public function isRisky(): bool + { + return true; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index > 0; $index--) { + if ($tokens[$index]->getContent() !== '?') { + continue; + } + + $nextIndex = $tokens->getNextMeaningfulToken($index); + \assert(\is_int($nextIndex)); + + if (!$tokens[$nextIndex]->equals([\T_STRING, 'bool'], false) && !$tokens[$nextIndex]->equals([\T_STRING, 'boolean'], false)) { + continue; + } + + $nextNextIndex = $tokens->getNextMeaningfulToken($nextIndex); + \assert(\is_int($nextNextIndex)); + + if (!$tokens[$nextNextIndex]->isGivenKind(\T_VARIABLE) && $tokens[$nextNextIndex]->getContent() !== '{') { + continue; + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoPhpStormGeneratedCommentFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoPhpStormGeneratedCommentFixer.php new file mode 100644 index 00000000..2eb5ce8a --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoPhpStormGeneratedCommentFixer.php @@ -0,0 +1,69 @@ +isAnyTokenKindsFound([\T_COMMENT, \T_DOC_COMMENT]); + } + + public function isRisky(): bool + { + return false; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index > 0; $index--) { + if (!$tokens[$index]->isGivenKind([\T_COMMENT, \T_DOC_COMMENT])) { + continue; + } + + if (!Preg::match('/\*\h+Created by PhpStorm/i', $tokens[$index]->getContent(), $matches)) { + continue; + } + + TokenRemover::removeWithLinesIfPossible($tokens, $index); + } + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoReferenceInFunctionDefinitionFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoReferenceInFunctionDefinitionFixer.php new file mode 100644 index 00000000..fb472ffb --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoReferenceInFunctionDefinitionFixer.php @@ -0,0 +1,89 @@ +isAnyTokenKindsFound([\T_FUNCTION]); + } + + public function isRisky(): bool + { + return true; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index > 0; $index--) { + if (!$tokens[$index]->isGivenKind(\T_FUNCTION)) { + continue; + } + + $indices = $this->getArgumentStartIndices($tokens, $index); + + foreach ($indices as $i) { + if ($tokens[$i]->getContent() === '&') { + $tokens->clearTokenAndMergeSurroundingWhitespace($i); + } + } + } + } + + /** + * @return list + */ + private function getArgumentStartIndices(Tokens $tokens, int $functionNameIndex): array + { + $argumentsAnalyzer = new ArgumentsAnalyzer(); + + $openParenthesis = $tokens->getNextTokenOfKind($functionNameIndex, ['(']); + \assert(\is_int($openParenthesis)); + + $closeParenthesis = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesis); + + $indices = []; + + foreach (\array_keys($argumentsAnalyzer->getArguments($tokens, $openParenthesis, $closeParenthesis)) as $startIndexCandidate) { + $index = $tokens->getNextMeaningfulToken($startIndexCandidate - 1); + \assert(\is_int($index)); + + $indices[] = $index; + } + + return $indices; + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoSuperfluousConcatenationFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoSuperfluousConcatenationFixer.php new file mode 100644 index 00000000..fcfa1061 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoSuperfluousConcatenationFixer.php @@ -0,0 +1,190 @@ +setAllowedTypes(['bool']) + ->setDefault($this->allowPreventingTrailingSpaces) + ->getOption(), + ]); + } + + /** + * @param array $configuration + */ + public function configure(array $configuration): void + { + if (\array_key_exists('allow_preventing_trailing_spaces', $configuration)) { + $this->allowPreventingTrailingSpaces = $configuration['allow_preventing_trailing_spaces']; + } + } + + /** + * Must run after SingleLineThrowFixer. + */ + public function getPriority(): int + { + return 0; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAllTokenKindsFound(['.', \T_CONSTANT_ENCAPSED_STRING]); + } + + public function isRisky(): bool + { + return false; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index > 0; $index--) { + if (!$tokens[$index]->equals('.')) { + continue; + } + + $firstIndex = $tokens->getPrevMeaningfulToken($index); + \assert(\is_int($firstIndex)); + + if (!$tokens[$firstIndex]->isGivenKind(\T_CONSTANT_ENCAPSED_STRING)) { + continue; + } + if (!$this->areOnlyHorizontalWhitespacesBetween($tokens, $firstIndex, $index)) { + continue; + } + + $secondIndex = $tokens->getNextMeaningfulToken($index); + \assert(\is_int($secondIndex)); + + if (!$tokens[$secondIndex]->isGivenKind(\T_CONSTANT_ENCAPSED_STRING)) { + continue; + } + if (!$this->areOnlyHorizontalWhitespacesBetween($tokens, $index, $secondIndex)) { + continue; + } + + $this->fixConcat($tokens, $firstIndex, $secondIndex); + } + } + + private function areOnlyHorizontalWhitespacesBetween(Tokens $tokens, int $indexStart, int $indexEnd): bool + { + for ($index = $indexStart + 1; $index < $indexEnd; $index++) { + if (!$tokens[$index]->isGivenKind(\T_WHITESPACE)) { + return false; + } + if (Preg::match('/\R/', $tokens[$index]->getContent())) { + return false; + } + } + + return true; + } + + private function fixConcat(Tokens $tokens, int $firstIndex, int $secondIndex): void + { + $prefix = ''; + $firstContent = $tokens[$firstIndex]->getContent(); + $secondContent = $tokens[$secondIndex]->getContent(); + + if ( + $this->allowPreventingTrailingSpaces + && Preg::match('/\h(\\\'|")$/', $firstContent) + && Preg::match('/^(\\\'|")\R/', $secondContent) + ) { + return; + } + + if (\strtolower($firstContent[0]) === 'b') { + $prefix = $firstContent[0]; + $firstContent = \ltrim($firstContent, 'bB'); + } + + $secondContent = \ltrim($secondContent, 'bB'); + + $border = $firstContent[0] === '"' || $secondContent[0] === '"' ? '"' : "'"; + + $tokens->overrideRange( + $firstIndex, + $secondIndex, + [ + new Token( + [\T_CONSTANT_ENCAPSED_STRING, + $prefix . $border . $this->getContentForBorder($firstContent, $border, true) . $this->getContentForBorder($secondContent, $border, false) . $border, + ], + ), + ], + ); + } + + private function getContentForBorder(string $content, string $targetBorder, bool $escapeDollarWhenIsLastCharacter): string + { + $currentBorder = $content[0]; + $content = \substr($content, 1, -1); + + if ($content === '') { + return ''; + } + + if ($currentBorder === '"') { + if ($escapeDollarWhenIsLastCharacter && $content[\strlen($content) - 1] === '$') { + $content = \substr($content, 0, -1) . '\$'; + } + + return $content; + } + if ($targetBorder === "'") { + return $content; + } + + // unescape single quote + $content = \str_replace('\\\'', '\'', $content); + + // escape dollar sign + $content = \str_replace('$', '\$', $content); + + // escape double quote + return Preg::replace( + '/(?isAnyTokenKindsFound([\T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN, CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, '(']); + } + + public function isRisky(): bool + { + return false; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index >= 0; $index--) { + if (!$tokens[$index]->equalsAny([')', [CT::T_ARRAY_SQUARE_BRACE_CLOSE], [CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE]])) { + continue; + } + + $this->removeCommas($tokens, $index); + } + } + + private function removeCommas(Tokens $tokens, int $index): void + { + $commaIndex = $tokens->getPrevMeaningfulToken($index); + \assert(\is_int($commaIndex)); + + while ($tokens[$commaIndex]->equals(',')) { + if ($tokens->isPartialCodeMultiline($commaIndex, $index)) { + return; + } + + $tokens->removeLeadingWhitespace($commaIndex); + $tokens->removeTrailingWhitespace($commaIndex); + $tokens->clearAt($commaIndex); + + $commaIndex = $tokens->getPrevMeaningfulToken($commaIndex); + \assert(\is_int($commaIndex)); + } + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoUselessCommentFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoUselessCommentFixer.php new file mode 100644 index 00000000..281c21eb --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoUselessCommentFixer.php @@ -0,0 +1,134 @@ +isAnyTokenKindsFound([\T_COMMENT, \T_DOC_COMMENT]); + } + + public function isRisky(): bool + { + return false; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index > 0; $index--) { + if (!$tokens[$index]->isGivenKind([\T_COMMENT, \T_DOC_COMMENT])) { + continue; + } + + $newContent = $this->getNewContent($tokens, $index); + + if ($newContent === $tokens[$index]->getContent()) { + continue; + } + + $id = $tokens[$index]->getId(); + \assert(\is_int($id)); + + $tokens[$index] = new Token([$id, $newContent]); + } + } + + private function getNewContent(Tokens $tokens, int $index): string + { + $content = $tokens[$index]->getContent(); + + $nextIndex = $tokens->getTokenNotOfKindSibling( + $index, + 1, + [[\T_WHITESPACE], [\T_COMMENT], [\T_ABSTRACT], [\T_FINAL], [\T_PUBLIC], [\T_PROTECTED], [\T_PRIVATE], [\T_STATIC]], + ); + + if ($nextIndex === null) { + return $content; + } + + if ($tokens[$nextIndex]->isGivenKind([\T_CLASS, \T_INTERFACE, \T_TRAIT])) { + $classyNameIndex = $tokens->getNextMeaningfulToken($nextIndex); + \assert(\is_int($classyNameIndex)); + + $content = Preg::replace( + \sprintf('~ + \R? + (?<=\n|\r|\r\n|^\#|^/{2}|^/\*[^\*\s]|^/\*{2}) + \h*\**\h* + ( + (class|interface|trait)\h+([a-zA-Z\d\\\]+) + | + %s + ) + \.? + \h* + (?=\R|\*/$|$) + ~ix', $tokens[$classyNameIndex]->getContent()), + '', + $content, + ); + } elseif ($tokens[$nextIndex]->isGivenKind(\T_FUNCTION)) { + $content = Preg::replace( + '/\R?(?<=\n|\r|\r\n|^#|^\/\/|^\/\*|^\/\*\*)\h+\**\h*((adds?|gets?|removes?|sets?)\h+[A-Za-z0-9\\\_]+|([A-Za-z0-9\\\_]+\h+)?constructor).?(?=\R|$)/i', + '', + $content, + ); + } else { + return $content; + } + + if ($content === '/***/') { + $content = '/** */'; + } + + return $content; + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoUselessDirnameCallFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoUselessDirnameCallFixer.php new file mode 100644 index 00000000..00e047f9 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoUselessDirnameCallFixer.php @@ -0,0 +1,163 @@ +isTokenKindFound(\T_DIR); + } + + public function isRisky(): bool + { + return false; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index > 0; $index--) { + if (!$tokens[$index]->isGivenKind(\T_DIR)) { + continue; + } + + $prevInserts = $this->getPrevTokensUpdates($tokens, $index); + if ($prevInserts === null) { + continue; + } + + $nextInserts = $this->getNextTokensUpdates($tokens, $index); + if ($nextInserts === null) { + continue; + } + + foreach ($prevInserts + $nextInserts as $i => $content) { + if ($content === '') { + $tokens->clearTokenAndMergeSurroundingWhitespace($i); + } else { + $tokens[$i] = new Token([\T_CONSTANT_ENCAPSED_STRING, $content]); + } + } + } + } + + /** + * @return null|array + */ + private function getPrevTokensUpdates(Tokens $tokens, int $index): ?array + { + $updates = []; + + $openParenthesisIndex = $tokens->getPrevMeaningfulToken($index); + \assert(\is_int($openParenthesisIndex)); + if (!$tokens[$openParenthesisIndex]->equals('(')) { + return null; + } + $updates[$openParenthesisIndex] = ''; + + $dirnameCallIndex = $tokens->getPrevMeaningfulToken($openParenthesisIndex); + \assert(\is_int($dirnameCallIndex)); + if (!$tokens[$dirnameCallIndex]->equals([\T_STRING, 'dirname'], false)) { + return null; + } + if (!(new FunctionsAnalyzer())->isGlobalFunctionCall($tokens, $dirnameCallIndex)) { + return null; + } + $updates[$dirnameCallIndex] = ''; + + $namespaceSeparatorIndex = $tokens->getPrevMeaningfulToken($dirnameCallIndex); + \assert(\is_int($namespaceSeparatorIndex)); + if ($tokens[$namespaceSeparatorIndex]->isGivenKind(\T_NS_SEPARATOR)) { + $updates[$namespaceSeparatorIndex] = ''; + } + + return $updates; + } + + /** + * @return null|array + */ + private function getNextTokensUpdates(Tokens $tokens, int $index): ?array + { + $depthLevel = 1; + $updates = []; + + $commaOrClosingParenthesisIndex = $tokens->getNextMeaningfulToken($index); + \assert(\is_int($commaOrClosingParenthesisIndex)); + if ($tokens[$commaOrClosingParenthesisIndex]->equals(',')) { + $updates[$commaOrClosingParenthesisIndex] = ''; + $afterCommaIndex = $tokens->getNextMeaningfulToken($commaOrClosingParenthesisIndex); + \assert(\is_int($afterCommaIndex)); + if ($tokens[$afterCommaIndex]->isGivenKind(\T_LNUMBER)) { + $depthLevel = (int) $tokens[$afterCommaIndex]->getContent(); + $updates[$afterCommaIndex] = ''; + $commaOrClosingParenthesisIndex = $tokens->getNextMeaningfulToken($afterCommaIndex); + \assert(\is_int($commaOrClosingParenthesisIndex)); + } + } + + if ($tokens[$commaOrClosingParenthesisIndex]->equals(',')) { + $updates[$commaOrClosingParenthesisIndex] = ''; + $commaOrClosingParenthesisIndex = $tokens->getNextMeaningfulToken($commaOrClosingParenthesisIndex); + \assert(\is_int($commaOrClosingParenthesisIndex)); + } + $closingParenthesisIndex = $commaOrClosingParenthesisIndex; + + if (!$tokens[$closingParenthesisIndex]->equals(')')) { + return null; + } + $updates[$closingParenthesisIndex] = ''; + + $concatenationIndex = $tokens->getNextMeaningfulToken($closingParenthesisIndex); + \assert(\is_int($concatenationIndex)); + if (!$tokens[$concatenationIndex]->equals('.')) { + return null; + } + + $stringIndex = $tokens->getNextMeaningfulToken($concatenationIndex); + \assert(\is_int($stringIndex)); + if (!$tokens[$stringIndex]->isGivenKind(\T_CONSTANT_ENCAPSED_STRING)) { + return null; + } + + $stringContent = $tokens[$stringIndex]->getContent(); + $updates[$stringIndex] = \substr($stringContent, 0, 1) . \str_repeat('/..', $depthLevel) . \substr($stringContent, 1); + + return $updates; + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoUselessDoctrineRepositoryCommentFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoUselessDoctrineRepositoryCommentFixer.php new file mode 100644 index 00000000..8f7e3b0e --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoUselessDoctrineRepositoryCommentFixer.php @@ -0,0 +1,68 @@ +isTokenKindFound(\T_DOC_COMMENT); + } + + public function isRisky(): bool + { + return false; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index > 0; $index--) { + if (!$tokens[$index]->isGivenKind(\T_DOC_COMMENT)) { + continue; + } + + if (\strpos($tokens[$index]->getContent(), 'This class was generated by the Doctrine ORM') === false) { + continue; + } + + TokenRemover::removeWithLinesIfPossible($tokens, $index); + } + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoUselessParenthesisFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoUselessParenthesisFixer.php new file mode 100644 index 00000000..fa905e21 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoUselessParenthesisFixer.php @@ -0,0 +1,229 @@ +isAnyTokenKindsFound(['(', CT::T_BRACE_CLASS_INSTANTIATION_OPEN]); + } + + public function isRisky(): bool + { + return false; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = 0; $index < $tokens->count(); $index++) { + if (!$tokens[$index]->equalsAny(['(', [CT::T_BRACE_CLASS_INSTANTIATION_OPEN]])) { + continue; + } + + /** @var array{type: Tokens::BLOCK_TYPE_*, isStart: bool} $blockType */ + $blockType = Tokens::detectBlockType($tokens[$index]); + $blockEndIndex = $tokens->findBlockEnd($blockType['type'], $index); + + if (!$this->isBlockToRemove($tokens, $index, $blockEndIndex)) { + continue; + } + + $this->clearWhitespace($tokens, $index + 1); + $this->clearWhitespace($tokens, $blockEndIndex - 1); + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + $tokens->clearTokenAndMergeSurroundingWhitespace($blockEndIndex); + + $prevIndex = $tokens->getPrevMeaningfulToken($index); + \assert(\is_int($prevIndex)); + + if ($tokens[$prevIndex]->isGivenKind([\T_RETURN, \T_THROW])) { + $tokens->ensureWhitespaceAtIndex($prevIndex + 1, 0, ' '); + } + } + } + + private function isBlockToRemove(Tokens $tokens, int $startIndex, int $endIndex): bool + { + if ($this->isParenthesisBlockInside($tokens, $startIndex, $endIndex)) { + return true; + } + + $prevStartIndex = $tokens->getPrevMeaningfulToken($startIndex); + \assert(\is_int($prevStartIndex)); + $nextEndIndex = $tokens->getNextMeaningfulToken($endIndex); + \assert(\is_int($nextEndIndex)); + + if ((new BlocksAnalyzer())->isBlock($tokens, $prevStartIndex, $nextEndIndex)) { + return true; + } + + if ($tokens[$nextEndIndex]->equalsAny(['(', '{', [\T_DOUBLE_ARROW], [CT::T_USE_LAMBDA], [CT::T_TYPE_COLON]])) { + return false; + } + + if ($this->isForbiddenBeforeOpenParenthesis($tokens, $prevStartIndex)) { + return false; + } + + if ($this->isExpressionInside($tokens, $startIndex, $endIndex)) { + return true; + } + + if ($this->hasLowPrecedenceLogicOperator($tokens, $startIndex, $endIndex)) { + return false; + } + + return $tokens[$prevStartIndex]->equalsAny(['=', [\T_RETURN], [\T_THROW]]) && $tokens[$nextEndIndex]->equals(';'); + } + + private function isForbiddenBeforeOpenParenthesis(Tokens $tokens, int $index): bool + { + if ( + $tokens[$index]->isGivenKind([ + \T_ARRAY, + \T_CLASS, + \T_ELSEIF, + \T_EMPTY, + \T_EVAL, + \T_EXIT, + \T_HALT_COMPILER, + \T_IF, + \T_ISSET, + \T_LIST, + \T_STATIC, + \T_STRING, + \T_SWITCH, + \T_UNSET, + \T_VARIABLE, + \T_WHILE, + CT::T_CLASS_CONSTANT, + CT::T_DOLLAR_CLOSE_CURLY_BRACES, + ]) + ) { + return true; + } + + /** @var null|array{isStart: bool, type: int} $blockType */ + $blockType = Tokens::detectBlockType($tokens[$index]); + + return $blockType !== null && !$blockType['isStart']; + } + + private function isParenthesisBlockInside(Tokens $tokens, int $startIndex, int $endIndex): bool + { + $nextStartIndex = $tokens->getNextMeaningfulToken($startIndex); + \assert(\is_int($nextStartIndex)); + + if (!$tokens[$nextStartIndex]->equalsAny(['(', [CT::T_BRACE_CLASS_INSTANTIATION_OPEN]])) { + return false; + } + + $prevIndex = $tokens->getPrevMeaningfulToken($endIndex); + \assert(\is_int($prevIndex)); + + return (new BlocksAnalyzer())->isBlock($tokens, $nextStartIndex, $prevIndex); + } + + private function isExpressionInside(Tokens $tokens, int $startIndex, int $endIndex): bool + { + $index = $tokens->getNextMeaningfulToken($startIndex); + \assert(\is_int($index)); + + while ($index < $endIndex) { + if ( + !$tokens[$index]->isGivenKind([ + \T_CONSTANT_ENCAPSED_STRING, + \T_DNUMBER, + \T_DOUBLE_COLON, + \T_LNUMBER, + \T_OBJECT_OPERATOR, + \T_STRING, + \T_VARIABLE, + ]) && !$tokens[$index]->isMagicConstant() + ) { + return false; + } + + $index = $tokens->getNextMeaningfulToken($index); + \assert(\is_int($index)); + } + + return true; + } + + private function hasLowPrecedenceLogicOperator(Tokens $tokens, int $startIndex, int $endIndex): bool + { + $index = $tokens->getNextMeaningfulToken($startIndex); + \assert(\is_int($index)); + + while ($index < $endIndex) { + if ( + $tokens[$index]->isGivenKind([ + \T_LOGICAL_XOR, + \T_LOGICAL_AND, + \T_LOGICAL_OR, + ]) + ) { + return true; + } + + $index = $tokens->getNextMeaningfulToken($index); + \assert(\is_int($index)); + } + + return false; + } + + private function clearWhitespace(Tokens $tokens, int $index): void + { + if (!$tokens[$index]->isWhitespace()) { + return; + } + + $prevIndex = $tokens->getNonEmptySibling($index, -1); + \assert(\is_int($prevIndex)); + + if ($tokens[$prevIndex]->isComment()) { + $tokens->ensureWhitespaceAtIndex($index, 0, \rtrim($tokens[$index]->getContent(), " \t")); + + return; + } + + $tokens->clearAt($index); + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoUselessStrlenFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoUselessStrlenFixer.php new file mode 100644 index 00000000..9a727ff6 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NoUselessStrlenFixer.php @@ -0,0 +1,180 @@ + 0; +', + ), + ], + '', + 'when the function `strlen` is overridden', + ); + } + + public function getPriority(): int + { + return 0; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(\T_LNUMBER) && $tokens->isAnyTokenKindsFound(['>', '<', \T_IS_IDENTICAL, \T_IS_NOT_IDENTICAL, \T_IS_EQUAL, \T_IS_NOT_EQUAL]); + } + + public function isRisky(): bool + { + return true; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + $argumentsAnalyzer = new ArgumentsAnalyzer(); + $functionsAnalyzer = new FunctionsAnalyzer(); + + for ($index = $tokens->count() - 1; $index > 0; $index--) { + if (!$tokens[$index]->equalsAny([[\T_STRING, 'strlen'], [\T_STRING, 'mb_strlen']], false)) { + continue; + } + + if (!$functionsAnalyzer->isGlobalFunctionCall($tokens, $index)) { + continue; + } + + $openParenthesisIndex = $tokens->getNextTokenOfKind($index, ['(']); + \assert(\is_int($openParenthesisIndex)); + + $closeParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesisIndex); + + if ($argumentsAnalyzer->countArguments($tokens, $openParenthesisIndex, $closeParenthesisIndex) !== 1) { + continue; + } + + $tokensToRemove = [ + $index => 1, + $openParenthesisIndex => 1, + $closeParenthesisIndex => -1, + ]; + + $prevIndex = $tokens->getPrevMeaningfulToken($index); + \assert(\is_int($prevIndex)); + + $startIndex = $index; + if ($tokens[$prevIndex]->isGivenKind(\T_NS_SEPARATOR)) { + $startIndex = $prevIndex; + $tokensToRemove[$prevIndex] = 1; + } + + if (!$this->transformCondition($tokens, $startIndex, $closeParenthesisIndex)) { + continue; + } + + $this->removeTokenAndSiblingWhitespace($tokens, $tokensToRemove); + } + } + + private function transformCondition(Tokens $tokens, int $startIndex, int $endIndex): bool + { + if ($this->transformConditionLeft($tokens, $startIndex)) { + return true; + } + + return $this->transformConditionRight($tokens, $endIndex); + } + + private function transformConditionLeft(Tokens $tokens, int $index): bool + { + $prevIndex = $tokens->getPrevMeaningfulToken($index); + \assert(\is_int($prevIndex)); + + $changeCondition = false; + if ($tokens[$prevIndex]->equals('<')) { + $changeCondition = true; + } elseif (!$tokens[$prevIndex]->isGivenKind([\T_IS_IDENTICAL, \T_IS_NOT_IDENTICAL, \T_IS_EQUAL, \T_IS_NOT_EQUAL])) { + return false; + } + + $prevPrevIndex = $tokens->getPrevMeaningfulToken($prevIndex); + \assert(\is_int($prevPrevIndex)); + + if (!$tokens[$prevPrevIndex]->equals([\T_LNUMBER, '0'])) { + return false; + } + + if ($changeCondition) { + $tokens[$prevIndex] = new Token([\T_IS_NOT_IDENTICAL, '!==']); + } + + $tokens[$prevPrevIndex] = new Token([\T_CONSTANT_ENCAPSED_STRING, '\'\'']); + + return true; + } + + private function transformConditionRight(Tokens $tokens, int $index): bool + { + $nextIndex = $tokens->getNextMeaningfulToken($index); + \assert(\is_int($nextIndex)); + + $changeCondition = false; + if ($tokens[$nextIndex]->equals('>')) { + $changeCondition = true; + } elseif (!$tokens[$nextIndex]->isGivenKind([\T_IS_IDENTICAL, \T_IS_NOT_IDENTICAL, \T_IS_EQUAL, \T_IS_NOT_EQUAL])) { + return false; + } + + $nextNextIndex = $tokens->getNextMeaningfulToken($nextIndex); + \assert(\is_int($nextNextIndex)); + + if (!$tokens[$nextNextIndex]->equals([\T_LNUMBER, '0'])) { + return false; + } + + if ($changeCondition) { + $tokens[$nextIndex] = new Token([\T_IS_NOT_IDENTICAL, '!==']); + } + + $tokens[$nextNextIndex] = new Token([\T_CONSTANT_ENCAPSED_STRING, '\'\'']); + + return true; + } + + /** + * @param array $tokensToRemove + */ + private function removeTokenAndSiblingWhitespace(Tokens $tokens, array $tokensToRemove): void + { + foreach ($tokensToRemove as $index => $direction) { + $tokens->clearAt($index); + + if ($tokens[$index + $direction]->isWhitespace()) { + $tokens->clearAt($index + $direction); + } + } + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NumericLiteralSeparatorFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NumericLiteralSeparatorFixer.php new file mode 100644 index 00000000..46d448a5 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/NumericLiteralSeparatorFixer.php @@ -0,0 +1,226 @@ +setAllowedTypes(['bool', 'null']) + ->setDefault($this->binarySeparator) + ->getOption(), + (new FixerOptionBuilder('decimal', 'whether add, remove or ignore separators in decimal numbers.')) + ->setAllowedTypes(['bool', 'null']) + ->setDefault($this->decimalSeparator) + ->getOption(), + (new FixerOptionBuilder('float', 'whether add, remove or ignore separators in float numbers.')) + ->setAllowedTypes(['bool', 'null']) + ->setDefault($this->floatSeparator) + ->getOption(), + (new FixerOptionBuilder('hexadecimal', 'whether add, remove or ignore separators in hexadecimal numbers.')) + ->setAllowedTypes(['bool', 'null']) + ->setDefault($this->hexadecimalSeparator) + ->getOption(), + (new FixerOptionBuilder('octal', 'whether add, remove or ignore separators in octal numbers.')) + ->setAllowedTypes(['bool', 'null']) + ->setDefault($this->octalSeparator) + ->getOption(), + ]); + } + + /** + * @param array $configuration + */ + public function configure(array $configuration): void + { + $this->binarySeparator = \array_key_exists('binary', $configuration) ? $configuration['binary'] : $this->binarySeparator; + $this->decimalSeparator = \array_key_exists('decimal', $configuration) ? $configuration['decimal'] : $this->decimalSeparator; + $this->floatSeparator = \array_key_exists('float', $configuration) ? $configuration['float'] : $this->floatSeparator; + $this->hexadecimalSeparator = \array_key_exists('hexadecimal', $configuration) ? $configuration['hexadecimal'] : $this->hexadecimalSeparator; + $this->octalSeparator = \array_key_exists('octal', $configuration) ? $configuration['octal'] : $this->octalSeparator; + } + + public function getPriority(): int + { + return 0; + } + + public function isCandidate(Tokens $tokens): bool + { + return \PHP_VERSION_ID >= 70400 && $tokens->isAnyTokenKindsFound([\T_DNUMBER, \T_LNUMBER]); + } + + public function isRisky(): bool + { + return false; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index > 0; $index--) { + if (!$tokens[$index]->isGivenKind([\T_DNUMBER, \T_LNUMBER])) { + continue; + } + + $content = $tokens[$index]->getContent(); + $newContent = $this->getNewContent($content); + + if ($content !== $newContent) { + $id = $tokens[$index]->getId(); + \assert(\is_int($id)); + + $tokens[$index] = new Token([$id, $newContent]); + } + } + } + + /** + * @return list + */ + public function getSuccessorsNames(): array + { + return [(new NLSFixer())->getName()]; + } + + private function getNewContent(string $content): string + { + if (\strpos($content, '.') !== false) { + $content = $this->updateContent($content, null, '.', 3, $this->floatSeparator); + $content = $this->updateContent($content, '.', 'e', 3, $this->floatSeparator, false); + + return $this->updateContent($content, 'e', null, 3, $this->floatSeparator); + } + + if (\stripos($content, '0b') === 0) { + return $this->updateContent($content, 'b', null, 8, $this->binarySeparator); + } + + if (\stripos($content, '0x') === 0) { + return $this->updateContent($content, 'x', null, 2, $this->hexadecimalSeparator); + } + + if (Preg::match('/e-?[\d_]+$/i', $content)) { + $content = $this->updateContent($content, null, 'e', 3, $this->floatSeparator); + + return $this->updateContent($content, 'e', null, 3, $this->floatSeparator); + } + + if (\strpos($content, '0') === 0) { + return $this->updateContent($content, '0', null, 4, $this->octalSeparator); + } + + return $this->updateContent($content, null, null, 3, $this->decimalSeparator); + } + + private function updateContent(string $content, ?string $startCharacter, ?string $endCharacter, int $groupSize, ?bool $addSeparators, bool $fromRight = true): string + { + if ($addSeparators === null) { + return $content; + } + + $startPosition = $this->getStartPosition($content, $startCharacter); + if ($startPosition === null) { + return $content; + } + $endPosition = $this->getEndPosition($content, $endCharacter); + + $substringToUpdate = \substr($content, $startPosition, $endPosition - $startPosition); + $substringToUpdate = \str_replace('_', '', $substringToUpdate); + + if ($addSeparators) { + if ($fromRight) { + $substringToUpdate = \strrev($substringToUpdate); + } + + $substringToUpdate = Preg::replace(\sprintf('/[\da-fA-F]{%d}(?!-)(?!$)/', $groupSize), '$0_', $substringToUpdate); + + if ($fromRight) { + $substringToUpdate = \strrev($substringToUpdate); + } + } + + return \substr($content, 0, $startPosition) . $substringToUpdate . \substr($content, $endPosition); + } + + private function getStartPosition(string $content, ?string $startCharacter): ?int + { + if ($startCharacter === null) { + return 0; + } + + $startPosition = \stripos($content, $startCharacter); + + if ($startPosition === false) { + return null; + } + + return $startPosition + 1; + } + + private function getEndPosition(string $content, ?string $endCharacter): int + { + if ($endCharacter === null) { + return \strlen($content); + } + + $endPosition = \stripos($content, $endCharacter); + + if ($endPosition === false) { + return \strlen($content); + } + + return $endPosition; + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpUnitAssertArgumentsOrderFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpUnitAssertArgumentsOrderFixer.php new file mode 100644 index 00000000..aed27e4b --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpUnitAssertArgumentsOrderFixer.php @@ -0,0 +1,171 @@ + true, + 'assertnotequals' => true, + 'assertequalscanonicalizing' => true, + 'assertnotequalscanonicalizing' => true, + 'assertequalsignoringcase' => true, + 'assertnotequalsignoringcase' => true, + 'assertequalswithdelta' => true, + 'assertnotequalswithdelta' => true, + 'assertsame' => true, + 'assertnotsame' => true, + 'assertgreaterthan' => 'assertLessThan', + 'assertgreaterthanorequal' => 'assertLessThanOrEqual', + 'assertlessthan' => 'assertGreaterThan', + 'assertlessthanorequal' => 'assertGreaterThanOrEqual', + ]; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'PHPUnit assertions must have expected argument before actual one.', + [new CodeSample('isAllTokenKindsFound([\T_CLASS, \T_EXTENDS, \T_FUNCTION]); + } + + public function isRisky(): bool + { + return true; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + $phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator(); + + /** @var list $indices */ + foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens) as $indices) { + $this->fixArgumentsOrder($tokens, $indices[0], $indices[1]); + } + } + + private function fixArgumentsOrder(Tokens $tokens, int $startIndex, int $endIndex): void + { + for ($index = $startIndex; $index < $endIndex; $index++) { + $newAssertion = self::getNewAssertion($tokens, $index); + if ($newAssertion === null) { + continue; + } + + $arguments = FunctionAnalyzer::getFunctionArguments($tokens, $index); + + if (!self::shouldArgumentsBeSwapped($arguments)) { + continue; + } + + if ($newAssertion !== $tokens[$index]->getContent()) { + $tokens[$index] = new Token([\T_STRING, $newAssertion]); + } + + self::swapArguments($tokens, $arguments); + } + } + + private static function getNewAssertion(Tokens $tokens, int $index): ?string + { + $oldAssertion = $tokens[$index]->getContent(); + + if (!\array_key_exists(\strtolower($oldAssertion), self::ASSERTIONS)) { + return null; + } + + $newAssertion = self::ASSERTIONS[\strtolower($oldAssertion)]; + + $openingBraceIndex = $tokens->getNextMeaningfulToken($index); + \assert(\is_int($openingBraceIndex)); + + if (!$tokens[$openingBraceIndex]->equals('(')) { + return null; + } + + if (!(new FunctionsAnalyzer())->isTheSameClassCall($tokens, $index)) { + return null; + } + + if (!\is_string($newAssertion)) { + return $oldAssertion; + } + + return $newAssertion; + } + + /** + * @param list $arguments + */ + private static function shouldArgumentsBeSwapped(array $arguments): bool + { + if (\count($arguments) < 2) { + return false; + } + + if ($arguments[0]->isConstant()) { + return false; + } + + return $arguments[1]->isConstant(); + } + + /** + * @param list $arguments + */ + private static function swapArguments(Tokens $tokens, array $arguments): void + { + $expectedArgumentTokens = []; // these will be 1st argument + for ($index = $arguments[1]->getStartIndex(); $index <= $arguments[1]->getEndIndex(); $index++) { + $expectedArgumentTokens[] = $tokens[$index]; + } + + $actualArgumentTokens = []; // these will be 2nd argument + for ($index = $arguments[0]->getStartIndex(); $index <= $arguments[0]->getEndIndex(); $index++) { + $actualArgumentTokens[] = $tokens[$index]; + } + + $tokens->overrideRange($arguments[1]->getStartIndex(), $arguments[1]->getEndIndex(), $actualArgumentTokens); + $tokens->overrideRange($arguments[0]->getStartIndex(), $arguments[0]->getEndIndex(), $expectedArgumentTokens); + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpUnitDedicatedAssertFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpUnitDedicatedAssertFixer.php new file mode 100644 index 00000000..49bcd25d --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpUnitDedicatedAssertFixer.php @@ -0,0 +1,163 @@ + true, + 'assertnotequals' => true, + 'assertsame' => true, + 'assertnotsame' => true, + ]; + private const REPLACEMENTS_MAP = [ + 'count' => [ + 'positive' => 'assertCount', + 'negative' => 'assertNotCount', + ], + 'get_class' => [ + 'positive' => 'assertInstanceOf', + 'negative' => 'assertNotInstanceOf', + ], + 'sizeof' => [ + 'positive' => 'assertCount', + 'negative' => 'assertNotCount', + ], + ]; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'PHPUnit assertions like `assertCount` and `assertInstanceOf` must be used over `assertEquals`/`assertSame`.', + [new CodeSample('isAllTokenKindsFound([\T_CLASS, \T_EXTENDS, \T_FUNCTION]); + } + + public function isRisky(): bool + { + return true; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + $phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator(); + + /** @var list $indices */ + foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens) as $indices) { + $this->fixAssertions($tokens, $indices[0], $indices[1]); + } + } + + private function fixAssertions(Tokens $tokens, int $startIndex, int $endIndex): void + { + for ($index = $startIndex; $index < $endIndex; $index++) { + if (!self::isAssertionToFix($tokens, $index)) { + continue; + } + + $arguments = FunctionAnalyzer::getFunctionArguments($tokens, $index); + if (\count($arguments) < 2) { + continue; + } + + self::fixAssertion($tokens, $index, $arguments[1]); + } + } + + private static function isAssertionToFix(Tokens $tokens, int $index): bool + { + if (!\array_key_exists(\strtolower($tokens[$index]->getContent()), self::ASSERTIONS)) { + return false; + } + + $openingBraceIndex = $tokens->getNextMeaningfulToken($index); + \assert(\is_int($openingBraceIndex)); + + if (!$tokens[$openingBraceIndex]->equals('(')) { + return false; + } + + return (new FunctionsAnalyzer())->isTheSameClassCall($tokens, $index); + } + + private static function fixAssertion(Tokens $tokens, int $assertionIndex, ArgumentAnalysis $secondArgument): void + { + $functionCallIndex = $secondArgument->getStartIndex(); + if ($tokens[$functionCallIndex]->isGivenKind(\T_NS_SEPARATOR)) { + $functionCallIndex = $tokens->getNextMeaningfulToken($functionCallIndex); + \assert(\is_int($functionCallIndex)); + } + + if (!(new FunctionsAnalyzer())->isGlobalFunctionCall($tokens, $functionCallIndex)) { + return; + } + + $arguments = FunctionAnalyzer::getFunctionArguments($tokens, $functionCallIndex); + if (\count($arguments) !== 1) { + return; + } + + $functionName = \strtolower($tokens[$functionCallIndex]->getContent()); + + if (!\array_key_exists($functionName, self::REPLACEMENTS_MAP)) { + return; + } + + $newAssertion = self::REPLACEMENTS_MAP[$functionName][\stripos($tokens[$assertionIndex]->getContent(), 'not', 6) === false ? 'positive' : 'negative']; + + $openParenthesisIndex = $tokens->getNextMeaningfulToken($functionCallIndex); + \assert(\is_int($openParenthesisIndex)); + $closeParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesisIndex); + + if ($closeParenthesisIndex < $secondArgument->getEndIndex()) { + return; + } + + $tokens[$assertionIndex] = new Token([\T_STRING, $newAssertion]); + $tokens->clearRange($secondArgument->getStartIndex(), $openParenthesisIndex - 1); + $tokens->clearTokenAndMergeSurroundingWhitespace($openParenthesisIndex); + $tokens->clearTokenAndMergeSurroundingWhitespace($closeParenthesisIndex); + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpUnitNoUselessReturnFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpUnitNoUselessReturnFixer.php new file mode 100644 index 00000000..8ab8740e --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpUnitNoUselessReturnFixer.php @@ -0,0 +1,124 @@ + $token[1], + self::FUNCTION_TOKENS, + )), + ), + [new CodeSample('markTestSkipped(); + return; + } +} +')], + 'They will throw an exception anyway.', + 'when original PHPUnit methods are overwritten', + ); + } + + /** + * Must run before NoExtraBlankLinesFixer. + */ + public function getPriority(): int + { + return 0; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAllTokenKindsFound([\T_CLASS, \T_EXTENDS, \T_FUNCTION, \T_STRING, \T_RETURN]); + } + + public function isRisky(): bool + { + return true; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + $phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator(); + + /** @var list $indices */ + foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens) as $indices) { + $this->removeUselessReturns($tokens, $indices[0], $indices[1]); + } + } + + private function removeUselessReturns(Tokens $tokens, int $startIndex, int $endIndex): void + { + $functionsAnalyzer = new FunctionsAnalyzer(); + + for ($index = $startIndex; $index < $endIndex; $index++) { + if (!$tokens[$index]->equalsAny(self::FUNCTION_TOKENS, false)) { + continue; + } + + $openingBraceIndex = $tokens->getNextMeaningfulToken($index); + \assert(\is_int($openingBraceIndex)); + + if (!$tokens[$openingBraceIndex]->equals('(')) { + continue; + } + + if (!$functionsAnalyzer->isTheSameClassCall($tokens, $index)) { + continue; + } + + $closingBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openingBraceIndex); + + $semicolonIndex = $tokens->getNextMeaningfulToken($closingBraceIndex); + \assert(\is_int($semicolonIndex)); + + $returnIndex = $tokens->getNextMeaningfulToken($semicolonIndex); + \assert(\is_int($returnIndex)); + + if (!$tokens[$returnIndex]->isGivenKind(\T_RETURN)) { + continue; + } + + $semicolonAfterReturnIndex = $tokens->getNextTokenOfKind($returnIndex, [';', '(']); + \assert(\is_int($semicolonAfterReturnIndex)); + + while ($tokens[$semicolonAfterReturnIndex]->equals('(')) { + $closingBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $semicolonAfterReturnIndex); + + $semicolonAfterReturnIndex = $tokens->getNextTokenOfKind($closingBraceIndex, [';', '(']); + \assert(\is_int($semicolonAfterReturnIndex)); + } + + $tokens->clearRange($returnIndex, $semicolonAfterReturnIndex - 1); + TokenRemover::removeWithLinesIfPossible($tokens, $semicolonAfterReturnIndex); + } + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpdocArrayStyleFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpdocArrayStyleFixer.php new file mode 100644 index 00000000..811eac84 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpdocArrayStyleFixer.php @@ -0,0 +1,81 @@ +phpdocArrayTypeFixer = new PhpdocArrayTypeFixer(); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Generic array style should be used in PHPDoc.', + [ + new CodeSample( + 'phpdocArrayTypeFixer->getPriority(); + } + + public function isCandidate(Tokens $tokens): bool + { + return $this->phpdocArrayTypeFixer->isCandidate($tokens); + } + + public function isRisky(): bool + { + return false; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + $this->phpdocArrayTypeFixer->fix($file, $tokens); + } + + /** + * @return list + */ + public function getSuccessorsNames(): array + { + return [$this->phpdocArrayTypeFixer->getName()]; + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpdocNoIncorrectVarAnnotationFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpdocNoIncorrectVarAnnotationFixer.php new file mode 100644 index 00000000..e97c8440 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpdocNoIncorrectVarAnnotationFixer.php @@ -0,0 +1,170 @@ +isTokenKindFound(\T_DOC_COMMENT); + } + + public function isRisky(): bool + { + return false; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index > 0; $index--) { + if (!$tokens[$index]->isGivenKind(\T_DOC_COMMENT)) { + continue; + } + + // remove ones not having type at the beginning + $this->removeVarAnnotationNotMatchingPattern($tokens, $index, '/@var\s+[\?\\\a-zA-Z_\x7f-\xff]/'); + + $nextIndex = $tokens->getNextMeaningfulToken($index); + + if ($nextIndex === null) { + $this->removeVarAnnotationNotMatchingPattern($tokens, $index, null); + continue; + } + + if ($tokens[$nextIndex]->isGivenKind([\T_PRIVATE, \T_PROTECTED, \T_PUBLIC, \T_VAR, \T_STATIC, CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC, CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED, CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE])) { + $this->removeForClassElement($tokens, $index, $nextIndex); + continue; + } + + if ($tokens[$nextIndex]->isGivenKind(\T_VARIABLE)) { + $this->removeVarAnnotation($tokens, $index, [$tokens[$nextIndex]->getContent()]); + continue; + } + + if ($tokens[$nextIndex]->isGivenKind([\T_FOR, \T_FOREACH, \T_IF, \T_SWITCH, \T_WHILE])) { + $this->removeVarAnnotationForControl($tokens, $index, $nextIndex); + continue; + } + + $this->removeVarAnnotationNotMatchingPattern($tokens, $index, null); + } + } + + private function removeForClassElement(Tokens $tokens, int $index, int $propertyStartIndex): void + { + $tokenKinds = [\T_NS_SEPARATOR, \T_STATIC, \T_STRING, \T_WHITESPACE, CT::T_ARRAY_TYPEHINT, CT::T_NULLABLE_TYPE, CT::T_TYPE_ALTERNATION]; + + if (\defined('T_READONLY')) { + $tokenKinds[] = CT::T_TYPE_INTERSECTION; + $tokenKinds[] = \T_READONLY; + } + + $variableIndex = $tokens->getTokenNotOfKindsSibling($propertyStartIndex, 1, $tokenKinds); + \assert(\is_int($variableIndex)); + + if (!$tokens[$variableIndex]->isGivenKind(\T_VARIABLE)) { + $this->removeVarAnnotationNotMatchingPattern($tokens, $index, null); + + return; + } + + if (Preg::match('/@var\h+(.+\h+)?\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $tokens[$index]->getContent())) { + $this->removeVarAnnotation($tokens, $index, [$tokens[$variableIndex]->getContent()]); + } + } + + /** + * @param list $allowedVariables + */ + private function removeVarAnnotation(Tokens $tokens, int $index, array $allowedVariables): void + { + $this->removeVarAnnotationNotMatchingPattern( + $tokens, + $index, + '/(\Q' . \implode('\E|\Q', $allowedVariables) . '\E)\b/i', + ); + } + + private function removeVarAnnotationForControl(Tokens $tokens, int $commentIndex, int $controlIndex): void + { + $index = $tokens->getNextMeaningfulToken($controlIndex); + \assert(\is_int($index)); + + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + + $variables = []; + + while ($index < $endIndex) { + $index++; + + if ($tokens[$index]->isGivenKind(\T_VARIABLE)) { + $variables[] = $tokens[$index]->getContent(); + } + } + + $this->removeVarAnnotation($tokens, $commentIndex, $variables); + } + + private function removeVarAnnotationNotMatchingPattern(Tokens $tokens, int $index, ?string $pattern): void + { + $doc = new DocBlock($tokens[$index]->getContent()); + + foreach ($doc->getAnnotationsOfType(['var']) as $annotation) { + if ($pattern === null || !Preg::match($pattern, $annotation->getContent())) { + $annotation->remove(); + } + } + + $content = $doc->getContent(); + + if ($content === $tokens[$index]->getContent()) { + return; + } + + if ($content === '') { + TokenRemover::removeWithLinesIfPossible($tokens, $index); + + return; + } + $tokens[$index] = new Token([\T_DOC_COMMENT, $content]); + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpdocNoSuperfluousParamFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpdocNoSuperfluousParamFixer.php new file mode 100644 index 00000000..e9819378 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpdocNoSuperfluousParamFixer.php @@ -0,0 +1,134 @@ +isAllTokenKindsFound([\T_DOC_COMMENT, \T_FUNCTION]); + } + + public function isRisky(): bool + { + return false; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = 0; $index < $tokens->count(); $index++) { + if (!$tokens[$index]->isGivenKind(\T_DOC_COMMENT)) { + continue; + } + + $functionIndex = $tokens->getTokenNotOfKindSibling($index, 1, [[\T_ABSTRACT], [\T_COMMENT], [\T_FINAL], [\T_PRIVATE], [\T_PROTECTED], [\T_PUBLIC], [\T_STATIC], [\T_WHITESPACE]]); + + if ($functionIndex === null) { + return; + } + + if (!$tokens[$functionIndex]->isGivenKind(\T_FUNCTION)) { + continue; + } + + $paramNames = $this->getParamNames($tokens, $functionIndex); + + $newContent = $this->getFilteredDocComment($tokens[$index]->getContent(), $paramNames); + + if ($newContent === $tokens[$index]->getContent()) { + continue; + } + + if ($newContent === '') { + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } else { + $tokens[$index] = new Token([\T_DOC_COMMENT, $newContent]); + } + } + } + + /** + * @return list + */ + private function getParamNames(Tokens $tokens, int $functionIndex): array + { + $paramBlockStartIndex = $tokens->getNextTokenOfKind($functionIndex, ['(']); + \assert(\is_int($paramBlockStartIndex)); + + $paramBlockEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $paramBlockStartIndex); + + $paramNames = []; + for ($index = $paramBlockStartIndex; $index < $paramBlockEndIndex; $index++) { + if ($tokens[$index]->isGivenKind(\T_VARIABLE)) { + $paramNames[] = $tokens[$index]->getContent(); + } + } + + return $paramNames; + } + + /** + * @param list $paramNames + */ + private function getFilteredDocComment(string $comment, array $paramNames): string + { + $regexParamNamesPattern = '(\Q' . \implode('\E|\Q', $paramNames) . '\E)'; + + $doc = new DocBlock($comment); + + $foundParamNames = []; + foreach ($doc->getAnnotationsOfType('param') as $annotation) { + if (Preg::match(\sprintf('/@param\s+(?:[^\$](?:[^<.]|<[^>]*>)*\s*)?(?:&|\.\.\.)?\s*(?=\$)%s\b/', $regexParamNamesPattern), $annotation->getContent(), $matches) && !\in_array($matches[1], $foundParamNames, true)) { + $foundParamNames[] = $matches[1]; + continue; + } + + $annotation->remove(); + } + + return $doc->getContent(); + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpdocOnlyAllowedAnnotationsFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpdocOnlyAllowedAnnotationsFixer.php new file mode 100644 index 00000000..233855cd --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpdocOnlyAllowedAnnotationsFixer.php @@ -0,0 +1,118 @@ + */ + private array $elements = []; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Only the listed annotations are allowed in PHPDoc.', + [new CodeSample( + ' ['author', 'version']], + )], + '', + ); + } + + public function getConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('elements', 'list of annotations to keep in PHPDoc')) + ->setAllowedTypes(['array']) + ->setDefault($this->elements) + ->getOption(), + ]); + } + + /** + * @param array> $configuration + */ + public function configure(array $configuration): void + { + if (\array_key_exists('elements', $configuration)) { + $this->elements = $configuration['elements']; + } + } + + /** + * Must run before NoEmptyPhpdocFixer. + * Must run after CommentToPhpdocFixer. + */ + public function getPriority(): int + { + return 4; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(\T_DOC_COMMENT); + } + + public function isRisky(): bool + { + return false; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index > 0; $index--) { + if (!$tokens[$index]->isGivenKind(\T_DOC_COMMENT)) { + continue; + } + + $docBlock = new DocBlock($tokens[$index]->getContent()); + + foreach ($docBlock->getAnnotations() as $annotation) { + if ( + Preg::match('/@([a-zA-Z0-9_\-\\\]+)/', $annotation->getContent(), $matches) + && \in_array($matches[1], $this->elements, true) + ) { + continue; + } + + $annotation->remove(); + } + + if ($docBlock->getContent() === '') { + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + continue; + } + + $tokens[$index] = new Token([\T_DOC_COMMENT, $docBlock->getContent()]); + } + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpdocParamOrderFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpdocParamOrderFixer.php new file mode 100644 index 00000000..61aa8a22 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpdocParamOrderFixer.php @@ -0,0 +1,79 @@ +phpdocParamOrderFixer = new \PhpCsFixer\Fixer\Phpdoc\PhpdocParamOrderFixer(); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + $this->phpdocParamOrderFixer->getDefinition()->getSummary(), + [new CodeSample('phpdocParamOrderFixer->getPriority(); + } + + public function isCandidate(Tokens $tokens): bool + { + return $this->phpdocParamOrderFixer->isCandidate($tokens); + } + + public function isRisky(): bool + { + return $this->phpdocParamOrderFixer->isRisky(); + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + $this->phpdocParamOrderFixer->fix($file, $tokens); + } + + /** + * @return list + */ + public function getSuccessorsNames(): array + { + return [$this->phpdocParamOrderFixer->getName()]; + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpdocParamTypeFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpdocParamTypeFixer.php new file mode 100644 index 00000000..10b66f7d --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpdocParamTypeFixer.php @@ -0,0 +1,77 @@ +isAnyTokenKindsFound([\T_COMMENT, \T_DOC_COMMENT]); + } + + public function isRisky(): bool + { + return false; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index > 0; $index--) { + if (!$tokens[$index]->isGivenKind([\T_DOC_COMMENT])) { + continue; + } + + $newContent = Preg::replace( + '/(@param) {0,7}( *\$)/i', + '$1 mixed $2', + $tokens[$index]->getContent(), + ); + + if ($newContent === $tokens[$index]->getContent()) { + continue; + } + + $tokens[$index] = new Token([\T_DOC_COMMENT, $newContent]); + } + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpdocSelfAccessorFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpdocSelfAccessorFixer.php new file mode 100644 index 00000000..cc51b2a1 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpdocSelfAccessorFixer.php @@ -0,0 +1,140 @@ +isAnyTokenKindsFound([\T_CLASS, \T_INTERFACE]) && $tokens->isTokenKindFound(\T_DOC_COMMENT); + } + + public function isRisky(): bool + { + return false; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + $namespaces = (new NamespacesAnalyzer())->getDeclarations($tokens); + + foreach ($namespaces as $namespace) { + $this->fixPhpdocSelfAccessor($tokens, $namespace->getScopeStartIndex(), $namespace->getScopeEndIndex(), $namespace->getFullName()); + } + } + + private function fixPhpdocSelfAccessor(Tokens $tokens, int $namespaceStartIndex, int $namespaceEndIndex, string $fullName): void + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + + $index = $namespaceStartIndex; + while ($index < $namespaceEndIndex) { + $index++; + + if (!$tokens[$index]->isGivenKind([\T_CLASS, \T_INTERFACE]) || $tokensAnalyzer->isAnonymousClass($index)) { + continue; + } + + $nameIndex = $tokens->getNextTokenOfKind($index, [[\T_STRING]]); + \assert(\is_int($nameIndex)); + + $startIndex = $tokens->getNextTokenOfKind($nameIndex, ['{']); + \assert(\is_int($startIndex)); + + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $startIndex); + + $classyName = $tokens[$nameIndex]->getContent(); + + $this->replaceNameOccurrences($tokens, $fullName, $classyName, $startIndex, $endIndex); + + $index = $endIndex; + } + } + + private function replaceNameOccurrences(Tokens $tokens, string $namespace, string $classyName, int $startIndex, int $endIndex): void + { + for ($index = $startIndex; $index < $endIndex; $index++) { + if (!$tokens[$index]->isGivenKind(\T_DOC_COMMENT)) { + continue; + } + + $newContent = $this->getNewContent($tokens[$index]->getContent(), $namespace, $classyName); + + if ($newContent === $tokens[$index]->getContent()) { + continue; + } + + $tokens[$index] = new Token([\T_DOC_COMMENT, $newContent]); + } + } + + private function getNewContent(string $content, string $namespace, string $classyName): string + { + $docBlock = new DocBlock($content); + + $fqcn = ($namespace !== '' ? '\\' . $namespace : '') . '\\' . $classyName; + + foreach ($docBlock->getAnnotations() as $annotation) { + if (!$annotation->supportTypes()) { + continue; + } + + $types = []; + foreach ($annotation->getTypes() as $type) { + $type = Preg::replace( + \sprintf('/(?setTypes($types); + } + } + + return $docBlock->getContent(); + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpdocSingleLineVarFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpdocSingleLineVarFixer.php new file mode 100644 index 00000000..a38f63c9 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpdocSingleLineVarFixer.php @@ -0,0 +1,80 @@ +isTokenKindFound(\T_DOC_COMMENT); + } + + public function isRisky(): bool + { + return false; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index > 0; $index--) { + if (!$tokens[$index]->isGivenKind(\T_DOC_COMMENT)) { + continue; + } + + if (!Preg::match('#^/\*\*[\s\*]+(@var[^\r\n]+)[\s\*]*\*\/$#u', $tokens[$index]->getContent(), $matches)) { + continue; + } + + $var = $matches[1]; + \assert(\is_string($var)); + + $newContent = '/** ' . \rtrim($var) . ' */'; + + if ($newContent === $tokens[$index]->getContent()) { + continue; + } + + $tokens[$index] = new Token([\T_DOC_COMMENT, $newContent]); + } + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpdocTypeListFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpdocTypeListFixer.php new file mode 100644 index 00000000..c41f3f75 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpdocTypeListFixer.php @@ -0,0 +1,78 @@ +phpdocListTypeFixer = new PhpdocListTypeFixer(); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'PHPDoc type `list` must be used instead of `array` without a key type.', + [new CodeSample(' + */ +function foo($x) {} +')], + '', + ); + } + + /** + * Must run before PhpdocAlignFixer, PhpdocTypesOrderFixer. + * Must run after CommentToPhpdocFixer, PhpdocArrayStyleFixer. + */ + public function getPriority(): int + { + return 1; + } + + public function isCandidate(Tokens $tokens): bool + { + return $this->phpdocListTypeFixer->isCandidate($tokens); + } + + public function isRisky(): bool + { + return false; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + $this->phpdocListTypeFixer->fix($file, $tokens); + } + + /** + * @return list + */ + public function getSuccessorsNames(): array + { + return [$this->phpdocListTypeFixer->getName()]; + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpdocTypesCommaSpacesFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpdocTypesCommaSpacesFixer.php new file mode 100644 index 00000000..755c28f6 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpdocTypesCommaSpacesFixer.php @@ -0,0 +1,39 @@ + */\n")], + '', + ); + } + + public function getPriority(): int + { + return 0; + } + + protected function fixType(string $type): string + { + return Preg::replace('/\h*,\s*/', ', ', $type); + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpdocTypesTrimFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpdocTypesTrimFixer.php new file mode 100644 index 00000000..a332c730 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PhpdocTypesTrimFixer.php @@ -0,0 +1,47 @@ +isAnyTokenKindsFound([\T_DOC_COMMENT, \T_VARIABLE]); + } + + public function isRisky(): bool + { + return false; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + for ($docCommentIndex = $tokens->count() - 1; $docCommentIndex > 0; $docCommentIndex--) { + if (!$tokens[$docCommentIndex]->isGivenKind([\T_DOC_COMMENT])) { + continue; + } + + $variableIndex = $this->getVariableIndex($tokens, $docCommentIndex); + if ($variableIndex === null) { + continue; + } + + $assertTokens = $this->getAssertTokens($tokens, $docCommentIndex, $tokens[$variableIndex]->getContent()); + if ($assertTokens === null) { + continue; + } + + $expressionEndIndex = $this->getExpressionEnd($tokens, $variableIndex); + + if (!$this->canBePlacedAfterExpression($tokens, $expressionEndIndex)) { + continue; + } + + if ($tokens[$variableIndex - 1]->isWhitespace()) { + \array_unshift($assertTokens, new Token([\T_WHITESPACE, $tokens[$variableIndex - 1]->getContent()])); + } + + $tokens->insertAt($expressionEndIndex + 1, $assertTokens); + + TokenRemover::removeWithLinesIfPossible($tokens, $docCommentIndex); + } + } + + private function getVariableIndex(Tokens $tokens, int $docCommentIndex): ?int + { + $prevIndex = $tokens->getPrevMeaningfulToken($docCommentIndex); + if (!$tokens[$prevIndex]->equalsAny([';', '{', '}', [\T_OPEN_TAG]])) { + return null; + } + + $variableIndex = $tokens->getNextMeaningfulToken($docCommentIndex); + if ($variableIndex === null) { + return null; + } + if (!$tokens[$variableIndex]->isGivenKind([\T_VARIABLE])) { + return null; + } + + $assignmentIndex = $tokens->getNextMeaningfulToken($variableIndex); + \assert(\is_int($assignmentIndex)); + + if (!$tokens[$assignmentIndex]->equals('=')) { + return null; + } + + return $variableIndex; + } + + /** + * @return null|list + */ + private function getAssertTokens(Tokens $tokens, int $docCommentIndex, string $variableName): ?array + { + $annotation = $this->getAnnotationForVariable($tokens, $docCommentIndex, $variableName); + if ($annotation === null) { + return null; + } + + $typeExpression = $annotation->getTypeExpression(); + if ($typeExpression === null) { + return null; + } + + $assertCode = 'getTypes() as $type) { + if (\substr($type, 0, 1) === '?') { + $assertions['null'] = $this->getCodeForType('null', $variableName); + $type = \substr($type, 1); + } + $assertions[$type] = $this->getCodeForType($type, $variableName); + } + + try { + $tokens = Tokens::fromCode($assertCode . \implode(' || ', $assertions) . ');'); + } catch (\ParseError $exception) { + return null; + } + + /** @var list $arrayTokens */ + $arrayTokens = $tokens->toArray(); + + return \array_slice($arrayTokens, 1); + } + + private function getAnnotationForVariable(Tokens $tokens, int $docCommentIndex, string $variableName): ?Annotation + { + $docBlock = new DocBlock($tokens[$docCommentIndex]->getContent()); + + if (\count($docBlock->getAnnotations()) !== 1) { + return null; + } + + $varAnnotations = $docBlock->getAnnotationsOfType('var'); + if (\count($varAnnotations) !== 1) { + return null; + } + + $varAnnotation = \reset($varAnnotations); + + if ($varAnnotation->getVariableName() !== $variableName) { + return null; + } + + return $varAnnotation; + } + + private function getCodeForType(string $type, string $variableName): string + { + $typesMap = [ + 'array' => 'is_array', + 'bool' => 'is_bool', + 'boolean' => 'is_bool', + 'callable' => 'is_callable', + 'double' => 'is_float', + 'float' => 'is_float', + 'int' => 'is_int', + 'integer' => 'is_int', + 'iterable' => 'is_iterable', + 'null' => 'is_null', + 'object' => 'is_object', + 'resource' => 'is_resource', + 'string' => 'is_string', + ]; + + if (\array_key_exists(\strtolower($type), $typesMap)) { + return \sprintf('%s(%s)', $typesMap[\strtolower($type)], $variableName); + } + + return \sprintf('%s instanceof %s', $variableName, $type); + } + + private function getExpressionEnd(Tokens $tokens, int $index): int + { + while (!$tokens[$index]->equals(';')) { + $index = $tokens->getNextMeaningfulToken($index); + \assert(\is_int($index)); + + $blockType = Tokens::detectBlockType($tokens[$index]); + if ($blockType !== null && $blockType['isStart']) { + $index = $tokens->findBlockEnd($blockType['type'], $index); + } + } + + return $index; + } + + private function canBePlacedAfterExpression(Tokens $tokens, int $expressionEndIndex): bool + { + $afterExpressionIndex = $tokens->getNextMeaningfulToken($expressionEndIndex); + + if ($afterExpressionIndex === null) { + return true; + } + + if ($tokens[$afterExpressionIndex]->isGivenKind(\T_NS_SEPARATOR)) { + $afterExpressionIndex = $tokens->getNextMeaningfulToken($afterExpressionIndex); + } + + return !$tokens[$afterExpressionIndex]->equals([\T_STRING, 'assert'], false); + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PromotedConstructorPropertyFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PromotedConstructorPropertyFixer.php new file mode 100644 index 00000000..d4b75ce1 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/PromotedConstructorPropertyFixer.php @@ -0,0 +1,426 @@ +> */ + private array $tokensToInsert; + + private bool $promoteOnlyExistingProperties = false; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Constructor properties must be promoted if possible.', + [ + new VersionSpecificCodeSample( + 'bar = $bar; + } +} +', + new VersionSpecification(80000), + ), + ], + '', + ); + } + + public function getConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('promote_only_existing_properties', 'whether to promote only properties that are defined in class')) + ->setAllowedTypes(['bool']) + ->setDefault($this->promoteOnlyExistingProperties) + ->getOption(), + ]); + } + + /** + * @param array $configuration + */ + public function configure(array $configuration): void + { + if (\array_key_exists('promote_only_existing_properties', $configuration)) { + $this->promoteOnlyExistingProperties = $configuration['promote_only_existing_properties']; + } + } + + /** + * Must run before BracesFixer, ClassAttributesSeparationFixer, ConstructorEmptyBracesFixer, MultilinePromotedPropertiesFixer, NoExtraBlankLinesFixer, ReadonlyPromotedPropertiesFixer. + */ + public function getPriority(): int + { + return 56; + } + + public function isCandidate(Tokens $tokens): bool + { + return \PHP_VERSION_ID >= 80000 && $tokens->isAllTokenKindsFound([\T_CLASS, \T_VARIABLE]); + } + + public function isRisky(): bool + { + return false; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + $constructorAnalyzer = new ConstructorAnalyzer(); + $this->tokensToInsert = []; + + for ($index = $tokens->count() - 1; $index > 0; $index--) { + if (!$tokens[$index]->isGivenKind(\T_CLASS)) { + continue; + } + + $constructorAnalysis = $constructorAnalyzer->findNonAbstractConstructor($tokens, $index); + if ($constructorAnalysis === null) { + continue; + } + + $this->promoteProperties($tokens, $index, $constructorAnalysis); + } + + \krsort($this->tokensToInsert); + + /** + * @var int $index + * @var list $tokensToInsert + */ + foreach ($this->tokensToInsert as $index => $tokensToInsert) { + $tokens->insertAt($index, $tokensToInsert); + } + } + + private function promoteProperties(Tokens $tokens, int $classIndex, ConstructorAnalysis $constructorAnalysis): void + { + $isDoctrineEntity = $this->isDoctrineEntity($tokens, $classIndex); + $properties = $this->getClassProperties($tokens, $classIndex); + + $constructorParameterNames = $constructorAnalysis->getConstructorParameterNames(); + $constructorPromotableParameters = $constructorAnalysis->getConstructorPromotableParameters(); + $constructorPromotableAssignments = $constructorAnalysis->getConstructorPromotableAssignments(); + + foreach ($constructorPromotableParameters as $constructorParameterIndex => $constructorParameterName) { + if (!\array_key_exists($constructorParameterName, $constructorPromotableAssignments)) { + continue; + } + + $propertyIndex = $this->getPropertyIndex($tokens, $properties, $constructorPromotableAssignments[$constructorParameterName]); + + if (!$this->isPropertyToPromote($tokens, $propertyIndex, $isDoctrineEntity)) { + continue; + } + + $propertyType = $this->getType($tokens, $propertyIndex); + $parameterType = $this->getType($tokens, $constructorParameterIndex); + + if (!$this->typesAllowPromoting($propertyType, $parameterType)) { + continue; + } + + $assignedPropertyIndex = $tokens->getPrevTokenOfKind($constructorPromotableAssignments[$constructorParameterName], [[\T_STRING]]); + $oldParameterName = $tokens[$constructorParameterIndex]->getContent(); + $newParameterName = '$' . $tokens[$assignedPropertyIndex]->getContent(); + if ($oldParameterName !== $newParameterName && \in_array($newParameterName, $constructorParameterNames, true)) { + continue; + } + + $tokensToInsert = $this->removePropertyAndReturnTokensToInsert($tokens, $propertyIndex); + + $this->renameVariable($tokens, $constructorAnalysis->getConstructorIndex(), $oldParameterName, $newParameterName); + + $this->removeAssignment($tokens, $constructorPromotableAssignments[$constructorParameterName]); + $this->updateParameterSignature( + $tokens, + $constructorParameterIndex, + $tokensToInsert, + \substr($propertyType, 0, 1) === '?', + ); + } + } + + private function isDoctrineEntity(Tokens $tokens, int $index): bool + { + $phpDocIndex = $tokens->getPrevNonWhitespace($index); + \assert(\is_int($phpDocIndex)); + + if (!$tokens[$phpDocIndex]->isGivenKind(\T_DOC_COMMENT)) { + return false; + } + + $docBlock = new DocBlock($tokens[$phpDocIndex]->getContent()); + + foreach ($docBlock->getAnnotations() as $annotation) { + if (Preg::match('/\*\h+(@Document|@Entity|@Mapping\\\Entity|@ODM\\\Document|@ORM\\\Entity|@ORM\\\Mapping\\\Entity)/', $annotation->getContent())) { + return true; + } + } + + return false; + } + + /** + * @param array $properties + */ + private function getPropertyIndex(Tokens $tokens, array $properties, int $assignmentIndex): ?int + { + $propertyNameIndex = $tokens->getPrevTokenOfKind($assignmentIndex, [[\T_STRING]]); + \assert(\is_int($propertyNameIndex)); + + $propertyName = $tokens[$propertyNameIndex]->getContent(); + + foreach ($properties as $name => $index) { + if ($name !== $propertyName) { + continue; + } + + return $index; + } + + return null; + } + + private function isPropertyToPromote(Tokens $tokens, ?int $propertyIndex, bool $isDoctrineEntity): bool + { + if ($propertyIndex === null) { + return !$this->promoteOnlyExistingProperties; + } + + if (!$isDoctrineEntity) { + return true; + } + + $phpDocIndex = $tokens->getPrevTokenOfKind($propertyIndex, [[\T_DOC_COMMENT]]); + \assert(\is_int($phpDocIndex)); + + $variableIndex = $tokens->getNextTokenOfKind($phpDocIndex, ['{', [\T_VARIABLE]]); + + if ($variableIndex !== $propertyIndex) { + return true; + } + + $docBlock = new DocBlock($tokens[$phpDocIndex]->getContent()); + + return \count($docBlock->getAnnotations()) === 0; + } + + private function getType(Tokens $tokens, ?int $variableIndex): string + { + if ($variableIndex === null) { + return ''; + } + + $index = $tokens->getPrevTokenOfKind($variableIndex, ['(', ',', [\T_PRIVATE], [\T_PROTECTED], [\T_PUBLIC], [\T_VAR], [CT::T_ATTRIBUTE_CLOSE]]); + \assert(\is_int($index)); + + $index = $tokens->getNextMeaningfulToken($index); + \assert(\is_int($index)); + + $type = ''; + while ($index < $variableIndex) { + $type .= $tokens[$index]->getContent(); + + $index = $tokens->getNextMeaningfulToken($index); + \assert(\is_int($index)); + } + + return $type; + } + + private function typesAllowPromoting(string $propertyType, string $parameterType): bool + { + if ($propertyType === '') { + return true; + } + + if (\substr($propertyType, 0, 1) === '?') { + $propertyType = \substr($propertyType, 1); + } + + if (\substr($parameterType, 0, 1) === '?') { + $parameterType = \substr($parameterType, 1); + } + + return \strtolower($propertyType) === \strtolower($parameterType); + } + + /** + * @return array + */ + private function getClassProperties(Tokens $tokens, int $classIndex): array + { + $properties = []; + $tokensAnalyzer = new TokensAnalyzer($tokens); + + foreach ($tokensAnalyzer->getClassyElements() as $index => $element) { + if ($element['classIndex'] !== $classIndex) { + continue; + } + if ($element['type'] !== 'property') { + continue; + } + + $properties[\substr($element['token']->getContent(), 1)] = $index; + } + + return $properties; + } + + /** + * @return list + */ + private function removePropertyAndReturnTokensToInsert(Tokens $tokens, ?int $propertyIndex): array + { + if ($propertyIndex === null) { + return [new Token([\T_PUBLIC, 'public'])]; + } + + $visibilityIndex = $tokens->getPrevTokenOfKind($propertyIndex, [[\T_PRIVATE], [\T_PROTECTED], [\T_PUBLIC], [\T_VAR]]); + \assert(\is_int($visibilityIndex)); + + $prevPropertyIndex = $this->getTokenOfKindSibling($tokens, -1, $propertyIndex, ['{', '}', ';', ',']); + $nextPropertyIndex = $this->getTokenOfKindSibling($tokens, 1, $propertyIndex, [';', ',']); + + $removeFrom = $tokens->getTokenNotOfKindSibling($prevPropertyIndex, 1, [[\T_WHITESPACE], [\T_COMMENT]]); + \assert(\is_int($removeFrom)); + $removeTo = $nextPropertyIndex; + if ($tokens[$prevPropertyIndex]->equals(',')) { + $removeFrom = $prevPropertyIndex; + $removeTo = $propertyIndex; + } elseif ($tokens[$nextPropertyIndex]->equals(',')) { + $removeFrom = $tokens->getPrevMeaningfulToken($propertyIndex); + \assert(\is_int($removeFrom)); + $removeFrom++; + } + + $tokensToInsert = []; + for ($index = $removeFrom; $index <= $visibilityIndex - 1; $index++) { + $tokensToInsert[] = $tokens[$index]; + } + + $visibilityToken = $tokens[$visibilityIndex]; + if ($tokens[$visibilityIndex]->isGivenKind(\T_VAR)) { + $visibilityToken = new Token([\T_PUBLIC, 'public']); + } + $tokensToInsert[] = $visibilityToken; + + $tokens->clearRange($removeFrom + 1, $removeTo); + TokenRemover::removeWithLinesIfPossible($tokens, $removeFrom); + + return $tokensToInsert; + } + + /** + * @param list $tokenKinds + */ + private function getTokenOfKindSibling(Tokens $tokens, int $direction, int $index, array $tokenKinds): int + { + $index += $direction; + + while (!$tokens[$index]->equalsAny($tokenKinds)) { + $blockType = Tokens::detectBlockType($tokens[$index]); + + if ($blockType !== null) { + if ($blockType['isStart']) { + $index = $tokens->findBlockEnd($blockType['type'], $index); + } else { + $index = $tokens->findBlockStart($blockType['type'], $index); + } + } + + $index += $direction; + } + + return $index; + } + + private function renameVariable(Tokens $tokens, int $constructorIndex, string $oldName, string $newName): void + { + $parenthesesOpenIndex = $tokens->getNextTokenOfKind($constructorIndex, ['(']); + \assert(\is_int($parenthesesOpenIndex)); + $parenthesesCloseIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $parenthesesOpenIndex); + $braceOpenIndex = $tokens->getNextTokenOfKind($parenthesesCloseIndex, ['{']); + \assert(\is_int($braceOpenIndex)); + $braceCloseIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $braceOpenIndex); + + for ($index = $parenthesesOpenIndex; $index < $braceCloseIndex; $index++) { + if ($tokens[$index]->equals([\T_VARIABLE, $oldName])) { + $tokens[$index] = new Token([\T_VARIABLE, $newName]); + } + } + } + + private function removeAssignment(Tokens $tokens, int $variableAssignmentIndex): void + { + $thisIndex = $tokens->getPrevTokenOfKind($variableAssignmentIndex, [[\T_VARIABLE]]); + \assert(\is_int($thisIndex)); + + $propertyEndIndex = $tokens->getNextTokenOfKind($variableAssignmentIndex, [';']); + \assert(\is_int($propertyEndIndex)); + + $tokens->clearRange($thisIndex + 1, $propertyEndIndex); + TokenRemover::removeWithLinesIfPossible($tokens, $thisIndex); + } + + /** + * @param list $tokensToInsert + */ + private function updateParameterSignature(Tokens $tokens, int $constructorParameterIndex, array $tokensToInsert, bool $makeTypeNullable): void + { + $prevElementIndex = $tokens->getPrevTokenOfKind($constructorParameterIndex, ['(', ',', [CT::T_ATTRIBUTE_CLOSE]]); + \assert(\is_int($prevElementIndex)); + + $propertyStartIndex = $tokens->getNextMeaningfulToken($prevElementIndex); + \assert(\is_int($propertyStartIndex)); + + foreach ($tokensToInsert as $index => $token) { + if ($token->isGivenKind(\T_PUBLIC)) { + $tokensToInsert[$index] = new Token([CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC, $token->getContent()]); + } elseif ($token->isGivenKind(\T_PROTECTED)) { + $tokensToInsert[$index] = new Token([CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED, $token->getContent()]); + } elseif ($token->isGivenKind(\T_PRIVATE)) { + $tokensToInsert[$index] = new Token([CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE, $token->getContent()]); + } + } + $tokensToInsert[] = new Token([\T_WHITESPACE, ' ']); + + if ($makeTypeNullable && !$tokens[$propertyStartIndex]->isGivenKind(CT::T_NULLABLE_TYPE)) { + $tokensToInsert[] = new Token([CT::T_NULLABLE_TYPE, '?']); + } + + $this->tokensToInsert[$propertyStartIndex] = $tokensToInsert; + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/ReadonlyPromotedPropertiesFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/ReadonlyPromotedPropertiesFixer.php new file mode 100644 index 00000000..198e162f --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/ReadonlyPromotedPropertiesFixer.php @@ -0,0 +1,167 @@ +isAnyTokenKindsFound([ + CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE, + CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED, + CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC, + ]); + } + + public function isRisky(): bool + { + return true; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + $constructorAnalyzer = new ConstructorAnalyzer(); + + for ($index = $tokens->count() - 1; $index > 0; $index--) { + if (!$tokens[$index]->isGivenKind(\T_CLASS)) { + continue; + } + + if ($this->isClassReadonly($tokens, $index)) { + continue; + } + + $constructorAnalysis = $constructorAnalyzer->findNonAbstractConstructor($tokens, $index); + if ($constructorAnalysis === null) { + continue; + } + + $classOpenBraceIndex = $tokens->getNextTokenOfKind($index, ['{']); + \assert(\is_int($classOpenBraceIndex)); + $classCloseBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classOpenBraceIndex); + + $constructorOpenParenthesisIndex = $tokens->getNextTokenOfKind($constructorAnalysis->getConstructorIndex(), ['(']); + \assert(\is_int($constructorOpenParenthesisIndex)); + $constructorCloseParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $constructorOpenParenthesisIndex); + + $this->fixParameters( + $tokens, + $classOpenBraceIndex, + $classCloseBraceIndex, + $constructorOpenParenthesisIndex, + $constructorCloseParenthesisIndex, + ); + } + } + + private function isClassReadonly(Tokens $tokens, int $index): bool + { + do { + $index = $tokens->getPrevMeaningfulToken($index); + \assert(\is_int($index)); + } while ($tokens[$index]->isGivenKind([\T_ABSTRACT, \T_FINAL])); + + return $tokens[$index]->isGivenKind(\T_READONLY); + } + + private function fixParameters( + Tokens $tokens, + int $classOpenBraceIndex, + int $classCloseBraceIndex, + int $constructorOpenParenthesisIndex, + int $constructorCloseParenthesisIndex + ): void { + for ($index = $constructorCloseParenthesisIndex; $index > $constructorOpenParenthesisIndex; $index--) { + if ( + !$tokens[$index]->isGivenKind([ + CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE, + CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED, + CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC, + ]) + ) { + continue; + } + + $nextIndex = $tokens->getNextMeaningfulToken($index); + if ($tokens[$nextIndex]->isGivenKind(\T_READONLY)) { + continue; + } + + $prevIndex = $tokens->getPrevMeaningfulToken($index); + if ($tokens[$prevIndex]->isGivenKind(\T_READONLY)) { + continue; + } + + $propertyIndex = $tokens->getNextTokenOfKind($index, [[\T_VARIABLE]]); + \assert(\is_int($propertyIndex)); + + $propertyAssignment = $tokens->findSequence( + [ + [\T_VARIABLE, '$this'], + [\T_OBJECT_OPERATOR], + [\T_STRING, \substr($tokens[$propertyIndex]->getContent(), 1)], + ], + $classOpenBraceIndex, + $classCloseBraceIndex, + ); + if ($propertyAssignment !== null) { + continue; + } + + $tokens->insertAt( + $index + 1, + [ + new Token([\T_WHITESPACE, ' ']), + new Token([\T_READONLY, 'readonly']), + ], + ); + } + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/SingleSpaceAfterStatementFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/SingleSpaceAfterStatementFixer.php new file mode 100644 index 00000000..d2dea2b4 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/SingleSpaceAfterStatementFixer.php @@ -0,0 +1,154 @@ + */ + private array $tokens = [ + \T_ABSTRACT, + \T_AS, + \T_BREAK, + \T_CASE, + \T_CATCH, + \T_CLASS, + \T_CLONE, + \T_CONST, + \T_CONTINUE, + \T_DO, + \T_ECHO, + \T_ELSE, + \T_ELSEIF, + \T_EXTENDS, + \T_FINAL, + \T_FINALLY, + \T_FOR, + \T_FOREACH, + \T_FUNCTION, + \T_GLOBAL, + \T_GOTO, + \T_IF, + \T_IMPLEMENTS, + \T_INCLUDE, + \T_INCLUDE_ONCE, + \T_INSTANCEOF, + \T_INSTEADOF, + \T_INTERFACE, + \T_NAMESPACE, + \T_NEW, + \T_PRINT, + \T_PRIVATE, + \T_PROTECTED, + \T_PUBLIC, + \T_REQUIRE, + \T_REQUIRE_ONCE, + \T_RETURN, + \T_SWITCH, + \T_THROW, + \T_TRAIT, + \T_TRY, + \T_USE, + \T_VAR, + \T_WHILE, + \T_YIELD, + \T_YIELD_FROM, + CT::T_CONST_IMPORT, + CT::T_FUNCTION_IMPORT, + CT::T_USE_TRAIT, + CT::T_USE_LAMBDA, + ]; + + private bool $allowLinebreak = false; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Statements not followed by a semicolon must be followed by a single space.', + [new CodeSample("bar();\n")], + '', + ); + } + + public function getConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('allow_linebreak', 'whether to allow statement followed by linebreak')) + ->setAllowedTypes(['bool']) + ->setDefault($this->allowLinebreak) + ->getOption(), + ]); + } + + /** + * @param array $configuration + */ + public function configure(array $configuration): void + { + if (\array_key_exists('allow_linebreak', $configuration)) { + $this->allowLinebreak = $configuration['allow_linebreak']; + } + } + + public function getPriority(): int + { + return 0; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound($this->tokens); + } + + public function isRisky(): bool + { + return false; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index > 0; $index--) { + if (!$tokens[$index]->isGivenKind($this->tokens)) { + continue; + } + + if (!$this->canAddSpaceAfter($tokens, $index)) { + continue; + } + + $tokens->ensureWhitespaceAtIndex($index + 1, 0, ' '); + } + } + + private function canAddSpaceAfter(Tokens $tokens, int $index): bool + { + if ($tokens[$index + 1]->isGivenKind(\T_WHITESPACE)) { + return !$this->allowLinebreak || !Preg::match('/\R/', $tokens[$index + 1]->getContent()); + } + + if ($tokens[$index]->isGivenKind(\T_CLASS) && $tokens[$index + 1]->equals('(')) { + return false; + } + + return !\in_array($tokens[$index + 1]->getContent(), [';', ':'], true); + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/SingleSpaceBeforeStatementFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/SingleSpaceBeforeStatementFixer.php new file mode 100644 index 00000000..b52fe0d7 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/SingleSpaceBeforeStatementFixer.php @@ -0,0 +1,143 @@ + */ + private array $tokens = [ + \T_ABSTRACT, + \T_AS, + \T_BREAK, + \T_CASE, + \T_CATCH, + \T_CLASS, + \T_CLONE, + \T_CONST, + \T_CONTINUE, + \T_DO, + \T_ECHO, + \T_ELSE, + \T_ELSEIF, + \T_EXTENDS, + \T_FINAL, + \T_FINALLY, + \T_FOR, + \T_FOREACH, + \T_FUNCTION, + \T_GLOBAL, + \T_GOTO, + \T_IF, + \T_IMPLEMENTS, + \T_INCLUDE, + \T_INCLUDE_ONCE, + \T_INSTANCEOF, + \T_INSTEADOF, + \T_INTERFACE, + \T_NAMESPACE, + \T_NEW, + \T_PRINT, + \T_PRIVATE, + \T_PROTECTED, + \T_PUBLIC, + \T_REQUIRE, + \T_REQUIRE_ONCE, + \T_RETURN, + \T_SWITCH, + \T_THROW, + \T_TRAIT, + \T_TRY, + \T_USE, + \T_VAR, + \T_WHILE, + \T_YIELD, + \T_YIELD_FROM, + CT::T_CONST_IMPORT, + CT::T_FUNCTION_IMPORT, + CT::T_USE_TRAIT, + CT::T_USE_LAMBDA, + ]; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Statements not preceded by a line break must be preceded by a single space.', + [new CodeSample("isAnyTokenKindsFound($this->tokens); + } + + public function isRisky(): bool + { + return false; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index > 0; $index--) { + if (!$tokens[$index]->isGivenKind($this->tokens)) { + continue; + } + + if ($tokens[$index - 1]->isGivenKind(\T_OPEN_TAG)) { + continue; + } + + if ($tokens[$index - 2]->isGivenKind(\T_OPEN_TAG)) { + $this->fixTwoTokensAfterOpenTag($tokens, $index); + continue; + } + + $this->fixMoreThanTwoTokensAfterOpenTag($tokens, $index); + } + } + + private function fixTwoTokensAfterOpenTag(Tokens $tokens, int $index): void + { + if ($tokens[$index - 1]->isGivenKind(\T_WHITESPACE) && !Preg::match('/\R/', $tokens[$index - 2]->getContent())) { + $tokens->clearAt($index - 1); + } + } + + private function fixMoreThanTwoTokensAfterOpenTag(Tokens $tokens, int $index): void + { + if ($tokens[$index - 1]->isGivenKind(\T_WHITESPACE)) { + if (!Preg::match('/\R/', $tokens[$index - 1]->getContent())) { + $tokens[$index - 1] = new Token([\T_WHITESPACE, ' ']); + } + + return; + } + + if (!\in_array($tokens[$index - 1]->getContent(), ['!', '(', '@', '[', '{'], true)) { + $tokens->insertAt($index, new Token([\T_WHITESPACE, ' '])); + } + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/StringableInterfaceFixer.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/StringableInterfaceFixer.php new file mode 100644 index 00000000..622c0609 --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixer/StringableInterfaceFixer.php @@ -0,0 +1,232 @@ += 80000 && $tokens->isAllTokenKindsFound([\T_CLASS, \T_STRING]); + } + + public function isRisky(): bool + { + return false; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + $namespaceStartIndex = 0; + + for ($index = 1; $index < $tokens->count(); $index++) { + if ($tokens[$index]->isGivenKind(\T_NAMESPACE)) { + $namespaceStartIndex = $index; + continue; + } + + if (!$tokens[$index]->isGivenKind(\T_CLASS)) { + continue; + } + + $classStartIndex = $tokens->getNextTokenOfKind($index, ['{']); + \assert(\is_int($classStartIndex)); + + $classEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classStartIndex); + + if (!$this->doesHaveToStringMethod($tokens, $classStartIndex, $classEndIndex)) { + continue; + } + + if ($this->doesImplementStringable($tokens, $namespaceStartIndex, $index, $classStartIndex)) { + continue; + } + + $this->addStringableInterface($tokens, $index); + } + } + + private function doesHaveToStringMethod(Tokens $tokens, int $classStartIndex, int $classEndIndex): bool + { + $index = $classStartIndex; + + while ($index < $classEndIndex) { + $index++; + + if ($tokens[$index]->equals('{')) { + $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); + continue; + } + + if (!$tokens[$index]->isGivenKind(\T_FUNCTION)) { + continue; + } + + $functionNameIndex = $tokens->getNextMeaningfulToken($index); + + if ($tokens[$functionNameIndex]->equals([\T_STRING, '__toString'], false)) { + return true; + } + } + + return false; + } + + private function doesImplementStringable(Tokens $tokens, int $namespaceStartIndex, int $classKeywordIndex, int $classOpenBraceIndex): bool + { + $interfaces = $this->getInterfaces($tokens, $classKeywordIndex, $classOpenBraceIndex); + if ($interfaces === []) { + return false; + } + + if (\in_array('\stringable', $interfaces, true)) { + return true; + } + + if ($namespaceStartIndex === 0 && \in_array('stringable', $interfaces, true)) { + return true; + } + + foreach ($this->getImports($tokens, $namespaceStartIndex, $classKeywordIndex) as $import) { + if (\in_array($import, $interfaces, true)) { + return true; + } + } + + return false; + } + + /** + * @return list + */ + private function getInterfaces(Tokens $tokens, int $classKeywordIndex, int $classOpenBraceIndex): array + { + $implementsIndex = $tokens->getNextTokenOfKind($classKeywordIndex, ['{', [\T_IMPLEMENTS]]); + \assert(\is_int($implementsIndex)); + + $interfaces = []; + $interface = ''; + for ( + $index = $tokens->getNextMeaningfulToken($implementsIndex); + $index < $classOpenBraceIndex; + $index = $tokens->getNextMeaningfulToken($index) + ) { + \assert(\is_int($index)); + if ($tokens[$index]->equals(',')) { + $interfaces[] = \strtolower($interface); + $interface = ''; + continue; + } + $interface .= $tokens[$index]->getContent(); + } + if ($interface !== '') { + $interfaces[] = \strtolower($interface); + } + + return $interfaces; + } + + /** + * @return iterable + */ + private function getImports(Tokens $tokens, int $namespaceStartIndex, int $classKeywordIndex): iterable + { + for ($index = $namespaceStartIndex; $index < $classKeywordIndex; $index++) { + if (!$tokens[$index]->isGivenKind(\T_USE)) { + continue; + } + $nameIndex = $tokens->getNextMeaningfulToken($index); + \assert(\is_int($nameIndex)); + + if ($tokens[$nameIndex]->isGivenKind(\T_NS_SEPARATOR)) { + $nameIndex = $tokens->getNextMeaningfulToken($nameIndex); + \assert(\is_int($nameIndex)); + } + + $nextIndex = $tokens->getNextMeaningfulToken($nameIndex); + \assert(\is_int($nextIndex)); + if ($tokens[$nextIndex]->isGivenKind(\T_AS)) { + $nameIndex = $tokens->getNextMeaningfulToken($nextIndex); + \assert(\is_int($nameIndex)); + } + + yield \strtolower($tokens[$nameIndex]->getContent()); + } + } + + private function addStringableInterface(Tokens $tokens, int $classIndex): void + { + $implementsIndex = $tokens->getNextTokenOfKind($classIndex, ['{', [\T_IMPLEMENTS]]); + \assert(\is_int($implementsIndex)); + + if ($tokens[$implementsIndex]->equals('{')) { + $prevIndex = $tokens->getPrevMeaningfulToken($implementsIndex); + \assert(\is_int($prevIndex)); + + $tokens->insertAt( + $prevIndex + 1, + [ + new Token([\T_WHITESPACE, ' ']), + new Token([\T_IMPLEMENTS, 'implements']), + new Token([\T_WHITESPACE, ' ']), + new Token([\T_NS_SEPARATOR, '\\']), + new Token([\T_STRING, \Stringable::class]), + ], + ); + + return; + } + + $afterImplementsIndex = $tokens->getNextMeaningfulToken($implementsIndex); + \assert(\is_int($afterImplementsIndex)); + + $tokens->insertAt( + $afterImplementsIndex, + [ + new Token([\T_NS_SEPARATOR, '\\']), + new Token([\T_STRING, \Stringable::class]), + new Token(','), + new Token([\T_WHITESPACE, ' ']), + ], + ); + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixers.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixers.php new file mode 100644 index 00000000..973b9e6e --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/Fixers.php @@ -0,0 +1,44 @@ + + */ +final class Fixers implements \IteratorAggregate +{ + /** + * @return \Generator + */ + public function getIterator(): \Generator + { + $classNames = []; + foreach (new \DirectoryIterator(__DIR__ . '/Fixer') as $fileInfo) { + $fileName = $fileInfo->getBasename('.php'); + if (\in_array($fileName, ['.', '..', 'AbstractFixer', 'AbstractTypesFixer'], true)) { + continue; + } + $classNames[] = __NAMESPACE__ . '\Fixer\\' . $fileName; + } + + \sort($classNames); + + foreach ($classNames as $className) { + $fixer = new $className(); + \assert($fixer instanceof FixerInterface); + + yield $fixer; + } + } +} diff --git a/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/TokenRemover.php b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/TokenRemover.php new file mode 100644 index 00000000..8aefbb6e --- /dev/null +++ b/vendor/kubawerlos/php-cs-fixer-custom-fixers/src/TokenRemover.php @@ -0,0 +1,105 @@ +getNonEmptySibling($index, -1); + \assert(\is_int($prevIndex)); + + $wasNewlineRemoved = self::handleWhitespaceBefore($tokens, $prevIndex); + + $nextIndex = $tokens->getNonEmptySibling($index, 1); + if ($nextIndex !== null) { + self::handleWhitespaceAfter($tokens, $nextIndex, $wasNewlineRemoved); + } + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } + + private static function isTokenOnlyMeaningfulInLine(Tokens $tokens, int $index): bool + { + return !self::hasMeaningTokenInLineBefore($tokens, $index) && !self::hasMeaningTokenInLineAfter($tokens, $index); + } + + private static function hasMeaningTokenInLineBefore(Tokens $tokens, int $index): bool + { + $prevIndex = $tokens->getNonEmptySibling($index, -1); + \assert(\is_int($prevIndex)); + + if (!$tokens[$prevIndex]->isGivenKind([\T_OPEN_TAG, \T_WHITESPACE])) { + return true; + } + + if ($tokens[$prevIndex]->isGivenKind(\T_OPEN_TAG) && !Preg::match('/\R$/', $tokens[$prevIndex]->getContent())) { + return true; + } + + if (!Preg::match('/\R/', $tokens[$prevIndex]->getContent())) { + $prevPrevIndex = $tokens->getNonEmptySibling($prevIndex, -1); + \assert(\is_int($prevPrevIndex)); + + if (!$tokens[$prevPrevIndex]->isGivenKind(\T_OPEN_TAG) || !Preg::match('/\R$/', $tokens[$prevPrevIndex]->getContent())) { + return true; + } + } + + return false; + } + + private static function hasMeaningTokenInLineAfter(Tokens $tokens, int $index): bool + { + $nextIndex = $tokens->getNonEmptySibling($index, 1); + if ($nextIndex === null) { + return false; + } + + if (!$tokens[$nextIndex]->isGivenKind(\T_WHITESPACE)) { + return true; + } + + return !Preg::match('/\R/', $tokens[$nextIndex]->getContent()); + } + + private static function handleWhitespaceBefore(Tokens $tokens, int $index): bool + { + if (!$tokens[$index]->isGivenKind(\T_WHITESPACE)) { + return false; + } + $contentWithoutTrailingSpaces = Preg::replace('/\h+$/', '', $tokens[$index]->getContent()); + + $contentWithoutTrailingSpacesAndNewline = Preg::replace('/\R$/', '', $contentWithoutTrailingSpaces, 1); + + $tokens->ensureWhitespaceAtIndex($index, 0, $contentWithoutTrailingSpacesAndNewline); + + return $contentWithoutTrailingSpaces !== $contentWithoutTrailingSpacesAndNewline; + } + + private static function handleWhitespaceAfter(Tokens $tokens, int $index, bool $wasNewlineRemoved): void + { + $pattern = $wasNewlineRemoved ? '/^\h+/' : '/^\h*\R/'; + + $newContent = Preg::replace($pattern, '', $tokens[$index]->getContent()); + + $tokens->ensureWhitespaceAtIndex($index, 0, $newContent); + } +} diff --git a/vendor/league/oauth2-client/LICENSE b/vendor/league/oauth2-client/LICENSE new file mode 100644 index 00000000..7dfa39b7 --- /dev/null +++ b/vendor/league/oauth2-client/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013-2020 Alex Bilbie + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/league/oauth2-client/README.md b/vendor/league/oauth2-client/README.md new file mode 100644 index 00000000..cbb449d4 --- /dev/null +++ b/vendor/league/oauth2-client/README.md @@ -0,0 +1,58 @@ +# OAuth 2.0 Client + +This package provides a base for integrating with [OAuth 2.0](http://oauth.net/2/) service providers. + +[![Gitter Chat](https://img.shields.io/badge/gitter-join_chat-brightgreen.svg?style=flat-square)](https://gitter.im/thephpleague/oauth2-client) +[![Source Code](https://img.shields.io/badge/source-thephpleague/oauth2--client-blue.svg?style=flat-square)](https://github.com/thephpleague/oauth2-client) +[![Latest Version](https://img.shields.io/github/release/thephpleague/oauth2-client.svg?style=flat-square)](https://github.com/thephpleague/oauth2-client/releases) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](https://github.com/thephpleague/oauth2-client/blob/master/LICENSE) +[![Build Status](https://img.shields.io/github/actions/workflow/status/thephpleague/oauth2-client/continuous-integration.yml?label=CI&logo=github&style=flat-square)](https://github.com/thephpleague/oauth2-client/actions?query=workflow%3ACI) +[![Codecov Code Coverage](https://img.shields.io/codecov/c/gh/thephpleague/oauth2-client?label=codecov&logo=codecov&style=flat-square)](https://codecov.io/gh/thephpleague/oauth2-client) +[![Total Downloads](https://img.shields.io/packagist/dt/league/oauth2-client.svg?style=flat-square)](https://packagist.org/packages/league/oauth2-client) + +--- + +The OAuth 2.0 login flow, seen commonly around the web in the form of "Connect with Facebook/Google/etc." buttons, is a common integration added to web applications, but it can be tricky and tedious to do right. To help, we've created the `league/oauth2-client` package, which provides a base for integrating with various OAuth 2.0 providers, without overburdening your application with the concerns of [RFC 6749](http://tools.ietf.org/html/rfc6749). + +This OAuth 2.0 client library will work with any OAuth 2.0 provider that conforms to the OAuth 2.0 Authorization Framework. Out-of-the-box, we provide a `GenericProvider` class to connect to any service provider that uses [Bearer tokens](http://tools.ietf.org/html/rfc6750). See our [basic usage guide](https://oauth2-client.thephpleague.com/usage/) for examples using `GenericProvider`. + +Many service providers provide additional functionality above and beyond the OAuth 2.0 specification. For this reason, you may extend and wrap this library to support additional behavior. There are already many [official](https://oauth2-client.thephpleague.com/providers/league/) and [third-party](https://oauth2-client.thephpleague.com/providers/thirdparty/) provider clients available (e.g., Facebook, GitHub, Google, Instagram, LinkedIn, etc.). If your provider isn't in the list, feel free to add it. + +This package is compliant with [PSR-1][], [PSR-2][], [PSR-4][], and [PSR-7][]. If you notice compliance oversights, please send a patch via pull request. If you're interested in contributing to this library, please take a look at our [contributing guidelines](https://github.com/thephpleague/oauth2-client/blob/master/CONTRIBUTING.md). + +## Requirements + +We support the following versions of PHP: + +* PHP 8.1 +* PHP 8.0 +* PHP 7.4 +* PHP 7.3 +* PHP 7.2 +* PHP 7.1 +* PHP 7.0 +* PHP 5.6 + +## Provider Clients + +We provide a list of [official PHP League provider clients](https://oauth2-client.thephpleague.com/providers/league/), as well as [third-party provider clients](https://oauth2-client.thephpleague.com/providers/thirdparty/). + +To build your own provider client, please refer to "[Implementing a Provider Client](https://oauth2-client.thephpleague.com/providers/implementing/)." + +## Usage + +For usage and code examples, check out our [basic usage guide](https://oauth2-client.thephpleague.com/usage/). + +## Contributing + +Please see [our contributing guidelines](https://github.com/thephpleague/oauth2-client/blob/master/CONTRIBUTING.md) for details. + +## License + +The MIT License (MIT). Please see [LICENSE](https://github.com/thephpleague/oauth2-client/blob/master/LICENSE) for more information. + + +[PSR-1]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md +[PSR-2]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md +[PSR-4]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md +[PSR-7]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-7-http-message.md diff --git a/vendor/league/oauth2-client/composer.json b/vendor/league/oauth2-client/composer.json new file mode 100644 index 00000000..59201f48 --- /dev/null +++ b/vendor/league/oauth2-client/composer.json @@ -0,0 +1,58 @@ +{ + "name": "league/oauth2-client", + "description": "OAuth 2.0 Client Library", + "license": "MIT", + "config": { + "sort-packages": true + }, + "require": { + "php": "^5.6 || ^7.0 || ^8.0", + "guzzlehttp/guzzle": "^6.0 || ^7.0", + "paragonie/random_compat": "^1 || ^2 || ^9.99" + }, + "require-dev": { + "mockery/mockery": "^1.3.5", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpunit/phpunit": "^5.7 || ^6.0 || ^9.5", + "squizlabs/php_codesniffer": "^2.3 || ^3.0" + }, + "keywords": [ + "oauth", + "oauth2", + "authorization", + "authentication", + "idp", + "identity", + "sso", + "single sign on" + ], + "authors": [ + { + "name": "Alex Bilbie", + "email": "hello@alexbilbie.com", + "homepage": "http://www.alexbilbie.com", + "role": "Developer" + }, + { + "name": "Woody Gilk", + "homepage": "https://github.com/shadowhand", + "role": "Contributor" + } + + ], + "autoload": { + "psr-4": { + "League\\OAuth2\\Client\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "League\\OAuth2\\Client\\Test\\": "test/src/" + } + }, + "extra": { + "branch-alias": { + "dev-2.x": "2.0.x-dev" + } + } +} diff --git a/vendor/league/oauth2-client/src/Grant/AbstractGrant.php b/vendor/league/oauth2-client/src/Grant/AbstractGrant.php new file mode 100644 index 00000000..2c0244ba --- /dev/null +++ b/vendor/league/oauth2-client/src/Grant/AbstractGrant.php @@ -0,0 +1,80 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Grant; + +use League\OAuth2\Client\Tool\RequiredParameterTrait; + +/** + * Represents a type of authorization grant. + * + * An authorization grant is a credential representing the resource + * owner's authorization (to access its protected resources) used by the + * client to obtain an access token. OAuth 2.0 defines four + * grant types -- authorization code, implicit, resource owner password + * credentials, and client credentials -- as well as an extensibility + * mechanism for defining additional types. + * + * @link http://tools.ietf.org/html/rfc6749#section-1.3 Authorization Grant (RFC 6749, §1.3) + */ +abstract class AbstractGrant +{ + use RequiredParameterTrait; + + /** + * Returns the name of this grant, eg. 'grant_name', which is used as the + * grant type when encoding URL query parameters. + * + * @return string + */ + abstract protected function getName(); + + /** + * Returns a list of all required request parameters. + * + * @return array + */ + abstract protected function getRequiredRequestParameters(); + + /** + * Returns this grant's name as its string representation. This allows for + * string interpolation when building URL query parameters. + * + * @return string + */ + public function __toString() + { + return $this->getName(); + } + + /** + * Prepares an access token request's parameters by checking that all + * required parameters are set, then merging with any given defaults. + * + * @param array $defaults + * @param array $options + * @return array + */ + public function prepareRequestParameters(array $defaults, array $options) + { + $defaults['grant_type'] = $this->getName(); + + $required = $this->getRequiredRequestParameters(); + $provided = array_merge($defaults, $options); + + $this->checkRequiredParameters($required, $provided); + + return $provided; + } +} diff --git a/vendor/league/oauth2-client/src/Grant/AuthorizationCode.php b/vendor/league/oauth2-client/src/Grant/AuthorizationCode.php new file mode 100644 index 00000000..c49460c0 --- /dev/null +++ b/vendor/league/oauth2-client/src/Grant/AuthorizationCode.php @@ -0,0 +1,41 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Grant; + +/** + * Represents an authorization code grant. + * + * @link http://tools.ietf.org/html/rfc6749#section-1.3.1 Authorization Code (RFC 6749, §1.3.1) + */ +class AuthorizationCode extends AbstractGrant +{ + /** + * @inheritdoc + */ + protected function getName() + { + return 'authorization_code'; + } + + /** + * @inheritdoc + */ + protected function getRequiredRequestParameters() + { + return [ + 'code', + ]; + } +} diff --git a/vendor/league/oauth2-client/src/Grant/ClientCredentials.php b/vendor/league/oauth2-client/src/Grant/ClientCredentials.php new file mode 100644 index 00000000..dc78c4fd --- /dev/null +++ b/vendor/league/oauth2-client/src/Grant/ClientCredentials.php @@ -0,0 +1,39 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Grant; + +/** + * Represents a client credentials grant. + * + * @link http://tools.ietf.org/html/rfc6749#section-1.3.4 Client Credentials (RFC 6749, §1.3.4) + */ +class ClientCredentials extends AbstractGrant +{ + /** + * @inheritdoc + */ + protected function getName() + { + return 'client_credentials'; + } + + /** + * @inheritdoc + */ + protected function getRequiredRequestParameters() + { + return []; + } +} diff --git a/vendor/league/oauth2-client/src/Grant/Exception/InvalidGrantException.php b/vendor/league/oauth2-client/src/Grant/Exception/InvalidGrantException.php new file mode 100644 index 00000000..c3c4e677 --- /dev/null +++ b/vendor/league/oauth2-client/src/Grant/Exception/InvalidGrantException.php @@ -0,0 +1,26 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Grant\Exception; + +use InvalidArgumentException; + +/** + * Exception thrown if the grant does not extend from AbstractGrant. + * + * @see League\OAuth2\Client\Grant\AbstractGrant + */ +class InvalidGrantException extends InvalidArgumentException +{ +} diff --git a/vendor/league/oauth2-client/src/Grant/GrantFactory.php b/vendor/league/oauth2-client/src/Grant/GrantFactory.php new file mode 100644 index 00000000..71990e83 --- /dev/null +++ b/vendor/league/oauth2-client/src/Grant/GrantFactory.php @@ -0,0 +1,104 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Grant; + +use League\OAuth2\Client\Grant\Exception\InvalidGrantException; + +/** + * Represents a factory used when retrieving an authorization grant type. + */ +class GrantFactory +{ + /** + * @var array + */ + protected $registry = []; + + /** + * Defines a grant singleton in the registry. + * + * @param string $name + * @param AbstractGrant $grant + * @return self + */ + public function setGrant($name, AbstractGrant $grant) + { + $this->registry[$name] = $grant; + + return $this; + } + + /** + * Returns a grant singleton by name. + * + * If the grant has not be registered, a default grant will be loaded. + * + * @param string $name + * @return AbstractGrant + */ + public function getGrant($name) + { + if (empty($this->registry[$name])) { + $this->registerDefaultGrant($name); + } + + return $this->registry[$name]; + } + + /** + * Registers a default grant singleton by name. + * + * @param string $name + * @return self + */ + protected function registerDefaultGrant($name) + { + // PascalCase the grant. E.g: 'authorization_code' becomes 'AuthorizationCode' + $class = str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $name))); + $class = 'League\\OAuth2\\Client\\Grant\\' . $class; + + $this->checkGrant($class); + + return $this->setGrant($name, new $class); + } + + /** + * Determines if a variable is a valid grant. + * + * @param mixed $class + * @return boolean + */ + public function isGrant($class) + { + return is_subclass_of($class, AbstractGrant::class); + } + + /** + * Checks if a variable is a valid grant. + * + * @throws InvalidGrantException + * @param mixed $class + * @return void + */ + public function checkGrant($class) + { + if (!$this->isGrant($class)) { + throw new InvalidGrantException(sprintf( + 'Grant "%s" must extend AbstractGrant', + is_object($class) ? get_class($class) : $class + )); + } + } +} diff --git a/vendor/league/oauth2-client/src/Grant/Password.php b/vendor/league/oauth2-client/src/Grant/Password.php new file mode 100644 index 00000000..6543b2eb --- /dev/null +++ b/vendor/league/oauth2-client/src/Grant/Password.php @@ -0,0 +1,42 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Grant; + +/** + * Represents a resource owner password credentials grant. + * + * @link http://tools.ietf.org/html/rfc6749#section-1.3.3 Resource Owner Password Credentials (RFC 6749, §1.3.3) + */ +class Password extends AbstractGrant +{ + /** + * @inheritdoc + */ + protected function getName() + { + return 'password'; + } + + /** + * @inheritdoc + */ + protected function getRequiredRequestParameters() + { + return [ + 'username', + 'password', + ]; + } +} diff --git a/vendor/league/oauth2-client/src/Grant/RefreshToken.php b/vendor/league/oauth2-client/src/Grant/RefreshToken.php new file mode 100644 index 00000000..81921823 --- /dev/null +++ b/vendor/league/oauth2-client/src/Grant/RefreshToken.php @@ -0,0 +1,41 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Grant; + +/** + * Represents a refresh token grant. + * + * @link http://tools.ietf.org/html/rfc6749#section-6 Refreshing an Access Token (RFC 6749, §6) + */ +class RefreshToken extends AbstractGrant +{ + /** + * @inheritdoc + */ + protected function getName() + { + return 'refresh_token'; + } + + /** + * @inheritdoc + */ + protected function getRequiredRequestParameters() + { + return [ + 'refresh_token', + ]; + } +} diff --git a/vendor/league/oauth2-client/src/OptionProvider/HttpBasicAuthOptionProvider.php b/vendor/league/oauth2-client/src/OptionProvider/HttpBasicAuthOptionProvider.php new file mode 100644 index 00000000..3da40656 --- /dev/null +++ b/vendor/league/oauth2-client/src/OptionProvider/HttpBasicAuthOptionProvider.php @@ -0,0 +1,42 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\OptionProvider; + +use InvalidArgumentException; + +/** + * Add http basic auth into access token request options + * @link https://tools.ietf.org/html/rfc6749#section-2.3.1 + */ +class HttpBasicAuthOptionProvider extends PostAuthOptionProvider +{ + /** + * @inheritdoc + */ + public function getAccessTokenOptions($method, array $params) + { + if (empty($params['client_id']) || empty($params['client_secret'])) { + throw new InvalidArgumentException('clientId and clientSecret are required for http basic auth'); + } + + $encodedCredentials = base64_encode(sprintf('%s:%s', $params['client_id'], $params['client_secret'])); + unset($params['client_id'], $params['client_secret']); + + $options = parent::getAccessTokenOptions($method, $params); + $options['headers']['Authorization'] = 'Basic ' . $encodedCredentials; + + return $options; + } +} diff --git a/vendor/league/oauth2-client/src/OptionProvider/OptionProviderInterface.php b/vendor/league/oauth2-client/src/OptionProvider/OptionProviderInterface.php new file mode 100644 index 00000000..1126d25a --- /dev/null +++ b/vendor/league/oauth2-client/src/OptionProvider/OptionProviderInterface.php @@ -0,0 +1,30 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\OptionProvider; + +/** + * Interface for access token options provider + */ +interface OptionProviderInterface +{ + /** + * Builds request options used for requesting an access token. + * + * @param string $method + * @param array $params + * @return array + */ + public function getAccessTokenOptions($method, array $params); +} diff --git a/vendor/league/oauth2-client/src/OptionProvider/PostAuthOptionProvider.php b/vendor/league/oauth2-client/src/OptionProvider/PostAuthOptionProvider.php new file mode 100644 index 00000000..12d920ec --- /dev/null +++ b/vendor/league/oauth2-client/src/OptionProvider/PostAuthOptionProvider.php @@ -0,0 +1,51 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\OptionProvider; + +use League\OAuth2\Client\Provider\AbstractProvider; +use League\OAuth2\Client\Tool\QueryBuilderTrait; + +/** + * Provide options for access token + */ +class PostAuthOptionProvider implements OptionProviderInterface +{ + use QueryBuilderTrait; + + /** + * @inheritdoc + */ + public function getAccessTokenOptions($method, array $params) + { + $options = ['headers' => ['content-type' => 'application/x-www-form-urlencoded']]; + + if ($method === AbstractProvider::METHOD_POST) { + $options['body'] = $this->getAccessTokenBody($params); + } + + return $options; + } + + /** + * Returns the request body for requesting an access token. + * + * @param array $params + * @return string + */ + protected function getAccessTokenBody(array $params) + { + return $this->buildQueryString($params); + } +} diff --git a/vendor/league/oauth2-client/src/Provider/AbstractProvider.php b/vendor/league/oauth2-client/src/Provider/AbstractProvider.php new file mode 100644 index 00000000..293a54d6 --- /dev/null +++ b/vendor/league/oauth2-client/src/Provider/AbstractProvider.php @@ -0,0 +1,941 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Provider; + +use GuzzleHttp\Client as HttpClient; +use GuzzleHttp\ClientInterface as HttpClientInterface; +use GuzzleHttp\Exception\BadResponseException; +use InvalidArgumentException; +use League\OAuth2\Client\Grant\AbstractGrant; +use League\OAuth2\Client\Grant\GrantFactory; +use League\OAuth2\Client\OptionProvider\OptionProviderInterface; +use League\OAuth2\Client\OptionProvider\PostAuthOptionProvider; +use League\OAuth2\Client\Provider\Exception\IdentityProviderException; +use League\OAuth2\Client\Token\AccessToken; +use League\OAuth2\Client\Token\AccessTokenInterface; +use League\OAuth2\Client\Tool\ArrayAccessorTrait; +use League\OAuth2\Client\Tool\GuardedPropertyTrait; +use League\OAuth2\Client\Tool\QueryBuilderTrait; +use League\OAuth2\Client\Tool\RequestFactory; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; +use UnexpectedValueException; + +/** + * Represents a service provider (authorization server). + * + * @link http://tools.ietf.org/html/rfc6749#section-1.1 Roles (RFC 6749, §1.1) + */ +abstract class AbstractProvider +{ + use ArrayAccessorTrait; + use GuardedPropertyTrait; + use QueryBuilderTrait; + + /** + * @var string|null Key used in a token response to identify the resource owner. + */ + const ACCESS_TOKEN_RESOURCE_OWNER_ID = null; + + /** + * @var string HTTP method used to fetch access tokens. + */ + const METHOD_GET = 'GET'; + + /** + * @var string HTTP method used to fetch access tokens. + */ + const METHOD_POST = 'POST'; + + /** + * @var string PKCE method used to fetch authorization token. + * The PKCE code challenge will be hashed with sha256 (recommended). + */ + const PKCE_METHOD_S256 = 'S256'; + + /** + * @var string PKCE method used to fetch authorization token. + * The PKCE code challenge will be sent as plain text, this is NOT recommended. + * Only use `plain` if no other option is possible. + */ + const PKCE_METHOD_PLAIN = 'plain'; + + /** + * @var string + */ + protected $clientId; + + /** + * @var string + */ + protected $clientSecret; + + /** + * @var string + */ + protected $redirectUri; + + /** + * @var string + */ + protected $state; + + /** + * @var string|null + */ + protected $pkceCode = null; + + /** + * @var GrantFactory + */ + protected $grantFactory; + + /** + * @var RequestFactory + */ + protected $requestFactory; + + /** + * @var HttpClientInterface + */ + protected $httpClient; + + /** + * @var OptionProviderInterface + */ + protected $optionProvider; + + /** + * Constructs an OAuth 2.0 service provider. + * + * @param array $options An array of options to set on this provider. + * Options include `clientId`, `clientSecret`, `redirectUri`, and `state`. + * Individual providers may introduce more options, as needed. + * @param array $collaborators An array of collaborators that may be used to + * override this provider's default behavior. Collaborators include + * `grantFactory`, `requestFactory`, and `httpClient`. + * Individual providers may introduce more collaborators, as needed. + */ + public function __construct(array $options = [], array $collaborators = []) + { + // We'll let the GuardedPropertyTrait handle mass assignment of incoming + // options, skipping any blacklisted properties defined in the provider + $this->fillProperties($options); + + if (empty($collaborators['grantFactory'])) { + $collaborators['grantFactory'] = new GrantFactory(); + } + $this->setGrantFactory($collaborators['grantFactory']); + + if (empty($collaborators['requestFactory'])) { + $collaborators['requestFactory'] = new RequestFactory(); + } + $this->setRequestFactory($collaborators['requestFactory']); + + if (empty($collaborators['httpClient'])) { + $client_options = $this->getAllowedClientOptions($options); + + $collaborators['httpClient'] = new HttpClient( + array_intersect_key($options, array_flip($client_options)) + ); + } + $this->setHttpClient($collaborators['httpClient']); + + if (empty($collaborators['optionProvider'])) { + $collaborators['optionProvider'] = new PostAuthOptionProvider(); + } + $this->setOptionProvider($collaborators['optionProvider']); + } + + /** + * Returns the list of options that can be passed to the HttpClient + * + * @param array $options An array of options to set on this provider. + * Options include `clientId`, `clientSecret`, `redirectUri`, and `state`. + * Individual providers may introduce more options, as needed. + * @return array The options to pass to the HttpClient constructor + */ + protected function getAllowedClientOptions(array $options) + { + $client_options = ['timeout', 'proxy']; + + // Only allow turning off ssl verification if it's for a proxy + if (!empty($options['proxy'])) { + $client_options[] = 'verify'; + } + + return $client_options; + } + + /** + * Sets the grant factory instance. + * + * @param GrantFactory $factory + * @return self + */ + public function setGrantFactory(GrantFactory $factory) + { + $this->grantFactory = $factory; + + return $this; + } + + /** + * Returns the current grant factory instance. + * + * @return GrantFactory + */ + public function getGrantFactory() + { + return $this->grantFactory; + } + + /** + * Sets the request factory instance. + * + * @param RequestFactory $factory + * @return self + */ + public function setRequestFactory(RequestFactory $factory) + { + $this->requestFactory = $factory; + + return $this; + } + + /** + * Returns the request factory instance. + * + * @return RequestFactory + */ + public function getRequestFactory() + { + return $this->requestFactory; + } + + /** + * Sets the HTTP client instance. + * + * @param HttpClientInterface $client + * @return self + */ + public function setHttpClient(HttpClientInterface $client) + { + $this->httpClient = $client; + + return $this; + } + + /** + * Returns the HTTP client instance. + * + * @return HttpClientInterface + */ + public function getHttpClient() + { + return $this->httpClient; + } + + /** + * Sets the option provider instance. + * + * @param OptionProviderInterface $provider + * @return self + */ + public function setOptionProvider(OptionProviderInterface $provider) + { + $this->optionProvider = $provider; + + return $this; + } + + /** + * Returns the option provider instance. + * + * @return OptionProviderInterface + */ + public function getOptionProvider() + { + return $this->optionProvider; + } + + /** + * Returns the current value of the state parameter. + * + * This can be accessed by the redirect handler during authorization. + * + * @return string + */ + public function getState() + { + return $this->state; + } + + /** + * Set the value of the pkceCode parameter. + * + * When using PKCE this should be set before requesting an access token. + * + * @param string $pkceCode + * @return self + */ + public function setPkceCode($pkceCode) + { + $this->pkceCode = $pkceCode; + return $this; + } + + /** + * Returns the current value of the pkceCode parameter. + * + * This can be accessed by the redirect handler during authorization. + * + * @return string|null + */ + public function getPkceCode() + { + return $this->pkceCode; + } + + /** + * Returns the base URL for authorizing a client. + * + * Eg. https://oauth.service.com/authorize + * + * @return string + */ + abstract public function getBaseAuthorizationUrl(); + + /** + * Returns the base URL for requesting an access token. + * + * Eg. https://oauth.service.com/token + * + * @param array $params + * @return string + */ + abstract public function getBaseAccessTokenUrl(array $params); + + /** + * Returns the URL for requesting the resource owner's details. + * + * @param AccessToken $token + * @return string + */ + abstract public function getResourceOwnerDetailsUrl(AccessToken $token); + + /** + * Returns a new random string to use as the state parameter in an + * authorization flow. + * + * @param int $length Length of the random string to be generated. + * @return string + */ + protected function getRandomState($length = 32) + { + // Converting bytes to hex will always double length. Hence, we can reduce + // the amount of bytes by half to produce the correct length. + return bin2hex(random_bytes($length / 2)); + } + + /** + * Returns a new random string to use as PKCE code_verifier and + * hashed as code_challenge parameters in an authorization flow. + * Must be between 43 and 128 characters long. + * + * @param int $length Length of the random string to be generated. + * @return string + */ + protected function getRandomPkceCode($length = 64) + { + return substr( + strtr( + base64_encode(random_bytes($length)), + '+/', + '-_' + ), + 0, + $length + ); + } + + /** + * Returns the default scopes used by this provider. + * + * This should only be the scopes that are required to request the details + * of the resource owner, rather than all the available scopes. + * + * @return array + */ + abstract protected function getDefaultScopes(); + + /** + * Returns the string that should be used to separate scopes when building + * the URL for requesting an access token. + * + * @return string Scope separator, defaults to ',' + */ + protected function getScopeSeparator() + { + return ','; + } + + /** + * @return string|null + */ + protected function getPkceMethod() + { + return null; + } + + /** + * Returns authorization parameters based on provided options. + * + * @param array $options + * @return array Authorization parameters + */ + protected function getAuthorizationParameters(array $options) + { + if (empty($options['state'])) { + $options['state'] = $this->getRandomState(); + } + + if (empty($options['scope'])) { + $options['scope'] = $this->getDefaultScopes(); + } + + $options += [ + 'response_type' => 'code', + 'approval_prompt' => 'auto' + ]; + + if (is_array($options['scope'])) { + $separator = $this->getScopeSeparator(); + $options['scope'] = implode($separator, $options['scope']); + } + + // Store the state as it may need to be accessed later on. + $this->state = $options['state']; + + $pkceMethod = $this->getPkceMethod(); + if (!empty($pkceMethod)) { + $this->pkceCode = $this->getRandomPkceCode(); + if ($pkceMethod === static::PKCE_METHOD_S256) { + $options['code_challenge'] = trim( + strtr( + base64_encode(hash('sha256', $this->pkceCode, true)), + '+/', + '-_' + ), + '=' + ); + } elseif ($pkceMethod === static::PKCE_METHOD_PLAIN) { + $options['code_challenge'] = $this->pkceCode; + } else { + throw new InvalidArgumentException('Unknown PKCE method "' . $pkceMethod . '".'); + } + $options['code_challenge_method'] = $pkceMethod; + } + + // Business code layer might set a different redirect_uri parameter + // depending on the context, leave it as-is + if (!isset($options['redirect_uri'])) { + $options['redirect_uri'] = $this->redirectUri; + } + + $options['client_id'] = $this->clientId; + + return $options; + } + + /** + * Builds the authorization URL's query string. + * + * @param array $params Query parameters + * @return string Query string + */ + protected function getAuthorizationQuery(array $params) + { + return $this->buildQueryString($params); + } + + /** + * Builds the authorization URL. + * + * @param array $options + * @return string Authorization URL + */ + public function getAuthorizationUrl(array $options = []) + { + $base = $this->getBaseAuthorizationUrl(); + $params = $this->getAuthorizationParameters($options); + $query = $this->getAuthorizationQuery($params); + + return $this->appendQuery($base, $query); + } + + /** + * Redirects the client for authorization. + * + * @param array $options + * @param callable|null $redirectHandler + * @return mixed + */ + public function authorize( + array $options = [], + callable $redirectHandler = null + ) { + $url = $this->getAuthorizationUrl($options); + if ($redirectHandler) { + return $redirectHandler($url, $this); + } + + // @codeCoverageIgnoreStart + header('Location: ' . $url); + exit; + // @codeCoverageIgnoreEnd + } + + /** + * Appends a query string to a URL. + * + * @param string $url The URL to append the query to + * @param string $query The HTTP query string + * @return string The resulting URL + */ + protected function appendQuery($url, $query) + { + $query = trim($query, '?&'); + + if ($query) { + $glue = strstr($url, '?') === false ? '?' : '&'; + return $url . $glue . $query; + } + + return $url; + } + + /** + * Returns the method to use when requesting an access token. + * + * @return string HTTP method + */ + protected function getAccessTokenMethod() + { + return self::METHOD_POST; + } + + /** + * Returns the key used in the access token response to identify the resource owner. + * + * @return string|null Resource owner identifier key + */ + protected function getAccessTokenResourceOwnerId() + { + return static::ACCESS_TOKEN_RESOURCE_OWNER_ID; + } + + /** + * Builds the access token URL's query string. + * + * @param array $params Query parameters + * @return string Query string + */ + protected function getAccessTokenQuery(array $params) + { + return $this->buildQueryString($params); + } + + /** + * Checks that a provided grant is valid, or attempts to produce one if the + * provided grant is a string. + * + * @param AbstractGrant|string $grant + * @return AbstractGrant + */ + protected function verifyGrant($grant) + { + if (is_string($grant)) { + return $this->grantFactory->getGrant($grant); + } + + $this->grantFactory->checkGrant($grant); + return $grant; + } + + /** + * Returns the full URL to use when requesting an access token. + * + * @param array $params Query parameters + * @return string + */ + protected function getAccessTokenUrl(array $params) + { + $url = $this->getBaseAccessTokenUrl($params); + + if ($this->getAccessTokenMethod() === self::METHOD_GET) { + $query = $this->getAccessTokenQuery($params); + return $this->appendQuery($url, $query); + } + + return $url; + } + + /** + * Returns a prepared request for requesting an access token. + * + * @param array $params Query string parameters + * @return RequestInterface + */ + protected function getAccessTokenRequest(array $params) + { + $method = $this->getAccessTokenMethod(); + $url = $this->getAccessTokenUrl($params); + $options = $this->optionProvider->getAccessTokenOptions($this->getAccessTokenMethod(), $params); + + return $this->getRequest($method, $url, $options); + } + + /** + * Requests an access token using a specified grant and option set. + * + * @param mixed $grant + * @param array $options + * @throws IdentityProviderException + * @return AccessTokenInterface + */ + public function getAccessToken($grant, array $options = []) + { + $grant = $this->verifyGrant($grant); + + $params = [ + 'client_id' => $this->clientId, + 'client_secret' => $this->clientSecret, + 'redirect_uri' => $this->redirectUri, + ]; + + if (!empty($this->pkceCode)) { + $params['code_verifier'] = $this->pkceCode; + } + + $params = $grant->prepareRequestParameters($params, $options); + $request = $this->getAccessTokenRequest($params); + $response = $this->getParsedResponse($request); + if (false === is_array($response)) { + throw new UnexpectedValueException( + 'Invalid response received from Authorization Server. Expected JSON.' + ); + } + $prepared = $this->prepareAccessTokenResponse($response); + $token = $this->createAccessToken($prepared, $grant); + + return $token; + } + + /** + * Returns a PSR-7 request instance that is not authenticated. + * + * @param string $method + * @param string $url + * @param array $options + * @return RequestInterface + */ + public function getRequest($method, $url, array $options = []) + { + return $this->createRequest($method, $url, null, $options); + } + + /** + * Returns an authenticated PSR-7 request instance. + * + * @param string $method + * @param string $url + * @param AccessTokenInterface|string|null $token + * @param array $options Any of "headers", "body", and "protocolVersion". + * @return RequestInterface + */ + public function getAuthenticatedRequest($method, $url, $token, array $options = []) + { + return $this->createRequest($method, $url, $token, $options); + } + + /** + * Creates a PSR-7 request instance. + * + * @param string $method + * @param string $url + * @param AccessTokenInterface|string|null $token + * @param array $options + * @return RequestInterface + */ + protected function createRequest($method, $url, $token, array $options) + { + $defaults = [ + 'headers' => $this->getHeaders($token), + ]; + + $options = array_merge_recursive($defaults, $options); + $factory = $this->getRequestFactory(); + + return $factory->getRequestWithOptions($method, $url, $options); + } + + /** + * Sends a request instance and returns a response instance. + * + * WARNING: This method does not attempt to catch exceptions caused by HTTP + * errors! It is recommended to wrap this method in a try/catch block. + * + * @param RequestInterface $request + * @return ResponseInterface + */ + public function getResponse(RequestInterface $request) + { + return $this->getHttpClient()->send($request); + } + + /** + * Sends a request and returns the parsed response. + * + * @param RequestInterface $request + * @throws IdentityProviderException + * @return mixed + */ + public function getParsedResponse(RequestInterface $request) + { + try { + $response = $this->getResponse($request); + } catch (BadResponseException $e) { + $response = $e->getResponse(); + } + + $parsed = $this->parseResponse($response); + + $this->checkResponse($response, $parsed); + + return $parsed; + } + + /** + * Attempts to parse a JSON response. + * + * @param string $content JSON content from response body + * @return array Parsed JSON data + * @throws UnexpectedValueException if the content could not be parsed + */ + protected function parseJson($content) + { + $content = json_decode($content, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new UnexpectedValueException(sprintf( + "Failed to parse JSON response: %s", + json_last_error_msg() + )); + } + + return $content; + } + + /** + * Returns the content type header of a response. + * + * @param ResponseInterface $response + * @return string Semi-colon separated join of content-type headers. + */ + protected function getContentType(ResponseInterface $response) + { + return join(';', (array) $response->getHeader('content-type')); + } + + /** + * Parses the response according to its content-type header. + * + * @throws UnexpectedValueException + * @param ResponseInterface $response + * @return array + */ + protected function parseResponse(ResponseInterface $response) + { + $content = (string) $response->getBody(); + $type = $this->getContentType($response); + + if (strpos($type, 'urlencoded') !== false) { + parse_str($content, $parsed); + return $parsed; + } + + // Attempt to parse the string as JSON regardless of content type, + // since some providers use non-standard content types. Only throw an + // exception if the JSON could not be parsed when it was expected to. + try { + return $this->parseJson($content); + } catch (UnexpectedValueException $e) { + if (strpos($type, 'json') !== false) { + throw $e; + } + + if ($response->getStatusCode() == 500) { + throw new UnexpectedValueException( + 'An OAuth server error was encountered that did not contain a JSON body', + 0, + $e + ); + } + + return $content; + } + } + + /** + * Checks a provider response for errors. + * + * @throws IdentityProviderException + * @param ResponseInterface $response + * @param array|string $data Parsed response data + * @return void + */ + abstract protected function checkResponse(ResponseInterface $response, $data); + + /** + * Prepares an parsed access token response for a grant. + * + * Custom mapping of expiration, etc should be done here. Always call the + * parent method when overloading this method. + * + * @param mixed $result + * @return array + */ + protected function prepareAccessTokenResponse(array $result) + { + if ($this->getAccessTokenResourceOwnerId() !== null) { + $result['resource_owner_id'] = $this->getValueByKey( + $result, + $this->getAccessTokenResourceOwnerId() + ); + } + return $result; + } + + /** + * Creates an access token from a response. + * + * The grant that was used to fetch the response can be used to provide + * additional context. + * + * @param array $response + * @param AbstractGrant $grant + * @return AccessTokenInterface + */ + protected function createAccessToken(array $response, AbstractGrant $grant) + { + return new AccessToken($response); + } + + /** + * Generates a resource owner object from a successful resource owner + * details request. + * + * @param array $response + * @param AccessToken $token + * @return ResourceOwnerInterface + */ + abstract protected function createResourceOwner(array $response, AccessToken $token); + + /** + * Requests and returns the resource owner of given access token. + * + * @param AccessToken $token + * @return ResourceOwnerInterface + */ + public function getResourceOwner(AccessToken $token) + { + $response = $this->fetchResourceOwnerDetails($token); + + return $this->createResourceOwner($response, $token); + } + + /** + * Requests resource owner details. + * + * @param AccessToken $token + * @return mixed + */ + protected function fetchResourceOwnerDetails(AccessToken $token) + { + $url = $this->getResourceOwnerDetailsUrl($token); + + $request = $this->getAuthenticatedRequest(self::METHOD_GET, $url, $token); + + $response = $this->getParsedResponse($request); + + if (false === is_array($response)) { + throw new UnexpectedValueException( + 'Invalid response received from Authorization Server. Expected JSON.' + ); + } + + return $response; + } + + /** + * Returns the default headers used by this provider. + * + * Typically this is used to set 'Accept' or 'Content-Type' headers. + * + * @return array + */ + protected function getDefaultHeaders() + { + return []; + } + + /** + * Returns the authorization headers used by this provider. + * + * Typically this is "Bearer" or "MAC". For more information see: + * http://tools.ietf.org/html/rfc6749#section-7.1 + * + * No default is provided, providers must overload this method to activate + * authorization headers. + * + * @param mixed|null $token Either a string or an access token instance + * @return array + */ + protected function getAuthorizationHeaders($token = null) + { + return []; + } + + /** + * Returns all headers used by this provider for a request. + * + * The request will be authenticated if an access token is provided. + * + * @param mixed|null $token object or string + * @return array + */ + public function getHeaders($token = null) + { + if ($token) { + return array_merge( + $this->getDefaultHeaders(), + $this->getAuthorizationHeaders($token) + ); + } + + return $this->getDefaultHeaders(); + } +} diff --git a/vendor/league/oauth2-client/src/Provider/Exception/IdentityProviderException.php b/vendor/league/oauth2-client/src/Provider/Exception/IdentityProviderException.php new file mode 100644 index 00000000..55cb438f --- /dev/null +++ b/vendor/league/oauth2-client/src/Provider/Exception/IdentityProviderException.php @@ -0,0 +1,48 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Provider\Exception; + +/** + * Exception thrown if the provider response contains errors. + */ +class IdentityProviderException extends \Exception +{ + /** + * @var mixed + */ + protected $response; + + /** + * @param string $message + * @param int $code + * @param mixed $response The response body + */ + public function __construct($message, $code, $response) + { + $this->response = $response; + + parent::__construct($message, $code); + } + + /** + * Returns the exception's response body. + * + * @return mixed + */ + public function getResponseBody() + { + return $this->response; + } +} diff --git a/vendor/league/oauth2-client/src/Provider/GenericProvider.php b/vendor/league/oauth2-client/src/Provider/GenericProvider.php new file mode 100644 index 00000000..0fc95f25 --- /dev/null +++ b/vendor/league/oauth2-client/src/Provider/GenericProvider.php @@ -0,0 +1,247 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Provider; + +use InvalidArgumentException; +use League\OAuth2\Client\Provider\Exception\IdentityProviderException; +use League\OAuth2\Client\Token\AccessToken; +use League\OAuth2\Client\Tool\BearerAuthorizationTrait; +use Psr\Http\Message\ResponseInterface; + +/** + * Represents a generic service provider that may be used to interact with any + * OAuth 2.0 service provider, using Bearer token authentication. + */ +class GenericProvider extends AbstractProvider +{ + use BearerAuthorizationTrait; + + /** + * @var string + */ + private $urlAuthorize; + + /** + * @var string + */ + private $urlAccessToken; + + /** + * @var string + */ + private $urlResourceOwnerDetails; + + /** + * @var string + */ + private $accessTokenMethod; + + /** + * @var string + */ + private $accessTokenResourceOwnerId; + + /** + * @var array|null + */ + private $scopes = null; + + /** + * @var string + */ + private $scopeSeparator; + + /** + * @var string + */ + private $responseError = 'error'; + + /** + * @var string + */ + private $responseCode; + + /** + * @var string + */ + private $responseResourceOwnerId = 'id'; + + /** + * @var string|null + */ + private $pkceMethod = null; + + /** + * @param array $options + * @param array $collaborators + */ + public function __construct(array $options = [], array $collaborators = []) + { + $this->assertRequiredOptions($options); + + $possible = $this->getConfigurableOptions(); + $configured = array_intersect_key($options, array_flip($possible)); + + foreach ($configured as $key => $value) { + $this->$key = $value; + } + + // Remove all options that are only used locally + $options = array_diff_key($options, $configured); + + parent::__construct($options, $collaborators); + } + + /** + * Returns all options that can be configured. + * + * @return array + */ + protected function getConfigurableOptions() + { + return array_merge($this->getRequiredOptions(), [ + 'accessTokenMethod', + 'accessTokenResourceOwnerId', + 'scopeSeparator', + 'responseError', + 'responseCode', + 'responseResourceOwnerId', + 'scopes', + 'pkceMethod', + ]); + } + + /** + * Returns all options that are required. + * + * @return array + */ + protected function getRequiredOptions() + { + return [ + 'urlAuthorize', + 'urlAccessToken', + 'urlResourceOwnerDetails', + ]; + } + + /** + * Verifies that all required options have been passed. + * + * @param array $options + * @return void + * @throws InvalidArgumentException + */ + private function assertRequiredOptions(array $options) + { + $missing = array_diff_key(array_flip($this->getRequiredOptions()), $options); + + if (!empty($missing)) { + throw new InvalidArgumentException( + 'Required options not defined: ' . implode(', ', array_keys($missing)) + ); + } + } + + /** + * @inheritdoc + */ + public function getBaseAuthorizationUrl() + { + return $this->urlAuthorize; + } + + /** + * @inheritdoc + */ + public function getBaseAccessTokenUrl(array $params) + { + return $this->urlAccessToken; + } + + /** + * @inheritdoc + */ + public function getResourceOwnerDetailsUrl(AccessToken $token) + { + return $this->urlResourceOwnerDetails; + } + + /** + * @inheritdoc + */ + public function getDefaultScopes() + { + return $this->scopes; + } + + /** + * @inheritdoc + */ + protected function getAccessTokenMethod() + { + return $this->accessTokenMethod ?: parent::getAccessTokenMethod(); + } + + /** + * @inheritdoc + */ + protected function getAccessTokenResourceOwnerId() + { + return $this->accessTokenResourceOwnerId ?: parent::getAccessTokenResourceOwnerId(); + } + + /** + * @inheritdoc + */ + protected function getScopeSeparator() + { + return $this->scopeSeparator ?: parent::getScopeSeparator(); + } + + /** + * @inheritdoc + */ + protected function getPkceMethod() + { + return $this->pkceMethod ?: parent::getPkceMethod(); + } + + /** + * @inheritdoc + */ + protected function checkResponse(ResponseInterface $response, $data) + { + if (!empty($data[$this->responseError])) { + $error = $data[$this->responseError]; + if (!is_string($error)) { + $error = var_export($error, true); + } + $code = $this->responseCode && !empty($data[$this->responseCode])? $data[$this->responseCode] : 0; + if (!is_int($code)) { + $code = intval($code); + } + throw new IdentityProviderException($error, $code, $data); + } + } + + /** + * @inheritdoc + */ + protected function createResourceOwner(array $response, AccessToken $token) + { + return new GenericResourceOwner($response, $this->responseResourceOwnerId); + } +} diff --git a/vendor/league/oauth2-client/src/Provider/GenericResourceOwner.php b/vendor/league/oauth2-client/src/Provider/GenericResourceOwner.php new file mode 100644 index 00000000..f8766148 --- /dev/null +++ b/vendor/league/oauth2-client/src/Provider/GenericResourceOwner.php @@ -0,0 +1,61 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Provider; + +/** + * Represents a generic resource owner for use with the GenericProvider. + */ +class GenericResourceOwner implements ResourceOwnerInterface +{ + /** + * @var array + */ + protected $response; + + /** + * @var string + */ + protected $resourceOwnerId; + + /** + * @param array $response + * @param string $resourceOwnerId + */ + public function __construct(array $response, $resourceOwnerId) + { + $this->response = $response; + $this->resourceOwnerId = $resourceOwnerId; + } + + /** + * Returns the identifier of the authorized resource owner. + * + * @return mixed + */ + public function getId() + { + return $this->response[$this->resourceOwnerId]; + } + + /** + * Returns the raw resource owner response. + * + * @return array + */ + public function toArray() + { + return $this->response; + } +} diff --git a/vendor/league/oauth2-client/src/Provider/ResourceOwnerInterface.php b/vendor/league/oauth2-client/src/Provider/ResourceOwnerInterface.php new file mode 100644 index 00000000..82844242 --- /dev/null +++ b/vendor/league/oauth2-client/src/Provider/ResourceOwnerInterface.php @@ -0,0 +1,36 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Provider; + +/** + * Classes implementing `ResourceOwnerInterface` may be used to represent + * the resource owner authenticated with a service provider. + */ +interface ResourceOwnerInterface +{ + /** + * Returns the identifier of the authorized resource owner. + * + * @return mixed + */ + public function getId(); + + /** + * Return all of the owner details available as an array. + * + * @return array + */ + public function toArray(); +} diff --git a/vendor/league/oauth2-client/src/Token/AccessToken.php b/vendor/league/oauth2-client/src/Token/AccessToken.php new file mode 100644 index 00000000..81533c30 --- /dev/null +++ b/vendor/league/oauth2-client/src/Token/AccessToken.php @@ -0,0 +1,243 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Token; + +use InvalidArgumentException; +use RuntimeException; + +/** + * Represents an access token. + * + * @link http://tools.ietf.org/html/rfc6749#section-1.4 Access Token (RFC 6749, §1.4) + */ +class AccessToken implements AccessTokenInterface, ResourceOwnerAccessTokenInterface +{ + /** + * @var string + */ + protected $accessToken; + + /** + * @var int + */ + protected $expires; + + /** + * @var string + */ + protected $refreshToken; + + /** + * @var string + */ + protected $resourceOwnerId; + + /** + * @var array + */ + protected $values = []; + + /** + * @var int + */ + private static $timeNow; + + /** + * Set the time now. This should only be used for testing purposes. + * + * @param int $timeNow the time in seconds since epoch + * @return void + */ + public static function setTimeNow($timeNow) + { + self::$timeNow = $timeNow; + } + + /** + * Reset the time now if it was set for test purposes. + * + * @return void + */ + public static function resetTimeNow() + { + self::$timeNow = null; + } + + /** + * @return int + */ + public function getTimeNow() + { + return self::$timeNow ? self::$timeNow : time(); + } + + /** + * Constructs an access token. + * + * @param array $options An array of options returned by the service provider + * in the access token request. The `access_token` option is required. + * @throws InvalidArgumentException if `access_token` is not provided in `$options`. + */ + public function __construct(array $options = []) + { + if (empty($options['access_token'])) { + throw new InvalidArgumentException('Required option not passed: "access_token"'); + } + + $this->accessToken = $options['access_token']; + + if (!empty($options['resource_owner_id'])) { + $this->resourceOwnerId = $options['resource_owner_id']; + } + + if (!empty($options['refresh_token'])) { + $this->refreshToken = $options['refresh_token']; + } + + // We need to know when the token expires. Show preference to + // 'expires_in' since it is defined in RFC6749 Section 5.1. + // Defer to 'expires' if it is provided instead. + if (isset($options['expires_in'])) { + if (!is_numeric($options['expires_in'])) { + throw new \InvalidArgumentException('expires_in value must be an integer'); + } + + $this->expires = $options['expires_in'] != 0 ? $this->getTimeNow() + $options['expires_in'] : 0; + } elseif (!empty($options['expires'])) { + // Some providers supply the seconds until expiration rather than + // the exact timestamp. Take a best guess at which we received. + $expires = $options['expires']; + + if (!$this->isExpirationTimestamp($expires)) { + $expires += $this->getTimeNow(); + } + + $this->expires = $expires; + } + + // Capture any additional values that might exist in the token but are + // not part of the standard response. Vendors will sometimes pass + // additional user data this way. + $this->values = array_diff_key($options, array_flip([ + 'access_token', + 'resource_owner_id', + 'refresh_token', + 'expires_in', + 'expires', + ])); + } + + /** + * Check if a value is an expiration timestamp or second value. + * + * @param integer $value + * @return bool + */ + protected function isExpirationTimestamp($value) + { + // If the given value is larger than the original OAuth 2 draft date, + // assume that it is meant to be a (possible expired) timestamp. + $oauth2InceptionDate = 1349067600; // 2012-10-01 + return ($value > $oauth2InceptionDate); + } + + /** + * @inheritdoc + */ + public function getToken() + { + return $this->accessToken; + } + + /** + * @inheritdoc + */ + public function getRefreshToken() + { + return $this->refreshToken; + } + + /** + * @inheritdoc + */ + public function getExpires() + { + return $this->expires; + } + + /** + * @inheritdoc + */ + public function getResourceOwnerId() + { + return $this->resourceOwnerId; + } + + /** + * @inheritdoc + */ + public function hasExpired() + { + $expires = $this->getExpires(); + + if (empty($expires)) { + throw new RuntimeException('"expires" is not set on the token'); + } + + return $expires < time(); + } + + /** + * @inheritdoc + */ + public function getValues() + { + return $this->values; + } + + /** + * @inheritdoc + */ + public function __toString() + { + return (string) $this->getToken(); + } + + /** + * @inheritdoc + */ + public function jsonSerialize() + { + $parameters = $this->values; + + if ($this->accessToken) { + $parameters['access_token'] = $this->accessToken; + } + + if ($this->refreshToken) { + $parameters['refresh_token'] = $this->refreshToken; + } + + if ($this->expires) { + $parameters['expires'] = $this->expires; + } + + if ($this->resourceOwnerId) { + $parameters['resource_owner_id'] = $this->resourceOwnerId; + } + + return $parameters; + } +} diff --git a/vendor/league/oauth2-client/src/Token/AccessTokenInterface.php b/vendor/league/oauth2-client/src/Token/AccessTokenInterface.php new file mode 100644 index 00000000..5fd219ff --- /dev/null +++ b/vendor/league/oauth2-client/src/Token/AccessTokenInterface.php @@ -0,0 +1,74 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Token; + +use JsonSerializable; +use ReturnTypeWillChange; +use RuntimeException; + +interface AccessTokenInterface extends JsonSerializable +{ + /** + * Returns the access token string of this instance. + * + * @return string + */ + public function getToken(); + + /** + * Returns the refresh token, if defined. + * + * @return string|null + */ + public function getRefreshToken(); + + /** + * Returns the expiration timestamp in seconds, if defined. + * + * @return integer|null + */ + public function getExpires(); + + /** + * Checks if this token has expired. + * + * @return boolean true if the token has expired, false otherwise. + * @throws RuntimeException if 'expires' is not set on the token. + */ + public function hasExpired(); + + /** + * Returns additional vendor values stored in the token. + * + * @return array + */ + public function getValues(); + + /** + * Returns a string representation of the access token + * + * @return string + */ + public function __toString(); + + /** + * Returns an array of parameters to serialize when this is serialized with + * json_encode(). + * + * @return array + */ + #[ReturnTypeWillChange] + public function jsonSerialize(); +} diff --git a/vendor/league/oauth2-client/src/Token/ResourceOwnerAccessTokenInterface.php b/vendor/league/oauth2-client/src/Token/ResourceOwnerAccessTokenInterface.php new file mode 100644 index 00000000..51e4ce41 --- /dev/null +++ b/vendor/league/oauth2-client/src/Token/ResourceOwnerAccessTokenInterface.php @@ -0,0 +1,25 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Token; + +interface ResourceOwnerAccessTokenInterface extends AccessTokenInterface +{ + /** + * Returns the resource owner identifier, if defined. + * + * @return string|null + */ + public function getResourceOwnerId(); +} diff --git a/vendor/league/oauth2-client/src/Tool/ArrayAccessorTrait.php b/vendor/league/oauth2-client/src/Tool/ArrayAccessorTrait.php new file mode 100644 index 00000000..a18198cf --- /dev/null +++ b/vendor/league/oauth2-client/src/Tool/ArrayAccessorTrait.php @@ -0,0 +1,52 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Tool; + +/** + * Provides generic array navigation tools. + */ +trait ArrayAccessorTrait +{ + /** + * Returns a value by key using dot notation. + * + * @param array $data + * @param string $key + * @param mixed|null $default + * @return mixed + */ + private function getValueByKey(array $data, $key, $default = null) + { + if (!is_string($key) || empty($key) || !count($data)) { + return $default; + } + + if (strpos($key, '.') !== false) { + $keys = explode('.', $key); + + foreach ($keys as $innerKey) { + if (!is_array($data) || !array_key_exists($innerKey, $data)) { + return $default; + } + + $data = $data[$innerKey]; + } + + return $data; + } + + return array_key_exists($key, $data) ? $data[$key] : $default; + } +} diff --git a/vendor/league/oauth2-client/src/Tool/BearerAuthorizationTrait.php b/vendor/league/oauth2-client/src/Tool/BearerAuthorizationTrait.php new file mode 100644 index 00000000..081c7c86 --- /dev/null +++ b/vendor/league/oauth2-client/src/Tool/BearerAuthorizationTrait.php @@ -0,0 +1,36 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Tool; + +use League\OAuth2\Client\Token\AccessTokenInterface; + +/** + * Enables `Bearer` header authorization for providers. + * + * @link http://tools.ietf.org/html/rfc6750 Bearer Token Usage (RFC 6750) + */ +trait BearerAuthorizationTrait +{ + /** + * Returns authorization headers for the 'bearer' grant. + * + * @param AccessTokenInterface|string|null $token Either a string or an access token instance + * @return array + */ + protected function getAuthorizationHeaders($token = null) + { + return ['Authorization' => 'Bearer ' . $token]; + } +} diff --git a/vendor/league/oauth2-client/src/Tool/GuardedPropertyTrait.php b/vendor/league/oauth2-client/src/Tool/GuardedPropertyTrait.php new file mode 100644 index 00000000..02c9ba5f --- /dev/null +++ b/vendor/league/oauth2-client/src/Tool/GuardedPropertyTrait.php @@ -0,0 +1,70 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Tool; + +/** + * Provides support for blacklisting explicit properties from the + * mass assignment behavior. + */ +trait GuardedPropertyTrait +{ + /** + * The properties that aren't mass assignable. + * + * @var array + */ + protected $guarded = []; + + /** + * Attempts to mass assign the given options to explicitly defined properties, + * skipping over any properties that are defined in the guarded array. + * + * @param array $options + * @return mixed + */ + protected function fillProperties(array $options = []) + { + if (isset($options['guarded'])) { + unset($options['guarded']); + } + + foreach ($options as $option => $value) { + if (property_exists($this, $option) && !$this->isGuarded($option)) { + $this->{$option} = $value; + } + } + } + + /** + * Returns current guarded properties. + * + * @return array + */ + public function getGuarded() + { + return $this->guarded; + } + + /** + * Determines if the given property is guarded. + * + * @param string $property + * @return bool + */ + public function isGuarded($property) + { + return in_array($property, $this->getGuarded()); + } +} diff --git a/vendor/league/oauth2-client/src/Tool/MacAuthorizationTrait.php b/vendor/league/oauth2-client/src/Tool/MacAuthorizationTrait.php new file mode 100644 index 00000000..f8dcd77c --- /dev/null +++ b/vendor/league/oauth2-client/src/Tool/MacAuthorizationTrait.php @@ -0,0 +1,83 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Tool; + +use League\OAuth2\Client\Token\AccessToken; +use League\OAuth2\Client\Token\AccessTokenInterface; + +/** + * Enables `MAC` header authorization for providers. + * + * @link http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-05 Message Authentication Code (MAC) Tokens + */ +trait MacAuthorizationTrait +{ + /** + * Returns the id of this token for MAC generation. + * + * @param AccessToken $token + * @return string + */ + abstract protected function getTokenId(AccessToken $token); + + /** + * Returns the MAC signature for the current request. + * + * @param string $id + * @param integer $ts + * @param string $nonce + * @return string + */ + abstract protected function getMacSignature($id, $ts, $nonce); + + /** + * Returns a new random string to use as the state parameter in an + * authorization flow. + * + * @param int $length Length of the random string to be generated. + * @return string + */ + abstract protected function getRandomState($length = 32); + + /** + * Returns the authorization headers for the 'mac' grant. + * + * @param AccessTokenInterface|string|null $token Either a string or an access token instance + * @return array + * @codeCoverageIgnore + * + * @todo This is currently untested and provided only as an example. If you + * complete the implementation, please create a pull request for + * https://github.com/thephpleague/oauth2-client + */ + protected function getAuthorizationHeaders($token = null) + { + if ($token === null) { + return []; + } + + $ts = time(); + $id = $this->getTokenId($token); + $nonce = $this->getRandomState(16); + $mac = $this->getMacSignature($id, $ts, $nonce); + + $parts = []; + foreach (compact('id', 'ts', 'nonce', 'mac') as $key => $value) { + $parts[] = sprintf('%s="%s"', $key, $value); + } + + return ['Authorization' => 'MAC ' . implode(', ', $parts)]; + } +} diff --git a/vendor/league/oauth2-client/src/Tool/ProviderRedirectTrait.php b/vendor/league/oauth2-client/src/Tool/ProviderRedirectTrait.php new file mode 100644 index 00000000..f81b511f --- /dev/null +++ b/vendor/league/oauth2-client/src/Tool/ProviderRedirectTrait.php @@ -0,0 +1,122 @@ +redirectLimit) { + $attempts++; + $response = $this->getHttpClient()->send($request, [ + 'allow_redirects' => false + ]); + + if ($this->isRedirect($response)) { + $redirectUrl = new Uri($response->getHeader('Location')[0]); + $request = $request->withUri($redirectUrl); + } else { + break; + } + } + + return $response; + } + + /** + * Returns the HTTP client instance. + * + * @return GuzzleHttp\ClientInterface + */ + abstract public function getHttpClient(); + + /** + * Retrieves current redirect limit. + * + * @return integer + */ + public function getRedirectLimit() + { + return $this->redirectLimit; + } + + /** + * Determines if a given response is a redirect. + * + * @param ResponseInterface $response + * + * @return boolean + */ + protected function isRedirect(ResponseInterface $response) + { + $statusCode = $response->getStatusCode(); + + return $statusCode > 300 && $statusCode < 400 && $response->hasHeader('Location'); + } + + /** + * Sends a request instance and returns a response instance. + * + * WARNING: This method does not attempt to catch exceptions caused by HTTP + * errors! It is recommended to wrap this method in a try/catch block. + * + * @param RequestInterface $request + * @return ResponseInterface + */ + public function getResponse(RequestInterface $request) + { + try { + $response = $this->followRequestRedirects($request); + } catch (BadResponseException $e) { + $response = $e->getResponse(); + } + + return $response; + } + + /** + * Updates the redirect limit. + * + * @param integer $limit + * @return League\OAuth2\Client\Provider\AbstractProvider + * @throws InvalidArgumentException + */ + public function setRedirectLimit($limit) + { + if (!is_int($limit)) { + throw new InvalidArgumentException('redirectLimit must be an integer.'); + } + + if ($limit < 1) { + throw new InvalidArgumentException('redirectLimit must be greater than or equal to one.'); + } + + $this->redirectLimit = $limit; + + return $this; + } +} diff --git a/vendor/league/oauth2-client/src/Tool/QueryBuilderTrait.php b/vendor/league/oauth2-client/src/Tool/QueryBuilderTrait.php new file mode 100644 index 00000000..bdda3e79 --- /dev/null +++ b/vendor/league/oauth2-client/src/Tool/QueryBuilderTrait.php @@ -0,0 +1,33 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Tool; + +/** + * Provides a standard way to generate query strings. + */ +trait QueryBuilderTrait +{ + /** + * Build a query string from an array. + * + * @param array $params + * + * @return string + */ + protected function buildQueryString(array $params) + { + return http_build_query($params, '', '&', \PHP_QUERY_RFC3986); + } +} diff --git a/vendor/league/oauth2-client/src/Tool/RequestFactory.php b/vendor/league/oauth2-client/src/Tool/RequestFactory.php new file mode 100644 index 00000000..1af43429 --- /dev/null +++ b/vendor/league/oauth2-client/src/Tool/RequestFactory.php @@ -0,0 +1,87 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Tool; + +use GuzzleHttp\Psr7\Request; + +/** + * Used to produce PSR-7 Request instances. + * + * @link https://github.com/guzzle/guzzle/pull/1101 + */ +class RequestFactory +{ + /** + * Creates a PSR-7 Request instance. + * + * @param null|string $method HTTP method for the request. + * @param null|string $uri URI for the request. + * @param array $headers Headers for the message. + * @param string|resource|StreamInterface $body Message body. + * @param string $version HTTP protocol version. + * + * @return Request + */ + public function getRequest( + $method, + $uri, + array $headers = [], + $body = null, + $version = '1.1' + ) { + return new Request($method, $uri, $headers, $body, $version); + } + + /** + * Parses simplified options. + * + * @param array $options Simplified options. + * + * @return array Extended options for use with getRequest. + */ + protected function parseOptions(array $options) + { + // Should match default values for getRequest + $defaults = [ + 'headers' => [], + 'body' => null, + 'version' => '1.1', + ]; + + return array_merge($defaults, $options); + } + + /** + * Creates a request using a simplified array of options. + * + * @param null|string $method + * @param null|string $uri + * @param array $options + * + * @return Request + */ + public function getRequestWithOptions($method, $uri, array $options = []) + { + $options = $this->parseOptions($options); + + return $this->getRequest( + $method, + $uri, + $options['headers'], + $options['body'], + $options['version'] + ); + } +} diff --git a/vendor/league/oauth2-client/src/Tool/RequiredParameterTrait.php b/vendor/league/oauth2-client/src/Tool/RequiredParameterTrait.php new file mode 100644 index 00000000..47da9771 --- /dev/null +++ b/vendor/league/oauth2-client/src/Tool/RequiredParameterTrait.php @@ -0,0 +1,56 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Tool; + +use BadMethodCallException; + +/** + * Provides functionality to check for required parameters. + */ +trait RequiredParameterTrait +{ + /** + * Checks for a required parameter in a hash. + * + * @throws BadMethodCallException + * @param string $name + * @param array $params + * @return void + */ + private function checkRequiredParameter($name, array $params) + { + if (!isset($params[$name])) { + throw new BadMethodCallException(sprintf( + 'Required parameter not passed: "%s"', + $name + )); + } + } + + /** + * Checks for multiple required parameters in a hash. + * + * @throws InvalidArgumentException + * @param array $names + * @param array $params + * @return void + */ + private function checkRequiredParameters(array $names, array $params) + { + foreach ($names as $name) { + $this->checkRequiredParameter($name, $params); + } + } +} diff --git a/vendor/myclabs/deep-copy/.github/FUNDING.yml b/vendor/myclabs/deep-copy/.github/FUNDING.yml new file mode 100644 index 00000000..b8da664d --- /dev/null +++ b/vendor/myclabs/deep-copy/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: "packagist/myclabs/deep-copy" +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/vendor/myclabs/deep-copy/.github/workflows/ci.yaml b/vendor/myclabs/deep-copy/.github/workflows/ci.yaml new file mode 100644 index 00000000..eac2812e --- /dev/null +++ b/vendor/myclabs/deep-copy/.github/workflows/ci.yaml @@ -0,0 +1,101 @@ +name: "Continuous Integration" + +on: + - pull_request + - push + +env: + COMPOSER_ROOT_VERSION: 1.99 + +jobs: + composer-json-lint: + name: "Lint composer.json" + + runs-on: "ubuntu-latest" + + strategy: + matrix: + php-version: + - "8.1" + + steps: + - name: "Checkout" + uses: "actions/checkout@v2" + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + coverage: "none" + php-version: "${{ matrix.php-version }}" + tools: composer-normalize + + - name: "Get composer cache directory" + id: composercache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: "Cache dependencies" + uses: actions/cache@v2 + with: + path: ${{ steps.composercache.outputs.dir }} + key: ${{ runner.os }}-php-${{ matrix.php-version }}-${{ matrix.dependencies }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-php-${{ matrix.php-version }}-${{ matrix.dependencies }}-composer- + + - name: "Install dependencies" + run: "composer update --no-interaction --no-progress" + + - name: "Validate composer.json" + run: "composer validate --strict" + + - name: "Normalize composer.json" + run: "composer-normalize --dry-run" + + tests: + name: "Tests" + + runs-on: "ubuntu-latest" + + strategy: + matrix: + php-version: + - "7.1" + - "7.2" + - "7.3" + - "7.4" + - "8.0" + - "8.1" + dependencies: + - "lowest" + - "highest" + + steps: + - name: "Checkout" + uses: "actions/checkout@v2" + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + php-version: "${{ matrix.php-version }}" + ini-values: zend.assertions=1 + + - name: "Get composer cache directory" + id: composercache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: "Cache dependencies" + uses: actions/cache@v2 + with: + path: ${{ steps.composercache.outputs.dir }} + key: ${{ runner.os }}-php-${{ matrix.php-version }}-${{ matrix.dependencies }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-php-${{ matrix.php-version }}-${{ matrix.dependencies }}-composer- + + - name: "Install lowest dependencies" + if: ${{ matrix.dependencies == 'lowest' }} + run: "composer update --no-interaction --no-progress --prefer-lowest" + + - name: "Install highest dependencies" + if: ${{ matrix.dependencies == 'highest' }} + run: "composer update --no-interaction --no-progress" + + - name: "Run tests" + timeout-minutes: 3 + run: "vendor/bin/phpunit" diff --git a/vendor/myclabs/deep-copy/LICENSE b/vendor/myclabs/deep-copy/LICENSE new file mode 100644 index 00000000..c3e83500 --- /dev/null +++ b/vendor/myclabs/deep-copy/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 My C-Sense + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/myclabs/deep-copy/README.md b/vendor/myclabs/deep-copy/README.md new file mode 100644 index 00000000..94aaa06d --- /dev/null +++ b/vendor/myclabs/deep-copy/README.md @@ -0,0 +1,406 @@ +# DeepCopy + +DeepCopy helps you create deep copies (clones) of your objects. It is designed to handle cycles in the association graph. + +[![Total Downloads](https://poser.pugx.org/myclabs/deep-copy/downloads.svg)](https://packagist.org/packages/myclabs/deep-copy) +[![Integrate](https://github.com/myclabs/DeepCopy/workflows/ci/badge.svg?branch=1.x)](https://github.com/myclabs/DeepCopy/actions) + +## Table of Contents + +1. [How](#how) +1. [Why](#why) + 1. [Using simply `clone`](#using-simply-clone) + 1. [Overriding `__clone()`](#overriding-__clone) + 1. [With `DeepCopy`](#with-deepcopy) +1. [How it works](#how-it-works) +1. [Going further](#going-further) + 1. [Matchers](#matchers) + 1. [Property name](#property-name) + 1. [Specific property](#specific-property) + 1. [Type](#type) + 1. [Filters](#filters) + 1. [`SetNullFilter`](#setnullfilter-filter) + 1. [`KeepFilter`](#keepfilter-filter) + 1. [`DoctrineCollectionFilter`](#doctrinecollectionfilter-filter) + 1. [`DoctrineEmptyCollectionFilter`](#doctrineemptycollectionfilter-filter) + 1. [`DoctrineProxyFilter`](#doctrineproxyfilter-filter) + 1. [`ReplaceFilter`](#replacefilter-type-filter) + 1. [`ShallowCopyFilter`](#shallowcopyfilter-type-filter) +1. [Edge cases](#edge-cases) +1. [Contributing](#contributing) + 1. [Tests](#tests) + + +## How? + +Install with Composer: + +``` +composer require myclabs/deep-copy +``` + +Use it: + +```php +use DeepCopy\DeepCopy; + +$copier = new DeepCopy(); +$myCopy = $copier->copy($myObject); +``` + + +## Why? + +- How do you create copies of your objects? + +```php +$myCopy = clone $myObject; +``` + +- How do you create **deep** copies of your objects (i.e. copying also all the objects referenced in the properties)? + +You use [`__clone()`](http://www.php.net/manual/en/language.oop5.cloning.php#object.clone) and implement the behavior +yourself. + +- But how do you handle **cycles** in the association graph? + +Now you're in for a big mess :( + +![association graph](doc/graph.png) + + +### Using simply `clone` + +![Using clone](doc/clone.png) + + +### Overriding `__clone()` + +![Overriding __clone](doc/deep-clone.png) + + +### With `DeepCopy` + +![With DeepCopy](doc/deep-copy.png) + + +## How it works + +DeepCopy recursively traverses all the object's properties and clones them. To avoid cloning the same object twice it +keeps a hash map of all instances and thus preserves the object graph. + +To use it: + +```php +use function DeepCopy\deep_copy; + +$copy = deep_copy($var); +``` + +Alternatively, you can create your own `DeepCopy` instance to configure it differently for example: + +```php +use DeepCopy\DeepCopy; + +$copier = new DeepCopy(true); + +$copy = $copier->copy($var); +``` + +You may want to roll your own deep copy function: + +```php +namespace Acme; + +use DeepCopy\DeepCopy; + +function deep_copy($var) +{ + static $copier = null; + + if (null === $copier) { + $copier = new DeepCopy(true); + } + + return $copier->copy($var); +} +``` + + +## Going further + +You can add filters to customize the copy process. + +The method to add a filter is `DeepCopy\DeepCopy::addFilter($filter, $matcher)`, +with `$filter` implementing `DeepCopy\Filter\Filter` +and `$matcher` implementing `DeepCopy\Matcher\Matcher`. + +We provide some generic filters and matchers. + + +### Matchers + + - `DeepCopy\Matcher` applies on a object attribute. + - `DeepCopy\TypeMatcher` applies on any element found in graph, including array elements. + + +#### Property name + +The `PropertyNameMatcher` will match a property by its name: + +```php +use DeepCopy\Matcher\PropertyNameMatcher; + +// Will apply a filter to any property of any objects named "id" +$matcher = new PropertyNameMatcher('id'); +``` + + +#### Specific property + +The `PropertyMatcher` will match a specific property of a specific class: + +```php +use DeepCopy\Matcher\PropertyMatcher; + +// Will apply a filter to the property "id" of any objects of the class "MyClass" +$matcher = new PropertyMatcher('MyClass', 'id'); +``` + + +#### Type + +The `TypeMatcher` will match any element by its type (instance of a class or any value that could be parameter of +[gettype()](http://php.net/manual/en/function.gettype.php) function): + +```php +use DeepCopy\TypeMatcher\TypeMatcher; + +// Will apply a filter to any object that is an instance of Doctrine\Common\Collections\Collection +$matcher = new TypeMatcher('Doctrine\Common\Collections\Collection'); +``` + + +### Filters + +- `DeepCopy\Filter` applies a transformation to the object attribute matched by `DeepCopy\Matcher` +- `DeepCopy\TypeFilter` applies a transformation to any element matched by `DeepCopy\TypeMatcher` + +By design, matching a filter will stop the chain of filters (i.e. the next ones will not be applied). +Using the ([`ChainableFilter`](#chainablefilter-filter)) won't stop the chain of filters. + + +#### `SetNullFilter` (filter) + +Let's say for example that you are copying a database record (or a Doctrine entity), so you want the copy not to have +any ID: + +```php +use DeepCopy\DeepCopy; +use DeepCopy\Filter\SetNullFilter; +use DeepCopy\Matcher\PropertyNameMatcher; + +$object = MyClass::load(123); +echo $object->id; // 123 + +$copier = new DeepCopy(); +$copier->addFilter(new SetNullFilter(), new PropertyNameMatcher('id')); + +$copy = $copier->copy($object); + +echo $copy->id; // null +``` + + +#### `KeepFilter` (filter) + +If you want a property to remain untouched (for example, an association to an object): + +```php +use DeepCopy\DeepCopy; +use DeepCopy\Filter\KeepFilter; +use DeepCopy\Matcher\PropertyMatcher; + +$copier = new DeepCopy(); +$copier->addFilter(new KeepFilter(), new PropertyMatcher('MyClass', 'category')); + +$copy = $copier->copy($object); +// $copy->category has not been touched +``` + + +#### `ChainableFilter` (filter) + +If you use cloning on proxy classes, you might want to apply two filters for: +1. loading the data +2. applying a transformation + +You can use the `ChainableFilter` as a decorator of the proxy loader filter, which won't stop the chain of filters (i.e. +the next ones may be applied). + + +```php +use DeepCopy\DeepCopy; +use DeepCopy\Filter\ChainableFilter; +use DeepCopy\Filter\Doctrine\DoctrineProxyFilter; +use DeepCopy\Filter\SetNullFilter; +use DeepCopy\Matcher\Doctrine\DoctrineProxyMatcher; +use DeepCopy\Matcher\PropertyNameMatcher; + +$copier = new DeepCopy(); +$copier->addFilter(new ChainableFilter(new DoctrineProxyFilter()), new DoctrineProxyMatcher()); +$copier->addFilter(new SetNullFilter(), new PropertyNameMatcher('id')); + +$copy = $copier->copy($object); + +echo $copy->id; // null +``` + + +#### `DoctrineCollectionFilter` (filter) + +If you use Doctrine and want to copy an entity, you will need to use the `DoctrineCollectionFilter`: + +```php +use DeepCopy\DeepCopy; +use DeepCopy\Filter\Doctrine\DoctrineCollectionFilter; +use DeepCopy\Matcher\PropertyTypeMatcher; + +$copier = new DeepCopy(); +$copier->addFilter(new DoctrineCollectionFilter(), new PropertyTypeMatcher('Doctrine\Common\Collections\Collection')); + +$copy = $copier->copy($object); +``` + + +#### `DoctrineEmptyCollectionFilter` (filter) + +If you use Doctrine and want to copy an entity who contains a `Collection` that you want to be reset, you can use the +`DoctrineEmptyCollectionFilter` + +```php +use DeepCopy\DeepCopy; +use DeepCopy\Filter\Doctrine\DoctrineEmptyCollectionFilter; +use DeepCopy\Matcher\PropertyMatcher; + +$copier = new DeepCopy(); +$copier->addFilter(new DoctrineEmptyCollectionFilter(), new PropertyMatcher('MyClass', 'myProperty')); + +$copy = $copier->copy($object); + +// $copy->myProperty will return an empty collection +``` + + +#### `DoctrineProxyFilter` (filter) + +If you use Doctrine and use cloning on lazy loaded entities, you might encounter errors mentioning missing fields on a +Doctrine proxy class (...\\\_\_CG\_\_\Proxy). +You can use the `DoctrineProxyFilter` to load the actual entity behind the Doctrine proxy class. +**Make sure, though, to put this as one of your very first filters in the filter chain so that the entity is loaded +before other filters are applied!** +We recommend to decorate the `DoctrineProxyFilter` with the `ChainableFilter` to allow applying other filters to the +cloned lazy loaded entities. + +```php +use DeepCopy\DeepCopy; +use DeepCopy\Filter\Doctrine\DoctrineProxyFilter; +use DeepCopy\Matcher\Doctrine\DoctrineProxyMatcher; + +$copier = new DeepCopy(); +$copier->addFilter(new ChainableFilter(new DoctrineProxyFilter()), new DoctrineProxyMatcher()); + +$copy = $copier->copy($object); + +// $copy should now contain a clone of all entities, including those that were not yet fully loaded. +``` + + +#### `ReplaceFilter` (type filter) + +1. If you want to replace the value of a property: + +```php +use DeepCopy\DeepCopy; +use DeepCopy\Filter\ReplaceFilter; +use DeepCopy\Matcher\PropertyMatcher; + +$copier = new DeepCopy(); +$callback = function ($currentValue) { + return $currentValue . ' (copy)' +}; +$copier->addFilter(new ReplaceFilter($callback), new PropertyMatcher('MyClass', 'title')); + +$copy = $copier->copy($object); + +// $copy->title will contain the data returned by the callback, e.g. 'The title (copy)' +``` + +2. If you want to replace whole element: + +```php +use DeepCopy\DeepCopy; +use DeepCopy\TypeFilter\ReplaceFilter; +use DeepCopy\TypeMatcher\TypeMatcher; + +$copier = new DeepCopy(); +$callback = function (MyClass $myClass) { + return get_class($myClass); +}; +$copier->addTypeFilter(new ReplaceFilter($callback), new TypeMatcher('MyClass')); + +$copy = $copier->copy([new MyClass, 'some string', new MyClass]); + +// $copy will contain ['MyClass', 'some string', 'MyClass'] +``` + + +The `$callback` parameter of the `ReplaceFilter` constructor accepts any PHP callable. + + +#### `ShallowCopyFilter` (type filter) + +Stop *DeepCopy* from recursively copying element, using standard `clone` instead: + +```php +use DeepCopy\DeepCopy; +use DeepCopy\TypeFilter\ShallowCopyFilter; +use DeepCopy\TypeMatcher\TypeMatcher; +use Mockery as m; + +$this->deepCopy = new DeepCopy(); +$this->deepCopy->addTypeFilter( + new ShallowCopyFilter, + new TypeMatcher(m\MockInterface::class) +); + +$myServiceWithMocks = new MyService(m::mock(MyDependency1::class), m::mock(MyDependency2::class)); +// All mocks will be just cloned, not deep copied +``` + + +## Edge cases + +The following structures cannot be deep-copied with PHP Reflection. As a result they are shallow cloned and filters are +not applied. There is two ways for you to handle them: + +- Implement your own `__clone()` method +- Use a filter with a type matcher + + +## Contributing + +DeepCopy is distributed under the MIT license. + + +### Tests + +Running the tests is simple: + +```php +vendor/bin/phpunit +``` + +### Support + +Get professional support via [the Tidelift Subscription](https://tidelift.com/subscription/pkg/packagist-myclabs-deep-copy?utm_source=packagist-myclabs-deep-copy&utm_medium=referral&utm_campaign=readme). diff --git a/vendor/myclabs/deep-copy/composer.json b/vendor/myclabs/deep-copy/composer.json new file mode 100644 index 00000000..66fb34a5 --- /dev/null +++ b/vendor/myclabs/deep-copy/composer.json @@ -0,0 +1,42 @@ +{ + "name": "myclabs/deep-copy", + "description": "Create deep copies (clones) of your objects", + "license": "MIT", + "type": "library", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3,<3.2.2" + }, + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "autoload-dev": { + "psr-4": { + "DeepCopy\\": "fixtures/", + "DeepCopyTest\\": "tests/DeepCopyTest/" + } + }, + "config": { + "sort-packages": true + } +} diff --git a/vendor/myclabs/deep-copy/src/DeepCopy/DeepCopy.php b/vendor/myclabs/deep-copy/src/DeepCopy/DeepCopy.php new file mode 100644 index 00000000..6e766d80 --- /dev/null +++ b/vendor/myclabs/deep-copy/src/DeepCopy/DeepCopy.php @@ -0,0 +1,308 @@ + Filter, 'matcher' => Matcher] pairs. + */ + private $filters = []; + + /** + * Type Filters to apply. + * + * @var array Array of ['filter' => Filter, 'matcher' => Matcher] pairs. + */ + private $typeFilters = []; + + /** + * @var bool + */ + private $skipUncloneable = false; + + /** + * @var bool + */ + private $useCloneMethod; + + /** + * @param bool $useCloneMethod If set to true, when an object implements the __clone() function, it will be used + * instead of the regular deep cloning. + */ + public function __construct($useCloneMethod = false) + { + $this->useCloneMethod = $useCloneMethod; + + $this->addTypeFilter(new ArrayObjectFilter($this), new TypeMatcher(ArrayObject::class)); + $this->addTypeFilter(new DateIntervalFilter(), new TypeMatcher(DateInterval::class)); + $this->addTypeFilter(new SplDoublyLinkedListFilter($this), new TypeMatcher(SplDoublyLinkedList::class)); + } + + /** + * If enabled, will not throw an exception when coming across an uncloneable property. + * + * @param $skipUncloneable + * + * @return $this + */ + public function skipUncloneable($skipUncloneable = true) + { + $this->skipUncloneable = $skipUncloneable; + + return $this; + } + + /** + * Deep copies the given object. + * + * @param mixed $object + * + * @return mixed + */ + public function copy($object) + { + $this->hashMap = []; + + return $this->recursiveCopy($object); + } + + public function addFilter(Filter $filter, Matcher $matcher) + { + $this->filters[] = [ + 'matcher' => $matcher, + 'filter' => $filter, + ]; + } + + public function prependFilter(Filter $filter, Matcher $matcher) + { + array_unshift($this->filters, [ + 'matcher' => $matcher, + 'filter' => $filter, + ]); + } + + public function addTypeFilter(TypeFilter $filter, TypeMatcher $matcher) + { + $this->typeFilters[] = [ + 'matcher' => $matcher, + 'filter' => $filter, + ]; + } + + private function recursiveCopy($var) + { + // Matches Type Filter + if ($filter = $this->getFirstMatchedTypeFilter($this->typeFilters, $var)) { + return $filter->apply($var); + } + + // Resource + if (is_resource($var)) { + return $var; + } + + // Array + if (is_array($var)) { + return $this->copyArray($var); + } + + // Scalar + if (! is_object($var)) { + return $var; + } + + // Enum + if (PHP_VERSION_ID >= 80100 && enum_exists(get_class($var))) { + return $var; + } + + // Object + return $this->copyObject($var); + } + + /** + * Copy an array + * @param array $array + * @return array + */ + private function copyArray(array $array) + { + foreach ($array as $key => $value) { + $array[$key] = $this->recursiveCopy($value); + } + + return $array; + } + + /** + * Copies an object. + * + * @param object $object + * + * @throws CloneException + * + * @return object + */ + private function copyObject($object) + { + $objectHash = spl_object_hash($object); + + if (isset($this->hashMap[$objectHash])) { + return $this->hashMap[$objectHash]; + } + + $reflectedObject = new ReflectionObject($object); + $isCloneable = $reflectedObject->isCloneable(); + + if (false === $isCloneable) { + if ($this->skipUncloneable) { + $this->hashMap[$objectHash] = $object; + + return $object; + } + + throw new CloneException( + sprintf( + 'The class "%s" is not cloneable.', + $reflectedObject->getName() + ) + ); + } + + $newObject = clone $object; + $this->hashMap[$objectHash] = $newObject; + + if ($this->useCloneMethod && $reflectedObject->hasMethod('__clone')) { + return $newObject; + } + + if ($newObject instanceof DateTimeInterface || $newObject instanceof DateTimeZone) { + return $newObject; + } + + foreach (ReflectionHelper::getProperties($reflectedObject) as $property) { + $this->copyObjectProperty($newObject, $property); + } + + return $newObject; + } + + private function copyObjectProperty($object, ReflectionProperty $property) + { + // Ignore static properties + if ($property->isStatic()) { + return; + } + + // Apply the filters + foreach ($this->filters as $item) { + /** @var Matcher $matcher */ + $matcher = $item['matcher']; + /** @var Filter $filter */ + $filter = $item['filter']; + + if ($matcher->matches($object, $property->getName())) { + $filter->apply( + $object, + $property->getName(), + function ($object) { + return $this->recursiveCopy($object); + } + ); + + if ($filter instanceof ChainableFilter) { + continue; + } + + // If a filter matches, we stop processing this property + return; + } + } + + $property->setAccessible(true); + + // Ignore uninitialized properties (for PHP >7.4) + if (method_exists($property, 'isInitialized') && !$property->isInitialized($object)) { + return; + } + + $propertyValue = $property->getValue($object); + + // Copy the property + $property->setValue($object, $this->recursiveCopy($propertyValue)); + } + + /** + * Returns first filter that matches variable, `null` if no such filter found. + * + * @param array $filterRecords Associative array with 2 members: 'filter' with value of type {@see TypeFilter} and + * 'matcher' with value of type {@see TypeMatcher} + * @param mixed $var + * + * @return TypeFilter|null + */ + private function getFirstMatchedTypeFilter(array $filterRecords, $var) + { + $matched = $this->first( + $filterRecords, + function (array $record) use ($var) { + /* @var TypeMatcher $matcher */ + $matcher = $record['matcher']; + + return $matcher->matches($var); + } + ); + + return isset($matched) ? $matched['filter'] : null; + } + + /** + * Returns first element that matches predicate, `null` if no such element found. + * + * @param array $elements Array of ['filter' => Filter, 'matcher' => Matcher] pairs. + * @param callable $predicate Predicate arguments are: element. + * + * @return array|null Associative array with 2 members: 'filter' with value of type {@see TypeFilter} and 'matcher' + * with value of type {@see TypeMatcher} or `null`. + */ + private function first(array $elements, callable $predicate) + { + foreach ($elements as $element) { + if (call_user_func($predicate, $element)) { + return $element; + } + } + + return null; + } +} diff --git a/vendor/myclabs/deep-copy/src/DeepCopy/Exception/CloneException.php b/vendor/myclabs/deep-copy/src/DeepCopy/Exception/CloneException.php new file mode 100644 index 00000000..c046706a --- /dev/null +++ b/vendor/myclabs/deep-copy/src/DeepCopy/Exception/CloneException.php @@ -0,0 +1,9 @@ +filter = $filter; + } + + public function apply($object, $property, $objectCopier) + { + $this->filter->apply($object, $property, $objectCopier); + } +} diff --git a/vendor/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineCollectionFilter.php b/vendor/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineCollectionFilter.php new file mode 100644 index 00000000..e6d93771 --- /dev/null +++ b/vendor/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineCollectionFilter.php @@ -0,0 +1,33 @@ +setAccessible(true); + $oldCollection = $reflectionProperty->getValue($object); + + $newCollection = $oldCollection->map( + function ($item) use ($objectCopier) { + return $objectCopier($item); + } + ); + + $reflectionProperty->setValue($object, $newCollection); + } +} diff --git a/vendor/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineEmptyCollectionFilter.php b/vendor/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineEmptyCollectionFilter.php new file mode 100644 index 00000000..7b33fd54 --- /dev/null +++ b/vendor/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineEmptyCollectionFilter.php @@ -0,0 +1,28 @@ +setAccessible(true); + + $reflectionProperty->setValue($object, new ArrayCollection()); + } +} \ No newline at end of file diff --git a/vendor/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineProxyFilter.php b/vendor/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineProxyFilter.php new file mode 100644 index 00000000..8bee8f76 --- /dev/null +++ b/vendor/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineProxyFilter.php @@ -0,0 +1,22 @@ +__load(); + } +} diff --git a/vendor/myclabs/deep-copy/src/DeepCopy/Filter/Filter.php b/vendor/myclabs/deep-copy/src/DeepCopy/Filter/Filter.php new file mode 100644 index 00000000..85ba18ce --- /dev/null +++ b/vendor/myclabs/deep-copy/src/DeepCopy/Filter/Filter.php @@ -0,0 +1,18 @@ +callback = $callable; + } + + /** + * Replaces the object property by the result of the callback called with the object property. + * + * {@inheritdoc} + */ + public function apply($object, $property, $objectCopier) + { + $reflectionProperty = ReflectionHelper::getProperty($object, $property); + $reflectionProperty->setAccessible(true); + + $value = call_user_func($this->callback, $reflectionProperty->getValue($object)); + + $reflectionProperty->setValue($object, $value); + } +} diff --git a/vendor/myclabs/deep-copy/src/DeepCopy/Filter/SetNullFilter.php b/vendor/myclabs/deep-copy/src/DeepCopy/Filter/SetNullFilter.php new file mode 100644 index 00000000..bea86b88 --- /dev/null +++ b/vendor/myclabs/deep-copy/src/DeepCopy/Filter/SetNullFilter.php @@ -0,0 +1,24 @@ +setAccessible(true); + $reflectionProperty->setValue($object, null); + } +} diff --git a/vendor/myclabs/deep-copy/src/DeepCopy/Matcher/Doctrine/DoctrineProxyMatcher.php b/vendor/myclabs/deep-copy/src/DeepCopy/Matcher/Doctrine/DoctrineProxyMatcher.php new file mode 100644 index 00000000..c5887b19 --- /dev/null +++ b/vendor/myclabs/deep-copy/src/DeepCopy/Matcher/Doctrine/DoctrineProxyMatcher.php @@ -0,0 +1,22 @@ +class = $class; + $this->property = $property; + } + + /** + * Matches a specific property of a specific class. + * + * {@inheritdoc} + */ + public function matches($object, $property) + { + return ($object instanceof $this->class) && $property == $this->property; + } +} diff --git a/vendor/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyNameMatcher.php b/vendor/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyNameMatcher.php new file mode 100644 index 00000000..c8ec0d2b --- /dev/null +++ b/vendor/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyNameMatcher.php @@ -0,0 +1,32 @@ +property = $property; + } + + /** + * Matches a property by its name. + * + * {@inheritdoc} + */ + public function matches($object, $property) + { + return $property == $this->property; + } +} diff --git a/vendor/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyTypeMatcher.php b/vendor/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyTypeMatcher.php new file mode 100644 index 00000000..c7f46908 --- /dev/null +++ b/vendor/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyTypeMatcher.php @@ -0,0 +1,52 @@ +propertyType = $propertyType; + } + + /** + * {@inheritdoc} + */ + public function matches($object, $property) + { + try { + $reflectionProperty = ReflectionHelper::getProperty($object, $property); + } catch (ReflectionException $exception) { + return false; + } + + $reflectionProperty->setAccessible(true); + + // Uninitialized properties (for PHP >7.4) + if (method_exists($reflectionProperty, 'isInitialized') && !$reflectionProperty->isInitialized($object)) { + // null instanceof $this->propertyType + return false; + } + + return $reflectionProperty->getValue($object) instanceof $this->propertyType; + } +} diff --git a/vendor/myclabs/deep-copy/src/DeepCopy/Reflection/ReflectionHelper.php b/vendor/myclabs/deep-copy/src/DeepCopy/Reflection/ReflectionHelper.php new file mode 100644 index 00000000..742410cb --- /dev/null +++ b/vendor/myclabs/deep-copy/src/DeepCopy/Reflection/ReflectionHelper.php @@ -0,0 +1,78 @@ +getProperties() does not return private properties from ancestor classes. + * + * @author muratyaman@gmail.com + * @see http://php.net/manual/en/reflectionclass.getproperties.php + * + * @param ReflectionClass $ref + * + * @return ReflectionProperty[] + */ + public static function getProperties(ReflectionClass $ref) + { + $props = $ref->getProperties(); + $propsArr = array(); + + foreach ($props as $prop) { + $propertyName = $prop->getName(); + $propsArr[$propertyName] = $prop; + } + + if ($parentClass = $ref->getParentClass()) { + $parentPropsArr = self::getProperties($parentClass); + foreach ($propsArr as $key => $property) { + $parentPropsArr[$key] = $property; + } + + return $parentPropsArr; + } + + return $propsArr; + } + + /** + * Retrieves property by name from object and all its ancestors. + * + * @param object|string $object + * @param string $name + * + * @throws PropertyException + * @throws ReflectionException + * + * @return ReflectionProperty + */ + public static function getProperty($object, $name) + { + $reflection = is_object($object) ? new ReflectionObject($object) : new ReflectionClass($object); + + if ($reflection->hasProperty($name)) { + return $reflection->getProperty($name); + } + + if ($parentClass = $reflection->getParentClass()) { + return self::getProperty($parentClass->getName(), $name); + } + + throw new PropertyException( + sprintf( + 'The class "%s" doesn\'t have a property with the given name: "%s".', + is_object($object) ? get_class($object) : $object, + $name + ) + ); + } +} diff --git a/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/Date/DateIntervalFilter.php b/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/Date/DateIntervalFilter.php new file mode 100644 index 00000000..becd1cff --- /dev/null +++ b/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/Date/DateIntervalFilter.php @@ -0,0 +1,33 @@ + $propertyValue) { + $copy->{$propertyName} = $propertyValue; + } + + return $copy; + } +} diff --git a/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/ReplaceFilter.php b/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/ReplaceFilter.php new file mode 100644 index 00000000..164f8b8e --- /dev/null +++ b/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/ReplaceFilter.php @@ -0,0 +1,30 @@ +callback = $callable; + } + + /** + * {@inheritdoc} + */ + public function apply($element) + { + return call_user_func($this->callback, $element); + } +} diff --git a/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/ShallowCopyFilter.php b/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/ShallowCopyFilter.php new file mode 100644 index 00000000..a5fbd7a2 --- /dev/null +++ b/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/ShallowCopyFilter.php @@ -0,0 +1,17 @@ +copier = $copier; + } + + /** + * {@inheritdoc} + */ + public function apply($arrayObject) + { + $clone = clone $arrayObject; + foreach ($arrayObject->getArrayCopy() as $k => $v) { + $clone->offsetSet($k, $this->copier->copy($v)); + } + + return $clone; + } +} + diff --git a/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/Spl/SplDoublyLinkedList.php b/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/Spl/SplDoublyLinkedList.php new file mode 100644 index 00000000..c5644cff --- /dev/null +++ b/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/Spl/SplDoublyLinkedList.php @@ -0,0 +1,10 @@ +copier = $copier; + } + + /** + * {@inheritdoc} + */ + public function apply($element) + { + $newElement = clone $element; + + $copy = $this->createCopyClosure(); + + return $copy($newElement); + } + + private function createCopyClosure() + { + $copier = $this->copier; + + $copy = function (SplDoublyLinkedList $list) use ($copier) { + // Replace each element in the list with a deep copy of itself + for ($i = 1; $i <= $list->count(); $i++) { + $copy = $copier->recursiveCopy($list->shift()); + + $list->push($copy); + } + + return $list; + }; + + return Closure::bind($copy, null, DeepCopy::class); + } +} diff --git a/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/TypeFilter.php b/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/TypeFilter.php new file mode 100644 index 00000000..5785a7da --- /dev/null +++ b/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/TypeFilter.php @@ -0,0 +1,13 @@ +type = $type; + } + + /** + * @param mixed $element + * + * @return boolean + */ + public function matches($element) + { + return is_object($element) ? is_a($element, $this->type) : gettype($element) === $this->type; + } +} diff --git a/vendor/myclabs/deep-copy/src/DeepCopy/deep_copy.php b/vendor/myclabs/deep-copy/src/DeepCopy/deep_copy.php new file mode 100644 index 00000000..55dcc926 --- /dev/null +++ b/vendor/myclabs/deep-copy/src/DeepCopy/deep_copy.php @@ -0,0 +1,20 @@ +copy($value); + } +} diff --git a/vendor/nikic/php-parser/LICENSE b/vendor/nikic/php-parser/LICENSE new file mode 100644 index 00000000..2e567183 --- /dev/null +++ b/vendor/nikic/php-parser/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2011, Nikita Popov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/nikic/php-parser/README.md b/vendor/nikic/php-parser/README.md new file mode 100644 index 00000000..7555838e --- /dev/null +++ b/vendor/nikic/php-parser/README.md @@ -0,0 +1,233 @@ +PHP Parser +========== + +[![Coverage Status](https://coveralls.io/repos/github/nikic/PHP-Parser/badge.svg?branch=master)](https://coveralls.io/github/nikic/PHP-Parser?branch=master) + +This is a PHP parser written in PHP. Its purpose is to simplify static code analysis and +manipulation. + +[**Documentation for version 5.x**][doc_master] (current; for running on PHP >= 7.4; for parsing PHP 7.0 to PHP 8.3, with limited support for parsing PHP 5.x). + +[Documentation for version 4.x][doc_4_x] (supported; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 8.3). + +Features +-------- + +The main features provided by this library are: + + * Parsing PHP 7, and PHP 8 code into an abstract syntax tree (AST). + * Invalid code can be parsed into a partial AST. + * The AST contains accurate location information. + * Dumping the AST in human-readable form. + * Converting an AST back to PHP code. + * Formatting can be preserved for partially changed ASTs. + * Infrastructure to traverse and modify ASTs. + * Resolution of namespaced names. + * Evaluation of constant expressions. + * Builders to simplify AST construction for code generation. + * Converting an AST into JSON and back. + +Quick Start +----------- + +Install the library using [composer](https://getcomposer.org): + + php composer.phar require nikic/php-parser + +Parse some PHP code into an AST and dump the result in human-readable form: + +```php +createForNewestSupportedVersion(); +try { + $ast = $parser->parse($code); +} catch (Error $error) { + echo "Parse error: {$error->getMessage()}\n"; + return; +} + +$dumper = new NodeDumper; +echo $dumper->dump($ast) . "\n"; +``` + +This dumps an AST looking something like this: + +``` +array( + 0: Stmt_Function( + attrGroups: array( + ) + byRef: false + name: Identifier( + name: test + ) + params: array( + 0: Param( + attrGroups: array( + ) + flags: 0 + type: null + byRef: false + variadic: false + var: Expr_Variable( + name: foo + ) + default: null + ) + ) + returnType: null + stmts: array( + 0: Stmt_Expression( + expr: Expr_FuncCall( + name: Name( + name: var_dump + ) + args: array( + 0: Arg( + name: null + value: Expr_Variable( + name: foo + ) + byRef: false + unpack: false + ) + ) + ) + ) + ) + ) +) +``` + +Let's traverse the AST and perform some kind of modification. For example, drop all function bodies: + +```php +use PhpParser\Node; +use PhpParser\Node\Stmt\Function_; +use PhpParser\NodeTraverser; +use PhpParser\NodeVisitorAbstract; + +$traverser = new NodeTraverser(); +$traverser->addVisitor(new class extends NodeVisitorAbstract { + public function enterNode(Node $node) { + if ($node instanceof Function_) { + // Clean out the function body + $node->stmts = []; + } + } +}); + +$ast = $traverser->traverse($ast); +echo $dumper->dump($ast) . "\n"; +``` + +This gives us an AST where the `Function_::$stmts` are empty: + +``` +array( + 0: Stmt_Function( + attrGroups: array( + ) + byRef: false + name: Identifier( + name: test + ) + params: array( + 0: Param( + attrGroups: array( + ) + type: null + byRef: false + variadic: false + var: Expr_Variable( + name: foo + ) + default: null + ) + ) + returnType: null + stmts: array( + ) + ) +) +``` + +Finally, we can convert the new AST back to PHP code: + +```php +use PhpParser\PrettyPrinter; + +$prettyPrinter = new PrettyPrinter\Standard; +echo $prettyPrinter->prettyPrintFile($ast); +``` + +This gives us our original code, minus the `var_dump()` call inside the function: + +```php +createForVersion($attributes['version']); +$dumper = new PhpParser\NodeDumper([ + 'dumpComments' => true, + 'dumpPositions' => $attributes['with-positions'], +]); +$prettyPrinter = new PhpParser\PrettyPrinter\Standard; + +$traverser = new PhpParser\NodeTraverser(); +$traverser->addVisitor(new PhpParser\NodeVisitor\NameResolver); + +foreach ($files as $file) { + if ($file === '-') { + $code = file_get_contents('php://stdin'); + fwrite(STDERR, "====> Stdin:\n"); + } else if (strpos($file, ' Code $code\n"); + } else { + if (!file_exists($file)) { + fwrite(STDERR, "File $file does not exist.\n"); + exit(1); + } + + $code = file_get_contents($file); + fwrite(STDERR, "====> File $file:\n"); + } + + if ($attributes['with-recovery']) { + $errorHandler = new PhpParser\ErrorHandler\Collecting; + $stmts = $parser->parse($code, $errorHandler); + foreach ($errorHandler->getErrors() as $error) { + $message = formatErrorMessage($error, $code, $attributes['with-column-info']); + fwrite(STDERR, $message . "\n"); + } + if (null === $stmts) { + continue; + } + } else { + try { + $stmts = $parser->parse($code); + } catch (PhpParser\Error $error) { + $message = formatErrorMessage($error, $code, $attributes['with-column-info']); + fwrite(STDERR, $message . "\n"); + exit(1); + } + } + + foreach ($operations as $operation) { + if ('dump' === $operation) { + fwrite(STDERR, "==> Node dump:\n"); + echo $dumper->dump($stmts, $code), "\n"; + } elseif ('pretty-print' === $operation) { + fwrite(STDERR, "==> Pretty print:\n"); + echo $prettyPrinter->prettyPrintFile($stmts), "\n"; + } elseif ('json-dump' === $operation) { + fwrite(STDERR, "==> JSON dump:\n"); + echo json_encode($stmts, JSON_PRETTY_PRINT), "\n"; + } elseif ('var-dump' === $operation) { + fwrite(STDERR, "==> var_dump():\n"); + var_dump($stmts); + } elseif ('resolve-names' === $operation) { + fwrite(STDERR, "==> Resolved names.\n"); + $stmts = $traverser->traverse($stmts); + } + } +} + +function formatErrorMessage(PhpParser\Error $e, $code, $withColumnInfo) { + if ($withColumnInfo && $e->hasColumnInfo()) { + return $e->getMessageWithColumnInfo($code); + } else { + return $e->getMessage(); + } +} + +function showHelp($error = '') { + if ($error) { + fwrite(STDERR, $error . "\n\n"); + } + fwrite($error ? STDERR : STDOUT, <<<'OUTPUT' +Usage: php-parse [operations] file1.php [file2.php ...] + or: php-parse [operations] " false, + 'with-positions' => false, + 'with-recovery' => false, + 'version' => PhpParser\PhpVersion::getNewestSupported(), + ]; + + array_shift($args); + $parseOptions = true; + foreach ($args as $arg) { + if (!$parseOptions) { + $files[] = $arg; + continue; + } + + switch ($arg) { + case '--dump': + case '-d': + $operations[] = 'dump'; + break; + case '--pretty-print': + case '-p': + $operations[] = 'pretty-print'; + break; + case '--json-dump': + case '-j': + $operations[] = 'json-dump'; + break; + case '--var-dump': + $operations[] = 'var-dump'; + break; + case '--resolve-names': + case '-N'; + $operations[] = 'resolve-names'; + break; + case '--with-column-info': + case '-c'; + $attributes['with-column-info'] = true; + break; + case '--with-positions': + case '-P': + $attributes['with-positions'] = true; + break; + case '--with-recovery': + case '-r': + $attributes['with-recovery'] = true; + break; + case '--help': + case '-h'; + showHelp(); + break; + case '--': + $parseOptions = false; + break; + default: + if (preg_match('/^--version=(.*)$/', $arg, $matches)) { + $attributes['version'] = PhpParser\PhpVersion::fromString($matches[1]); + } elseif ($arg[0] === '-' && \strlen($arg[0]) > 1) { + showHelp("Invalid operation $arg."); + } else { + $files[] = $arg; + } + } + } + + return [$operations, $files, $attributes]; +} diff --git a/vendor/nikic/php-parser/composer.json b/vendor/nikic/php-parser/composer.json new file mode 100644 index 00000000..b4b1bca7 --- /dev/null +++ b/vendor/nikic/php-parser/composer.json @@ -0,0 +1,43 @@ +{ + "name": "nikic/php-parser", + "type": "library", + "description": "A PHP parser written in PHP", + "keywords": [ + "php", + "parser" + ], + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Nikita Popov" + } + ], + "require": { + "php": ">=7.4", + "ext-tokenizer": "*", + "ext-json": "*", + "ext-ctype": "*" + }, + "require-dev": { + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "ircmaxell/php-yacc": "^0.0.7" + }, + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "autoload-dev": { + "psr-4": { + "PhpParser\\": "test/PhpParser/" + } + }, + "bin": [ + "bin/php-parse" + ] +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder.php b/vendor/nikic/php-parser/lib/PhpParser/Builder.php new file mode 100644 index 00000000..d6aa124c --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder.php @@ -0,0 +1,12 @@ + */ + protected array $attributes = []; + /** @var list */ + protected array $constants = []; + + /** @var list */ + protected array $attributeGroups = []; + /** @var Identifier|Node\Name|Node\ComplexType|null */ + protected ?Node $type = null; + + /** + * Creates a class constant builder + * + * @param string|Identifier $name Name + * @param Node\Expr|bool|null|int|float|string|array $value Value + */ + public function __construct($name, $value) { + $this->constants = [new Const_($name, BuilderHelpers::normalizeValue($value))]; + } + + /** + * Add another constant to const group + * + * @param string|Identifier $name Name + * @param Node\Expr|bool|null|int|float|string|array $value Value + * + * @return $this The builder instance (for fluid interface) + */ + public function addConst($name, $value) { + $this->constants[] = new Const_($name, BuilderHelpers::normalizeValue($value)); + + return $this; + } + + /** + * Makes the constant public. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePublic() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PUBLIC); + + return $this; + } + + /** + * Makes the constant protected. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeProtected() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PROTECTED); + + return $this; + } + + /** + * Makes the constant private. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePrivate() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PRIVATE); + + return $this; + } + + /** + * Makes the constant final. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeFinal() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::FINAL); + + return $this; + } + + /** + * Sets doc comment for the constant. + * + * @param PhpParser\Comment\Doc|string $docComment Doc comment to set + * + * @return $this The builder instance (for fluid interface) + */ + public function setDocComment($docComment) { + $this->attributes = [ + 'comments' => [BuilderHelpers::normalizeDocComment($docComment)] + ]; + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Sets the constant type. + * + * @param string|Node\Name|Identifier|Node\ComplexType $type + * + * @return $this + */ + public function setType($type) { + $this->type = BuilderHelpers::normalizeType($type); + + return $this; + } + + /** + * Returns the built class node. + * + * @return Stmt\ClassConst The built constant node + */ + public function getNode(): PhpParser\Node { + return new Stmt\ClassConst( + $this->constants, + $this->flags, + $this->attributes, + $this->attributeGroups, + $this->type + ); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Class_.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Class_.php new file mode 100644 index 00000000..6f394315 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Class_.php @@ -0,0 +1,151 @@ + */ + protected array $implements = []; + protected int $flags = 0; + /** @var list */ + protected array $uses = []; + /** @var list */ + protected array $constants = []; + /** @var list */ + protected array $properties = []; + /** @var list */ + protected array $methods = []; + /** @var list */ + protected array $attributeGroups = []; + + /** + * Creates a class builder. + * + * @param string $name Name of the class + */ + public function __construct(string $name) { + $this->name = $name; + } + + /** + * Extends a class. + * + * @param Name|string $class Name of class to extend + * + * @return $this The builder instance (for fluid interface) + */ + public function extend($class) { + $this->extends = BuilderHelpers::normalizeName($class); + + return $this; + } + + /** + * Implements one or more interfaces. + * + * @param Name|string ...$interfaces Names of interfaces to implement + * + * @return $this The builder instance (for fluid interface) + */ + public function implement(...$interfaces) { + foreach ($interfaces as $interface) { + $this->implements[] = BuilderHelpers::normalizeName($interface); + } + + return $this; + } + + /** + * Makes the class abstract. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeAbstract() { + $this->flags = BuilderHelpers::addClassModifier($this->flags, Modifiers::ABSTRACT); + + return $this; + } + + /** + * Makes the class final. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeFinal() { + $this->flags = BuilderHelpers::addClassModifier($this->flags, Modifiers::FINAL); + + return $this; + } + + /** + * Makes the class readonly. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeReadonly() { + $this->flags = BuilderHelpers::addClassModifier($this->flags, Modifiers::READONLY); + + return $this; + } + + /** + * Adds a statement. + * + * @param Stmt|PhpParser\Builder $stmt The statement to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addStmt($stmt) { + $stmt = BuilderHelpers::normalizeNode($stmt); + + if ($stmt instanceof Stmt\Property) { + $this->properties[] = $stmt; + } elseif ($stmt instanceof Stmt\ClassMethod) { + $this->methods[] = $stmt; + } elseif ($stmt instanceof Stmt\TraitUse) { + $this->uses[] = $stmt; + } elseif ($stmt instanceof Stmt\ClassConst) { + $this->constants[] = $stmt; + } else { + throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType())); + } + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built class node. + * + * @return Stmt\Class_ The built class node + */ + public function getNode(): PhpParser\Node { + return new Stmt\Class_($this->name, [ + 'flags' => $this->flags, + 'extends' => $this->extends, + 'implements' => $this->implements, + 'stmts' => array_merge($this->uses, $this->constants, $this->properties, $this->methods), + 'attrGroups' => $this->attributeGroups, + ], $this->attributes); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Declaration.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Declaration.php new file mode 100644 index 00000000..488b7213 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Declaration.php @@ -0,0 +1,50 @@ + */ + protected array $attributes = []; + + /** + * Adds a statement. + * + * @param PhpParser\Node\Stmt|PhpParser\Builder $stmt The statement to add + * + * @return $this The builder instance (for fluid interface) + */ + abstract public function addStmt($stmt); + + /** + * Adds multiple statements. + * + * @param (PhpParser\Node\Stmt|PhpParser\Builder)[] $stmts The statements to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addStmts(array $stmts) { + foreach ($stmts as $stmt) { + $this->addStmt($stmt); + } + + return $this; + } + + /** + * Sets doc comment for the declaration. + * + * @param PhpParser\Comment\Doc|string $docComment Doc comment to set + * + * @return $this The builder instance (for fluid interface) + */ + public function setDocComment($docComment) { + $this->attributes['comments'] = [ + BuilderHelpers::normalizeDocComment($docComment) + ]; + + return $this; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/EnumCase.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/EnumCase.php new file mode 100644 index 00000000..04058bf5 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/EnumCase.php @@ -0,0 +1,87 @@ + */ + protected array $attributes = []; + + /** @var list */ + protected array $attributeGroups = []; + + /** + * Creates an enum case builder. + * + * @param string|Identifier $name Name + */ + public function __construct($name) { + $this->name = $name; + } + + /** + * Sets the value. + * + * @param Node\Expr|string|int $value + * + * @return $this + */ + public function setValue($value) { + $this->value = BuilderHelpers::normalizeValue($value); + + return $this; + } + + /** + * Sets doc comment for the constant. + * + * @param PhpParser\Comment\Doc|string $docComment Doc comment to set + * + * @return $this The builder instance (for fluid interface) + */ + public function setDocComment($docComment) { + $this->attributes = [ + 'comments' => [BuilderHelpers::normalizeDocComment($docComment)] + ]; + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built enum case node. + * + * @return Stmt\EnumCase The built constant node + */ + public function getNode(): PhpParser\Node { + return new Stmt\EnumCase( + $this->name, + $this->value, + $this->attributeGroups, + $this->attributes + ); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Enum_.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Enum_.php new file mode 100644 index 00000000..c00df03f --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Enum_.php @@ -0,0 +1,116 @@ + */ + protected array $implements = []; + /** @var list */ + protected array $uses = []; + /** @var list */ + protected array $enumCases = []; + /** @var list */ + protected array $constants = []; + /** @var list */ + protected array $methods = []; + /** @var list */ + protected array $attributeGroups = []; + + /** + * Creates an enum builder. + * + * @param string $name Name of the enum + */ + public function __construct(string $name) { + $this->name = $name; + } + + /** + * Sets the scalar type. + * + * @param string|Identifier $scalarType + * + * @return $this + */ + public function setScalarType($scalarType) { + $this->scalarType = BuilderHelpers::normalizeType($scalarType); + + return $this; + } + + /** + * Implements one or more interfaces. + * + * @param Name|string ...$interfaces Names of interfaces to implement + * + * @return $this The builder instance (for fluid interface) + */ + public function implement(...$interfaces) { + foreach ($interfaces as $interface) { + $this->implements[] = BuilderHelpers::normalizeName($interface); + } + + return $this; + } + + /** + * Adds a statement. + * + * @param Stmt|PhpParser\Builder $stmt The statement to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addStmt($stmt) { + $stmt = BuilderHelpers::normalizeNode($stmt); + + if ($stmt instanceof Stmt\EnumCase) { + $this->enumCases[] = $stmt; + } elseif ($stmt instanceof Stmt\ClassMethod) { + $this->methods[] = $stmt; + } elseif ($stmt instanceof Stmt\TraitUse) { + $this->uses[] = $stmt; + } elseif ($stmt instanceof Stmt\ClassConst) { + $this->constants[] = $stmt; + } else { + throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType())); + } + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built class node. + * + * @return Stmt\Enum_ The built enum node + */ + public function getNode(): PhpParser\Node { + return new Stmt\Enum_($this->name, [ + 'scalarType' => $this->scalarType, + 'implements' => $this->implements, + 'stmts' => array_merge($this->uses, $this->enumCases, $this->constants, $this->methods), + 'attrGroups' => $this->attributeGroups, + ], $this->attributes); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/FunctionLike.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/FunctionLike.php new file mode 100644 index 00000000..ff79cb6b --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/FunctionLike.php @@ -0,0 +1,73 @@ +returnByRef = true; + + return $this; + } + + /** + * Adds a parameter. + * + * @param Node\Param|Param $param The parameter to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addParam($param) { + $param = BuilderHelpers::normalizeNode($param); + + if (!$param instanceof Node\Param) { + throw new \LogicException(sprintf('Expected parameter node, got "%s"', $param->getType())); + } + + $this->params[] = $param; + + return $this; + } + + /** + * Adds multiple parameters. + * + * @param (Node\Param|Param)[] $params The parameters to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addParams(array $params) { + foreach ($params as $param) { + $this->addParam($param); + } + + return $this; + } + + /** + * Sets the return type for PHP 7. + * + * @param string|Node\Name|Node\Identifier|Node\ComplexType $type + * + * @return $this The builder instance (for fluid interface) + */ + public function setReturnType($type) { + $this->returnType = BuilderHelpers::normalizeType($type); + + return $this; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Function_.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Function_.php new file mode 100644 index 00000000..48f5f693 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Function_.php @@ -0,0 +1,67 @@ + */ + protected array $stmts = []; + + /** @var list */ + protected array $attributeGroups = []; + + /** + * Creates a function builder. + * + * @param string $name Name of the function + */ + public function __construct(string $name) { + $this->name = $name; + } + + /** + * Adds a statement. + * + * @param Node|PhpParser\Builder $stmt The statement to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addStmt($stmt) { + $this->stmts[] = BuilderHelpers::normalizeStmt($stmt); + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built function node. + * + * @return Stmt\Function_ The built function node + */ + public function getNode(): Node { + return new Stmt\Function_($this->name, [ + 'byRef' => $this->returnByRef, + 'params' => $this->params, + 'returnType' => $this->returnType, + 'stmts' => $this->stmts, + 'attrGroups' => $this->attributeGroups, + ], $this->attributes); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Interface_.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Interface_.php new file mode 100644 index 00000000..13dd3f7f --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Interface_.php @@ -0,0 +1,94 @@ + */ + protected array $extends = []; + /** @var list */ + protected array $constants = []; + /** @var list */ + protected array $methods = []; + /** @var list */ + protected array $attributeGroups = []; + + /** + * Creates an interface builder. + * + * @param string $name Name of the interface + */ + public function __construct(string $name) { + $this->name = $name; + } + + /** + * Extends one or more interfaces. + * + * @param Name|string ...$interfaces Names of interfaces to extend + * + * @return $this The builder instance (for fluid interface) + */ + public function extend(...$interfaces) { + foreach ($interfaces as $interface) { + $this->extends[] = BuilderHelpers::normalizeName($interface); + } + + return $this; + } + + /** + * Adds a statement. + * + * @param Stmt|PhpParser\Builder $stmt The statement to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addStmt($stmt) { + $stmt = BuilderHelpers::normalizeNode($stmt); + + if ($stmt instanceof Stmt\ClassConst) { + $this->constants[] = $stmt; + } elseif ($stmt instanceof Stmt\ClassMethod) { + // we erase all statements in the body of an interface method + $stmt->stmts = null; + $this->methods[] = $stmt; + } else { + throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType())); + } + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built interface node. + * + * @return Stmt\Interface_ The built interface node + */ + public function getNode(): PhpParser\Node { + return new Stmt\Interface_($this->name, [ + 'extends' => $this->extends, + 'stmts' => array_merge($this->constants, $this->methods), + 'attrGroups' => $this->attributeGroups, + ], $this->attributes); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Method.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Method.php new file mode 100644 index 00000000..8358dbe3 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Method.php @@ -0,0 +1,147 @@ +|null */ + protected ?array $stmts = []; + + /** @var list */ + protected array $attributeGroups = []; + + /** + * Creates a method builder. + * + * @param string $name Name of the method + */ + public function __construct(string $name) { + $this->name = $name; + } + + /** + * Makes the method public. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePublic() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PUBLIC); + + return $this; + } + + /** + * Makes the method protected. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeProtected() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PROTECTED); + + return $this; + } + + /** + * Makes the method private. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePrivate() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PRIVATE); + + return $this; + } + + /** + * Makes the method static. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeStatic() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::STATIC); + + return $this; + } + + /** + * Makes the method abstract. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeAbstract() { + if (!empty($this->stmts)) { + throw new \LogicException('Cannot make method with statements abstract'); + } + + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::ABSTRACT); + $this->stmts = null; // abstract methods don't have statements + + return $this; + } + + /** + * Makes the method final. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeFinal() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::FINAL); + + return $this; + } + + /** + * Adds a statement. + * + * @param Node|PhpParser\Builder $stmt The statement to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addStmt($stmt) { + if (null === $this->stmts) { + throw new \LogicException('Cannot add statements to an abstract method'); + } + + $this->stmts[] = BuilderHelpers::normalizeStmt($stmt); + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built method node. + * + * @return Stmt\ClassMethod The built method node + */ + public function getNode(): Node { + return new Stmt\ClassMethod($this->name, [ + 'flags' => $this->flags, + 'byRef' => $this->returnByRef, + 'params' => $this->params, + 'returnType' => $this->returnType, + 'stmts' => $this->stmts, + 'attrGroups' => $this->attributeGroups, + ], $this->attributes); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Namespace_.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Namespace_.php new file mode 100644 index 00000000..80fe6f84 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Namespace_.php @@ -0,0 +1,45 @@ +name = null !== $name ? BuilderHelpers::normalizeName($name) : null; + } + + /** + * Adds a statement. + * + * @param Node|PhpParser\Builder $stmt The statement to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addStmt($stmt) { + $this->stmts[] = BuilderHelpers::normalizeStmt($stmt); + + return $this; + } + + /** + * Returns the built node. + * + * @return Stmt\Namespace_ The built node + */ + public function getNode(): Node { + return new Stmt\Namespace_($this->name, $this->stmts, $this->attributes); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Param.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Param.php new file mode 100644 index 00000000..f439e876 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Param.php @@ -0,0 +1,149 @@ + */ + protected array $attributeGroups = []; + + /** + * Creates a parameter builder. + * + * @param string $name Name of the parameter + */ + public function __construct(string $name) { + $this->name = $name; + } + + /** + * Sets default value for the parameter. + * + * @param mixed $value Default value to use + * + * @return $this The builder instance (for fluid interface) + */ + public function setDefault($value) { + $this->default = BuilderHelpers::normalizeValue($value); + + return $this; + } + + /** + * Sets type for the parameter. + * + * @param string|Node\Name|Node\Identifier|Node\ComplexType $type Parameter type + * + * @return $this The builder instance (for fluid interface) + */ + public function setType($type) { + $this->type = BuilderHelpers::normalizeType($type); + if ($this->type == 'void') { + throw new \LogicException('Parameter type cannot be void'); + } + + return $this; + } + + /** + * Make the parameter accept the value by reference. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeByRef() { + $this->byRef = true; + + return $this; + } + + /** + * Make the parameter variadic + * + * @return $this The builder instance (for fluid interface) + */ + public function makeVariadic() { + $this->variadic = true; + + return $this; + } + + /** + * Makes the (promoted) parameter public. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePublic() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PUBLIC); + + return $this; + } + + /** + * Makes the (promoted) parameter protected. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeProtected() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PROTECTED); + + return $this; + } + + /** + * Makes the (promoted) parameter private. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePrivate() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PRIVATE); + + return $this; + } + + /** + * Makes the (promoted) parameter readonly. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeReadonly() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::READONLY); + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built parameter node. + * + * @return Node\Param The built parameter node + */ + public function getNode(): Node { + return new Node\Param( + new Node\Expr\Variable($this->name), + $this->default, $this->type, $this->byRef, $this->variadic, [], $this->flags, $this->attributeGroups + ); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Property.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Property.php new file mode 100644 index 00000000..3fc96d98 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Property.php @@ -0,0 +1,161 @@ + */ + protected array $attributes = []; + /** @var null|Identifier|Name|ComplexType */ + protected ?Node $type = null; + /** @var list */ + protected array $attributeGroups = []; + + /** + * Creates a property builder. + * + * @param string $name Name of the property + */ + public function __construct(string $name) { + $this->name = $name; + } + + /** + * Makes the property public. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePublic() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PUBLIC); + + return $this; + } + + /** + * Makes the property protected. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeProtected() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PROTECTED); + + return $this; + } + + /** + * Makes the property private. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePrivate() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PRIVATE); + + return $this; + } + + /** + * Makes the property static. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeStatic() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::STATIC); + + return $this; + } + + /** + * Makes the property readonly. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeReadonly() { + $this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::READONLY); + + return $this; + } + + /** + * Sets default value for the property. + * + * @param mixed $value Default value to use + * + * @return $this The builder instance (for fluid interface) + */ + public function setDefault($value) { + $this->default = BuilderHelpers::normalizeValue($value); + + return $this; + } + + /** + * Sets doc comment for the property. + * + * @param PhpParser\Comment\Doc|string $docComment Doc comment to set + * + * @return $this The builder instance (for fluid interface) + */ + public function setDocComment($docComment) { + $this->attributes = [ + 'comments' => [BuilderHelpers::normalizeDocComment($docComment)] + ]; + + return $this; + } + + /** + * Sets the property type for PHP 7.4+. + * + * @param string|Name|Identifier|ComplexType $type + * + * @return $this + */ + public function setType($type) { + $this->type = BuilderHelpers::normalizeType($type); + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built class node. + * + * @return Stmt\Property The built property node + */ + public function getNode(): PhpParser\Node { + return new Stmt\Property( + $this->flags !== 0 ? $this->flags : Modifiers::PUBLIC, + [ + new Node\PropertyItem($this->name, $this->default) + ], + $this->attributes, + $this->type, + $this->attributeGroups + ); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/TraitUse.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/TraitUse.php new file mode 100644 index 00000000..cf21c821 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/TraitUse.php @@ -0,0 +1,65 @@ +and($trait); + } + } + + /** + * Adds used trait. + * + * @param Node\Name|string $trait Trait name + * + * @return $this The builder instance (for fluid interface) + */ + public function and($trait) { + $this->traits[] = BuilderHelpers::normalizeName($trait); + return $this; + } + + /** + * Adds trait adaptation. + * + * @param Stmt\TraitUseAdaptation|Builder\TraitUseAdaptation $adaptation Trait adaptation + * + * @return $this The builder instance (for fluid interface) + */ + public function with($adaptation) { + $adaptation = BuilderHelpers::normalizeNode($adaptation); + + if (!$adaptation instanceof Stmt\TraitUseAdaptation) { + throw new \LogicException('Adaptation must have type TraitUseAdaptation'); + } + + $this->adaptations[] = $adaptation; + return $this; + } + + /** + * Returns the built node. + * + * @return Node The built node + */ + public function getNode(): Node { + return new Stmt\TraitUse($this->traits, $this->adaptations); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/TraitUseAdaptation.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/TraitUseAdaptation.php new file mode 100644 index 00000000..fee09583 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/TraitUseAdaptation.php @@ -0,0 +1,145 @@ +type = self::TYPE_UNDEFINED; + + $this->trait = is_null($trait) ? null : BuilderHelpers::normalizeName($trait); + $this->method = BuilderHelpers::normalizeIdentifier($method); + } + + /** + * Sets alias of method. + * + * @param Node\Identifier|string $alias Alias for adapted method + * + * @return $this The builder instance (for fluid interface) + */ + public function as($alias) { + if ($this->type === self::TYPE_UNDEFINED) { + $this->type = self::TYPE_ALIAS; + } + + if ($this->type !== self::TYPE_ALIAS) { + throw new \LogicException('Cannot set alias for not alias adaptation buider'); + } + + $this->alias = BuilderHelpers::normalizeIdentifier($alias); + return $this; + } + + /** + * Sets adapted method public. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePublic() { + $this->setModifier(Modifiers::PUBLIC); + return $this; + } + + /** + * Sets adapted method protected. + * + * @return $this The builder instance (for fluid interface) + */ + public function makeProtected() { + $this->setModifier(Modifiers::PROTECTED); + return $this; + } + + /** + * Sets adapted method private. + * + * @return $this The builder instance (for fluid interface) + */ + public function makePrivate() { + $this->setModifier(Modifiers::PRIVATE); + return $this; + } + + /** + * Adds overwritten traits. + * + * @param Node\Name|string ...$traits Traits for overwrite + * + * @return $this The builder instance (for fluid interface) + */ + public function insteadof(...$traits) { + if ($this->type === self::TYPE_UNDEFINED) { + if (is_null($this->trait)) { + throw new \LogicException('Precedence adaptation must have trait'); + } + + $this->type = self::TYPE_PRECEDENCE; + } + + if ($this->type !== self::TYPE_PRECEDENCE) { + throw new \LogicException('Cannot add overwritten traits for not precedence adaptation buider'); + } + + foreach ($traits as $trait) { + $this->insteadof[] = BuilderHelpers::normalizeName($trait); + } + + return $this; + } + + protected function setModifier(int $modifier): void { + if ($this->type === self::TYPE_UNDEFINED) { + $this->type = self::TYPE_ALIAS; + } + + if ($this->type !== self::TYPE_ALIAS) { + throw new \LogicException('Cannot set access modifier for not alias adaptation buider'); + } + + if (is_null($this->modifier)) { + $this->modifier = $modifier; + } else { + throw new \LogicException('Multiple access type modifiers are not allowed'); + } + } + + /** + * Returns the built node. + * + * @return Node The built node + */ + public function getNode(): Node { + switch ($this->type) { + case self::TYPE_ALIAS: + return new Stmt\TraitUseAdaptation\Alias($this->trait, $this->method, $this->modifier, $this->alias); + case self::TYPE_PRECEDENCE: + return new Stmt\TraitUseAdaptation\Precedence($this->trait, $this->method, $this->insteadof); + default: + throw new \LogicException('Type of adaptation is not defined'); + } + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Trait_.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Trait_.php new file mode 100644 index 00000000..ffa1bd5c --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Trait_.php @@ -0,0 +1,83 @@ + */ + protected array $uses = []; + /** @var list */ + protected array $constants = []; + /** @var list */ + protected array $properties = []; + /** @var list */ + protected array $methods = []; + /** @var list */ + protected array $attributeGroups = []; + + /** + * Creates an interface builder. + * + * @param string $name Name of the interface + */ + public function __construct(string $name) { + $this->name = $name; + } + + /** + * Adds a statement. + * + * @param Stmt|PhpParser\Builder $stmt The statement to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addStmt($stmt) { + $stmt = BuilderHelpers::normalizeNode($stmt); + + if ($stmt instanceof Stmt\Property) { + $this->properties[] = $stmt; + } elseif ($stmt instanceof Stmt\ClassMethod) { + $this->methods[] = $stmt; + } elseif ($stmt instanceof Stmt\TraitUse) { + $this->uses[] = $stmt; + } elseif ($stmt instanceof Stmt\ClassConst) { + $this->constants[] = $stmt; + } else { + throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType())); + } + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built trait node. + * + * @return Stmt\Trait_ The built interface node + */ + public function getNode(): PhpParser\Node { + return new Stmt\Trait_( + $this->name, [ + 'stmts' => array_merge($this->uses, $this->constants, $this->properties, $this->methods), + 'attrGroups' => $this->attributeGroups, + ], $this->attributes + ); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Builder/Use_.php b/vendor/nikic/php-parser/lib/PhpParser/Builder/Use_.php new file mode 100644 index 00000000..b82cf139 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Builder/Use_.php @@ -0,0 +1,49 @@ +name = BuilderHelpers::normalizeName($name); + $this->type = $type; + } + + /** + * Sets alias for used name. + * + * @param string $alias Alias to use (last component of full name by default) + * + * @return $this The builder instance (for fluid interface) + */ + public function as(string $alias) { + $this->alias = $alias; + return $this; + } + + /** + * Returns the built node. + * + * @return Stmt\Use_ The built node + */ + public function getNode(): Node { + return new Stmt\Use_([ + new Node\UseItem($this->name, $this->alias) + ], $this->type); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/BuilderFactory.php b/vendor/nikic/php-parser/lib/PhpParser/BuilderFactory.php new file mode 100644 index 00000000..b7efe5e9 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/BuilderFactory.php @@ -0,0 +1,375 @@ +args($args) + ); + } + + /** + * Creates a namespace builder. + * + * @param null|string|Node\Name $name Name of the namespace + * + * @return Builder\Namespace_ The created namespace builder + */ + public function namespace($name): Builder\Namespace_ { + return new Builder\Namespace_($name); + } + + /** + * Creates a class builder. + * + * @param string $name Name of the class + * + * @return Builder\Class_ The created class builder + */ + public function class(string $name): Builder\Class_ { + return new Builder\Class_($name); + } + + /** + * Creates an interface builder. + * + * @param string $name Name of the interface + * + * @return Builder\Interface_ The created interface builder + */ + public function interface(string $name): Builder\Interface_ { + return new Builder\Interface_($name); + } + + /** + * Creates a trait builder. + * + * @param string $name Name of the trait + * + * @return Builder\Trait_ The created trait builder + */ + public function trait(string $name): Builder\Trait_ { + return new Builder\Trait_($name); + } + + /** + * Creates an enum builder. + * + * @param string $name Name of the enum + * + * @return Builder\Enum_ The created enum builder + */ + public function enum(string $name): Builder\Enum_ { + return new Builder\Enum_($name); + } + + /** + * Creates a trait use builder. + * + * @param Node\Name|string ...$traits Trait names + * + * @return Builder\TraitUse The created trait use builder + */ + public function useTrait(...$traits): Builder\TraitUse { + return new Builder\TraitUse(...$traits); + } + + /** + * Creates a trait use adaptation builder. + * + * @param Node\Name|string|null $trait Trait name + * @param Node\Identifier|string $method Method name + * + * @return Builder\TraitUseAdaptation The created trait use adaptation builder + */ + public function traitUseAdaptation($trait, $method = null): Builder\TraitUseAdaptation { + if ($method === null) { + $method = $trait; + $trait = null; + } + + return new Builder\TraitUseAdaptation($trait, $method); + } + + /** + * Creates a method builder. + * + * @param string $name Name of the method + * + * @return Builder\Method The created method builder + */ + public function method(string $name): Builder\Method { + return new Builder\Method($name); + } + + /** + * Creates a parameter builder. + * + * @param string $name Name of the parameter + * + * @return Builder\Param The created parameter builder + */ + public function param(string $name): Builder\Param { + return new Builder\Param($name); + } + + /** + * Creates a property builder. + * + * @param string $name Name of the property + * + * @return Builder\Property The created property builder + */ + public function property(string $name): Builder\Property { + return new Builder\Property($name); + } + + /** + * Creates a function builder. + * + * @param string $name Name of the function + * + * @return Builder\Function_ The created function builder + */ + public function function(string $name): Builder\Function_ { + return new Builder\Function_($name); + } + + /** + * Creates a namespace/class use builder. + * + * @param Node\Name|string $name Name of the entity (namespace or class) to alias + * + * @return Builder\Use_ The created use builder + */ + public function use($name): Builder\Use_ { + return new Builder\Use_($name, Use_::TYPE_NORMAL); + } + + /** + * Creates a function use builder. + * + * @param Node\Name|string $name Name of the function to alias + * + * @return Builder\Use_ The created use function builder + */ + public function useFunction($name): Builder\Use_ { + return new Builder\Use_($name, Use_::TYPE_FUNCTION); + } + + /** + * Creates a constant use builder. + * + * @param Node\Name|string $name Name of the const to alias + * + * @return Builder\Use_ The created use const builder + */ + public function useConst($name): Builder\Use_ { + return new Builder\Use_($name, Use_::TYPE_CONSTANT); + } + + /** + * Creates a class constant builder. + * + * @param string|Identifier $name Name + * @param Node\Expr|bool|null|int|float|string|array $value Value + * + * @return Builder\ClassConst The created use const builder + */ + public function classConst($name, $value): Builder\ClassConst { + return new Builder\ClassConst($name, $value); + } + + /** + * Creates an enum case builder. + * + * @param string|Identifier $name Name + * + * @return Builder\EnumCase The created use const builder + */ + public function enumCase($name): Builder\EnumCase { + return new Builder\EnumCase($name); + } + + /** + * Creates node a for a literal value. + * + * @param Expr|bool|null|int|float|string|array $value $value + */ + public function val($value): Expr { + return BuilderHelpers::normalizeValue($value); + } + + /** + * Creates variable node. + * + * @param string|Expr $name Name + */ + public function var($name): Expr\Variable { + if (!\is_string($name) && !$name instanceof Expr) { + throw new \LogicException('Variable name must be string or Expr'); + } + + return new Expr\Variable($name); + } + + /** + * Normalizes an argument list. + * + * Creates Arg nodes for all arguments and converts literal values to expressions. + * + * @param array $args List of arguments to normalize + * + * @return list + */ + public function args(array $args): array { + $normalizedArgs = []; + foreach ($args as $key => $arg) { + if (!($arg instanceof Arg)) { + $arg = new Arg(BuilderHelpers::normalizeValue($arg)); + } + if (\is_string($key)) { + $arg->name = BuilderHelpers::normalizeIdentifier($key); + } + $normalizedArgs[] = $arg; + } + return $normalizedArgs; + } + + /** + * Creates a function call node. + * + * @param string|Name|Expr $name Function name + * @param array $args Function arguments + */ + public function funcCall($name, array $args = []): Expr\FuncCall { + return new Expr\FuncCall( + BuilderHelpers::normalizeNameOrExpr($name), + $this->args($args) + ); + } + + /** + * Creates a method call node. + * + * @param Expr $var Variable the method is called on + * @param string|Identifier|Expr $name Method name + * @param array $args Method arguments + */ + public function methodCall(Expr $var, $name, array $args = []): Expr\MethodCall { + return new Expr\MethodCall( + $var, + BuilderHelpers::normalizeIdentifierOrExpr($name), + $this->args($args) + ); + } + + /** + * Creates a static method call node. + * + * @param string|Name|Expr $class Class name + * @param string|Identifier|Expr $name Method name + * @param array $args Method arguments + */ + public function staticCall($class, $name, array $args = []): Expr\StaticCall { + return new Expr\StaticCall( + BuilderHelpers::normalizeNameOrExpr($class), + BuilderHelpers::normalizeIdentifierOrExpr($name), + $this->args($args) + ); + } + + /** + * Creates an object creation node. + * + * @param string|Name|Expr $class Class name + * @param array $args Constructor arguments + */ + public function new($class, array $args = []): Expr\New_ { + return new Expr\New_( + BuilderHelpers::normalizeNameOrExpr($class), + $this->args($args) + ); + } + + /** + * Creates a constant fetch node. + * + * @param string|Name $name Constant name + */ + public function constFetch($name): Expr\ConstFetch { + return new Expr\ConstFetch(BuilderHelpers::normalizeName($name)); + } + + /** + * Creates a property fetch node. + * + * @param Expr $var Variable holding object + * @param string|Identifier|Expr $name Property name + */ + public function propertyFetch(Expr $var, $name): Expr\PropertyFetch { + return new Expr\PropertyFetch($var, BuilderHelpers::normalizeIdentifierOrExpr($name)); + } + + /** + * Creates a class constant fetch node. + * + * @param string|Name|Expr $class Class name + * @param string|Identifier|Expr $name Constant name + */ + public function classConstFetch($class, $name): Expr\ClassConstFetch { + return new Expr\ClassConstFetch( + BuilderHelpers::normalizeNameOrExpr($class), + BuilderHelpers::normalizeIdentifierOrExpr($name) + ); + } + + /** + * Creates nested Concat nodes from a list of expressions. + * + * @param Expr|string ...$exprs Expressions or literal strings + */ + public function concat(...$exprs): Concat { + $numExprs = count($exprs); + if ($numExprs < 2) { + throw new \LogicException('Expected at least two expressions'); + } + + $lastConcat = $this->normalizeStringExpr($exprs[0]); + for ($i = 1; $i < $numExprs; $i++) { + $lastConcat = new Concat($lastConcat, $this->normalizeStringExpr($exprs[$i])); + } + return $lastConcat; + } + + /** + * @param string|Expr $expr + */ + private function normalizeStringExpr($expr): Expr { + if ($expr instanceof Expr) { + return $expr; + } + + if (\is_string($expr)) { + return new String_($expr); + } + + throw new \LogicException('Expected string or Expr'); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/BuilderHelpers.php b/vendor/nikic/php-parser/lib/PhpParser/BuilderHelpers.php new file mode 100644 index 00000000..3e41b26f --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/BuilderHelpers.php @@ -0,0 +1,333 @@ +getNode(); + } + + if ($node instanceof Node) { + return $node; + } + + throw new \LogicException('Expected node or builder object'); + } + + /** + * Normalizes a node to a statement. + * + * Expressions are wrapped in a Stmt\Expression node. + * + * @param Node|Builder $node The node to normalize + * + * @return Stmt The normalized statement node + */ + public static function normalizeStmt($node): Stmt { + $node = self::normalizeNode($node); + if ($node instanceof Stmt) { + return $node; + } + + if ($node instanceof Expr) { + return new Stmt\Expression($node); + } + + throw new \LogicException('Expected statement or expression node'); + } + + /** + * Normalizes strings to Identifier. + * + * @param string|Identifier $name The identifier to normalize + * + * @return Identifier The normalized identifier + */ + public static function normalizeIdentifier($name): Identifier { + if ($name instanceof Identifier) { + return $name; + } + + if (\is_string($name)) { + return new Identifier($name); + } + + throw new \LogicException('Expected string or instance of Node\Identifier'); + } + + /** + * Normalizes strings to Identifier, also allowing expressions. + * + * @param string|Identifier|Expr $name The identifier to normalize + * + * @return Identifier|Expr The normalized identifier or expression + */ + public static function normalizeIdentifierOrExpr($name) { + if ($name instanceof Identifier || $name instanceof Expr) { + return $name; + } + + if (\is_string($name)) { + return new Identifier($name); + } + + throw new \LogicException('Expected string or instance of Node\Identifier or Node\Expr'); + } + + /** + * Normalizes a name: Converts string names to Name nodes. + * + * @param Name|string $name The name to normalize + * + * @return Name The normalized name + */ + public static function normalizeName($name): Name { + if ($name instanceof Name) { + return $name; + } + + if (is_string($name)) { + if (!$name) { + throw new \LogicException('Name cannot be empty'); + } + + if ($name[0] === '\\') { + return new Name\FullyQualified(substr($name, 1)); + } + + if (0 === strpos($name, 'namespace\\')) { + return new Name\Relative(substr($name, strlen('namespace\\'))); + } + + return new Name($name); + } + + throw new \LogicException('Name must be a string or an instance of Node\Name'); + } + + /** + * Normalizes a name: Converts string names to Name nodes, while also allowing expressions. + * + * @param Expr|Name|string $name The name to normalize + * + * @return Name|Expr The normalized name or expression + */ + public static function normalizeNameOrExpr($name) { + if ($name instanceof Expr) { + return $name; + } + + if (!is_string($name) && !($name instanceof Name)) { + throw new \LogicException( + 'Name must be a string or an instance of Node\Name or Node\Expr' + ); + } + + return self::normalizeName($name); + } + + /** + * Normalizes a type: Converts plain-text type names into proper AST representation. + * + * In particular, builtin types become Identifiers, custom types become Names and nullables + * are wrapped in NullableType nodes. + * + * @param string|Name|Identifier|ComplexType $type The type to normalize + * + * @return Name|Identifier|ComplexType The normalized type + */ + public static function normalizeType($type) { + if (!is_string($type)) { + if ( + !$type instanceof Name && !$type instanceof Identifier && + !$type instanceof ComplexType + ) { + throw new \LogicException( + 'Type must be a string, or an instance of Name, Identifier or ComplexType' + ); + } + return $type; + } + + $nullable = false; + if (strlen($type) > 0 && $type[0] === '?') { + $nullable = true; + $type = substr($type, 1); + } + + $builtinTypes = [ + 'array', + 'callable', + 'bool', + 'int', + 'float', + 'string', + 'iterable', + 'void', + 'object', + 'null', + 'false', + 'mixed', + 'never', + 'true', + ]; + + $lowerType = strtolower($type); + if (in_array($lowerType, $builtinTypes)) { + $type = new Identifier($lowerType); + } else { + $type = self::normalizeName($type); + } + + $notNullableTypes = [ + 'void', 'mixed', 'never', + ]; + if ($nullable && in_array((string) $type, $notNullableTypes)) { + throw new \LogicException(sprintf('%s type cannot be nullable', $type)); + } + + return $nullable ? new NullableType($type) : $type; + } + + /** + * Normalizes a value: Converts nulls, booleans, integers, + * floats, strings and arrays into their respective nodes + * + * @param Node\Expr|bool|null|int|float|string|array $value The value to normalize + * + * @return Expr The normalized value + */ + public static function normalizeValue($value): Expr { + if ($value instanceof Node\Expr) { + return $value; + } + + if (is_null($value)) { + return new Expr\ConstFetch( + new Name('null') + ); + } + + if (is_bool($value)) { + return new Expr\ConstFetch( + new Name($value ? 'true' : 'false') + ); + } + + if (is_int($value)) { + return new Scalar\Int_($value); + } + + if (is_float($value)) { + return new Scalar\Float_($value); + } + + if (is_string($value)) { + return new Scalar\String_($value); + } + + if (is_array($value)) { + $items = []; + $lastKey = -1; + foreach ($value as $itemKey => $itemValue) { + // for consecutive, numeric keys don't generate keys + if (null !== $lastKey && ++$lastKey === $itemKey) { + $items[] = new Node\ArrayItem( + self::normalizeValue($itemValue) + ); + } else { + $lastKey = null; + $items[] = new Node\ArrayItem( + self::normalizeValue($itemValue), + self::normalizeValue($itemKey) + ); + } + } + + return new Expr\Array_($items); + } + + throw new \LogicException('Invalid value'); + } + + /** + * Normalizes a doc comment: Converts plain strings to PhpParser\Comment\Doc. + * + * @param Comment\Doc|string $docComment The doc comment to normalize + * + * @return Comment\Doc The normalized doc comment + */ + public static function normalizeDocComment($docComment): Comment\Doc { + if ($docComment instanceof Comment\Doc) { + return $docComment; + } + + if (is_string($docComment)) { + return new Comment\Doc($docComment); + } + + throw new \LogicException('Doc comment must be a string or an instance of PhpParser\Comment\Doc'); + } + + /** + * Normalizes a attribute: Converts attribute to the Attribute Group if needed. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return Node\AttributeGroup The Attribute Group + */ + public static function normalizeAttribute($attribute): Node\AttributeGroup { + if ($attribute instanceof Node\AttributeGroup) { + return $attribute; + } + + if (!($attribute instanceof Node\Attribute)) { + throw new \LogicException('Attribute must be an instance of PhpParser\Node\Attribute or PhpParser\Node\AttributeGroup'); + } + + return new Node\AttributeGroup([$attribute]); + } + + /** + * Adds a modifier and returns new modifier bitmask. + * + * @param int $modifiers Existing modifiers + * @param int $modifier Modifier to set + * + * @return int New modifiers + */ + public static function addModifier(int $modifiers, int $modifier): int { + Modifiers::verifyModifier($modifiers, $modifier); + return $modifiers | $modifier; + } + + /** + * Adds a modifier and returns new modifier bitmask. + * @return int New modifiers + */ + public static function addClassModifier(int $existingModifiers, int $modifierToSet): int { + Modifiers::verifyClassModifier($existingModifiers, $modifierToSet); + return $existingModifiers | $modifierToSet; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Comment.php b/vendor/nikic/php-parser/lib/PhpParser/Comment.php new file mode 100644 index 00000000..17dc4c73 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Comment.php @@ -0,0 +1,207 @@ +text = $text; + $this->startLine = $startLine; + $this->startFilePos = $startFilePos; + $this->startTokenPos = $startTokenPos; + $this->endLine = $endLine; + $this->endFilePos = $endFilePos; + $this->endTokenPos = $endTokenPos; + } + + /** + * Gets the comment text. + * + * @return string The comment text (including comment delimiters like /*) + */ + public function getText(): string { + return $this->text; + } + + /** + * Gets the line number the comment started on. + * + * @return int Line number (or -1 if not available) + */ + public function getStartLine(): int { + return $this->startLine; + } + + /** + * Gets the file offset the comment started on. + * + * @return int File offset (or -1 if not available) + */ + public function getStartFilePos(): int { + return $this->startFilePos; + } + + /** + * Gets the token offset the comment started on. + * + * @return int Token offset (or -1 if not available) + */ + public function getStartTokenPos(): int { + return $this->startTokenPos; + } + + /** + * Gets the line number the comment ends on. + * + * @return int Line number (or -1 if not available) + */ + public function getEndLine(): int { + return $this->endLine; + } + + /** + * Gets the file offset the comment ends on. + * + * @return int File offset (or -1 if not available) + */ + public function getEndFilePos(): int { + return $this->endFilePos; + } + + /** + * Gets the token offset the comment ends on. + * + * @return int Token offset (or -1 if not available) + */ + public function getEndTokenPos(): int { + return $this->endTokenPos; + } + + /** + * Gets the comment text. + * + * @return string The comment text (including comment delimiters like /*) + */ + public function __toString(): string { + return $this->text; + } + + /** + * Gets the reformatted comment text. + * + * "Reformatted" here means that we try to clean up the whitespace at the + * starts of the lines. This is necessary because we receive the comments + * without leading whitespace on the first line, but with leading whitespace + * on all subsequent lines. + * + * Additionally, this normalizes CRLF newlines to LF newlines. + */ + public function getReformattedText(): string { + $text = str_replace("\r\n", "\n", $this->text); + $newlinePos = strpos($text, "\n"); + if (false === $newlinePos) { + // Single line comments don't need further processing + return $text; + } + if (preg_match('(^.*(?:\n\s+\*.*)+$)', $text)) { + // Multi line comment of the type + // + // /* + // * Some text. + // * Some more text. + // */ + // + // is handled by replacing the whitespace sequences before the * by a single space + return preg_replace('(^\s+\*)m', ' *', $text); + } + if (preg_match('(^/\*\*?\s*\n)', $text) && preg_match('(\n(\s*)\*/$)', $text, $matches)) { + // Multi line comment of the type + // + // /* + // Some text. + // Some more text. + // */ + // + // is handled by removing the whitespace sequence on the line before the closing + // */ on all lines. So if the last line is " */", then " " is removed at the + // start of all lines. + return preg_replace('(^' . preg_quote($matches[1]) . ')m', '', $text); + } + if (preg_match('(^/\*\*?\s*(?!\s))', $text, $matches)) { + // Multi line comment of the type + // + // /* Some text. + // Some more text. + // Indented text. + // Even more text. */ + // + // is handled by removing the difference between the shortest whitespace prefix on all + // lines and the length of the "/* " opening sequence. + $prefixLen = $this->getShortestWhitespacePrefixLen(substr($text, $newlinePos + 1)); + $removeLen = $prefixLen - strlen($matches[0]); + return preg_replace('(^\s{' . $removeLen . '})m', '', $text); + } + + // No idea how to format this comment, so simply return as is + return $text; + } + + /** + * Get length of shortest whitespace prefix (at the start of a line). + * + * If there is a line with no prefix whitespace, 0 is a valid return value. + * + * @param string $str String to check + * @return int Length in characters. Tabs count as single characters. + */ + private function getShortestWhitespacePrefixLen(string $str): int { + $lines = explode("\n", $str); + $shortestPrefixLen = \PHP_INT_MAX; + foreach ($lines as $line) { + preg_match('(^\s*)', $line, $matches); + $prefixLen = strlen($matches[0]); + if ($prefixLen < $shortestPrefixLen) { + $shortestPrefixLen = $prefixLen; + } + } + return $shortestPrefixLen; + } + + /** + * @return array{nodeType:string, text:mixed, line:mixed, filePos:mixed} + */ + public function jsonSerialize(): array { + // Technically not a node, but we make it look like one anyway + $type = $this instanceof Comment\Doc ? 'Comment_Doc' : 'Comment'; + return [ + 'nodeType' => $type, + 'text' => $this->text, + // TODO: Rename these to include "start". + 'line' => $this->startLine, + 'filePos' => $this->startFilePos, + 'tokenPos' => $this->startTokenPos, + 'endLine' => $this->endLine, + 'endFilePos' => $this->endFilePos, + 'endTokenPos' => $this->endTokenPos, + ]; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Comment/Doc.php b/vendor/nikic/php-parser/lib/PhpParser/Comment/Doc.php new file mode 100644 index 00000000..bb3e9146 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Comment/Doc.php @@ -0,0 +1,6 @@ +fallbackEvaluator = $fallbackEvaluator ?? function (Expr $expr) { + throw new ConstExprEvaluationException( + "Expression of type {$expr->getType()} cannot be evaluated" + ); + }; + } + + /** + * Silently evaluates a constant expression into a PHP value. + * + * Thrown Errors, warnings or notices will be converted into a ConstExprEvaluationException. + * The original source of the exception is available through getPrevious(). + * + * If some part of the expression cannot be evaluated, the fallback evaluator passed to the + * constructor will be invoked. By default, if no fallback is provided, an exception of type + * ConstExprEvaluationException is thrown. + * + * See class doc comment for caveats and limitations. + * + * @param Expr $expr Constant expression to evaluate + * @return mixed Result of evaluation + * + * @throws ConstExprEvaluationException if the expression cannot be evaluated or an error occurred + */ + public function evaluateSilently(Expr $expr) { + set_error_handler(function ($num, $str, $file, $line) { + throw new \ErrorException($str, 0, $num, $file, $line); + }); + + try { + return $this->evaluate($expr); + } catch (\Throwable $e) { + if (!$e instanceof ConstExprEvaluationException) { + $e = new ConstExprEvaluationException( + "An error occurred during constant expression evaluation", 0, $e); + } + throw $e; + } finally { + restore_error_handler(); + } + } + + /** + * Directly evaluates a constant expression into a PHP value. + * + * May generate Error exceptions, warnings or notices. Use evaluateSilently() to convert these + * into a ConstExprEvaluationException. + * + * If some part of the expression cannot be evaluated, the fallback evaluator passed to the + * constructor will be invoked. By default, if no fallback is provided, an exception of type + * ConstExprEvaluationException is thrown. + * + * See class doc comment for caveats and limitations. + * + * @param Expr $expr Constant expression to evaluate + * @return mixed Result of evaluation + * + * @throws ConstExprEvaluationException if the expression cannot be evaluated + */ + public function evaluateDirectly(Expr $expr) { + return $this->evaluate($expr); + } + + /** @return mixed */ + private function evaluate(Expr $expr) { + if ($expr instanceof Scalar\Int_ + || $expr instanceof Scalar\Float_ + || $expr instanceof Scalar\String_ + ) { + return $expr->value; + } + + if ($expr instanceof Expr\Array_) { + return $this->evaluateArray($expr); + } + + // Unary operators + if ($expr instanceof Expr\UnaryPlus) { + return +$this->evaluate($expr->expr); + } + if ($expr instanceof Expr\UnaryMinus) { + return -$this->evaluate($expr->expr); + } + if ($expr instanceof Expr\BooleanNot) { + return !$this->evaluate($expr->expr); + } + if ($expr instanceof Expr\BitwiseNot) { + return ~$this->evaluate($expr->expr); + } + + if ($expr instanceof Expr\BinaryOp) { + return $this->evaluateBinaryOp($expr); + } + + if ($expr instanceof Expr\Ternary) { + return $this->evaluateTernary($expr); + } + + if ($expr instanceof Expr\ArrayDimFetch && null !== $expr->dim) { + return $this->evaluate($expr->var)[$this->evaluate($expr->dim)]; + } + + if ($expr instanceof Expr\ConstFetch) { + return $this->evaluateConstFetch($expr); + } + + return ($this->fallbackEvaluator)($expr); + } + + private function evaluateArray(Expr\Array_ $expr): array { + $array = []; + foreach ($expr->items as $item) { + if (null !== $item->key) { + $array[$this->evaluate($item->key)] = $this->evaluate($item->value); + } elseif ($item->unpack) { + $array = array_merge($array, $this->evaluate($item->value)); + } else { + $array[] = $this->evaluate($item->value); + } + } + return $array; + } + + /** @return mixed */ + private function evaluateTernary(Expr\Ternary $expr) { + if (null === $expr->if) { + return $this->evaluate($expr->cond) ?: $this->evaluate($expr->else); + } + + return $this->evaluate($expr->cond) + ? $this->evaluate($expr->if) + : $this->evaluate($expr->else); + } + + /** @return mixed */ + private function evaluateBinaryOp(Expr\BinaryOp $expr) { + if ($expr instanceof Expr\BinaryOp\Coalesce + && $expr->left instanceof Expr\ArrayDimFetch + ) { + // This needs to be special cased to respect BP_VAR_IS fetch semantics + return $this->evaluate($expr->left->var)[$this->evaluate($expr->left->dim)] + ?? $this->evaluate($expr->right); + } + + // The evaluate() calls are repeated in each branch, because some of the operators are + // short-circuiting and evaluating the RHS in advance may be illegal in that case + $l = $expr->left; + $r = $expr->right; + switch ($expr->getOperatorSigil()) { + case '&': return $this->evaluate($l) & $this->evaluate($r); + case '|': return $this->evaluate($l) | $this->evaluate($r); + case '^': return $this->evaluate($l) ^ $this->evaluate($r); + case '&&': return $this->evaluate($l) && $this->evaluate($r); + case '||': return $this->evaluate($l) || $this->evaluate($r); + case '??': return $this->evaluate($l) ?? $this->evaluate($r); + case '.': return $this->evaluate($l) . $this->evaluate($r); + case '/': return $this->evaluate($l) / $this->evaluate($r); + case '==': return $this->evaluate($l) == $this->evaluate($r); + case '>': return $this->evaluate($l) > $this->evaluate($r); + case '>=': return $this->evaluate($l) >= $this->evaluate($r); + case '===': return $this->evaluate($l) === $this->evaluate($r); + case 'and': return $this->evaluate($l) and $this->evaluate($r); + case 'or': return $this->evaluate($l) or $this->evaluate($r); + case 'xor': return $this->evaluate($l) xor $this->evaluate($r); + case '-': return $this->evaluate($l) - $this->evaluate($r); + case '%': return $this->evaluate($l) % $this->evaluate($r); + case '*': return $this->evaluate($l) * $this->evaluate($r); + case '!=': return $this->evaluate($l) != $this->evaluate($r); + case '!==': return $this->evaluate($l) !== $this->evaluate($r); + case '+': return $this->evaluate($l) + $this->evaluate($r); + case '**': return $this->evaluate($l) ** $this->evaluate($r); + case '<<': return $this->evaluate($l) << $this->evaluate($r); + case '>>': return $this->evaluate($l) >> $this->evaluate($r); + case '<': return $this->evaluate($l) < $this->evaluate($r); + case '<=': return $this->evaluate($l) <= $this->evaluate($r); + case '<=>': return $this->evaluate($l) <=> $this->evaluate($r); + } + + throw new \Exception('Should not happen'); + } + + /** @return mixed */ + private function evaluateConstFetch(Expr\ConstFetch $expr) { + $name = $expr->name->toLowerString(); + switch ($name) { + case 'null': return null; + case 'false': return false; + case 'true': return true; + } + + return ($this->fallbackEvaluator)($expr); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Error.php b/vendor/nikic/php-parser/lib/PhpParser/Error.php new file mode 100644 index 00000000..02feace0 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Error.php @@ -0,0 +1,171 @@ + */ + protected array $attributes; + + /** + * Creates an Exception signifying a parse error. + * + * @param string $message Error message + * @param array $attributes Attributes of node/token where error occurred + */ + public function __construct(string $message, array $attributes = []) { + $this->rawMessage = $message; + $this->attributes = $attributes; + $this->updateMessage(); + } + + /** + * Gets the error message + * + * @return string Error message + */ + public function getRawMessage(): string { + return $this->rawMessage; + } + + /** + * Gets the line the error starts in. + * + * @return int Error start line + */ + public function getStartLine(): int { + return $this->attributes['startLine'] ?? -1; + } + + /** + * Gets the line the error ends in. + * + * @return int Error end line + */ + public function getEndLine(): int { + return $this->attributes['endLine'] ?? -1; + } + + /** + * Gets the attributes of the node/token the error occurred at. + * + * @return array + */ + public function getAttributes(): array { + return $this->attributes; + } + + /** + * Sets the attributes of the node/token the error occurred at. + * + * @param array $attributes + */ + public function setAttributes(array $attributes): void { + $this->attributes = $attributes; + $this->updateMessage(); + } + + /** + * Sets the line of the PHP file the error occurred in. + * + * @param string $message Error message + */ + public function setRawMessage(string $message): void { + $this->rawMessage = $message; + $this->updateMessage(); + } + + /** + * Sets the line the error starts in. + * + * @param int $line Error start line + */ + public function setStartLine(int $line): void { + $this->attributes['startLine'] = $line; + $this->updateMessage(); + } + + /** + * Returns whether the error has start and end column information. + * + * For column information enable the startFilePos and endFilePos in the lexer options. + */ + public function hasColumnInfo(): bool { + return isset($this->attributes['startFilePos'], $this->attributes['endFilePos']); + } + + /** + * Gets the start column (1-based) into the line where the error started. + * + * @param string $code Source code of the file + */ + public function getStartColumn(string $code): int { + if (!$this->hasColumnInfo()) { + throw new \RuntimeException('Error does not have column information'); + } + + return $this->toColumn($code, $this->attributes['startFilePos']); + } + + /** + * Gets the end column (1-based) into the line where the error ended. + * + * @param string $code Source code of the file + */ + public function getEndColumn(string $code): int { + if (!$this->hasColumnInfo()) { + throw new \RuntimeException('Error does not have column information'); + } + + return $this->toColumn($code, $this->attributes['endFilePos']); + } + + /** + * Formats message including line and column information. + * + * @param string $code Source code associated with the error, for calculation of the columns + * + * @return string Formatted message + */ + public function getMessageWithColumnInfo(string $code): string { + return sprintf( + '%s from %d:%d to %d:%d', $this->getRawMessage(), + $this->getStartLine(), $this->getStartColumn($code), + $this->getEndLine(), $this->getEndColumn($code) + ); + } + + /** + * Converts a file offset into a column. + * + * @param string $code Source code that $pos indexes into + * @param int $pos 0-based position in $code + * + * @return int 1-based column (relative to start of line) + */ + private function toColumn(string $code, int $pos): int { + if ($pos > strlen($code)) { + throw new \RuntimeException('Invalid position information'); + } + + $lineStartPos = strrpos($code, "\n", $pos - strlen($code)); + if (false === $lineStartPos) { + $lineStartPos = -1; + } + + return $pos - $lineStartPos; + } + + /** + * Updates the exception message after a change to rawMessage or rawLine. + */ + protected function updateMessage(): void { + $this->message = $this->rawMessage; + + if (-1 === $this->getStartLine()) { + $this->message .= ' on unknown line'; + } else { + $this->message .= ' on line ' . $this->getStartLine(); + } + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/ErrorHandler.php b/vendor/nikic/php-parser/lib/PhpParser/ErrorHandler.php new file mode 100644 index 00000000..51ad730c --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/ErrorHandler.php @@ -0,0 +1,12 @@ +errors[] = $error; + } + + /** + * Get collected errors. + * + * @return Error[] + */ + public function getErrors(): array { + return $this->errors; + } + + /** + * Check whether there are any errors. + */ + public function hasErrors(): bool { + return !empty($this->errors); + } + + /** + * Reset/clear collected errors. + */ + public function clearErrors(): void { + $this->errors = []; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/ErrorHandler/Throwing.php b/vendor/nikic/php-parser/lib/PhpParser/ErrorHandler/Throwing.php new file mode 100644 index 00000000..dff33dd0 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/ErrorHandler/Throwing.php @@ -0,0 +1,17 @@ +type = $type; + $this->old = $old; + $this->new = $new; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Internal/Differ.php b/vendor/nikic/php-parser/lib/PhpParser/Internal/Differ.php new file mode 100644 index 00000000..253e1757 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Internal/Differ.php @@ -0,0 +1,178 @@ +isEqual = $isEqual; + } + + /** + * Calculate diff (edit script) from $old to $new. + * + * @param T[] $old Original array + * @param T[] $new New array + * + * @return DiffElem[] Diff (edit script) + */ + public function diff(array $old, array $new): array { + $old = \array_values($old); + $new = \array_values($new); + list($trace, $x, $y) = $this->calculateTrace($old, $new); + return $this->extractDiff($trace, $x, $y, $old, $new); + } + + /** + * Calculate diff, including "replace" operations. + * + * If a sequence of remove operations is followed by the same number of add operations, these + * will be coalesced into replace operations. + * + * @param T[] $old Original array + * @param T[] $new New array + * + * @return DiffElem[] Diff (edit script), including replace operations + */ + public function diffWithReplacements(array $old, array $new): array { + return $this->coalesceReplacements($this->diff($old, $new)); + } + + /** + * @param T[] $old + * @param T[] $new + * @return array{array>, int, int} + */ + private function calculateTrace(array $old, array $new): array { + $n = \count($old); + $m = \count($new); + $max = $n + $m; + $v = [1 => 0]; + $trace = []; + for ($d = 0; $d <= $max; $d++) { + $trace[] = $v; + for ($k = -$d; $k <= $d; $k += 2) { + if ($k === -$d || ($k !== $d && $v[$k - 1] < $v[$k + 1])) { + $x = $v[$k + 1]; + } else { + $x = $v[$k - 1] + 1; + } + + $y = $x - $k; + while ($x < $n && $y < $m && ($this->isEqual)($old[$x], $new[$y])) { + $x++; + $y++; + } + + $v[$k] = $x; + if ($x >= $n && $y >= $m) { + return [$trace, $x, $y]; + } + } + } + throw new \Exception('Should not happen'); + } + + /** + * @param array> $trace + * @param T[] $old + * @param T[] $new + * @return DiffElem[] + */ + private function extractDiff(array $trace, int $x, int $y, array $old, array $new): array { + $result = []; + for ($d = \count($trace) - 1; $d >= 0; $d--) { + $v = $trace[$d]; + $k = $x - $y; + + if ($k === -$d || ($k !== $d && $v[$k - 1] < $v[$k + 1])) { + $prevK = $k + 1; + } else { + $prevK = $k - 1; + } + + $prevX = $v[$prevK]; + $prevY = $prevX - $prevK; + + while ($x > $prevX && $y > $prevY) { + $result[] = new DiffElem(DiffElem::TYPE_KEEP, $old[$x - 1], $new[$y - 1]); + $x--; + $y--; + } + + if ($d === 0) { + break; + } + + while ($x > $prevX) { + $result[] = new DiffElem(DiffElem::TYPE_REMOVE, $old[$x - 1], null); + $x--; + } + + while ($y > $prevY) { + $result[] = new DiffElem(DiffElem::TYPE_ADD, null, $new[$y - 1]); + $y--; + } + } + return array_reverse($result); + } + + /** + * Coalesce equal-length sequences of remove+add into a replace operation. + * + * @param DiffElem[] $diff + * @return DiffElem[] + */ + private function coalesceReplacements(array $diff): array { + $newDiff = []; + $c = \count($diff); + for ($i = 0; $i < $c; $i++) { + $diffType = $diff[$i]->type; + if ($diffType !== DiffElem::TYPE_REMOVE) { + $newDiff[] = $diff[$i]; + continue; + } + + $j = $i; + while ($j < $c && $diff[$j]->type === DiffElem::TYPE_REMOVE) { + $j++; + } + + $k = $j; + while ($k < $c && $diff[$k]->type === DiffElem::TYPE_ADD) { + $k++; + } + + if ($j - $i === $k - $j) { + $len = $j - $i; + for ($n = 0; $n < $len; $n++) { + $newDiff[] = new DiffElem( + DiffElem::TYPE_REPLACE, $diff[$i + $n]->old, $diff[$j + $n]->new + ); + } + } else { + for (; $i < $k; $i++) { + $newDiff[] = $diff[$i]; + } + } + $i = $k - 1; + } + return $newDiff; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Internal/PrintableNewAnonClassNode.php b/vendor/nikic/php-parser/lib/PhpParser/Internal/PrintableNewAnonClassNode.php new file mode 100644 index 00000000..b30a99a1 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Internal/PrintableNewAnonClassNode.php @@ -0,0 +1,71 @@ + $attributes Attributes + */ + public function __construct( + array $attrGroups, int $flags, array $args, ?Node\Name $extends, array $implements, + array $stmts, array $attributes + ) { + parent::__construct($attributes); + $this->attrGroups = $attrGroups; + $this->flags = $flags; + $this->args = $args; + $this->extends = $extends; + $this->implements = $implements; + $this->stmts = $stmts; + } + + public static function fromNewNode(Expr\New_ $newNode): self { + $class = $newNode->class; + assert($class instanceof Node\Stmt\Class_); + // We don't assert that $class->name is null here, to allow consumers to assign unique names + // to anonymous classes for their own purposes. We simplify ignore the name here. + return new self( + $class->attrGroups, $class->flags, $newNode->args, $class->extends, $class->implements, + $class->stmts, $newNode->getAttributes() + ); + } + + public function getType(): string { + return 'Expr_PrintableNewAnonClass'; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'flags', 'args', 'extends', 'implements', 'stmts']; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Internal/TokenPolyfill.php b/vendor/nikic/php-parser/lib/PhpParser/Internal/TokenPolyfill.php new file mode 100644 index 00000000..36022d09 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Internal/TokenPolyfill.php @@ -0,0 +1,237 @@ += 80000) { + class TokenPolyfill extends \PhpToken { + } + return; +} + +/** + * This is a polyfill for the PhpToken class introduced in PHP 8.0. We do not actually polyfill + * PhpToken, because composer might end up picking a different polyfill implementation, which does + * not meet our requirements. + * + * @internal + */ +class TokenPolyfill { + /** @var int The ID of the token. Either a T_* constant of a character code < 256. */ + public int $id; + /** @var string The textual content of the token. */ + public string $text; + /** @var int The 1-based starting line of the token (or -1 if unknown). */ + public int $line; + /** @var int The 0-based starting position of the token (or -1 if unknown). */ + public int $pos; + + /** @var array Tokens ignored by the PHP parser. */ + private const IGNORABLE_TOKENS = [ + \T_WHITESPACE => true, + \T_COMMENT => true, + \T_DOC_COMMENT => true, + \T_OPEN_TAG => true, + ]; + + /** @var array Tokens that may be part of a T_NAME_* identifier. */ + private static array $identifierTokens; + + /** + * Create a Token with the given ID and text, as well optional line and position information. + */ + final public function __construct(int $id, string $text, int $line = -1, int $pos = -1) { + $this->id = $id; + $this->text = $text; + $this->line = $line; + $this->pos = $pos; + } + + /** + * Get the name of the token. For single-char tokens this will be the token character. + * Otherwise it will be a T_* style name, or null if the token ID is unknown. + */ + public function getTokenName(): ?string { + if ($this->id < 256) { + return \chr($this->id); + } + + $name = token_name($this->id); + return $name === 'UNKNOWN' ? null : $name; + } + + /** + * Check whether the token is of the given kind. The kind may be either an integer that matches + * the token ID, a string that matches the token text, or an array of integers/strings. In the + * latter case, the function returns true if any of the kinds in the array match. + * + * @param int|string|(int|string)[] $kind + */ + public function is($kind): bool { + if (\is_int($kind)) { + return $this->id === $kind; + } + if (\is_string($kind)) { + return $this->text === $kind; + } + if (\is_array($kind)) { + foreach ($kind as $entry) { + if (\is_int($entry)) { + if ($this->id === $entry) { + return true; + } + } elseif (\is_string($entry)) { + if ($this->text === $entry) { + return true; + } + } else { + throw new \TypeError( + 'Argument #1 ($kind) must only have elements of type string|int, ' . + gettype($entry) . ' given'); + } + } + return false; + } + throw new \TypeError( + 'Argument #1 ($kind) must be of type string|int|array, ' .gettype($kind) . ' given'); + } + + /** + * Check whether this token would be ignored by the PHP parser. Returns true for T_WHITESPACE, + * T_COMMENT, T_DOC_COMMENT and T_OPEN_TAG, and false for everything else. + */ + public function isIgnorable(): bool { + return isset(self::IGNORABLE_TOKENS[$this->id]); + } + + /** + * Return the textual content of the token. + */ + public function __toString(): string { + return $this->text; + } + + /** + * Tokenize the given source code and return an array of tokens. + * + * This performs certain canonicalizations to match the PHP 8.0 token format: + * * Bad characters are represented using T_BAD_CHARACTER rather than omitted. + * * T_COMMENT does not include trailing newlines, instead the newline is part of a following + * T_WHITESPACE token. + * * Namespaced names are represented using T_NAME_* tokens. + * + * @return static[] + */ + public static function tokenize(string $code, int $flags = 0): array { + self::init(); + + $tokens = []; + $line = 1; + $pos = 0; + $origTokens = \token_get_all($code, $flags); + + $numTokens = \count($origTokens); + for ($i = 0; $i < $numTokens; $i++) { + $token = $origTokens[$i]; + if (\is_string($token)) { + if (\strlen($token) === 2) { + // b" and B" are tokenized as single-char tokens, even though they aren't. + $tokens[] = new static(\ord('"'), $token, $line, $pos); + $pos += 2; + } else { + $tokens[] = new static(\ord($token), $token, $line, $pos); + $pos++; + } + } else { + $id = $token[0]; + $text = $token[1]; + + // Emulate PHP 8.0 comment format, which does not include trailing whitespace anymore. + if ($id === \T_COMMENT && \substr($text, 0, 2) !== '/*' && + \preg_match('/(\r\n|\n|\r)$/D', $text, $matches) + ) { + $trailingNewline = $matches[0]; + $text = \substr($text, 0, -\strlen($trailingNewline)); + $tokens[] = new static($id, $text, $line, $pos); + $pos += \strlen($text); + + if ($i + 1 < $numTokens && $origTokens[$i + 1][0] === \T_WHITESPACE) { + // Move trailing newline into following T_WHITESPACE token, if it already exists. + $origTokens[$i + 1][1] = $trailingNewline . $origTokens[$i + 1][1]; + $origTokens[$i + 1][2]--; + } else { + // Otherwise, we need to create a new T_WHITESPACE token. + $tokens[] = new static(\T_WHITESPACE, $trailingNewline, $line, $pos); + $line++; + $pos += \strlen($trailingNewline); + } + continue; + } + + // Emulate PHP 8.0 T_NAME_* tokens, by combining sequences of T_NS_SEPARATOR and + // T_STRING into a single token. + if (($id === \T_NS_SEPARATOR || isset(self::$identifierTokens[$id]))) { + $newText = $text; + $lastWasSeparator = $id === \T_NS_SEPARATOR; + for ($j = $i + 1; $j < $numTokens; $j++) { + if ($lastWasSeparator) { + if (!isset(self::$identifierTokens[$origTokens[$j][0]])) { + break; + } + $lastWasSeparator = false; + } else { + if ($origTokens[$j][0] !== \T_NS_SEPARATOR) { + break; + } + $lastWasSeparator = true; + } + $newText .= $origTokens[$j][1]; + } + if ($lastWasSeparator) { + // Trailing separator is not part of the name. + $j--; + $newText = \substr($newText, 0, -1); + } + if ($j > $i + 1) { + if ($id === \T_NS_SEPARATOR) { + $id = \T_NAME_FULLY_QUALIFIED; + } elseif ($id === \T_NAMESPACE) { + $id = \T_NAME_RELATIVE; + } else { + $id = \T_NAME_QUALIFIED; + } + $tokens[] = new static($id, $newText, $line, $pos); + $pos += \strlen($newText); + $i = $j - 1; + continue; + } + } + + $tokens[] = new static($id, $text, $line, $pos); + $line += \substr_count($text, "\n"); + $pos += \strlen($text); + } + } + return $tokens; + } + + /** Initialize private static state needed by tokenize(). */ + private static function init(): void { + if (isset(self::$identifierTokens)) { + return; + } + + // Based on semi_reserved production. + self::$identifierTokens = \array_fill_keys([ + \T_STRING, + \T_STATIC, \T_ABSTRACT, \T_FINAL, \T_PRIVATE, \T_PROTECTED, \T_PUBLIC, \T_READONLY, + \T_INCLUDE, \T_INCLUDE_ONCE, \T_EVAL, \T_REQUIRE, \T_REQUIRE_ONCE, \T_LOGICAL_OR, \T_LOGICAL_XOR, \T_LOGICAL_AND, + \T_INSTANCEOF, \T_NEW, \T_CLONE, \T_EXIT, \T_IF, \T_ELSEIF, \T_ELSE, \T_ENDIF, \T_ECHO, \T_DO, \T_WHILE, + \T_ENDWHILE, \T_FOR, \T_ENDFOR, \T_FOREACH, \T_ENDFOREACH, \T_DECLARE, \T_ENDDECLARE, \T_AS, \T_TRY, \T_CATCH, + \T_FINALLY, \T_THROW, \T_USE, \T_INSTEADOF, \T_GLOBAL, \T_VAR, \T_UNSET, \T_ISSET, \T_EMPTY, \T_CONTINUE, \T_GOTO, + \T_FUNCTION, \T_CONST, \T_RETURN, \T_PRINT, \T_YIELD, \T_LIST, \T_SWITCH, \T_ENDSWITCH, \T_CASE, \T_DEFAULT, + \T_BREAK, \T_ARRAY, \T_CALLABLE, \T_EXTENDS, \T_IMPLEMENTS, \T_NAMESPACE, \T_TRAIT, \T_INTERFACE, \T_CLASS, + \T_CLASS_C, \T_TRAIT_C, \T_FUNC_C, \T_METHOD_C, \T_LINE, \T_FILE, \T_DIR, \T_NS_C, \T_HALT_COMPILER, \T_FN, + \T_MATCH, + ], true); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Internal/TokenStream.php b/vendor/nikic/php-parser/lib/PhpParser/Internal/TokenStream.php new file mode 100644 index 00000000..c02844ac --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Internal/TokenStream.php @@ -0,0 +1,275 @@ +tokens = $tokens; + $this->indentMap = $this->calcIndentMap(); + } + + /** + * Whether the given position is immediately surrounded by parenthesis. + * + * @param int $startPos Start position + * @param int $endPos End position + */ + public function haveParens(int $startPos, int $endPos): bool { + return $this->haveTokenImmediatelyBefore($startPos, '(') + && $this->haveTokenImmediatelyAfter($endPos, ')'); + } + + /** + * Whether the given position is immediately surrounded by braces. + * + * @param int $startPos Start position + * @param int $endPos End position + */ + public function haveBraces(int $startPos, int $endPos): bool { + return ($this->haveTokenImmediatelyBefore($startPos, '{') + || $this->haveTokenImmediatelyBefore($startPos, T_CURLY_OPEN)) + && $this->haveTokenImmediatelyAfter($endPos, '}'); + } + + /** + * Check whether the position is directly preceded by a certain token type. + * + * During this check whitespace and comments are skipped. + * + * @param int $pos Position before which the token should occur + * @param int|string $expectedTokenType Token to check for + * + * @return bool Whether the expected token was found + */ + public function haveTokenImmediatelyBefore(int $pos, $expectedTokenType): bool { + $tokens = $this->tokens; + $pos--; + for (; $pos >= 0; $pos--) { + $token = $tokens[$pos]; + if ($token->is($expectedTokenType)) { + return true; + } + if (!$token->isIgnorable()) { + break; + } + } + return false; + } + + /** + * Check whether the position is directly followed by a certain token type. + * + * During this check whitespace and comments are skipped. + * + * @param int $pos Position after which the token should occur + * @param int|string $expectedTokenType Token to check for + * + * @return bool Whether the expected token was found + */ + public function haveTokenImmediatelyAfter(int $pos, $expectedTokenType): bool { + $tokens = $this->tokens; + $pos++; + for ($c = \count($tokens); $pos < $c; $pos++) { + $token = $tokens[$pos]; + if ($token->is($expectedTokenType)) { + return true; + } + if (!$token->isIgnorable()) { + break; + } + } + return false; + } + + /** @param int|string|(int|string)[] $skipTokenType */ + public function skipLeft(int $pos, $skipTokenType): int { + $tokens = $this->tokens; + + $pos = $this->skipLeftWhitespace($pos); + if ($skipTokenType === \T_WHITESPACE) { + return $pos; + } + + if (!$tokens[$pos]->is($skipTokenType)) { + // Shouldn't happen. The skip token MUST be there + throw new \Exception('Encountered unexpected token'); + } + $pos--; + + return $this->skipLeftWhitespace($pos); + } + + /** @param int|string|(int|string)[] $skipTokenType */ + public function skipRight(int $pos, $skipTokenType): int { + $tokens = $this->tokens; + + $pos = $this->skipRightWhitespace($pos); + if ($skipTokenType === \T_WHITESPACE) { + return $pos; + } + + if (!$tokens[$pos]->is($skipTokenType)) { + // Shouldn't happen. The skip token MUST be there + throw new \Exception('Encountered unexpected token'); + } + $pos++; + + return $this->skipRightWhitespace($pos); + } + + /** + * Return first non-whitespace token position smaller or equal to passed position. + * + * @param int $pos Token position + * @return int Non-whitespace token position + */ + public function skipLeftWhitespace(int $pos): int { + $tokens = $this->tokens; + for (; $pos >= 0; $pos--) { + if (!$tokens[$pos]->isIgnorable()) { + break; + } + } + return $pos; + } + + /** + * Return first non-whitespace position greater or equal to passed position. + * + * @param int $pos Token position + * @return int Non-whitespace token position + */ + public function skipRightWhitespace(int $pos): int { + $tokens = $this->tokens; + for ($count = \count($tokens); $pos < $count; $pos++) { + if (!$tokens[$pos]->isIgnorable()) { + break; + } + } + return $pos; + } + + /** @param int|string|(int|string)[] $findTokenType */ + public function findRight(int $pos, $findTokenType): int { + $tokens = $this->tokens; + for ($count = \count($tokens); $pos < $count; $pos++) { + if ($tokens[$pos]->is($findTokenType)) { + return $pos; + } + } + return -1; + } + + /** + * Whether the given position range contains a certain token type. + * + * @param int $startPos Starting position (inclusive) + * @param int $endPos Ending position (exclusive) + * @param int|string $tokenType Token type to look for + * @return bool Whether the token occurs in the given range + */ + public function haveTokenInRange(int $startPos, int $endPos, $tokenType): bool { + $tokens = $this->tokens; + for ($pos = $startPos; $pos < $endPos; $pos++) { + if ($tokens[$pos]->is($tokenType)) { + return true; + } + } + return false; + } + + public function haveTagInRange(int $startPos, int $endPos): bool { + return $this->haveTokenInRange($startPos, $endPos, \T_OPEN_TAG) + || $this->haveTokenInRange($startPos, $endPos, \T_CLOSE_TAG); + } + + /** + * Get indentation before token position. + * + * @param int $pos Token position + * + * @return int Indentation depth (in spaces) + */ + public function getIndentationBefore(int $pos): int { + return $this->indentMap[$pos]; + } + + /** + * Get the code corresponding to a token offset range, optionally adjusted for indentation. + * + * @param int $from Token start position (inclusive) + * @param int $to Token end position (exclusive) + * @param int $indent By how much the code should be indented (can be negative as well) + * + * @return string Code corresponding to token range, adjusted for indentation + */ + public function getTokenCode(int $from, int $to, int $indent): string { + $tokens = $this->tokens; + $result = ''; + for ($pos = $from; $pos < $to; $pos++) { + $token = $tokens[$pos]; + $id = $token->id; + $text = $token->text; + if ($id === \T_CONSTANT_ENCAPSED_STRING || $id === \T_ENCAPSED_AND_WHITESPACE) { + $result .= $text; + } else { + // TODO Handle non-space indentation + if ($indent < 0) { + $result .= str_replace("\n" . str_repeat(" ", -$indent), "\n", $text); + } elseif ($indent > 0) { + $result .= str_replace("\n", "\n" . str_repeat(" ", $indent), $text); + } else { + $result .= $text; + } + } + } + return $result; + } + + /** + * Precalculate the indentation at every token position. + * + * @return int[] Token position to indentation map + */ + private function calcIndentMap(): array { + $indentMap = []; + $indent = 0; + foreach ($this->tokens as $i => $token) { + $indentMap[] = $indent; + + if ($token->id === \T_WHITESPACE) { + $content = $token->text; + $newlinePos = \strrpos($content, "\n"); + if (false !== $newlinePos) { + $indent = \strlen($content) - $newlinePos - 1; + } elseif ($i === 1 && $this->tokens[0]->id === \T_OPEN_TAG && + $this->tokens[0]->text[\strlen($this->tokens[0]->text) - 1] === "\n") { + // Special case: Newline at the end of opening tag followed by whitespace. + $indent = \strlen($content); + } + } + } + + // Add a sentinel for one past end of the file + $indentMap[] = $indent; + + return $indentMap; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/JsonDecoder.php b/vendor/nikic/php-parser/lib/PhpParser/JsonDecoder.php new file mode 100644 index 00000000..7be41426 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/JsonDecoder.php @@ -0,0 +1,108 @@ +[] Node type to reflection class map */ + private array $reflectionClassCache; + + /** @return mixed */ + public function decode(string $json) { + $value = json_decode($json, true); + if (json_last_error()) { + throw new \RuntimeException('JSON decoding error: ' . json_last_error_msg()); + } + + return $this->decodeRecursive($value); + } + + /** + * @param mixed $value + * @return mixed + */ + private function decodeRecursive($value) { + if (\is_array($value)) { + if (isset($value['nodeType'])) { + if ($value['nodeType'] === 'Comment' || $value['nodeType'] === 'Comment_Doc') { + return $this->decodeComment($value); + } + return $this->decodeNode($value); + } + return $this->decodeArray($value); + } + return $value; + } + + private function decodeArray(array $array): array { + $decodedArray = []; + foreach ($array as $key => $value) { + $decodedArray[$key] = $this->decodeRecursive($value); + } + return $decodedArray; + } + + private function decodeNode(array $value): Node { + $nodeType = $value['nodeType']; + if (!\is_string($nodeType)) { + throw new \RuntimeException('Node type must be a string'); + } + + $reflectionClass = $this->reflectionClassFromNodeType($nodeType); + $node = $reflectionClass->newInstanceWithoutConstructor(); + + if (isset($value['attributes'])) { + if (!\is_array($value['attributes'])) { + throw new \RuntimeException('Attributes must be an array'); + } + + $node->setAttributes($this->decodeArray($value['attributes'])); + } + + foreach ($value as $name => $subNode) { + if ($name === 'nodeType' || $name === 'attributes') { + continue; + } + + $node->$name = $this->decodeRecursive($subNode); + } + + return $node; + } + + private function decodeComment(array $value): Comment { + $className = $value['nodeType'] === 'Comment' ? Comment::class : Comment\Doc::class; + if (!isset($value['text'])) { + throw new \RuntimeException('Comment must have text'); + } + + return new $className( + $value['text'], + $value['line'] ?? -1, $value['filePos'] ?? -1, $value['tokenPos'] ?? -1, + $value['endLine'] ?? -1, $value['endFilePos'] ?? -1, $value['endTokenPos'] ?? -1 + ); + } + + /** @return \ReflectionClass */ + private function reflectionClassFromNodeType(string $nodeType): \ReflectionClass { + if (!isset($this->reflectionClassCache[$nodeType])) { + $className = $this->classNameFromNodeType($nodeType); + $this->reflectionClassCache[$nodeType] = new \ReflectionClass($className); + } + return $this->reflectionClassCache[$nodeType]; + } + + /** @return class-string */ + private function classNameFromNodeType(string $nodeType): string { + $className = 'PhpParser\\Node\\' . strtr($nodeType, '_', '\\'); + if (class_exists($className)) { + return $className; + } + + $className .= '_'; + if (class_exists($className)) { + return $className; + } + + throw new \RuntimeException("Unknown node type \"$nodeType\""); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer.php new file mode 100644 index 00000000..5e2ece96 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer.php @@ -0,0 +1,116 @@ +postprocessTokens($tokens, $errorHandler); + + if (false !== $scream) { + ini_set('xdebug.scream', $scream); + } + + return $tokens; + } + + private function handleInvalidCharacter(Token $token, ErrorHandler $errorHandler): void { + $chr = $token->text; + if ($chr === "\0") { + // PHP cuts error message after null byte, so need special case + $errorMsg = 'Unexpected null byte'; + } else { + $errorMsg = sprintf( + 'Unexpected character "%s" (ASCII %d)', $chr, ord($chr) + ); + } + + $errorHandler->handleError(new Error($errorMsg, [ + 'startLine' => $token->line, + 'endLine' => $token->line, + 'startFilePos' => $token->pos, + 'endFilePos' => $token->pos, + ])); + } + + private function isUnterminatedComment(Token $token): bool { + return $token->is([\T_COMMENT, \T_DOC_COMMENT]) + && substr($token->text, 0, 2) === '/*' + && substr($token->text, -2) !== '*/'; + } + + /** + * @param list $tokens + */ + protected function postprocessTokens(array &$tokens, ErrorHandler $errorHandler): void { + // This function reports errors (bad characters and unterminated comments) in the token + // array, and performs certain canonicalizations: + // * Use PHP 8.1 T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG and + // T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG tokens used to disambiguate intersection types. + // * Add a sentinel token with ID 0. + + $numTokens = \count($tokens); + if ($numTokens === 0) { + // Empty input edge case: Just add the sentinel token. + $tokens[] = new Token(0, "\0", 1, 0); + return; + } + + for ($i = 0; $i < $numTokens; $i++) { + $token = $tokens[$i]; + if ($token->id === \T_BAD_CHARACTER) { + $this->handleInvalidCharacter($token, $errorHandler); + } + + if ($token->id === \ord('&')) { + $next = $i + 1; + while (isset($tokens[$next]) && $tokens[$next]->id === \T_WHITESPACE) { + $next++; + } + $followedByVarOrVarArg = isset($tokens[$next]) && + $tokens[$next]->is([\T_VARIABLE, \T_ELLIPSIS]); + $token->id = $followedByVarOrVarArg + ? \T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG + : \T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG; + } + } + + // Check for unterminated comment + $lastToken = $tokens[$numTokens - 1]; + if ($this->isUnterminatedComment($lastToken)) { + $errorHandler->handleError(new Error('Unterminated comment', [ + 'startLine' => $lastToken->line, + 'endLine' => $lastToken->getEndLine(), + 'startFilePos' => $lastToken->pos, + 'endFilePos' => $lastToken->getEndPos(), + ])); + } + + // Add sentinel token. + $tokens[] = new Token(0, "\0", $lastToken->getEndLine(), $lastToken->getEndPos()); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php new file mode 100644 index 00000000..934954cd --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php @@ -0,0 +1,226 @@ + */ + private array $emulators = []; + + private PhpVersion $targetPhpVersion; + + private PhpVersion $hostPhpVersion; + + /** + * @param PhpVersion|null $phpVersion PHP version to emulate. Defaults to newest supported. + */ + public function __construct(?PhpVersion $phpVersion = null) { + $this->targetPhpVersion = $phpVersion ?? PhpVersion::getNewestSupported(); + $this->hostPhpVersion = PhpVersion::getHostVersion(); + + $emulators = [ + new MatchTokenEmulator(), + new NullsafeTokenEmulator(), + new AttributeEmulator(), + new EnumTokenEmulator(), + new ReadonlyTokenEmulator(), + new ExplicitOctalEmulator(), + new ReadonlyFunctionTokenEmulator(), + ]; + + // Collect emulators that are relevant for the PHP version we're running + // and the PHP version we're targeting for emulation. + foreach ($emulators as $emulator) { + $emulatorPhpVersion = $emulator->getPhpVersion(); + if ($this->isForwardEmulationNeeded($emulatorPhpVersion)) { + $this->emulators[] = $emulator; + } elseif ($this->isReverseEmulationNeeded($emulatorPhpVersion)) { + $this->emulators[] = new ReverseEmulator($emulator); + } + } + } + + public function tokenize(string $code, ?ErrorHandler $errorHandler = null): array { + $emulators = array_filter($this->emulators, function ($emulator) use ($code) { + return $emulator->isEmulationNeeded($code); + }); + + if (empty($emulators)) { + // Nothing to emulate, yay + return parent::tokenize($code, $errorHandler); + } + + if ($errorHandler === null) { + $errorHandler = new ErrorHandler\Throwing(); + } + + $this->patches = []; + foreach ($emulators as $emulator) { + $code = $emulator->preprocessCode($code, $this->patches); + } + + $collector = new ErrorHandler\Collecting(); + $tokens = parent::tokenize($code, $collector); + $this->sortPatches(); + $tokens = $this->fixupTokens($tokens); + + $errors = $collector->getErrors(); + if (!empty($errors)) { + $this->fixupErrors($errors); + foreach ($errors as $error) { + $errorHandler->handleError($error); + } + } + + foreach ($emulators as $emulator) { + $tokens = $emulator->emulate($code, $tokens); + } + + return $tokens; + } + + private function isForwardEmulationNeeded(PhpVersion $emulatorPhpVersion): bool { + return $this->hostPhpVersion->older($emulatorPhpVersion) + && $this->targetPhpVersion->newerOrEqual($emulatorPhpVersion); + } + + private function isReverseEmulationNeeded(PhpVersion $emulatorPhpVersion): bool { + return $this->hostPhpVersion->newerOrEqual($emulatorPhpVersion) + && $this->targetPhpVersion->older($emulatorPhpVersion); + } + + private function sortPatches(): void { + // Patches may be contributed by different emulators. + // Make sure they are sorted by increasing patch position. + usort($this->patches, function ($p1, $p2) { + return $p1[0] <=> $p2[0]; + }); + } + + /** + * @param list $tokens + * @return list + */ + private function fixupTokens(array $tokens): array { + if (\count($this->patches) === 0) { + return $tokens; + } + + // Load first patch + $patchIdx = 0; + list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx]; + + // We use a manual loop over the tokens, because we modify the array on the fly + $posDelta = 0; + $lineDelta = 0; + for ($i = 0, $c = \count($tokens); $i < $c; $i++) { + $token = $tokens[$i]; + $pos = $token->pos; + $token->pos += $posDelta; + $token->line += $lineDelta; + $localPosDelta = 0; + $len = \strlen($token->text); + while ($patchPos >= $pos && $patchPos < $pos + $len) { + $patchTextLen = \strlen($patchText); + if ($patchType === 'remove') { + if ($patchPos === $pos && $patchTextLen === $len) { + // Remove token entirely + array_splice($tokens, $i, 1, []); + $i--; + $c--; + } else { + // Remove from token string + $token->text = substr_replace( + $token->text, '', $patchPos - $pos + $localPosDelta, $patchTextLen + ); + $localPosDelta -= $patchTextLen; + } + $lineDelta -= \substr_count($patchText, "\n"); + } elseif ($patchType === 'add') { + // Insert into the token string + $token->text = substr_replace( + $token->text, $patchText, $patchPos - $pos + $localPosDelta, 0 + ); + $localPosDelta += $patchTextLen; + $lineDelta += \substr_count($patchText, "\n"); + } elseif ($patchType === 'replace') { + // Replace inside the token string + $token->text = substr_replace( + $token->text, $patchText, $patchPos - $pos + $localPosDelta, $patchTextLen + ); + } else { + assert(false); + } + + // Fetch the next patch + $patchIdx++; + if ($patchIdx >= \count($this->patches)) { + // No more patches. However, we still need to adjust position. + $patchPos = \PHP_INT_MAX; + break; + } + + list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx]; + } + + $posDelta += $localPosDelta; + } + return $tokens; + } + + /** + * Fixup line and position information in errors. + * + * @param Error[] $errors + */ + private function fixupErrors(array $errors): void { + foreach ($errors as $error) { + $attrs = $error->getAttributes(); + + $posDelta = 0; + $lineDelta = 0; + foreach ($this->patches as $patch) { + list($patchPos, $patchType, $patchText) = $patch; + if ($patchPos >= $attrs['startFilePos']) { + // No longer relevant + break; + } + + if ($patchType === 'add') { + $posDelta += strlen($patchText); + $lineDelta += substr_count($patchText, "\n"); + } elseif ($patchType === 'remove') { + $posDelta -= strlen($patchText); + $lineDelta -= substr_count($patchText, "\n"); + } + } + + $attrs['startFilePos'] += $posDelta; + $attrs['endFilePos'] += $posDelta; + $attrs['startLine'] += $lineDelta; + $attrs['endLine'] += $lineDelta; + $error->setAttributes($attrs); + } + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/AttributeEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/AttributeEmulator.php new file mode 100644 index 00000000..2c12f33a --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/AttributeEmulator.php @@ -0,0 +1,49 @@ +text === '#' && isset($tokens[$i + 1]) && $tokens[$i + 1]->text === '[') { + array_splice($tokens, $i, 2, [ + new Token(\T_ATTRIBUTE, '#[', $token->line, $token->pos), + ]); + $c--; + continue; + } + } + + return $tokens; + } + + public function reverseEmulate(string $code, array $tokens): array { + // TODO + return $tokens; + } + + public function preprocessCode(string $code, array &$patches): string { + $pos = 0; + while (false !== $pos = strpos($code, '#[', $pos)) { + // Replace #[ with %[ + $code[$pos] = '%'; + $patches[] = [$pos, 'replace', '#']; + $pos += 2; + } + return $code; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/EnumTokenEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/EnumTokenEmulator.php new file mode 100644 index 00000000..5418f52c --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/EnumTokenEmulator.php @@ -0,0 +1,26 @@ +id === \T_WHITESPACE + && $tokens[$pos + 2]->id === \T_STRING; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ExplicitOctalEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ExplicitOctalEmulator.php new file mode 100644 index 00000000..9cadf420 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ExplicitOctalEmulator.php @@ -0,0 +1,45 @@ +id == \T_LNUMBER && $token->text === '0' && + isset($tokens[$i + 1]) && $tokens[$i + 1]->id == \T_STRING && + preg_match('/[oO][0-7]+(?:_[0-7]+)*/', $tokens[$i + 1]->text) + ) { + $tokenKind = $this->resolveIntegerOrFloatToken($tokens[$i + 1]->text); + array_splice($tokens, $i, 2, [ + new Token($tokenKind, '0' . $tokens[$i + 1]->text, $token->line, $token->pos), + ]); + $c--; + } + } + return $tokens; + } + + private function resolveIntegerOrFloatToken(string $str): int { + $str = substr($str, 1); + $str = str_replace('_', '', $str); + $num = octdec($str); + return is_float($num) ? \T_DNUMBER : \T_LNUMBER; + } + + public function reverseEmulate(string $code, array $tokens): array { + // Explicit octals were not legal code previously, don't bother. + return $tokens; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/KeywordEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/KeywordEmulator.php new file mode 100644 index 00000000..9803f996 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/KeywordEmulator.php @@ -0,0 +1,56 @@ +getKeywordString()) !== false; + } + + /** @param Token[] $tokens */ + protected function isKeywordContext(array $tokens, int $pos): bool { + $previousNonSpaceToken = $this->getPreviousNonSpaceToken($tokens, $pos); + return $previousNonSpaceToken === null || $previousNonSpaceToken->id !== \T_OBJECT_OPERATOR; + } + + public function emulate(string $code, array $tokens): array { + $keywordString = $this->getKeywordString(); + foreach ($tokens as $i => $token) { + if ($token->id === T_STRING && strtolower($token->text) === $keywordString + && $this->isKeywordContext($tokens, $i)) { + $token->id = $this->getKeywordToken(); + } + } + + return $tokens; + } + + /** @param Token[] $tokens */ + private function getPreviousNonSpaceToken(array $tokens, int $start): ?Token { + for ($i = $start - 1; $i >= 0; --$i) { + if ($tokens[$i]->id === T_WHITESPACE) { + continue; + } + + return $tokens[$i]; + } + + return null; + } + + public function reverseEmulate(string $code, array $tokens): array { + $keywordToken = $this->getKeywordToken(); + foreach ($tokens as $token) { + if ($token->id === $keywordToken) { + $token->id = \T_STRING; + } + } + + return $tokens; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/MatchTokenEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/MatchTokenEmulator.php new file mode 100644 index 00000000..0fa5fbc2 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/MatchTokenEmulator.php @@ -0,0 +1,19 @@ +') !== false; + } + + public function emulate(string $code, array $tokens): array { + // We need to manually iterate and manage a count because we'll change + // the tokens array on the way + for ($i = 0, $c = count($tokens); $i < $c; ++$i) { + $token = $tokens[$i]; + if ($token->text === '?' && isset($tokens[$i + 1]) && $tokens[$i + 1]->id === \T_OBJECT_OPERATOR) { + array_splice($tokens, $i, 2, [ + new Token(\T_NULLSAFE_OBJECT_OPERATOR, '?->', $token->line, $token->pos), + ]); + $c--; + continue; + } + + // Handle ?-> inside encapsed string. + if ($token->id === \T_ENCAPSED_AND_WHITESPACE && isset($tokens[$i - 1]) + && $tokens[$i - 1]->id === \T_VARIABLE + && preg_match('/^\?->([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)/', $token->text, $matches) + ) { + $replacement = [ + new Token(\T_NULLSAFE_OBJECT_OPERATOR, '?->', $token->line, $token->pos), + new Token(\T_STRING, $matches[1], $token->line, $token->pos + 3), + ]; + $matchLen = \strlen($matches[0]); + if ($matchLen !== \strlen($token->text)) { + $replacement[] = new Token( + \T_ENCAPSED_AND_WHITESPACE, + \substr($token->text, $matchLen), + $token->line, $token->pos + $matchLen + ); + } + array_splice($tokens, $i, 1, $replacement); + $c += \count($replacement) - 1; + continue; + } + } + + return $tokens; + } + + public function reverseEmulate(string $code, array $tokens): array { + // ?-> was not valid code previously, don't bother. + return $tokens; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ReadonlyFunctionTokenEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ReadonlyFunctionTokenEmulator.php new file mode 100644 index 00000000..5990d7fa --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ReadonlyFunctionTokenEmulator.php @@ -0,0 +1,31 @@ +text === '(' || + ($tokens[$pos + 1]->id === \T_WHITESPACE && + isset($tokens[$pos + 2]) && + $tokens[$pos + 2]->text === '('))); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ReverseEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ReverseEmulator.php new file mode 100644 index 00000000..851b5c4a --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ReverseEmulator.php @@ -0,0 +1,37 @@ +emulator = $emulator; + } + + public function getPhpVersion(): PhpVersion { + return $this->emulator->getPhpVersion(); + } + + public function isEmulationNeeded(string $code): bool { + return $this->emulator->isEmulationNeeded($code); + } + + public function emulate(string $code, array $tokens): array { + return $this->emulator->reverseEmulate($code, $tokens); + } + + public function reverseEmulate(string $code, array $tokens): array { + return $this->emulator->emulate($code, $tokens); + } + + public function preprocessCode(string $code, array &$patches): string { + return $code; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/TokenEmulator.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/TokenEmulator.php new file mode 100644 index 00000000..fec2f19f --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/TokenEmulator.php @@ -0,0 +1,30 @@ + [aliasName => originalName]] */ + protected array $aliases = []; + + /** @var Name[][] Same as $aliases but preserving original case */ + protected array $origAliases = []; + + /** @var ErrorHandler Error handler */ + protected ErrorHandler $errorHandler; + + /** + * Create a name context. + * + * @param ErrorHandler $errorHandler Error handling used to report errors + */ + public function __construct(ErrorHandler $errorHandler) { + $this->errorHandler = $errorHandler; + } + + /** + * Start a new namespace. + * + * This also resets the alias table. + * + * @param Name|null $namespace Null is the global namespace + */ + public function startNamespace(?Name $namespace = null): void { + $this->namespace = $namespace; + $this->origAliases = $this->aliases = [ + Stmt\Use_::TYPE_NORMAL => [], + Stmt\Use_::TYPE_FUNCTION => [], + Stmt\Use_::TYPE_CONSTANT => [], + ]; + } + + /** + * Add an alias / import. + * + * @param Name $name Original name + * @param string $aliasName Aliased name + * @param Stmt\Use_::TYPE_* $type One of Stmt\Use_::TYPE_* + * @param array $errorAttrs Attributes to use to report an error + */ + public function addAlias(Name $name, string $aliasName, int $type, array $errorAttrs = []): void { + // Constant names are case sensitive, everything else case insensitive + if ($type === Stmt\Use_::TYPE_CONSTANT) { + $aliasLookupName = $aliasName; + } else { + $aliasLookupName = strtolower($aliasName); + } + + if (isset($this->aliases[$type][$aliasLookupName])) { + $typeStringMap = [ + Stmt\Use_::TYPE_NORMAL => '', + Stmt\Use_::TYPE_FUNCTION => 'function ', + Stmt\Use_::TYPE_CONSTANT => 'const ', + ]; + + $this->errorHandler->handleError(new Error( + sprintf( + 'Cannot use %s%s as %s because the name is already in use', + $typeStringMap[$type], $name, $aliasName + ), + $errorAttrs + )); + return; + } + + $this->aliases[$type][$aliasLookupName] = $name; + $this->origAliases[$type][$aliasName] = $name; + } + + /** + * Get current namespace. + * + * @return null|Name Namespace (or null if global namespace) + */ + public function getNamespace(): ?Name { + return $this->namespace; + } + + /** + * Get resolved name. + * + * @param Name $name Name to resolve + * @param Stmt\Use_::TYPE_* $type One of Stmt\Use_::TYPE_{FUNCTION|CONSTANT} + * + * @return null|Name Resolved name, or null if static resolution is not possible + */ + public function getResolvedName(Name $name, int $type): ?Name { + // don't resolve special class names + if ($type === Stmt\Use_::TYPE_NORMAL && $name->isSpecialClassName()) { + if (!$name->isUnqualified()) { + $this->errorHandler->handleError(new Error( + sprintf("'\\%s' is an invalid class name", $name->toString()), + $name->getAttributes() + )); + } + return $name; + } + + // fully qualified names are already resolved + if ($name->isFullyQualified()) { + return $name; + } + + // Try to resolve aliases + if (null !== $resolvedName = $this->resolveAlias($name, $type)) { + return $resolvedName; + } + + if ($type !== Stmt\Use_::TYPE_NORMAL && $name->isUnqualified()) { + if (null === $this->namespace) { + // outside of a namespace unaliased unqualified is same as fully qualified + return new FullyQualified($name, $name->getAttributes()); + } + + // Cannot resolve statically + return null; + } + + // if no alias exists prepend current namespace + return FullyQualified::concat($this->namespace, $name, $name->getAttributes()); + } + + /** + * Get resolved class name. + * + * @param Name $name Class ame to resolve + * + * @return Name Resolved name + */ + public function getResolvedClassName(Name $name): Name { + return $this->getResolvedName($name, Stmt\Use_::TYPE_NORMAL); + } + + /** + * Get possible ways of writing a fully qualified name (e.g., by making use of aliases). + * + * @param string $name Fully-qualified name (without leading namespace separator) + * @param Stmt\Use_::TYPE_* $type One of Stmt\Use_::TYPE_* + * + * @return Name[] Possible representations of the name + */ + public function getPossibleNames(string $name, int $type): array { + $lcName = strtolower($name); + + if ($type === Stmt\Use_::TYPE_NORMAL) { + // self, parent and static must always be unqualified + if ($lcName === "self" || $lcName === "parent" || $lcName === "static") { + return [new Name($name)]; + } + } + + // Collect possible ways to write this name, starting with the fully-qualified name + $possibleNames = [new FullyQualified($name)]; + + if (null !== $nsRelativeName = $this->getNamespaceRelativeName($name, $lcName, $type)) { + // Make sure there is no alias that makes the normally namespace-relative name + // into something else + if (null === $this->resolveAlias($nsRelativeName, $type)) { + $possibleNames[] = $nsRelativeName; + } + } + + // Check for relevant namespace use statements + foreach ($this->origAliases[Stmt\Use_::TYPE_NORMAL] as $alias => $orig) { + $lcOrig = $orig->toLowerString(); + if (0 === strpos($lcName, $lcOrig . '\\')) { + $possibleNames[] = new Name($alias . substr($name, strlen($lcOrig))); + } + } + + // Check for relevant type-specific use statements + foreach ($this->origAliases[$type] as $alias => $orig) { + if ($type === Stmt\Use_::TYPE_CONSTANT) { + // Constants are are complicated-sensitive + $normalizedOrig = $this->normalizeConstName($orig->toString()); + if ($normalizedOrig === $this->normalizeConstName($name)) { + $possibleNames[] = new Name($alias); + } + } else { + // Everything else is case-insensitive + if ($orig->toLowerString() === $lcName) { + $possibleNames[] = new Name($alias); + } + } + } + + return $possibleNames; + } + + /** + * Get shortest representation of this fully-qualified name. + * + * @param string $name Fully-qualified name (without leading namespace separator) + * @param Stmt\Use_::TYPE_* $type One of Stmt\Use_::TYPE_* + * + * @return Name Shortest representation + */ + public function getShortName(string $name, int $type): Name { + $possibleNames = $this->getPossibleNames($name, $type); + + // Find shortest name + $shortestName = null; + $shortestLength = \INF; + foreach ($possibleNames as $possibleName) { + $length = strlen($possibleName->toCodeString()); + if ($length < $shortestLength) { + $shortestName = $possibleName; + $shortestLength = $length; + } + } + + return $shortestName; + } + + private function resolveAlias(Name $name, int $type): ?FullyQualified { + $firstPart = $name->getFirst(); + + if ($name->isQualified()) { + // resolve aliases for qualified names, always against class alias table + $checkName = strtolower($firstPart); + if (isset($this->aliases[Stmt\Use_::TYPE_NORMAL][$checkName])) { + $alias = $this->aliases[Stmt\Use_::TYPE_NORMAL][$checkName]; + return FullyQualified::concat($alias, $name->slice(1), $name->getAttributes()); + } + } elseif ($name->isUnqualified()) { + // constant aliases are case-sensitive, function aliases case-insensitive + $checkName = $type === Stmt\Use_::TYPE_CONSTANT ? $firstPart : strtolower($firstPart); + if (isset($this->aliases[$type][$checkName])) { + // resolve unqualified aliases + return new FullyQualified($this->aliases[$type][$checkName], $name->getAttributes()); + } + } + + // No applicable aliases + return null; + } + + private function getNamespaceRelativeName(string $name, string $lcName, int $type): ?Name { + if (null === $this->namespace) { + return new Name($name); + } + + if ($type === Stmt\Use_::TYPE_CONSTANT) { + // The constants true/false/null always resolve to the global symbols, even inside a + // namespace, so they may be used without qualification + if ($lcName === "true" || $lcName === "false" || $lcName === "null") { + return new Name($name); + } + } + + $namespacePrefix = strtolower($this->namespace . '\\'); + if (0 === strpos($lcName, $namespacePrefix)) { + return new Name(substr($name, strlen($namespacePrefix))); + } + + return null; + } + + private function normalizeConstName(string $name): string { + $nsSep = strrpos($name, '\\'); + if (false === $nsSep) { + return $name; + } + + // Constants have case-insensitive namespace and case-sensitive short-name + $ns = substr($name, 0, $nsSep); + $shortName = substr($name, $nsSep + 1); + return strtolower($ns) . '\\' . $shortName; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node.php b/vendor/nikic/php-parser/lib/PhpParser/Node.php new file mode 100644 index 00000000..258e4516 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node.php @@ -0,0 +1,146 @@ + + */ + public function getAttributes(): array; + + /** + * Replaces all the attributes of this node. + * + * @param array $attributes + */ + public function setAttributes(array $attributes): void; +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Arg.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Arg.php new file mode 100644 index 00000000..6680efac --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Arg.php @@ -0,0 +1,44 @@ + $attributes Additional attributes + * @param Identifier|null $name Parameter name (for named parameters) + */ + public function __construct( + Expr $value, bool $byRef = false, bool $unpack = false, array $attributes = [], + ?Identifier $name = null + ) { + $this->attributes = $attributes; + $this->name = $name; + $this->value = $value; + $this->byRef = $byRef; + $this->unpack = $unpack; + } + + public function getSubNodeNames(): array { + return ['name', 'value', 'byRef', 'unpack']; + } + + public function getType(): string { + return 'Arg'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/ArrayItem.php b/vendor/nikic/php-parser/lib/PhpParser/Node/ArrayItem.php new file mode 100644 index 00000000..fa1cff52 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/ArrayItem.php @@ -0,0 +1,43 @@ + $attributes Additional attributes + */ + public function __construct(Expr $value, ?Expr $key = null, bool $byRef = false, array $attributes = [], bool $unpack = false) { + $this->attributes = $attributes; + $this->key = $key; + $this->value = $value; + $this->byRef = $byRef; + $this->unpack = $unpack; + } + + public function getSubNodeNames(): array { + return ['key', 'value', 'byRef', 'unpack']; + } + + public function getType(): string { + return 'ArrayItem'; + } +} + +// @deprecated compatibility alias +class_alias(ArrayItem::class, Expr\ArrayItem::class); diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Attribute.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Attribute.php new file mode 100644 index 00000000..9d892436 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Attribute.php @@ -0,0 +1,33 @@ + Attribute arguments */ + public array $args; + + /** + * @param Node\Name $name Attribute name + * @param list $args Attribute arguments + * @param array $attributes Additional node attributes + */ + public function __construct(Name $name, array $args = [], array $attributes = []) { + $this->attributes = $attributes; + $this->name = $name; + $this->args = $args; + } + + public function getSubNodeNames(): array { + return ['name', 'args']; + } + + public function getType(): string { + return 'Attribute'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/AttributeGroup.php b/vendor/nikic/php-parser/lib/PhpParser/Node/AttributeGroup.php new file mode 100644 index 00000000..b9eb588d --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/AttributeGroup.php @@ -0,0 +1,27 @@ + $attributes Additional node attributes + */ + public function __construct(array $attrs, array $attributes = []) { + $this->attributes = $attributes; + $this->attrs = $attrs; + } + + public function getSubNodeNames(): array { + return ['attrs']; + } + + public function getType(): string { + return 'AttributeGroup'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/ClosureUse.php b/vendor/nikic/php-parser/lib/PhpParser/Node/ClosureUse.php new file mode 100644 index 00000000..e313280b --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/ClosureUse.php @@ -0,0 +1,36 @@ + $attributes Additional attributes + */ + public function __construct(Expr\Variable $var, bool $byRef = false, array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + $this->byRef = $byRef; + } + + public function getSubNodeNames(): array { + return ['var', 'byRef']; + } + + public function getType(): string { + return 'ClosureUse'; + } +} + +// @deprecated compatibility alias +class_alias(ClosureUse::class, Expr\ClosureUse::class); diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/ComplexType.php b/vendor/nikic/php-parser/lib/PhpParser/Node/ComplexType.php new file mode 100644 index 00000000..05a5e5ee --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/ComplexType.php @@ -0,0 +1,13 @@ + $attributes Additional attributes + */ + public function __construct($name, Expr $value, array $attributes = []) { + $this->attributes = $attributes; + $this->name = \is_string($name) ? new Identifier($name) : $name; + $this->value = $value; + } + + public function getSubNodeNames(): array { + return ['name', 'value']; + } + + public function getType(): string { + return 'Const'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/DeclareItem.php b/vendor/nikic/php-parser/lib/PhpParser/Node/DeclareItem.php new file mode 100644 index 00000000..55c1fe4f --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/DeclareItem.php @@ -0,0 +1,37 @@ +value pair node. + * + * @param string|Node\Identifier $key Key + * @param Node\Expr $value Value + * @param array $attributes Additional attributes + */ + public function __construct($key, Node\Expr $value, array $attributes = []) { + $this->attributes = $attributes; + $this->key = \is_string($key) ? new Node\Identifier($key) : $key; + $this->value = $value; + } + + public function getSubNodeNames(): array { + return ['key', 'value']; + } + + public function getType(): string { + return 'DeclareItem'; + } +} + +// @deprecated compatibility alias +class_alias(DeclareItem::class, Stmt\DeclareDeclare::class); diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr.php new file mode 100644 index 00000000..8b7dbb6c --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr.php @@ -0,0 +1,8 @@ + $attributes Additional attributes + */ + public function __construct(Expr $var, ?Expr $dim = null, array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + $this->dim = $dim; + } + + public function getSubNodeNames(): array { + return ['var', 'dim']; + } + + public function getType(): string { + return 'Expr_ArrayDimFetch'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayItem.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayItem.php new file mode 100644 index 00000000..490ac937 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayItem.php @@ -0,0 +1,3 @@ + $attributes Additional attributes + */ + public function __construct(array $items = [], array $attributes = []) { + $this->attributes = $attributes; + $this->items = $items; + } + + public function getSubNodeNames(): array { + return ['items']; + } + + public function getType(): string { + return 'Expr_Array'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrowFunction.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrowFunction.php new file mode 100644 index 00000000..0e98ce9f --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrowFunction.php @@ -0,0 +1,84 @@ + false : Whether the closure is static + * 'byRef' => false : Whether to return by reference + * 'params' => array() : Parameters + * 'returnType' => null : Return type + * 'attrGroups' => array() : PHP attribute groups + * @param array $attributes Additional attributes + */ + public function __construct(array $subNodes, array $attributes = []) { + $this->attributes = $attributes; + $this->static = $subNodes['static'] ?? false; + $this->byRef = $subNodes['byRef'] ?? false; + $this->params = $subNodes['params'] ?? []; + $this->returnType = $subNodes['returnType'] ?? null; + $this->expr = $subNodes['expr']; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'static', 'byRef', 'params', 'returnType', 'expr']; + } + + public function returnsByRef(): bool { + return $this->byRef; + } + + public function getParams(): array { + return $this->params; + } + + public function getReturnType() { + return $this->returnType; + } + + public function getAttrGroups(): array { + return $this->attrGroups; + } + + /** + * @return Node\Stmt\Return_[] + */ + public function getStmts(): array { + return [new Node\Stmt\Return_($this->expr)]; + } + + public function getType(): string { + return 'Expr_ArrowFunction'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Assign.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Assign.php new file mode 100644 index 00000000..dcbf84dd --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Assign.php @@ -0,0 +1,33 @@ + $attributes Additional attributes + */ + public function __construct(Expr $var, Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['var', 'expr']; + } + + public function getType(): string { + return 'Expr_Assign'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp.php new file mode 100644 index 00000000..5209a64b --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $var, Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['var', 'expr']; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php new file mode 100644 index 00000000..4f3623fb --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php @@ -0,0 +1,11 @@ + $attributes Additional attributes + */ + public function __construct(Expr $var, Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['var', 'expr']; + } + + public function getType(): string { + return 'Expr_AssignRef'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp.php new file mode 100644 index 00000000..1b92bd4f --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp.php @@ -0,0 +1,37 @@ + $attributes Additional attributes + */ + public function __construct(Expr $left, Expr $right, array $attributes = []) { + $this->attributes = $attributes; + $this->left = $left; + $this->right = $right; + } + + public function getSubNodeNames(): array { + return ['left', 'right']; + } + + /** + * Get the operator sigil for this binary operation. + * + * In the case there are multiple possible sigils for an operator, this method does not + * necessarily return the one used in the parsed code. + */ + abstract public function getOperatorSigil(): string; +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php new file mode 100644 index 00000000..5930c541 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php @@ -0,0 +1,15 @@ +'; + } + + public function getType(): string { + return 'Expr_BinaryOp_Greater'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php new file mode 100644 index 00000000..4d440b10 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php @@ -0,0 +1,15 @@ +='; + } + + public function getType(): string { + return 'Expr_BinaryOp_GreaterOrEqual'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Identical.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Identical.php new file mode 100644 index 00000000..e25d17cd --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Identical.php @@ -0,0 +1,15 @@ +>'; + } + + public function getType(): string { + return 'Expr_BinaryOp_ShiftRight'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Smaller.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Smaller.php new file mode 100644 index 00000000..01e9b231 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Smaller.php @@ -0,0 +1,15 @@ +'; + } + + public function getType(): string { + return 'Expr_BinaryOp_Spaceship'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BitwiseNot.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BitwiseNot.php new file mode 100644 index 00000000..b7175a7a --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BitwiseNot.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Expr_BitwiseNot'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BooleanNot.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BooleanNot.php new file mode 100644 index 00000000..c66d2332 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BooleanNot.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Expr_BooleanNot'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/CallLike.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/CallLike.php new file mode 100644 index 00000000..2af2245b --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/CallLike.php @@ -0,0 +1,35 @@ + + */ + abstract public function getRawArgs(): array; + + /** + * Returns whether this call expression is actually a first class callable. + */ + public function isFirstClassCallable(): bool { + $rawArgs = $this->getRawArgs(); + return count($rawArgs) === 1 && current($rawArgs) instanceof VariadicPlaceholder; + } + + /** + * Assert that this is not a first-class callable and return only ordinary Args. + * + * @return Arg[] + */ + public function getArgs(): array { + assert(!$this->isFirstClassCallable()); + return $this->getRawArgs(); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast.php new file mode 100644 index 00000000..c2751de4 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast.php @@ -0,0 +1,25 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Array_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Array_.php new file mode 100644 index 00000000..471cb824 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Array_.php @@ -0,0 +1,11 @@ + $attributes Additional attributes + */ + public function __construct(Node $class, $name, array $attributes = []) { + $this->attributes = $attributes; + $this->class = $class; + $this->name = \is_string($name) ? new Identifier($name) : $name; + } + + public function getSubNodeNames(): array { + return ['class', 'name']; + } + + public function getType(): string { + return 'Expr_ClassConstFetch'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Clone_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Clone_.php new file mode 100644 index 00000000..d85bc9ab --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Clone_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Expr_Clone'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Closure.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Closure.php new file mode 100644 index 00000000..0680446f --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Closure.php @@ -0,0 +1,86 @@ + false : Whether the closure is static + * 'byRef' => false : Whether to return by reference + * 'params' => array(): Parameters + * 'uses' => array(): use()s + * 'returnType' => null : Return type + * 'stmts' => array(): Statements + * 'attrGroups' => array(): PHP attributes groups + * @param array $attributes Additional attributes + */ + public function __construct(array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->static = $subNodes['static'] ?? false; + $this->byRef = $subNodes['byRef'] ?? false; + $this->params = $subNodes['params'] ?? []; + $this->uses = $subNodes['uses'] ?? []; + $this->returnType = $subNodes['returnType'] ?? null; + $this->stmts = $subNodes['stmts'] ?? []; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'static', 'byRef', 'params', 'uses', 'returnType', 'stmts']; + } + + public function returnsByRef(): bool { + return $this->byRef; + } + + public function getParams(): array { + return $this->params; + } + + public function getReturnType() { + return $this->returnType; + } + + /** @return Node\Stmt[] */ + public function getStmts(): array { + return $this->stmts; + } + + public function getAttrGroups(): array { + return $this->attrGroups; + } + + public function getType(): string { + return 'Expr_Closure'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ClosureUse.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ClosureUse.php new file mode 100644 index 00000000..681ff317 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ClosureUse.php @@ -0,0 +1,3 @@ + $attributes Additional attributes + */ + public function __construct(Name $name, array $attributes = []) { + $this->attributes = $attributes; + $this->name = $name; + } + + public function getSubNodeNames(): array { + return ['name']; + } + + public function getType(): string { + return 'Expr_ConstFetch'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Empty_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Empty_.php new file mode 100644 index 00000000..d2f30506 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Empty_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Expr_Empty'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Error.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Error.php new file mode 100644 index 00000000..43010ac4 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Error.php @@ -0,0 +1,30 @@ + $attributes Additional attributes + */ + public function __construct(array $attributes = []) { + $this->attributes = $attributes; + } + + public function getSubNodeNames(): array { + return []; + } + + public function getType(): string { + return 'Expr_Error'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ErrorSuppress.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ErrorSuppress.php new file mode 100644 index 00000000..32625a23 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ErrorSuppress.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Expr_ErrorSuppress'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Eval_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Eval_.php new file mode 100644 index 00000000..5120b1b4 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Eval_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Expr_Eval'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Exit_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Exit_.php new file mode 100644 index 00000000..cf002466 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Exit_.php @@ -0,0 +1,33 @@ + $attributes Additional attributes + */ + public function __construct(?Expr $expr = null, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Expr_Exit'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/FuncCall.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/FuncCall.php new file mode 100644 index 00000000..0b85840d --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/FuncCall.php @@ -0,0 +1,38 @@ + Arguments */ + public array $args; + + /** + * Constructs a function call node. + * + * @param Node\Name|Expr $name Function name + * @param array $args Arguments + * @param array $attributes Additional attributes + */ + public function __construct(Node $name, array $args = [], array $attributes = []) { + $this->attributes = $attributes; + $this->name = $name; + $this->args = $args; + } + + public function getSubNodeNames(): array { + return ['name', 'args']; + } + + public function getType(): string { + return 'Expr_FuncCall'; + } + + public function getRawArgs(): array { + return $this->args; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Include_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Include_.php new file mode 100644 index 00000000..e1187b19 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Include_.php @@ -0,0 +1,38 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, int $type, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + $this->type = $type; + } + + public function getSubNodeNames(): array { + return ['expr', 'type']; + } + + public function getType(): string { + return 'Expr_Include'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Instanceof_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Instanceof_.php new file mode 100644 index 00000000..a2783cb3 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Instanceof_.php @@ -0,0 +1,35 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, Node $class, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + $this->class = $class; + } + + public function getSubNodeNames(): array { + return ['expr', 'class']; + } + + public function getType(): string { + return 'Expr_Instanceof'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Isset_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Isset_.php new file mode 100644 index 00000000..4f80fff7 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Isset_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(array $vars, array $attributes = []) { + $this->attributes = $attributes; + $this->vars = $vars; + } + + public function getSubNodeNames(): array { + return ['vars']; + } + + public function getType(): string { + return 'Expr_Isset'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/List_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/List_.php new file mode 100644 index 00000000..496b7b38 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/List_.php @@ -0,0 +1,34 @@ + $attributes Additional attributes + */ + public function __construct(array $items, array $attributes = []) { + $this->attributes = $attributes; + $this->items = $items; + } + + public function getSubNodeNames(): array { + return ['items']; + } + + public function getType(): string { + return 'Expr_List'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Match_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Match_.php new file mode 100644 index 00000000..cd028a2d --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Match_.php @@ -0,0 +1,32 @@ + $attributes Additional attributes + */ + public function __construct(Node\Expr $cond, array $arms = [], array $attributes = []) { + $this->attributes = $attributes; + $this->cond = $cond; + $this->arms = $arms; + } + + public function getSubNodeNames(): array { + return ['cond', 'arms']; + } + + public function getType(): string { + return 'Expr_Match'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/MethodCall.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/MethodCall.php new file mode 100644 index 00000000..2703c75d --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/MethodCall.php @@ -0,0 +1,45 @@ + Arguments */ + public array $args; + + /** + * Constructs a function call node. + * + * @param Expr $var Variable holding object + * @param string|Identifier|Expr $name Method name + * @param array $args Arguments + * @param array $attributes Additional attributes + */ + public function __construct(Expr $var, $name, array $args = [], array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + $this->name = \is_string($name) ? new Identifier($name) : $name; + $this->args = $args; + } + + public function getSubNodeNames(): array { + return ['var', 'name', 'args']; + } + + public function getType(): string { + return 'Expr_MethodCall'; + } + + public function getRawArgs(): array { + return $this->args; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/New_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/New_.php new file mode 100644 index 00000000..eedaaa1e --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/New_.php @@ -0,0 +1,40 @@ + Arguments */ + public array $args; + + /** + * Constructs a function call node. + * + * @param Node\Name|Expr|Node\Stmt\Class_ $class Class name (or class node for anonymous classes) + * @param array $args Arguments + * @param array $attributes Additional attributes + */ + public function __construct(Node $class, array $args = [], array $attributes = []) { + $this->attributes = $attributes; + $this->class = $class; + $this->args = $args; + } + + public function getSubNodeNames(): array { + return ['class', 'args']; + } + + public function getType(): string { + return 'Expr_New'; + } + + public function getRawArgs(): array { + return $this->args; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafeMethodCall.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafeMethodCall.php new file mode 100644 index 00000000..a151f715 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafeMethodCall.php @@ -0,0 +1,45 @@ + Arguments */ + public array $args; + + /** + * Constructs a nullsafe method call node. + * + * @param Expr $var Variable holding object + * @param string|Identifier|Expr $name Method name + * @param array $args Arguments + * @param array $attributes Additional attributes + */ + public function __construct(Expr $var, $name, array $args = [], array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + $this->name = \is_string($name) ? new Identifier($name) : $name; + $this->args = $args; + } + + public function getSubNodeNames(): array { + return ['var', 'name', 'args']; + } + + public function getType(): string { + return 'Expr_NullsafeMethodCall'; + } + + public function getRawArgs(): array { + return $this->args; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafePropertyFetch.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafePropertyFetch.php new file mode 100644 index 00000000..6f73a16d --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafePropertyFetch.php @@ -0,0 +1,35 @@ + $attributes Additional attributes + */ + public function __construct(Expr $var, $name, array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + $this->name = \is_string($name) ? new Identifier($name) : $name; + } + + public function getSubNodeNames(): array { + return ['var', 'name']; + } + + public function getType(): string { + return 'Expr_NullsafePropertyFetch'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostDec.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostDec.php new file mode 100644 index 00000000..3dca8fdc --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostDec.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $var, array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + } + + public function getSubNodeNames(): array { + return ['var']; + } + + public function getType(): string { + return 'Expr_PostDec'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostInc.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostInc.php new file mode 100644 index 00000000..bc990c30 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostInc.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $var, array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + } + + public function getSubNodeNames(): array { + return ['var']; + } + + public function getType(): string { + return 'Expr_PostInc'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreDec.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreDec.php new file mode 100644 index 00000000..2f168730 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreDec.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $var, array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + } + + public function getSubNodeNames(): array { + return ['var']; + } + + public function getType(): string { + return 'Expr_PreDec'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreInc.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreInc.php new file mode 100644 index 00000000..fd455f55 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreInc.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $var, array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + } + + public function getSubNodeNames(): array { + return ['var']; + } + + public function getType(): string { + return 'Expr_PreInc'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Print_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Print_.php new file mode 100644 index 00000000..60574760 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Print_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Expr_Print'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PropertyFetch.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PropertyFetch.php new file mode 100644 index 00000000..8c416a8c --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PropertyFetch.php @@ -0,0 +1,35 @@ + $attributes Additional attributes + */ + public function __construct(Expr $var, $name, array $attributes = []) { + $this->attributes = $attributes; + $this->var = $var; + $this->name = \is_string($name) ? new Identifier($name) : $name; + } + + public function getSubNodeNames(): array { + return ['var', 'name']; + } + + public function getType(): string { + return 'Expr_PropertyFetch'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ShellExec.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ShellExec.php new file mode 100644 index 00000000..e4003512 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ShellExec.php @@ -0,0 +1,30 @@ + $attributes Additional attributes + */ + public function __construct(array $parts, array $attributes = []) { + $this->attributes = $attributes; + $this->parts = $parts; + } + + public function getSubNodeNames(): array { + return ['parts']; + } + + public function getType(): string { + return 'Expr_ShellExec'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticCall.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticCall.php new file mode 100644 index 00000000..707f34b6 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticCall.php @@ -0,0 +1,45 @@ + Arguments */ + public array $args; + + /** + * Constructs a static method call node. + * + * @param Node\Name|Expr $class Class name + * @param string|Identifier|Expr $name Method name + * @param array $args Arguments + * @param array $attributes Additional attributes + */ + public function __construct(Node $class, $name, array $args = [], array $attributes = []) { + $this->attributes = $attributes; + $this->class = $class; + $this->name = \is_string($name) ? new Identifier($name) : $name; + $this->args = $args; + } + + public function getSubNodeNames(): array { + return ['class', 'name', 'args']; + } + + public function getType(): string { + return 'Expr_StaticCall'; + } + + public function getRawArgs(): array { + return $this->args; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticPropertyFetch.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticPropertyFetch.php new file mode 100644 index 00000000..4836a65b --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticPropertyFetch.php @@ -0,0 +1,36 @@ + $attributes Additional attributes + */ + public function __construct(Node $class, $name, array $attributes = []) { + $this->attributes = $attributes; + $this->class = $class; + $this->name = \is_string($name) ? new VarLikeIdentifier($name) : $name; + } + + public function getSubNodeNames(): array { + return ['class', 'name']; + } + + public function getType(): string { + return 'Expr_StaticPropertyFetch'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Ternary.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Ternary.php new file mode 100644 index 00000000..d4837e64 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Ternary.php @@ -0,0 +1,37 @@ + $attributes Additional attributes + */ + public function __construct(Expr $cond, ?Expr $if, Expr $else, array $attributes = []) { + $this->attributes = $attributes; + $this->cond = $cond; + $this->if = $if; + $this->else = $else; + } + + public function getSubNodeNames(): array { + return ['cond', 'if', 'else']; + } + + public function getType(): string { + return 'Expr_Ternary'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Throw_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Throw_.php new file mode 100644 index 00000000..ee49f835 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Throw_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Node\Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Expr_Throw'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryMinus.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryMinus.php new file mode 100644 index 00000000..cd06f74b --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryMinus.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Expr_UnaryMinus'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryPlus.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryPlus.php new file mode 100644 index 00000000..1b44f7b3 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryPlus.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Expr_UnaryPlus'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Variable.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Variable.php new file mode 100644 index 00000000..bab74920 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Variable.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct($name, array $attributes = []) { + $this->attributes = $attributes; + $this->name = $name; + } + + public function getSubNodeNames(): array { + return ['name']; + } + + public function getType(): string { + return 'Expr_Variable'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/YieldFrom.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/YieldFrom.php new file mode 100644 index 00000000..5cff88f8 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/YieldFrom.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Expr_YieldFrom'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Yield_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Yield_.php new file mode 100644 index 00000000..bd81e69b --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Yield_.php @@ -0,0 +1,33 @@ + $attributes Additional attributes + */ + public function __construct(?Expr $value = null, ?Expr $key = null, array $attributes = []) { + $this->attributes = $attributes; + $this->key = $key; + $this->value = $value; + } + + public function getSubNodeNames(): array { + return ['key', 'value']; + } + + public function getType(): string { + return 'Expr_Yield'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/FunctionLike.php b/vendor/nikic/php-parser/lib/PhpParser/Node/FunctionLike.php new file mode 100644 index 00000000..58f653a8 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/FunctionLike.php @@ -0,0 +1,40 @@ + */ + private static array $specialClassNames = [ + 'self' => true, + 'parent' => true, + 'static' => true, + ]; + + /** + * Constructs an identifier node. + * + * @param string $name Identifier as string + * @param array $attributes Additional attributes + */ + public function __construct(string $name, array $attributes = []) { + $this->attributes = $attributes; + $this->name = $name; + } + + public function getSubNodeNames(): array { + return ['name']; + } + + /** + * Get identifier as string. + * + * @return string Identifier as string. + */ + public function toString(): string { + return $this->name; + } + + /** + * Get lowercased identifier as string. + * + * @return string Lowercased identifier as string + */ + public function toLowerString(): string { + return strtolower($this->name); + } + + /** + * Checks whether the identifier is a special class name (self, parent or static). + * + * @return bool Whether identifier is a special class name + */ + public function isSpecialClassName(): bool { + return isset(self::$specialClassNames[strtolower($this->name)]); + } + + /** + * Get identifier as string. + * + * @return string Identifier as string + */ + public function __toString(): string { + return $this->name; + } + + public function getType(): string { + return 'Identifier'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/InterpolatedStringPart.php b/vendor/nikic/php-parser/lib/PhpParser/Node/InterpolatedStringPart.php new file mode 100644 index 00000000..576dac46 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/InterpolatedStringPart.php @@ -0,0 +1,32 @@ + $attributes Additional attributes + */ + public function __construct(string $value, array $attributes = []) { + $this->attributes = $attributes; + $this->value = $value; + } + + public function getSubNodeNames(): array { + return ['value']; + } + + public function getType(): string { + return 'InterpolatedStringPart'; + } +} + +// @deprecated compatibility alias +class_alias(InterpolatedStringPart::class, Scalar\EncapsedStringPart::class); diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/IntersectionType.php b/vendor/nikic/php-parser/lib/PhpParser/Node/IntersectionType.php new file mode 100644 index 00000000..3b39cf10 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/IntersectionType.php @@ -0,0 +1,27 @@ + $attributes Additional attributes + */ + public function __construct(array $types, array $attributes = []) { + $this->attributes = $attributes; + $this->types = $types; + } + + public function getSubNodeNames(): array { + return ['types']; + } + + public function getType(): string { + return 'IntersectionType'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/MatchArm.php b/vendor/nikic/php-parser/lib/PhpParser/Node/MatchArm.php new file mode 100644 index 00000000..2927f029 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/MatchArm.php @@ -0,0 +1,30 @@ + */ + public ?array $conds; + /** @var Node\Expr */ + public Expr $body; + + /** + * @param null|list $conds + */ + public function __construct(?array $conds, Node\Expr $body, array $attributes = []) { + $this->conds = $conds; + $this->body = $body; + $this->attributes = $attributes; + } + + public function getSubNodeNames(): array { + return ['conds', 'body']; + } + + public function getType(): string { + return 'MatchArm'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Name.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Name.php new file mode 100644 index 00000000..26b863e4 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Name.php @@ -0,0 +1,269 @@ + */ + private static array $specialClassNames = [ + 'self' => true, + 'parent' => true, + 'static' => true, + ]; + + /** + * Constructs a name node. + * + * @param string|string[]|self $name Name as string, part array or Name instance (copy ctor) + * @param array $attributes Additional attributes + */ + final public function __construct($name, array $attributes = []) { + $this->attributes = $attributes; + $this->name = self::prepareName($name); + } + + public function getSubNodeNames(): array { + return ['name']; + } + + /** + * Get parts of name (split by the namespace separator). + * + * @return string[] Parts of name + */ + public function getParts(): array { + return \explode('\\', $this->name); + } + + /** + * Gets the first part of the name, i.e. everything before the first namespace separator. + * + * @return string First part of the name + */ + public function getFirst(): string { + if (false !== $pos = \strpos($this->name, '\\')) { + return \substr($this->name, 0, $pos); + } + return $this->name; + } + + /** + * Gets the last part of the name, i.e. everything after the last namespace separator. + * + * @return string Last part of the name + */ + public function getLast(): string { + if (false !== $pos = \strrpos($this->name, '\\')) { + return \substr($this->name, $pos + 1); + } + return $this->name; + } + + /** + * Checks whether the name is unqualified. (E.g. Name) + * + * @return bool Whether the name is unqualified + */ + public function isUnqualified(): bool { + return false === \strpos($this->name, '\\'); + } + + /** + * Checks whether the name is qualified. (E.g. Name\Name) + * + * @return bool Whether the name is qualified + */ + public function isQualified(): bool { + return false !== \strpos($this->name, '\\'); + } + + /** + * Checks whether the name is fully qualified. (E.g. \Name) + * + * @return bool Whether the name is fully qualified + */ + public function isFullyQualified(): bool { + return false; + } + + /** + * Checks whether the name is explicitly relative to the current namespace. (E.g. namespace\Name) + * + * @return bool Whether the name is relative + */ + public function isRelative(): bool { + return false; + } + + /** + * Returns a string representation of the name itself, without taking the name type into + * account (e.g., not including a leading backslash for fully qualified names). + * + * @return string String representation + */ + public function toString(): string { + return $this->name; + } + + /** + * Returns a string representation of the name as it would occur in code (e.g., including + * leading backslash for fully qualified names. + * + * @return string String representation + */ + public function toCodeString(): string { + return $this->toString(); + } + + /** + * Returns lowercased string representation of the name, without taking the name type into + * account (e.g., no leading backslash for fully qualified names). + * + * @return string Lowercased string representation + */ + public function toLowerString(): string { + return strtolower($this->name); + } + + /** + * Checks whether the identifier is a special class name (self, parent or static). + * + * @return bool Whether identifier is a special class name + */ + public function isSpecialClassName(): bool { + return isset(self::$specialClassNames[strtolower($this->name)]); + } + + /** + * Returns a string representation of the name by imploding the namespace parts with the + * namespace separator. + * + * @return string String representation + */ + public function __toString(): string { + return $this->name; + } + + /** + * Gets a slice of a name (similar to array_slice). + * + * This method returns a new instance of the same type as the original and with the same + * attributes. + * + * If the slice is empty, null is returned. The null value will be correctly handled in + * concatenations using concat(). + * + * Offset and length have the same meaning as in array_slice(). + * + * @param int $offset Offset to start the slice at (may be negative) + * @param int|null $length Length of the slice (may be negative) + * + * @return static|null Sliced name + */ + public function slice(int $offset, ?int $length = null) { + if ($offset === 1 && $length === null) { + // Short-circuit the common case. + if (false !== $pos = \strpos($this->name, '\\')) { + return new static(\substr($this->name, $pos + 1)); + } + return null; + } + + $parts = \explode('\\', $this->name); + $numParts = \count($parts); + + $realOffset = $offset < 0 ? $offset + $numParts : $offset; + if ($realOffset < 0 || $realOffset > $numParts) { + throw new \OutOfBoundsException(sprintf('Offset %d is out of bounds', $offset)); + } + + if (null === $length) { + $realLength = $numParts - $realOffset; + } else { + $realLength = $length < 0 ? $length + $numParts - $realOffset : $length; + if ($realLength < 0 || $realLength > $numParts - $realOffset) { + throw new \OutOfBoundsException(sprintf('Length %d is out of bounds', $length)); + } + } + + if ($realLength === 0) { + // Empty slice is represented as null + return null; + } + + return new static(array_slice($parts, $realOffset, $realLength), $this->attributes); + } + + /** + * Concatenate two names, yielding a new Name instance. + * + * The type of the generated instance depends on which class this method is called on, for + * example Name\FullyQualified::concat() will yield a Name\FullyQualified instance. + * + * If one of the arguments is null, a new instance of the other name will be returned. If both + * arguments are null, null will be returned. As such, writing + * Name::concat($namespace, $shortName) + * where $namespace is a Name node or null will work as expected. + * + * @param string|string[]|self|null $name1 The first name + * @param string|string[]|self|null $name2 The second name + * @param array $attributes Attributes to assign to concatenated name + * + * @return static|null Concatenated name + */ + public static function concat($name1, $name2, array $attributes = []) { + if (null === $name1 && null === $name2) { + return null; + } + if (null === $name1) { + return new static($name2, $attributes); + } + if (null === $name2) { + return new static($name1, $attributes); + } else { + return new static( + self::prepareName($name1) . '\\' . self::prepareName($name2), $attributes + ); + } + } + + /** + * Prepares a (string, array or Name node) name for use in name changing methods by converting + * it to a string. + * + * @param string|string[]|self $name Name to prepare + * + * @return string Prepared name + */ + private static function prepareName($name): string { + if (\is_string($name)) { + if ('' === $name) { + throw new \InvalidArgumentException('Name cannot be empty'); + } + + return $name; + } + if (\is_array($name)) { + if (empty($name)) { + throw new \InvalidArgumentException('Name cannot be empty'); + } + + return implode('\\', $name); + } + if ($name instanceof self) { + return $name->name; + } + + throw new \InvalidArgumentException( + 'Expected string, array of parts or Name instance' + ); + } + + public function getType(): string { + return 'Name'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Name/FullyQualified.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Name/FullyQualified.php new file mode 100644 index 00000000..21183786 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Name/FullyQualified.php @@ -0,0 +1,49 @@ +toString(); + } + + public function getType(): string { + return 'Name_FullyQualified'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Name/Relative.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Name/Relative.php new file mode 100644 index 00000000..0226a4e4 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Name/Relative.php @@ -0,0 +1,49 @@ +toString(); + } + + public function getType(): string { + return 'Name_Relative'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/NullableType.php b/vendor/nikic/php-parser/lib/PhpParser/Node/NullableType.php new file mode 100644 index 00000000..b99acd13 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/NullableType.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(Node $type, array $attributes = []) { + $this->attributes = $attributes; + $this->type = $type; + } + + public function getSubNodeNames(): array { + return ['type']; + } + + public function getType(): string { + return 'NullableType'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Param.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Param.php new file mode 100644 index 00000000..0e9ff0e2 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Param.php @@ -0,0 +1,84 @@ + $attributes Additional attributes + * @param int $flags Optional visibility flags + * @param list $attrGroups PHP attribute groups + */ + public function __construct( + Expr $var, ?Expr $default = null, ?Node $type = null, + bool $byRef = false, bool $variadic = false, + array $attributes = [], + int $flags = 0, + array $attrGroups = [] + ) { + $this->attributes = $attributes; + $this->type = $type; + $this->byRef = $byRef; + $this->variadic = $variadic; + $this->var = $var; + $this->default = $default; + $this->flags = $flags; + $this->attrGroups = $attrGroups; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'flags', 'type', 'byRef', 'variadic', 'var', 'default']; + } + + public function getType(): string { + return 'Param'; + } + + /** + * Whether this parameter uses constructor property promotion. + */ + public function isPromoted(): bool { + return $this->flags !== 0; + } + + public function isPublic(): bool { + return (bool) ($this->flags & Modifiers::PUBLIC); + } + + public function isProtected(): bool { + return (bool) ($this->flags & Modifiers::PROTECTED); + } + + public function isPrivate(): bool { + return (bool) ($this->flags & Modifiers::PRIVATE); + } + + public function isReadonly(): bool { + return (bool) ($this->flags & Modifiers::READONLY); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/PropertyItem.php b/vendor/nikic/php-parser/lib/PhpParser/Node/PropertyItem.php new file mode 100644 index 00000000..101611e6 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/PropertyItem.php @@ -0,0 +1,37 @@ + $attributes Additional attributes + */ + public function __construct($name, ?Node\Expr $default = null, array $attributes = []) { + $this->attributes = $attributes; + $this->name = \is_string($name) ? new Node\VarLikeIdentifier($name) : $name; + $this->default = $default; + } + + public function getSubNodeNames(): array { + return ['name', 'default']; + } + + public function getType(): string { + return 'PropertyItem'; + } +} + +// @deprecated compatibility alias +class_alias(PropertyItem::class, Stmt\PropertyProperty::class); diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar.php new file mode 100644 index 00000000..3df25721 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar.php @@ -0,0 +1,6 @@ + $attributes Additional attributes + */ + public function __construct(float $value, array $attributes = []) { + $this->attributes = $attributes; + $this->value = $value; + } + + public function getSubNodeNames(): array { + return ['value']; + } + + /** + * @param mixed[] $attributes + */ + public static function fromString(string $str, array $attributes = []): Float_ { + $attributes['rawValue'] = $str; + $float = self::parse($str); + + return new Float_($float, $attributes); + } + + /** + * @internal + * + * Parses a DNUMBER token like PHP would. + * + * @param string $str A string number + * + * @return float The parsed number + */ + public static function parse(string $str): float { + $str = str_replace('_', '', $str); + + // Check whether this is one of the special integer notations. + if ('0' === $str[0]) { + // hex + if ('x' === $str[1] || 'X' === $str[1]) { + return hexdec($str); + } + + // bin + if ('b' === $str[1] || 'B' === $str[1]) { + return bindec($str); + } + + // oct, but only if the string does not contain any of '.eE'. + if (false === strpbrk($str, '.eE')) { + // substr($str, 0, strcspn($str, '89')) cuts the string at the first invalid digit + // (8 or 9) so that only the digits before that are used. + return octdec(substr($str, 0, strcspn($str, '89'))); + } + } + + // dec + return (float) $str; + } + + public function getType(): string { + return 'Scalar_Float'; + } +} + +// @deprecated compatibility alias +class_alias(Float_::class, DNumber::class); diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/Int_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/Int_.php new file mode 100644 index 00000000..bcc257a6 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/Int_.php @@ -0,0 +1,82 @@ + $attributes Additional attributes + */ + public function __construct(int $value, array $attributes = []) { + $this->attributes = $attributes; + $this->value = $value; + } + + public function getSubNodeNames(): array { + return ['value']; + } + + /** + * Constructs an Int node from a string number literal. + * + * @param string $str String number literal (decimal, octal, hex or binary) + * @param array $attributes Additional attributes + * @param bool $allowInvalidOctal Whether to allow invalid octal numbers (PHP 5) + * + * @return Int_ The constructed LNumber, including kind attribute + */ + public static function fromString(string $str, array $attributes = [], bool $allowInvalidOctal = false): Int_ { + $attributes['rawValue'] = $str; + + $str = str_replace('_', '', $str); + + if ('0' !== $str[0] || '0' === $str) { + $attributes['kind'] = Int_::KIND_DEC; + return new Int_((int) $str, $attributes); + } + + if ('x' === $str[1] || 'X' === $str[1]) { + $attributes['kind'] = Int_::KIND_HEX; + return new Int_(hexdec($str), $attributes); + } + + if ('b' === $str[1] || 'B' === $str[1]) { + $attributes['kind'] = Int_::KIND_BIN; + return new Int_(bindec($str), $attributes); + } + + if (!$allowInvalidOctal && strpbrk($str, '89')) { + throw new Error('Invalid numeric literal', $attributes); + } + + // Strip optional explicit octal prefix. + if ('o' === $str[1] || 'O' === $str[1]) { + $str = substr($str, 2); + } + + // use intval instead of octdec to get proper cutting behavior with malformed numbers + $attributes['kind'] = Int_::KIND_OCT; + return new Int_(intval($str, 8), $attributes); + } + + public function getType(): string { + return 'Scalar_Int'; + } +} + +// @deprecated compatibility alias +class_alias(Int_::class, LNumber::class); diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/InterpolatedString.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/InterpolatedString.php new file mode 100644 index 00000000..9336dfe4 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/InterpolatedString.php @@ -0,0 +1,34 @@ + $attributes Additional attributes + */ + public function __construct(array $parts, array $attributes = []) { + $this->attributes = $attributes; + $this->parts = $parts; + } + + public function getSubNodeNames(): array { + return ['parts']; + } + + public function getType(): string { + return 'Scalar_InterpolatedString'; + } +} + +// @deprecated compatibility alias +class_alias(InterpolatedString::class, Encapsed::class); diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.php new file mode 100644 index 00000000..cfe8c8c1 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.php @@ -0,0 +1,3 @@ + $attributes Additional attributes + */ + public function __construct(array $attributes = []) { + $this->attributes = $attributes; + } + + public function getSubNodeNames(): array { + return []; + } + + /** + * Get name of magic constant. + * + * @return string Name of magic constant + */ + abstract public function getName(): string; +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Class_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Class_.php new file mode 100644 index 00000000..732ed140 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Class_.php @@ -0,0 +1,15 @@ + Escaped character to its decoded value */ + protected static array $replacements = [ + '\\' => '\\', + '$' => '$', + 'n' => "\n", + 'r' => "\r", + 't' => "\t", + 'f' => "\f", + 'v' => "\v", + 'e' => "\x1B", + ]; + + /** + * Constructs a string scalar node. + * + * @param string $value Value of the string + * @param array $attributes Additional attributes + */ + public function __construct(string $value, array $attributes = []) { + $this->attributes = $attributes; + $this->value = $value; + } + + public function getSubNodeNames(): array { + return ['value']; + } + + /** + * @param array $attributes + * @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes + */ + public static function fromString(string $str, array $attributes = [], bool $parseUnicodeEscape = true): self { + $attributes['kind'] = ($str[0] === "'" || ($str[1] === "'" && ($str[0] === 'b' || $str[0] === 'B'))) + ? Scalar\String_::KIND_SINGLE_QUOTED + : Scalar\String_::KIND_DOUBLE_QUOTED; + + $attributes['rawValue'] = $str; + + $string = self::parse($str, $parseUnicodeEscape); + + return new self($string, $attributes); + } + + /** + * @internal + * + * Parses a string token. + * + * @param string $str String token content + * @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes + * + * @return string The parsed string + */ + public static function parse(string $str, bool $parseUnicodeEscape = true): string { + $bLength = 0; + if ('b' === $str[0] || 'B' === $str[0]) { + $bLength = 1; + } + + if ('\'' === $str[$bLength]) { + return str_replace( + ['\\\\', '\\\''], + ['\\', '\''], + substr($str, $bLength + 1, -1) + ); + } else { + return self::parseEscapeSequences( + substr($str, $bLength + 1, -1), '"', $parseUnicodeEscape + ); + } + } + + /** + * @internal + * + * Parses escape sequences in strings (all string types apart from single quoted). + * + * @param string $str String without quotes + * @param null|string $quote Quote type + * @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes + * + * @return string String with escape sequences parsed + */ + public static function parseEscapeSequences(string $str, ?string $quote, bool $parseUnicodeEscape = true): string { + if (null !== $quote) { + $str = str_replace('\\' . $quote, $quote, $str); + } + + $extra = ''; + if ($parseUnicodeEscape) { + $extra = '|u\{([0-9a-fA-F]+)\}'; + } + + return preg_replace_callback( + '~\\\\([\\\\$nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3}' . $extra . ')~', + function ($matches) { + $str = $matches[1]; + + if (isset(self::$replacements[$str])) { + return self::$replacements[$str]; + } + if ('x' === $str[0] || 'X' === $str[0]) { + return chr(hexdec(substr($str, 1))); + } + if ('u' === $str[0]) { + $dec = hexdec($matches[2]); + // If it overflowed to float, treat as INT_MAX, it will throw an error anyway. + return self::codePointToUtf8(\is_int($dec) ? $dec : \PHP_INT_MAX); + } else { + return chr(octdec($str)); + } + }, + $str + ); + } + + /** + * Converts a Unicode code point to its UTF-8 encoded representation. + * + * @param int $num Code point + * + * @return string UTF-8 representation of code point + */ + private static function codePointToUtf8(int $num): string { + if ($num <= 0x7F) { + return chr($num); + } + if ($num <= 0x7FF) { + return chr(($num >> 6) + 0xC0) . chr(($num & 0x3F) + 0x80); + } + if ($num <= 0xFFFF) { + return chr(($num >> 12) + 0xE0) . chr((($num >> 6) & 0x3F) + 0x80) . chr(($num & 0x3F) + 0x80); + } + if ($num <= 0x1FFFFF) { + return chr(($num >> 18) + 0xF0) . chr((($num >> 12) & 0x3F) + 0x80) + . chr((($num >> 6) & 0x3F) + 0x80) . chr(($num & 0x3F) + 0x80); + } + throw new Error('Invalid UTF-8 codepoint escape sequence: Codepoint too large'); + } + + public function getType(): string { + return 'Scalar_String'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/StaticVar.php b/vendor/nikic/php-parser/lib/PhpParser/Node/StaticVar.php new file mode 100644 index 00000000..517c0edd --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/StaticVar.php @@ -0,0 +1,39 @@ + $attributes Additional attributes + */ + public function __construct( + Expr\Variable $var, ?Node\Expr $default = null, array $attributes = [] + ) { + $this->attributes = $attributes; + $this->var = $var; + $this->default = $default; + } + + public function getSubNodeNames(): array { + return ['var', 'default']; + } + + public function getType(): string { + return 'StaticVar'; + } +} + +// @deprecated compatibility alias +class_alias(StaticVar::class, Stmt\StaticVar::class); diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt.php new file mode 100644 index 00000000..481d31a9 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt.php @@ -0,0 +1,8 @@ + $attributes Additional attributes + */ + public function __construct(array $stmts, array $attributes = []) { + $this->attributes = $attributes; + $this->stmts = $stmts; + } + + public function getType(): string { + return 'Stmt_Block'; + } + + public function getSubNodeNames(): array { + return ['stmts']; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Break_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Break_.php new file mode 100644 index 00000000..d2bcc5eb --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Break_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(?Node\Expr $num = null, array $attributes = []) { + $this->attributes = $attributes; + $this->num = $num; + } + + public function getSubNodeNames(): array { + return ['num']; + } + + public function getType(): string { + return 'Stmt_Break'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Case_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Case_.php new file mode 100644 index 00000000..a06ca183 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Case_.php @@ -0,0 +1,33 @@ + $attributes Additional attributes + */ + public function __construct(?Node\Expr $cond, array $stmts = [], array $attributes = []) { + $this->attributes = $attributes; + $this->cond = $cond; + $this->stmts = $stmts; + } + + public function getSubNodeNames(): array { + return ['cond', 'stmts']; + } + + public function getType(): string { + return 'Stmt_Case'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Catch_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Catch_.php new file mode 100644 index 00000000..e8d39c9c --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Catch_.php @@ -0,0 +1,40 @@ + $attributes Additional attributes + */ + public function __construct( + array $types, ?Expr\Variable $var = null, array $stmts = [], array $attributes = [] + ) { + $this->attributes = $attributes; + $this->types = $types; + $this->var = $var; + $this->stmts = $stmts; + } + + public function getSubNodeNames(): array { + return ['types', 'var', 'stmts']; + } + + public function getType(): string { + return 'Stmt_Catch'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassConst.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassConst.php new file mode 100644 index 00000000..9bdce1f1 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassConst.php @@ -0,0 +1,77 @@ + $attributes Additional attributes + * @param list $attrGroups PHP attribute groups + * @param null|Node\Identifier|Node\Name|Node\ComplexType $type Type declaration + */ + public function __construct( + array $consts, + int $flags = 0, + array $attributes = [], + array $attrGroups = [], + ?Node $type = null + ) { + $this->attributes = $attributes; + $this->flags = $flags; + $this->consts = $consts; + $this->attrGroups = $attrGroups; + $this->type = $type; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'flags', 'type', 'consts']; + } + + /** + * Whether constant is explicitly or implicitly public. + */ + public function isPublic(): bool { + return ($this->flags & Modifiers::PUBLIC) !== 0 + || ($this->flags & Modifiers::VISIBILITY_MASK) === 0; + } + + /** + * Whether constant is protected. + */ + public function isProtected(): bool { + return (bool) ($this->flags & Modifiers::PROTECTED); + } + + /** + * Whether constant is private. + */ + public function isPrivate(): bool { + return (bool) ($this->flags & Modifiers::PRIVATE); + } + + /** + * Whether constant is final. + */ + public function isFinal(): bool { + return (bool) ($this->flags & Modifiers::FINAL); + } + + public function getType(): string { + return 'Stmt_ClassConst'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassLike.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassLike.php new file mode 100644 index 00000000..fb9ba4f5 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassLike.php @@ -0,0 +1,109 @@ +stmts as $stmt) { + if ($stmt instanceof TraitUse) { + $traitUses[] = $stmt; + } + } + return $traitUses; + } + + /** + * @return ClassConst[] + */ + public function getConstants(): array { + $constants = []; + foreach ($this->stmts as $stmt) { + if ($stmt instanceof ClassConst) { + $constants[] = $stmt; + } + } + return $constants; + } + + /** + * @return Property[] + */ + public function getProperties(): array { + $properties = []; + foreach ($this->stmts as $stmt) { + if ($stmt instanceof Property) { + $properties[] = $stmt; + } + } + return $properties; + } + + /** + * Gets property with the given name defined directly in this class/interface/trait. + * + * @param string $name Name of the property + * + * @return Property|null Property node or null if the property does not exist + */ + public function getProperty(string $name): ?Property { + foreach ($this->stmts as $stmt) { + if ($stmt instanceof Property) { + foreach ($stmt->props as $prop) { + if ($prop instanceof PropertyItem && $name === $prop->name->toString()) { + return $stmt; + } + } + } + } + return null; + } + + /** + * Gets all methods defined directly in this class/interface/trait + * + * @return ClassMethod[] + */ + public function getMethods(): array { + $methods = []; + foreach ($this->stmts as $stmt) { + if ($stmt instanceof ClassMethod) { + $methods[] = $stmt; + } + } + return $methods; + } + + /** + * Gets method with the given name defined directly in this class/interface/trait. + * + * @param string $name Name of the method (compared case-insensitively) + * + * @return ClassMethod|null Method node or null if the method does not exist + */ + public function getMethod(string $name): ?ClassMethod { + $lowerName = strtolower($name); + foreach ($this->stmts as $stmt) { + if ($stmt instanceof ClassMethod && $lowerName === $stmt->name->toLowerString()) { + return $stmt; + } + } + return null; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassMethod.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassMethod.php new file mode 100644 index 00000000..59c0519e --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassMethod.php @@ -0,0 +1,154 @@ + */ + private static array $magicNames = [ + '__construct' => true, + '__destruct' => true, + '__call' => true, + '__callstatic' => true, + '__get' => true, + '__set' => true, + '__isset' => true, + '__unset' => true, + '__sleep' => true, + '__wakeup' => true, + '__tostring' => true, + '__set_state' => true, + '__clone' => true, + '__invoke' => true, + '__debuginfo' => true, + '__serialize' => true, + '__unserialize' => true, + ]; + + /** + * Constructs a class method node. + * + * @param string|Node\Identifier $name Name + * @param array{ + * flags?: int, + * byRef?: bool, + * params?: Node\Param[], + * returnType?: null|Node\Identifier|Node\Name|Node\ComplexType, + * stmts?: Node\Stmt[]|null, + * attrGroups?: Node\AttributeGroup[], + * } $subNodes Array of the following optional subnodes: + * 'flags => 0 : Flags + * 'byRef' => false : Whether to return by reference + * 'params' => array() : Parameters + * 'returnType' => null : Return type + * 'stmts' => array() : Statements + * 'attrGroups' => array() : PHP attribute groups + * @param array $attributes Additional attributes + */ + public function __construct($name, array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->flags = $subNodes['flags'] ?? $subNodes['type'] ?? 0; + $this->byRef = $subNodes['byRef'] ?? false; + $this->name = \is_string($name) ? new Node\Identifier($name) : $name; + $this->params = $subNodes['params'] ?? []; + $this->returnType = $subNodes['returnType'] ?? null; + $this->stmts = array_key_exists('stmts', $subNodes) ? $subNodes['stmts'] : []; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'flags', 'byRef', 'name', 'params', 'returnType', 'stmts']; + } + + public function returnsByRef(): bool { + return $this->byRef; + } + + public function getParams(): array { + return $this->params; + } + + public function getReturnType() { + return $this->returnType; + } + + public function getStmts(): ?array { + return $this->stmts; + } + + public function getAttrGroups(): array { + return $this->attrGroups; + } + + /** + * Whether the method is explicitly or implicitly public. + */ + public function isPublic(): bool { + return ($this->flags & Modifiers::PUBLIC) !== 0 + || ($this->flags & Modifiers::VISIBILITY_MASK) === 0; + } + + /** + * Whether the method is protected. + */ + public function isProtected(): bool { + return (bool) ($this->flags & Modifiers::PROTECTED); + } + + /** + * Whether the method is private. + */ + public function isPrivate(): bool { + return (bool) ($this->flags & Modifiers::PRIVATE); + } + + /** + * Whether the method is abstract. + */ + public function isAbstract(): bool { + return (bool) ($this->flags & Modifiers::ABSTRACT); + } + + /** + * Whether the method is final. + */ + public function isFinal(): bool { + return (bool) ($this->flags & Modifiers::FINAL); + } + + /** + * Whether the method is static. + */ + public function isStatic(): bool { + return (bool) ($this->flags & Modifiers::STATIC); + } + + /** + * Whether the method is magic. + */ + public function isMagic(): bool { + return isset(self::$magicNames[$this->name->toLowerString()]); + } + + public function getType(): string { + return 'Stmt_ClassMethod'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Class_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Class_.php new file mode 100644 index 00000000..3f492b7b --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Class_.php @@ -0,0 +1,94 @@ + 0 : Flags + * 'extends' => null : Name of extended class + * 'implements' => array(): Names of implemented interfaces + * 'stmts' => array(): Statements + * 'attrGroups' => array(): PHP attribute groups + * @param array $attributes Additional attributes + */ + public function __construct($name, array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->flags = $subNodes['flags'] ?? $subNodes['type'] ?? 0; + $this->name = \is_string($name) ? new Node\Identifier($name) : $name; + $this->extends = $subNodes['extends'] ?? null; + $this->implements = $subNodes['implements'] ?? []; + $this->stmts = $subNodes['stmts'] ?? []; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'flags', 'name', 'extends', 'implements', 'stmts']; + } + + /** + * Whether the class is explicitly abstract. + */ + public function isAbstract(): bool { + return (bool) ($this->flags & Modifiers::ABSTRACT); + } + + /** + * Whether the class is final. + */ + public function isFinal(): bool { + return (bool) ($this->flags & Modifiers::FINAL); + } + + public function isReadonly(): bool { + return (bool) ($this->flags & Modifiers::READONLY); + } + + /** + * Whether the class is anonymous. + */ + public function isAnonymous(): bool { + return null === $this->name; + } + + public function getType(): string { + return 'Stmt_Class'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Const_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Const_.php new file mode 100644 index 00000000..f1165fd0 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Const_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(array $consts, array $attributes = []) { + $this->attributes = $attributes; + $this->consts = $consts; + } + + public function getSubNodeNames(): array { + return ['consts']; + } + + public function getType(): string { + return 'Stmt_Const'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Continue_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Continue_.php new file mode 100644 index 00000000..54e979dd --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Continue_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(?Node\Expr $num = null, array $attributes = []) { + $this->attributes = $attributes; + $this->num = $num; + } + + public function getSubNodeNames(): array { + return ['num']; + } + + public function getType(): string { + return 'Stmt_Continue'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/DeclareDeclare.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/DeclareDeclare.php new file mode 100644 index 00000000..cb9e8376 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/DeclareDeclare.php @@ -0,0 +1,3 @@ + $attributes Additional attributes + */ + public function __construct(array $declares, ?array $stmts = null, array $attributes = []) { + $this->attributes = $attributes; + $this->declares = $declares; + $this->stmts = $stmts; + } + + public function getSubNodeNames(): array { + return ['declares', 'stmts']; + } + + public function getType(): string { + return 'Stmt_Declare'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Do_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Do_.php new file mode 100644 index 00000000..61244428 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Do_.php @@ -0,0 +1,33 @@ + $attributes Additional attributes + */ + public function __construct(Node\Expr $cond, array $stmts = [], array $attributes = []) { + $this->attributes = $attributes; + $this->cond = $cond; + $this->stmts = $stmts; + } + + public function getSubNodeNames(): array { + return ['stmts', 'cond']; + } + + public function getType(): string { + return 'Stmt_Do'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Echo_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Echo_.php new file mode 100644 index 00000000..4d424523 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Echo_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(array $exprs, array $attributes = []) { + $this->attributes = $attributes; + $this->exprs = $exprs; + } + + public function getSubNodeNames(): array { + return ['exprs']; + } + + public function getType(): string { + return 'Stmt_Echo'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ElseIf_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ElseIf_.php new file mode 100644 index 00000000..b26d59ce --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ElseIf_.php @@ -0,0 +1,33 @@ + $attributes Additional attributes + */ + public function __construct(Node\Expr $cond, array $stmts = [], array $attributes = []) { + $this->attributes = $attributes; + $this->cond = $cond; + $this->stmts = $stmts; + } + + public function getSubNodeNames(): array { + return ['cond', 'stmts']; + } + + public function getType(): string { + return 'Stmt_ElseIf'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Else_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Else_.php new file mode 100644 index 00000000..3d2b066e --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Else_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(array $stmts = [], array $attributes = []) { + $this->attributes = $attributes; + $this->stmts = $stmts; + } + + public function getSubNodeNames(): array { + return ['stmts']; + } + + public function getType(): string { + return 'Stmt_Else'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/EnumCase.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/EnumCase.php new file mode 100644 index 00000000..c071a0af --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/EnumCase.php @@ -0,0 +1,36 @@ + $attrGroups PHP attribute groups + * @param array $attributes Additional attributes + */ + public function __construct($name, ?Node\Expr $expr = null, array $attrGroups = [], array $attributes = []) { + parent::__construct($attributes); + $this->name = \is_string($name) ? new Node\Identifier($name) : $name; + $this->expr = $expr; + $this->attrGroups = $attrGroups; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'name', 'expr']; + } + + public function getType(): string { + return 'Stmt_EnumCase'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Enum_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Enum_.php new file mode 100644 index 00000000..7eea6a69 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Enum_.php @@ -0,0 +1,44 @@ + null : Scalar type + * 'implements' => array() : Names of implemented interfaces + * 'stmts' => array() : Statements + * 'attrGroups' => array() : PHP attribute groups + * @param array $attributes Additional attributes + */ + public function __construct($name, array $subNodes = [], array $attributes = []) { + $this->name = \is_string($name) ? new Node\Identifier($name) : $name; + $this->scalarType = $subNodes['scalarType'] ?? null; + $this->implements = $subNodes['implements'] ?? []; + $this->stmts = $subNodes['stmts'] ?? []; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + + parent::__construct($attributes); + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'name', 'scalarType', 'implements', 'stmts']; + } + + public function getType(): string { + return 'Stmt_Enum'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Expression.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Expression.php new file mode 100644 index 00000000..89751fa2 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Expression.php @@ -0,0 +1,32 @@ + $attributes Additional attributes + */ + public function __construct(Node\Expr $expr, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Stmt_Expression'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Finally_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Finally_.php new file mode 100644 index 00000000..69ecf253 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Finally_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(array $stmts = [], array $attributes = []) { + $this->attributes = $attributes; + $this->stmts = $stmts; + } + + public function getSubNodeNames(): array { + return ['stmts']; + } + + public function getType(): string { + return 'Stmt_Finally'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/For_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/For_.php new file mode 100644 index 00000000..6f2fbb9e --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/For_.php @@ -0,0 +1,47 @@ + array(): Init expressions + * 'cond' => array(): Loop conditions + * 'loop' => array(): Loop expressions + * 'stmts' => array(): Statements + * @param array $attributes Additional attributes + */ + public function __construct(array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->init = $subNodes['init'] ?? []; + $this->cond = $subNodes['cond'] ?? []; + $this->loop = $subNodes['loop'] ?? []; + $this->stmts = $subNodes['stmts'] ?? []; + } + + public function getSubNodeNames(): array { + return ['init', 'cond', 'loop', 'stmts']; + } + + public function getType(): string { + return 'Stmt_For'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Foreach_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Foreach_.php new file mode 100644 index 00000000..c5d9a8b1 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Foreach_.php @@ -0,0 +1,50 @@ + null : Variable to assign key to + * 'byRef' => false : Whether to assign value by reference + * 'stmts' => array(): Statements + * @param array $attributes Additional attributes + */ + public function __construct(Node\Expr $expr, Node\Expr $valueVar, array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + $this->keyVar = $subNodes['keyVar'] ?? null; + $this->byRef = $subNodes['byRef'] ?? false; + $this->valueVar = $valueVar; + $this->stmts = $subNodes['stmts'] ?? []; + } + + public function getSubNodeNames(): array { + return ['expr', 'keyVar', 'byRef', 'valueVar', 'stmts']; + } + + public function getType(): string { + return 'Stmt_Foreach'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Function_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Function_.php new file mode 100644 index 00000000..2111bab7 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Function_.php @@ -0,0 +1,81 @@ + false : Whether to return by reference + * 'params' => array(): Parameters + * 'returnType' => null : Return type + * 'stmts' => array(): Statements + * 'attrGroups' => array(): PHP attribute groups + * @param array $attributes Additional attributes + */ + public function __construct($name, array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->byRef = $subNodes['byRef'] ?? false; + $this->name = \is_string($name) ? new Node\Identifier($name) : $name; + $this->params = $subNodes['params'] ?? []; + $this->returnType = $subNodes['returnType'] ?? null; + $this->stmts = $subNodes['stmts'] ?? []; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'byRef', 'name', 'params', 'returnType', 'stmts']; + } + + public function returnsByRef(): bool { + return $this->byRef; + } + + public function getParams(): array { + return $this->params; + } + + public function getReturnType() { + return $this->returnType; + } + + public function getAttrGroups(): array { + return $this->attrGroups; + } + + /** @return Node\Stmt[] */ + public function getStmts(): array { + return $this->stmts; + } + + public function getType(): string { + return 'Stmt_Function'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Global_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Global_.php new file mode 100644 index 00000000..d3ab12fc --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Global_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(array $vars, array $attributes = []) { + $this->attributes = $attributes; + $this->vars = $vars; + } + + public function getSubNodeNames(): array { + return ['vars']; + } + + public function getType(): string { + return 'Stmt_Global'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Goto_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Goto_.php new file mode 100644 index 00000000..26a0d01e --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Goto_.php @@ -0,0 +1,30 @@ + $attributes Additional attributes + */ + public function __construct($name, array $attributes = []) { + $this->attributes = $attributes; + $this->name = \is_string($name) ? new Identifier($name) : $name; + } + + public function getSubNodeNames(): array { + return ['name']; + } + + public function getType(): string { + return 'Stmt_Goto'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/GroupUse.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/GroupUse.php new file mode 100644 index 00000000..0ec8e9d4 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/GroupUse.php @@ -0,0 +1,41 @@ + $attributes Additional attributes + */ + public function __construct(Name $prefix, array $uses, int $type = Use_::TYPE_NORMAL, array $attributes = []) { + $this->attributes = $attributes; + $this->type = $type; + $this->prefix = $prefix; + $this->uses = $uses; + } + + public function getSubNodeNames(): array { + return ['type', 'prefix', 'uses']; + } + + public function getType(): string { + return 'Stmt_GroupUse'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/HaltCompiler.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/HaltCompiler.php new file mode 100644 index 00000000..665bacde --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/HaltCompiler.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(string $remaining, array $attributes = []) { + $this->attributes = $attributes; + $this->remaining = $remaining; + } + + public function getSubNodeNames(): array { + return ['remaining']; + } + + public function getType(): string { + return 'Stmt_HaltCompiler'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/If_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/If_.php new file mode 100644 index 00000000..544390ff --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/If_.php @@ -0,0 +1,46 @@ + array(): Statements + * 'elseifs' => array(): Elseif clauses + * 'else' => null : Else clause + * @param array $attributes Additional attributes + */ + public function __construct(Node\Expr $cond, array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->cond = $cond; + $this->stmts = $subNodes['stmts'] ?? []; + $this->elseifs = $subNodes['elseifs'] ?? []; + $this->else = $subNodes['else'] ?? null; + } + + public function getSubNodeNames(): array { + return ['cond', 'stmts', 'elseifs', 'else']; + } + + public function getType(): string { + return 'Stmt_If'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/InlineHTML.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/InlineHTML.php new file mode 100644 index 00000000..0515d020 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/InlineHTML.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(string $value, array $attributes = []) { + $this->attributes = $attributes; + $this->value = $value; + } + + public function getSubNodeNames(): array { + return ['value']; + } + + public function getType(): string { + return 'Stmt_InlineHTML'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Interface_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Interface_.php new file mode 100644 index 00000000..9359064f --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Interface_.php @@ -0,0 +1,40 @@ + array(): Name of extended interfaces + * 'stmts' => array(): Statements + * 'attrGroups' => array(): PHP attribute groups + * @param array $attributes Additional attributes + */ + public function __construct($name, array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->name = \is_string($name) ? new Node\Identifier($name) : $name; + $this->extends = $subNodes['extends'] ?? []; + $this->stmts = $subNodes['stmts'] ?? []; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'name', 'extends', 'stmts']; + } + + public function getType(): string { + return 'Stmt_Interface'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Label.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Label.php new file mode 100644 index 00000000..658468d2 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Label.php @@ -0,0 +1,30 @@ + $attributes Additional attributes + */ + public function __construct($name, array $attributes = []) { + $this->attributes = $attributes; + $this->name = \is_string($name) ? new Identifier($name) : $name; + } + + public function getSubNodeNames(): array { + return ['name']; + } + + public function getType(): string { + return 'Stmt_Label'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Namespace_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Namespace_.php new file mode 100644 index 00000000..f5b59ad6 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Namespace_.php @@ -0,0 +1,37 @@ + $attributes Additional attributes + */ + public function __construct(?Node\Name $name = null, ?array $stmts = [], array $attributes = []) { + $this->attributes = $attributes; + $this->name = $name; + $this->stmts = $stmts; + } + + public function getSubNodeNames(): array { + return ['name', 'stmts']; + } + + public function getType(): string { + return 'Stmt_Namespace'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Nop.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Nop.php new file mode 100644 index 00000000..3acfa46f --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Nop.php @@ -0,0 +1,16 @@ + $attributes Additional attributes + * @param null|Identifier|Name|ComplexType $type Type declaration + * @param Node\AttributeGroup[] $attrGroups PHP attribute groups + */ + public function __construct(int $flags, array $props, array $attributes = [], ?Node $type = null, array $attrGroups = []) { + $this->attributes = $attributes; + $this->flags = $flags; + $this->props = $props; + $this->type = $type; + $this->attrGroups = $attrGroups; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'flags', 'type', 'props']; + } + + /** + * Whether the property is explicitly or implicitly public. + */ + public function isPublic(): bool { + return ($this->flags & Modifiers::PUBLIC) !== 0 + || ($this->flags & Modifiers::VISIBILITY_MASK) === 0; + } + + /** + * Whether the property is protected. + */ + public function isProtected(): bool { + return (bool) ($this->flags & Modifiers::PROTECTED); + } + + /** + * Whether the property is private. + */ + public function isPrivate(): bool { + return (bool) ($this->flags & Modifiers::PRIVATE); + } + + /** + * Whether the property is static. + */ + public function isStatic(): bool { + return (bool) ($this->flags & Modifiers::STATIC); + } + + /** + * Whether the property is readonly. + */ + public function isReadonly(): bool { + return (bool) ($this->flags & Modifiers::READONLY); + } + + public function getType(): string { + return 'Stmt_Property'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/PropertyProperty.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/PropertyProperty.php new file mode 100644 index 00000000..4a21a880 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/PropertyProperty.php @@ -0,0 +1,3 @@ + $attributes Additional attributes + */ + public function __construct(?Node\Expr $expr = null, array $attributes = []) { + $this->attributes = $attributes; + $this->expr = $expr; + } + + public function getSubNodeNames(): array { + return ['expr']; + } + + public function getType(): string { + return 'Stmt_Return'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/StaticVar.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/StaticVar.php new file mode 100644 index 00000000..88452e7f --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/StaticVar.php @@ -0,0 +1,3 @@ + $attributes Additional attributes + */ + public function __construct(array $vars, array $attributes = []) { + $this->attributes = $attributes; + $this->vars = $vars; + } + + public function getSubNodeNames(): array { + return ['vars']; + } + + public function getType(): string { + return 'Stmt_Static'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Switch_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Switch_.php new file mode 100644 index 00000000..21e5efa5 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Switch_.php @@ -0,0 +1,33 @@ + $attributes Additional attributes + */ + public function __construct(Node\Expr $cond, array $cases, array $attributes = []) { + $this->attributes = $attributes; + $this->cond = $cond; + $this->cases = $cases; + } + + public function getSubNodeNames(): array { + return ['cond', 'cases']; + } + + public function getType(): string { + return 'Stmt_Switch'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUse.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUse.php new file mode 100644 index 00000000..7705a570 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUse.php @@ -0,0 +1,33 @@ + $attributes Additional attributes + */ + public function __construct(array $traits, array $adaptations = [], array $attributes = []) { + $this->attributes = $attributes; + $this->traits = $traits; + $this->adaptations = $adaptations; + } + + public function getSubNodeNames(): array { + return ['traits', 'adaptations']; + } + + public function getType(): string { + return 'Stmt_TraitUse'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation.php new file mode 100644 index 00000000..987bc88e --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation.php @@ -0,0 +1,12 @@ + $attributes Additional attributes + */ + public function __construct(?Node\Name $trait, $method, ?int $newModifier, $newName, array $attributes = []) { + $this->attributes = $attributes; + $this->trait = $trait; + $this->method = \is_string($method) ? new Node\Identifier($method) : $method; + $this->newModifier = $newModifier; + $this->newName = \is_string($newName) ? new Node\Identifier($newName) : $newName; + } + + public function getSubNodeNames(): array { + return ['trait', 'method', 'newModifier', 'newName']; + } + + public function getType(): string { + return 'Stmt_TraitUseAdaptation_Alias'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php new file mode 100644 index 00000000..7bc40837 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php @@ -0,0 +1,33 @@ + $attributes Additional attributes + */ + public function __construct(Node\Name $trait, $method, array $insteadof, array $attributes = []) { + $this->attributes = $attributes; + $this->trait = $trait; + $this->method = \is_string($method) ? new Node\Identifier($method) : $method; + $this->insteadof = $insteadof; + } + + public function getSubNodeNames(): array { + return ['trait', 'method', 'insteadof']; + } + + public function getType(): string { + return 'Stmt_TraitUseAdaptation_Precedence'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Trait_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Trait_.php new file mode 100644 index 00000000..5f2b3307 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Trait_.php @@ -0,0 +1,34 @@ + array(): Statements + * 'attrGroups' => array(): PHP attribute groups + * @param array $attributes Additional attributes + */ + public function __construct($name, array $subNodes = [], array $attributes = []) { + $this->attributes = $attributes; + $this->name = \is_string($name) ? new Node\Identifier($name) : $name; + $this->stmts = $subNodes['stmts'] ?? []; + $this->attrGroups = $subNodes['attrGroups'] ?? []; + } + + public function getSubNodeNames(): array { + return ['attrGroups', 'name', 'stmts']; + } + + public function getType(): string { + return 'Stmt_Trait'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TryCatch.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TryCatch.php new file mode 100644 index 00000000..6414c46c --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TryCatch.php @@ -0,0 +1,37 @@ + $attributes Additional attributes + */ + public function __construct(array $stmts, array $catches, ?Finally_ $finally = null, array $attributes = []) { + $this->attributes = $attributes; + $this->stmts = $stmts; + $this->catches = $catches; + $this->finally = $finally; + } + + public function getSubNodeNames(): array { + return ['stmts', 'catches', 'finally']; + } + + public function getType(): string { + return 'Stmt_TryCatch'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Unset_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Unset_.php new file mode 100644 index 00000000..c211beb0 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Unset_.php @@ -0,0 +1,29 @@ + $attributes Additional attributes + */ + public function __construct(array $vars, array $attributes = []) { + $this->attributes = $attributes; + $this->vars = $vars; + } + + public function getSubNodeNames(): array { + return ['vars']; + } + + public function getType(): string { + return 'Stmt_Unset'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/UseUse.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/UseUse.php new file mode 100644 index 00000000..85830edc --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/UseUse.php @@ -0,0 +1,3 @@ + $attributes Additional attributes + */ + public function __construct(array $uses, int $type = self::TYPE_NORMAL, array $attributes = []) { + $this->attributes = $attributes; + $this->type = $type; + $this->uses = $uses; + } + + public function getSubNodeNames(): array { + return ['type', 'uses']; + } + + public function getType(): string { + return 'Stmt_Use'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/While_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/While_.php new file mode 100644 index 00000000..2f7aed23 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/While_.php @@ -0,0 +1,33 @@ + $attributes Additional attributes + */ + public function __construct(Node\Expr $cond, array $stmts = [], array $attributes = []) { + $this->attributes = $attributes; + $this->cond = $cond; + $this->stmts = $stmts; + } + + public function getSubNodeNames(): array { + return ['cond', 'stmts']; + } + + public function getType(): string { + return 'Stmt_While'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/UnionType.php b/vendor/nikic/php-parser/lib/PhpParser/Node/UnionType.php new file mode 100644 index 00000000..bad88d2b --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/UnionType.php @@ -0,0 +1,27 @@ + $attributes Additional attributes + */ + public function __construct(array $types, array $attributes = []) { + $this->attributes = $attributes; + $this->types = $types; + } + + public function getSubNodeNames(): array { + return ['types']; + } + + public function getType(): string { + return 'UnionType'; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/UseItem.php b/vendor/nikic/php-parser/lib/PhpParser/Node/UseItem.php new file mode 100644 index 00000000..a7d9fc44 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/UseItem.php @@ -0,0 +1,55 @@ + $attributes Additional attributes + */ + public function __construct(Node\Name $name, $alias = null, int $type = Use_::TYPE_UNKNOWN, array $attributes = []) { + $this->attributes = $attributes; + $this->type = $type; + $this->name = $name; + $this->alias = \is_string($alias) ? new Identifier($alias) : $alias; + } + + public function getSubNodeNames(): array { + return ['type', 'name', 'alias']; + } + + /** + * Get alias. If not explicitly given this is the last component of the used name. + */ + public function getAlias(): Identifier { + if (null !== $this->alias) { + return $this->alias; + } + + return new Identifier($this->name->getLast()); + } + + public function getType(): string { + return 'UseItem'; + } +} + +// @deprecated compatibility alias +class_alias(UseItem::class, Stmt\UseUse::class); diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/VarLikeIdentifier.php b/vendor/nikic/php-parser/lib/PhpParser/Node/VarLikeIdentifier.php new file mode 100644 index 00000000..9baa6fe0 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Node/VarLikeIdentifier.php @@ -0,0 +1,16 @@ + $attributes Additional attributes + */ + public function __construct(array $attributes = []) { + $this->attributes = $attributes; + } + + public function getType(): string { + return 'VariadicPlaceholder'; + } + + public function getSubNodeNames(): array { + return []; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeAbstract.php b/vendor/nikic/php-parser/lib/PhpParser/NodeAbstract.php new file mode 100644 index 00000000..7c3a3607 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeAbstract.php @@ -0,0 +1,178 @@ + Attributes */ + protected array $attributes; + + /** + * Creates a Node. + * + * @param array $attributes Array of attributes + */ + public function __construct(array $attributes = []) { + $this->attributes = $attributes; + } + + /** + * Gets line the node started in (alias of getStartLine). + * + * @return int Start line (or -1 if not available) + */ + public function getLine(): int { + return $this->attributes['startLine'] ?? -1; + } + + /** + * Gets line the node started in. + * + * Requires the 'startLine' attribute to be enabled in the lexer (enabled by default). + * + * @return int Start line (or -1 if not available) + */ + public function getStartLine(): int { + return $this->attributes['startLine'] ?? -1; + } + + /** + * Gets the line the node ended in. + * + * Requires the 'endLine' attribute to be enabled in the lexer (enabled by default). + * + * @return int End line (or -1 if not available) + */ + public function getEndLine(): int { + return $this->attributes['endLine'] ?? -1; + } + + /** + * Gets the token offset of the first token that is part of this node. + * + * The offset is an index into the array returned by Lexer::getTokens(). + * + * Requires the 'startTokenPos' attribute to be enabled in the lexer (DISABLED by default). + * + * @return int Token start position (or -1 if not available) + */ + public function getStartTokenPos(): int { + return $this->attributes['startTokenPos'] ?? -1; + } + + /** + * Gets the token offset of the last token that is part of this node. + * + * The offset is an index into the array returned by Lexer::getTokens(). + * + * Requires the 'endTokenPos' attribute to be enabled in the lexer (DISABLED by default). + * + * @return int Token end position (or -1 if not available) + */ + public function getEndTokenPos(): int { + return $this->attributes['endTokenPos'] ?? -1; + } + + /** + * Gets the file offset of the first character that is part of this node. + * + * Requires the 'startFilePos' attribute to be enabled in the lexer (DISABLED by default). + * + * @return int File start position (or -1 if not available) + */ + public function getStartFilePos(): int { + return $this->attributes['startFilePos'] ?? -1; + } + + /** + * Gets the file offset of the last character that is part of this node. + * + * Requires the 'endFilePos' attribute to be enabled in the lexer (DISABLED by default). + * + * @return int File end position (or -1 if not available) + */ + public function getEndFilePos(): int { + return $this->attributes['endFilePos'] ?? -1; + } + + /** + * Gets all comments directly preceding this node. + * + * The comments are also available through the "comments" attribute. + * + * @return Comment[] + */ + public function getComments(): array { + return $this->attributes['comments'] ?? []; + } + + /** + * Gets the doc comment of the node. + * + * @return null|Comment\Doc Doc comment object or null + */ + public function getDocComment(): ?Comment\Doc { + $comments = $this->getComments(); + for ($i = count($comments) - 1; $i >= 0; $i--) { + $comment = $comments[$i]; + if ($comment instanceof Comment\Doc) { + return $comment; + } + } + + return null; + } + + /** + * Sets the doc comment of the node. + * + * This will either replace an existing doc comment or add it to the comments array. + * + * @param Comment\Doc $docComment Doc comment to set + */ + public function setDocComment(Comment\Doc $docComment): void { + $comments = $this->getComments(); + for ($i = count($comments) - 1; $i >= 0; $i--) { + if ($comments[$i] instanceof Comment\Doc) { + // Replace existing doc comment. + $comments[$i] = $docComment; + $this->setAttribute('comments', $comments); + return; + } + } + + // Append new doc comment. + $comments[] = $docComment; + $this->setAttribute('comments', $comments); + } + + public function setAttribute(string $key, $value): void { + $this->attributes[$key] = $value; + } + + public function hasAttribute(string $key): bool { + return array_key_exists($key, $this->attributes); + } + + public function getAttribute(string $key, $default = null) { + if (array_key_exists($key, $this->attributes)) { + return $this->attributes[$key]; + } + + return $default; + } + + public function getAttributes(): array { + return $this->attributes; + } + + public function setAttributes(array $attributes): void { + $this->attributes = $attributes; + } + + /** + * @return array + */ + public function jsonSerialize(): array { + return ['nodeType' => $this->getType()] + get_object_vars($this); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeDumper.php b/vendor/nikic/php-parser/lib/PhpParser/NodeDumper.php new file mode 100644 index 00000000..a2535de7 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeDumper.php @@ -0,0 +1,290 @@ + true, + 'startLine' => true, + 'endLine' => true, + 'startFilePos' => true, + 'endFilePos' => true, + 'startTokenPos' => true, + 'endTokenPos' => true, + ]; + + /** + * Constructs a NodeDumper. + * + * Supported options: + * * bool dumpComments: Whether comments should be dumped. + * * bool dumpPositions: Whether line/offset information should be dumped. To dump offset + * information, the code needs to be passed to dump(). + * * bool dumpOtherAttributes: Whether non-comment, non-position attributes should be dumped. + * + * @param array $options Options (see description) + */ + public function __construct(array $options = []) { + $this->dumpComments = !empty($options['dumpComments']); + $this->dumpPositions = !empty($options['dumpPositions']); + $this->dumpOtherAttributes = !empty($options['dumpOtherAttributes']); + } + + /** + * Dumps a node or array. + * + * @param array|Node $node Node or array to dump + * @param string|null $code Code corresponding to dumped AST. This only needs to be passed if + * the dumpPositions option is enabled and the dumping of node offsets + * is desired. + * + * @return string Dumped value + */ + public function dump($node, ?string $code = null): string { + $this->code = $code; + $this->res = ''; + $this->nl = "\n"; + $this->dumpRecursive($node, false); + return $this->res; + } + + /** @param mixed $node */ + protected function dumpRecursive($node, bool $indent = true): void { + if ($indent) { + $this->nl .= " "; + } + if ($node instanceof Node) { + $this->res .= $node->getType(); + if ($this->dumpPositions && null !== $p = $this->dumpPosition($node)) { + $this->res .= $p; + } + $this->res .= '('; + + foreach ($node->getSubNodeNames() as $key) { + $this->res .= "$this->nl " . $key . ': '; + + $value = $node->$key; + if (\is_int($value)) { + if ('flags' === $key || 'newModifier' === $key) { + $this->res .= $this->dumpFlags($value); + continue; + } + if ('type' === $key && $node instanceof Include_) { + $this->res .= $this->dumpIncludeType($value); + continue; + } + if ('type' === $key + && ($node instanceof Use_ || $node instanceof UseItem || $node instanceof GroupUse)) { + $this->res .= $this->dumpUseType($value); + continue; + } + } + $this->dumpRecursive($value); + } + + if ($this->dumpComments && $comments = $node->getComments()) { + $this->res .= "$this->nl comments: "; + $this->dumpRecursive($comments); + } + + if ($this->dumpOtherAttributes) { + foreach ($node->getAttributes() as $key => $value) { + if (isset(self::IGNORE_ATTRIBUTES[$key])) { + continue; + } + + $this->res .= "$this->nl $key: "; + if (\is_int($value)) { + if ('kind' === $key) { + if ($node instanceof Int_) { + $this->res .= $this->dumpIntKind($value); + continue; + } + if ($node instanceof String_ || $node instanceof InterpolatedString) { + $this->res .= $this->dumpStringKind($value); + continue; + } + if ($node instanceof Array_) { + $this->res .= $this->dumpArrayKind($value); + continue; + } + if ($node instanceof List_) { + $this->res .= $this->dumpListKind($value); + continue; + } + } + } + $this->dumpRecursive($value); + } + } + $this->res .= "$this->nl)"; + } elseif (\is_array($node)) { + $this->res .= 'array('; + foreach ($node as $key => $value) { + $this->res .= "$this->nl " . $key . ': '; + $this->dumpRecursive($value); + } + $this->res .= "$this->nl)"; + } elseif ($node instanceof Comment) { + $this->res .= \str_replace("\n", $this->nl, $node->getReformattedText()); + } elseif (\is_string($node)) { + $this->res .= \str_replace("\n", $this->nl, (string)$node); + } elseif (\is_int($node) || \is_float($node)) { + $this->res .= $node; + } elseif (null === $node) { + $this->res .= 'null'; + } elseif (false === $node) { + $this->res .= 'false'; + } elseif (true === $node) { + $this->res .= 'true'; + } else { + throw new \InvalidArgumentException('Can only dump nodes and arrays.'); + } + if ($indent) { + $this->nl = \substr($this->nl, 0, -4); + } + } + + protected function dumpFlags(int $flags): string { + $strs = []; + if ($flags & Modifiers::PUBLIC) { + $strs[] = 'PUBLIC'; + } + if ($flags & Modifiers::PROTECTED) { + $strs[] = 'PROTECTED'; + } + if ($flags & Modifiers::PRIVATE) { + $strs[] = 'PRIVATE'; + } + if ($flags & Modifiers::ABSTRACT) { + $strs[] = 'ABSTRACT'; + } + if ($flags & Modifiers::STATIC) { + $strs[] = 'STATIC'; + } + if ($flags & Modifiers::FINAL) { + $strs[] = 'FINAL'; + } + if ($flags & Modifiers::READONLY) { + $strs[] = 'READONLY'; + } + + if ($strs) { + return implode(' | ', $strs) . ' (' . $flags . ')'; + } else { + return (string) $flags; + } + } + + /** @param array $map */ + private function dumpEnum(int $value, array $map): string { + if (!isset($map[$value])) { + return (string) $value; + } + return $map[$value] . ' (' . $value . ')'; + } + + private function dumpIncludeType(int $type): string { + return $this->dumpEnum($type, [ + Include_::TYPE_INCLUDE => 'TYPE_INCLUDE', + Include_::TYPE_INCLUDE_ONCE => 'TYPE_INCLUDE_ONCE', + Include_::TYPE_REQUIRE => 'TYPE_REQUIRE', + Include_::TYPE_REQUIRE_ONCE => 'TYPE_REQUIRE_ONCE', + ]); + } + + private function dumpUseType(int $type): string { + return $this->dumpEnum($type, [ + Use_::TYPE_UNKNOWN => 'TYPE_UNKNOWN', + Use_::TYPE_NORMAL => 'TYPE_NORMAL', + Use_::TYPE_FUNCTION => 'TYPE_FUNCTION', + Use_::TYPE_CONSTANT => 'TYPE_CONSTANT', + ]); + } + + private function dumpIntKind(int $kind): string { + return $this->dumpEnum($kind, [ + Int_::KIND_BIN => 'KIND_BIN', + Int_::KIND_OCT => 'KIND_OCT', + Int_::KIND_DEC => 'KIND_DEC', + Int_::KIND_HEX => 'KIND_HEX', + ]); + } + + private function dumpStringKind(int $kind): string { + return $this->dumpEnum($kind, [ + String_::KIND_SINGLE_QUOTED => 'KIND_SINGLE_QUOTED', + String_::KIND_DOUBLE_QUOTED => 'KIND_DOUBLE_QUOTED', + String_::KIND_HEREDOC => 'KIND_HEREDOC', + String_::KIND_NOWDOC => 'KIND_NOWDOC', + ]); + } + + private function dumpArrayKind(int $kind): string { + return $this->dumpEnum($kind, [ + Array_::KIND_LONG => 'KIND_LONG', + Array_::KIND_SHORT => 'KIND_SHORT', + ]); + } + + private function dumpListKind(int $kind): string { + return $this->dumpEnum($kind, [ + List_::KIND_LIST => 'KIND_LIST', + List_::KIND_ARRAY => 'KIND_ARRAY', + ]); + } + + /** + * Dump node position, if possible. + * + * @param Node $node Node for which to dump position + * + * @return string|null Dump of position, or null if position information not available + */ + protected function dumpPosition(Node $node): ?string { + if (!$node->hasAttribute('startLine') || !$node->hasAttribute('endLine')) { + return null; + } + + $start = $node->getStartLine(); + $end = $node->getEndLine(); + if ($node->hasAttribute('startFilePos') && $node->hasAttribute('endFilePos') + && null !== $this->code + ) { + $start .= ':' . $this->toColumn($this->code, $node->getStartFilePos()); + $end .= ':' . $this->toColumn($this->code, $node->getEndFilePos()); + } + return "[$start - $end]"; + } + + // Copied from Error class + private function toColumn(string $code, int $pos): int { + if ($pos > strlen($code)) { + throw new \RuntimeException('Invalid position information'); + } + + $lineStartPos = strrpos($code, "\n", $pos - strlen($code)); + if (false === $lineStartPos) { + $lineStartPos = -1; + } + + return $pos - $lineStartPos; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeFinder.php b/vendor/nikic/php-parser/lib/PhpParser/NodeFinder.php new file mode 100644 index 00000000..96c84526 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeFinder.php @@ -0,0 +1,90 @@ +traverse($nodes); + + return $visitor->getFoundNodes(); + } + + /** + * Find all nodes that are instances of a certain class. + + * @template TNode as Node + * + * @param Node|Node[] $nodes Single node or array of nodes to search in + * @param class-string $class Class name + * + * @return TNode[] Found nodes (all instances of $class) + */ + public function findInstanceOf($nodes, string $class): array { + return $this->find($nodes, function ($node) use ($class) { + return $node instanceof $class; + }); + } + + /** + * Find first node satisfying a filter callback. + * + * @param Node|Node[] $nodes Single node or array of nodes to search in + * @param callable $filter Filter callback: function(Node $node) : bool + * + * @return null|Node Found node (or null if none found) + */ + public function findFirst($nodes, callable $filter): ?Node { + if ($nodes === []) { + return null; + } + + if (!is_array($nodes)) { + $nodes = [$nodes]; + } + + $visitor = new FirstFindingVisitor($filter); + + $traverser = new NodeTraverser($visitor); + $traverser->traverse($nodes); + + return $visitor->getFoundNode(); + } + + /** + * Find first node that is an instance of a certain class. + * + * @template TNode as Node + * + * @param Node|Node[] $nodes Single node or array of nodes to search in + * @param class-string $class Class name + * + * @return null|TNode Found node, which is an instance of $class (or null if none found) + */ + public function findFirstInstanceOf($nodes, string $class): ?Node { + return $this->findFirst($nodes, function ($node) use ($class) { + return $node instanceof $class; + }); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php b/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php new file mode 100644 index 00000000..f5b868a1 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php @@ -0,0 +1,278 @@ + Visitors */ + protected array $visitors = []; + + /** @var bool Whether traversal should be stopped */ + protected bool $stopTraversal; + + /** + * Create a traverser with the given visitors. + * + * @param NodeVisitor ...$visitors Node visitors + */ + public function __construct(NodeVisitor ...$visitors) { + $this->visitors = $visitors; + } + + /** + * Adds a visitor. + * + * @param NodeVisitor $visitor Visitor to add + */ + public function addVisitor(NodeVisitor $visitor): void { + $this->visitors[] = $visitor; + } + + /** + * Removes an added visitor. + */ + public function removeVisitor(NodeVisitor $visitor): void { + $index = array_search($visitor, $this->visitors); + if ($index !== false) { + array_splice($this->visitors, $index, 1, []); + } + } + + /** + * Traverses an array of nodes using the registered visitors. + * + * @param Node[] $nodes Array of nodes + * + * @return Node[] Traversed array of nodes + */ + public function traverse(array $nodes): array { + $this->stopTraversal = false; + + foreach ($this->visitors as $visitor) { + if (null !== $return = $visitor->beforeTraverse($nodes)) { + $nodes = $return; + } + } + + $nodes = $this->traverseArray($nodes); + + for ($i = \count($this->visitors) - 1; $i >= 0; --$i) { + $visitor = $this->visitors[$i]; + if (null !== $return = $visitor->afterTraverse($nodes)) { + $nodes = $return; + } + } + + return $nodes; + } + + /** + * Recursively traverse a node. + * + * @param Node $node Node to traverse. + */ + protected function traverseNode(Node $node): void { + foreach ($node->getSubNodeNames() as $name) { + $subNode = $node->$name; + + if (\is_array($subNode)) { + $node->$name = $this->traverseArray($subNode); + if ($this->stopTraversal) { + break; + } + } elseif ($subNode instanceof Node) { + $traverseChildren = true; + $visitorIndex = -1; + + foreach ($this->visitors as $visitorIndex => $visitor) { + $return = $visitor->enterNode($subNode); + if (null !== $return) { + if ($return instanceof Node) { + $this->ensureReplacementReasonable($subNode, $return); + $subNode = $node->$name = $return; + } elseif (NodeVisitor::DONT_TRAVERSE_CHILDREN === $return) { + $traverseChildren = false; + } elseif (NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) { + $traverseChildren = false; + break; + } elseif (NodeVisitor::STOP_TRAVERSAL === $return) { + $this->stopTraversal = true; + break 2; + } elseif (NodeVisitor::REPLACE_WITH_NULL === $return) { + $node->$name = null; + continue 2; + } else { + throw new \LogicException( + 'enterNode() returned invalid value of type ' . gettype($return) + ); + } + } + } + + if ($traverseChildren) { + $this->traverseNode($subNode); + if ($this->stopTraversal) { + break; + } + } + + for (; $visitorIndex >= 0; --$visitorIndex) { + $visitor = $this->visitors[$visitorIndex]; + $return = $visitor->leaveNode($subNode); + + if (null !== $return) { + if ($return instanceof Node) { + $this->ensureReplacementReasonable($subNode, $return); + $subNode = $node->$name = $return; + } elseif (NodeVisitor::STOP_TRAVERSAL === $return) { + $this->stopTraversal = true; + break 2; + } elseif (NodeVisitor::REPLACE_WITH_NULL === $return) { + $node->$name = null; + break; + } elseif (\is_array($return)) { + throw new \LogicException( + 'leaveNode() may only return an array ' . + 'if the parent structure is an array' + ); + } else { + throw new \LogicException( + 'leaveNode() returned invalid value of type ' . gettype($return) + ); + } + } + } + } + } + } + + /** + * Recursively traverse array (usually of nodes). + * + * @param array $nodes Array to traverse + * + * @return array Result of traversal (may be original array or changed one) + */ + protected function traverseArray(array $nodes): array { + $doNodes = []; + + foreach ($nodes as $i => $node) { + if ($node instanceof Node) { + $traverseChildren = true; + $visitorIndex = -1; + + foreach ($this->visitors as $visitorIndex => $visitor) { + $return = $visitor->enterNode($node); + if (null !== $return) { + if ($return instanceof Node) { + $this->ensureReplacementReasonable($node, $return); + $nodes[$i] = $node = $return; + } elseif (\is_array($return)) { + $doNodes[] = [$i, $return]; + continue 2; + } elseif (NodeVisitor::REMOVE_NODE === $return) { + $doNodes[] = [$i, []]; + continue 2; + } elseif (NodeVisitor::DONT_TRAVERSE_CHILDREN === $return) { + $traverseChildren = false; + } elseif (NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) { + $traverseChildren = false; + break; + } elseif (NodeVisitor::STOP_TRAVERSAL === $return) { + $this->stopTraversal = true; + break 2; + } elseif (NodeVisitor::REPLACE_WITH_NULL === $return) { + throw new \LogicException( + 'REPLACE_WITH_NULL can not be used if the parent structure is an array'); + } else { + throw new \LogicException( + 'enterNode() returned invalid value of type ' . gettype($return) + ); + } + } + } + + if ($traverseChildren) { + $this->traverseNode($node); + if ($this->stopTraversal) { + break; + } + } + + for (; $visitorIndex >= 0; --$visitorIndex) { + $visitor = $this->visitors[$visitorIndex]; + $return = $visitor->leaveNode($node); + + if (null !== $return) { + if ($return instanceof Node) { + $this->ensureReplacementReasonable($node, $return); + $nodes[$i] = $node = $return; + } elseif (\is_array($return)) { + $doNodes[] = [$i, $return]; + break; + } elseif (NodeVisitor::REMOVE_NODE === $return) { + $doNodes[] = [$i, []]; + break; + } elseif (NodeVisitor::STOP_TRAVERSAL === $return) { + $this->stopTraversal = true; + break 2; + } elseif (NodeVisitor::REPLACE_WITH_NULL === $return) { + throw new \LogicException( + 'REPLACE_WITH_NULL can not be used if the parent structure is an array'); + } else { + throw new \LogicException( + 'leaveNode() returned invalid value of type ' . gettype($return) + ); + } + } + } + } elseif (\is_array($node)) { + throw new \LogicException('Invalid node structure: Contains nested arrays'); + } + } + + if (!empty($doNodes)) { + while (list($i, $replace) = array_pop($doNodes)) { + array_splice($nodes, $i, 1, $replace); + } + } + + return $nodes; + } + + private function ensureReplacementReasonable(Node $old, Node $new): void { + if ($old instanceof Node\Stmt && $new instanceof Node\Expr) { + throw new \LogicException( + "Trying to replace statement ({$old->getType()}) " . + "with expression ({$new->getType()}). Are you missing a " . + "Stmt_Expression wrapper?" + ); + } + + if ($old instanceof Node\Expr && $new instanceof Node\Stmt) { + throw new \LogicException( + "Trying to replace expression ({$old->getType()}) " . + "with statement ({$new->getType()})" + ); + } + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeTraverserInterface.php b/vendor/nikic/php-parser/lib/PhpParser/NodeTraverserInterface.php new file mode 100644 index 00000000..c3992b3d --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeTraverserInterface.php @@ -0,0 +1,26 @@ + $node stays as-is + * * array (of Nodes) + * => The return value is merged into the parent array (at the position of the $node) + * * NodeVisitor::REMOVE_NODE + * => $node is removed from the parent array + * * NodeVisitor::REPLACE_WITH_NULL + * => $node is replaced with null + * * NodeVisitor::DONT_TRAVERSE_CHILDREN + * => Children of $node are not traversed. $node stays as-is + * * NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN + * => Further visitors for the current node are skipped, and its children are not + * traversed. $node stays as-is. + * * NodeVisitor::STOP_TRAVERSAL + * => Traversal is aborted. $node stays as-is + * * otherwise + * => $node is set to the return value + * + * @param Node $node Node + * + * @return null|int|Node|Node[] Replacement node (or special return value) + */ + public function enterNode(Node $node); + + /** + * Called when leaving a node. + * + * Return value semantics: + * * null + * => $node stays as-is + * * NodeVisitor::REMOVE_NODE + * => $node is removed from the parent array + * * NodeVisitor::REPLACE_WITH_NULL + * => $node is replaced with null + * * NodeVisitor::STOP_TRAVERSAL + * => Traversal is aborted. $node stays as-is + * * array (of Nodes) + * => The return value is merged into the parent array (at the position of the $node) + * * otherwise + * => $node is set to the return value + * + * @param Node $node Node + * + * @return null|int|Node|Node[] Replacement node (or special return value) + */ + public function leaveNode(Node $node); + + /** + * Called once after traversal. + * + * Return value semantics: + * * null: $nodes stays as-is + * * otherwise: $nodes is set to the return value + * + * @param Node[] $nodes Array of nodes + * + * @return null|Node[] Array of nodes + */ + public function afterTraverse(array $nodes); +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/CloningVisitor.php b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/CloningVisitor.php new file mode 100644 index 00000000..cba92499 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/CloningVisitor.php @@ -0,0 +1,19 @@ +setAttribute('origNode', $origNode); + return $node; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/CommentAnnotatingVisitor.php b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/CommentAnnotatingVisitor.php new file mode 100644 index 00000000..5e2aed31 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/CommentAnnotatingVisitor.php @@ -0,0 +1,82 @@ + Token positions of comments */ + private array $commentPositions = []; + + /** + * Create a comment annotation visitor. + * + * @param Token[] $tokens Token array + */ + public function __construct(array $tokens) { + $this->tokens = $tokens; + + // Collect positions of comments. We use this to avoid traversing parts of the AST where + // there are no comments. + foreach ($tokens as $i => $token) { + if ($token->id === \T_COMMENT || $token->id === \T_DOC_COMMENT) { + $this->commentPositions[] = $i; + } + } + } + + public function enterNode(Node $node) { + $nextCommentPos = current($this->commentPositions); + if ($nextCommentPos === false) { + // No more comments. + return self::STOP_TRAVERSAL; + } + + $oldPos = $this->pos; + $this->pos = $pos = $node->getStartTokenPos(); + if ($nextCommentPos > $oldPos && $nextCommentPos < $pos) { + $comments = []; + while (--$pos >= $oldPos) { + $token = $this->tokens[$pos]; + if ($token->id === \T_DOC_COMMENT) { + $comments[] = new Comment\Doc( + $token->text, $token->line, $token->pos, $pos, + $token->getEndLine(), $token->getEndPos() - 1, $pos); + continue; + } + if ($token->id === \T_COMMENT) { + $comments[] = new Comment( + $token->text, $token->line, $token->pos, $pos, + $token->getEndLine(), $token->getEndPos() - 1, $pos); + continue; + } + if ($token->id !== \T_WHITESPACE) { + break; + } + } + if (!empty($comments)) { + $node->setAttribute('comments', array_reverse($comments)); + } + + do { + $nextCommentPos = next($this->commentPositions); + } while ($nextCommentPos !== false && $nextCommentPos < $this->pos); + } + + $endPos = $node->getEndTokenPos(); + if ($nextCommentPos > $endPos) { + // Skip children if there are no comments located inside this node. + $this->pos = $endPos; + return self::DONT_TRAVERSE_CHILDREN; + } + + return null; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/FindingVisitor.php b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/FindingVisitor.php new file mode 100644 index 00000000..1f3f4bae --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/FindingVisitor.php @@ -0,0 +1,47 @@ +filterCallback = $filterCallback; + } + + /** + * Get found nodes satisfying the filter callback. + * + * Nodes are returned in pre-order. + * + * @return Node[] Found nodes + */ + public function getFoundNodes(): array { + return $this->foundNodes; + } + + public function beforeTraverse(array $nodes): ?array { + $this->foundNodes = []; + + return null; + } + + public function enterNode(Node $node) { + $filterCallback = $this->filterCallback; + if ($filterCallback($node)) { + $this->foundNodes[] = $node; + } + + return null; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/FirstFindingVisitor.php b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/FirstFindingVisitor.php new file mode 100644 index 00000000..05deed59 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/FirstFindingVisitor.php @@ -0,0 +1,49 @@ +filterCallback = $filterCallback; + } + + /** + * Get found node satisfying the filter callback. + * + * Returns null if no node satisfies the filter callback. + * + * @return null|Node Found node (or null if not found) + */ + public function getFoundNode(): ?Node { + return $this->foundNode; + } + + public function beforeTraverse(array $nodes): ?array { + $this->foundNode = null; + + return null; + } + + public function enterNode(Node $node) { + $filterCallback = $this->filterCallback; + if ($filterCallback($node)) { + $this->foundNode = $node; + return NodeVisitor::STOP_TRAVERSAL; + } + + return null; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/NameResolver.php b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/NameResolver.php new file mode 100644 index 00000000..ccd014eb --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/NameResolver.php @@ -0,0 +1,262 @@ +nameContext = new NameContext($errorHandler ?? new ErrorHandler\Throwing()); + $this->preserveOriginalNames = $options['preserveOriginalNames'] ?? false; + $this->replaceNodes = $options['replaceNodes'] ?? true; + } + + /** + * Get name resolution context. + */ + public function getNameContext(): NameContext { + return $this->nameContext; + } + + public function beforeTraverse(array $nodes): ?array { + $this->nameContext->startNamespace(); + return null; + } + + public function enterNode(Node $node) { + if ($node instanceof Stmt\Namespace_) { + $this->nameContext->startNamespace($node->name); + } elseif ($node instanceof Stmt\Use_) { + foreach ($node->uses as $use) { + $this->addAlias($use, $node->type, null); + } + } elseif ($node instanceof Stmt\GroupUse) { + foreach ($node->uses as $use) { + $this->addAlias($use, $node->type, $node->prefix); + } + } elseif ($node instanceof Stmt\Class_) { + if (null !== $node->extends) { + $node->extends = $this->resolveClassName($node->extends); + } + + foreach ($node->implements as &$interface) { + $interface = $this->resolveClassName($interface); + } + + $this->resolveAttrGroups($node); + if (null !== $node->name) { + $this->addNamespacedName($node); + } else { + $node->namespacedName = null; + } + } elseif ($node instanceof Stmt\Interface_) { + foreach ($node->extends as &$interface) { + $interface = $this->resolveClassName($interface); + } + + $this->resolveAttrGroups($node); + $this->addNamespacedName($node); + } elseif ($node instanceof Stmt\Enum_) { + foreach ($node->implements as &$interface) { + $interface = $this->resolveClassName($interface); + } + + $this->resolveAttrGroups($node); + $this->addNamespacedName($node); + } elseif ($node instanceof Stmt\Trait_) { + $this->resolveAttrGroups($node); + $this->addNamespacedName($node); + } elseif ($node instanceof Stmt\Function_) { + $this->resolveSignature($node); + $this->resolveAttrGroups($node); + $this->addNamespacedName($node); + } elseif ($node instanceof Stmt\ClassMethod + || $node instanceof Expr\Closure + || $node instanceof Expr\ArrowFunction + ) { + $this->resolveSignature($node); + $this->resolveAttrGroups($node); + } elseif ($node instanceof Stmt\Property) { + if (null !== $node->type) { + $node->type = $this->resolveType($node->type); + } + $this->resolveAttrGroups($node); + } elseif ($node instanceof Stmt\Const_) { + foreach ($node->consts as $const) { + $this->addNamespacedName($const); + } + } elseif ($node instanceof Stmt\ClassConst) { + if (null !== $node->type) { + $node->type = $this->resolveType($node->type); + } + $this->resolveAttrGroups($node); + } elseif ($node instanceof Stmt\EnumCase) { + $this->resolveAttrGroups($node); + } elseif ($node instanceof Expr\StaticCall + || $node instanceof Expr\StaticPropertyFetch + || $node instanceof Expr\ClassConstFetch + || $node instanceof Expr\New_ + || $node instanceof Expr\Instanceof_ + ) { + if ($node->class instanceof Name) { + $node->class = $this->resolveClassName($node->class); + } + } elseif ($node instanceof Stmt\Catch_) { + foreach ($node->types as &$type) { + $type = $this->resolveClassName($type); + } + } elseif ($node instanceof Expr\FuncCall) { + if ($node->name instanceof Name) { + $node->name = $this->resolveName($node->name, Stmt\Use_::TYPE_FUNCTION); + } + } elseif ($node instanceof Expr\ConstFetch) { + $node->name = $this->resolveName($node->name, Stmt\Use_::TYPE_CONSTANT); + } elseif ($node instanceof Stmt\TraitUse) { + foreach ($node->traits as &$trait) { + $trait = $this->resolveClassName($trait); + } + + foreach ($node->adaptations as $adaptation) { + if (null !== $adaptation->trait) { + $adaptation->trait = $this->resolveClassName($adaptation->trait); + } + + if ($adaptation instanceof Stmt\TraitUseAdaptation\Precedence) { + foreach ($adaptation->insteadof as &$insteadof) { + $insteadof = $this->resolveClassName($insteadof); + } + } + } + } + + return null; + } + + /** @param Stmt\Use_::TYPE_* $type */ + private function addAlias(Node\UseItem $use, int $type, ?Name $prefix = null): void { + // Add prefix for group uses + $name = $prefix ? Name::concat($prefix, $use->name) : $use->name; + // Type is determined either by individual element or whole use declaration + $type |= $use->type; + + $this->nameContext->addAlias( + $name, (string) $use->getAlias(), $type, $use->getAttributes() + ); + } + + /** @param Stmt\Function_|Stmt\ClassMethod|Expr\Closure|Expr\ArrowFunction $node */ + private function resolveSignature($node): void { + foreach ($node->params as $param) { + $param->type = $this->resolveType($param->type); + $this->resolveAttrGroups($param); + } + $node->returnType = $this->resolveType($node->returnType); + } + + /** + * @template T of Node\Identifier|Name|Node\ComplexType|null + * @param T $node + * @return T + */ + private function resolveType(?Node $node): ?Node { + if ($node instanceof Name) { + return $this->resolveClassName($node); + } + if ($node instanceof Node\NullableType) { + $node->type = $this->resolveType($node->type); + return $node; + } + if ($node instanceof Node\UnionType || $node instanceof Node\IntersectionType) { + foreach ($node->types as &$type) { + $type = $this->resolveType($type); + } + return $node; + } + return $node; + } + + /** + * Resolve name, according to name resolver options. + * + * @param Name $name Function or constant name to resolve + * @param Stmt\Use_::TYPE_* $type One of Stmt\Use_::TYPE_* + * + * @return Name Resolved name, or original name with attribute + */ + protected function resolveName(Name $name, int $type): Name { + if (!$this->replaceNodes) { + $resolvedName = $this->nameContext->getResolvedName($name, $type); + if (null !== $resolvedName) { + $name->setAttribute('resolvedName', $resolvedName); + } else { + $name->setAttribute('namespacedName', FullyQualified::concat( + $this->nameContext->getNamespace(), $name, $name->getAttributes())); + } + return $name; + } + + if ($this->preserveOriginalNames) { + // Save the original name + $originalName = $name; + $name = clone $originalName; + $name->setAttribute('originalName', $originalName); + } + + $resolvedName = $this->nameContext->getResolvedName($name, $type); + if (null !== $resolvedName) { + return $resolvedName; + } + + // unqualified names inside a namespace cannot be resolved at compile-time + // add the namespaced version of the name as an attribute + $name->setAttribute('namespacedName', FullyQualified::concat( + $this->nameContext->getNamespace(), $name, $name->getAttributes())); + return $name; + } + + protected function resolveClassName(Name $name): Name { + return $this->resolveName($name, Stmt\Use_::TYPE_NORMAL); + } + + protected function addNamespacedName(Node $node): void { + $node->namespacedName = Name::concat( + $this->nameContext->getNamespace(), (string) $node->name); + } + + protected function resolveAttrGroups(Node $node): void { + foreach ($node->attrGroups as $attrGroup) { + foreach ($attrGroup->attrs as $attr) { + $attr->name = $this->resolveClassName($attr->name); + } + } + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php new file mode 100644 index 00000000..38fedfd5 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php @@ -0,0 +1,51 @@ +$node->getAttribute('parent'), the previous + * node can be accessed through $node->getAttribute('previous'), + * and the next node can be accessed through $node->getAttribute('next'). + */ +final class NodeConnectingVisitor extends NodeVisitorAbstract { + /** + * @var Node[] + */ + private array $stack = []; + + /** + * @var ?Node + */ + private $previous; + + public function beforeTraverse(array $nodes) { + $this->stack = []; + $this->previous = null; + } + + public function enterNode(Node $node) { + if (!empty($this->stack)) { + $node->setAttribute('parent', $this->stack[count($this->stack) - 1]); + } + + if ($this->previous !== null && $this->previous->getAttribute('parent') === $node->getAttribute('parent')) { + $node->setAttribute('previous', $this->previous); + $this->previous->setAttribute('next', $node); + } + + $this->stack[] = $node; + } + + public function leaveNode(Node $node) { + $this->previous = $node; + + array_pop($this->stack); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php new file mode 100644 index 00000000..1e7e9e8b --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php @@ -0,0 +1,38 @@ +$node->getAttribute('parent'). + */ +final class ParentConnectingVisitor extends NodeVisitorAbstract { + /** + * @var Node[] + */ + private array $stack = []; + + public function beforeTraverse(array $nodes) { + $this->stack = []; + } + + public function enterNode(Node $node) { + if (!empty($this->stack)) { + $node->setAttribute('parent', $this->stack[count($this->stack) - 1]); + } + + $this->stack[] = $node; + } + + public function leaveNode(Node $node) { + array_pop($this->stack); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/NodeVisitorAbstract.php b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitorAbstract.php new file mode 100644 index 00000000..6fb15cca --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/NodeVisitorAbstract.php @@ -0,0 +1,24 @@ +'", + "T_IS_GREATER_OR_EQUAL", + "T_SL", + "T_SR", + "'+'", + "'-'", + "'.'", + "'*'", + "'/'", + "'%'", + "'!'", + "T_INSTANCEOF", + "'~'", + "T_INC", + "T_DEC", + "T_INT_CAST", + "T_DOUBLE_CAST", + "T_STRING_CAST", + "T_ARRAY_CAST", + "T_OBJECT_CAST", + "T_BOOL_CAST", + "T_UNSET_CAST", + "'@'", + "T_POW", + "'['", + "T_NEW", + "T_CLONE", + "T_EXIT", + "T_IF", + "T_ELSEIF", + "T_ELSE", + "T_ENDIF", + "T_LNUMBER", + "T_DNUMBER", + "T_STRING", + "T_STRING_VARNAME", + "T_VARIABLE", + "T_NUM_STRING", + "T_INLINE_HTML", + "T_ENCAPSED_AND_WHITESPACE", + "T_CONSTANT_ENCAPSED_STRING", + "T_ECHO", + "T_DO", + "T_WHILE", + "T_ENDWHILE", + "T_FOR", + "T_ENDFOR", + "T_FOREACH", + "T_ENDFOREACH", + "T_DECLARE", + "T_ENDDECLARE", + "T_AS", + "T_SWITCH", + "T_MATCH", + "T_ENDSWITCH", + "T_CASE", + "T_DEFAULT", + "T_BREAK", + "T_CONTINUE", + "T_GOTO", + "T_FUNCTION", + "T_FN", + "T_CONST", + "T_RETURN", + "T_TRY", + "T_CATCH", + "T_FINALLY", + "T_USE", + "T_INSTEADOF", + "T_GLOBAL", + "T_STATIC", + "T_ABSTRACT", + "T_FINAL", + "T_PRIVATE", + "T_PROTECTED", + "T_PUBLIC", + "T_READONLY", + "T_VAR", + "T_UNSET", + "T_ISSET", + "T_EMPTY", + "T_HALT_COMPILER", + "T_CLASS", + "T_TRAIT", + "T_INTERFACE", + "T_ENUM", + "T_EXTENDS", + "T_IMPLEMENTS", + "T_OBJECT_OPERATOR", + "T_NULLSAFE_OBJECT_OPERATOR", + "T_LIST", + "T_ARRAY", + "T_CALLABLE", + "T_CLASS_C", + "T_TRAIT_C", + "T_METHOD_C", + "T_FUNC_C", + "T_LINE", + "T_FILE", + "T_START_HEREDOC", + "T_END_HEREDOC", + "T_DOLLAR_OPEN_CURLY_BRACES", + "T_CURLY_OPEN", + "T_PAAMAYIM_NEKUDOTAYIM", + "T_NAMESPACE", + "T_NS_C", + "T_DIR", + "T_NS_SEPARATOR", + "T_ELLIPSIS", + "T_NAME_FULLY_QUALIFIED", + "T_NAME_QUALIFIED", + "T_NAME_RELATIVE", + "T_ATTRIBUTE", + "';'", + "']'", + "'('", + "')'", + "'{'", + "'}'", + "'`'", + "'\"'", + "'$'" + ); + + protected array $tokenToSymbol = array( + 0, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 56, 166, 168, 167, 55, 168, 168, + 161, 162, 53, 50, 8, 51, 52, 54, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 31, 159, + 44, 16, 46, 30, 68, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 70, 168, 160, 36, 168, 165, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 163, 35, 164, 58, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 1, 2, 3, 4, + 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 32, 33, 34, 37, 38, 39, 40, + 41, 42, 43, 45, 47, 48, 49, 57, 59, 60, + 61, 62, 63, 64, 65, 66, 67, 69, 71, 72, + 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, + 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, + 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, + 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, + 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, + 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, + 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, + 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, + 153, 154, 155, 156, 157, 158 + ); + + protected array $action = array( + 133, 134, 135, 582, 136, 137, 0, 751, 752, 753, + 138, 38,-32766,-32766,-32766, 151,-32766,-32766,-32766,-32767, + -32767,-32767,-32767, 102, 103, 104, 105, 106, 1112, 1113, + 1114, 1111, 1110, 1109, 1115, 745, 744,-32766,-32766,-32766, + -32766,-32766,-32766,-32766,-32766,-32766,-32767,-32767,-32767,-32767, + -32767, 1245, 837,-32766, 1322, 754,-32766,-32766,-32766,-32766, + -594,-32766,-32766,-32766, 104, 105, 106, -594, 1306, 265, + 139, 404, 758, 759, 760, 761, 990,-32766, 429,-32766, + -32766, -16,-32766, 242, 1027, 815, 762, 763, 764, 765, + 766, 767, 768, 769, 770, 771, 791, 583, 792, 793, + 794, 795, 783, 784, 345, 346, 786, 787, 772, 773, + 774, 776, 777, 778, 356, 818, 819, 820, 821, 822, + 584, 779, 780, 585, 586,-32766, 803, 801, 802, 814, + 798, 799, 835, 826, 587, 588, 797, 589, 590, 591, + 592, 593, 594, 826, 459, 460, 461, 1036, 800, 595, + 596, 941, 140, 2, 133, 134, 135, 582, 136, 137, + 1060, 751, 752, 753, 138, 38, -328, -110, -110, 1326, + 290, 23, -110,-32766,-32766,-32766, 1325, 35, -110, 1112, + 1113, 1114, 1111, 1110, 1109, 1115, 612,-32766, 129, 745, + 744, 107, 108, 109,-32766, 274,-32766,-32766,-32766,-32766, + -32766,-32766,-32766, 828, 991, -194, 145, 110, 298, 754, + 836, 75,-32766,-32766,-32766, 1351, 142, 326, 1352, -594, + 326, -594, 254, 265, 139, 404, 758, 759, 760, 761, + 82, -272, 429,-32766, 326,-32766,-32766,-32766,-32766, 815, + 762, 763, 764, 765, 766, 767, 768, 769, 770, 771, + 791, 583, 792, 793, 794, 795, 783, 784, 345, 346, + 786, 787, 772, 773, 774, 776, 777, 778, 356, 818, + 819, 820, 821, 822, 584, 779, 780, 585, 586, 830, + 803, 801, 802, 814, 798, 799, 712, 309, 587, 588, + 797, 589, 590, 591, 592, 593, 594, -78, 83, 84, + 85, -85, 800, 595, 596, 311, 149, 775, 746, 747, + 748, 749, 750, 725, 751, 752, 753, 788, 789, 37, + -328, 86, 87, 88, 89, 90, 91, 92, 93, 94, + 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 323, 274, 482,-32766,-32766, + -32766, -58,-32766,-32766,-32766, 959, 960, 127, 110, -194, + 961, 339, 754,-32766,-32766,-32766, 955, -85, 291,-32766, + 1088,-32766,-32766,-32766,-32766,-32766, 755, 756, 757, 758, + 759, 760, 761, -193,-32766, 824,-32766,-32766,-32766, -367, + 429, -367, 815, 762, 763, 764, 765, 766, 767, 768, + 769, 770, 771, 791, 813, 792, 793, 794, 795, 783, + 784, 785, 812, 786, 787, 772, 773, 774, 776, 777, + 778, 817, 818, 819, 820, 821, 822, 823, 779, 780, + 781, 782, -548, 803, 801, 802, 814, 798, 799, 340, + 327, 790, 796, 797, 804, 805, 807, 806, 808, 809, + 1033, 391, 606, 7,-32766, 800, 811, 810, 50, 51, + 52, 513, 53, 54, 831, 1240, 1239, 1241, 55, 56, + -110, 57, 1036, 920, 1090, -110, 1036, -110, 291, 483, + 745, 744, 305, 382, 381, -110, -110, -110, -110, -110, + -110, -110, -110, 423, 920, 283, -548, -548, 152, 290, + 380, 381, 1245, 715, 467, 468, 58, 59, 370, 21, + 423, -545, 60, 556, 61, 248, 249, 62, 63, 64, + 65, 66, 67, 68, 69, -548, 28, 267, 70, 445, + 514, 1104, 374, -342, 1272, 1273, 515, -193, 835, 154, + 832, -544, 1270, 42, 25, 516, 389, 517, 241, 518, + 920, 519, 298, 1238, 520, 521, 910, 920, 441, 44, + 45, 446, 377, 376,-32766, 46, 522, 1023, 1022, 1021, + 1024, 368, 338, 442, 1278, -545, -545, 910, 1231, 443, + 524, 525, 526, 835, 1245, 835, 1036, 716, 1341, 1236, + -545, 155, 528, 529,-32766, 1259, 1260, 1261, 1262, 1256, + 1257, 297, -551, 943, -545, -544, -544, 1263, 1258, 290, + 1035, 1240, 1239, 1241, 298, 444, 1036, 71, 1266, 841, + -544, 321, 322, 326, -153, -153, -153, 920, 1240, 1239, + 1241, 922, -550, 910, -544, 710, 943, -591,-32766, -153, + 910, -153, 357, -153, -591, -153, 862, 1033, 863, 1089, + 36, 251, 922, 737, 156, 375, 710, 717, 862, -585, + 863, -585, 75, 158, -546, 835, 959, 960, 326, 1036, + -57, 523, 920,-32766,-32766, 362, 896, 955, -110, -110, + -110, 32, 111, 112, 113, 114, 115, 116, 117, 118, + 119, 120, 121, 122, 123, 745, 744, 656, 26, 835, + -110, -110, 720, 745, 744, -110, 33, 834, 922, 124, + 910, -110, 710, -153, 125, 922, 675, 676, 130, 710, + -32766, 150, 407, 131, 1150, 1152, 48, 144, -546, -546, + 378, 379,-32766, 383, 384, -543, 28, 159, 1238, 920, + 160, 298, 1059, -546, 75,-32766,-32766,-32766, 835,-32766, + 326,-32766, 1270,-32766, -87, 910,-32766, -546, 647, 648, + 161,-32766,-32766,-32766, -4, 920, -84,-32766,-32766, 727, + 162, 287, 163,-32766, 420, -302, -78, -73, -72, -71, + 141, 287,-32766, -70, 326, 976, 745, 744, 1231, 710, + 299, 300, -69, -68, -67, -298, -591, -66, -591, -543, + -543, -65, 528, 529, -46, 1259, 1260, 1261, 1262, 1256, + 1257, -18, 74, 148, -543, 273, 284, 1263, 1258, 126, + -543, 726, 910,-32766, 729, 919, 147, 73, -543, 1238, + 922, 690, 322, 326, 710, 279,-32766,-32766,-32766, 280, + -32766, 285,-32766, 286,-32766, 332, 288,-32766, 910, 289, + 292, 49,-32766,-32766,-32766, 293, 274, 1033,-32766,-32766, + 937, 110, -50, 685,-32766, 420, 146, 691, 826, 701, + 375, 703, 436,-32766, 1353, 20, 561, 296, 645, 1036, + 835, 959, 960, 1119, -543, -543, 523,-32766, 692, 693, + 306, 527, 955, -110, -110, -110, 132, 922, 834, -543, + 464, 710, 283, 662, 657,-32766, 1240, 1239, 1241, 678, + 304, 1238, 283, -543, 10, 301, 302, 493,-32766,-32766, + -32766, 663,-32766, 922,-32766, 679,-32766, 710, -4,-32766, + 373, 40, -508, 956,-32766,-32766,-32766, -275, 731,-32766, + -32766,-32766, 920, 303, 128, 1238,-32766, 420, 310, 0, + 567, 0,-32766,-32766,-32766,-32766,-32766, 0,-32766, 0, + -32766,-32766, 0,-32766, 0, 1277, -498, 0,-32766,-32766, + -32766,-32766, 1279, 0,-32766,-32766, 8, 1238, 24, 372, + -32766, 420, 920, 1267,-32766,-32766,-32766, 610,-32766,-32766, + -32766, 939,-32766, 298, -579,-32766, 846, 41, 734, 488, + -32766,-32766,-32766,-32766, 735, 854,-32766,-32766, 901, 1238, + 574, 1000,-32766, 420, 977, 984,-32766,-32766,-32766, 974, + -32766,-32766,-32766, 985,-32766, 910, 899,-32766, 972, 1093, + 1096, 1097,-32766,-32766,-32766, 1094, 1095, 1101,-32766,-32766, + 1292, -250, -250, -250,-32766, 420, 1310, 375, 1344, 650, + 28, 267, -578,-32766, -577, -551, -550, -549, 959, 960, + -492, 1, 835, 523, 29, 910, 1270, 30, 896, 955, + -110, -110, -110, 39, 43, 47, 72, 76, 77, 78, + 79, -249, -249, -249, 80, 81, 143, 375, 153, 157, + 897, 247, 328, 357, 358, 359, 360, 361, 959, 960, + 922, 362, 1231, 523, 710, -250, 363, 364, 896, 955, + -110, -110, -110, 365, 366, 367, 369, 529, 28, 1259, + 1260, 1261, 1262, 1256, 1257, 437, 555, 1348, -273, -272, + 835, 1263, 1258, 13, 1270, 14,-32766, 15, 16, 18, + 922, 73, 1238, 1350, 710, -249, 322, 326, 406,-32766, + -32766,-32766, 484,-32766, 485,-32766, 492,-32766, 495, 496, + -32766, 497, 498, 502, 503,-32766,-32766,-32766, 504, 511, + 1231,-32766,-32766, 572, 696, 1249, 1190,-32766, 420, 1268, + 1062, 1061, 1042, 1226, 1038, 529,-32766, 1259, 1260, 1261, + 1262, 1256, 1257, -277, -102, 12, 17, 27, 295, 1263, + 1258, 405, 603, 607, 636, 702, 1194, 1244, 1191, 73, + 34, 1323, 0, 320, 322, 326, 371, 711, 714, 718, + 719, 721, 722, 723, 724, 0, 728, 713, 0, 857, + 856, 865, 949, 992, 864, 1349, 948, 946, 947, 950, + 1222, 930, 940, 928, 982, 983, 634, 1347, 1304, 1293, + 1311, 1320, 0, 1207, 0, 1271, 0, 326 + ); + + protected array $actionCheck = array( + 2, 3, 4, 5, 6, 7, 0, 9, 10, 11, + 12, 13, 9, 10, 11, 14, 9, 10, 11, 44, + 45, 46, 47, 48, 49, 50, 51, 52, 116, 117, + 118, 119, 120, 121, 122, 37, 38, 30, 116, 32, + 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, + 43, 1, 1, 9, 1, 57, 9, 10, 11, 137, + 1, 9, 10, 11, 50, 51, 52, 8, 1, 71, + 72, 73, 74, 75, 76, 77, 31, 30, 80, 32, + 33, 31, 30, 14, 1, 87, 88, 89, 90, 91, + 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, + 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, + 122, 123, 124, 125, 126, 116, 128, 129, 130, 131, + 132, 133, 82, 80, 136, 137, 138, 139, 140, 141, + 142, 143, 144, 80, 129, 130, 131, 138, 150, 151, + 152, 1, 154, 8, 2, 3, 4, 5, 6, 7, + 162, 9, 10, 11, 12, 13, 8, 117, 118, 1, + 161, 8, 122, 9, 10, 11, 8, 8, 128, 116, + 117, 118, 119, 120, 121, 122, 51, 137, 8, 37, + 38, 53, 54, 55, 30, 57, 32, 33, 34, 35, + 36, 37, 38, 80, 159, 8, 8, 69, 158, 57, + 159, 161, 9, 10, 11, 80, 163, 167, 83, 160, + 167, 162, 8, 71, 72, 73, 74, 75, 76, 77, + 163, 162, 80, 30, 167, 32, 33, 34, 35, 87, + 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, + 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, + 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, + 118, 119, 120, 121, 122, 123, 124, 125, 126, 156, + 128, 129, 130, 131, 132, 133, 163, 8, 136, 137, + 138, 139, 140, 141, 142, 143, 144, 16, 9, 10, + 11, 31, 150, 151, 152, 8, 154, 2, 3, 4, + 5, 6, 7, 163, 9, 10, 11, 12, 13, 30, + 162, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, + 51, 52, 53, 54, 55, 8, 57, 31, 9, 10, + 11, 16, 9, 10, 11, 117, 118, 14, 69, 162, + 122, 8, 57, 9, 10, 11, 128, 97, 30, 30, + 1, 32, 33, 34, 35, 36, 71, 72, 73, 74, + 75, 76, 77, 8, 30, 80, 32, 33, 34, 106, + 80, 108, 87, 88, 89, 90, 91, 92, 93, 94, + 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, + 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, + 125, 126, 70, 128, 129, 130, 131, 132, 133, 8, + 70, 136, 137, 138, 139, 140, 141, 142, 143, 144, + 116, 106, 1, 108, 116, 150, 151, 152, 2, 3, + 4, 5, 6, 7, 80, 155, 156, 157, 12, 13, + 101, 15, 138, 1, 164, 106, 138, 108, 30, 163, + 37, 38, 113, 106, 107, 116, 117, 118, 119, 120, + 121, 122, 123, 116, 1, 161, 134, 135, 14, 161, + 106, 107, 1, 31, 134, 135, 50, 51, 8, 101, + 116, 70, 56, 85, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 163, 70, 71, 72, 73, + 74, 123, 8, 164, 78, 79, 80, 162, 82, 14, + 156, 70, 86, 87, 88, 89, 8, 91, 97, 93, + 1, 95, 158, 80, 98, 99, 84, 1, 8, 103, + 104, 105, 106, 107, 116, 109, 110, 119, 120, 121, + 122, 115, 116, 8, 146, 134, 135, 84, 122, 8, + 124, 125, 126, 82, 1, 82, 138, 31, 85, 116, + 149, 14, 136, 137, 116, 139, 140, 141, 142, 143, + 144, 145, 161, 122, 163, 134, 135, 151, 152, 161, + 137, 155, 156, 157, 158, 8, 138, 161, 1, 8, + 149, 165, 166, 167, 75, 76, 77, 1, 155, 156, + 157, 159, 161, 84, 163, 163, 122, 1, 137, 90, + 84, 92, 161, 94, 8, 96, 106, 116, 108, 159, + 147, 148, 159, 163, 14, 106, 163, 31, 106, 160, + 108, 162, 161, 14, 70, 82, 117, 118, 167, 138, + 16, 122, 1, 9, 10, 161, 127, 128, 129, 130, + 131, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 37, 38, 75, 76, 82, + 117, 118, 31, 37, 38, 122, 14, 155, 159, 16, + 84, 128, 163, 164, 16, 159, 75, 76, 16, 163, + 137, 101, 102, 16, 59, 60, 70, 16, 134, 135, + 106, 107, 74, 106, 107, 70, 70, 16, 80, 1, + 16, 158, 1, 149, 161, 87, 88, 89, 82, 91, + 167, 93, 86, 95, 31, 84, 98, 163, 111, 112, + 16, 103, 104, 105, 0, 1, 31, 109, 110, 31, + 16, 30, 16, 115, 116, 35, 31, 31, 31, 31, + 163, 30, 124, 31, 167, 159, 37, 38, 122, 163, + 134, 135, 31, 31, 31, 35, 160, 31, 162, 134, + 135, 31, 136, 137, 31, 139, 140, 141, 142, 143, + 144, 31, 154, 31, 149, 31, 31, 151, 152, 163, + 70, 31, 84, 74, 31, 31, 31, 161, 163, 80, + 159, 80, 166, 167, 163, 35, 87, 88, 89, 35, + 91, 35, 93, 35, 95, 35, 37, 98, 84, 37, + 37, 70, 103, 104, 105, 37, 57, 116, 109, 110, + 38, 69, 31, 77, 115, 116, 70, 116, 80, 80, + 106, 92, 108, 124, 83, 97, 89, 113, 113, 138, + 82, 117, 118, 82, 134, 135, 122, 85, 137, 138, + 114, 127, 128, 129, 130, 131, 31, 159, 155, 149, + 97, 163, 161, 96, 90, 74, 155, 156, 157, 94, + 133, 80, 161, 163, 150, 134, 135, 97, 87, 88, + 89, 100, 91, 159, 93, 100, 95, 163, 164, 98, + 149, 159, 149, 128, 103, 104, 105, 162, 164, 74, + 109, 110, 1, 132, 163, 80, 115, 116, 132, -1, + 153, -1, 87, 88, 89, 124, 91, -1, 93, -1, + 95, 137, -1, 98, -1, 146, 149, -1, 103, 104, + 105, 74, 146, -1, 109, 110, 149, 80, 149, 149, + 115, 116, 1, 160, 87, 88, 89, 153, 91, 124, + 93, 154, 95, 158, 161, 98, 160, 159, 159, 102, + 103, 104, 105, 74, 159, 159, 109, 110, 159, 80, + 81, 159, 115, 116, 159, 159, 87, 88, 89, 159, + 91, 124, 93, 159, 95, 84, 159, 98, 159, 159, + 159, 159, 103, 104, 105, 159, 159, 159, 109, 110, + 160, 100, 101, 102, 115, 116, 160, 106, 160, 160, + 70, 71, 161, 124, 161, 161, 161, 161, 117, 118, + 161, 161, 82, 122, 161, 84, 86, 161, 127, 128, + 129, 130, 131, 161, 161, 161, 161, 161, 161, 161, + 161, 100, 101, 102, 161, 161, 161, 106, 161, 161, + 164, 161, 161, 161, 161, 161, 161, 161, 117, 118, + 159, 161, 122, 122, 163, 164, 161, 161, 127, 128, + 129, 130, 131, 161, 161, 161, 161, 137, 70, 139, + 140, 141, 142, 143, 144, 161, 161, 164, 162, 162, + 82, 151, 152, 162, 86, 162, 74, 162, 162, 162, + 159, 161, 80, 164, 163, 164, 166, 167, 162, 87, + 88, 89, 162, 91, 162, 93, 162, 95, 162, 162, + 98, 162, 162, 162, 162, 103, 104, 105, 162, 162, + 122, 109, 110, 162, 162, 162, 162, 115, 116, 162, + 162, 162, 162, 162, 162, 137, 124, 139, 140, 141, + 142, 143, 144, 162, 162, 162, 162, 162, 162, 151, + 152, 162, 162, 162, 162, 162, 162, 162, 162, 161, + 163, 162, -1, 163, 166, 167, 163, 163, 163, 163, + 163, 163, 163, 163, 163, -1, 163, 163, -1, 164, + 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, + 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, + 164, 164, -1, 165, -1, 166, -1, 167 + ); + + protected array $actionBase = array( + 0, -2, 152, 549, 764, 941, 981, 751, 617, 310, + 123, 877, 556, 671, 671, 738, 671, 472, 626, 789, + 63, 305, 305, 789, 305, 493, 493, 493, 658, 658, + 658, 658, 749, 749, 897, 897, 929, 865, 831, 1062, + 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, + 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, + 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, + 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, + 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, + 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, + 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, + 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, + 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, + 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, + 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, + 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, + 1062, 1062, 1062, 1062, 51, 45, 451, 692, 1036, 1044, + 1040, 1045, 1034, 1033, 1039, 1041, 1046, 1083, 1084, 795, + 1085, 1086, 1082, 1087, 1042, 889, 1035, 1043, 289, 289, + 289, 289, 289, 289, 289, 289, 289, 289, 289, 289, + 289, 289, 289, 289, 289, 289, 289, 289, 289, 289, + 289, 289, 289, 289, 289, 44, 343, 664, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 52, 52, + 52, 666, 666, 47, 354, 980, 203, 1048, 1048, 1048, + 1048, 1048, 1048, 1048, 1048, 1048, 665, 339, 164, 164, + 7, 7, 7, 7, 7, 50, 369, 583, -25, -25, + -25, -25, 448, 741, 501, 408, 283, 338, 394, 334, + 334, 14, 14, 531, 531, 9, 9, 531, 531, 531, + 478, 478, 478, 478, 441, 471, 552, 428, 824, 53, + 53, 53, 53, 824, 824, 824, 824, 826, 1089, 824, + 824, 824, 594, 750, 750, 781, 138, 138, 138, 750, + 540, 503, 503, 540, 238, 503, 67, 135, -78, 805, + 377, 499, -78, 362, 656, 636, 59, 743, 624, 743, + 1032, 481, 802, 802, 514, 773, 746, 878, 1064, 1049, + 821, 1080, 825, 1081, 15, 370, 745, 1031, 1031, 1031, + 1031, 1031, 1031, 1031, 1031, 1031, 1031, 1031, 1090, 443, + 1032, 384, 1090, 1090, 1090, 443, 443, 443, 443, 443, + 443, 443, 443, 443, 443, 647, 384, 622, 641, 384, + 810, 443, 51, 817, 51, 51, 51, 51, 51, 51, + 51, 51, 51, 51, 780, 316, 51, 45, 150, 150, + 490, 83, 150, 150, 150, 150, 51, 51, 51, 51, + 624, 799, 797, 627, 834, 375, 799, 799, 799, 270, + 158, 69, 197, 740, 760, 345, 788, 788, 801, 900, + 900, 788, 798, 788, 801, 914, 788, 788, 900, 900, + 835, 180, 550, 353, 524, 565, 900, 279, 788, 788, + 788, 788, 816, 571, 788, 214, 198, 788, 788, 816, + 811, 785, 145, 777, 900, 900, 900, 816, 500, 777, + 777, 777, 839, 845, 765, 784, 337, 297, 611, 169, + 822, 784, 784, 788, 538, 765, 784, 765, 784, 837, + 784, 784, 784, 765, 784, 798, 431, 784, 721, 607, + 163, 784, 6, 915, 916, 723, 917, 912, 918, 964, + 919, 923, 1054, 899, 930, 913, 924, 965, 906, 903, + 794, 693, 698, 827, 783, 896, 792, 792, 792, 894, + 792, 792, 792, 792, 792, 792, 792, 792, 693, 823, + 830, 787, 933, 702, 707, 1011, 819, 926, 1088, 932, + 1013, 925, 772, 711, 977, 934, 774, 1050, 935, 936, + 986, 1014, 846, 1017, 963, 796, 979, 1065, 836, 945, + 1055, 792, 915, 923, 735, 913, 924, 906, 903, 770, + 766, 762, 763, 761, 752, 747, 748, 782, 1018, 893, + 833, 880, 940, 895, 693, 886, 971, 1047, 990, 992, + 1053, 803, 791, 888, 1066, 946, 952, 953, 1056, 1019, + 1057, 838, 973, 775, 994, 820, 1067, 996, 997, 999, + 1000, 1058, 1068, 1059, 891, 1060, 849, 814, 966, 807, + 1069, 1, 806, 808, 818, 955, 484, 931, 1061, 1070, + 1071, 1001, 1002, 1006, 1072, 1073, 927, 852, 975, 815, + 976, 967, 855, 856, 525, 813, 1020, 800, 804, 812, + 577, 640, 1074, 1075, 1076, 928, 790, 786, 860, 864, + 1021, 809, 1022, 1077, 649, 867, 724, 1078, 1012, 744, + 754, 281, 654, 335, 756, 779, 1063, 829, 776, 778, + 954, 754, 793, 869, 1079, 870, 871, 872, 1007, 876, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 456, 456, 456, 456, 456, 456, 305, 305, 305, 305, + 305, 456, 456, 456, 456, 456, 456, 456, 305, 305, + 0, 0, 305, 0, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 289, 289, 289, 289, 289, 289, 289, + 289, 289, 289, 289, 289, 289, 289, 289, 289, 289, + 289, 289, 289, 289, 289, 289, 289, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 289, 289, 289, 289, 289, 289, + 289, 289, 289, 289, 289, 289, 289, 289, 289, 289, + 289, 289, 289, 289, 289, 289, 289, 289, 289, 289, + 473, 473, 289, 289, 473, 289, 473, 473, 473, 473, + 473, 473, 473, 473, 473, 0, 289, 289, 289, 289, + 289, 289, 289, 289, 473, 835, 473, 138, 138, 138, + 138, 473, 473, 473, -88, -88, 473, 238, 473, 473, + 138, 138, 473, 473, 473, 473, 473, 473, 473, 473, + 473, 473, 473, 0, 0, 384, 503, 473, 798, 798, + 798, 798, 473, 473, 473, 473, 503, 503, 473, 473, + 473, 0, 0, 0, 0, 0, 0, 0, 0, 384, + 0, 0, 384, 0, 0, 798, 798, 473, 238, 835, + 168, 473, 0, 0, 0, 0, 384, 798, 384, 443, + 788, 503, 503, 788, 443, 443, 150, 51, 168, 620, + 620, 620, 620, 0, 0, 624, 835, 835, 835, 835, + 835, 835, 835, 835, 835, 835, 835, 798, 0, 835, + 0, 798, 798, 798, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 798, + 0, 0, 900, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 914, 0, 0, 0, 0, 0, 0, + 798, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 792, 803, 0, 803, 0, 792, 792, 792, 0, 0, + 0, 0, 813, 809 + ); + + protected array $actionDefault = array( + 3,32767, 102,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767, 100,32767,32767,32767,32767, 597, 597, + 597, 597,32767,32767, 254, 102,32767,32767, 470, 387, + 387, 387,32767,32767, 541, 541, 541, 541, 541, 541, + 32767,32767,32767,32767,32767,32767, 470,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767, 100, + 32767,32767,32767, 36, 7, 8, 10, 11, 49, 17, + 324,32767,32767,32767,32767, 102,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767, 590,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767, 474, 453, + 454, 456, 457, 386, 542, 596, 327, 593, 385, 145, + 339, 329, 242, 330, 258, 475, 259, 476, 479, 480, + 215, 287, 382, 149, 150, 417, 471, 419, 469, 473, + 418, 392, 398, 399, 400, 401, 402, 403, 404, 405, + 406, 407, 408, 409, 410, 390, 391, 472, 450, 449, + 448,32767,32767, 415, 416,32767, 420,32767,32767,32767, + 32767,32767,32767,32767, 102,32767, 389, 423, 421, 422, + 439, 440, 437, 438, 441,32767,32767,32767, 442, 443, + 444, 445, 316,32767,32767, 366, 364, 316, 111,32767, + 32767, 430, 431,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767, 535, 447,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767, 102, + 32767, 100, 537, 412, 414, 504, 425, 426, 424, 393, + 32767, 511,32767, 102,32767, 513,32767,32767,32767,32767, + 32767,32767,32767, 536,32767, 543, 543,32767, 497, 100, + 195,32767,32767, 512,32767, 195, 195,32767,32767,32767, + 32767,32767,32767,32767,32767, 604, 497, 110, 110, 110, + 110, 110, 110, 110, 110, 110, 110, 110,32767, 195, + 110,32767,32767,32767, 100, 195, 195, 195, 195, 195, + 195, 195, 195, 195, 195, 190,32767, 268, 270, 102, + 558, 195,32767, 516,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767, 509,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 497, 435, 138,32767, 138, 543, 427, 428, 429, 499, + 543, 543, 543, 312, 289,32767,32767,32767,32767, 514, + 514, 100, 100, 100, 100, 509,32767,32767,32767,32767, + 111, 99, 99, 99, 99, 99, 103, 101,32767,32767, + 32767,32767, 223, 99,32767, 101, 101,32767,32767, 223, + 225, 212, 101, 227,32767, 562, 563, 223, 101, 227, + 227, 227, 247, 247, 486, 318, 101, 99, 101, 101, + 197, 318, 318,32767, 101, 486, 318, 486, 318, 199, + 318, 318, 318, 486, 318,32767, 101, 318, 214, 99, + 99, 318,32767,32767,32767, 499,32767,32767,32767,32767, + 32767,32767,32767, 222,32767,32767,32767,32767,32767,32767, + 32767,32767, 530,32767, 547, 560, 433, 434, 436, 545, + 458, 459, 460, 461, 462, 463, 464, 466, 592,32767, + 503,32767,32767,32767, 338,32767, 602,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767, 603,32767, 543,32767,32767,32767, + 32767, 432, 9, 74, 492, 42, 43, 51, 57, 520, + 521, 522, 523, 517, 518, 524, 519,32767,32767, 525, + 568,32767,32767, 544, 595,32767,32767,32767,32767,32767, + 32767, 138,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767, 530,32767, 136,32767,32767,32767,32767, + 32767,32767,32767,32767, 526,32767,32767,32767, 543,32767, + 32767,32767,32767, 314, 311,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767, 543,32767,32767,32767,32767,32767, 291,32767, 308, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767, 286,32767,32767, 381, + 499, 294, 296, 297,32767,32767,32767,32767, 360,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 152, 152, 3, 3, 341, 152, 152, 152, 341, 341, + 152, 341, 341, 341, 152, 152, 152, 152, 152, 152, + 280, 185, 262, 265, 247, 247, 152, 352, 152 + ); + + protected array $goto = array( + 196, 196, 1034, 1065, 697, 431, 661, 621, 658, 319, + 706, 425, 313, 314, 335, 576, 430, 336, 432, 638, + 654, 655, 852, 672, 673, 674, 853, 167, 167, 167, + 167, 221, 197, 193, 193, 177, 179, 216, 193, 193, + 193, 193, 193, 194, 194, 194, 194, 194, 194, 188, + 189, 190, 191, 192, 218, 216, 219, 536, 537, 421, + 538, 540, 541, 542, 543, 544, 545, 546, 547, 1136, + 168, 169, 170, 195, 171, 172, 173, 166, 174, 175, + 176, 178, 215, 217, 220, 238, 243, 244, 246, 257, + 258, 259, 260, 261, 262, 263, 264, 268, 269, 270, + 271, 281, 282, 316, 317, 318, 426, 427, 428, 581, + 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, + 232, 233, 234, 235, 236, 180, 237, 181, 198, 199, + 200, 239, 188, 189, 190, 191, 192, 218, 1136, 201, + 182, 183, 184, 202, 198, 185, 240, 203, 201, 165, + 204, 205, 186, 206, 207, 208, 187, 209, 210, 211, + 212, 213, 214, 855, 466, 466, 278, 278, 278, 278, + 623, 623, 351, 466, 1269, 600, 1269, 1269, 1269, 1269, + 1269, 1269, 1269, 1269, 1269, 1287, 1287, 599, 1100, 1287, + 709, 1287, 1287, 1287, 1287, 1287, 1287, 1287, 1287, 1287, + 508, 700, 458, 1098, 975, 559, 552, 860, 419, 909, + 904, 905, 918, 861, 906, 858, 907, 908, 859, 848, + 827, 912, 354, 354, 354, 354, 396, 399, 560, 601, + 605, 1087, 1082, 1083, 1084, 341, 552, 559, 568, 569, + 344, 579, 602, 616, 617, 408, 409, 1232, 440, 479, + 670, 22, 671, 886, 412, 413, 414, 481, 684, 349, + 1237, 415, 1237, 1107, 1108, 347, 833, 1034, 1034, 1237, + 573, 848, 1034, 1327, 1034, 1034, 1040, 1039, 1034, 1034, + 1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, 1034, 1319, + 1319, 1319, 1319, 1237, 893, 851, 893, 893, 1237, 1237, + 1237, 1237, 1233, 1234, 1237, 1237, 1237, 833, 355, 833, + 843, 996, 252, 252, 1043, 1044, 1037, 1037, 355, 355, + 681, 952, 394, 926, 1029, 1045, 1046, 927, 1235, 1295, + 1296, 942, 355, 355, 942, 913, 355, 914, 1354, 250, + 250, 250, 250, 245, 253, 548, 548, 548, 548, 554, + 604, 1285, 1285, 355, 355, 1285, 571, 1285, 1285, 1285, + 1285, 1285, 1285, 1285, 1285, 1285, 539, 539, 342, 424, + 539, 611, 539, 539, 539, 539, 539, 539, 539, 539, + 539, 566, 476, 1312, 1313, 733, 637, 639, 325, 308, + 659, 848, 343, 342, 683, 687, 1010, 695, 704, 1006, + 660, 1298, 609, 624, 627, 628, 629, 630, 651, 652, + 653, 708, 1216, 944, 1314, 1315, 1217, 1220, 945, 1221, + 1337, 1337, 686, 352, 353, 868, 553, 563, 450, 450, + 450, 553, 1309, 563, 1309, 1133, 397, 462, 1337, 1058, + 880, 1309, 1185, 867, 500, 5, 501, 6, 469, 580, + 470, 471, 507, 554, 878, 1340, 1340, 1345, 1346, 433, + 438, 550, 666, 550, 433, 682, 1321, 1321, 1321, 1321, + 550, 337, 1041, 1041, 931, 1123, 873, 665, 1052, 1048, + 1049, 619, 845, 876, 324, 275, 324, 1015, 967, 410, + 705, 577, 614, 1305, 456, 872, 403, 664, 994, 969, + 969, 969, 969, 866, 870, 456, 963, 970, 881, 869, + 1070, 1074, 631, 633, 635, 1227, 1230, 958, 615, 978, + 450, 450, 450, 450, 450, 450, 450, 450, 450, 450, + 450, 999, 1018, 450, 971, 1073, 732, 477, 1228, 1307, + 1307, 1073, 736, 968, 551, 1008, 1003, 882, 694, 1075, + 1071, 829, 255, 255, 980, 0, 1118, 0, 1013, 1013, + 694, 0, 0, 0, 694, 1116, 885 + ); + + protected array $gotoCheck = array( + 42, 42, 73, 127, 73, 66, 66, 56, 56, 66, + 9, 66, 66, 66, 66, 66, 66, 66, 66, 66, + 86, 86, 26, 86, 86, 86, 27, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 15, 149, 149, 23, 23, 23, 23, + 108, 108, 97, 149, 108, 130, 108, 108, 108, 108, + 108, 108, 108, 108, 108, 170, 170, 8, 8, 170, + 8, 170, 170, 170, 170, 170, 170, 170, 170, 170, + 8, 8, 83, 8, 49, 76, 76, 15, 43, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 22, + 6, 15, 24, 24, 24, 24, 59, 59, 59, 59, + 59, 15, 15, 15, 15, 76, 76, 76, 76, 76, + 76, 76, 76, 76, 76, 82, 82, 20, 83, 84, + 82, 76, 82, 45, 82, 82, 82, 84, 82, 179, + 73, 82, 73, 144, 144, 82, 12, 73, 73, 73, + 172, 22, 73, 181, 73, 73, 118, 118, 73, 73, + 73, 73, 73, 73, 73, 73, 73, 73, 73, 9, + 9, 9, 9, 73, 25, 25, 25, 25, 73, 73, + 73, 73, 20, 20, 73, 73, 73, 12, 14, 12, + 20, 103, 5, 5, 119, 119, 89, 89, 14, 14, + 89, 89, 62, 73, 89, 89, 89, 73, 20, 20, + 20, 9, 14, 14, 9, 65, 14, 65, 14, 5, + 5, 5, 5, 5, 5, 107, 107, 107, 107, 14, + 107, 171, 171, 14, 14, 171, 104, 171, 171, 171, + 171, 171, 171, 171, 171, 171, 173, 173, 168, 13, + 173, 13, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 48, 176, 176, 176, 48, 48, 48, 169, 169, + 48, 22, 168, 168, 48, 48, 48, 48, 48, 48, + 64, 14, 81, 81, 81, 81, 81, 81, 81, 81, + 81, 81, 79, 79, 178, 178, 79, 79, 79, 79, + 182, 182, 14, 97, 97, 35, 9, 9, 23, 23, + 23, 9, 130, 9, 130, 150, 9, 9, 182, 114, + 35, 130, 151, 35, 155, 46, 155, 46, 9, 9, + 9, 9, 155, 14, 9, 182, 182, 9, 9, 117, + 113, 19, 120, 19, 117, 116, 130, 130, 130, 130, + 19, 29, 117, 117, 17, 17, 39, 117, 117, 117, + 117, 17, 18, 9, 24, 24, 24, 17, 93, 93, + 93, 2, 2, 130, 19, 17, 28, 17, 17, 19, + 19, 19, 19, 17, 37, 19, 19, 19, 16, 16, + 16, 16, 85, 85, 85, 17, 14, 92, 80, 16, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 50, 110, 23, 50, 130, 50, 157, 160, 130, + 130, 130, 99, 16, 50, 50, 50, 41, 7, 132, + 129, 7, 5, 5, 96, -1, 147, -1, 107, 107, + 7, -1, -1, -1, 7, 16, 16 + ); + + protected array $gotoBase = array( + 0, 0, -221, 0, 0, 311, 200, 541, 179, -10, + 0, 0, -30, 32, 11, -185, 56, 9, 173, 196, + -146, 0, -59, 163, 219, 291, 18, 22, 159, 175, + 0, 0, 0, 0, 0, 54, 0, 165, 0, 153, + 0, 106, -1, 189, 0, 230, -291, 0, -330, 186, + 519, 0, 0, 0, 0, 0, -33, 0, 0, 181, + 0, 0, 280, 0, 158, 321, -236, 0, 0, 0, + 0, 0, 0, -5, 0, 0, -140, 0, 0, 4, + 174, 44, -246, -76, -220, 33, -698, 0, 0, 37, + 0, 0, 188, 184, 0, 0, 111, -311, 0, 135, + 0, 0, 0, 276, 313, 0, 0, 317, -71, 0, + 162, 0, 0, 183, 166, 0, 182, 187, -3, 29, + 172, 0, 0, 0, 0, 0, 0, 1, 0, 176, + 167, 0, 107, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, -12, 0, 0, 112, 0, 130, + 190, 168, 0, 0, 0, -51, 0, 97, 0, 0, + 169, 0, 0, 0, 0, 0, 0, 0, 71, 67, + -56, 110, 241, 125, 0, 0, 82, 0, 42, 229, + 0, 242, 113, 0, 0 + ); + + protected array $gotoDefault = array( + -32768, 512, 740, 4, 741, 935, 816, 825, 597, 530, + 707, 348, 625, 422, 1303, 911, 1122, 578, 844, 1246, + 1254, 457, 847, 330, 730, 923, 894, 895, 400, 386, + 392, 398, 649, 626, 494, 879, 453, 871, 486, 874, + 452, 883, 164, 418, 510, 887, 3, 890, 557, 921, + 973, 387, 898, 388, 677, 900, 562, 902, 903, 395, + 401, 402, 1127, 570, 622, 915, 256, 564, 916, 385, + 917, 925, 390, 393, 688, 465, 505, 499, 411, 1102, + 565, 608, 646, 447, 473, 620, 632, 618, 480, 434, + 416, 329, 957, 965, 487, 463, 979, 350, 987, 738, + 1135, 640, 489, 995, 641, 1002, 1005, 531, 532, 478, + 1017, 272, 1020, 490, 19, 667, 1031, 1032, 668, 642, + 1054, 643, 669, 644, 1056, 472, 598, 1064, 454, 1072, + 1291, 455, 1076, 266, 1079, 277, 417, 435, 1085, 1086, + 9, 1092, 698, 699, 11, 276, 509, 1117, 689, 451, + 1134, 439, 1204, 1206, 558, 491, 1224, 1223, 680, 506, + 1229, 448, 1294, 449, 533, 474, 315, 534, 1338, 307, + 333, 312, 549, 294, 334, 535, 475, 1300, 1308, 331, + 31, 1328, 1339, 575, 613 + ); + + protected array $ruleToNonTerminal = array( + 0, 1, 3, 3, 2, 5, 5, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, + 7, 7, 7, 7, 7, 8, 8, 9, 10, 11, + 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, + 16, 17, 17, 18, 18, 21, 21, 22, 23, 23, + 24, 24, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 29, 29, 30, 30, 32, 34, 34, + 28, 36, 36, 33, 38, 38, 35, 35, 37, 37, + 39, 39, 31, 40, 40, 41, 43, 44, 44, 45, + 45, 46, 46, 48, 47, 47, 47, 47, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 25, 25, 50, 69, 69, 72, 72, 71, + 70, 70, 63, 75, 75, 76, 76, 77, 77, 78, + 78, 79, 79, 80, 80, 26, 26, 27, 27, 27, + 27, 27, 88, 88, 90, 90, 83, 83, 91, 91, + 92, 92, 92, 84, 84, 87, 87, 85, 85, 93, + 94, 94, 57, 57, 65, 65, 68, 68, 68, 67, + 95, 95, 96, 58, 58, 58, 58, 97, 97, 98, + 98, 99, 99, 100, 101, 101, 102, 102, 103, 103, + 55, 55, 51, 51, 105, 53, 53, 106, 52, 52, + 54, 54, 64, 64, 64, 64, 81, 81, 109, 109, + 111, 111, 112, 112, 112, 112, 110, 110, 110, 114, + 114, 114, 114, 89, 89, 117, 117, 117, 118, 118, + 115, 115, 119, 119, 121, 121, 122, 122, 116, 123, + 123, 120, 124, 124, 124, 124, 113, 113, 82, 82, + 82, 20, 20, 20, 126, 125, 125, 127, 127, 127, + 127, 60, 128, 128, 129, 61, 131, 131, 132, 132, + 133, 133, 86, 134, 134, 134, 134, 134, 134, 134, + 139, 139, 140, 140, 141, 141, 141, 141, 141, 142, + 143, 143, 138, 138, 135, 135, 137, 137, 145, 145, + 144, 144, 144, 144, 144, 144, 144, 136, 146, 146, + 148, 147, 147, 62, 104, 149, 149, 56, 56, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 156, 150, 150, 155, 155, 158, 159, + 159, 160, 161, 162, 162, 162, 162, 19, 19, 73, + 73, 73, 73, 151, 151, 151, 151, 164, 164, 152, + 152, 154, 154, 154, 157, 157, 170, 170, 170, 170, + 170, 170, 170, 170, 170, 171, 171, 171, 108, 173, + 173, 173, 173, 153, 153, 153, 153, 153, 153, 153, + 153, 59, 59, 167, 167, 167, 167, 174, 174, 163, + 163, 163, 175, 175, 175, 175, 175, 175, 74, 74, + 66, 66, 66, 66, 130, 130, 130, 130, 178, 177, + 166, 166, 166, 166, 166, 166, 166, 165, 165, 165, + 176, 176, 176, 176, 107, 172, 180, 180, 179, 179, + 181, 181, 181, 181, 181, 181, 181, 181, 169, 169, + 169, 169, 168, 183, 182, 182, 182, 182, 182, 182, + 182, 182, 184, 184, 184, 184 + ); + + protected array $ruleToLength = array( + 1, 1, 2, 0, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 1, 0, 1, 1, 2, 1, 3, 4, 1, 2, + 0, 1, 1, 1, 1, 4, 3, 5, 4, 3, + 4, 2, 3, 1, 1, 7, 6, 2, 3, 1, + 2, 3, 1, 2, 3, 1, 1, 3, 1, 3, + 1, 2, 2, 3, 1, 3, 2, 3, 1, 3, + 3, 2, 0, 1, 1, 1, 1, 1, 3, 7, + 10, 5, 7, 9, 5, 3, 3, 3, 3, 3, + 3, 1, 2, 5, 7, 9, 6, 5, 6, 3, + 2, 1, 1, 1, 1, 0, 2, 1, 3, 8, + 0, 4, 2, 1, 3, 0, 1, 0, 1, 0, + 1, 3, 1, 1, 1, 8, 9, 7, 8, 7, + 6, 8, 0, 2, 0, 2, 1, 2, 1, 2, + 1, 1, 1, 0, 2, 0, 2, 0, 2, 2, + 1, 3, 1, 4, 1, 4, 1, 1, 4, 2, + 1, 3, 3, 3, 4, 4, 5, 0, 2, 4, + 3, 1, 1, 7, 0, 2, 1, 3, 3, 4, + 1, 4, 0, 2, 5, 0, 2, 6, 0, 2, + 0, 3, 1, 2, 1, 1, 2, 0, 1, 3, + 0, 2, 1, 1, 1, 1, 6, 8, 6, 1, + 2, 1, 1, 1, 1, 1, 1, 1, 1, 3, + 3, 3, 1, 3, 3, 3, 3, 3, 1, 3, + 3, 1, 1, 2, 1, 1, 0, 1, 0, 2, + 2, 2, 4, 3, 1, 1, 3, 1, 2, 2, + 3, 2, 3, 1, 1, 2, 3, 1, 1, 3, + 2, 0, 1, 5, 5, 6, 10, 3, 5, 1, + 1, 3, 0, 2, 4, 5, 4, 4, 4, 3, + 1, 1, 1, 1, 1, 1, 0, 1, 1, 2, + 1, 1, 1, 1, 1, 1, 1, 2, 1, 3, + 1, 1, 3, 2, 2, 3, 1, 0, 1, 1, + 3, 3, 3, 4, 4, 1, 1, 2, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 2, 2, 2, 2, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 2, 2, 2, 2, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 5, 4, + 3, 4, 4, 2, 2, 4, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 1, 3, 2, + 1, 2, 4, 2, 2, 8, 9, 8, 9, 9, + 10, 9, 10, 8, 3, 2, 0, 4, 2, 1, + 3, 2, 1, 2, 2, 2, 4, 1, 1, 1, + 1, 1, 1, 1, 1, 3, 1, 1, 1, 0, + 3, 0, 1, 1, 0, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 3, 5, 3, 3, 4, + 1, 1, 3, 1, 1, 1, 1, 1, 3, 2, + 3, 0, 1, 1, 3, 1, 1, 1, 1, 1, + 3, 1, 1, 4, 4, 1, 4, 4, 0, 1, + 1, 1, 3, 3, 1, 4, 2, 2, 1, 3, + 1, 4, 4, 3, 3, 3, 3, 1, 3, 1, + 1, 3, 1, 1, 4, 1, 1, 1, 3, 1, + 1, 2, 1, 3, 4, 3, 2, 0, 2, 2, + 1, 2, 1, 1, 1, 4, 3, 3, 3, 3, + 6, 3, 1, 1, 2, 1 + ); + + protected function initReduceCallbacks(): void { + $this->reduceCallbacks = [ + 0 => null, + 1 => static function ($self, $stackPos) { + $self->semValue = $self->handleNamespaces($self->semStack[$stackPos-(1-1)]); + }, + 2 => static function ($self, $stackPos) { + if ($self->semStack[$stackPos-(2-2)] !== null) { $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; } $self->semValue = $self->semStack[$stackPos-(2-1)];; + }, + 3 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 4 => static function ($self, $stackPos) { + $nop = $self->maybeCreateZeroLengthNop($self->tokenPos);; + if ($nop !== null) { $self->semStack[$stackPos-(1-1)][] = $nop; } $self->semValue = $self->semStack[$stackPos-(1-1)]; + }, + 5 => null, + 6 => null, + 7 => null, + 8 => null, + 9 => null, + 10 => null, + 11 => null, + 12 => null, + 13 => null, + 14 => null, + 15 => null, + 16 => null, + 17 => null, + 18 => null, + 19 => null, + 20 => null, + 21 => null, + 22 => null, + 23 => null, + 24 => null, + 25 => null, + 26 => null, + 27 => null, + 28 => null, + 29 => null, + 30 => null, + 31 => null, + 32 => null, + 33 => null, + 34 => null, + 35 => null, + 36 => null, + 37 => null, + 38 => null, + 39 => null, + 40 => null, + 41 => null, + 42 => null, + 43 => null, + 44 => null, + 45 => null, + 46 => null, + 47 => null, + 48 => null, + 49 => null, + 50 => null, + 51 => null, + 52 => null, + 53 => null, + 54 => null, + 55 => null, + 56 => null, + 57 => null, + 58 => null, + 59 => null, + 60 => null, + 61 => null, + 62 => null, + 63 => null, + 64 => null, + 65 => null, + 66 => null, + 67 => null, + 68 => null, + 69 => null, + 70 => null, + 71 => null, + 72 => null, + 73 => null, + 74 => null, + 75 => null, + 76 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(1-1)]; if ($self->semValue === "emitError(new Error('Cannot use "getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]))); + }, + 77 => null, + 78 => null, + 79 => null, + 80 => null, + 81 => null, + 82 => null, + 83 => null, + 84 => null, + 85 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 86 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 87 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 88 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 89 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 90 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 91 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 92 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 93 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 94 => null, + 95 => static function ($self, $stackPos) { + $self->semValue = new Name(substr($self->semStack[$stackPos-(1-1)], 1), $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 96 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable(substr($self->semStack[$stackPos-(1-1)], 1), $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 97 => static function ($self, $stackPos) { + /* nothing */ + }, + 98 => static function ($self, $stackPos) { + /* nothing */ + }, + 99 => static function ($self, $stackPos) { + /* nothing */ + }, + 100 => static function ($self, $stackPos) { + $self->emitError(new Error('A trailing comma is not allowed here', $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]))); + }, + 101 => null, + 102 => null, + 103 => static function ($self, $stackPos) { + $self->semValue = new Node\Attribute($self->semStack[$stackPos-(1-1)], [], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 104 => static function ($self, $stackPos) { + $self->semValue = new Node\Attribute($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 105 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 106 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 107 => static function ($self, $stackPos) { + $self->semValue = new Node\AttributeGroup($self->semStack[$stackPos-(4-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 108 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 109 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 110 => static function ($self, $stackPos) { + $self->semValue = []; + }, + 111 => null, + 112 => null, + 113 => null, + 114 => null, + 115 => static function ($self, $stackPos) { + $self->semValue = new Stmt\HaltCompiler($self->handleHaltCompiler(), $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 116 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Namespace_($self->semStack[$stackPos-(3-2)], null, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + $self->semValue->setAttribute('kind', Stmt\Namespace_::KIND_SEMICOLON); + $self->checkNamespace($self->semValue); + }, + 117 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Namespace_($self->semStack[$stackPos-(5-2)], $self->semStack[$stackPos-(5-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + $self->semValue->setAttribute('kind', Stmt\Namespace_::KIND_BRACED); + $self->checkNamespace($self->semValue); + }, + 118 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Namespace_(null, $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + $self->semValue->setAttribute('kind', Stmt\Namespace_::KIND_BRACED); + $self->checkNamespace($self->semValue); + }, + 119 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Use_($self->semStack[$stackPos-(3-2)], Stmt\Use_::TYPE_NORMAL, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 120 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Use_($self->semStack[$stackPos-(4-3)], $self->semStack[$stackPos-(4-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 121 => null, + 122 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Const_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 123 => static function ($self, $stackPos) { + $self->semValue = Stmt\Use_::TYPE_FUNCTION; + }, + 124 => static function ($self, $stackPos) { + $self->semValue = Stmt\Use_::TYPE_CONSTANT; + }, + 125 => static function ($self, $stackPos) { + $self->semValue = new Stmt\GroupUse($self->semStack[$stackPos-(7-3)], $self->semStack[$stackPos-(7-6)], $self->semStack[$stackPos-(7-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + }, + 126 => static function ($self, $stackPos) { + $self->semValue = new Stmt\GroupUse($self->semStack[$stackPos-(6-2)], $self->semStack[$stackPos-(6-5)], Stmt\Use_::TYPE_UNKNOWN, $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])); + }, + 127 => null, + 128 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 129 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 130 => null, + 131 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 132 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 133 => null, + 134 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 135 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 136 => static function ($self, $stackPos) { + $self->semValue = new Node\UseItem($self->semStack[$stackPos-(1-1)], null, Stmt\Use_::TYPE_UNKNOWN, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); $self->checkUseUse($self->semValue, $stackPos-(1-1)); + }, + 137 => static function ($self, $stackPos) { + $self->semValue = new Node\UseItem($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], Stmt\Use_::TYPE_UNKNOWN, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); $self->checkUseUse($self->semValue, $stackPos-(3-3)); + }, + 138 => static function ($self, $stackPos) { + $self->semValue = new Node\UseItem($self->semStack[$stackPos-(1-1)], null, Stmt\Use_::TYPE_UNKNOWN, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); $self->checkUseUse($self->semValue, $stackPos-(1-1)); + }, + 139 => static function ($self, $stackPos) { + $self->semValue = new Node\UseItem($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], Stmt\Use_::TYPE_UNKNOWN, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); $self->checkUseUse($self->semValue, $stackPos-(3-3)); + }, + 140 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(1-1)]; $self->semValue->type = Stmt\Use_::TYPE_NORMAL; + }, + 141 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; $self->semValue->type = $self->semStack[$stackPos-(2-1)]; + }, + 142 => null, + 143 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 144 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 145 => static function ($self, $stackPos) { + $self->semValue = new Node\Const_($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 146 => null, + 147 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 148 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 149 => static function ($self, $stackPos) { + $self->semValue = new Node\Const_(new Node\Identifier($self->semStack[$stackPos-(3-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos-(3-1)])), $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 150 => static function ($self, $stackPos) { + $self->semValue = new Node\Const_(new Node\Identifier($self->semStack[$stackPos-(3-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos-(3-1)])), $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 151 => static function ($self, $stackPos) { + if ($self->semStack[$stackPos-(2-2)] !== null) { $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; } $self->semValue = $self->semStack[$stackPos-(2-1)];; + }, + 152 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 153 => static function ($self, $stackPos) { + $nop = $self->maybeCreateZeroLengthNop($self->tokenPos);; + if ($nop !== null) { $self->semStack[$stackPos-(1-1)][] = $nop; } $self->semValue = $self->semStack[$stackPos-(1-1)]; + }, + 154 => null, + 155 => null, + 156 => null, + 157 => static function ($self, $stackPos) { + throw new Error('__HALT_COMPILER() can only be used from the outermost scope', $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 158 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Block($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 159 => static function ($self, $stackPos) { + $self->semValue = new Stmt\If_($self->semStack[$stackPos-(7-3)], ['stmts' => $self->semStack[$stackPos-(7-5)], 'elseifs' => $self->semStack[$stackPos-(7-6)], 'else' => $self->semStack[$stackPos-(7-7)]], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + }, + 160 => static function ($self, $stackPos) { + $self->semValue = new Stmt\If_($self->semStack[$stackPos-(10-3)], ['stmts' => $self->semStack[$stackPos-(10-6)], 'elseifs' => $self->semStack[$stackPos-(10-7)], 'else' => $self->semStack[$stackPos-(10-8)]], $self->getAttributes($self->tokenStartStack[$stackPos-(10-1)], $self->tokenEndStack[$stackPos])); + }, + 161 => static function ($self, $stackPos) { + $self->semValue = new Stmt\While_($self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 162 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Do_($self->semStack[$stackPos-(7-5)], $self->semStack[$stackPos-(7-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + }, + 163 => static function ($self, $stackPos) { + $self->semValue = new Stmt\For_(['init' => $self->semStack[$stackPos-(9-3)], 'cond' => $self->semStack[$stackPos-(9-5)], 'loop' => $self->semStack[$stackPos-(9-7)], 'stmts' => $self->semStack[$stackPos-(9-9)]], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 164 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Switch_($self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 165 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Break_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 166 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Continue_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 167 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Return_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 168 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Global_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 169 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Static_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 170 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Echo_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 171 => static function ($self, $stackPos) { + + $self->semValue = new Stmt\InlineHTML($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + $self->semValue->setAttribute('hasLeadingNewline', $self->inlineHtmlHasLeadingNewline($stackPos-(1-1))); + + }, + 172 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Expression($self->semStack[$stackPos-(2-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 173 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Unset_($self->semStack[$stackPos-(5-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 174 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Foreach_($self->semStack[$stackPos-(7-3)], $self->semStack[$stackPos-(7-5)][0], ['keyVar' => null, 'byRef' => $self->semStack[$stackPos-(7-5)][1], 'stmts' => $self->semStack[$stackPos-(7-7)]], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + }, + 175 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Foreach_($self->semStack[$stackPos-(9-3)], $self->semStack[$stackPos-(9-7)][0], ['keyVar' => $self->semStack[$stackPos-(9-5)], 'byRef' => $self->semStack[$stackPos-(9-7)][1], 'stmts' => $self->semStack[$stackPos-(9-9)]], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 176 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Foreach_($self->semStack[$stackPos-(6-3)], new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(6-4)], $self->tokenEndStack[$stackPos-(6-4)])), ['stmts' => $self->semStack[$stackPos-(6-6)]], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])); + }, + 177 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Declare_($self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 178 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TryCatch($self->semStack[$stackPos-(6-3)], $self->semStack[$stackPos-(6-5)], $self->semStack[$stackPos-(6-6)], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])); $self->checkTryCatch($self->semValue); + }, + 179 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Goto_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 180 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Label($self->semStack[$stackPos-(2-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 181 => static function ($self, $stackPos) { + $self->semValue = null; /* means: no statement */ + }, + 182 => null, + 183 => static function ($self, $stackPos) { + $self->semValue = $self->maybeCreateNop($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]); + }, + 184 => static function ($self, $stackPos) { + if ($self->semStack[$stackPos-(1-1)] instanceof Stmt\Block) { $self->semValue = $self->semStack[$stackPos-(1-1)]->stmts; } else if ($self->semStack[$stackPos-(1-1)] === null) { $self->semValue = []; } else { $self->semValue = [$self->semStack[$stackPos-(1-1)]]; }; + }, + 185 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 186 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 187 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 188 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 189 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Catch_($self->semStack[$stackPos-(8-3)], $self->semStack[$stackPos-(8-4)], $self->semStack[$stackPos-(8-7)], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + }, + 190 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 191 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Finally_($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 192 => null, + 193 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 194 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 195 => static function ($self, $stackPos) { + $self->semValue = false; + }, + 196 => static function ($self, $stackPos) { + $self->semValue = true; + }, + 197 => static function ($self, $stackPos) { + $self->semValue = false; + }, + 198 => static function ($self, $stackPos) { + $self->semValue = true; + }, + 199 => static function ($self, $stackPos) { + $self->semValue = false; + }, + 200 => static function ($self, $stackPos) { + $self->semValue = true; + }, + 201 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 202 => static function ($self, $stackPos) { + $self->semValue = []; + }, + 203 => null, + 204 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 205 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Function_($self->semStack[$stackPos-(8-3)], ['byRef' => $self->semStack[$stackPos-(8-2)], 'params' => $self->semStack[$stackPos-(8-5)], 'returnType' => $self->semStack[$stackPos-(8-7)], 'stmts' => $self->semStack[$stackPos-(8-8)], 'attrGroups' => []], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + }, + 206 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Function_($self->semStack[$stackPos-(9-4)], ['byRef' => $self->semStack[$stackPos-(9-3)], 'params' => $self->semStack[$stackPos-(9-6)], 'returnType' => $self->semStack[$stackPos-(9-8)], 'stmts' => $self->semStack[$stackPos-(9-9)], 'attrGroups' => $self->semStack[$stackPos-(9-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 207 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Class_($self->semStack[$stackPos-(7-2)], ['type' => $self->semStack[$stackPos-(7-1)], 'extends' => $self->semStack[$stackPos-(7-3)], 'implements' => $self->semStack[$stackPos-(7-4)], 'stmts' => $self->semStack[$stackPos-(7-6)], 'attrGroups' => []], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + $self->checkClass($self->semValue, $stackPos-(7-2)); + }, + 208 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Class_($self->semStack[$stackPos-(8-3)], ['type' => $self->semStack[$stackPos-(8-2)], 'extends' => $self->semStack[$stackPos-(8-4)], 'implements' => $self->semStack[$stackPos-(8-5)], 'stmts' => $self->semStack[$stackPos-(8-7)], 'attrGroups' => $self->semStack[$stackPos-(8-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + $self->checkClass($self->semValue, $stackPos-(8-3)); + }, + 209 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Interface_($self->semStack[$stackPos-(7-3)], ['extends' => $self->semStack[$stackPos-(7-4)], 'stmts' => $self->semStack[$stackPos-(7-6)], 'attrGroups' => $self->semStack[$stackPos-(7-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + $self->checkInterface($self->semValue, $stackPos-(7-3)); + }, + 210 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Trait_($self->semStack[$stackPos-(6-3)], ['stmts' => $self->semStack[$stackPos-(6-5)], 'attrGroups' => $self->semStack[$stackPos-(6-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])); + }, + 211 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Enum_($self->semStack[$stackPos-(8-3)], ['scalarType' => $self->semStack[$stackPos-(8-4)], 'implements' => $self->semStack[$stackPos-(8-5)], 'stmts' => $self->semStack[$stackPos-(8-7)], 'attrGroups' => $self->semStack[$stackPos-(8-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + $self->checkEnum($self->semValue, $stackPos-(8-3)); + }, + 212 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 213 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; + }, + 214 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 215 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; + }, + 216 => static function ($self, $stackPos) { + $self->semValue = 0; + }, + 217 => null, + 218 => null, + 219 => static function ($self, $stackPos) { + $self->checkClassModifier($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $stackPos-(2-2)); $self->semValue = $self->semStack[$stackPos-(2-1)] | $self->semStack[$stackPos-(2-2)]; + }, + 220 => static function ($self, $stackPos) { + $self->semValue = Modifiers::ABSTRACT; + }, + 221 => static function ($self, $stackPos) { + $self->semValue = Modifiers::FINAL; + }, + 222 => static function ($self, $stackPos) { + $self->semValue = Modifiers::READONLY; + }, + 223 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 224 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; + }, + 225 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 226 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; + }, + 227 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 228 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; + }, + 229 => null, + 230 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 231 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 232 => null, + 233 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 234 => null, + 235 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 236 => static function ($self, $stackPos) { + if ($self->semStack[$stackPos-(1-1)] instanceof Stmt\Block) { $self->semValue = $self->semStack[$stackPos-(1-1)]->stmts; } else if ($self->semStack[$stackPos-(1-1)] === null) { $self->semValue = []; } else { $self->semValue = [$self->semStack[$stackPos-(1-1)]]; }; + }, + 237 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 238 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 239 => null, + 240 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 241 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 242 => static function ($self, $stackPos) { + $self->semValue = new Node\DeclareItem($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 243 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 244 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-3)]; + }, + 245 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 246 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(5-3)]; + }, + 247 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 248 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 249 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Case_($self->semStack[$stackPos-(4-2)], $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 250 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Case_(null, $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 251 => null, + 252 => null, + 253 => static function ($self, $stackPos) { + $self->semValue = new Expr\Match_($self->semStack[$stackPos-(7-3)], $self->semStack[$stackPos-(7-6)], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + }, + 254 => static function ($self, $stackPos) { + $self->semValue = []; + }, + 255 => null, + 256 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 257 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 258 => static function ($self, $stackPos) { + $self->semValue = new Node\MatchArm($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 259 => static function ($self, $stackPos) { + $self->semValue = new Node\MatchArm(null, $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 260 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(1-1)]; + }, + 261 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 262 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 263 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 264 => static function ($self, $stackPos) { + $self->semValue = new Stmt\ElseIf_($self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 265 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 266 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 267 => static function ($self, $stackPos) { + $self->semValue = new Stmt\ElseIf_($self->semStack[$stackPos-(6-3)], $self->semStack[$stackPos-(6-6)], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])); $self->fixupAlternativeElse($self->semValue); + }, + 268 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 269 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Else_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 270 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 271 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Else_($self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); $self->fixupAlternativeElse($self->semValue); + }, + 272 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)], false); + }, + 273 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(2-2)], true); + }, + 274 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)], false); + }, + 275 => static function ($self, $stackPos) { + $self->semValue = array($self->fixupArrayDestructuring($self->semStack[$stackPos-(1-1)]), false); + }, + 276 => null, + 277 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 278 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 279 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 280 => static function ($self, $stackPos) { + $self->semValue = 0; + }, + 281 => static function ($self, $stackPos) { + $self->checkModifier($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $stackPos-(2-2)); $self->semValue = $self->semStack[$stackPos-(2-1)] | $self->semStack[$stackPos-(2-2)]; + }, + 282 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PUBLIC; + }, + 283 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PROTECTED; + }, + 284 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PRIVATE; + }, + 285 => static function ($self, $stackPos) { + $self->semValue = Modifiers::READONLY; + }, + 286 => static function ($self, $stackPos) { + $self->semValue = new Node\Param($self->semStack[$stackPos-(6-6)], null, $self->semStack[$stackPos-(6-3)], $self->semStack[$stackPos-(6-4)], $self->semStack[$stackPos-(6-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(6-2)], $self->semStack[$stackPos-(6-1)]); + $self->checkParam($self->semValue); + }, + 287 => static function ($self, $stackPos) { + $self->semValue = new Node\Param($self->semStack[$stackPos-(8-6)], $self->semStack[$stackPos-(8-8)], $self->semStack[$stackPos-(8-3)], $self->semStack[$stackPos-(8-4)], $self->semStack[$stackPos-(8-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(8-2)], $self->semStack[$stackPos-(8-1)]); + $self->checkParam($self->semValue); + }, + 288 => static function ($self, $stackPos) { + $self->semValue = new Node\Param(new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])), null, $self->semStack[$stackPos-(6-3)], $self->semStack[$stackPos-(6-4)], $self->semStack[$stackPos-(6-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(6-2)], $self->semStack[$stackPos-(6-1)]); + }, + 289 => null, + 290 => static function ($self, $stackPos) { + $self->semValue = new Node\NullableType($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 291 => static function ($self, $stackPos) { + $self->semValue = new Node\UnionType($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 292 => null, + 293 => null, + 294 => static function ($self, $stackPos) { + $self->semValue = new Node\Name('static', $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 295 => static function ($self, $stackPos) { + $self->semValue = $self->handleBuiltinTypes($self->semStack[$stackPos-(1-1)]); + }, + 296 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier('array', $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 297 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier('callable', $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 298 => null, + 299 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 300 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)]); + }, + 301 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 302 => null, + 303 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 304 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)]); + }, + 305 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 306 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)]); + }, + 307 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 308 => static function ($self, $stackPos) { + $self->semValue = new Node\IntersectionType($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 309 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)]); + }, + 310 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 311 => static function ($self, $stackPos) { + $self->semValue = new Node\IntersectionType($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 312 => null, + 313 => static function ($self, $stackPos) { + $self->semValue = new Node\NullableType($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 314 => static function ($self, $stackPos) { + $self->semValue = new Node\UnionType($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 315 => null, + 316 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 317 => null, + 318 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 319 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; + }, + 320 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 321 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 322 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 323 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-2)]); + }, + 324 => static function ($self, $stackPos) { + $self->semValue = new Node\VariadicPlaceholder($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 325 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 326 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 327 => static function ($self, $stackPos) { + $self->semValue = new Node\Arg($self->semStack[$stackPos-(1-1)], false, false, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 328 => static function ($self, $stackPos) { + $self->semValue = new Node\Arg($self->semStack[$stackPos-(2-2)], true, false, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 329 => static function ($self, $stackPos) { + $self->semValue = new Node\Arg($self->semStack[$stackPos-(2-2)], false, true, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 330 => static function ($self, $stackPos) { + $self->semValue = new Node\Arg($self->semStack[$stackPos-(3-3)], false, false, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(3-1)]); + }, + 331 => null, + 332 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 333 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 334 => null, + 335 => null, + 336 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 337 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 338 => static function ($self, $stackPos) { + $self->semValue = new Node\StaticVar($self->semStack[$stackPos-(1-1)], null, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 339 => static function ($self, $stackPos) { + $self->semValue = new Node\StaticVar($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 340 => static function ($self, $stackPos) { + if ($self->semStack[$stackPos-(2-2)] !== null) { $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; } else { $self->semValue = $self->semStack[$stackPos-(2-1)]; } + }, + 341 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 342 => static function ($self, $stackPos) { + $nop = $self->maybeCreateZeroLengthNop($self->tokenPos);; + if ($nop !== null) { $self->semStack[$stackPos-(1-1)][] = $nop; } $self->semValue = $self->semStack[$stackPos-(1-1)]; + }, + 343 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Property($self->semStack[$stackPos-(5-2)], $self->semStack[$stackPos-(5-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-1)]); + $self->checkProperty($self->semValue, $stackPos-(5-2)); + }, + 344 => static function ($self, $stackPos) { + $self->semValue = new Stmt\ClassConst($self->semStack[$stackPos-(5-4)], $self->semStack[$stackPos-(5-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(5-1)]); + $self->checkClassConst($self->semValue, $stackPos-(5-2)); + }, + 345 => static function ($self, $stackPos) { + $self->semValue = new Stmt\ClassConst($self->semStack[$stackPos-(6-5)], $self->semStack[$stackPos-(6-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(6-1)], $self->semStack[$stackPos-(6-4)]); + $self->checkClassConst($self->semValue, $stackPos-(6-2)); + }, + 346 => static function ($self, $stackPos) { + $self->semValue = new Stmt\ClassMethod($self->semStack[$stackPos-(10-5)], ['type' => $self->semStack[$stackPos-(10-2)], 'byRef' => $self->semStack[$stackPos-(10-4)], 'params' => $self->semStack[$stackPos-(10-7)], 'returnType' => $self->semStack[$stackPos-(10-9)], 'stmts' => $self->semStack[$stackPos-(10-10)], 'attrGroups' => $self->semStack[$stackPos-(10-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(10-1)], $self->tokenEndStack[$stackPos])); + $self->checkClassMethod($self->semValue, $stackPos-(10-2)); + }, + 347 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TraitUse($self->semStack[$stackPos-(3-2)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 348 => static function ($self, $stackPos) { + $self->semValue = new Stmt\EnumCase($self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-4)], $self->semStack[$stackPos-(5-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 349 => static function ($self, $stackPos) { + $self->semValue = null; /* will be skipped */ + }, + 350 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 351 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 352 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 353 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 354 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TraitUseAdaptation\Precedence($self->semStack[$stackPos-(4-1)][0], $self->semStack[$stackPos-(4-1)][1], $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 355 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TraitUseAdaptation\Alias($self->semStack[$stackPos-(5-1)][0], $self->semStack[$stackPos-(5-1)][1], $self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 356 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TraitUseAdaptation\Alias($self->semStack[$stackPos-(4-1)][0], $self->semStack[$stackPos-(4-1)][1], $self->semStack[$stackPos-(4-3)], null, $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 357 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TraitUseAdaptation\Alias($self->semStack[$stackPos-(4-1)][0], $self->semStack[$stackPos-(4-1)][1], null, $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 358 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TraitUseAdaptation\Alias($self->semStack[$stackPos-(4-1)][0], $self->semStack[$stackPos-(4-1)][1], null, $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 359 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)]); + }, + 360 => null, + 361 => static function ($self, $stackPos) { + $self->semValue = array(null, $self->semStack[$stackPos-(1-1)]); + }, + 362 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 363 => null, + 364 => null, + 365 => static function ($self, $stackPos) { + $self->semValue = 0; + }, + 366 => static function ($self, $stackPos) { + $self->semValue = 0; + }, + 367 => null, + 368 => null, + 369 => static function ($self, $stackPos) { + $self->checkModifier($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $stackPos-(2-2)); $self->semValue = $self->semStack[$stackPos-(2-1)] | $self->semStack[$stackPos-(2-2)]; + }, + 370 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PUBLIC; + }, + 371 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PROTECTED; + }, + 372 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PRIVATE; + }, + 373 => static function ($self, $stackPos) { + $self->semValue = Modifiers::STATIC; + }, + 374 => static function ($self, $stackPos) { + $self->semValue = Modifiers::ABSTRACT; + }, + 375 => static function ($self, $stackPos) { + $self->semValue = Modifiers::FINAL; + }, + 376 => static function ($self, $stackPos) { + $self->semValue = Modifiers::READONLY; + }, + 377 => null, + 378 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 379 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 380 => static function ($self, $stackPos) { + $self->semValue = new Node\VarLikeIdentifier(substr($self->semStack[$stackPos-(1-1)], 1), $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 381 => static function ($self, $stackPos) { + $self->semValue = new Node\PropertyItem($self->semStack[$stackPos-(1-1)], null, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 382 => static function ($self, $stackPos) { + $self->semValue = new Node\PropertyItem($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 383 => null, + 384 => null, + 385 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 386 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 387 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 388 => null, + 389 => null, + 390 => static function ($self, $stackPos) { + $self->semValue = new Expr\Assign($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 391 => static function ($self, $stackPos) { + $self->semValue = new Expr\Assign($self->fixupArrayDestructuring($self->semStack[$stackPos-(3-1)]), $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 392 => static function ($self, $stackPos) { + $self->semValue = new Expr\Assign($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 393 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignRef($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 394 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignRef($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + if (!$self->phpVersion->allowsAssignNewByReference()) { + $self->emitError(new Error('Cannot assign new by reference', $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos]))); + } + + }, + 395 => null, + 396 => null, + 397 => static function ($self, $stackPos) { + $self->semValue = new Expr\Clone_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 398 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Plus($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 399 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Minus($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 400 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Mul($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 401 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Div($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 402 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Concat($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 403 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Mod($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 404 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\BitwiseAnd($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 405 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\BitwiseOr($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 406 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\BitwiseXor($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 407 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\ShiftLeft($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 408 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\ShiftRight($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 409 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Pow($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 410 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Coalesce($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 411 => static function ($self, $stackPos) { + $self->semValue = new Expr\PostInc($self->semStack[$stackPos-(2-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 412 => static function ($self, $stackPos) { + $self->semValue = new Expr\PreInc($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 413 => static function ($self, $stackPos) { + $self->semValue = new Expr\PostDec($self->semStack[$stackPos-(2-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 414 => static function ($self, $stackPos) { + $self->semValue = new Expr\PreDec($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 415 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\BooleanOr($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 416 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\BooleanAnd($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 417 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\LogicalOr($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 418 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\LogicalAnd($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 419 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\LogicalXor($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 420 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\BitwiseOr($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 421 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\BitwiseAnd($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 422 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\BitwiseAnd($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 423 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\BitwiseXor($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 424 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Concat($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 425 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Plus($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 426 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Minus($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 427 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Mul($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 428 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Div($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 429 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Mod($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 430 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\ShiftLeft($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 431 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\ShiftRight($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 432 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Pow($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 433 => static function ($self, $stackPos) { + $self->semValue = new Expr\UnaryPlus($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 434 => static function ($self, $stackPos) { + $self->semValue = new Expr\UnaryMinus($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 435 => static function ($self, $stackPos) { + $self->semValue = new Expr\BooleanNot($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 436 => static function ($self, $stackPos) { + $self->semValue = new Expr\BitwiseNot($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 437 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Identical($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 438 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\NotIdentical($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 439 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Equal($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 440 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\NotEqual($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 441 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Spaceship($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 442 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Smaller($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 443 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\SmallerOrEqual($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 444 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Greater($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 445 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\GreaterOrEqual($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 446 => static function ($self, $stackPos) { + $self->semValue = new Expr\Instanceof_($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 447 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 448 => static function ($self, $stackPos) { + $self->semValue = new Expr\Ternary($self->semStack[$stackPos-(5-1)], $self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 449 => static function ($self, $stackPos) { + $self->semValue = new Expr\Ternary($self->semStack[$stackPos-(4-1)], null, $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 450 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Coalesce($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 451 => static function ($self, $stackPos) { + $self->semValue = new Expr\Isset_($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 452 => static function ($self, $stackPos) { + $self->semValue = new Expr\Empty_($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 453 => static function ($self, $stackPos) { + $self->semValue = new Expr\Include_($self->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 454 => static function ($self, $stackPos) { + $self->semValue = new Expr\Include_($self->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE_ONCE, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 455 => static function ($self, $stackPos) { + $self->semValue = new Expr\Eval_($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 456 => static function ($self, $stackPos) { + $self->semValue = new Expr\Include_($self->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 457 => static function ($self, $stackPos) { + $self->semValue = new Expr\Include_($self->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE_ONCE, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 458 => static function ($self, $stackPos) { + $self->semValue = new Expr\Cast\Int_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 459 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]); + $attrs['kind'] = $self->getFloatCastKind($self->semStack[$stackPos-(2-1)]); + $self->semValue = new Expr\Cast\Double($self->semStack[$stackPos-(2-2)], $attrs); + }, + 460 => static function ($self, $stackPos) { + $self->semValue = new Expr\Cast\String_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 461 => static function ($self, $stackPos) { + $self->semValue = new Expr\Cast\Array_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 462 => static function ($self, $stackPos) { + $self->semValue = new Expr\Cast\Object_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 463 => static function ($self, $stackPos) { + $self->semValue = new Expr\Cast\Bool_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 464 => static function ($self, $stackPos) { + $self->semValue = new Expr\Cast\Unset_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 465 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]); + $attrs['kind'] = strtolower($self->semStack[$stackPos-(2-1)]) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE; + $self->semValue = new Expr\Exit_($self->semStack[$stackPos-(2-2)], $attrs); + }, + 466 => static function ($self, $stackPos) { + $self->semValue = new Expr\ErrorSuppress($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 467 => null, + 468 => static function ($self, $stackPos) { + $self->semValue = new Expr\ShellExec($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 469 => static function ($self, $stackPos) { + $self->semValue = new Expr\Print_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 470 => static function ($self, $stackPos) { + $self->semValue = new Expr\Yield_(null, null, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 471 => static function ($self, $stackPos) { + $self->semValue = new Expr\Yield_($self->semStack[$stackPos-(2-2)], null, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 472 => static function ($self, $stackPos) { + $self->semValue = new Expr\Yield_($self->semStack[$stackPos-(4-4)], $self->semStack[$stackPos-(4-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 473 => static function ($self, $stackPos) { + $self->semValue = new Expr\YieldFrom($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 474 => static function ($self, $stackPos) { + $self->semValue = new Expr\Throw_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 475 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrowFunction(['static' => false, 'byRef' => $self->semStack[$stackPos-(8-2)], 'params' => $self->semStack[$stackPos-(8-4)], 'returnType' => $self->semStack[$stackPos-(8-6)], 'expr' => $self->semStack[$stackPos-(8-8)], 'attrGroups' => []], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + }, + 476 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrowFunction(['static' => true, 'byRef' => $self->semStack[$stackPos-(9-3)], 'params' => $self->semStack[$stackPos-(9-5)], 'returnType' => $self->semStack[$stackPos-(9-7)], 'expr' => $self->semStack[$stackPos-(9-9)], 'attrGroups' => []], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 477 => static function ($self, $stackPos) { + $self->semValue = new Expr\Closure(['static' => false, 'byRef' => $self->semStack[$stackPos-(8-2)], 'params' => $self->semStack[$stackPos-(8-4)], 'uses' => $self->semStack[$stackPos-(8-6)], 'returnType' => $self->semStack[$stackPos-(8-7)], 'stmts' => $self->semStack[$stackPos-(8-8)], 'attrGroups' => []], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + }, + 478 => static function ($self, $stackPos) { + $self->semValue = new Expr\Closure(['static' => true, 'byRef' => $self->semStack[$stackPos-(9-3)], 'params' => $self->semStack[$stackPos-(9-5)], 'uses' => $self->semStack[$stackPos-(9-7)], 'returnType' => $self->semStack[$stackPos-(9-8)], 'stmts' => $self->semStack[$stackPos-(9-9)], 'attrGroups' => []], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 479 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrowFunction(['static' => false, 'byRef' => $self->semStack[$stackPos-(9-3)], 'params' => $self->semStack[$stackPos-(9-5)], 'returnType' => $self->semStack[$stackPos-(9-7)], 'expr' => $self->semStack[$stackPos-(9-9)], 'attrGroups' => $self->semStack[$stackPos-(9-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 480 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrowFunction(['static' => true, 'byRef' => $self->semStack[$stackPos-(10-4)], 'params' => $self->semStack[$stackPos-(10-6)], 'returnType' => $self->semStack[$stackPos-(10-8)], 'expr' => $self->semStack[$stackPos-(10-10)], 'attrGroups' => $self->semStack[$stackPos-(10-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(10-1)], $self->tokenEndStack[$stackPos])); + }, + 481 => static function ($self, $stackPos) { + $self->semValue = new Expr\Closure(['static' => false, 'byRef' => $self->semStack[$stackPos-(9-3)], 'params' => $self->semStack[$stackPos-(9-5)], 'uses' => $self->semStack[$stackPos-(9-7)], 'returnType' => $self->semStack[$stackPos-(9-8)], 'stmts' => $self->semStack[$stackPos-(9-9)], 'attrGroups' => $self->semStack[$stackPos-(9-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 482 => static function ($self, $stackPos) { + $self->semValue = new Expr\Closure(['static' => true, 'byRef' => $self->semStack[$stackPos-(10-4)], 'params' => $self->semStack[$stackPos-(10-6)], 'uses' => $self->semStack[$stackPos-(10-8)], 'returnType' => $self->semStack[$stackPos-(10-9)], 'stmts' => $self->semStack[$stackPos-(10-10)], 'attrGroups' => $self->semStack[$stackPos-(10-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(10-1)], $self->tokenEndStack[$stackPos])); + }, + 483 => static function ($self, $stackPos) { + $self->semValue = array(new Stmt\Class_(null, ['type' => $self->semStack[$stackPos-(8-2)], 'extends' => $self->semStack[$stackPos-(8-4)], 'implements' => $self->semStack[$stackPos-(8-5)], 'stmts' => $self->semStack[$stackPos-(8-7)], 'attrGroups' => $self->semStack[$stackPos-(8-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])), $self->semStack[$stackPos-(8-3)]); + $self->checkClass($self->semValue[0], -1); + }, + 484 => static function ($self, $stackPos) { + $self->semValue = new Expr\New_($self->semStack[$stackPos-(3-2)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 485 => static function ($self, $stackPos) { + list($class, $ctorArgs) = $self->semStack[$stackPos-(2-2)]; $self->semValue = new Expr\New_($class, $ctorArgs, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 486 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 487 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-3)]; + }, + 488 => null, + 489 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 490 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 491 => static function ($self, $stackPos) { + $self->semValue = new Node\ClosureUse($self->semStack[$stackPos-(2-2)], $self->semStack[$stackPos-(2-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 492 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 493 => static function ($self, $stackPos) { + $self->semValue = new Expr\FuncCall($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 494 => static function ($self, $stackPos) { + $self->semValue = new Expr\FuncCall($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 495 => static function ($self, $stackPos) { + $self->semValue = new Expr\FuncCall($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 496 => static function ($self, $stackPos) { + $self->semValue = new Expr\StaticCall($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 497 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 498 => null, + 499 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 500 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 501 => static function ($self, $stackPos) { + $self->semValue = new Name\FullyQualified(substr($self->semStack[$stackPos-(1-1)], 1), $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 502 => static function ($self, $stackPos) { + $self->semValue = new Name\Relative(substr($self->semStack[$stackPos-(1-1)], 10), $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 503 => null, + 504 => null, + 505 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 506 => static function ($self, $stackPos) { + $self->semValue = new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); $self->errorState = 2; + }, + 507 => null, + 508 => null, + 509 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 510 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 511 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 512 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); foreach ($self->semValue as $s) { if ($s instanceof Node\InterpolatedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '`', $self->phpVersion->supportsUnicodeEscapes()); } }; + }, + 513 => static function ($self, $stackPos) { + foreach ($self->semStack[$stackPos-(1-1)] as $s) { if ($s instanceof Node\InterpolatedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '`', $self->phpVersion->supportsUnicodeEscapes()); } }; $self->semValue = $self->semStack[$stackPos-(1-1)]; + }, + 514 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 515 => null, + 516 => static function ($self, $stackPos) { + $self->semValue = new Expr\ConstFetch($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 517 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Line($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 518 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\File($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 519 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Dir($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 520 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Class_($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 521 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Trait_($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 522 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Method($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 523 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Function_($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 524 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Namespace_($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 525 => static function ($self, $stackPos) { + $self->semValue = new Expr\ClassConstFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 526 => static function ($self, $stackPos) { + $self->semValue = new Expr\ClassConstFetch($self->semStack[$stackPos-(5-1)], $self->semStack[$stackPos-(5-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 527 => static function ($self, $stackPos) { + $self->semValue = new Expr\ClassConstFetch($self->semStack[$stackPos-(3-1)], new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(3-3)], $self->tokenEndStack[$stackPos-(3-3)])), $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); $self->errorState = 2; + }, + 528 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]); $attrs['kind'] = Expr\Array_::KIND_SHORT; + $self->semValue = new Expr\Array_($self->semStack[$stackPos-(3-2)], $attrs); + }, + 529 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos]); $attrs['kind'] = Expr\Array_::KIND_LONG; + $self->semValue = new Expr\Array_($self->semStack[$stackPos-(4-3)], $attrs); + $self->createdArrays->attach($self->semValue); + }, + 530 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(1-1)]; $self->createdArrays->attach($self->semValue); + }, + 531 => static function ($self, $stackPos) { + $self->semValue = Scalar\String_::fromString($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]), $self->phpVersion->supportsUnicodeEscapes()); + }, + 532 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED; + foreach ($self->semStack[$stackPos-(3-2)] as $s) { if ($s instanceof Node\InterpolatedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '"', $self->phpVersion->supportsUnicodeEscapes()); } }; $self->semValue = new Scalar\InterpolatedString($self->semStack[$stackPos-(3-2)], $attrs); + }, + 533 => static function ($self, $stackPos) { + $self->semValue = $self->parseLNumber($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]), $self->phpVersion->allowsInvalidOctals()); + }, + 534 => static function ($self, $stackPos) { + $self->semValue = Scalar\Float_::fromString($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 535 => null, + 536 => null, + 537 => null, + 538 => static function ($self, $stackPos) { + $self->semValue = $self->parseDocString($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-2)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]), $self->getAttributes($self->tokenStartStack[$stackPos-(3-3)], $self->tokenEndStack[$stackPos-(3-3)]), true); + }, + 539 => static function ($self, $stackPos) { + $self->semValue = $self->parseDocString($self->semStack[$stackPos-(2-1)], '', $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]), $self->getAttributes($self->tokenStartStack[$stackPos-(2-2)], $self->tokenEndStack[$stackPos-(2-2)]), true); + }, + 540 => static function ($self, $stackPos) { + $self->semValue = $self->parseDocString($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-2)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]), $self->getAttributes($self->tokenStartStack[$stackPos-(3-3)], $self->tokenEndStack[$stackPos-(3-3)]), true); + }, + 541 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 542 => null, + 543 => null, + 544 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 545 => null, + 546 => null, + 547 => null, + 548 => null, + 549 => null, + 550 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 551 => null, + 552 => null, + 553 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrayDimFetch($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 554 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrayDimFetch($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 555 => null, + 556 => static function ($self, $stackPos) { + $self->semValue = new Expr\MethodCall($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 557 => static function ($self, $stackPos) { + $self->semValue = new Expr\NullsafeMethodCall($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 558 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 559 => null, + 560 => null, + 561 => null, + 562 => static function ($self, $stackPos) { + $self->semValue = new Expr\PropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 563 => static function ($self, $stackPos) { + $self->semValue = new Expr\NullsafePropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 564 => null, + 565 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 566 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 567 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable(new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])), $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); $self->errorState = 2; + }, + 568 => static function ($self, $stackPos) { + $var = $self->semStack[$stackPos-(1-1)]->name; $self->semValue = \is_string($var) ? new Node\VarLikeIdentifier($var, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])) : $var; + }, + 569 => static function ($self, $stackPos) { + $self->semValue = new Expr\StaticPropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 570 => null, + 571 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrayDimFetch($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 572 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrayDimFetch($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 573 => static function ($self, $stackPos) { + $self->semValue = new Expr\PropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 574 => static function ($self, $stackPos) { + $self->semValue = new Expr\NullsafePropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 575 => static function ($self, $stackPos) { + $self->semValue = new Expr\StaticPropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 576 => static function ($self, $stackPos) { + $self->semValue = new Expr\StaticPropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 577 => null, + 578 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 579 => null, + 580 => null, + 581 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 582 => null, + 583 => static function ($self, $stackPos) { + $self->semValue = new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); $self->errorState = 2; + }, + 584 => static function ($self, $stackPos) { + $self->semValue = new Expr\List_($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); $self->semValue->setAttribute('kind', Expr\List_::KIND_LIST); + $self->postprocessList($self->semValue); + }, + 585 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(1-1)]; $end = count($self->semValue)-1; if ($self->semValue[$end]->value instanceof Expr\Error) array_pop($self->semValue); + }, + 586 => null, + 587 => static function ($self, $stackPos) { + /* do nothing -- prevent default action of $$=$self->semStack[$1]. See $551. */ + }, + 588 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 589 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 590 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(1-1)], null, false, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 591 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(2-2)], null, true, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 592 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(1-1)], null, false, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 593 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(3-3)], $self->semStack[$stackPos-(3-1)], false, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 594 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(4-4)], $self->semStack[$stackPos-(4-1)], true, $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 595 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(3-3)], $self->semStack[$stackPos-(3-1)], false, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 596 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(2-2)], null, false, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]), true); + }, + 597 => static function ($self, $stackPos) { + /* Create an Error node now to remember the position. We'll later either report an error, + or convert this into a null element, depending on whether this is a creation or destructuring context. */ + $attrs = $self->createEmptyElemAttributes($self->tokenPos); + $self->semValue = new Node\ArrayItem(new Expr\Error($attrs), null, false, $attrs); + }, + 598 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 599 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 600 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 601 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)]); + }, + 602 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]); $attrs['rawValue'] = $self->semStack[$stackPos-(1-1)]; $self->semValue = new Node\InterpolatedStringPart($self->semStack[$stackPos-(1-1)], $attrs); + }, + 603 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 604 => null, + 605 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrayDimFetch($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 606 => static function ($self, $stackPos) { + $self->semValue = new Expr\PropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 607 => static function ($self, $stackPos) { + $self->semValue = new Expr\NullsafePropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 608 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 609 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 610 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrayDimFetch($self->semStack[$stackPos-(6-2)], $self->semStack[$stackPos-(6-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])); + }, + 611 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 612 => static function ($self, $stackPos) { + $self->semValue = new Scalar\String_($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 613 => static function ($self, $stackPos) { + $self->semValue = $self->parseNumString($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 614 => static function ($self, $stackPos) { + $self->semValue = $self->parseNumString('-' . $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 615 => null, + ]; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Parser/Php8.php b/vendor/nikic/php-parser/lib/PhpParser/Parser/Php8.php new file mode 100644 index 00000000..27154d24 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Parser/Php8.php @@ -0,0 +1,2717 @@ +'", + "T_IS_GREATER_OR_EQUAL", + "'.'", + "T_SL", + "T_SR", + "'+'", + "'-'", + "'*'", + "'/'", + "'%'", + "'!'", + "T_INSTANCEOF", + "'~'", + "T_INC", + "T_DEC", + "T_INT_CAST", + "T_DOUBLE_CAST", + "T_STRING_CAST", + "T_ARRAY_CAST", + "T_OBJECT_CAST", + "T_BOOL_CAST", + "T_UNSET_CAST", + "'@'", + "T_POW", + "'['", + "T_NEW", + "T_CLONE", + "T_EXIT", + "T_IF", + "T_ELSEIF", + "T_ELSE", + "T_ENDIF", + "T_LNUMBER", + "T_DNUMBER", + "T_STRING", + "T_STRING_VARNAME", + "T_VARIABLE", + "T_NUM_STRING", + "T_INLINE_HTML", + "T_ENCAPSED_AND_WHITESPACE", + "T_CONSTANT_ENCAPSED_STRING", + "T_ECHO", + "T_DO", + "T_WHILE", + "T_ENDWHILE", + "T_FOR", + "T_ENDFOR", + "T_FOREACH", + "T_ENDFOREACH", + "T_DECLARE", + "T_ENDDECLARE", + "T_AS", + "T_SWITCH", + "T_MATCH", + "T_ENDSWITCH", + "T_CASE", + "T_DEFAULT", + "T_BREAK", + "T_CONTINUE", + "T_GOTO", + "T_FUNCTION", + "T_FN", + "T_CONST", + "T_RETURN", + "T_TRY", + "T_CATCH", + "T_FINALLY", + "T_USE", + "T_INSTEADOF", + "T_GLOBAL", + "T_STATIC", + "T_ABSTRACT", + "T_FINAL", + "T_PRIVATE", + "T_PROTECTED", + "T_PUBLIC", + "T_READONLY", + "T_VAR", + "T_UNSET", + "T_ISSET", + "T_EMPTY", + "T_HALT_COMPILER", + "T_CLASS", + "T_TRAIT", + "T_INTERFACE", + "T_ENUM", + "T_EXTENDS", + "T_IMPLEMENTS", + "T_OBJECT_OPERATOR", + "T_NULLSAFE_OBJECT_OPERATOR", + "T_LIST", + "T_ARRAY", + "T_CALLABLE", + "T_CLASS_C", + "T_TRAIT_C", + "T_METHOD_C", + "T_FUNC_C", + "T_LINE", + "T_FILE", + "T_START_HEREDOC", + "T_END_HEREDOC", + "T_DOLLAR_OPEN_CURLY_BRACES", + "T_CURLY_OPEN", + "T_PAAMAYIM_NEKUDOTAYIM", + "T_NAMESPACE", + "T_NS_C", + "T_DIR", + "T_NS_SEPARATOR", + "T_ELLIPSIS", + "T_NAME_FULLY_QUALIFIED", + "T_NAME_QUALIFIED", + "T_NAME_RELATIVE", + "T_ATTRIBUTE", + "';'", + "']'", + "'('", + "')'", + "'{'", + "'}'", + "'`'", + "'\"'", + "'$'" + ); + + protected array $tokenToSymbol = array( + 0, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 56, 166, 168, 167, 55, 168, 168, + 161, 162, 53, 51, 8, 52, 48, 54, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 31, 159, + 44, 16, 46, 30, 68, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 70, 168, 160, 36, 168, 165, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 163, 35, 164, 58, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 1, 2, 3, 4, + 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 32, 33, 34, 37, 38, 39, 40, + 41, 42, 43, 45, 47, 49, 50, 57, 59, 60, + 61, 62, 63, 64, 65, 66, 67, 69, 71, 72, + 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, + 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, + 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, + 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, + 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, + 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, + 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, + 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, + 153, 154, 155, 156, 157, 158 + ); + + protected array $action = array( + 133, 134, 135, 582, 136, 137, 0, 751, 752, 753, + 138, 38, 327,-32766,-32766,-32766,-32766,-32766,-32766, 837, + 826,-32767,-32767,-32767,-32767, 102, 103, 104, 1112, 1113, + 1114, 1111, 1110, 1109, 1115, 745, 744,-32766, 1027,-32766, + -32766,-32766,-32766,-32766,-32766,-32766,-32767,-32767,-32767,-32767, + -32767, 1245,-32766,-32766, 1322, 754, 1112, 1113, 1114, 1111, + 1110, 1109, 1115, 459, 460, 461, 2, 990, 1306, 265, + 139, 404, 758, 759, 760, 761, 467, 468, 429, 835, + 606, -16, 1341, 23, 292, 815, 762, 763, 764, 765, + 766, 767, 768, 769, 770, 771, 791, 583, 792, 793, + 794, 795, 783, 784, 345, 346, 786, 787, 772, 773, + 774, 776, 777, 778, 356, 818, 819, 820, 821, 822, + 584, 779, 780, 585, 586, 941, 803, 801, 802, 814, + 798, 799, 835, 826, 587, 588, 797, 589, 590, 591, + 592, 593, 594, -328, 36, 251, 35, -194, 800, 595, + 596, -193, 140, -85, 133, 134, 135, 582, 136, 137, + 1060, 751, 752, 753, 138, 38, 129, -110, -110, -585, + -32766, -585, -110,-32766,-32766,-32766, 241, 836, -110, 145, + 959, 960,-32766,-32766,-32766, 961, -594,-32766, 482, 745, + 744, 955, 1036, -594,-32766, 991,-32766,-32766,-32766,-32766, + -32766,-32766,-32766,-32766,-32766,-32766,-32766,-32766, 299, 754, + 831, 75,-32766,-32766,-32766, 291, 142, 326, 242, -85, + 326, 382, 381, 265, 139, 404, 758, 759, 760, 761, + 82, 423, 429,-32766, 326,-32766,-32766,-32766,-32766, 815, + 762, 763, 764, 765, 766, 767, 768, 769, 770, 771, + 791, 583, 792, 793, 794, 795, 783, 784, 345, 346, + 786, 787, 772, 773, 774, 776, 777, 778, 356, 818, + 819, 820, 821, 822, 584, 779, 780, 585, 586, 254, + 803, 801, 802, 814, 798, 799, 832, 725, 587, 588, + 797, 589, 590, 591, 592, 593, 594, -328, 83, 84, + 85, -194, 800, 595, 596, -193, 149, 775, 746, 747, + 748, 749, 750, 151, 751, 752, 753, 788, 789, 37, + 483, 86, 87, 88, 89, 90, 91, 92, 93, 94, + 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, -594, 274, -594,-32766,-32766, + -32766,-32766,-32766,-32766, 310, 1089, 127, 312, 110, 737, + 1326, 21, 754,-32766,-32766,-32766, -272, 1325,-32766,-32766, + 1088,-32766,-32766,-32766,-32766,-32766, 755, 756, 757, 758, + 759, 760, 761, 1104,-32766, 824,-32766,-32766, -545, 429, + 1036, 323, 815, 762, 763, 764, 765, 766, 767, 768, + 769, 770, 771, 791, 813, 792, 793, 794, 795, 783, + 784, 785, 812, 786, 787, 772, 773, 774, 776, 777, + 778, 817, 818, 819, 820, 821, 822, 823, 779, 780, + 781, 782, 1033, 803, 801, 802, 814, 798, 799, 745, + 744, 790, 796, 797, 804, 805, 807, 806, 808, 809, + 152,-32766, -545, -545, 1036, 800, 811, 810, 50, 51, + 52, 513, 53, 54, 1240, 1239, 1241, -545, 55, 56, + -110, 57,-32766, 1090, 920, -110, 556, -110, 292, -551, + 339, -545, 306, 103, 104, -110, -110, -110, -110, -110, + -110, -110, -110, 105, 106, 107, 108, 109, 1245, 274, + 380, 381, -591, -367, 715, -367, 340, 58, 59, -591, + 423, 110, 60, 370, 61, 248, 249, 62, 63, 64, + 65, 66, 67, 68, 69, -544, 28, 267, 70, 445, + 514,-32766, 374, -342, 1272, 1273, 515, 1278, 835, 862, + 389, 863, 1270, 42, 25, 516, 943, 517, 943, 518, + 920, 519, 299, 1036, 520, 521, 1266, 910, 441, 44, + 45, 446, 377, 376,-32766, 46, 522, 1023, 1022, 1021, + 1024, 368, 338, 391, 1238, 7, 291, 442, 1231, 835, + 524, 525, 526, 443, 1245, 357, 1036, 362, 834, -544, + -544, 154, 528, 529, 444, 1259, 1260, 1261, 1262, 1256, + 1257, 298,-32766,-32766, -544, -548, 1059, 1263, 1258, 291, + 1236, 1240, 1239, 1241, 299, 841, -550, 71, -544, 656, + 26, 321, 322, 326, -153, -153, -153, 920, 612, 675, + 676, 1035, 922, 910,-32766, 286, 710, 835, 155, -153, + 828, -153, 862, -153, 863, -153, 150, 407, 156, 1240, + 1239, 1241,-32766,-32766,-32766, 375, 1351, 716, 75, 1352, + 158, -591, 33, -591, 326, 835, 959, 960, -78, -548, + -548, 523, 920,-32766, 378, 379, 896, 955, -110, -110, + -110, 32, 111, 112, 113, 114, 115, 116, 117, 118, + 119, 120, 121, 122, 123, 745, 744, -58, -548, -57, + -110, -110, 717, 745, 744, -110, 383, 384, 922, 1033, + 910, -110, 710, -153, 647, 648, 830, 124, 141, 125, + -32766, 1033, 326, 712, 1150, 1152, 48, 130, 131, 144, + 159, 1036,-32766, 160, 161, -543, 28, 162, 1238, 920, + 163, 299, 920, 1036, 75,-32766,-32766,-32766, 835,-32766, + 326,-32766, 1270,-32766, 282, 910,-32766, -87, -84, -78, + -73,-32766,-32766,-32766, -4, 920, 282,-32766,-32766, 720, + -72, -71, 727,-32766, 420, -70, -69, -68, -67, -66, + 287, 286,-32766, -65, -46, 922, 745, 744, 1231, 710, + 300, 301, -546, -18, 148, -302, 273, 283, 726, -543, + -543, 729, 528, 529, 920, 1259, 1260, 1261, 1262, 1256, + 1257, 919, 74, 147, -543, 288, 293, 1263, 1258, 126, + -298, 280, 910,-32766, 281, 910, 284, 73, -543, 1238, + 976, 690, 322, 326, 710, 285,-32766,-32766,-32766, 332, + -32766, 274,-32766, 294,-32766, 937, 110,-32766, 910, 685, + 835, -543,-32766,-32766,-32766, 826, -546, -546,-32766,-32766, + 146,-32766, -50, 701,-32766, 420, 703, 691, 20, 1119, + 375, -546, 436,-32766, 645, 1353, 1277, 297, 657,-32766, + 1279, 959, 960, 561, 956, -546, 523, 910, 692, 693, + 678, 527, 955, -110, -110, -110, 132, 922, 662, 663, + 922, 710, 464, -508, 710,-32766, 1240, 1239, 1241, 493, + 679, 1238, 282, 939, 10, -543, -543, 40,-32766,-32766, + -32766, 731,-32766, 922,-32766, 307,-32766, 710, -4,-32766, + -543, 305, 41, 304,-32766,-32766,-32766, 0, 0,-32766, + -32766,-32766, 920, 0, -543, 1238,-32766, 420, 311, 0, + 567, 299,-32766,-32766,-32766,-32766,-32766, -498,-32766, 897, + -32766, 0, 922,-32766, 8, 0, 710, 24,-32766,-32766, + -32766,-32766, 372, 610,-32766,-32766, 834, 1238, 734, -275, + -32766, 420, 920, 735,-32766,-32766,-32766, 854,-32766,-32766, + -32766, 901,-32766, 1000, 977,-32766, 49, 984, 974, 488, + -32766,-32766,-32766,-32766, 985, 899,-32766,-32766, 972, 1238, + 574, 1093,-32766, 420, 1096, 1097,-32766,-32766,-32766, 1094, + -32766,-32766,-32766, 1095,-32766, 910, 1101,-32766, 1267, 846, + 1292, 1310,-32766,-32766,-32766, 1344, 650, 34,-32766,-32766, + -579, -250, -250, -250,-32766, 420, -578, 375, -577, -551, + 28, 267, -550,-32766, -549, -492, 1, 29, 959, 960, + 302, 303, 835, 523, 30, 910, 1270, 39, 896, 955, + -110, -110, -110, 43, 47, 373, 72, 76, 77, 78, + 79, -249, -249, -249, 80, 81, 143, 375, 153, 128, + -273, 157, 247, 328, 357, 358, 359, 360, 959, 960, + 922, 361, 1231, 523, 710, -250, 362, 363, 896, 955, + -110, -110, -110, 364, 365, 366, 367, 529, 28, 1259, + 1260, 1261, 1262, 1256, 1257, 369, 437, 555, 1207, -272, + 835, 1263, 1258, 13, 1270, 14,-32766, 15, 16, 18, + 922, 73, 1238, 1348, 710, -249, 322, 326, 406,-32766, + -32766,-32766, 484,-32766, 485,-32766, 492,-32766, 495, 496, + -32766, 497, 498, 502, 503,-32766,-32766,-32766, 504, 511, + 1231,-32766,-32766, 572, 696, 1249, 1190,-32766, 420, 1268, + 1062, 1061, 1042, 1226, 1038, 529,-32766, 1259, 1260, 1261, + 1262, 1256, 1257, -277, -102, 12, 17, 27, 296, 1263, + 1258, 405, 603, 607, 636, 702, 1194, 1244, 1191, 73, + 320, 1323, 0, 371, 322, 326, 711, 714, 718, 719, + 721, 722, 723, 724, 728, 0, 713, 0, 1350, 857, + 856, 865, 949, 992, 864, 1349, 948, 946, 947, 950, + 1222, 930, 940, 928, 982, 983, 634, 1347, 1304, 1293, + 1311, 1320, 0, 0, 1271, 0, 326 + ); + + protected array $actionCheck = array( + 2, 3, 4, 5, 6, 7, 0, 9, 10, 11, + 12, 13, 70, 9, 10, 11, 9, 10, 11, 1, + 80, 44, 45, 46, 47, 48, 49, 50, 116, 117, + 118, 119, 120, 121, 122, 37, 38, 30, 1, 32, + 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, + 43, 1, 9, 10, 1, 57, 116, 117, 118, 119, + 120, 121, 122, 129, 130, 131, 8, 31, 1, 71, + 72, 73, 74, 75, 76, 77, 134, 135, 80, 82, + 1, 31, 85, 8, 30, 87, 88, 89, 90, 91, + 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, + 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, + 122, 123, 124, 125, 126, 1, 128, 129, 130, 131, + 132, 133, 82, 80, 136, 137, 138, 139, 140, 141, + 142, 143, 144, 8, 147, 148, 8, 8, 150, 151, + 152, 8, 154, 31, 2, 3, 4, 5, 6, 7, + 162, 9, 10, 11, 12, 13, 8, 117, 118, 160, + 116, 162, 122, 9, 10, 11, 97, 159, 128, 8, + 117, 118, 9, 10, 11, 122, 1, 137, 31, 37, + 38, 128, 138, 8, 30, 159, 32, 33, 34, 35, + 36, 37, 38, 30, 9, 32, 33, 34, 158, 57, + 80, 161, 9, 10, 11, 161, 163, 167, 14, 97, + 167, 106, 107, 71, 72, 73, 74, 75, 76, 77, + 163, 116, 80, 30, 167, 32, 33, 34, 35, 87, + 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, + 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, + 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, + 118, 119, 120, 121, 122, 123, 124, 125, 126, 8, + 128, 129, 130, 131, 132, 133, 156, 163, 136, 137, + 138, 139, 140, 141, 142, 143, 144, 162, 9, 10, + 11, 162, 150, 151, 152, 162, 154, 2, 3, 4, + 5, 6, 7, 14, 9, 10, 11, 12, 13, 30, + 163, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, + 51, 52, 53, 54, 55, 160, 57, 162, 9, 10, + 11, 9, 10, 11, 8, 159, 14, 8, 69, 163, + 1, 101, 57, 9, 10, 11, 162, 8, 116, 30, + 1, 32, 33, 34, 35, 36, 71, 72, 73, 74, + 75, 76, 77, 123, 30, 80, 32, 33, 70, 80, + 138, 8, 87, 88, 89, 90, 91, 92, 93, 94, + 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, + 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, + 125, 126, 116, 128, 129, 130, 131, 132, 133, 37, + 38, 136, 137, 138, 139, 140, 141, 142, 143, 144, + 14, 116, 134, 135, 138, 150, 151, 152, 2, 3, + 4, 5, 6, 7, 155, 156, 157, 149, 12, 13, + 101, 15, 137, 164, 1, 106, 85, 108, 30, 161, + 8, 163, 113, 49, 50, 116, 117, 118, 119, 120, + 121, 122, 123, 51, 52, 53, 54, 55, 1, 57, + 106, 107, 1, 106, 31, 108, 8, 51, 52, 8, + 116, 69, 56, 8, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 70, 70, 71, 72, 73, + 74, 116, 8, 164, 78, 79, 80, 146, 82, 106, + 8, 108, 86, 87, 88, 89, 122, 91, 122, 93, + 1, 95, 158, 138, 98, 99, 1, 84, 8, 103, + 104, 105, 106, 107, 116, 109, 110, 119, 120, 121, + 122, 115, 116, 106, 80, 108, 161, 8, 122, 82, + 124, 125, 126, 8, 1, 161, 138, 161, 155, 134, + 135, 14, 136, 137, 8, 139, 140, 141, 142, 143, + 144, 145, 51, 52, 149, 70, 1, 151, 152, 161, + 116, 155, 156, 157, 158, 8, 161, 161, 163, 75, + 76, 165, 166, 167, 75, 76, 77, 1, 52, 75, + 76, 137, 159, 84, 137, 30, 163, 82, 14, 90, + 80, 92, 106, 94, 108, 96, 101, 102, 14, 155, + 156, 157, 9, 10, 11, 106, 80, 31, 161, 83, + 14, 160, 14, 162, 167, 82, 117, 118, 16, 134, + 135, 122, 1, 30, 106, 107, 127, 128, 129, 130, + 131, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 37, 38, 16, 163, 16, + 117, 118, 31, 37, 38, 122, 106, 107, 159, 116, + 84, 128, 163, 164, 111, 112, 156, 16, 163, 16, + 137, 116, 167, 163, 59, 60, 70, 16, 16, 16, + 16, 138, 74, 16, 16, 70, 70, 16, 80, 1, + 16, 158, 1, 138, 161, 87, 88, 89, 82, 91, + 167, 93, 86, 95, 161, 84, 98, 31, 31, 31, + 31, 103, 104, 105, 0, 1, 161, 109, 110, 31, + 31, 31, 31, 115, 116, 31, 31, 31, 31, 31, + 37, 30, 124, 31, 31, 159, 37, 38, 122, 163, + 134, 135, 70, 31, 31, 35, 31, 31, 31, 134, + 135, 31, 136, 137, 1, 139, 140, 141, 142, 143, + 144, 31, 154, 31, 149, 37, 37, 151, 152, 163, + 35, 35, 84, 74, 35, 84, 35, 161, 163, 80, + 159, 80, 166, 167, 163, 35, 87, 88, 89, 35, + 91, 57, 93, 37, 95, 38, 69, 98, 84, 77, + 82, 70, 103, 104, 105, 80, 134, 135, 109, 110, + 70, 85, 31, 80, 115, 116, 92, 116, 97, 82, + 106, 149, 108, 124, 113, 83, 146, 113, 90, 137, + 146, 117, 118, 89, 128, 163, 122, 84, 137, 138, + 94, 127, 128, 129, 130, 131, 31, 159, 96, 100, + 159, 163, 97, 149, 163, 74, 155, 156, 157, 97, + 100, 80, 161, 154, 150, 134, 135, 159, 87, 88, + 89, 164, 91, 159, 93, 114, 95, 163, 164, 98, + 149, 133, 159, 132, 103, 104, 105, -1, -1, 74, + 109, 110, 1, -1, 163, 80, 115, 116, 132, -1, + 153, 158, 87, 88, 89, 124, 91, 149, 93, 164, + 95, -1, 159, 98, 149, -1, 163, 149, 103, 104, + 105, 74, 149, 153, 109, 110, 155, 80, 159, 162, + 115, 116, 1, 159, 87, 88, 89, 159, 91, 124, + 93, 159, 95, 159, 159, 98, 70, 159, 159, 102, + 103, 104, 105, 74, 159, 159, 109, 110, 159, 80, + 81, 159, 115, 116, 159, 159, 87, 88, 89, 159, + 91, 124, 93, 159, 95, 84, 159, 98, 160, 160, + 160, 160, 103, 104, 105, 160, 160, 163, 109, 110, + 161, 100, 101, 102, 115, 116, 161, 106, 161, 161, + 70, 71, 161, 124, 161, 161, 161, 161, 117, 118, + 134, 135, 82, 122, 161, 84, 86, 161, 127, 128, + 129, 130, 131, 161, 161, 149, 161, 161, 161, 161, + 161, 100, 101, 102, 161, 161, 161, 106, 161, 163, + 162, 161, 161, 161, 161, 161, 161, 161, 117, 118, + 159, 161, 122, 122, 163, 164, 161, 161, 127, 128, + 129, 130, 131, 161, 161, 161, 161, 137, 70, 139, + 140, 141, 142, 143, 144, 161, 161, 161, 165, 162, + 82, 151, 152, 162, 86, 162, 74, 162, 162, 162, + 159, 161, 80, 164, 163, 164, 166, 167, 162, 87, + 88, 89, 162, 91, 162, 93, 162, 95, 162, 162, + 98, 162, 162, 162, 162, 103, 104, 105, 162, 162, + 122, 109, 110, 162, 162, 162, 162, 115, 116, 162, + 162, 162, 162, 162, 162, 137, 124, 139, 140, 141, + 142, 143, 144, 162, 162, 162, 162, 162, 162, 151, + 152, 162, 162, 162, 162, 162, 162, 162, 162, 161, + 163, 162, -1, 163, 166, 167, 163, 163, 163, 163, + 163, 163, 163, 163, 163, -1, 163, -1, 164, 164, + 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, + 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, + 164, 164, -1, -1, 166, -1, 167 + ); + + protected array $actionBase = array( + 0, -2, 152, 549, 764, 941, 981, 751, 555, 309, + 560, 864, 626, 738, 738, 741, 738, 473, 671, 783, + -60, 305, 305, 783, 305, 803, 803, 803, 658, 658, + 658, 658, 749, 749, 897, 897, 929, 865, 831, 1062, + 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, + 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, + 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, + 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, + 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, + 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, + 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, + 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, + 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, + 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, + 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, + 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, 1062, + 1062, 1062, 1062, 1062, 18, 36, 79, 648, 1036, 1044, + 1040, 1045, 1034, 1033, 1039, 1041, 1046, 1083, 1084, 782, + 1085, 1086, 1082, 1087, 1042, 876, 1035, 1043, 289, 289, + 289, 289, 289, 289, 289, 289, 289, 289, 289, 289, + 289, 289, 289, 289, 289, 289, 289, 289, 289, 289, + 289, 289, 289, 289, 289, 195, 342, 43, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 643, 643, + 643, 666, 666, 354, 173, 980, 203, 1048, 1048, 1048, + 1048, 1048, 1048, 1048, 1048, 1048, 665, 339, 164, 164, + 7, 7, 7, 7, 7, 50, 369, 583, -23, -23, + -23, -23, 448, 605, 497, 260, 397, 434, 54, 394, + 593, 593, 316, 316, 415, 415, 316, 316, 316, 442, + 442, 252, 252, 252, 252, 318, 455, 433, 391, 742, + 53, 53, 53, 53, 742, 742, 742, 742, 734, 1088, + 742, 742, 742, 722, 781, 781, 926, 551, 551, 781, + 536, -3, -3, 536, 63, -3, 67, 576, 335, 756, + 115, 9, 335, 535, 656, 501, 185, 821, 568, 821, + 1032, 424, 776, 776, 426, 753, 729, 867, 1063, 1049, + 799, 1080, 810, 1081, -66, -58, 728, 1031, 1031, 1031, + 1031, 1031, 1031, 1031, 1031, 1031, 1031, 1031, 1089, 402, + 1032, 130, 1089, 1089, 1089, 402, 402, 402, 402, 402, + 402, 402, 402, 402, 402, 603, 130, 544, 554, 130, + 804, 402, 18, 808, 18, 18, 18, 18, 18, 18, + 18, 18, 18, 18, 762, 157, 18, 36, 124, 124, + 196, 37, 124, 124, 124, 124, 18, 18, 18, 18, + 568, 784, 797, 600, 820, 143, 784, 784, 784, 122, + 135, 204, 139, 760, 785, 467, 775, 775, 787, 895, + 895, 775, 768, 775, 787, 913, 775, 775, 895, 895, + 793, 158, 550, 472, 524, 569, 895, 346, 775, 775, + 775, 775, 816, 575, 775, 271, 171, 775, 775, 816, + 801, 766, 58, 798, 895, 895, 895, 816, 505, 798, + 798, 798, 819, 824, 761, 765, 383, 349, 607, 138, + 807, 765, 765, 775, 532, 761, 765, 761, 765, 759, + 765, 765, 765, 761, 765, 768, 498, 765, 714, 586, + 75, 765, 6, 915, 916, 726, 917, 906, 918, 965, + 919, 923, 1053, 894, 931, 912, 924, 966, 903, 896, + 780, 701, 703, 815, 754, 893, 777, 777, 777, 888, + 777, 777, 777, 777, 777, 777, 777, 777, 701, 868, + 823, 794, 934, 711, 712, 1011, 730, 795, 963, 933, + 1013, 925, 758, 713, 977, 935, 757, 1047, 936, 940, + 986, 1014, 828, 1017, 979, 790, 1064, 1065, 869, 946, + 1054, 777, 915, 923, 727, 912, 924, 903, 896, 752, + 748, 746, 747, 745, 744, 739, 740, 763, 1018, 887, + 879, 870, 945, 891, 701, 871, 971, 874, 990, 992, + 1050, 805, 792, 875, 1066, 952, 953, 954, 1055, 1019, + 1056, 773, 973, 817, 994, 812, 1067, 996, 997, 999, + 1000, 1057, 1068, 1058, 885, 1059, 832, 788, 928, 802, + 1069, 299, 791, 800, 806, 964, 436, 932, 1060, 1070, + 1071, 1001, 1002, 1006, 1072, 1073, 927, 834, 975, 796, + 976, 967, 835, 838, 577, 779, 1020, 786, 789, 778, + 624, 634, 1074, 1075, 1076, 930, 767, 772, 839, 845, + 1021, 743, 1022, 1077, 646, 846, 717, 1078, 1012, 718, + 721, 652, 683, 681, 724, 774, 1061, 818, 811, 771, + 955, 721, 770, 849, 1079, 852, 855, 856, 1007, 860, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 456, 456, 456, 456, 456, 456, 305, 305, 305, 305, + 305, 456, 456, 456, 456, 456, 456, 456, 305, 305, + 0, 0, 305, 0, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 456, 456, 456, 456, 456, 456, 456, + 456, 456, 456, 289, 289, 289, 289, 289, 289, 289, + 289, 289, 289, 289, 289, 289, 289, 289, 289, 289, + 289, 289, 289, 289, 289, 289, 289, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 289, 289, 289, 289, 289, 289, + 289, 289, 289, 289, 289, 289, 289, 289, 289, 289, + 289, 289, 289, 289, 289, 289, 289, 289, 289, 289, + 494, 494, 289, 289, 494, 289, 494, 494, 494, 494, + 494, 494, 494, 494, 494, 0, 289, 289, 289, 289, + 289, 289, 289, 289, 494, 793, 494, 442, 442, 442, + 442, 494, 494, 494, -88, -88, 442, 494, 63, 494, + 494, 494, 494, 494, 494, 494, 494, 494, 0, 0, + 494, 494, 494, 494, 0, 0, 130, -3, 494, 768, + 768, 768, 768, 494, 494, 494, 494, -3, -3, 494, + 494, 494, 0, 0, 0, 0, 442, 442, 0, 130, + 0, 0, 130, 0, 0, 768, 768, 494, 63, 793, + 359, 494, 0, 0, 0, 0, 130, 768, 130, 402, + 775, -3, -3, 775, 402, 402, 124, 18, 359, 545, + 545, 545, 545, 0, 0, 568, 793, 793, 793, 793, + 793, 793, 793, 793, 793, 793, 793, 768, 0, 793, + 0, 768, 768, 768, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 768, + 0, 0, 895, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 913, 0, 0, 0, 0, 0, 0, + 768, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 777, 805, 0, 805, 0, 777, 777, 777, 0, 0, + 0, 0, 779, 743 + ); + + protected array $actionDefault = array( + 3,32767, 102,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767, 100,32767,32767,32767,32767, 597, 597, + 597, 597,32767,32767, 254, 102,32767,32767, 470, 387, + 387, 387,32767,32767, 541, 541, 541, 541, 541, 541, + 32767,32767,32767,32767,32767,32767, 470,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767, 100, + 32767,32767,32767, 36, 7, 8, 10, 11, 49, 17, + 324,32767,32767,32767,32767, 102,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767, 590,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767, 474, 453, + 454, 456, 457, 386, 542, 596, 327, 593, 385, 145, + 339, 329, 242, 330, 258, 475, 259, 476, 479, 480, + 215, 287, 382, 149, 150, 417, 471, 419, 469, 473, + 418, 392, 398, 399, 400, 401, 402, 403, 404, 405, + 406, 407, 408, 409, 410, 390, 391, 472, 450, 449, + 448,32767,32767, 415, 416,32767, 420,32767,32767,32767, + 32767,32767,32767,32767, 102,32767, 389, 423, 421, 422, + 439, 440, 437, 438, 441,32767,32767,32767, 442, 443, + 444, 445, 316,32767,32767, 366, 364, 424, 316, 111, + 32767,32767,32767,32767,32767,32767,32767,32767,32767, 430, + 431,32767,32767,32767,32767, 535, 447,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 102,32767, 100, 537, 412, 414, 504, 425, 426, 393, + 32767, 511,32767, 102,32767, 513,32767,32767,32767,32767, + 32767,32767,32767, 536,32767, 543, 543,32767, 497, 100, + 195,32767,32767, 512,32767, 195, 195,32767,32767,32767, + 32767,32767,32767,32767,32767, 604, 497, 110, 110, 110, + 110, 110, 110, 110, 110, 110, 110, 110,32767, 195, + 110,32767,32767,32767, 100, 195, 195, 195, 195, 195, + 195, 195, 195, 195, 195, 190,32767, 268, 270, 102, + 558, 195,32767, 516,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767, 509,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 497, 435, 138,32767, 138, 543, 427, 428, 429, 499, + 543, 543, 543, 312, 289,32767,32767,32767,32767, 514, + 514, 100, 100, 100, 100, 509,32767,32767,32767,32767, + 111, 99, 99, 99, 99, 99, 103, 101,32767,32767, + 32767,32767, 223, 99,32767, 101, 101,32767,32767, 223, + 225, 212, 101, 227,32767, 562, 563, 223, 101, 227, + 227, 227, 247, 247, 486, 318, 101, 99, 101, 101, + 197, 318, 318,32767, 101, 486, 318, 486, 318, 199, + 318, 318, 318, 486, 318,32767, 101, 318, 214, 99, + 99, 318,32767,32767,32767, 499,32767,32767,32767,32767, + 32767,32767,32767, 222,32767,32767,32767,32767,32767,32767, + 32767,32767, 530,32767, 547, 560, 433, 434, 436, 545, + 458, 459, 460, 461, 462, 463, 464, 466, 592,32767, + 503,32767,32767,32767, 338,32767, 602,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767, 603,32767, 543,32767,32767,32767, + 32767, 432, 9, 74, 492, 42, 43, 51, 57, 520, + 521, 522, 523, 517, 518, 524, 519,32767,32767, 525, + 568,32767,32767, 544, 595,32767,32767,32767,32767,32767, + 32767, 138,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767, 530,32767, 136,32767,32767,32767,32767, + 32767,32767,32767,32767, 526,32767,32767,32767, 543,32767, + 32767,32767,32767, 314, 311,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767, 543,32767,32767,32767,32767,32767, 291,32767, 308, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767, 286,32767,32767, 381, + 499, 294, 296, 297,32767,32767,32767,32767, 360,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 152, 152, 3, 3, 341, 152, 152, 152, 341, 341, + 152, 341, 341, 341, 152, 152, 152, 152, 152, 152, + 280, 185, 262, 265, 247, 247, 152, 352, 152 + ); + + protected array $goto = array( + 196, 196, 1034, 1065, 697, 431, 661, 621, 658, 319, + 706, 425, 314, 315, 335, 576, 430, 336, 432, 638, + 654, 655, 852, 672, 673, 674, 853, 167, 167, 167, + 167, 221, 197, 193, 193, 177, 179, 216, 193, 193, + 193, 193, 193, 194, 194, 194, 194, 194, 194, 188, + 189, 190, 191, 192, 218, 216, 219, 536, 537, 421, + 538, 540, 541, 542, 543, 544, 545, 546, 547, 1136, + 168, 169, 170, 195, 171, 172, 173, 166, 174, 175, + 176, 178, 215, 217, 220, 238, 243, 244, 246, 257, + 258, 259, 260, 261, 262, 263, 264, 268, 269, 270, + 271, 277, 289, 290, 317, 318, 426, 427, 428, 581, + 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, + 232, 233, 234, 235, 236, 180, 237, 181, 198, 199, + 200, 239, 188, 189, 190, 191, 192, 218, 1136, 201, + 182, 183, 184, 202, 198, 185, 240, 203, 201, 165, + 204, 205, 186, 206, 207, 208, 187, 209, 210, 211, + 212, 213, 214, 855, 1232, 975, 279, 279, 279, 279, + 623, 623, 419, 351, 1269, 600, 1269, 1269, 1269, 1269, + 1269, 1269, 1269, 1269, 1269, 1287, 1287, 599, 1100, 1287, + 709, 1287, 1287, 1287, 1287, 1287, 1287, 1287, 1287, 1287, + 508, 700, 827, 1098, 458, 559, 552, 860, 833, 909, + 904, 905, 918, 861, 906, 858, 907, 908, 859, 1233, + 1234, 912, 500, 886, 501, 252, 252, 843, 1107, 1108, + 507, 1087, 1082, 1083, 1084, 341, 552, 559, 568, 569, + 344, 579, 602, 616, 617, 1235, 1295, 1296, 833, 440, + 833, 22, 250, 250, 250, 250, 245, 253, 694, 573, + 1237, 829, 1237, 893, 851, 893, 893, 1034, 1034, 1237, + 694, 349, 342, 1034, 694, 1034, 1034, 1034, 1034, 1034, + 1034, 1034, 1034, 1034, 848, 1327, 1034, 1034, 1034, 1034, + 1319, 1319, 1319, 1319, 1237, 343, 342, 1040, 1039, 1237, + 1237, 1237, 1237, 868, 996, 1237, 1237, 1237, 913, 355, + 914, 354, 354, 354, 354, 466, 466, 479, 880, 355, + 355, 867, 394, 926, 466, 481, 571, 927, 967, 410, + 705, 942, 355, 355, 942, 848, 355, 660, 1354, 609, + 624, 627, 628, 629, 630, 651, 652, 653, 708, 554, + 1133, 1285, 1285, 355, 355, 1285, 1058, 1285, 1285, 1285, + 1285, 1285, 1285, 1285, 1285, 1285, 539, 539, 1185, 424, + 539, 611, 539, 539, 539, 539, 539, 539, 539, 539, + 539, 566, 682, 1337, 1337, 733, 637, 639, 1043, 1044, + 659, 476, 1312, 1313, 683, 687, 1010, 695, 704, 1006, + 1337, 1298, 438, 408, 409, 631, 633, 635, 670, 5, + 671, 6, 412, 413, 414, 337, 684, 1340, 1340, 415, + 325, 309, 686, 347, 352, 353, 553, 563, 450, 450, + 450, 553, 1309, 563, 1309, 666, 397, 462, 845, 1314, + 1315, 1309, 548, 548, 548, 548, 873, 604, 469, 580, + 470, 471, 403, 554, 878, 848, 958, 1345, 1346, 577, + 614, 870, 550, 615, 550, 255, 255, 1321, 1321, 1321, + 1321, 550, 999, 1018, 477, 971, 1228, 732, 736, 881, + 869, 1070, 1074, 876, 882, 551, 1008, 1003, 1071, 1075, + 978, 980, 0, 1305, 1118, 0, 456, 0, 0, 0, + 0, 969, 969, 969, 969, 0, 0, 456, 963, 970, + 0, 0, 0, 0, 968, 0, 1230, 0, 0, 0, + 450, 450, 450, 450, 450, 450, 450, 450, 450, 450, + 450, 931, 1123, 450, 0, 1073, 1116, 885, 619, 1307, + 1307, 1073, 1216, 944, 1015, 433, 1217, 1220, 945, 1221, + 0, 433, 872, 0, 664, 994, 0, 1041, 1041, 0, + 866, 0, 0, 0, 665, 1052, 1048, 1049, 0, 0, + 0, 0, 1227, 324, 275, 324, 1037, 1037, 681, 952, + 0, 0, 1029, 1045, 1046, 396, 399, 560, 601, 605, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1013, 1013 + ); + + protected array $gotoCheck = array( + 42, 42, 73, 127, 73, 66, 66, 56, 56, 66, + 9, 66, 66, 66, 66, 66, 66, 66, 66, 66, + 86, 86, 26, 86, 86, 86, 27, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 15, 20, 49, 23, 23, 23, 23, + 108, 108, 43, 97, 108, 130, 108, 108, 108, 108, + 108, 108, 108, 108, 108, 170, 170, 8, 8, 170, + 8, 170, 170, 170, 170, 170, 170, 170, 170, 170, + 8, 8, 6, 8, 83, 76, 76, 15, 12, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 20, + 20, 15, 155, 45, 155, 5, 5, 20, 144, 144, + 155, 15, 15, 15, 15, 76, 76, 76, 76, 76, + 76, 76, 76, 76, 76, 20, 20, 20, 12, 83, + 12, 76, 5, 5, 5, 5, 5, 5, 7, 172, + 73, 7, 73, 25, 25, 25, 25, 73, 73, 73, + 7, 179, 168, 73, 7, 73, 73, 73, 73, 73, + 73, 73, 73, 73, 22, 181, 73, 73, 73, 73, + 9, 9, 9, 9, 73, 168, 168, 118, 118, 73, + 73, 73, 73, 35, 103, 73, 73, 73, 65, 14, + 65, 24, 24, 24, 24, 149, 149, 84, 35, 14, + 14, 35, 62, 73, 149, 84, 104, 73, 93, 93, + 93, 9, 14, 14, 9, 22, 14, 64, 14, 81, + 81, 81, 81, 81, 81, 81, 81, 81, 81, 14, + 150, 171, 171, 14, 14, 171, 114, 171, 171, 171, + 171, 171, 171, 171, 171, 171, 173, 173, 151, 13, + 173, 13, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 48, 116, 182, 182, 48, 48, 48, 119, 119, + 48, 176, 176, 176, 48, 48, 48, 48, 48, 48, + 182, 14, 113, 82, 82, 85, 85, 85, 82, 46, + 82, 46, 82, 82, 82, 29, 82, 182, 182, 82, + 169, 169, 14, 82, 97, 97, 9, 9, 23, 23, + 23, 9, 130, 9, 130, 120, 9, 9, 18, 178, + 178, 130, 107, 107, 107, 107, 39, 107, 9, 9, + 9, 9, 28, 14, 9, 22, 92, 9, 9, 2, + 2, 37, 19, 80, 19, 5, 5, 130, 130, 130, + 130, 19, 50, 110, 157, 50, 160, 50, 99, 16, + 16, 16, 16, 9, 41, 50, 50, 50, 129, 132, + 16, 96, -1, 130, 147, -1, 19, -1, -1, -1, + -1, 19, 19, 19, 19, -1, -1, 19, 19, 19, + -1, -1, -1, -1, 16, -1, 14, -1, -1, -1, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 17, 17, 23, -1, 130, 16, 16, 17, 130, + 130, 130, 79, 79, 17, 117, 79, 79, 79, 79, + -1, 117, 17, -1, 17, 17, -1, 117, 117, -1, + 17, -1, -1, -1, 117, 117, 117, 117, -1, -1, + -1, -1, 17, 24, 24, 24, 89, 89, 89, 89, + -1, -1, 89, 89, 89, 59, 59, 59, 59, 59, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 107, 107 + ); + + protected array $gotoBase = array( + 0, 0, -253, 0, 0, 224, 182, 251, 179, -10, + 0, 0, -89, 32, 11, -185, 27, 66, 128, 197, + -229, 0, 5, 163, 308, 260, 18, 22, 115, 118, + 0, 0, 0, 0, 0, -68, 0, 122, 0, 123, + 0, 43, -1, 153, 0, 200, -327, 0, -330, 147, + 460, 0, 0, 0, 0, 0, -33, 0, 0, 540, + 0, 0, 280, 0, 95, 294, -236, 0, 0, 0, + 0, 0, 0, -5, 0, 0, -140, 0, 0, 134, + 119, -19, -88, -75, -152, -74, -698, 0, 0, 296, + 0, 0, 127, 23, 0, 0, 48, -310, 0, 71, + 0, 0, 0, 269, 283, 0, 0, 414, -71, 0, + 103, 0, 0, 124, 83, 0, 100, 273, 17, 104, + 144, 0, 0, 0, 0, 0, 0, 1, 0, 114, + 167, 0, 47, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, -47, 0, 0, 50, 0, 281, + 105, 94, 0, 0, 0, -273, 0, 34, 0, 0, + 107, 0, 0, 0, 0, 0, 0, 0, -26, 99, + -56, 110, 230, 125, 0, 0, 90, 0, 67, 241, + 0, 254, 75, 0, 0 + ); + + protected array $gotoDefault = array( + -32768, 512, 740, 4, 741, 935, 816, 825, 597, 530, + 707, 348, 625, 422, 1303, 911, 1122, 578, 844, 1246, + 1254, 457, 847, 330, 730, 923, 894, 895, 400, 386, + 392, 398, 649, 626, 494, 879, 453, 871, 486, 874, + 452, 883, 164, 418, 510, 887, 3, 890, 557, 921, + 973, 387, 898, 388, 677, 900, 562, 902, 903, 395, + 401, 402, 1127, 570, 622, 915, 256, 564, 916, 385, + 917, 925, 390, 393, 688, 465, 505, 499, 411, 1102, + 565, 608, 646, 447, 473, 620, 632, 618, 480, 434, + 416, 329, 957, 965, 487, 463, 979, 350, 987, 738, + 1135, 640, 489, 995, 641, 1002, 1005, 531, 532, 478, + 1017, 272, 1020, 490, 19, 667, 1031, 1032, 668, 642, + 1054, 643, 669, 644, 1056, 472, 598, 1064, 454, 1072, + 1291, 455, 1076, 266, 1079, 278, 417, 435, 1085, 1086, + 9, 1092, 698, 699, 11, 276, 509, 1117, 689, 451, + 1134, 439, 1204, 1206, 558, 491, 1224, 1223, 680, 506, + 1229, 448, 1294, 449, 533, 474, 316, 534, 1338, 308, + 333, 313, 549, 295, 334, 535, 475, 1300, 1308, 331, + 31, 1328, 1339, 575, 613 + ); + + protected array $ruleToNonTerminal = array( + 0, 1, 3, 3, 2, 5, 5, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, + 7, 7, 7, 7, 7, 8, 8, 9, 10, 11, + 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, + 16, 17, 17, 18, 18, 21, 21, 22, 23, 23, + 24, 24, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 29, 29, 30, 30, 32, 34, 34, + 28, 36, 36, 33, 38, 38, 35, 35, 37, 37, + 39, 39, 31, 40, 40, 41, 43, 44, 44, 45, + 45, 46, 46, 48, 47, 47, 47, 47, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 25, 25, 50, 69, 69, 72, 72, 71, + 70, 70, 63, 75, 75, 76, 76, 77, 77, 78, + 78, 79, 79, 80, 80, 26, 26, 27, 27, 27, + 27, 27, 88, 88, 90, 90, 83, 83, 91, 91, + 92, 92, 92, 84, 84, 87, 87, 85, 85, 93, + 94, 94, 57, 57, 65, 65, 68, 68, 68, 67, + 95, 95, 96, 58, 58, 58, 58, 97, 97, 98, + 98, 99, 99, 100, 101, 101, 102, 102, 103, 103, + 55, 55, 51, 51, 105, 53, 53, 106, 52, 52, + 54, 54, 64, 64, 64, 64, 81, 81, 109, 109, + 111, 111, 112, 112, 112, 112, 110, 110, 110, 114, + 114, 114, 114, 89, 89, 117, 117, 117, 118, 118, + 115, 115, 119, 119, 121, 121, 122, 122, 116, 123, + 123, 120, 124, 124, 124, 124, 113, 113, 82, 82, + 82, 20, 20, 20, 126, 125, 125, 127, 127, 127, + 127, 60, 128, 128, 129, 61, 131, 131, 132, 132, + 133, 133, 86, 134, 134, 134, 134, 134, 134, 134, + 139, 139, 140, 140, 141, 141, 141, 141, 141, 142, + 143, 143, 138, 138, 135, 135, 137, 137, 145, 145, + 144, 144, 144, 144, 144, 144, 144, 136, 146, 146, + 148, 147, 147, 62, 104, 149, 149, 56, 56, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 156, 150, 150, 155, 155, 158, 159, + 159, 160, 161, 162, 162, 162, 162, 19, 19, 73, + 73, 73, 73, 151, 151, 151, 151, 164, 164, 152, + 152, 154, 154, 154, 157, 157, 170, 170, 170, 170, + 170, 170, 170, 170, 170, 171, 171, 171, 108, 173, + 173, 173, 173, 153, 153, 153, 153, 153, 153, 153, + 153, 59, 59, 167, 167, 167, 167, 174, 174, 163, + 163, 163, 175, 175, 175, 175, 175, 175, 74, 74, + 66, 66, 66, 66, 130, 130, 130, 130, 178, 177, + 166, 166, 166, 166, 166, 166, 166, 165, 165, 165, + 176, 176, 176, 176, 107, 172, 180, 180, 179, 179, + 181, 181, 181, 181, 181, 181, 181, 181, 169, 169, + 169, 169, 168, 183, 182, 182, 182, 182, 182, 182, + 182, 182, 184, 184, 184, 184 + ); + + protected array $ruleToLength = array( + 1, 1, 2, 0, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 1, 0, 1, 1, 2, 1, 3, 4, 1, 2, + 0, 1, 1, 1, 1, 4, 3, 5, 4, 3, + 4, 2, 3, 1, 1, 7, 6, 2, 3, 1, + 2, 3, 1, 2, 3, 1, 1, 3, 1, 3, + 1, 2, 2, 3, 1, 3, 2, 3, 1, 3, + 3, 2, 0, 1, 1, 1, 1, 1, 3, 7, + 10, 5, 7, 9, 5, 3, 3, 3, 3, 3, + 3, 1, 2, 5, 7, 9, 6, 5, 6, 3, + 2, 1, 1, 1, 1, 0, 2, 1, 3, 8, + 0, 4, 2, 1, 3, 0, 1, 0, 1, 0, + 1, 3, 1, 1, 1, 8, 9, 7, 8, 7, + 6, 8, 0, 2, 0, 2, 1, 2, 1, 2, + 1, 1, 1, 0, 2, 0, 2, 0, 2, 2, + 1, 3, 1, 4, 1, 4, 1, 1, 4, 2, + 1, 3, 3, 3, 4, 4, 5, 0, 2, 4, + 3, 1, 1, 7, 0, 2, 1, 3, 3, 4, + 1, 4, 0, 2, 5, 0, 2, 6, 0, 2, + 0, 3, 1, 2, 1, 1, 2, 0, 1, 3, + 0, 2, 1, 1, 1, 1, 6, 8, 6, 1, + 2, 1, 1, 1, 1, 1, 1, 1, 1, 3, + 3, 3, 1, 3, 3, 3, 3, 3, 1, 3, + 3, 1, 1, 2, 1, 1, 0, 1, 0, 2, + 2, 2, 4, 3, 1, 1, 3, 1, 2, 2, + 3, 2, 3, 1, 1, 2, 3, 1, 1, 3, + 2, 0, 1, 5, 5, 6, 10, 3, 5, 1, + 1, 3, 0, 2, 4, 5, 4, 4, 4, 3, + 1, 1, 1, 1, 1, 1, 0, 1, 1, 2, + 1, 1, 1, 1, 1, 1, 1, 2, 1, 3, + 1, 1, 3, 2, 2, 3, 1, 0, 1, 1, + 3, 3, 3, 4, 4, 1, 1, 2, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 2, 2, 2, 2, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 2, 2, 2, 2, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 5, 4, + 3, 4, 4, 2, 2, 4, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 1, 3, 2, + 1, 2, 4, 2, 2, 8, 9, 8, 9, 9, + 10, 9, 10, 8, 3, 2, 0, 4, 2, 1, + 3, 2, 1, 2, 2, 2, 4, 1, 1, 1, + 1, 1, 1, 1, 1, 3, 1, 1, 1, 0, + 3, 0, 1, 1, 0, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 3, 5, 3, 3, 4, + 1, 1, 3, 1, 1, 1, 1, 1, 3, 2, + 3, 0, 1, 1, 3, 1, 1, 1, 1, 1, + 3, 1, 1, 4, 4, 1, 4, 4, 0, 1, + 1, 1, 3, 3, 1, 4, 2, 2, 1, 3, + 1, 4, 4, 3, 3, 3, 3, 1, 3, 1, + 1, 3, 1, 1, 4, 1, 1, 1, 3, 1, + 1, 2, 1, 3, 4, 3, 2, 0, 2, 2, + 1, 2, 1, 1, 1, 4, 3, 3, 3, 3, + 6, 3, 1, 1, 2, 1 + ); + + protected function initReduceCallbacks(): void { + $this->reduceCallbacks = [ + 0 => null, + 1 => static function ($self, $stackPos) { + $self->semValue = $self->handleNamespaces($self->semStack[$stackPos-(1-1)]); + }, + 2 => static function ($self, $stackPos) { + if ($self->semStack[$stackPos-(2-2)] !== null) { $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; } $self->semValue = $self->semStack[$stackPos-(2-1)];; + }, + 3 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 4 => static function ($self, $stackPos) { + $nop = $self->maybeCreateZeroLengthNop($self->tokenPos);; + if ($nop !== null) { $self->semStack[$stackPos-(1-1)][] = $nop; } $self->semValue = $self->semStack[$stackPos-(1-1)]; + }, + 5 => null, + 6 => null, + 7 => null, + 8 => null, + 9 => null, + 10 => null, + 11 => null, + 12 => null, + 13 => null, + 14 => null, + 15 => null, + 16 => null, + 17 => null, + 18 => null, + 19 => null, + 20 => null, + 21 => null, + 22 => null, + 23 => null, + 24 => null, + 25 => null, + 26 => null, + 27 => null, + 28 => null, + 29 => null, + 30 => null, + 31 => null, + 32 => null, + 33 => null, + 34 => null, + 35 => null, + 36 => null, + 37 => null, + 38 => null, + 39 => null, + 40 => null, + 41 => null, + 42 => null, + 43 => null, + 44 => null, + 45 => null, + 46 => null, + 47 => null, + 48 => null, + 49 => null, + 50 => null, + 51 => null, + 52 => null, + 53 => null, + 54 => null, + 55 => null, + 56 => null, + 57 => null, + 58 => null, + 59 => null, + 60 => null, + 61 => null, + 62 => null, + 63 => null, + 64 => null, + 65 => null, + 66 => null, + 67 => null, + 68 => null, + 69 => null, + 70 => null, + 71 => null, + 72 => null, + 73 => null, + 74 => null, + 75 => null, + 76 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(1-1)]; if ($self->semValue === "emitError(new Error('Cannot use "getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]))); + }, + 77 => null, + 78 => null, + 79 => null, + 80 => null, + 81 => null, + 82 => null, + 83 => null, + 84 => null, + 85 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 86 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 87 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 88 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 89 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 90 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 91 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 92 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 93 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 94 => null, + 95 => static function ($self, $stackPos) { + $self->semValue = new Name(substr($self->semStack[$stackPos-(1-1)], 1), $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 96 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable(substr($self->semStack[$stackPos-(1-1)], 1), $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 97 => static function ($self, $stackPos) { + /* nothing */ + }, + 98 => static function ($self, $stackPos) { + /* nothing */ + }, + 99 => static function ($self, $stackPos) { + /* nothing */ + }, + 100 => static function ($self, $stackPos) { + $self->emitError(new Error('A trailing comma is not allowed here', $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]))); + }, + 101 => null, + 102 => null, + 103 => static function ($self, $stackPos) { + $self->semValue = new Node\Attribute($self->semStack[$stackPos-(1-1)], [], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 104 => static function ($self, $stackPos) { + $self->semValue = new Node\Attribute($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 105 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 106 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 107 => static function ($self, $stackPos) { + $self->semValue = new Node\AttributeGroup($self->semStack[$stackPos-(4-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 108 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 109 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 110 => static function ($self, $stackPos) { + $self->semValue = []; + }, + 111 => null, + 112 => null, + 113 => null, + 114 => null, + 115 => static function ($self, $stackPos) { + $self->semValue = new Stmt\HaltCompiler($self->handleHaltCompiler(), $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 116 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Namespace_($self->semStack[$stackPos-(3-2)], null, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + $self->semValue->setAttribute('kind', Stmt\Namespace_::KIND_SEMICOLON); + $self->checkNamespace($self->semValue); + }, + 117 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Namespace_($self->semStack[$stackPos-(5-2)], $self->semStack[$stackPos-(5-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + $self->semValue->setAttribute('kind', Stmt\Namespace_::KIND_BRACED); + $self->checkNamespace($self->semValue); + }, + 118 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Namespace_(null, $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + $self->semValue->setAttribute('kind', Stmt\Namespace_::KIND_BRACED); + $self->checkNamespace($self->semValue); + }, + 119 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Use_($self->semStack[$stackPos-(3-2)], Stmt\Use_::TYPE_NORMAL, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 120 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Use_($self->semStack[$stackPos-(4-3)], $self->semStack[$stackPos-(4-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 121 => null, + 122 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Const_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 123 => static function ($self, $stackPos) { + $self->semValue = Stmt\Use_::TYPE_FUNCTION; + }, + 124 => static function ($self, $stackPos) { + $self->semValue = Stmt\Use_::TYPE_CONSTANT; + }, + 125 => static function ($self, $stackPos) { + $self->semValue = new Stmt\GroupUse($self->semStack[$stackPos-(7-3)], $self->semStack[$stackPos-(7-6)], $self->semStack[$stackPos-(7-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + }, + 126 => static function ($self, $stackPos) { + $self->semValue = new Stmt\GroupUse($self->semStack[$stackPos-(6-2)], $self->semStack[$stackPos-(6-5)], Stmt\Use_::TYPE_UNKNOWN, $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])); + }, + 127 => null, + 128 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 129 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 130 => null, + 131 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 132 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 133 => null, + 134 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 135 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 136 => static function ($self, $stackPos) { + $self->semValue = new Node\UseItem($self->semStack[$stackPos-(1-1)], null, Stmt\Use_::TYPE_UNKNOWN, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); $self->checkUseUse($self->semValue, $stackPos-(1-1)); + }, + 137 => static function ($self, $stackPos) { + $self->semValue = new Node\UseItem($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], Stmt\Use_::TYPE_UNKNOWN, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); $self->checkUseUse($self->semValue, $stackPos-(3-3)); + }, + 138 => static function ($self, $stackPos) { + $self->semValue = new Node\UseItem($self->semStack[$stackPos-(1-1)], null, Stmt\Use_::TYPE_UNKNOWN, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); $self->checkUseUse($self->semValue, $stackPos-(1-1)); + }, + 139 => static function ($self, $stackPos) { + $self->semValue = new Node\UseItem($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], Stmt\Use_::TYPE_UNKNOWN, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); $self->checkUseUse($self->semValue, $stackPos-(3-3)); + }, + 140 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(1-1)]; $self->semValue->type = Stmt\Use_::TYPE_NORMAL; + }, + 141 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; $self->semValue->type = $self->semStack[$stackPos-(2-1)]; + }, + 142 => null, + 143 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 144 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 145 => static function ($self, $stackPos) { + $self->semValue = new Node\Const_($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 146 => null, + 147 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 148 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 149 => static function ($self, $stackPos) { + $self->semValue = new Node\Const_(new Node\Identifier($self->semStack[$stackPos-(3-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos-(3-1)])), $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 150 => static function ($self, $stackPos) { + $self->semValue = new Node\Const_(new Node\Identifier($self->semStack[$stackPos-(3-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos-(3-1)])), $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 151 => static function ($self, $stackPos) { + if ($self->semStack[$stackPos-(2-2)] !== null) { $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; } $self->semValue = $self->semStack[$stackPos-(2-1)];; + }, + 152 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 153 => static function ($self, $stackPos) { + $nop = $self->maybeCreateZeroLengthNop($self->tokenPos);; + if ($nop !== null) { $self->semStack[$stackPos-(1-1)][] = $nop; } $self->semValue = $self->semStack[$stackPos-(1-1)]; + }, + 154 => null, + 155 => null, + 156 => null, + 157 => static function ($self, $stackPos) { + throw new Error('__HALT_COMPILER() can only be used from the outermost scope', $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 158 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Block($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 159 => static function ($self, $stackPos) { + $self->semValue = new Stmt\If_($self->semStack[$stackPos-(7-3)], ['stmts' => $self->semStack[$stackPos-(7-5)], 'elseifs' => $self->semStack[$stackPos-(7-6)], 'else' => $self->semStack[$stackPos-(7-7)]], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + }, + 160 => static function ($self, $stackPos) { + $self->semValue = new Stmt\If_($self->semStack[$stackPos-(10-3)], ['stmts' => $self->semStack[$stackPos-(10-6)], 'elseifs' => $self->semStack[$stackPos-(10-7)], 'else' => $self->semStack[$stackPos-(10-8)]], $self->getAttributes($self->tokenStartStack[$stackPos-(10-1)], $self->tokenEndStack[$stackPos])); + }, + 161 => static function ($self, $stackPos) { + $self->semValue = new Stmt\While_($self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 162 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Do_($self->semStack[$stackPos-(7-5)], $self->semStack[$stackPos-(7-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + }, + 163 => static function ($self, $stackPos) { + $self->semValue = new Stmt\For_(['init' => $self->semStack[$stackPos-(9-3)], 'cond' => $self->semStack[$stackPos-(9-5)], 'loop' => $self->semStack[$stackPos-(9-7)], 'stmts' => $self->semStack[$stackPos-(9-9)]], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 164 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Switch_($self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 165 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Break_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 166 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Continue_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 167 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Return_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 168 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Global_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 169 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Static_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 170 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Echo_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 171 => static function ($self, $stackPos) { + + $self->semValue = new Stmt\InlineHTML($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + $self->semValue->setAttribute('hasLeadingNewline', $self->inlineHtmlHasLeadingNewline($stackPos-(1-1))); + + }, + 172 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Expression($self->semStack[$stackPos-(2-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 173 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Unset_($self->semStack[$stackPos-(5-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 174 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Foreach_($self->semStack[$stackPos-(7-3)], $self->semStack[$stackPos-(7-5)][0], ['keyVar' => null, 'byRef' => $self->semStack[$stackPos-(7-5)][1], 'stmts' => $self->semStack[$stackPos-(7-7)]], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + }, + 175 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Foreach_($self->semStack[$stackPos-(9-3)], $self->semStack[$stackPos-(9-7)][0], ['keyVar' => $self->semStack[$stackPos-(9-5)], 'byRef' => $self->semStack[$stackPos-(9-7)][1], 'stmts' => $self->semStack[$stackPos-(9-9)]], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 176 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Foreach_($self->semStack[$stackPos-(6-3)], new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(6-4)], $self->tokenEndStack[$stackPos-(6-4)])), ['stmts' => $self->semStack[$stackPos-(6-6)]], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])); + }, + 177 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Declare_($self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 178 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TryCatch($self->semStack[$stackPos-(6-3)], $self->semStack[$stackPos-(6-5)], $self->semStack[$stackPos-(6-6)], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])); $self->checkTryCatch($self->semValue); + }, + 179 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Goto_($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 180 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Label($self->semStack[$stackPos-(2-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 181 => static function ($self, $stackPos) { + $self->semValue = null; /* means: no statement */ + }, + 182 => null, + 183 => static function ($self, $stackPos) { + $self->semValue = $self->maybeCreateNop($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]); + }, + 184 => static function ($self, $stackPos) { + if ($self->semStack[$stackPos-(1-1)] instanceof Stmt\Block) { $self->semValue = $self->semStack[$stackPos-(1-1)]->stmts; } else if ($self->semStack[$stackPos-(1-1)] === null) { $self->semValue = []; } else { $self->semValue = [$self->semStack[$stackPos-(1-1)]]; }; + }, + 185 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 186 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 187 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 188 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 189 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Catch_($self->semStack[$stackPos-(8-3)], $self->semStack[$stackPos-(8-4)], $self->semStack[$stackPos-(8-7)], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + }, + 190 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 191 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Finally_($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 192 => null, + 193 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 194 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 195 => static function ($self, $stackPos) { + $self->semValue = false; + }, + 196 => static function ($self, $stackPos) { + $self->semValue = true; + }, + 197 => static function ($self, $stackPos) { + $self->semValue = false; + }, + 198 => static function ($self, $stackPos) { + $self->semValue = true; + }, + 199 => static function ($self, $stackPos) { + $self->semValue = false; + }, + 200 => static function ($self, $stackPos) { + $self->semValue = true; + }, + 201 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 202 => static function ($self, $stackPos) { + $self->semValue = []; + }, + 203 => null, + 204 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 205 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Function_($self->semStack[$stackPos-(8-3)], ['byRef' => $self->semStack[$stackPos-(8-2)], 'params' => $self->semStack[$stackPos-(8-5)], 'returnType' => $self->semStack[$stackPos-(8-7)], 'stmts' => $self->semStack[$stackPos-(8-8)], 'attrGroups' => []], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + }, + 206 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Function_($self->semStack[$stackPos-(9-4)], ['byRef' => $self->semStack[$stackPos-(9-3)], 'params' => $self->semStack[$stackPos-(9-6)], 'returnType' => $self->semStack[$stackPos-(9-8)], 'stmts' => $self->semStack[$stackPos-(9-9)], 'attrGroups' => $self->semStack[$stackPos-(9-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 207 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Class_($self->semStack[$stackPos-(7-2)], ['type' => $self->semStack[$stackPos-(7-1)], 'extends' => $self->semStack[$stackPos-(7-3)], 'implements' => $self->semStack[$stackPos-(7-4)], 'stmts' => $self->semStack[$stackPos-(7-6)], 'attrGroups' => []], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + $self->checkClass($self->semValue, $stackPos-(7-2)); + }, + 208 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Class_($self->semStack[$stackPos-(8-3)], ['type' => $self->semStack[$stackPos-(8-2)], 'extends' => $self->semStack[$stackPos-(8-4)], 'implements' => $self->semStack[$stackPos-(8-5)], 'stmts' => $self->semStack[$stackPos-(8-7)], 'attrGroups' => $self->semStack[$stackPos-(8-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + $self->checkClass($self->semValue, $stackPos-(8-3)); + }, + 209 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Interface_($self->semStack[$stackPos-(7-3)], ['extends' => $self->semStack[$stackPos-(7-4)], 'stmts' => $self->semStack[$stackPos-(7-6)], 'attrGroups' => $self->semStack[$stackPos-(7-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + $self->checkInterface($self->semValue, $stackPos-(7-3)); + }, + 210 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Trait_($self->semStack[$stackPos-(6-3)], ['stmts' => $self->semStack[$stackPos-(6-5)], 'attrGroups' => $self->semStack[$stackPos-(6-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])); + }, + 211 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Enum_($self->semStack[$stackPos-(8-3)], ['scalarType' => $self->semStack[$stackPos-(8-4)], 'implements' => $self->semStack[$stackPos-(8-5)], 'stmts' => $self->semStack[$stackPos-(8-7)], 'attrGroups' => $self->semStack[$stackPos-(8-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + $self->checkEnum($self->semValue, $stackPos-(8-3)); + }, + 212 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 213 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; + }, + 214 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 215 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; + }, + 216 => static function ($self, $stackPos) { + $self->semValue = 0; + }, + 217 => null, + 218 => null, + 219 => static function ($self, $stackPos) { + $self->checkClassModifier($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $stackPos-(2-2)); $self->semValue = $self->semStack[$stackPos-(2-1)] | $self->semStack[$stackPos-(2-2)]; + }, + 220 => static function ($self, $stackPos) { + $self->semValue = Modifiers::ABSTRACT; + }, + 221 => static function ($self, $stackPos) { + $self->semValue = Modifiers::FINAL; + }, + 222 => static function ($self, $stackPos) { + $self->semValue = Modifiers::READONLY; + }, + 223 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 224 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; + }, + 225 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 226 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; + }, + 227 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 228 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; + }, + 229 => null, + 230 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 231 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 232 => null, + 233 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 234 => null, + 235 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 236 => static function ($self, $stackPos) { + if ($self->semStack[$stackPos-(1-1)] instanceof Stmt\Block) { $self->semValue = $self->semStack[$stackPos-(1-1)]->stmts; } else if ($self->semStack[$stackPos-(1-1)] === null) { $self->semValue = []; } else { $self->semValue = [$self->semStack[$stackPos-(1-1)]]; }; + }, + 237 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 238 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 239 => null, + 240 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 241 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 242 => static function ($self, $stackPos) { + $self->semValue = new Node\DeclareItem($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 243 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 244 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-3)]; + }, + 245 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 246 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(5-3)]; + }, + 247 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 248 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 249 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Case_($self->semStack[$stackPos-(4-2)], $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 250 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Case_(null, $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 251 => null, + 252 => null, + 253 => static function ($self, $stackPos) { + $self->semValue = new Expr\Match_($self->semStack[$stackPos-(7-3)], $self->semStack[$stackPos-(7-6)], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos])); + }, + 254 => static function ($self, $stackPos) { + $self->semValue = []; + }, + 255 => null, + 256 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 257 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 258 => static function ($self, $stackPos) { + $self->semValue = new Node\MatchArm($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 259 => static function ($self, $stackPos) { + $self->semValue = new Node\MatchArm(null, $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 260 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(1-1)]; + }, + 261 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 262 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 263 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 264 => static function ($self, $stackPos) { + $self->semValue = new Stmt\ElseIf_($self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 265 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 266 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 267 => static function ($self, $stackPos) { + $self->semValue = new Stmt\ElseIf_($self->semStack[$stackPos-(6-3)], $self->semStack[$stackPos-(6-6)], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])); $self->fixupAlternativeElse($self->semValue); + }, + 268 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 269 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Else_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 270 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 271 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Else_($self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); $self->fixupAlternativeElse($self->semValue); + }, + 272 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)], false); + }, + 273 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(2-2)], true); + }, + 274 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)], false); + }, + 275 => static function ($self, $stackPos) { + $self->semValue = array($self->fixupArrayDestructuring($self->semStack[$stackPos-(1-1)]), false); + }, + 276 => null, + 277 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 278 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 279 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 280 => static function ($self, $stackPos) { + $self->semValue = 0; + }, + 281 => static function ($self, $stackPos) { + $self->checkModifier($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $stackPos-(2-2)); $self->semValue = $self->semStack[$stackPos-(2-1)] | $self->semStack[$stackPos-(2-2)]; + }, + 282 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PUBLIC; + }, + 283 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PROTECTED; + }, + 284 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PRIVATE; + }, + 285 => static function ($self, $stackPos) { + $self->semValue = Modifiers::READONLY; + }, + 286 => static function ($self, $stackPos) { + $self->semValue = new Node\Param($self->semStack[$stackPos-(6-6)], null, $self->semStack[$stackPos-(6-3)], $self->semStack[$stackPos-(6-4)], $self->semStack[$stackPos-(6-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(6-2)], $self->semStack[$stackPos-(6-1)]); + $self->checkParam($self->semValue); + }, + 287 => static function ($self, $stackPos) { + $self->semValue = new Node\Param($self->semStack[$stackPos-(8-6)], $self->semStack[$stackPos-(8-8)], $self->semStack[$stackPos-(8-3)], $self->semStack[$stackPos-(8-4)], $self->semStack[$stackPos-(8-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(8-2)], $self->semStack[$stackPos-(8-1)]); + $self->checkParam($self->semValue); + }, + 288 => static function ($self, $stackPos) { + $self->semValue = new Node\Param(new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])), null, $self->semStack[$stackPos-(6-3)], $self->semStack[$stackPos-(6-4)], $self->semStack[$stackPos-(6-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(6-2)], $self->semStack[$stackPos-(6-1)]); + }, + 289 => null, + 290 => static function ($self, $stackPos) { + $self->semValue = new Node\NullableType($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 291 => static function ($self, $stackPos) { + $self->semValue = new Node\UnionType($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 292 => null, + 293 => null, + 294 => static function ($self, $stackPos) { + $self->semValue = new Node\Name('static', $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 295 => static function ($self, $stackPos) { + $self->semValue = $self->handleBuiltinTypes($self->semStack[$stackPos-(1-1)]); + }, + 296 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier('array', $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 297 => static function ($self, $stackPos) { + $self->semValue = new Node\Identifier('callable', $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 298 => null, + 299 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 300 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)]); + }, + 301 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 302 => null, + 303 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 304 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)]); + }, + 305 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 306 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)]); + }, + 307 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 308 => static function ($self, $stackPos) { + $self->semValue = new Node\IntersectionType($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 309 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)]); + }, + 310 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 311 => static function ($self, $stackPos) { + $self->semValue = new Node\IntersectionType($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 312 => null, + 313 => static function ($self, $stackPos) { + $self->semValue = new Node\NullableType($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 314 => static function ($self, $stackPos) { + $self->semValue = new Node\UnionType($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 315 => null, + 316 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 317 => null, + 318 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 319 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(2-2)]; + }, + 320 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 321 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 322 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-2)]; + }, + 323 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-2)]); + }, + 324 => static function ($self, $stackPos) { + $self->semValue = new Node\VariadicPlaceholder($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 325 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 326 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 327 => static function ($self, $stackPos) { + $self->semValue = new Node\Arg($self->semStack[$stackPos-(1-1)], false, false, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 328 => static function ($self, $stackPos) { + $self->semValue = new Node\Arg($self->semStack[$stackPos-(2-2)], true, false, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 329 => static function ($self, $stackPos) { + $self->semValue = new Node\Arg($self->semStack[$stackPos-(2-2)], false, true, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 330 => static function ($self, $stackPos) { + $self->semValue = new Node\Arg($self->semStack[$stackPos-(3-3)], false, false, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(3-1)]); + }, + 331 => null, + 332 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 333 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 334 => null, + 335 => null, + 336 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 337 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 338 => static function ($self, $stackPos) { + $self->semValue = new Node\StaticVar($self->semStack[$stackPos-(1-1)], null, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 339 => static function ($self, $stackPos) { + $self->semValue = new Node\StaticVar($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 340 => static function ($self, $stackPos) { + if ($self->semStack[$stackPos-(2-2)] !== null) { $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; } else { $self->semValue = $self->semStack[$stackPos-(2-1)]; } + }, + 341 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 342 => static function ($self, $stackPos) { + $nop = $self->maybeCreateZeroLengthNop($self->tokenPos);; + if ($nop !== null) { $self->semStack[$stackPos-(1-1)][] = $nop; } $self->semValue = $self->semStack[$stackPos-(1-1)]; + }, + 343 => static function ($self, $stackPos) { + $self->semValue = new Stmt\Property($self->semStack[$stackPos-(5-2)], $self->semStack[$stackPos-(5-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-1)]); + $self->checkProperty($self->semValue, $stackPos-(5-2)); + }, + 344 => static function ($self, $stackPos) { + $self->semValue = new Stmt\ClassConst($self->semStack[$stackPos-(5-4)], $self->semStack[$stackPos-(5-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(5-1)]); + $self->checkClassConst($self->semValue, $stackPos-(5-2)); + }, + 345 => static function ($self, $stackPos) { + $self->semValue = new Stmt\ClassConst($self->semStack[$stackPos-(6-5)], $self->semStack[$stackPos-(6-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(6-1)], $self->semStack[$stackPos-(6-4)]); + $self->checkClassConst($self->semValue, $stackPos-(6-2)); + }, + 346 => static function ($self, $stackPos) { + $self->semValue = new Stmt\ClassMethod($self->semStack[$stackPos-(10-5)], ['type' => $self->semStack[$stackPos-(10-2)], 'byRef' => $self->semStack[$stackPos-(10-4)], 'params' => $self->semStack[$stackPos-(10-7)], 'returnType' => $self->semStack[$stackPos-(10-9)], 'stmts' => $self->semStack[$stackPos-(10-10)], 'attrGroups' => $self->semStack[$stackPos-(10-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(10-1)], $self->tokenEndStack[$stackPos])); + $self->checkClassMethod($self->semValue, $stackPos-(10-2)); + }, + 347 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TraitUse($self->semStack[$stackPos-(3-2)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 348 => static function ($self, $stackPos) { + $self->semValue = new Stmt\EnumCase($self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-4)], $self->semStack[$stackPos-(5-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 349 => static function ($self, $stackPos) { + $self->semValue = null; /* will be skipped */ + }, + 350 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 351 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 352 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 353 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 354 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TraitUseAdaptation\Precedence($self->semStack[$stackPos-(4-1)][0], $self->semStack[$stackPos-(4-1)][1], $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 355 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TraitUseAdaptation\Alias($self->semStack[$stackPos-(5-1)][0], $self->semStack[$stackPos-(5-1)][1], $self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 356 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TraitUseAdaptation\Alias($self->semStack[$stackPos-(4-1)][0], $self->semStack[$stackPos-(4-1)][1], $self->semStack[$stackPos-(4-3)], null, $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 357 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TraitUseAdaptation\Alias($self->semStack[$stackPos-(4-1)][0], $self->semStack[$stackPos-(4-1)][1], null, $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 358 => static function ($self, $stackPos) { + $self->semValue = new Stmt\TraitUseAdaptation\Alias($self->semStack[$stackPos-(4-1)][0], $self->semStack[$stackPos-(4-1)][1], null, $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 359 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)]); + }, + 360 => null, + 361 => static function ($self, $stackPos) { + $self->semValue = array(null, $self->semStack[$stackPos-(1-1)]); + }, + 362 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 363 => null, + 364 => null, + 365 => static function ($self, $stackPos) { + $self->semValue = 0; + }, + 366 => static function ($self, $stackPos) { + $self->semValue = 0; + }, + 367 => null, + 368 => null, + 369 => static function ($self, $stackPos) { + $self->checkModifier($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $stackPos-(2-2)); $self->semValue = $self->semStack[$stackPos-(2-1)] | $self->semStack[$stackPos-(2-2)]; + }, + 370 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PUBLIC; + }, + 371 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PROTECTED; + }, + 372 => static function ($self, $stackPos) { + $self->semValue = Modifiers::PRIVATE; + }, + 373 => static function ($self, $stackPos) { + $self->semValue = Modifiers::STATIC; + }, + 374 => static function ($self, $stackPos) { + $self->semValue = Modifiers::ABSTRACT; + }, + 375 => static function ($self, $stackPos) { + $self->semValue = Modifiers::FINAL; + }, + 376 => static function ($self, $stackPos) { + $self->semValue = Modifiers::READONLY; + }, + 377 => null, + 378 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 379 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 380 => static function ($self, $stackPos) { + $self->semValue = new Node\VarLikeIdentifier(substr($self->semStack[$stackPos-(1-1)], 1), $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 381 => static function ($self, $stackPos) { + $self->semValue = new Node\PropertyItem($self->semStack[$stackPos-(1-1)], null, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 382 => static function ($self, $stackPos) { + $self->semValue = new Node\PropertyItem($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 383 => null, + 384 => null, + 385 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 386 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 387 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 388 => null, + 389 => null, + 390 => static function ($self, $stackPos) { + $self->semValue = new Expr\Assign($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 391 => static function ($self, $stackPos) { + $self->semValue = new Expr\Assign($self->fixupArrayDestructuring($self->semStack[$stackPos-(3-1)]), $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 392 => static function ($self, $stackPos) { + $self->semValue = new Expr\Assign($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 393 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignRef($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 394 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignRef($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + if (!$self->phpVersion->allowsAssignNewByReference()) { + $self->emitError(new Error('Cannot assign new by reference', $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos]))); + } + + }, + 395 => null, + 396 => null, + 397 => static function ($self, $stackPos) { + $self->semValue = new Expr\Clone_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 398 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Plus($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 399 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Minus($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 400 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Mul($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 401 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Div($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 402 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Concat($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 403 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Mod($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 404 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\BitwiseAnd($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 405 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\BitwiseOr($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 406 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\BitwiseXor($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 407 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\ShiftLeft($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 408 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\ShiftRight($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 409 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Pow($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 410 => static function ($self, $stackPos) { + $self->semValue = new Expr\AssignOp\Coalesce($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 411 => static function ($self, $stackPos) { + $self->semValue = new Expr\PostInc($self->semStack[$stackPos-(2-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 412 => static function ($self, $stackPos) { + $self->semValue = new Expr\PreInc($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 413 => static function ($self, $stackPos) { + $self->semValue = new Expr\PostDec($self->semStack[$stackPos-(2-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 414 => static function ($self, $stackPos) { + $self->semValue = new Expr\PreDec($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 415 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\BooleanOr($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 416 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\BooleanAnd($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 417 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\LogicalOr($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 418 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\LogicalAnd($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 419 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\LogicalXor($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 420 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\BitwiseOr($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 421 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\BitwiseAnd($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 422 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\BitwiseAnd($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 423 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\BitwiseXor($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 424 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Concat($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 425 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Plus($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 426 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Minus($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 427 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Mul($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 428 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Div($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 429 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Mod($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 430 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\ShiftLeft($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 431 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\ShiftRight($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 432 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Pow($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 433 => static function ($self, $stackPos) { + $self->semValue = new Expr\UnaryPlus($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 434 => static function ($self, $stackPos) { + $self->semValue = new Expr\UnaryMinus($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 435 => static function ($self, $stackPos) { + $self->semValue = new Expr\BooleanNot($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 436 => static function ($self, $stackPos) { + $self->semValue = new Expr\BitwiseNot($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 437 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Identical($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 438 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\NotIdentical($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 439 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Equal($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 440 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\NotEqual($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 441 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Spaceship($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 442 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Smaller($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 443 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\SmallerOrEqual($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 444 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Greater($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 445 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\GreaterOrEqual($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 446 => static function ($self, $stackPos) { + $self->semValue = new Expr\Instanceof_($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 447 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 448 => static function ($self, $stackPos) { + $self->semValue = new Expr\Ternary($self->semStack[$stackPos-(5-1)], $self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 449 => static function ($self, $stackPos) { + $self->semValue = new Expr\Ternary($self->semStack[$stackPos-(4-1)], null, $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 450 => static function ($self, $stackPos) { + $self->semValue = new Expr\BinaryOp\Coalesce($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 451 => static function ($self, $stackPos) { + $self->semValue = new Expr\Isset_($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 452 => static function ($self, $stackPos) { + $self->semValue = new Expr\Empty_($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 453 => static function ($self, $stackPos) { + $self->semValue = new Expr\Include_($self->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 454 => static function ($self, $stackPos) { + $self->semValue = new Expr\Include_($self->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE_ONCE, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 455 => static function ($self, $stackPos) { + $self->semValue = new Expr\Eval_($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 456 => static function ($self, $stackPos) { + $self->semValue = new Expr\Include_($self->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 457 => static function ($self, $stackPos) { + $self->semValue = new Expr\Include_($self->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE_ONCE, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 458 => static function ($self, $stackPos) { + $self->semValue = new Expr\Cast\Int_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 459 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]); + $attrs['kind'] = $self->getFloatCastKind($self->semStack[$stackPos-(2-1)]); + $self->semValue = new Expr\Cast\Double($self->semStack[$stackPos-(2-2)], $attrs); + }, + 460 => static function ($self, $stackPos) { + $self->semValue = new Expr\Cast\String_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 461 => static function ($self, $stackPos) { + $self->semValue = new Expr\Cast\Array_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 462 => static function ($self, $stackPos) { + $self->semValue = new Expr\Cast\Object_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 463 => static function ($self, $stackPos) { + $self->semValue = new Expr\Cast\Bool_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 464 => static function ($self, $stackPos) { + $self->semValue = new Expr\Cast\Unset_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 465 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]); + $attrs['kind'] = strtolower($self->semStack[$stackPos-(2-1)]) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE; + $self->semValue = new Expr\Exit_($self->semStack[$stackPos-(2-2)], $attrs); + }, + 466 => static function ($self, $stackPos) { + $self->semValue = new Expr\ErrorSuppress($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 467 => null, + 468 => static function ($self, $stackPos) { + $self->semValue = new Expr\ShellExec($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 469 => static function ($self, $stackPos) { + $self->semValue = new Expr\Print_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 470 => static function ($self, $stackPos) { + $self->semValue = new Expr\Yield_(null, null, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 471 => static function ($self, $stackPos) { + $self->semValue = new Expr\Yield_($self->semStack[$stackPos-(2-2)], null, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 472 => static function ($self, $stackPos) { + $self->semValue = new Expr\Yield_($self->semStack[$stackPos-(4-4)], $self->semStack[$stackPos-(4-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 473 => static function ($self, $stackPos) { + $self->semValue = new Expr\YieldFrom($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 474 => static function ($self, $stackPos) { + $self->semValue = new Expr\Throw_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 475 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrowFunction(['static' => false, 'byRef' => $self->semStack[$stackPos-(8-2)], 'params' => $self->semStack[$stackPos-(8-4)], 'returnType' => $self->semStack[$stackPos-(8-6)], 'expr' => $self->semStack[$stackPos-(8-8)], 'attrGroups' => []], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + }, + 476 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrowFunction(['static' => true, 'byRef' => $self->semStack[$stackPos-(9-3)], 'params' => $self->semStack[$stackPos-(9-5)], 'returnType' => $self->semStack[$stackPos-(9-7)], 'expr' => $self->semStack[$stackPos-(9-9)], 'attrGroups' => []], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 477 => static function ($self, $stackPos) { + $self->semValue = new Expr\Closure(['static' => false, 'byRef' => $self->semStack[$stackPos-(8-2)], 'params' => $self->semStack[$stackPos-(8-4)], 'uses' => $self->semStack[$stackPos-(8-6)], 'returnType' => $self->semStack[$stackPos-(8-7)], 'stmts' => $self->semStack[$stackPos-(8-8)], 'attrGroups' => []], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])); + }, + 478 => static function ($self, $stackPos) { + $self->semValue = new Expr\Closure(['static' => true, 'byRef' => $self->semStack[$stackPos-(9-3)], 'params' => $self->semStack[$stackPos-(9-5)], 'uses' => $self->semStack[$stackPos-(9-7)], 'returnType' => $self->semStack[$stackPos-(9-8)], 'stmts' => $self->semStack[$stackPos-(9-9)], 'attrGroups' => []], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 479 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrowFunction(['static' => false, 'byRef' => $self->semStack[$stackPos-(9-3)], 'params' => $self->semStack[$stackPos-(9-5)], 'returnType' => $self->semStack[$stackPos-(9-7)], 'expr' => $self->semStack[$stackPos-(9-9)], 'attrGroups' => $self->semStack[$stackPos-(9-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 480 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrowFunction(['static' => true, 'byRef' => $self->semStack[$stackPos-(10-4)], 'params' => $self->semStack[$stackPos-(10-6)], 'returnType' => $self->semStack[$stackPos-(10-8)], 'expr' => $self->semStack[$stackPos-(10-10)], 'attrGroups' => $self->semStack[$stackPos-(10-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(10-1)], $self->tokenEndStack[$stackPos])); + }, + 481 => static function ($self, $stackPos) { + $self->semValue = new Expr\Closure(['static' => false, 'byRef' => $self->semStack[$stackPos-(9-3)], 'params' => $self->semStack[$stackPos-(9-5)], 'uses' => $self->semStack[$stackPos-(9-7)], 'returnType' => $self->semStack[$stackPos-(9-8)], 'stmts' => $self->semStack[$stackPos-(9-9)], 'attrGroups' => $self->semStack[$stackPos-(9-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos])); + }, + 482 => static function ($self, $stackPos) { + $self->semValue = new Expr\Closure(['static' => true, 'byRef' => $self->semStack[$stackPos-(10-4)], 'params' => $self->semStack[$stackPos-(10-6)], 'uses' => $self->semStack[$stackPos-(10-8)], 'returnType' => $self->semStack[$stackPos-(10-9)], 'stmts' => $self->semStack[$stackPos-(10-10)], 'attrGroups' => $self->semStack[$stackPos-(10-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(10-1)], $self->tokenEndStack[$stackPos])); + }, + 483 => static function ($self, $stackPos) { + $self->semValue = array(new Stmt\Class_(null, ['type' => $self->semStack[$stackPos-(8-2)], 'extends' => $self->semStack[$stackPos-(8-4)], 'implements' => $self->semStack[$stackPos-(8-5)], 'stmts' => $self->semStack[$stackPos-(8-7)], 'attrGroups' => $self->semStack[$stackPos-(8-1)]], $self->getAttributes($self->tokenStartStack[$stackPos-(8-1)], $self->tokenEndStack[$stackPos])), $self->semStack[$stackPos-(8-3)]); + $self->checkClass($self->semValue[0], -1); + }, + 484 => static function ($self, $stackPos) { + $self->semValue = new Expr\New_($self->semStack[$stackPos-(3-2)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 485 => static function ($self, $stackPos) { + list($class, $ctorArgs) = $self->semStack[$stackPos-(2-2)]; $self->semValue = new Expr\New_($class, $ctorArgs, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 486 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 487 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(4-3)]; + }, + 488 => null, + 489 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 490 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 491 => static function ($self, $stackPos) { + $self->semValue = new Node\ClosureUse($self->semStack[$stackPos-(2-2)], $self->semStack[$stackPos-(2-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 492 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 493 => static function ($self, $stackPos) { + $self->semValue = new Expr\FuncCall($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 494 => static function ($self, $stackPos) { + $self->semValue = new Expr\FuncCall($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 495 => static function ($self, $stackPos) { + $self->semValue = new Expr\FuncCall($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 496 => static function ($self, $stackPos) { + $self->semValue = new Expr\StaticCall($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 497 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 498 => null, + 499 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 500 => static function ($self, $stackPos) { + $self->semValue = new Name($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 501 => static function ($self, $stackPos) { + $self->semValue = new Name\FullyQualified(substr($self->semStack[$stackPos-(1-1)], 1), $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 502 => static function ($self, $stackPos) { + $self->semValue = new Name\Relative(substr($self->semStack[$stackPos-(1-1)], 10), $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 503 => null, + 504 => null, + 505 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 506 => static function ($self, $stackPos) { + $self->semValue = new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); $self->errorState = 2; + }, + 507 => null, + 508 => null, + 509 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 510 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 511 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 512 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); foreach ($self->semValue as $s) { if ($s instanceof Node\InterpolatedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '`', $self->phpVersion->supportsUnicodeEscapes()); } }; + }, + 513 => static function ($self, $stackPos) { + foreach ($self->semStack[$stackPos-(1-1)] as $s) { if ($s instanceof Node\InterpolatedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '`', $self->phpVersion->supportsUnicodeEscapes()); } }; $self->semValue = $self->semStack[$stackPos-(1-1)]; + }, + 514 => static function ($self, $stackPos) { + $self->semValue = array(); + }, + 515 => null, + 516 => static function ($self, $stackPos) { + $self->semValue = new Expr\ConstFetch($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 517 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Line($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 518 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\File($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 519 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Dir($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 520 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Class_($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 521 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Trait_($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 522 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Method($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 523 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Function_($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 524 => static function ($self, $stackPos) { + $self->semValue = new Scalar\MagicConst\Namespace_($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 525 => static function ($self, $stackPos) { + $self->semValue = new Expr\ClassConstFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 526 => static function ($self, $stackPos) { + $self->semValue = new Expr\ClassConstFetch($self->semStack[$stackPos-(5-1)], $self->semStack[$stackPos-(5-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos])); + }, + 527 => static function ($self, $stackPos) { + $self->semValue = new Expr\ClassConstFetch($self->semStack[$stackPos-(3-1)], new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(3-3)], $self->tokenEndStack[$stackPos-(3-3)])), $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); $self->errorState = 2; + }, + 528 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]); $attrs['kind'] = Expr\Array_::KIND_SHORT; + $self->semValue = new Expr\Array_($self->semStack[$stackPos-(3-2)], $attrs); + }, + 529 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos]); $attrs['kind'] = Expr\Array_::KIND_LONG; + $self->semValue = new Expr\Array_($self->semStack[$stackPos-(4-3)], $attrs); + $self->createdArrays->attach($self->semValue); + }, + 530 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(1-1)]; $self->createdArrays->attach($self->semValue); + }, + 531 => static function ($self, $stackPos) { + $self->semValue = Scalar\String_::fromString($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]), $self->phpVersion->supportsUnicodeEscapes()); + }, + 532 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED; + foreach ($self->semStack[$stackPos-(3-2)] as $s) { if ($s instanceof Node\InterpolatedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '"', $self->phpVersion->supportsUnicodeEscapes()); } }; $self->semValue = new Scalar\InterpolatedString($self->semStack[$stackPos-(3-2)], $attrs); + }, + 533 => static function ($self, $stackPos) { + $self->semValue = $self->parseLNumber($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]), $self->phpVersion->allowsInvalidOctals()); + }, + 534 => static function ($self, $stackPos) { + $self->semValue = Scalar\Float_::fromString($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 535 => null, + 536 => null, + 537 => null, + 538 => static function ($self, $stackPos) { + $self->semValue = $self->parseDocString($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-2)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]), $self->getAttributes($self->tokenStartStack[$stackPos-(3-3)], $self->tokenEndStack[$stackPos-(3-3)]), true); + }, + 539 => static function ($self, $stackPos) { + $self->semValue = $self->parseDocString($self->semStack[$stackPos-(2-1)], '', $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]), $self->getAttributes($self->tokenStartStack[$stackPos-(2-2)], $self->tokenEndStack[$stackPos-(2-2)]), true); + }, + 540 => static function ($self, $stackPos) { + $self->semValue = $self->parseDocString($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-2)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]), $self->getAttributes($self->tokenStartStack[$stackPos-(3-3)], $self->tokenEndStack[$stackPos-(3-3)]), true); + }, + 541 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 542 => null, + 543 => null, + 544 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 545 => null, + 546 => null, + 547 => null, + 548 => null, + 549 => null, + 550 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 551 => null, + 552 => null, + 553 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrayDimFetch($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 554 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrayDimFetch($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 555 => null, + 556 => static function ($self, $stackPos) { + $self->semValue = new Expr\MethodCall($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 557 => static function ($self, $stackPos) { + $self->semValue = new Expr\NullsafeMethodCall($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->semStack[$stackPos-(4-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 558 => static function ($self, $stackPos) { + $self->semValue = null; + }, + 559 => null, + 560 => null, + 561 => null, + 562 => static function ($self, $stackPos) { + $self->semValue = new Expr\PropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 563 => static function ($self, $stackPos) { + $self->semValue = new Expr\NullsafePropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 564 => null, + 565 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 566 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 567 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable(new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])), $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); $self->errorState = 2; + }, + 568 => static function ($self, $stackPos) { + $var = $self->semStack[$stackPos-(1-1)]->name; $self->semValue = \is_string($var) ? new Node\VarLikeIdentifier($var, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])) : $var; + }, + 569 => static function ($self, $stackPos) { + $self->semValue = new Expr\StaticPropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 570 => null, + 571 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrayDimFetch($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 572 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrayDimFetch($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 573 => static function ($self, $stackPos) { + $self->semValue = new Expr\PropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 574 => static function ($self, $stackPos) { + $self->semValue = new Expr\NullsafePropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 575 => static function ($self, $stackPos) { + $self->semValue = new Expr\StaticPropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 576 => static function ($self, $stackPos) { + $self->semValue = new Expr\StaticPropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 577 => null, + 578 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 579 => null, + 580 => null, + 581 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 582 => null, + 583 => static function ($self, $stackPos) { + $self->semValue = new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); $self->errorState = 2; + }, + 584 => static function ($self, $stackPos) { + $self->semValue = new Expr\List_($self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); $self->semValue->setAttribute('kind', Expr\List_::KIND_LIST); + $self->postprocessList($self->semValue); + }, + 585 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(1-1)]; $end = count($self->semValue)-1; if ($self->semValue[$end]->value instanceof Expr\Error) array_pop($self->semValue); + }, + 586 => null, + 587 => static function ($self, $stackPos) { + /* do nothing -- prevent default action of $$=$self->semStack[$1]. See $551. */ + }, + 588 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(3-1)][] = $self->semStack[$stackPos-(3-3)]; $self->semValue = $self->semStack[$stackPos-(3-1)]; + }, + 589 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 590 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(1-1)], null, false, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 591 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(2-2)], null, true, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 592 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(1-1)], null, false, $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 593 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(3-3)], $self->semStack[$stackPos-(3-1)], false, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 594 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(4-4)], $self->semStack[$stackPos-(4-1)], true, $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 595 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(3-3)], $self->semStack[$stackPos-(3-1)], false, $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 596 => static function ($self, $stackPos) { + $self->semValue = new Node\ArrayItem($self->semStack[$stackPos-(2-2)], null, false, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]), true); + }, + 597 => static function ($self, $stackPos) { + /* Create an Error node now to remember the position. We'll later either report an error, + or convert this into a null element, depending on whether this is a creation or destructuring context. */ + $attrs = $self->createEmptyElemAttributes($self->tokenPos); + $self->semValue = new Node\ArrayItem(new Expr\Error($attrs), null, false, $attrs); + }, + 598 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 599 => static function ($self, $stackPos) { + $self->semStack[$stackPos-(2-1)][] = $self->semStack[$stackPos-(2-2)]; $self->semValue = $self->semStack[$stackPos-(2-1)]; + }, + 600 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(1-1)]); + }, + 601 => static function ($self, $stackPos) { + $self->semValue = array($self->semStack[$stackPos-(2-1)], $self->semStack[$stackPos-(2-2)]); + }, + 602 => static function ($self, $stackPos) { + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]); $attrs['rawValue'] = $self->semStack[$stackPos-(1-1)]; $self->semValue = new Node\InterpolatedStringPart($self->semStack[$stackPos-(1-1)], $attrs); + }, + 603 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 604 => null, + 605 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrayDimFetch($self->semStack[$stackPos-(4-1)], $self->semStack[$stackPos-(4-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos])); + }, + 606 => static function ($self, $stackPos) { + $self->semValue = new Expr\PropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 607 => static function ($self, $stackPos) { + $self->semValue = new Expr\NullsafePropertyFetch($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 608 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 609 => static function ($self, $stackPos) { + $self->semValue = new Expr\Variable($self->semStack[$stackPos-(3-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos])); + }, + 610 => static function ($self, $stackPos) { + $self->semValue = new Expr\ArrayDimFetch($self->semStack[$stackPos-(6-2)], $self->semStack[$stackPos-(6-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])); + }, + 611 => static function ($self, $stackPos) { + $self->semValue = $self->semStack[$stackPos-(3-2)]; + }, + 612 => static function ($self, $stackPos) { + $self->semValue = new Scalar\String_($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 613 => static function ($self, $stackPos) { + $self->semValue = $self->parseNumString($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos])); + }, + 614 => static function ($self, $stackPos) { + $self->semValue = $self->parseNumString('-' . $self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + }, + 615 => null, + ]; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/ParserAbstract.php b/vendor/nikic/php-parser/lib/PhpParser/ParserAbstract.php new file mode 100644 index 00000000..42723313 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/ParserAbstract.php @@ -0,0 +1,1241 @@ + Map of PHP token IDs to drop */ + protected array $dropTokens; + /** @var int[] Map of external symbols (static::T_*) to internal symbols */ + protected array $tokenToSymbol; + /** @var string[] Map of symbols to their names */ + protected array $symbolToName; + /** @var array Names of the production rules (only necessary for debugging) */ + protected array $productions; + + /** @var int[] Map of states to a displacement into the $action table. The corresponding action for this + * state/symbol pair is $action[$actionBase[$state] + $symbol]. If $actionBase[$state] is 0, the + * action is defaulted, i.e. $actionDefault[$state] should be used instead. */ + protected array $actionBase; + /** @var int[] Table of actions. Indexed according to $actionBase comment. */ + protected array $action; + /** @var int[] Table indexed analogously to $action. If $actionCheck[$actionBase[$state] + $symbol] != $symbol + * then the action is defaulted, i.e. $actionDefault[$state] should be used instead. */ + protected array $actionCheck; + /** @var int[] Map of states to their default action */ + protected array $actionDefault; + /** @var callable[] Semantic action callbacks */ + protected array $reduceCallbacks; + + /** @var int[] Map of non-terminals to a displacement into the $goto table. The corresponding goto state for this + * non-terminal/state pair is $goto[$gotoBase[$nonTerminal] + $state] (unless defaulted) */ + protected array $gotoBase; + /** @var int[] Table of states to goto after reduction. Indexed according to $gotoBase comment. */ + protected array $goto; + /** @var int[] Table indexed analogously to $goto. If $gotoCheck[$gotoBase[$nonTerminal] + $state] != $nonTerminal + * then the goto state is defaulted, i.e. $gotoDefault[$nonTerminal] should be used. */ + protected array $gotoCheck; + /** @var int[] Map of non-terminals to the default state to goto after their reduction */ + protected array $gotoDefault; + + /** @var int[] Map of rules to the non-terminal on their left-hand side, i.e. the non-terminal to use for + * determining the state to goto after reduction. */ + protected array $ruleToNonTerminal; + /** @var int[] Map of rules to the length of their right-hand side, which is the number of elements that have to + * be popped from the stack(s) on reduction. */ + protected array $ruleToLength; + + /* + * The following members are part of the parser state: + */ + + /** @var mixed Temporary value containing the result of last semantic action (reduction) */ + protected $semValue; + /** @var mixed[] Semantic value stack (contains values of tokens and semantic action results) */ + protected array $semStack; + /** @var int[] Token start position stack */ + protected array $tokenStartStack; + /** @var int[] Token end position stack */ + protected array $tokenEndStack; + + /** @var ErrorHandler Error handler */ + protected ErrorHandler $errorHandler; + /** @var int Error state, used to avoid error floods */ + protected int $errorState; + + /** @var \SplObjectStorage|null Array nodes created during parsing, for postprocessing of empty elements. */ + protected ?\SplObjectStorage $createdArrays; + + /** @var Token[] Tokens for the current parse */ + protected array $tokens; + /** @var int Current position in token array */ + protected int $tokenPos; + + /** + * Initialize $reduceCallbacks map. + */ + abstract protected function initReduceCallbacks(): void; + + /** + * Creates a parser instance. + * + * Options: + * * phpVersion: ?PhpVersion, + * + * @param Lexer $lexer A lexer + * @param PhpVersion $phpVersion PHP version to target, defaults to latest supported. This + * option is best-effort: Even if specified, parsing will generally assume the latest + * supported version and only adjust behavior in minor ways, for example by omitting + * errors in older versions and interpreting type hints as a name or identifier depending + * on version. + */ + public function __construct(Lexer $lexer, ?PhpVersion $phpVersion = null) { + $this->lexer = $lexer; + $this->phpVersion = $phpVersion ?? PhpVersion::getNewestSupported(); + + $this->initReduceCallbacks(); + $this->phpTokenToSymbol = $this->createTokenMap(); + $this->dropTokens = array_fill_keys( + [\T_WHITESPACE, \T_OPEN_TAG, \T_COMMENT, \T_DOC_COMMENT, \T_BAD_CHARACTER], true + ); + } + + /** + * Parses PHP code into a node tree. + * + * If a non-throwing error handler is used, the parser will continue parsing after an error + * occurred and attempt to build a partial AST. + * + * @param string $code The source code to parse + * @param ErrorHandler|null $errorHandler Error handler to use for lexer/parser errors, defaults + * to ErrorHandler\Throwing. + * + * @return Node\Stmt[]|null Array of statements (or null non-throwing error handler is used and + * the parser was unable to recover from an error). + */ + public function parse(string $code, ?ErrorHandler $errorHandler = null): ?array { + $this->errorHandler = $errorHandler ?: new ErrorHandler\Throwing(); + $this->createdArrays = new \SplObjectStorage(); + + $this->tokens = $this->lexer->tokenize($code, $this->errorHandler); + $result = $this->doParse(); + + // Report errors for any empty elements used inside arrays. This is delayed until after the main parse, + // because we don't know a priori whether a given array expression will be used in a destructuring context + // or not. + foreach ($this->createdArrays as $node) { + foreach ($node->items as $item) { + if ($item->value instanceof Expr\Error) { + $this->errorHandler->handleError( + new Error('Cannot use empty array elements in arrays', $item->getAttributes())); + } + } + } + + // Clear out some of the interior state, so we don't hold onto unnecessary + // memory between uses of the parser + $this->tokenStartStack = []; + $this->tokenEndStack = []; + $this->semStack = []; + $this->semValue = null; + $this->createdArrays = null; + + if ($result !== null) { + $traverser = new NodeTraverser(new CommentAnnotatingVisitor($this->tokens)); + $traverser->traverse($result); + } + + return $result; + } + + public function getTokens(): array { + return $this->tokens; + } + + /** @return Stmt[]|null */ + protected function doParse(): ?array { + // We start off with no lookahead-token + $symbol = self::SYMBOL_NONE; + $tokenValue = null; + $this->tokenPos = -1; + + // Keep stack of start and end attributes + $this->tokenStartStack = []; + $this->tokenEndStack = [0]; + + // Start off in the initial state and keep a stack of previous states + $state = 0; + $stateStack = [$state]; + + // Semantic value stack (contains values of tokens and semantic action results) + $this->semStack = []; + + // Current position in the stack(s) + $stackPos = 0; + + $this->errorState = 0; + + for (;;) { + //$this->traceNewState($state, $symbol); + + if ($this->actionBase[$state] === 0) { + $rule = $this->actionDefault[$state]; + } else { + if ($symbol === self::SYMBOL_NONE) { + do { + $token = $this->tokens[++$this->tokenPos]; + $tokenId = $token->id; + } while (isset($this->dropTokens[$tokenId])); + + // Map the lexer token id to the internally used symbols. + $tokenValue = $token->text; + if (!isset($this->phpTokenToSymbol[$tokenId])) { + throw new \RangeException(sprintf( + 'The lexer returned an invalid token (id=%d, value=%s)', + $tokenId, $tokenValue + )); + } + $symbol = $this->phpTokenToSymbol[$tokenId]; + + //$this->traceRead($symbol); + } + + $idx = $this->actionBase[$state] + $symbol; + if ((($idx >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol) + || ($state < $this->YY2TBLSTATE + && ($idx = $this->actionBase[$state + $this->numNonLeafStates] + $symbol) >= 0 + && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol)) + && ($action = $this->action[$idx]) !== $this->defaultAction) { + /* + * >= numNonLeafStates: shift and reduce + * > 0: shift + * = 0: accept + * < 0: reduce + * = -YYUNEXPECTED: error + */ + if ($action > 0) { + /* shift */ + //$this->traceShift($symbol); + + ++$stackPos; + $stateStack[$stackPos] = $state = $action; + $this->semStack[$stackPos] = $tokenValue; + $this->tokenStartStack[$stackPos] = $this->tokenPos; + $this->tokenEndStack[$stackPos] = $this->tokenPos; + $symbol = self::SYMBOL_NONE; + + if ($this->errorState) { + --$this->errorState; + } + + if ($action < $this->numNonLeafStates) { + continue; + } + + /* $yyn >= numNonLeafStates means shift-and-reduce */ + $rule = $action - $this->numNonLeafStates; + } else { + $rule = -$action; + } + } else { + $rule = $this->actionDefault[$state]; + } + } + + for (;;) { + if ($rule === 0) { + /* accept */ + //$this->traceAccept(); + return $this->semValue; + } + if ($rule !== $this->unexpectedTokenRule) { + /* reduce */ + //$this->traceReduce($rule); + + $ruleLength = $this->ruleToLength[$rule]; + try { + $callback = $this->reduceCallbacks[$rule]; + if ($callback !== null) { + $callback($this, $stackPos); + } elseif ($ruleLength > 0) { + $this->semValue = $this->semStack[$stackPos - $ruleLength + 1]; + } + } catch (Error $e) { + if (-1 === $e->getStartLine()) { + $e->setStartLine($this->tokens[$this->tokenPos]->line); + } + + $this->emitError($e); + // Can't recover from this type of error + return null; + } + + /* Goto - shift nonterminal */ + $lastTokenEnd = $this->tokenEndStack[$stackPos]; + $stackPos -= $ruleLength; + $nonTerminal = $this->ruleToNonTerminal[$rule]; + $idx = $this->gotoBase[$nonTerminal] + $stateStack[$stackPos]; + if ($idx >= 0 && $idx < $this->gotoTableSize && $this->gotoCheck[$idx] === $nonTerminal) { + $state = $this->goto[$idx]; + } else { + $state = $this->gotoDefault[$nonTerminal]; + } + + ++$stackPos; + $stateStack[$stackPos] = $state; + $this->semStack[$stackPos] = $this->semValue; + $this->tokenEndStack[$stackPos] = $lastTokenEnd; + if ($ruleLength === 0) { + // Empty productions use the start attributes of the lookahead token. + $this->tokenStartStack[$stackPos] = $this->tokenPos; + } + } else { + /* error */ + switch ($this->errorState) { + case 0: + $msg = $this->getErrorMessage($symbol, $state); + $this->emitError(new Error($msg, $this->getAttributesForToken($this->tokenPos))); + // Break missing intentionally + // no break + case 1: + case 2: + $this->errorState = 3; + + // Pop until error-expecting state uncovered + while (!( + (($idx = $this->actionBase[$state] + $this->errorSymbol) >= 0 + && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $this->errorSymbol) + || ($state < $this->YY2TBLSTATE + && ($idx = $this->actionBase[$state + $this->numNonLeafStates] + $this->errorSymbol) >= 0 + && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $this->errorSymbol) + ) || ($action = $this->action[$idx]) === $this->defaultAction) { // Not totally sure about this + if ($stackPos <= 0) { + // Could not recover from error + return null; + } + $state = $stateStack[--$stackPos]; + //$this->tracePop($state); + } + + //$this->traceShift($this->errorSymbol); + ++$stackPos; + $stateStack[$stackPos] = $state = $action; + + // We treat the error symbol as being empty, so we reset the end attributes + // to the end attributes of the last non-error symbol + $this->tokenStartStack[$stackPos] = $this->tokenPos; + $this->tokenEndStack[$stackPos] = $this->tokenEndStack[$stackPos - 1]; + break; + + case 3: + if ($symbol === 0) { + // Reached EOF without recovering from error + return null; + } + + //$this->traceDiscard($symbol); + $symbol = self::SYMBOL_NONE; + break 2; + } + } + + if ($state < $this->numNonLeafStates) { + break; + } + + /* >= numNonLeafStates means shift-and-reduce */ + $rule = $state - $this->numNonLeafStates; + } + } + + throw new \RuntimeException('Reached end of parser loop'); + } + + protected function emitError(Error $error): void { + $this->errorHandler->handleError($error); + } + + /** + * Format error message including expected tokens. + * + * @param int $symbol Unexpected symbol + * @param int $state State at time of error + * + * @return string Formatted error message + */ + protected function getErrorMessage(int $symbol, int $state): string { + $expectedString = ''; + if ($expected = $this->getExpectedTokens($state)) { + $expectedString = ', expecting ' . implode(' or ', $expected); + } + + return 'Syntax error, unexpected ' . $this->symbolToName[$symbol] . $expectedString; + } + + /** + * Get limited number of expected tokens in given state. + * + * @param int $state State + * + * @return string[] Expected tokens. If too many, an empty array is returned. + */ + protected function getExpectedTokens(int $state): array { + $expected = []; + + $base = $this->actionBase[$state]; + foreach ($this->symbolToName as $symbol => $name) { + $idx = $base + $symbol; + if ($idx >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol + || $state < $this->YY2TBLSTATE + && ($idx = $this->actionBase[$state + $this->numNonLeafStates] + $symbol) >= 0 + && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol + ) { + if ($this->action[$idx] !== $this->unexpectedTokenRule + && $this->action[$idx] !== $this->defaultAction + && $symbol !== $this->errorSymbol + ) { + if (count($expected) === 4) { + /* Too many expected tokens */ + return []; + } + + $expected[] = $name; + } + } + } + + return $expected; + } + + /** + * Get attributes for a node with the given start and end token positions. + * + * @param int $tokenStartPos Token position the node starts at + * @param int $tokenEndPos Token position the node ends at + * @return array Attributes + */ + protected function getAttributes(int $tokenStartPos, int $tokenEndPos): array { + $startToken = $this->tokens[$tokenStartPos]; + $afterEndToken = $this->tokens[$tokenEndPos + 1]; + return [ + 'startLine' => $startToken->line, + 'startTokenPos' => $tokenStartPos, + 'startFilePos' => $startToken->pos, + 'endLine' => $afterEndToken->line, + 'endTokenPos' => $tokenEndPos, + 'endFilePos' => $afterEndToken->pos - 1, + ]; + } + + /** + * Get attributes for a single token at the given token position. + * + * @return array Attributes + */ + protected function getAttributesForToken(int $tokenPos): array { + if ($tokenPos < \count($this->tokens) - 1) { + return $this->getAttributes($tokenPos, $tokenPos); + } + + // Get attributes for the sentinel token. + $token = $this->tokens[$tokenPos]; + return [ + 'startLine' => $token->line, + 'startTokenPos' => $tokenPos, + 'startFilePos' => $token->pos, + 'endLine' => $token->line, + 'endTokenPos' => $tokenPos, + 'endFilePos' => $token->pos, + ]; + } + + /* + * Tracing functions used for debugging the parser. + */ + + /* + protected function traceNewState($state, $symbol): void { + echo '% State ' . $state + . ', Lookahead ' . ($symbol == self::SYMBOL_NONE ? '--none--' : $this->symbolToName[$symbol]) . "\n"; + } + + protected function traceRead($symbol): void { + echo '% Reading ' . $this->symbolToName[$symbol] . "\n"; + } + + protected function traceShift($symbol): void { + echo '% Shift ' . $this->symbolToName[$symbol] . "\n"; + } + + protected function traceAccept(): void { + echo "% Accepted.\n"; + } + + protected function traceReduce($n): void { + echo '% Reduce by (' . $n . ') ' . $this->productions[$n] . "\n"; + } + + protected function tracePop($state): void { + echo '% Recovering, uncovered state ' . $state . "\n"; + } + + protected function traceDiscard($symbol): void { + echo '% Discard ' . $this->symbolToName[$symbol] . "\n"; + } + */ + + /* + * Helper functions invoked by semantic actions + */ + + /** + * Moves statements of semicolon-style namespaces into $ns->stmts and checks various error conditions. + * + * @param Node\Stmt[] $stmts + * @return Node\Stmt[] + */ + protected function handleNamespaces(array $stmts): array { + $hasErrored = false; + $style = $this->getNamespacingStyle($stmts); + if (null === $style) { + // not namespaced, nothing to do + return $stmts; + } + if ('brace' === $style) { + // For braced namespaces we only have to check that there are no invalid statements between the namespaces + $afterFirstNamespace = false; + foreach ($stmts as $stmt) { + if ($stmt instanceof Node\Stmt\Namespace_) { + $afterFirstNamespace = true; + } elseif (!$stmt instanceof Node\Stmt\HaltCompiler + && !$stmt instanceof Node\Stmt\Nop + && $afterFirstNamespace && !$hasErrored) { + $this->emitError(new Error( + 'No code may exist outside of namespace {}', $stmt->getAttributes())); + $hasErrored = true; // Avoid one error for every statement + } + } + return $stmts; + } else { + // For semicolon namespaces we have to move the statements after a namespace declaration into ->stmts + $resultStmts = []; + $targetStmts = &$resultStmts; + $lastNs = null; + foreach ($stmts as $stmt) { + if ($stmt instanceof Node\Stmt\Namespace_) { + if ($lastNs !== null) { + $this->fixupNamespaceAttributes($lastNs); + } + if ($stmt->stmts === null) { + $stmt->stmts = []; + $targetStmts = &$stmt->stmts; + $resultStmts[] = $stmt; + } else { + // This handles the invalid case of mixed style namespaces + $resultStmts[] = $stmt; + $targetStmts = &$resultStmts; + } + $lastNs = $stmt; + } elseif ($stmt instanceof Node\Stmt\HaltCompiler) { + // __halt_compiler() is not moved into the namespace + $resultStmts[] = $stmt; + } else { + $targetStmts[] = $stmt; + } + } + if ($lastNs !== null) { + $this->fixupNamespaceAttributes($lastNs); + } + return $resultStmts; + } + } + + private function fixupNamespaceAttributes(Node\Stmt\Namespace_ $stmt): void { + // We moved the statements into the namespace node, as such the end of the namespace node + // needs to be extended to the end of the statements. + if (empty($stmt->stmts)) { + return; + } + + // We only move the builtin end attributes here. This is the best we can do with the + // knowledge we have. + $endAttributes = ['endLine', 'endFilePos', 'endTokenPos']; + $lastStmt = $stmt->stmts[count($stmt->stmts) - 1]; + foreach ($endAttributes as $endAttribute) { + if ($lastStmt->hasAttribute($endAttribute)) { + $stmt->setAttribute($endAttribute, $lastStmt->getAttribute($endAttribute)); + } + } + } + + /** @return array */ + private function getNamespaceErrorAttributes(Namespace_ $node): array { + $attrs = $node->getAttributes(); + // Adjust end attributes to only cover the "namespace" keyword, not the whole namespace. + if (isset($attrs['startLine'])) { + $attrs['endLine'] = $attrs['startLine']; + } + if (isset($attrs['startTokenPos'])) { + $attrs['endTokenPos'] = $attrs['startTokenPos']; + } + if (isset($attrs['startFilePos'])) { + $attrs['endFilePos'] = $attrs['startFilePos'] + \strlen('namespace') - 1; + } + return $attrs; + } + + /** + * Determine namespacing style (semicolon or brace) + * + * @param Node[] $stmts Top-level statements. + * + * @return null|string One of "semicolon", "brace" or null (no namespaces) + */ + private function getNamespacingStyle(array $stmts): ?string { + $style = null; + $hasNotAllowedStmts = false; + foreach ($stmts as $i => $stmt) { + if ($stmt instanceof Node\Stmt\Namespace_) { + $currentStyle = null === $stmt->stmts ? 'semicolon' : 'brace'; + if (null === $style) { + $style = $currentStyle; + if ($hasNotAllowedStmts) { + $this->emitError(new Error( + 'Namespace declaration statement has to be the very first statement in the script', + $this->getNamespaceErrorAttributes($stmt) + )); + } + } elseif ($style !== $currentStyle) { + $this->emitError(new Error( + 'Cannot mix bracketed namespace declarations with unbracketed namespace declarations', + $this->getNamespaceErrorAttributes($stmt) + )); + // Treat like semicolon style for namespace normalization + return 'semicolon'; + } + continue; + } + + /* declare(), __halt_compiler() and nops can be used before a namespace declaration */ + if ($stmt instanceof Node\Stmt\Declare_ + || $stmt instanceof Node\Stmt\HaltCompiler + || $stmt instanceof Node\Stmt\Nop) { + continue; + } + + /* There may be a hashbang line at the very start of the file */ + if ($i === 0 && $stmt instanceof Node\Stmt\InlineHTML && preg_match('/\A#!.*\r?\n\z/', $stmt->value)) { + continue; + } + + /* Everything else if forbidden before namespace declarations */ + $hasNotAllowedStmts = true; + } + return $style; + } + + /** @return Name|Identifier */ + protected function handleBuiltinTypes(Name $name) { + if (!$name->isUnqualified()) { + return $name; + } + + $lowerName = $name->toLowerString(); + if (!$this->phpVersion->supportsBuiltinType($lowerName)) { + return $name; + } + + return new Node\Identifier($lowerName, $name->getAttributes()); + } + + /** + * Get combined start and end attributes at a stack location + * + * @param int $stackPos Stack location + * + * @return array Combined start and end attributes + */ + protected function getAttributesAt(int $stackPos): array { + return $this->getAttributes($this->tokenStartStack[$stackPos], $this->tokenEndStack[$stackPos]); + } + + protected function getFloatCastKind(string $cast): int { + $cast = strtolower($cast); + if (strpos($cast, 'float') !== false) { + return Double::KIND_FLOAT; + } + + if (strpos($cast, 'real') !== false) { + return Double::KIND_REAL; + } + + return Double::KIND_DOUBLE; + } + + /** @param array $attributes */ + protected function parseLNumber(string $str, array $attributes, bool $allowInvalidOctal = false): Int_ { + try { + return Int_::fromString($str, $attributes, $allowInvalidOctal); + } catch (Error $error) { + $this->emitError($error); + // Use dummy value + return new Int_(0, $attributes); + } + } + + /** + * Parse a T_NUM_STRING token into either an integer or string node. + * + * @param string $str Number string + * @param array $attributes Attributes + * + * @return Int_|String_ Integer or string node. + */ + protected function parseNumString(string $str, array $attributes) { + if (!preg_match('/^(?:0|-?[1-9][0-9]*)$/', $str)) { + return new String_($str, $attributes); + } + + $num = +$str; + if (!is_int($num)) { + return new String_($str, $attributes); + } + + return new Int_($num, $attributes); + } + + /** @param array $attributes */ + protected function stripIndentation( + string $string, int $indentLen, string $indentChar, + bool $newlineAtStart, bool $newlineAtEnd, array $attributes + ): string { + if ($indentLen === 0) { + return $string; + } + + $start = $newlineAtStart ? '(?:(?<=\n)|\A)' : '(?<=\n)'; + $end = $newlineAtEnd ? '(?:(?=[\r\n])|\z)' : '(?=[\r\n])'; + $regex = '/' . $start . '([ \t]*)(' . $end . ')?/'; + return preg_replace_callback( + $regex, + function ($matches) use ($indentLen, $indentChar, $attributes) { + $prefix = substr($matches[1], 0, $indentLen); + if (false !== strpos($prefix, $indentChar === " " ? "\t" : " ")) { + $this->emitError(new Error( + 'Invalid indentation - tabs and spaces cannot be mixed', $attributes + )); + } elseif (strlen($prefix) < $indentLen && !isset($matches[2])) { + $this->emitError(new Error( + 'Invalid body indentation level ' . + '(expecting an indentation level of at least ' . $indentLen . ')', + $attributes + )); + } + return substr($matches[0], strlen($prefix)); + }, + $string + ); + } + + /** + * @param string|(Expr|InterpolatedStringPart)[] $contents + * @param array $attributes + * @param array $endTokenAttributes + */ + protected function parseDocString( + string $startToken, $contents, string $endToken, + array $attributes, array $endTokenAttributes, bool $parseUnicodeEscape + ): Expr { + $kind = strpos($startToken, "'") === false + ? String_::KIND_HEREDOC : String_::KIND_NOWDOC; + + $regex = '/\A[bB]?<<<[ \t]*[\'"]?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\'"]?(?:\r\n|\n|\r)\z/'; + $result = preg_match($regex, $startToken, $matches); + assert($result === 1); + $label = $matches[1]; + + $result = preg_match('/\A[ \t]*/', $endToken, $matches); + assert($result === 1); + $indentation = $matches[0]; + + $attributes['kind'] = $kind; + $attributes['docLabel'] = $label; + $attributes['docIndentation'] = $indentation; + + $indentHasSpaces = false !== strpos($indentation, " "); + $indentHasTabs = false !== strpos($indentation, "\t"); + if ($indentHasSpaces && $indentHasTabs) { + $this->emitError(new Error( + 'Invalid indentation - tabs and spaces cannot be mixed', + $endTokenAttributes + )); + + // Proceed processing as if this doc string is not indented + $indentation = ''; + } + + $indentLen = \strlen($indentation); + $indentChar = $indentHasSpaces ? " " : "\t"; + + if (\is_string($contents)) { + if ($contents === '') { + $attributes['rawValue'] = $contents; + return new String_('', $attributes); + } + + $contents = $this->stripIndentation( + $contents, $indentLen, $indentChar, true, true, $attributes + ); + $contents = preg_replace('~(\r\n|\n|\r)\z~', '', $contents); + $attributes['rawValue'] = $contents; + + if ($kind === String_::KIND_HEREDOC) { + $contents = String_::parseEscapeSequences($contents, null, $parseUnicodeEscape); + } + + return new String_($contents, $attributes); + } else { + assert(count($contents) > 0); + if (!$contents[0] instanceof Node\InterpolatedStringPart) { + // If there is no leading encapsed string part, pretend there is an empty one + $this->stripIndentation( + '', $indentLen, $indentChar, true, false, $contents[0]->getAttributes() + ); + } + + $newContents = []; + foreach ($contents as $i => $part) { + if ($part instanceof Node\InterpolatedStringPart) { + $isLast = $i === \count($contents) - 1; + $part->value = $this->stripIndentation( + $part->value, $indentLen, $indentChar, + $i === 0, $isLast, $part->getAttributes() + ); + if ($isLast) { + $part->value = preg_replace('~(\r\n|\n|\r)\z~', '', $part->value); + } + $part->setAttribute('rawValue', $part->value); + $part->value = String_::parseEscapeSequences($part->value, null, $parseUnicodeEscape); + if ('' === $part->value) { + continue; + } + } + $newContents[] = $part; + } + return new InterpolatedString($newContents, $attributes); + } + } + + protected function createCommentFromToken(Token $token, int $tokenPos): Comment { + assert($token->id === \T_COMMENT || $token->id == \T_DOC_COMMENT); + return \T_DOC_COMMENT === $token->id + ? new Comment\Doc($token->text, $token->line, $token->pos, $tokenPos, + $token->getEndLine(), $token->getEndPos() - 1, $tokenPos) + : new Comment($token->text, $token->line, $token->pos, $tokenPos, + $token->getEndLine(), $token->getEndPos() - 1, $tokenPos); + } + + /** + * Get last comment before the given token position, if any + */ + protected function getCommentBeforeToken(int $tokenPos): ?Comment { + while (--$tokenPos >= 0) { + $token = $this->tokens[$tokenPos]; + if (!isset($this->dropTokens[$token->id])) { + break; + } + + if ($token->id === \T_COMMENT || $token->id === \T_DOC_COMMENT) { + return $this->createCommentFromToken($token, $tokenPos); + } + } + return null; + } + + /** + * Create a zero-length nop to capture preceding comments, if any. + */ + protected function maybeCreateZeroLengthNop(int $tokenPos): ?Nop { + $comment = $this->getCommentBeforeToken($tokenPos); + if ($comment === null) { + return null; + } + + $commentEndLine = $comment->getEndLine(); + $commentEndFilePos = $comment->getEndFilePos(); + $commentEndTokenPos = $comment->getEndTokenPos(); + $attributes = [ + 'startLine' => $commentEndLine, + 'endLine' => $commentEndLine, + 'startFilePos' => $commentEndFilePos + 1, + 'endFilePos' => $commentEndFilePos, + 'startTokenPos' => $commentEndTokenPos + 1, + 'endTokenPos' => $commentEndTokenPos, + ]; + return new Nop($attributes); + } + + protected function maybeCreateNop(int $tokenStartPos, int $tokenEndPos): ?Nop { + if ($this->getCommentBeforeToken($tokenStartPos) === null) { + return null; + } + return new Nop($this->getAttributes($tokenStartPos, $tokenEndPos)); + } + + protected function handleHaltCompiler(): string { + // Prevent the lexer from returning any further tokens. + $nextToken = $this->tokens[$this->tokenPos + 1]; + $this->tokenPos = \count($this->tokens) - 2; + + // Return text after __halt_compiler. + return $nextToken->id === \T_INLINE_HTML ? $nextToken->text : ''; + } + + protected function inlineHtmlHasLeadingNewline(int $stackPos): bool { + $tokenPos = $this->tokenStartStack[$stackPos]; + $token = $this->tokens[$tokenPos]; + assert($token->id == \T_INLINE_HTML); + if ($tokenPos > 0) { + $prevToken = $this->tokens[$tokenPos - 1]; + assert($prevToken->id == \T_CLOSE_TAG); + return false !== strpos($prevToken->text, "\n") + || false !== strpos($prevToken->text, "\r"); + } + return true; + } + + /** + * @return array + */ + protected function createEmptyElemAttributes(int $tokenPos): array { + return $this->getAttributesForToken($tokenPos); + } + + protected function fixupArrayDestructuring(Array_ $node): Expr\List_ { + $this->createdArrays->detach($node); + return new Expr\List_(array_map(function (Node\ArrayItem $item) { + if ($item->value instanceof Expr\Error) { + // We used Error as a placeholder for empty elements, which are legal for destructuring. + return null; + } + if ($item->value instanceof Array_) { + return new Node\ArrayItem( + $this->fixupArrayDestructuring($item->value), + $item->key, $item->byRef, $item->getAttributes()); + } + return $item; + }, $node->items), ['kind' => Expr\List_::KIND_ARRAY] + $node->getAttributes()); + } + + protected function postprocessList(Expr\List_ $node): void { + foreach ($node->items as $i => $item) { + if ($item->value instanceof Expr\Error) { + // We used Error as a placeholder for empty elements, which are legal for destructuring. + $node->items[$i] = null; + } + } + } + + /** @param ElseIf_|Else_ $node */ + protected function fixupAlternativeElse($node): void { + // Make sure a trailing nop statement carrying comments is part of the node. + $numStmts = \count($node->stmts); + if ($numStmts !== 0 && $node->stmts[$numStmts - 1] instanceof Nop) { + $nopAttrs = $node->stmts[$numStmts - 1]->getAttributes(); + if (isset($nopAttrs['endLine'])) { + $node->setAttribute('endLine', $nopAttrs['endLine']); + } + if (isset($nopAttrs['endFilePos'])) { + $node->setAttribute('endFilePos', $nopAttrs['endFilePos']); + } + if (isset($nopAttrs['endTokenPos'])) { + $node->setAttribute('endTokenPos', $nopAttrs['endTokenPos']); + } + } + } + + protected function checkClassModifier(int $a, int $b, int $modifierPos): void { + try { + Modifiers::verifyClassModifier($a, $b); + } catch (Error $error) { + $error->setAttributes($this->getAttributesAt($modifierPos)); + $this->emitError($error); + } + } + + protected function checkModifier(int $a, int $b, int $modifierPos): void { + // Jumping through some hoops here because verifyModifier() is also used elsewhere + try { + Modifiers::verifyModifier($a, $b); + } catch (Error $error) { + $error->setAttributes($this->getAttributesAt($modifierPos)); + $this->emitError($error); + } + } + + protected function checkParam(Param $node): void { + if ($node->variadic && null !== $node->default) { + $this->emitError(new Error( + 'Variadic parameter cannot have a default value', + $node->default->getAttributes() + )); + } + } + + protected function checkTryCatch(TryCatch $node): void { + if (empty($node->catches) && null === $node->finally) { + $this->emitError(new Error( + 'Cannot use try without catch or finally', $node->getAttributes() + )); + } + } + + protected function checkNamespace(Namespace_ $node): void { + if (null !== $node->stmts) { + foreach ($node->stmts as $stmt) { + if ($stmt instanceof Namespace_) { + $this->emitError(new Error( + 'Namespace declarations cannot be nested', $stmt->getAttributes() + )); + } + } + } + } + + private function checkClassName(?Identifier $name, int $namePos): void { + if (null !== $name && $name->isSpecialClassName()) { + $this->emitError(new Error( + sprintf('Cannot use \'%s\' as class name as it is reserved', $name), + $this->getAttributesAt($namePos) + )); + } + } + + /** @param Name[] $interfaces */ + private function checkImplementedInterfaces(array $interfaces): void { + foreach ($interfaces as $interface) { + if ($interface->isSpecialClassName()) { + $this->emitError(new Error( + sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface), + $interface->getAttributes() + )); + } + } + } + + protected function checkClass(Class_ $node, int $namePos): void { + $this->checkClassName($node->name, $namePos); + + if ($node->extends && $node->extends->isSpecialClassName()) { + $this->emitError(new Error( + sprintf('Cannot use \'%s\' as class name as it is reserved', $node->extends), + $node->extends->getAttributes() + )); + } + + $this->checkImplementedInterfaces($node->implements); + } + + protected function checkInterface(Interface_ $node, int $namePos): void { + $this->checkClassName($node->name, $namePos); + $this->checkImplementedInterfaces($node->extends); + } + + protected function checkEnum(Enum_ $node, int $namePos): void { + $this->checkClassName($node->name, $namePos); + $this->checkImplementedInterfaces($node->implements); + } + + protected function checkClassMethod(ClassMethod $node, int $modifierPos): void { + if ($node->flags & Modifiers::STATIC) { + switch ($node->name->toLowerString()) { + case '__construct': + $this->emitError(new Error( + sprintf('Constructor %s() cannot be static', $node->name), + $this->getAttributesAt($modifierPos))); + break; + case '__destruct': + $this->emitError(new Error( + sprintf('Destructor %s() cannot be static', $node->name), + $this->getAttributesAt($modifierPos))); + break; + case '__clone': + $this->emitError(new Error( + sprintf('Clone method %s() cannot be static', $node->name), + $this->getAttributesAt($modifierPos))); + break; + } + } + + if ($node->flags & Modifiers::READONLY) { + $this->emitError(new Error( + sprintf('Method %s() cannot be readonly', $node->name), + $this->getAttributesAt($modifierPos))); + } + } + + protected function checkClassConst(ClassConst $node, int $modifierPos): void { + if ($node->flags & Modifiers::STATIC) { + $this->emitError(new Error( + "Cannot use 'static' as constant modifier", + $this->getAttributesAt($modifierPos))); + } + if ($node->flags & Modifiers::ABSTRACT) { + $this->emitError(new Error( + "Cannot use 'abstract' as constant modifier", + $this->getAttributesAt($modifierPos))); + } + if ($node->flags & Modifiers::READONLY) { + $this->emitError(new Error( + "Cannot use 'readonly' as constant modifier", + $this->getAttributesAt($modifierPos))); + } + } + + protected function checkProperty(Property $node, int $modifierPos): void { + if ($node->flags & Modifiers::ABSTRACT) { + $this->emitError(new Error('Properties cannot be declared abstract', + $this->getAttributesAt($modifierPos))); + } + + if ($node->flags & Modifiers::FINAL) { + $this->emitError(new Error('Properties cannot be declared final', + $this->getAttributesAt($modifierPos))); + } + } + + protected function checkUseUse(UseItem $node, int $namePos): void { + if ($node->alias && $node->alias->isSpecialClassName()) { + $this->emitError(new Error( + sprintf( + 'Cannot use %s as %s because \'%2$s\' is a special class name', + $node->name, $node->alias + ), + $this->getAttributesAt($namePos) + )); + } + } + + /** + * Creates the token map. + * + * The token map maps the PHP internal token identifiers + * to the identifiers used by the Parser. Additionally it + * maps T_OPEN_TAG_WITH_ECHO to T_ECHO and T_CLOSE_TAG to ';'. + * + * @return array The token map + */ + protected function createTokenMap(): array { + $tokenMap = []; + + for ($i = 0; $i < 1000; ++$i) { + if ($i < 256) { + // Single-char tokens use an identity mapping. + $tokenMap[$i] = $i; + } elseif (\T_DOUBLE_COLON === $i) { + // T_DOUBLE_COLON is equivalent to T_PAAMAYIM_NEKUDOTAYIM + $tokenMap[$i] = static::T_PAAMAYIM_NEKUDOTAYIM; + } elseif (\T_OPEN_TAG_WITH_ECHO === $i) { + // T_OPEN_TAG_WITH_ECHO with dropped T_OPEN_TAG results in T_ECHO + $tokenMap[$i] = static::T_ECHO; + } elseif (\T_CLOSE_TAG === $i) { + // T_CLOSE_TAG is equivalent to ';' + $tokenMap[$i] = ord(';'); + } elseif ('UNKNOWN' !== $name = token_name($i)) { + if (defined($name = static::class . '::' . $name)) { + // Other tokens can be mapped directly + $tokenMap[$i] = constant($name); + } + } + } + + // Assign tokens for which we define compatibility constants, as token_name() does not know them. + $tokenMap[\T_FN] = static::T_FN; + $tokenMap[\T_COALESCE_EQUAL] = static::T_COALESCE_EQUAL; + $tokenMap[\T_NAME_QUALIFIED] = static::T_NAME_QUALIFIED; + $tokenMap[\T_NAME_FULLY_QUALIFIED] = static::T_NAME_FULLY_QUALIFIED; + $tokenMap[\T_NAME_RELATIVE] = static::T_NAME_RELATIVE; + $tokenMap[\T_MATCH] = static::T_MATCH; + $tokenMap[\T_NULLSAFE_OBJECT_OPERATOR] = static::T_NULLSAFE_OBJECT_OPERATOR; + $tokenMap[\T_ATTRIBUTE] = static::T_ATTRIBUTE; + $tokenMap[\T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG] = static::T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG; + $tokenMap[\T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG] = static::T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG; + $tokenMap[\T_ENUM] = static::T_ENUM; + $tokenMap[\T_READONLY] = static::T_READONLY; + + // We have create a map from PHP token IDs to external symbol IDs. + // Now map them to the internal symbol ID. + $fullTokenMap = []; + foreach ($tokenMap as $phpToken => $extSymbol) { + $intSymbol = $this->tokenToSymbol[$extSymbol]; + if ($intSymbol === $this->invalidSymbol) { + continue; + } + $fullTokenMap[$phpToken] = $intSymbol; + } + + return $fullTokenMap; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/ParserFactory.php b/vendor/nikic/php-parser/lib/PhpParser/ParserFactory.php new file mode 100644 index 00000000..3a7586ea --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/ParserFactory.php @@ -0,0 +1,42 @@ +isHostVersion()) { + $lexer = new Lexer(); + } else { + $lexer = new Lexer\Emulative($version); + } + if ($version->id >= 80000) { + return new Php8($lexer, $version); + } + return new Php7($lexer, $version); + } + + /** + * Create a parser targeting the newest version supported by this library. Code for older + * versions will be accepted if there have been no relevant backwards-compatibility breaks in + * PHP. + */ + public function createForNewestSupportedVersion(): Parser { + return $this->createForVersion(PhpVersion::getNewestSupported()); + } + + /** + * Create a parser targeting the host PHP version, that is the PHP version we're currently + * running on. This parser will not use any token emulation. + */ + public function createForHostVersion(): Parser { + return $this->createForVersion(PhpVersion::getHostVersion()); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/PhpVersion.php b/vendor/nikic/php-parser/lib/PhpParser/PhpVersion.php new file mode 100644 index 00000000..db85b1e5 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/PhpVersion.php @@ -0,0 +1,164 @@ + 50100, + 'callable' => 50400, + 'bool' => 70000, + 'int' => 70000, + 'float' => 70000, + 'string' => 70000, + 'iterable' => 70100, + 'void' => 70100, + 'object' => 70200, + 'null' => 80000, + 'false' => 80000, + 'mixed' => 80000, + 'never' => 80100, + 'true' => 80200, + ]; + + private function __construct(int $id) { + $this->id = $id; + } + + /** + * Create a PhpVersion object from major and minor version components. + */ + public static function fromComponents(int $major, int $minor): self { + return new self($major * 10000 + $minor * 100); + } + + /** + * Get the newest PHP version supported by this library. Support for this version may be partial, + * if it is still under development. + */ + public static function getNewestSupported(): self { + return self::fromComponents(8, 3); + } + + /** + * Get the host PHP version, that is the PHP version we're currently running on. + */ + public static function getHostVersion(): self { + return self::fromComponents(\PHP_MAJOR_VERSION, \PHP_MINOR_VERSION); + } + + /** + * Parse the version from a string like "8.1". + */ + public static function fromString(string $version): self { + if (!preg_match('/^(\d+)\.(\d+)/', $version, $matches)) { + throw new \LogicException("Invalid PHP version \"$version\""); + } + return self::fromComponents((int) $matches[1], (int) $matches[2]); + } + + /** + * Check whether two versions are the same. + */ + public function equals(PhpVersion $other): bool { + return $this->id === $other->id; + } + + /** + * Check whether this version is greater than or equal to the argument. + */ + public function newerOrEqual(PhpVersion $other): bool { + return $this->id >= $other->id; + } + + /** + * Check whether this version is older than the argument. + */ + public function older(PhpVersion $other): bool { + return $this->id < $other->id; + } + + /** + * Check whether this is the host PHP version. + */ + public function isHostVersion(): bool { + return $this->equals(self::getHostVersion()); + } + + /** + * Check whether this PHP version supports the given builtin type. Type name must be lowercase. + */ + public function supportsBuiltinType(string $type): bool { + $minVersion = self::BUILTIN_TYPE_VERSIONS[$type] ?? null; + return $minVersion !== null && $this->id >= $minVersion; + } + + /** + * Whether this version supports [] array literals. + */ + public function supportsShortArraySyntax(): bool { + return $this->id >= 50400; + } + + /** + * Whether this version supports [] for destructuring. + */ + public function supportsShortArrayDestructuring(): bool { + return $this->id >= 70100; + } + + /** + * Whether this version supports flexible heredoc/nowdoc. + */ + public function supportsFlexibleHeredoc(): bool { + return $this->id >= 70300; + } + + /** + * Whether this version supports trailing commas in parameter lists. + */ + public function supportsTrailingCommaInParamList(): bool { + return $this->id >= 80000; + } + + /** + * Whether this version allows "$var =& new Obj". + */ + public function allowsAssignNewByReference(): bool { + return $this->id < 70000; + } + + /** + * Whether this version allows invalid octals like "08". + */ + public function allowsInvalidOctals(): bool { + return $this->id < 70000; + } + + /** + * Whether this version allows DEL (\x7f) to occur in identifiers. + */ + public function allowsDelInIdentifiers(): bool { + return $this->id < 70100; + } + + /** + * Whether this version supports yield in expression context without parentheses. + */ + public function supportsYieldWithoutParentheses(): bool { + return $this->id >= 70000; + } + + /** + * Whether this version supports unicode escape sequences in strings. + */ + public function supportsUnicodeEscapes(): bool { + return $this->id >= 70000; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/PrettyPrinter.php b/vendor/nikic/php-parser/lib/PhpParser/PrettyPrinter.php new file mode 100644 index 00000000..892c686e --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/PrettyPrinter.php @@ -0,0 +1,51 @@ +pAttrGroups($node->attrGroups, true) + . $this->pModifiers($node->flags) + . ($node->type ? $this->p($node->type) . ' ' : '') + . ($node->byRef ? '&' : '') + . ($node->variadic ? '...' : '') + . $this->p($node->var) + . ($node->default ? ' = ' . $this->p($node->default) : ''); + } + + protected function pArg(Node\Arg $node): string { + return ($node->name ? $node->name->toString() . ': ' : '') + . ($node->byRef ? '&' : '') . ($node->unpack ? '...' : '') + . $this->p($node->value); + } + + protected function pVariadicPlaceholder(Node\VariadicPlaceholder $node): string { + return '...'; + } + + protected function pConst(Node\Const_ $node): string { + return $node->name . ' = ' . $this->p($node->value); + } + + protected function pNullableType(Node\NullableType $node): string { + return '?' . $this->p($node->type); + } + + protected function pUnionType(Node\UnionType $node): string { + $types = []; + foreach ($node->types as $typeNode) { + if ($typeNode instanceof Node\IntersectionType) { + $types[] = '('. $this->p($typeNode) . ')'; + continue; + } + $types[] = $this->p($typeNode); + } + return implode('|', $types); + } + + protected function pIntersectionType(Node\IntersectionType $node): string { + return $this->pImplode($node->types, '&'); + } + + protected function pIdentifier(Node\Identifier $node): string { + return $node->name; + } + + protected function pVarLikeIdentifier(Node\VarLikeIdentifier $node): string { + return '$' . $node->name; + } + + protected function pAttribute(Node\Attribute $node): string { + return $this->p($node->name) + . ($node->args ? '(' . $this->pCommaSeparated($node->args) . ')' : ''); + } + + protected function pAttributeGroup(Node\AttributeGroup $node): string { + return '#[' . $this->pCommaSeparated($node->attrs) . ']'; + } + + // Names + + protected function pName(Name $node): string { + return $node->name; + } + + protected function pName_FullyQualified(Name\FullyQualified $node): string { + return '\\' . $node->name; + } + + protected function pName_Relative(Name\Relative $node): string { + return 'namespace\\' . $node->name; + } + + // Magic Constants + + protected function pScalar_MagicConst_Class(MagicConst\Class_ $node): string { + return '__CLASS__'; + } + + protected function pScalar_MagicConst_Dir(MagicConst\Dir $node): string { + return '__DIR__'; + } + + protected function pScalar_MagicConst_File(MagicConst\File $node): string { + return '__FILE__'; + } + + protected function pScalar_MagicConst_Function(MagicConst\Function_ $node): string { + return '__FUNCTION__'; + } + + protected function pScalar_MagicConst_Line(MagicConst\Line $node): string { + return '__LINE__'; + } + + protected function pScalar_MagicConst_Method(MagicConst\Method $node): string { + return '__METHOD__'; + } + + protected function pScalar_MagicConst_Namespace(MagicConst\Namespace_ $node): string { + return '__NAMESPACE__'; + } + + protected function pScalar_MagicConst_Trait(MagicConst\Trait_ $node): string { + return '__TRAIT__'; + } + + // Scalars + + private function indentString(string $str): string { + return str_replace("\n", $this->nl, $str); + } + + protected function pScalar_String(Scalar\String_ $node): string { + $kind = $node->getAttribute('kind', Scalar\String_::KIND_SINGLE_QUOTED); + switch ($kind) { + case Scalar\String_::KIND_NOWDOC: + $label = $node->getAttribute('docLabel'); + if ($label && !$this->containsEndLabel($node->value, $label)) { + $shouldIdent = $this->phpVersion->supportsFlexibleHeredoc(); + $nl = $shouldIdent ? $this->nl : $this->newline; + if ($node->value === '') { + return "<<<'$label'$nl$label{$this->docStringEndToken}"; + } + + // Make sure trailing \r is not combined with following \n into CRLF. + if ($node->value[strlen($node->value) - 1] !== "\r") { + $value = $shouldIdent ? $this->indentString($node->value) : $node->value; + return "<<<'$label'$nl$value$nl$label{$this->docStringEndToken}"; + } + } + /* break missing intentionally */ + // no break + case Scalar\String_::KIND_SINGLE_QUOTED: + return $this->pSingleQuotedString($node->value); + case Scalar\String_::KIND_HEREDOC: + $label = $node->getAttribute('docLabel'); + $escaped = $this->escapeString($node->value, null); + if ($label && !$this->containsEndLabel($escaped, $label)) { + $nl = $this->phpVersion->supportsFlexibleHeredoc() ? $this->nl : $this->newline; + if ($escaped === '') { + return "<<<$label$nl$label{$this->docStringEndToken}"; + } + + return "<<<$label$nl$escaped$nl$label{$this->docStringEndToken}"; + } + /* break missing intentionally */ + // no break + case Scalar\String_::KIND_DOUBLE_QUOTED: + return '"' . $this->escapeString($node->value, '"') . '"'; + } + throw new \Exception('Invalid string kind'); + } + + protected function pScalar_InterpolatedString(Scalar\InterpolatedString $node): string { + if ($node->getAttribute('kind') === Scalar\String_::KIND_HEREDOC) { + $label = $node->getAttribute('docLabel'); + if ($label && !$this->encapsedContainsEndLabel($node->parts, $label)) { + $nl = $this->phpVersion->supportsFlexibleHeredoc() ? $this->nl : $this->newline; + if (count($node->parts) === 1 + && $node->parts[0] instanceof Node\InterpolatedStringPart + && $node->parts[0]->value === '' + ) { + return "<<<$label$nl$label{$this->docStringEndToken}"; + } + + return "<<<$label$nl" . $this->pEncapsList($node->parts, null) + . "$nl$label{$this->docStringEndToken}"; + } + } + return '"' . $this->pEncapsList($node->parts, '"') . '"'; + } + + protected function pScalar_Int(Scalar\Int_ $node): string { + if ($node->value === -\PHP_INT_MAX - 1) { + // PHP_INT_MIN cannot be represented as a literal, + // because the sign is not part of the literal + return '(-' . \PHP_INT_MAX . '-1)'; + } + + $kind = $node->getAttribute('kind', Scalar\Int_::KIND_DEC); + if (Scalar\Int_::KIND_DEC === $kind) { + return (string) $node->value; + } + + if ($node->value < 0) { + $sign = '-'; + $str = (string) -$node->value; + } else { + $sign = ''; + $str = (string) $node->value; + } + switch ($kind) { + case Scalar\Int_::KIND_BIN: + return $sign . '0b' . base_convert($str, 10, 2); + case Scalar\Int_::KIND_OCT: + return $sign . '0' . base_convert($str, 10, 8); + case Scalar\Int_::KIND_HEX: + return $sign . '0x' . base_convert($str, 10, 16); + } + throw new \Exception('Invalid number kind'); + } + + protected function pScalar_Float(Scalar\Float_ $node): string { + if (!is_finite($node->value)) { + if ($node->value === \INF) { + return '1.0E+1000'; + } + if ($node->value === -\INF) { + return '-1.0E+1000'; + } else { + return '\NAN'; + } + } + + // Try to find a short full-precision representation + $stringValue = sprintf('%.16G', $node->value); + if ($node->value !== (float) $stringValue) { + $stringValue = sprintf('%.17G', $node->value); + } + + // %G is locale dependent and there exists no locale-independent alternative. We don't want + // mess with switching locales here, so let's assume that a comma is the only non-standard + // decimal separator we may encounter... + $stringValue = str_replace(',', '.', $stringValue); + + // ensure that number is really printed as float + return preg_match('/^-?[0-9]+$/', $stringValue) ? $stringValue . '.0' : $stringValue; + } + + // Assignments + + protected function pExpr_Assign(Expr\Assign $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Expr\Assign::class, $this->p($node->var) . ' = ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignRef(Expr\AssignRef $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Expr\AssignRef::class, $this->p($node->var) . ' =& ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_Plus(AssignOp\Plus $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\Plus::class, $this->p($node->var) . ' += ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_Minus(AssignOp\Minus $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\Minus::class, $this->p($node->var) . ' -= ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_Mul(AssignOp\Mul $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\Mul::class, $this->p($node->var) . ' *= ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_Div(AssignOp\Div $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\Div::class, $this->p($node->var) . ' /= ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_Concat(AssignOp\Concat $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\Concat::class, $this->p($node->var) . ' .= ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_Mod(AssignOp\Mod $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\Mod::class, $this->p($node->var) . ' %= ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_BitwiseAnd(AssignOp\BitwiseAnd $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\BitwiseAnd::class, $this->p($node->var) . ' &= ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_BitwiseOr(AssignOp\BitwiseOr $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\BitwiseOr::class, $this->p($node->var) . ' |= ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_BitwiseXor(AssignOp\BitwiseXor $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\BitwiseXor::class, $this->p($node->var) . ' ^= ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_ShiftLeft(AssignOp\ShiftLeft $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\ShiftLeft::class, $this->p($node->var) . ' <<= ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_ShiftRight(AssignOp\ShiftRight $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\ShiftRight::class, $this->p($node->var) . ' >>= ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_Pow(AssignOp\Pow $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\Pow::class, $this->p($node->var) . ' **= ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_AssignOp_Coalesce(AssignOp\Coalesce $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(AssignOp\Coalesce::class, $this->p($node->var) . ' ??= ', $node->expr, $precedence, $lhsPrecedence); + } + + // Binary expressions + + protected function pExpr_BinaryOp_Plus(BinaryOp\Plus $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Plus::class, $node->left, ' + ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Minus(BinaryOp\Minus $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Minus::class, $node->left, ' - ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Mul(BinaryOp\Mul $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Mul::class, $node->left, ' * ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Div(BinaryOp\Div $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Div::class, $node->left, ' / ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Concat(BinaryOp\Concat $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Concat::class, $node->left, ' . ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Mod(BinaryOp\Mod $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Mod::class, $node->left, ' % ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_BooleanAnd(BinaryOp\BooleanAnd $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\BooleanAnd::class, $node->left, ' && ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_BooleanOr(BinaryOp\BooleanOr $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\BooleanOr::class, $node->left, ' || ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_BitwiseAnd(BinaryOp\BitwiseAnd $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\BitwiseAnd::class, $node->left, ' & ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_BitwiseOr(BinaryOp\BitwiseOr $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\BitwiseOr::class, $node->left, ' | ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_BitwiseXor(BinaryOp\BitwiseXor $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\BitwiseXor::class, $node->left, ' ^ ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_ShiftLeft(BinaryOp\ShiftLeft $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\ShiftLeft::class, $node->left, ' << ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_ShiftRight(BinaryOp\ShiftRight $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\ShiftRight::class, $node->left, ' >> ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Pow(BinaryOp\Pow $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Pow::class, $node->left, ' ** ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_LogicalAnd(BinaryOp\LogicalAnd $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\LogicalAnd::class, $node->left, ' and ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_LogicalOr(BinaryOp\LogicalOr $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\LogicalOr::class, $node->left, ' or ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_LogicalXor(BinaryOp\LogicalXor $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\LogicalXor::class, $node->left, ' xor ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Equal(BinaryOp\Equal $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Equal::class, $node->left, ' == ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_NotEqual(BinaryOp\NotEqual $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\NotEqual::class, $node->left, ' != ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Identical(BinaryOp\Identical $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Identical::class, $node->left, ' === ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_NotIdentical(BinaryOp\NotIdentical $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\NotIdentical::class, $node->left, ' !== ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Spaceship(BinaryOp\Spaceship $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Spaceship::class, $node->left, ' <=> ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Greater(BinaryOp\Greater $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Greater::class, $node->left, ' > ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_GreaterOrEqual(BinaryOp\GreaterOrEqual $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\GreaterOrEqual::class, $node->left, ' >= ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Smaller(BinaryOp\Smaller $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Smaller::class, $node->left, ' < ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_SmallerOrEqual(BinaryOp\SmallerOrEqual $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\SmallerOrEqual::class, $node->left, ' <= ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_BinaryOp_Coalesce(BinaryOp\Coalesce $node, int $precedence, int $lhsPrecedence): string { + return $this->pInfixOp(BinaryOp\Coalesce::class, $node->left, ' ?? ', $node->right, $precedence, $lhsPrecedence); + } + + protected function pExpr_Instanceof(Expr\Instanceof_ $node, int $precedence, int $lhsPrecedence): string { + return $this->pPostfixOp( + Expr\Instanceof_::class, $node->expr, + ' instanceof ' . $this->pNewOperand($node->class), + $precedence, $lhsPrecedence); + } + + // Unary expressions + + protected function pExpr_BooleanNot(Expr\BooleanNot $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Expr\BooleanNot::class, '!', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_BitwiseNot(Expr\BitwiseNot $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Expr\BitwiseNot::class, '~', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_UnaryMinus(Expr\UnaryMinus $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Expr\UnaryMinus::class, '-', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_UnaryPlus(Expr\UnaryPlus $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Expr\UnaryPlus::class, '+', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_PreInc(Expr\PreInc $node): string { + return '++' . $this->p($node->var); + } + + protected function pExpr_PreDec(Expr\PreDec $node): string { + return '--' . $this->p($node->var); + } + + protected function pExpr_PostInc(Expr\PostInc $node): string { + return $this->p($node->var) . '++'; + } + + protected function pExpr_PostDec(Expr\PostDec $node): string { + return $this->p($node->var) . '--'; + } + + protected function pExpr_ErrorSuppress(Expr\ErrorSuppress $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Expr\ErrorSuppress::class, '@', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_YieldFrom(Expr\YieldFrom $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Expr\YieldFrom::class, 'yield from ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_Print(Expr\Print_ $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Expr\Print_::class, 'print ', $node->expr, $precedence, $lhsPrecedence); + } + + // Casts + + protected function pExpr_Cast_Int(Cast\Int_ $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Cast\Int_::class, '(int) ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_Cast_Double(Cast\Double $node, int $precedence, int $lhsPrecedence): string { + $kind = $node->getAttribute('kind', Cast\Double::KIND_DOUBLE); + if ($kind === Cast\Double::KIND_DOUBLE) { + $cast = '(double)'; + } elseif ($kind === Cast\Double::KIND_FLOAT) { + $cast = '(float)'; + } else { + assert($kind === Cast\Double::KIND_REAL); + $cast = '(real)'; + } + return $this->pPrefixOp(Cast\Double::class, $cast . ' ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_Cast_String(Cast\String_ $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Cast\String_::class, '(string) ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_Cast_Array(Cast\Array_ $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Cast\Array_::class, '(array) ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_Cast_Object(Cast\Object_ $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Cast\Object_::class, '(object) ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_Cast_Bool(Cast\Bool_ $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Cast\Bool_::class, '(bool) ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_Cast_Unset(Cast\Unset_ $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Cast\Unset_::class, '(unset) ', $node->expr, $precedence, $lhsPrecedence); + } + + // Function calls and similar constructs + + protected function pExpr_FuncCall(Expr\FuncCall $node): string { + return $this->pCallLhs($node->name) + . '(' . $this->pMaybeMultiline($node->args) . ')'; + } + + protected function pExpr_MethodCall(Expr\MethodCall $node): string { + return $this->pDereferenceLhs($node->var) . '->' . $this->pObjectProperty($node->name) + . '(' . $this->pMaybeMultiline($node->args) . ')'; + } + + protected function pExpr_NullsafeMethodCall(Expr\NullsafeMethodCall $node): string { + return $this->pDereferenceLhs($node->var) . '?->' . $this->pObjectProperty($node->name) + . '(' . $this->pMaybeMultiline($node->args) . ')'; + } + + protected function pExpr_StaticCall(Expr\StaticCall $node): string { + return $this->pStaticDereferenceLhs($node->class) . '::' + . ($node->name instanceof Expr + ? ($node->name instanceof Expr\Variable + ? $this->p($node->name) + : '{' . $this->p($node->name) . '}') + : $node->name) + . '(' . $this->pMaybeMultiline($node->args) . ')'; + } + + protected function pExpr_Empty(Expr\Empty_ $node): string { + return 'empty(' . $this->p($node->expr) . ')'; + } + + protected function pExpr_Isset(Expr\Isset_ $node): string { + return 'isset(' . $this->pCommaSeparated($node->vars) . ')'; + } + + protected function pExpr_Eval(Expr\Eval_ $node): string { + return 'eval(' . $this->p($node->expr) . ')'; + } + + protected function pExpr_Include(Expr\Include_ $node, int $precedence, int $lhsPrecedence): string { + static $map = [ + Expr\Include_::TYPE_INCLUDE => 'include', + Expr\Include_::TYPE_INCLUDE_ONCE => 'include_once', + Expr\Include_::TYPE_REQUIRE => 'require', + Expr\Include_::TYPE_REQUIRE_ONCE => 'require_once', + ]; + + return $this->pPrefixOp(Expr\Include_::class, $map[$node->type] . ' ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_List(Expr\List_ $node): string { + $syntax = $node->getAttribute('kind', + $this->phpVersion->supportsShortArrayDestructuring() ? Expr\List_::KIND_ARRAY : Expr\List_::KIND_LIST); + if ($syntax === Expr\List_::KIND_ARRAY) { + return '[' . $this->pMaybeMultiline($node->items, true) . ']'; + } else { + return 'list(' . $this->pMaybeMultiline($node->items, true) . ')'; + } + } + + // Other + + protected function pExpr_Error(Expr\Error $node): string { + throw new \LogicException('Cannot pretty-print AST with Error nodes'); + } + + protected function pExpr_Variable(Expr\Variable $node): string { + if ($node->name instanceof Expr) { + return '${' . $this->p($node->name) . '}'; + } else { + return '$' . $node->name; + } + } + + protected function pExpr_Array(Expr\Array_ $node): string { + $syntax = $node->getAttribute('kind', + $this->shortArraySyntax ? Expr\Array_::KIND_SHORT : Expr\Array_::KIND_LONG); + if ($syntax === Expr\Array_::KIND_SHORT) { + return '[' . $this->pMaybeMultiline($node->items, true) . ']'; + } else { + return 'array(' . $this->pMaybeMultiline($node->items, true) . ')'; + } + } + + protected function pKey(?Node $node): string { + if ($node === null) { + return ''; + } + + // => is not really an operator and does not typically participate in precedence resolution. + // However, there is an exception if yield expressions with keys are involved: + // [yield $a => $b] is interpreted as [(yield $a => $b)], so we need to ensure that + // [(yield $a) => $b] is printed with parentheses. We approximate this by lowering the LHS + // precedence to that of yield (which will also print unnecessary parentheses for rare low + // precedence unary operators like include). + $yieldPrecedence = $this->precedenceMap[Expr\Yield_::class][0]; + return $this->p($node, self::MAX_PRECEDENCE, $yieldPrecedence) . ' => '; + } + + protected function pArrayItem(Node\ArrayItem $node): string { + return $this->pKey($node->key) + . ($node->byRef ? '&' : '') + . ($node->unpack ? '...' : '') + . $this->p($node->value); + } + + protected function pExpr_ArrayDimFetch(Expr\ArrayDimFetch $node): string { + return $this->pDereferenceLhs($node->var) + . '[' . (null !== $node->dim ? $this->p($node->dim) : '') . ']'; + } + + protected function pExpr_ConstFetch(Expr\ConstFetch $node): string { + return $this->p($node->name); + } + + protected function pExpr_ClassConstFetch(Expr\ClassConstFetch $node): string { + return $this->pStaticDereferenceLhs($node->class) . '::' . $this->pObjectProperty($node->name); + } + + protected function pExpr_PropertyFetch(Expr\PropertyFetch $node): string { + return $this->pDereferenceLhs($node->var) . '->' . $this->pObjectProperty($node->name); + } + + protected function pExpr_NullsafePropertyFetch(Expr\NullsafePropertyFetch $node): string { + return $this->pDereferenceLhs($node->var) . '?->' . $this->pObjectProperty($node->name); + } + + protected function pExpr_StaticPropertyFetch(Expr\StaticPropertyFetch $node): string { + return $this->pStaticDereferenceLhs($node->class) . '::$' . $this->pObjectProperty($node->name); + } + + protected function pExpr_ShellExec(Expr\ShellExec $node): string { + return '`' . $this->pEncapsList($node->parts, '`') . '`'; + } + + protected function pExpr_Closure(Expr\Closure $node): string { + return $this->pAttrGroups($node->attrGroups, true) + . $this->pStatic($node->static) + . 'function ' . ($node->byRef ? '&' : '') + . '(' . $this->pMaybeMultiline($node->params, $this->phpVersion->supportsTrailingCommaInParamList()) . ')' + . (!empty($node->uses) ? ' use (' . $this->pCommaSeparated($node->uses) . ')' : '') + . (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '') + . ' {' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pExpr_Match(Expr\Match_ $node): string { + return 'match (' . $this->p($node->cond) . ') {' + . $this->pCommaSeparatedMultiline($node->arms, true) + . $this->nl + . '}'; + } + + protected function pMatchArm(Node\MatchArm $node): string { + $result = ''; + if ($node->conds) { + for ($i = 0, $c = \count($node->conds); $i + 1 < $c; $i++) { + $result .= $this->p($node->conds[$i]) . ', '; + } + $result .= $this->pKey($node->conds[$i]); + } else { + $result = 'default => '; + } + return $result . $this->p($node->body); + } + + protected function pExpr_ArrowFunction(Expr\ArrowFunction $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp( + Expr\ArrowFunction::class, + $this->pAttrGroups($node->attrGroups, true) + . $this->pStatic($node->static) + . 'fn' . ($node->byRef ? '&' : '') + . '(' . $this->pMaybeMultiline($node->params, $this->phpVersion->supportsTrailingCommaInParamList()) . ')' + . (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '') + . ' => ', + $node->expr, $precedence, $lhsPrecedence); + } + + protected function pClosureUse(Node\ClosureUse $node): string { + return ($node->byRef ? '&' : '') . $this->p($node->var); + } + + protected function pExpr_New(Expr\New_ $node): string { + if ($node->class instanceof Stmt\Class_) { + $args = $node->args ? '(' . $this->pMaybeMultiline($node->args) . ')' : ''; + return 'new ' . $this->pClassCommon($node->class, $args); + } + return 'new ' . $this->pNewOperand($node->class) + . '(' . $this->pMaybeMultiline($node->args) . ')'; + } + + protected function pExpr_Clone(Expr\Clone_ $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Expr\Clone_::class, 'clone ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_Ternary(Expr\Ternary $node, int $precedence, int $lhsPrecedence): string { + // a bit of cheating: we treat the ternary as a binary op where the ?...: part is the operator. + // this is okay because the part between ? and : never needs parentheses. + return $this->pInfixOp(Expr\Ternary::class, + $node->cond, ' ?' . (null !== $node->if ? ' ' . $this->p($node->if) . ' ' : '') . ': ', $node->else, + $precedence, $lhsPrecedence + ); + } + + protected function pExpr_Exit(Expr\Exit_ $node): string { + $kind = $node->getAttribute('kind', Expr\Exit_::KIND_DIE); + return ($kind === Expr\Exit_::KIND_EXIT ? 'exit' : 'die') + . (null !== $node->expr ? '(' . $this->p($node->expr) . ')' : ''); + } + + protected function pExpr_Throw(Expr\Throw_ $node, int $precedence, int $lhsPrecedence): string { + return $this->pPrefixOp(Expr\Throw_::class, 'throw ', $node->expr, $precedence, $lhsPrecedence); + } + + protected function pExpr_Yield(Expr\Yield_ $node, int $precedence, int $lhsPrecedence): string { + if ($node->value === null) { + $opPrecedence = $this->precedenceMap[Expr\Yield_::class][0]; + return $opPrecedence >= $lhsPrecedence ? '(yield)' : 'yield'; + } else { + if (!$this->phpVersion->supportsYieldWithoutParentheses()) { + return '(yield ' . $this->pKey($node->key) . $this->p($node->value) . ')'; + } + return $this->pPrefixOp( + Expr\Yield_::class, 'yield ' . $this->pKey($node->key), + $node->value, $precedence, $lhsPrecedence); + } + } + + // Declarations + + protected function pStmt_Namespace(Stmt\Namespace_ $node): string { + if ($this->canUseSemicolonNamespaces) { + return 'namespace ' . $this->p($node->name) . ';' + . $this->nl . $this->pStmts($node->stmts, false); + } else { + return 'namespace' . (null !== $node->name ? ' ' . $this->p($node->name) : '') + . ' {' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + } + + protected function pStmt_Use(Stmt\Use_ $node): string { + return 'use ' . $this->pUseType($node->type) + . $this->pCommaSeparated($node->uses) . ';'; + } + + protected function pStmt_GroupUse(Stmt\GroupUse $node): string { + return 'use ' . $this->pUseType($node->type) . $this->pName($node->prefix) + . '\{' . $this->pCommaSeparated($node->uses) . '};'; + } + + protected function pUseItem(Node\UseItem $node): string { + return $this->pUseType($node->type) . $this->p($node->name) + . (null !== $node->alias ? ' as ' . $node->alias : ''); + } + + protected function pUseType(int $type): string { + return $type === Stmt\Use_::TYPE_FUNCTION ? 'function ' + : ($type === Stmt\Use_::TYPE_CONSTANT ? 'const ' : ''); + } + + protected function pStmt_Interface(Stmt\Interface_ $node): string { + return $this->pAttrGroups($node->attrGroups) + . 'interface ' . $node->name + . (!empty($node->extends) ? ' extends ' . $this->pCommaSeparated($node->extends) : '') + . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_Enum(Stmt\Enum_ $node): string { + return $this->pAttrGroups($node->attrGroups) + . 'enum ' . $node->name + . ($node->scalarType ? ' : ' . $this->p($node->scalarType) : '') + . (!empty($node->implements) ? ' implements ' . $this->pCommaSeparated($node->implements) : '') + . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_Class(Stmt\Class_ $node): string { + return $this->pClassCommon($node, ' ' . $node->name); + } + + protected function pStmt_Trait(Stmt\Trait_ $node): string { + return $this->pAttrGroups($node->attrGroups) + . 'trait ' . $node->name + . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_EnumCase(Stmt\EnumCase $node): string { + return $this->pAttrGroups($node->attrGroups) + . 'case ' . $node->name + . ($node->expr ? ' = ' . $this->p($node->expr) : '') + . ';'; + } + + protected function pStmt_TraitUse(Stmt\TraitUse $node): string { + return 'use ' . $this->pCommaSeparated($node->traits) + . (empty($node->adaptations) + ? ';' + : ' {' . $this->pStmts($node->adaptations) . $this->nl . '}'); + } + + protected function pStmt_TraitUseAdaptation_Precedence(Stmt\TraitUseAdaptation\Precedence $node): string { + return $this->p($node->trait) . '::' . $node->method + . ' insteadof ' . $this->pCommaSeparated($node->insteadof) . ';'; + } + + protected function pStmt_TraitUseAdaptation_Alias(Stmt\TraitUseAdaptation\Alias $node): string { + return (null !== $node->trait ? $this->p($node->trait) . '::' : '') + . $node->method . ' as' + . (null !== $node->newModifier ? ' ' . rtrim($this->pModifiers($node->newModifier), ' ') : '') + . (null !== $node->newName ? ' ' . $node->newName : '') + . ';'; + } + + protected function pStmt_Property(Stmt\Property $node): string { + return $this->pAttrGroups($node->attrGroups) + . (0 === $node->flags ? 'var ' : $this->pModifiers($node->flags)) + . ($node->type ? $this->p($node->type) . ' ' : '') + . $this->pCommaSeparated($node->props) . ';'; + } + + protected function pPropertyItem(Node\PropertyItem $node): string { + return '$' . $node->name + . (null !== $node->default ? ' = ' . $this->p($node->default) : ''); + } + + protected function pStmt_ClassMethod(Stmt\ClassMethod $node): string { + return $this->pAttrGroups($node->attrGroups) + . $this->pModifiers($node->flags) + . 'function ' . ($node->byRef ? '&' : '') . $node->name + . '(' . $this->pMaybeMultiline($node->params, $this->phpVersion->supportsTrailingCommaInParamList()) . ')' + . (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '') + . (null !== $node->stmts + ? $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}' + : ';'); + } + + protected function pStmt_ClassConst(Stmt\ClassConst $node): string { + return $this->pAttrGroups($node->attrGroups) + . $this->pModifiers($node->flags) + . 'const ' + . (null !== $node->type ? $this->p($node->type) . ' ' : '') + . $this->pCommaSeparated($node->consts) . ';'; + } + + protected function pStmt_Function(Stmt\Function_ $node): string { + return $this->pAttrGroups($node->attrGroups) + . 'function ' . ($node->byRef ? '&' : '') . $node->name + . '(' . $this->pMaybeMultiline($node->params, $this->phpVersion->supportsTrailingCommaInParamList()) . ')' + . (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '') + . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_Const(Stmt\Const_ $node): string { + return 'const ' . $this->pCommaSeparated($node->consts) . ';'; + } + + protected function pStmt_Declare(Stmt\Declare_ $node): string { + return 'declare (' . $this->pCommaSeparated($node->declares) . ')' + . (null !== $node->stmts ? ' {' . $this->pStmts($node->stmts) . $this->nl . '}' : ';'); + } + + protected function pDeclareItem(Node\DeclareItem $node): string { + return $node->key . '=' . $this->p($node->value); + } + + // Control flow + + protected function pStmt_If(Stmt\If_ $node): string { + return 'if (' . $this->p($node->cond) . ') {' + . $this->pStmts($node->stmts) . $this->nl . '}' + . ($node->elseifs ? ' ' . $this->pImplode($node->elseifs, ' ') : '') + . (null !== $node->else ? ' ' . $this->p($node->else) : ''); + } + + protected function pStmt_ElseIf(Stmt\ElseIf_ $node): string { + return 'elseif (' . $this->p($node->cond) . ') {' + . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_Else(Stmt\Else_ $node): string { + if (\count($node->stmts) === 1 && $node->stmts[0] instanceof Stmt\If_) { + // Print as "else if" rather than "else { if }" + return 'else ' . $this->p($node->stmts[0]); + } + return 'else {' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_For(Stmt\For_ $node): string { + return 'for (' + . $this->pCommaSeparated($node->init) . ';' . (!empty($node->cond) ? ' ' : '') + . $this->pCommaSeparated($node->cond) . ';' . (!empty($node->loop) ? ' ' : '') + . $this->pCommaSeparated($node->loop) + . ') {' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_Foreach(Stmt\Foreach_ $node): string { + return 'foreach (' . $this->p($node->expr) . ' as ' + . (null !== $node->keyVar ? $this->p($node->keyVar) . ' => ' : '') + . ($node->byRef ? '&' : '') . $this->p($node->valueVar) . ') {' + . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_While(Stmt\While_ $node): string { + return 'while (' . $this->p($node->cond) . ') {' + . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_Do(Stmt\Do_ $node): string { + return 'do {' . $this->pStmts($node->stmts) . $this->nl + . '} while (' . $this->p($node->cond) . ');'; + } + + protected function pStmt_Switch(Stmt\Switch_ $node): string { + return 'switch (' . $this->p($node->cond) . ') {' + . $this->pStmts($node->cases) . $this->nl . '}'; + } + + protected function pStmt_Case(Stmt\Case_ $node): string { + return (null !== $node->cond ? 'case ' . $this->p($node->cond) : 'default') . ':' + . $this->pStmts($node->stmts); + } + + protected function pStmt_TryCatch(Stmt\TryCatch $node): string { + return 'try {' . $this->pStmts($node->stmts) . $this->nl . '}' + . ($node->catches ? ' ' . $this->pImplode($node->catches, ' ') : '') + . ($node->finally !== null ? ' ' . $this->p($node->finally) : ''); + } + + protected function pStmt_Catch(Stmt\Catch_ $node): string { + return 'catch (' . $this->pImplode($node->types, '|') + . ($node->var !== null ? ' ' . $this->p($node->var) : '') + . ') {' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_Finally(Stmt\Finally_ $node): string { + return 'finally {' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pStmt_Break(Stmt\Break_ $node): string { + return 'break' . ($node->num !== null ? ' ' . $this->p($node->num) : '') . ';'; + } + + protected function pStmt_Continue(Stmt\Continue_ $node): string { + return 'continue' . ($node->num !== null ? ' ' . $this->p($node->num) : '') . ';'; + } + + protected function pStmt_Return(Stmt\Return_ $node): string { + return 'return' . (null !== $node->expr ? ' ' . $this->p($node->expr) : '') . ';'; + } + + protected function pStmt_Label(Stmt\Label $node): string { + return $node->name . ':'; + } + + protected function pStmt_Goto(Stmt\Goto_ $node): string { + return 'goto ' . $node->name . ';'; + } + + // Other + + protected function pStmt_Expression(Stmt\Expression $node): string { + return $this->p($node->expr) . ';'; + } + + protected function pStmt_Echo(Stmt\Echo_ $node): string { + return 'echo ' . $this->pCommaSeparated($node->exprs) . ';'; + } + + protected function pStmt_Static(Stmt\Static_ $node): string { + return 'static ' . $this->pCommaSeparated($node->vars) . ';'; + } + + protected function pStmt_Global(Stmt\Global_ $node): string { + return 'global ' . $this->pCommaSeparated($node->vars) . ';'; + } + + protected function pStaticVar(Node\StaticVar $node): string { + return $this->p($node->var) + . (null !== $node->default ? ' = ' . $this->p($node->default) : ''); + } + + protected function pStmt_Unset(Stmt\Unset_ $node): string { + return 'unset(' . $this->pCommaSeparated($node->vars) . ');'; + } + + protected function pStmt_InlineHTML(Stmt\InlineHTML $node): string { + $newline = $node->getAttribute('hasLeadingNewline', true) ? $this->newline : ''; + return '?>' . $newline . $node->value . 'remaining; + } + + protected function pStmt_Nop(Stmt\Nop $node): string { + return ''; + } + + protected function pStmt_Block(Stmt\Block $node): string { + return '{' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + // Helpers + + protected function pClassCommon(Stmt\Class_ $node, string $afterClassToken): string { + return $this->pAttrGroups($node->attrGroups, $node->name === null) + . $this->pModifiers($node->flags) + . 'class' . $afterClassToken + . (null !== $node->extends ? ' extends ' . $this->p($node->extends) : '') + . (!empty($node->implements) ? ' implements ' . $this->pCommaSeparated($node->implements) : '') + . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; + } + + protected function pObjectProperty(Node $node): string { + if ($node instanceof Expr) { + return '{' . $this->p($node) . '}'; + } else { + assert($node instanceof Node\Identifier); + return $node->name; + } + } + + /** @param (Expr|Node\InterpolatedStringPart)[] $encapsList */ + protected function pEncapsList(array $encapsList, ?string $quote): string { + $return = ''; + foreach ($encapsList as $element) { + if ($element instanceof Node\InterpolatedStringPart) { + $return .= $this->escapeString($element->value, $quote); + } else { + $return .= '{' . $this->p($element) . '}'; + } + } + + return $return; + } + + protected function pSingleQuotedString(string $string): string { + // It is idiomatic to only escape backslashes when necessary, i.e. when followed by ', \ or + // the end of the string ('Foo\Bar' instead of 'Foo\\Bar'). However, we also don't want to + // produce an odd number of backslashes, so '\\\\a' should not get rendered as '\\\a', even + // though that would be legal. + $regex = '/\'|\\\\(?=[\'\\\\]|$)|(?<=\\\\)\\\\/'; + return '\'' . preg_replace($regex, '\\\\$0', $string) . '\''; + } + + protected function escapeString(string $string, ?string $quote): string { + if (null === $quote) { + // For doc strings, don't escape newlines + $escaped = addcslashes($string, "\t\f\v$\\"); + // But do escape isolated \r. Combined with the terminating newline, it might get + // interpreted as \r\n and dropped from the string contents. + $escaped = preg_replace('/\r(?!\n)/', '\\r', $escaped); + if ($this->phpVersion->supportsFlexibleHeredoc()) { + $escaped = $this->indentString($escaped); + } + } else { + $escaped = addcslashes($string, "\n\r\t\f\v$" . $quote . "\\"); + } + + // Escape control characters and non-UTF-8 characters. + // Regex based on https://stackoverflow.com/a/11709412/385378. + $regex = '/( + [\x00-\x08\x0E-\x1F] # Control characters + | [\xC0-\xC1] # Invalid UTF-8 Bytes + | [\xF5-\xFF] # Invalid UTF-8 Bytes + | \xE0(?=[\x80-\x9F]) # Overlong encoding of prior code point + | \xF0(?=[\x80-\x8F]) # Overlong encoding of prior code point + | [\xC2-\xDF](?![\x80-\xBF]) # Invalid UTF-8 Sequence Start + | [\xE0-\xEF](?![\x80-\xBF]{2}) # Invalid UTF-8 Sequence Start + | [\xF0-\xF4](?![\x80-\xBF]{3}) # Invalid UTF-8 Sequence Start + | (?<=[\x00-\x7F\xF5-\xFF])[\x80-\xBF] # Invalid UTF-8 Sequence Middle + | (? $part) { + if ($part instanceof Node\InterpolatedStringPart + && $this->containsEndLabel($this->escapeString($part->value, null), $label, $i === 0) + ) { + return true; + } + } + return false; + } + + protected function pDereferenceLhs(Node $node): string { + if (!$this->dereferenceLhsRequiresParens($node)) { + return $this->p($node); + } else { + return '(' . $this->p($node) . ')'; + } + } + + protected function pStaticDereferenceLhs(Node $node): string { + if (!$this->staticDereferenceLhsRequiresParens($node)) { + return $this->p($node); + } else { + return '(' . $this->p($node) . ')'; + } + } + + protected function pCallLhs(Node $node): string { + if (!$this->callLhsRequiresParens($node)) { + return $this->p($node); + } else { + return '(' . $this->p($node) . ')'; + } + } + + protected function pNewOperand(Node $node): string { + if (!$this->newOperandRequiresParens($node)) { + return $this->p($node); + } else { + return '(' . $this->p($node) . ')'; + } + } + + /** + * @param Node[] $nodes + */ + protected function hasNodeWithComments(array $nodes): bool { + foreach ($nodes as $node) { + if ($node && $node->getComments()) { + return true; + } + } + return false; + } + + /** @param Node[] $nodes */ + protected function pMaybeMultiline(array $nodes, bool $trailingComma = false): string { + if (!$this->hasNodeWithComments($nodes)) { + return $this->pCommaSeparated($nodes); + } else { + return $this->pCommaSeparatedMultiline($nodes, $trailingComma) . $this->nl; + } + } + + /** @param Node\AttributeGroup[] $nodes */ + protected function pAttrGroups(array $nodes, bool $inline = false): string { + $result = ''; + $sep = $inline ? ' ' : $this->nl; + foreach ($nodes as $node) { + $result .= $this->p($node) . $sep; + } + + return $result; + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/PrettyPrinterAbstract.php b/vendor/nikic/php-parser/lib/PhpParser/PrettyPrinterAbstract.php new file mode 100644 index 00000000..8303c427 --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/PrettyPrinterAbstract.php @@ -0,0 +1,1655 @@ + */ + protected array $precedenceMap = [ + // [precedence, precedenceLHS, precedenceRHS] + // Where the latter two are the precedences to use for the LHS and RHS of a binary operator, + // where 1 is added to one of the sides depending on associativity. This information is not + // used for unary operators and set to -1. + Expr\Clone_::class => [-10, 0, 1], + BinaryOp\Pow::class => [ 0, 0, 1], + Expr\BitwiseNot::class => [ 10, -1, -1], + Expr\UnaryPlus::class => [ 10, -1, -1], + Expr\UnaryMinus::class => [ 10, -1, -1], + Cast\Int_::class => [ 10, -1, -1], + Cast\Double::class => [ 10, -1, -1], + Cast\String_::class => [ 10, -1, -1], + Cast\Array_::class => [ 10, -1, -1], + Cast\Object_::class => [ 10, -1, -1], + Cast\Bool_::class => [ 10, -1, -1], + Cast\Unset_::class => [ 10, -1, -1], + Expr\ErrorSuppress::class => [ 10, -1, -1], + Expr\Instanceof_::class => [ 20, -1, -1], + Expr\BooleanNot::class => [ 30, -1, -1], + BinaryOp\Mul::class => [ 40, 41, 40], + BinaryOp\Div::class => [ 40, 41, 40], + BinaryOp\Mod::class => [ 40, 41, 40], + BinaryOp\Plus::class => [ 50, 51, 50], + BinaryOp\Minus::class => [ 50, 51, 50], + BinaryOp\Concat::class => [ 50, 51, 50], + BinaryOp\ShiftLeft::class => [ 60, 61, 60], + BinaryOp\ShiftRight::class => [ 60, 61, 60], + BinaryOp\Smaller::class => [ 70, 70, 70], + BinaryOp\SmallerOrEqual::class => [ 70, 70, 70], + BinaryOp\Greater::class => [ 70, 70, 70], + BinaryOp\GreaterOrEqual::class => [ 70, 70, 70], + BinaryOp\Equal::class => [ 80, 80, 80], + BinaryOp\NotEqual::class => [ 80, 80, 80], + BinaryOp\Identical::class => [ 80, 80, 80], + BinaryOp\NotIdentical::class => [ 80, 80, 80], + BinaryOp\Spaceship::class => [ 80, 80, 80], + BinaryOp\BitwiseAnd::class => [ 90, 91, 90], + BinaryOp\BitwiseXor::class => [100, 101, 100], + BinaryOp\BitwiseOr::class => [110, 111, 110], + BinaryOp\BooleanAnd::class => [120, 121, 120], + BinaryOp\BooleanOr::class => [130, 131, 130], + BinaryOp\Coalesce::class => [140, 140, 141], + Expr\Ternary::class => [150, -1, -1], + Expr\Assign::class => [160, -1, -1], + Expr\AssignRef::class => [160, -1, -1], + AssignOp\Plus::class => [160, -1, -1], + AssignOp\Minus::class => [160, -1, -1], + AssignOp\Mul::class => [160, -1, -1], + AssignOp\Div::class => [160, -1, -1], + AssignOp\Concat::class => [160, -1, -1], + AssignOp\Mod::class => [160, -1, -1], + AssignOp\BitwiseAnd::class => [160, -1, -1], + AssignOp\BitwiseOr::class => [160, -1, -1], + AssignOp\BitwiseXor::class => [160, -1, -1], + AssignOp\ShiftLeft::class => [160, -1, -1], + AssignOp\ShiftRight::class => [160, -1, -1], + AssignOp\Pow::class => [160, -1, -1], + AssignOp\Coalesce::class => [160, -1, -1], + Expr\YieldFrom::class => [170, -1, -1], + Expr\Yield_::class => [175, -1, -1], + Expr\Print_::class => [180, -1, -1], + BinaryOp\LogicalAnd::class => [190, 191, 190], + BinaryOp\LogicalXor::class => [200, 201, 200], + BinaryOp\LogicalOr::class => [210, 211, 210], + Expr\Include_::class => [220, -1, -1], + Expr\ArrowFunction::class => [230, -1, -1], + Expr\Throw_::class => [240, -1, -1], + ]; + + /** @var int Current indentation level. */ + protected int $indentLevel; + /** @var string Newline style. Does not include current indentation. */ + protected string $newline; + /** @var string Newline including current indentation. */ + protected string $nl; + /** @var string|null Token placed at end of doc string to ensure it is followed by a newline. + * Null if flexible doc strings are used. */ + protected ?string $docStringEndToken; + /** @var bool Whether semicolon namespaces can be used (i.e. no global namespace is used) */ + protected bool $canUseSemicolonNamespaces; + /** @var bool Whether to use short array syntax if the node specifies no preference */ + protected bool $shortArraySyntax; + /** @var PhpVersion PHP version to target */ + protected PhpVersion $phpVersion; + + /** @var TokenStream|null Original tokens for use in format-preserving pretty print */ + protected ?TokenStream $origTokens; + /** @var Internal\Differ Differ for node lists */ + protected Differ $nodeListDiffer; + /** @var array Map determining whether a certain character is a label character */ + protected array $labelCharMap; + /** + * @var array> Map from token classes and subnode names to FIXUP_* constants. + * This is used during format-preserving prints to place additional parens/braces if necessary. + */ + protected array $fixupMap; + /** + * @var array Map from "{$node->getType()}->{$subNode}" + * to ['left' => $l, 'right' => $r], where $l and $r specify the token type that needs to be stripped + * when removing this node. + */ + protected array $removalMap; + /** + * @var array Map from + * "{$node->getType()}->{$subNode}" to [$find, $beforeToken, $extraLeft, $extraRight]. + * $find is an optional token after which the insertion occurs. $extraLeft/Right + * are optionally added before/after the main insertions. + */ + protected array $insertionMap; + /** + * @var array Map From "{$class}->{$subNode}" to string that should be inserted + * between elements of this list subnode. + */ + protected array $listInsertionMap; + + /** + * @var array + */ + protected array $emptyListInsertionMap; + /** @var array Map from "{$class}->{$subNode}" to [$printFn, $token] + * where $printFn is the function to print the modifiers and $token is the token before which + * the modifiers should be reprinted. */ + protected array $modifierChangeMap; + + /** + * Creates a pretty printer instance using the given options. + * + * Supported options: + * * PhpVersion $phpVersion: The PHP version to target (default to PHP 7.4). This option + * controls compatibility of the generated code with older PHP + * versions in cases where a simple stylistic choice exists (e.g. + * array() vs []). It is safe to pretty-print an AST for a newer + * PHP version while specifying an older target (but the result will + * of course not be compatible with the older version in that case). + * * string $newline: The newline style to use. Should be "\n" (default) or "\r\n". + * * bool $shortArraySyntax: Whether to use [] instead of array() as the default array + * syntax, if the node does not specify a format. Defaults to whether + * the phpVersion support short array syntax. + * + * @param array{ + * phpVersion?: PhpVersion, newline?: string, shortArraySyntax?: bool + * } $options Dictionary of formatting options + */ + public function __construct(array $options = []) { + $this->phpVersion = $options['phpVersion'] ?? PhpVersion::fromComponents(7, 4); + + $this->newline = $options['newline'] ?? "\n"; + if ($this->newline !== "\n" && $this->newline != "\r\n") { + throw new \LogicException('Option "newline" must be one of "\n" or "\r\n"'); + } + + $this->shortArraySyntax = + $options['shortArraySyntax'] ?? $this->phpVersion->supportsShortArraySyntax(); + $this->docStringEndToken = + $this->phpVersion->supportsFlexibleHeredoc() ? null : '_DOC_STRING_END_' . mt_rand(); + } + + /** + * Reset pretty printing state. + */ + protected function resetState(): void { + $this->indentLevel = 0; + $this->nl = $this->newline; + $this->origTokens = null; + } + + /** + * Set indentation level + * + * @param int $level Level in number of spaces + */ + protected function setIndentLevel(int $level): void { + $this->indentLevel = $level; + $this->nl = $this->newline . \str_repeat(' ', $level); + } + + /** + * Increase indentation level. + */ + protected function indent(): void { + $this->indentLevel += 4; + $this->nl .= ' '; + } + + /** + * Decrease indentation level. + */ + protected function outdent(): void { + assert($this->indentLevel >= 4); + $this->indentLevel -= 4; + $this->nl = $this->newline . str_repeat(' ', $this->indentLevel); + } + + /** + * Pretty prints an array of statements. + * + * @param Node[] $stmts Array of statements + * + * @return string Pretty printed statements + */ + public function prettyPrint(array $stmts): string { + $this->resetState(); + $this->preprocessNodes($stmts); + + return ltrim($this->handleMagicTokens($this->pStmts($stmts, false))); + } + + /** + * Pretty prints an expression. + * + * @param Expr $node Expression node + * + * @return string Pretty printed node + */ + public function prettyPrintExpr(Expr $node): string { + $this->resetState(); + return $this->handleMagicTokens($this->p($node)); + } + + /** + * Pretty prints a file of statements (includes the opening newline . $this->newline; + } + + $p = "newline . $this->newline . $this->prettyPrint($stmts); + + if ($stmts[0] instanceof Stmt\InlineHTML) { + $p = preg_replace('/^<\?php\s+\?>\r?\n?/', '', $p); + } + if ($stmts[count($stmts) - 1] instanceof Stmt\InlineHTML) { + $p = preg_replace('/<\?php$/', '', rtrim($p)); + } + + return $p; + } + + /** + * Preprocesses the top-level nodes to initialize pretty printer state. + * + * @param Node[] $nodes Array of nodes + */ + protected function preprocessNodes(array $nodes): void { + /* We can use semicolon-namespaces unless there is a global namespace declaration */ + $this->canUseSemicolonNamespaces = true; + foreach ($nodes as $node) { + if ($node instanceof Stmt\Namespace_ && null === $node->name) { + $this->canUseSemicolonNamespaces = false; + break; + } + } + } + + /** + * Handles (and removes) doc-string-end tokens. + */ + protected function handleMagicTokens(string $str): string { + if ($this->docStringEndToken !== null) { + // Replace doc-string-end tokens with nothing or a newline + $str = str_replace( + $this->docStringEndToken . ';' . $this->newline, + ';' . $this->newline, + $str); + $str = str_replace($this->docStringEndToken, $this->newline, $str); + } + + return $str; + } + + /** + * Pretty prints an array of nodes (statements) and indents them optionally. + * + * @param Node[] $nodes Array of nodes + * @param bool $indent Whether to indent the printed nodes + * + * @return string Pretty printed statements + */ + protected function pStmts(array $nodes, bool $indent = true): string { + if ($indent) { + $this->indent(); + } + + $result = ''; + foreach ($nodes as $node) { + $comments = $node->getComments(); + if ($comments) { + $result .= $this->nl . $this->pComments($comments); + if ($node instanceof Stmt\Nop) { + continue; + } + } + + $result .= $this->nl . $this->p($node); + } + + if ($indent) { + $this->outdent(); + } + + return $result; + } + + /** + * Pretty-print an infix operation while taking precedence into account. + * + * @param string $class Node class of operator + * @param Node $leftNode Left-hand side node + * @param string $operatorString String representation of the operator + * @param Node $rightNode Right-hand side node + * @param int $precedence Precedence of parent operator + * @param int $lhsPrecedence Precedence for unary operator on LHS of binary operator + * + * @return string Pretty printed infix operation + */ + protected function pInfixOp( + string $class, Node $leftNode, string $operatorString, Node $rightNode, + int $precedence, int $lhsPrecedence + ): string { + list($opPrecedence, $newPrecedenceLHS, $newPrecedenceRHS) = $this->precedenceMap[$class]; + $prefix = ''; + $suffix = ''; + if ($opPrecedence >= $precedence) { + $prefix = '('; + $suffix = ')'; + $lhsPrecedence = self::MAX_PRECEDENCE; + } + return $prefix . $this->p($leftNode, $newPrecedenceLHS, $newPrecedenceLHS) + . $operatorString . $this->p($rightNode, $newPrecedenceRHS, $lhsPrecedence) . $suffix; + } + + /** + * Pretty-print a prefix operation while taking precedence into account. + * + * @param string $class Node class of operator + * @param string $operatorString String representation of the operator + * @param Node $node Node + * @param int $precedence Precedence of parent operator + * @param int $lhsPrecedence Precedence for unary operator on LHS of binary operator + * + * @return string Pretty printed prefix operation + */ + protected function pPrefixOp(string $class, string $operatorString, Node $node, int $precedence, int $lhsPrecedence): string { + $opPrecedence = $this->precedenceMap[$class][0]; + $prefix = ''; + $suffix = ''; + if ($opPrecedence >= $lhsPrecedence) { + $prefix = '('; + $suffix = ')'; + $lhsPrecedence = self::MAX_PRECEDENCE; + } + $printedArg = $this->p($node, $opPrecedence, $lhsPrecedence); + if (($operatorString === '+' && $printedArg[0] === '+') || + ($operatorString === '-' && $printedArg[0] === '-') + ) { + // Avoid printing +(+$a) as ++$a and similar. + $printedArg = '(' . $printedArg . ')'; + } + return $prefix . $operatorString . $printedArg . $suffix; + } + + /** + * Pretty-print a postfix operation while taking precedence into account. + * + * @param string $class Node class of operator + * @param string $operatorString String representation of the operator + * @param Node $node Node + * @param int $precedence Precedence of parent operator + * @param int $lhsPrecedence Precedence for unary operator on LHS of binary operator + * + * @return string Pretty printed postfix operation + */ + protected function pPostfixOp(string $class, Node $node, string $operatorString, int $precedence, int $lhsPrecedence): string { + $opPrecedence = $this->precedenceMap[$class][0]; + $prefix = ''; + $suffix = ''; + if ($opPrecedence >= $precedence) { + $prefix = '('; + $suffix = ')'; + $lhsPrecedence = self::MAX_PRECEDENCE; + } + if ($opPrecedence < $lhsPrecedence) { + $lhsPrecedence = $opPrecedence; + } + return $prefix . $this->p($node, $opPrecedence, $lhsPrecedence) . $operatorString . $suffix; + } + + /** + * Pretty prints an array of nodes and implodes the printed values. + * + * @param Node[] $nodes Array of Nodes to be printed + * @param string $glue Character to implode with + * + * @return string Imploded pretty printed nodes> $pre + */ + protected function pImplode(array $nodes, string $glue = ''): string { + $pNodes = []; + foreach ($nodes as $node) { + if (null === $node) { + $pNodes[] = ''; + } else { + $pNodes[] = $this->p($node); + } + } + + return implode($glue, $pNodes); + } + + /** + * Pretty prints an array of nodes and implodes the printed values with commas. + * + * @param Node[] $nodes Array of Nodes to be printed + * + * @return string Comma separated pretty printed nodes + */ + protected function pCommaSeparated(array $nodes): string { + return $this->pImplode($nodes, ', '); + } + + /** + * Pretty prints a comma-separated list of nodes in multiline style, including comments. + * + * The result includes a leading newline and one level of indentation (same as pStmts). + * + * @param Node[] $nodes Array of Nodes to be printed + * @param bool $trailingComma Whether to use a trailing comma + * + * @return string Comma separated pretty printed nodes in multiline style + */ + protected function pCommaSeparatedMultiline(array $nodes, bool $trailingComma): string { + $this->indent(); + + $result = ''; + $lastIdx = count($nodes) - 1; + foreach ($nodes as $idx => $node) { + if ($node !== null) { + $comments = $node->getComments(); + if ($comments) { + $result .= $this->nl . $this->pComments($comments); + } + + $result .= $this->nl . $this->p($node); + } else { + $result .= $this->nl; + } + if ($trailingComma || $idx !== $lastIdx) { + $result .= ','; + } + } + + $this->outdent(); + return $result; + } + + /** + * Prints reformatted text of the passed comments. + * + * @param Comment[] $comments List of comments + * + * @return string Reformatted text of comments + */ + protected function pComments(array $comments): string { + $formattedComments = []; + + foreach ($comments as $comment) { + $formattedComments[] = str_replace("\n", $this->nl, $comment->getReformattedText()); + } + + return implode($this->nl, $formattedComments); + } + + /** + * Perform a format-preserving pretty print of an AST. + * + * The format preservation is best effort. For some changes to the AST the formatting will not + * be preserved (at least not locally). + * + * In order to use this method a number of prerequisites must be satisfied: + * * The startTokenPos and endTokenPos attributes in the lexer must be enabled. + * * The CloningVisitor must be run on the AST prior to modification. + * * The original tokens must be provided, using the getTokens() method on the lexer. + * + * @param Node[] $stmts Modified AST with links to original AST + * @param Node[] $origStmts Original AST with token offset information + * @param Token[] $origTokens Tokens of the original code + */ + public function printFormatPreserving(array $stmts, array $origStmts, array $origTokens): string { + $this->initializeNodeListDiffer(); + $this->initializeLabelCharMap(); + $this->initializeFixupMap(); + $this->initializeRemovalMap(); + $this->initializeInsertionMap(); + $this->initializeListInsertionMap(); + $this->initializeEmptyListInsertionMap(); + $this->initializeModifierChangeMap(); + + $this->resetState(); + $this->origTokens = new TokenStream($origTokens); + + $this->preprocessNodes($stmts); + + $pos = 0; + $result = $this->pArray($stmts, $origStmts, $pos, 0, 'File', 'stmts', null); + if (null !== $result) { + $result .= $this->origTokens->getTokenCode($pos, count($origTokens) - 1, 0); + } else { + // Fallback + // TODO Add newline . $this->pStmts($stmts, false); + } + + return $this->handleMagicTokens($result); + } + + protected function pFallback(Node $node, int $precedence, int $lhsPrecedence): string { + return $this->{'p' . $node->getType()}($node, $precedence, $lhsPrecedence); + } + + /** + * Pretty prints a node. + * + * This method also handles formatting preservation for nodes. + * + * @param Node $node Node to be pretty printed + * @param int $precedence Precedence of parent operator + * @param int $lhsPrecedence Precedence for unary operator on LHS of binary operator + * @param bool $parentFormatPreserved Whether parent node has preserved formatting + * + * @return string Pretty printed node + */ + protected function p( + Node $node, int $precedence = self::MAX_PRECEDENCE, int $lhsPrecedence = self::MAX_PRECEDENCE, + bool $parentFormatPreserved = false + ): string { + // No orig tokens means this is a normal pretty print without preservation of formatting + if (!$this->origTokens) { + return $this->{'p' . $node->getType()}($node, $precedence, $lhsPrecedence); + } + + /** @var Node|null $origNode */ + $origNode = $node->getAttribute('origNode'); + if (null === $origNode) { + return $this->pFallback($node, $precedence, $lhsPrecedence); + } + + $class = \get_class($node); + \assert($class === \get_class($origNode)); + + $startPos = $origNode->getStartTokenPos(); + $endPos = $origNode->getEndTokenPos(); + \assert($startPos >= 0 && $endPos >= 0); + + $fallbackNode = $node; + if ($node instanceof Expr\New_ && $node->class instanceof Stmt\Class_) { + // Normalize node structure of anonymous classes + assert($origNode instanceof Expr\New_); + $node = PrintableNewAnonClassNode::fromNewNode($node); + $origNode = PrintableNewAnonClassNode::fromNewNode($origNode); + $class = PrintableNewAnonClassNode::class; + } + + // InlineHTML node does not contain closing and opening PHP tags. If the parent formatting + // is not preserved, then we need to use the fallback code to make sure the tags are + // printed. + if ($node instanceof Stmt\InlineHTML && !$parentFormatPreserved) { + return $this->pFallback($fallbackNode, $precedence, $lhsPrecedence); + } + + $indentAdjustment = $this->indentLevel - $this->origTokens->getIndentationBefore($startPos); + + $type = $node->getType(); + $fixupInfo = $this->fixupMap[$class] ?? null; + + $result = ''; + $pos = $startPos; + foreach ($node->getSubNodeNames() as $subNodeName) { + $subNode = $node->$subNodeName; + $origSubNode = $origNode->$subNodeName; + + if ((!$subNode instanceof Node && $subNode !== null) + || (!$origSubNode instanceof Node && $origSubNode !== null) + ) { + if ($subNode === $origSubNode) { + // Unchanged, can reuse old code + continue; + } + + if (is_array($subNode) && is_array($origSubNode)) { + // Array subnode changed, we might be able to reconstruct it + $listResult = $this->pArray( + $subNode, $origSubNode, $pos, $indentAdjustment, $class, $subNodeName, + $fixupInfo[$subNodeName] ?? null + ); + if (null === $listResult) { + return $this->pFallback($fallbackNode, $precedence, $lhsPrecedence); + } + + $result .= $listResult; + continue; + } + + // Check if this is a modifier change + $key = $class . '->' . $subNodeName; + if (!isset($this->modifierChangeMap[$key])) { + return $this->pFallback($fallbackNode, $precedence, $lhsPrecedence); + } + + [$printFn, $findToken] = $this->modifierChangeMap[$key]; + $result .= $this->$printFn($subNode); + $pos = $this->origTokens->findRight($pos, $findToken); + continue; + } + + $extraLeft = ''; + $extraRight = ''; + if ($origSubNode !== null) { + $subStartPos = $origSubNode->getStartTokenPos(); + $subEndPos = $origSubNode->getEndTokenPos(); + \assert($subStartPos >= 0 && $subEndPos >= 0); + } else { + if ($subNode === null) { + // Both null, nothing to do + continue; + } + + // A node has been inserted, check if we have insertion information for it + $key = $type . '->' . $subNodeName; + if (!isset($this->insertionMap[$key])) { + return $this->pFallback($fallbackNode, $precedence, $lhsPrecedence); + } + + list($findToken, $beforeToken, $extraLeft, $extraRight) = $this->insertionMap[$key]; + if (null !== $findToken) { + $subStartPos = $this->origTokens->findRight($pos, $findToken) + + (int) !$beforeToken; + } else { + $subStartPos = $pos; + } + + if (null === $extraLeft && null !== $extraRight) { + // If inserting on the right only, skipping whitespace looks better + $subStartPos = $this->origTokens->skipRightWhitespace($subStartPos); + } + $subEndPos = $subStartPos - 1; + } + + if (null === $subNode) { + // A node has been removed, check if we have removal information for it + $key = $type . '->' . $subNodeName; + if (!isset($this->removalMap[$key])) { + return $this->pFallback($fallbackNode, $precedence, $lhsPrecedence); + } + + // Adjust positions to account for additional tokens that must be skipped + $removalInfo = $this->removalMap[$key]; + if (isset($removalInfo['left'])) { + $subStartPos = $this->origTokens->skipLeft($subStartPos - 1, $removalInfo['left']) + 1; + } + if (isset($removalInfo['right'])) { + $subEndPos = $this->origTokens->skipRight($subEndPos + 1, $removalInfo['right']) - 1; + } + } + + $result .= $this->origTokens->getTokenCode($pos, $subStartPos, $indentAdjustment); + + if (null !== $subNode) { + $result .= $extraLeft; + + $origIndentLevel = $this->indentLevel; + $this->setIndentLevel($this->origTokens->getIndentationBefore($subStartPos) + $indentAdjustment); + + // If it's the same node that was previously in this position, it certainly doesn't + // need fixup. It's important to check this here, because our fixup checks are more + // conservative than strictly necessary. + if (isset($fixupInfo[$subNodeName]) + && $subNode->getAttribute('origNode') !== $origSubNode + ) { + $fixup = $fixupInfo[$subNodeName]; + $res = $this->pFixup($fixup, $subNode, $class, $subStartPos, $subEndPos); + } else { + $res = $this->p($subNode, self::MAX_PRECEDENCE, self::MAX_PRECEDENCE, true); + } + + $this->safeAppend($result, $res); + $this->setIndentLevel($origIndentLevel); + + $result .= $extraRight; + } + + $pos = $subEndPos + 1; + } + + $result .= $this->origTokens->getTokenCode($pos, $endPos + 1, $indentAdjustment); + return $result; + } + + /** + * Perform a format-preserving pretty print of an array. + * + * @param Node[] $nodes New nodes + * @param Node[] $origNodes Original nodes + * @param int $pos Current token position (updated by reference) + * @param int $indentAdjustment Adjustment for indentation + * @param string $parentNodeClass Class of the containing node. + * @param string $subNodeName Name of array subnode. + * @param null|int $fixup Fixup information for array item nodes + * + * @return null|string Result of pretty print or null if cannot preserve formatting + */ + protected function pArray( + array $nodes, array $origNodes, int &$pos, int $indentAdjustment, + string $parentNodeClass, string $subNodeName, ?int $fixup + ): ?string { + $diff = $this->nodeListDiffer->diffWithReplacements($origNodes, $nodes); + + $mapKey = $parentNodeClass . '->' . $subNodeName; + $insertStr = $this->listInsertionMap[$mapKey] ?? null; + $isStmtList = $subNodeName === 'stmts'; + + $beforeFirstKeepOrReplace = true; + $skipRemovedNode = false; + $delayedAdd = []; + $lastElemIndentLevel = $this->indentLevel; + + $insertNewline = false; + if ($insertStr === "\n") { + $insertStr = ''; + $insertNewline = true; + } + + if ($isStmtList && \count($origNodes) === 1 && \count($nodes) !== 1) { + $startPos = $origNodes[0]->getStartTokenPos(); + $endPos = $origNodes[0]->getEndTokenPos(); + \assert($startPos >= 0 && $endPos >= 0); + if (!$this->origTokens->haveBraces($startPos, $endPos)) { + // This was a single statement without braces, but either additional statements + // have been added, or the single statement has been removed. This requires the + // addition of braces. For now fall back. + // TODO: Try to preserve formatting + return null; + } + } + + $result = ''; + foreach ($diff as $i => $diffElem) { + $diffType = $diffElem->type; + /** @var Node|string|null $arrItem */ + $arrItem = $diffElem->new; + /** @var Node|string|null $origArrItem */ + $origArrItem = $diffElem->old; + + if ($diffType === DiffElem::TYPE_KEEP || $diffType === DiffElem::TYPE_REPLACE) { + $beforeFirstKeepOrReplace = false; + + if ($origArrItem === null || $arrItem === null) { + // We can only handle the case where both are null + if ($origArrItem === $arrItem) { + continue; + } + return null; + } + + if (!$arrItem instanceof Node || !$origArrItem instanceof Node) { + // We can only deal with nodes. This can occur for Names, which use string arrays. + return null; + } + + $itemStartPos = $origArrItem->getStartTokenPos(); + $itemEndPos = $origArrItem->getEndTokenPos(); + \assert($itemStartPos >= 0 && $itemEndPos >= 0 && $itemStartPos >= $pos); + + $origIndentLevel = $this->indentLevel; + $lastElemIndentLevel = $this->origTokens->getIndentationBefore($itemStartPos) + $indentAdjustment; + $this->setIndentLevel($lastElemIndentLevel); + + $comments = $arrItem->getComments(); + $origComments = $origArrItem->getComments(); + $commentStartPos = $origComments ? $origComments[0]->getStartTokenPos() : $itemStartPos; + \assert($commentStartPos >= 0); + + if ($commentStartPos < $pos) { + // Comments may be assigned to multiple nodes if they start at the same position. + // Make sure we don't try to print them multiple times. + $commentStartPos = $itemStartPos; + } + + if ($skipRemovedNode) { + if ($isStmtList && $this->origTokens->haveTagInRange($pos, $itemStartPos)) { + // We'd remove an opening/closing PHP tag. + // TODO: Preserve formatting. + $this->setIndentLevel($origIndentLevel); + return null; + } + } else { + $result .= $this->origTokens->getTokenCode( + $pos, $commentStartPos, $indentAdjustment); + } + + if (!empty($delayedAdd)) { + /** @var Node $delayedAddNode */ + foreach ($delayedAdd as $delayedAddNode) { + if ($insertNewline) { + $delayedAddComments = $delayedAddNode->getComments(); + if ($delayedAddComments) { + $result .= $this->pComments($delayedAddComments) . $this->nl; + } + } + + $this->safeAppend($result, $this->p($delayedAddNode, self::MAX_PRECEDENCE, self::MAX_PRECEDENCE, true)); + + if ($insertNewline) { + $result .= $insertStr . $this->nl; + } else { + $result .= $insertStr; + } + } + + $delayedAdd = []; + } + + if ($comments !== $origComments) { + if ($comments) { + $result .= $this->pComments($comments) . $this->nl; + } + } else { + $result .= $this->origTokens->getTokenCode( + $commentStartPos, $itemStartPos, $indentAdjustment); + } + + // If we had to remove anything, we have done so now. + $skipRemovedNode = false; + } elseif ($diffType === DiffElem::TYPE_ADD) { + if (null === $insertStr) { + // We don't have insertion information for this list type + return null; + } + + if (!$arrItem instanceof Node) { + // We only support list insertion of nodes. + return null; + } + + // We go multiline if the original code was multiline, + // or if it's an array item with a comment above it. + // Match always uses multiline formatting. + if ($insertStr === ', ' && + ($this->isMultiline($origNodes) || $arrItem->getComments() || + $parentNodeClass === Expr\Match_::class) + ) { + $insertStr = ','; + $insertNewline = true; + } + + if ($beforeFirstKeepOrReplace) { + // Will be inserted at the next "replace" or "keep" element + $delayedAdd[] = $arrItem; + continue; + } + + $itemStartPos = $pos; + $itemEndPos = $pos - 1; + + $origIndentLevel = $this->indentLevel; + $this->setIndentLevel($lastElemIndentLevel); + + if ($insertNewline) { + $result .= $insertStr . $this->nl; + $comments = $arrItem->getComments(); + if ($comments) { + $result .= $this->pComments($comments) . $this->nl; + } + } else { + $result .= $insertStr; + } + } elseif ($diffType === DiffElem::TYPE_REMOVE) { + if (!$origArrItem instanceof Node) { + // We only support removal for nodes + return null; + } + + $itemStartPos = $origArrItem->getStartTokenPos(); + $itemEndPos = $origArrItem->getEndTokenPos(); + \assert($itemStartPos >= 0 && $itemEndPos >= 0); + + // Consider comments part of the node. + $origComments = $origArrItem->getComments(); + if ($origComments) { + $itemStartPos = $origComments[0]->getStartTokenPos(); + } + + if ($i === 0) { + // If we're removing from the start, keep the tokens before the node and drop those after it, + // instead of the other way around. + $result .= $this->origTokens->getTokenCode( + $pos, $itemStartPos, $indentAdjustment); + $skipRemovedNode = true; + } else { + if ($isStmtList && $this->origTokens->haveTagInRange($pos, $itemStartPos)) { + // We'd remove an opening/closing PHP tag. + // TODO: Preserve formatting. + return null; + } + } + + $pos = $itemEndPos + 1; + continue; + } else { + throw new \Exception("Shouldn't happen"); + } + + if (null !== $fixup && $arrItem->getAttribute('origNode') !== $origArrItem) { + $res = $this->pFixup($fixup, $arrItem, null, $itemStartPos, $itemEndPos); + } else { + $res = $this->p($arrItem, self::MAX_PRECEDENCE, self::MAX_PRECEDENCE, true); + } + $this->safeAppend($result, $res); + + $this->setIndentLevel($origIndentLevel); + $pos = $itemEndPos + 1; + } + + if ($skipRemovedNode) { + // TODO: Support removing single node. + return null; + } + + if (!empty($delayedAdd)) { + if (!isset($this->emptyListInsertionMap[$mapKey])) { + return null; + } + + list($findToken, $extraLeft, $extraRight) = $this->emptyListInsertionMap[$mapKey]; + if (null !== $findToken) { + $insertPos = $this->origTokens->findRight($pos, $findToken) + 1; + $result .= $this->origTokens->getTokenCode($pos, $insertPos, $indentAdjustment); + $pos = $insertPos; + } + + $first = true; + $result .= $extraLeft; + foreach ($delayedAdd as $delayedAddNode) { + if (!$first) { + $result .= $insertStr; + if ($insertNewline) { + $result .= $this->nl; + } + } + $result .= $this->p($delayedAddNode, self::MAX_PRECEDENCE, self::MAX_PRECEDENCE, true); + $first = false; + } + $result .= $extraRight === "\n" ? $this->nl : $extraRight; + } + + return $result; + } + + /** + * Print node with fixups. + * + * Fixups here refer to the addition of extra parentheses, braces or other characters, that + * are required to preserve program semantics in a certain context (e.g. to maintain precedence + * or because only certain expressions are allowed in certain places). + * + * @param int $fixup Fixup type + * @param Node $subNode Subnode to print + * @param string|null $parentClass Class of parent node + * @param int $subStartPos Original start pos of subnode + * @param int $subEndPos Original end pos of subnode + * + * @return string Result of fixed-up print of subnode + */ + protected function pFixup(int $fixup, Node $subNode, ?string $parentClass, int $subStartPos, int $subEndPos): string { + switch ($fixup) { + case self::FIXUP_PREC_LEFT: + // We use a conservative approximation where lhsPrecedence == precedence. + if (!$this->origTokens->haveParens($subStartPos, $subEndPos)) { + $precedence = $this->precedenceMap[$parentClass][1]; + return $this->p($subNode, $precedence, $precedence); + } + break; + case self::FIXUP_PREC_RIGHT: + if (!$this->origTokens->haveParens($subStartPos, $subEndPos)) { + $precedence = $this->precedenceMap[$parentClass][2]; + return $this->p($subNode, $precedence, $precedence); + } + break; + case self::FIXUP_PREC_UNARY: + if (!$this->origTokens->haveParens($subStartPos, $subEndPos)) { + $precedence = $this->precedenceMap[$parentClass][0]; + return $this->p($subNode, $precedence, $precedence); + } + break; + case self::FIXUP_CALL_LHS: + if ($this->callLhsRequiresParens($subNode) + && !$this->origTokens->haveParens($subStartPos, $subEndPos) + ) { + return '(' . $this->p($subNode) . ')'; + } + break; + case self::FIXUP_DEREF_LHS: + if ($this->dereferenceLhsRequiresParens($subNode) + && !$this->origTokens->haveParens($subStartPos, $subEndPos) + ) { + return '(' . $this->p($subNode) . ')'; + } + break; + case self::FIXUP_STATIC_DEREF_LHS: + if ($this->staticDereferenceLhsRequiresParens($subNode) + && !$this->origTokens->haveParens($subStartPos, $subEndPos) + ) { + return '(' . $this->p($subNode) . ')'; + } + break; + case self::FIXUP_NEW: + if ($this->newOperandRequiresParens($subNode) + && !$this->origTokens->haveParens($subStartPos, $subEndPos)) { + return '(' . $this->p($subNode) . ')'; + } + break; + case self::FIXUP_BRACED_NAME: + case self::FIXUP_VAR_BRACED_NAME: + if ($subNode instanceof Expr + && !$this->origTokens->haveBraces($subStartPos, $subEndPos) + ) { + return ($fixup === self::FIXUP_VAR_BRACED_NAME ? '$' : '') + . '{' . $this->p($subNode) . '}'; + } + break; + case self::FIXUP_ENCAPSED: + if (!$subNode instanceof Node\InterpolatedStringPart + && !$this->origTokens->haveBraces($subStartPos, $subEndPos) + ) { + return '{' . $this->p($subNode) . '}'; + } + break; + default: + throw new \Exception('Cannot happen'); + } + + // Nothing special to do + return $this->p($subNode); + } + + /** + * Appends to a string, ensuring whitespace between label characters. + * + * Example: "echo" and "$x" result in "echo$x", but "echo" and "x" result in "echo x". + * Without safeAppend the result would be "echox", which does not preserve semantics. + */ + protected function safeAppend(string &$str, string $append): void { + if ($str === "") { + $str = $append; + return; + } + + if ($append === "") { + return; + } + + if (!$this->labelCharMap[$append[0]] + || !$this->labelCharMap[$str[\strlen($str) - 1]]) { + $str .= $append; + } else { + $str .= " " . $append; + } + } + + /** + * Determines whether the LHS of a call must be wrapped in parenthesis. + * + * @param Node $node LHS of a call + * + * @return bool Whether parentheses are required + */ + protected function callLhsRequiresParens(Node $node): bool { + return !($node instanceof Node\Name + || $node instanceof Expr\Variable + || $node instanceof Expr\ArrayDimFetch + || $node instanceof Expr\FuncCall + || $node instanceof Expr\MethodCall + || $node instanceof Expr\NullsafeMethodCall + || $node instanceof Expr\StaticCall + || $node instanceof Expr\Array_); + } + + /** + * Determines whether the LHS of an array/object operation must be wrapped in parentheses. + * + * @param Node $node LHS of dereferencing operation + * + * @return bool Whether parentheses are required + */ + protected function dereferenceLhsRequiresParens(Node $node): bool { + // A constant can occur on the LHS of an array/object deref, but not a static deref. + return $this->staticDereferenceLhsRequiresParens($node) + && !$node instanceof Expr\ConstFetch; + } + + /** + * Determines whether the LHS of a static operation must be wrapped in parentheses. + * + * @param Node $node LHS of dereferencing operation + * + * @return bool Whether parentheses are required + */ + protected function staticDereferenceLhsRequiresParens(Node $node): bool { + return !($node instanceof Expr\Variable + || $node instanceof Node\Name + || $node instanceof Expr\ArrayDimFetch + || $node instanceof Expr\PropertyFetch + || $node instanceof Expr\NullsafePropertyFetch + || $node instanceof Expr\StaticPropertyFetch + || $node instanceof Expr\FuncCall + || $node instanceof Expr\MethodCall + || $node instanceof Expr\NullsafeMethodCall + || $node instanceof Expr\StaticCall + || $node instanceof Expr\Array_ + || $node instanceof Scalar\String_ + || $node instanceof Expr\ClassConstFetch); + } + + /** + * Determines whether an expression used in "new" or "instanceof" requires parentheses. + * + * @param Node $node New or instanceof operand + * + * @return bool Whether parentheses are required + */ + protected function newOperandRequiresParens(Node $node): bool { + if ($node instanceof Node\Name || $node instanceof Expr\Variable) { + return false; + } + if ($node instanceof Expr\ArrayDimFetch || $node instanceof Expr\PropertyFetch || + $node instanceof Expr\NullsafePropertyFetch + ) { + return $this->newOperandRequiresParens($node->var); + } + if ($node instanceof Expr\StaticPropertyFetch) { + return $this->newOperandRequiresParens($node->class); + } + return true; + } + + /** + * Print modifiers, including trailing whitespace. + * + * @param int $modifiers Modifier mask to print + * + * @return string Printed modifiers + */ + protected function pModifiers(int $modifiers): string { + return ($modifiers & Modifiers::FINAL ? 'final ' : '') + . ($modifiers & Modifiers::ABSTRACT ? 'abstract ' : '') + . ($modifiers & Modifiers::PUBLIC ? 'public ' : '') + . ($modifiers & Modifiers::PROTECTED ? 'protected ' : '') + . ($modifiers & Modifiers::PRIVATE ? 'private ' : '') + . ($modifiers & Modifiers::STATIC ? 'static ' : '') + . ($modifiers & Modifiers::READONLY ? 'readonly ' : ''); + } + + protected function pStatic(bool $static): string { + return $static ? 'static ' : ''; + } + + /** + * Determine whether a list of nodes uses multiline formatting. + * + * @param (Node|null)[] $nodes Node list + * + * @return bool Whether multiline formatting is used + */ + protected function isMultiline(array $nodes): bool { + if (\count($nodes) < 2) { + return false; + } + + $pos = -1; + foreach ($nodes as $node) { + if (null === $node) { + continue; + } + + $endPos = $node->getEndTokenPos() + 1; + if ($pos >= 0) { + $text = $this->origTokens->getTokenCode($pos, $endPos, 0); + if (false === strpos($text, "\n")) { + // We require that a newline is present between *every* item. If the formatting + // is inconsistent, with only some items having newlines, we don't consider it + // as multiline + return false; + } + } + $pos = $endPos; + } + + return true; + } + + /** + * Lazily initializes label char map. + * + * The label char map determines whether a certain character may occur in a label. + */ + protected function initializeLabelCharMap(): void { + if (isset($this->labelCharMap)) { + return; + } + + $this->labelCharMap = []; + for ($i = 0; $i < 256; $i++) { + $chr = chr($i); + $this->labelCharMap[$chr] = $i >= 0x80 || ctype_alnum($chr); + } + + if ($this->phpVersion->allowsDelInIdentifiers()) { + $this->labelCharMap["\x7f"] = true; + } + } + + /** + * Lazily initializes node list differ. + * + * The node list differ is used to determine differences between two array subnodes. + */ + protected function initializeNodeListDiffer(): void { + if (isset($this->nodeListDiffer)) { + return; + } + + $this->nodeListDiffer = new Internal\Differ(function ($a, $b) { + if ($a instanceof Node && $b instanceof Node) { + return $a === $b->getAttribute('origNode'); + } + // Can happen for array destructuring + return $a === null && $b === null; + }); + } + + /** + * Lazily initializes fixup map. + * + * The fixup map is used to determine whether a certain subnode of a certain node may require + * some kind of "fixup" operation, e.g. the addition of parenthesis or braces. + */ + protected function initializeFixupMap(): void { + if (isset($this->fixupMap)) { + return; + } + + $this->fixupMap = [ + Expr\Instanceof_::class => [ + 'expr' => self::FIXUP_PREC_UNARY, + 'class' => self::FIXUP_NEW, + ], + Expr\Ternary::class => [ + 'cond' => self::FIXUP_PREC_LEFT, + 'else' => self::FIXUP_PREC_RIGHT, + ], + Expr\Yield_::class => ['value' => self::FIXUP_PREC_UNARY], + + Expr\FuncCall::class => ['name' => self::FIXUP_CALL_LHS], + Expr\StaticCall::class => ['class' => self::FIXUP_STATIC_DEREF_LHS], + Expr\ArrayDimFetch::class => ['var' => self::FIXUP_DEREF_LHS], + Expr\ClassConstFetch::class => [ + 'class' => self::FIXUP_STATIC_DEREF_LHS, + 'name' => self::FIXUP_BRACED_NAME, + ], + Expr\New_::class => ['class' => self::FIXUP_NEW], + Expr\MethodCall::class => [ + 'var' => self::FIXUP_DEREF_LHS, + 'name' => self::FIXUP_BRACED_NAME, + ], + Expr\NullsafeMethodCall::class => [ + 'var' => self::FIXUP_DEREF_LHS, + 'name' => self::FIXUP_BRACED_NAME, + ], + Expr\StaticPropertyFetch::class => [ + 'class' => self::FIXUP_STATIC_DEREF_LHS, + 'name' => self::FIXUP_VAR_BRACED_NAME, + ], + Expr\PropertyFetch::class => [ + 'var' => self::FIXUP_DEREF_LHS, + 'name' => self::FIXUP_BRACED_NAME, + ], + Expr\NullsafePropertyFetch::class => [ + 'var' => self::FIXUP_DEREF_LHS, + 'name' => self::FIXUP_BRACED_NAME, + ], + Scalar\InterpolatedString::class => [ + 'parts' => self::FIXUP_ENCAPSED, + ], + ]; + + $binaryOps = [ + BinaryOp\Pow::class, BinaryOp\Mul::class, BinaryOp\Div::class, BinaryOp\Mod::class, + BinaryOp\Plus::class, BinaryOp\Minus::class, BinaryOp\Concat::class, + BinaryOp\ShiftLeft::class, BinaryOp\ShiftRight::class, BinaryOp\Smaller::class, + BinaryOp\SmallerOrEqual::class, BinaryOp\Greater::class, BinaryOp\GreaterOrEqual::class, + BinaryOp\Equal::class, BinaryOp\NotEqual::class, BinaryOp\Identical::class, + BinaryOp\NotIdentical::class, BinaryOp\Spaceship::class, BinaryOp\BitwiseAnd::class, + BinaryOp\BitwiseXor::class, BinaryOp\BitwiseOr::class, BinaryOp\BooleanAnd::class, + BinaryOp\BooleanOr::class, BinaryOp\Coalesce::class, BinaryOp\LogicalAnd::class, + BinaryOp\LogicalXor::class, BinaryOp\LogicalOr::class, + ]; + foreach ($binaryOps as $binaryOp) { + $this->fixupMap[$binaryOp] = [ + 'left' => self::FIXUP_PREC_LEFT, + 'right' => self::FIXUP_PREC_RIGHT + ]; + } + + $prefixOps = [ + Expr\Clone_::class, Expr\BitwiseNot::class, Expr\BooleanNot::class, Expr\UnaryPlus::class, Expr\UnaryMinus::class, + Cast\Int_::class, Cast\Double::class, Cast\String_::class, Cast\Array_::class, + Cast\Object_::class, Cast\Bool_::class, Cast\Unset_::class, Expr\ErrorSuppress::class, + Expr\YieldFrom::class, Expr\Print_::class, Expr\Include_::class, + Expr\Assign::class, Expr\AssignRef::class, AssignOp\Plus::class, AssignOp\Minus::class, + AssignOp\Mul::class, AssignOp\Div::class, AssignOp\Concat::class, AssignOp\Mod::class, + AssignOp\BitwiseAnd::class, AssignOp\BitwiseOr::class, AssignOp\BitwiseXor::class, + AssignOp\ShiftLeft::class, AssignOp\ShiftRight::class, AssignOp\Pow::class, AssignOp\Coalesce::class, + Expr\ArrowFunction::class, Expr\Throw_::class, + ]; + foreach ($prefixOps as $prefixOp) { + $this->fixupMap[$prefixOp] = ['expr' => self::FIXUP_PREC_UNARY]; + } + } + + /** + * Lazily initializes the removal map. + * + * The removal map is used to determine which additional tokens should be removed when a + * certain node is replaced by null. + */ + protected function initializeRemovalMap(): void { + if (isset($this->removalMap)) { + return; + } + + $stripBoth = ['left' => \T_WHITESPACE, 'right' => \T_WHITESPACE]; + $stripLeft = ['left' => \T_WHITESPACE]; + $stripRight = ['right' => \T_WHITESPACE]; + $stripDoubleArrow = ['right' => \T_DOUBLE_ARROW]; + $stripColon = ['left' => ':']; + $stripEquals = ['left' => '=']; + $this->removalMap = [ + 'Expr_ArrayDimFetch->dim' => $stripBoth, + 'ArrayItem->key' => $stripDoubleArrow, + 'Expr_ArrowFunction->returnType' => $stripColon, + 'Expr_Closure->returnType' => $stripColon, + 'Expr_Exit->expr' => $stripBoth, + 'Expr_Ternary->if' => $stripBoth, + 'Expr_Yield->key' => $stripDoubleArrow, + 'Expr_Yield->value' => $stripBoth, + 'Param->type' => $stripRight, + 'Param->default' => $stripEquals, + 'Stmt_Break->num' => $stripBoth, + 'Stmt_Catch->var' => $stripLeft, + 'Stmt_ClassConst->type' => $stripRight, + 'Stmt_ClassMethod->returnType' => $stripColon, + 'Stmt_Class->extends' => ['left' => \T_EXTENDS], + 'Stmt_Enum->scalarType' => $stripColon, + 'Stmt_EnumCase->expr' => $stripEquals, + 'Expr_PrintableNewAnonClass->extends' => ['left' => \T_EXTENDS], + 'Stmt_Continue->num' => $stripBoth, + 'Stmt_Foreach->keyVar' => $stripDoubleArrow, + 'Stmt_Function->returnType' => $stripColon, + 'Stmt_If->else' => $stripLeft, + 'Stmt_Namespace->name' => $stripLeft, + 'Stmt_Property->type' => $stripRight, + 'PropertyItem->default' => $stripEquals, + 'Stmt_Return->expr' => $stripBoth, + 'Stmt_StaticVar->default' => $stripEquals, + 'Stmt_TraitUseAdaptation_Alias->newName' => $stripLeft, + 'Stmt_TryCatch->finally' => $stripLeft, + // 'Stmt_Case->cond': Replace with "default" + // 'Stmt_Class->name': Unclear what to do + // 'Stmt_Declare->stmts': Not a plain node + // 'Stmt_TraitUseAdaptation_Alias->newModifier': Not a plain node + ]; + } + + protected function initializeInsertionMap(): void { + if (isset($this->insertionMap)) { + return; + } + + // TODO: "yield" where both key and value are inserted doesn't work + // [$find, $beforeToken, $extraLeft, $extraRight] + $this->insertionMap = [ + 'Expr_ArrayDimFetch->dim' => ['[', false, null, null], + 'ArrayItem->key' => [null, false, null, ' => '], + 'Expr_ArrowFunction->returnType' => [')', false, ': ', null], + 'Expr_Closure->returnType' => [')', false, ': ', null], + 'Expr_Ternary->if' => ['?', false, ' ', ' '], + 'Expr_Yield->key' => [\T_YIELD, false, null, ' => '], + 'Expr_Yield->value' => [\T_YIELD, false, ' ', null], + 'Param->type' => [null, false, null, ' '], + 'Param->default' => [null, false, ' = ', null], + 'Stmt_Break->num' => [\T_BREAK, false, ' ', null], + 'Stmt_Catch->var' => [null, false, ' ', null], + 'Stmt_ClassMethod->returnType' => [')', false, ': ', null], + 'Stmt_ClassConst->type' => [\T_CONST, false, ' ', null], + 'Stmt_Class->extends' => [null, false, ' extends ', null], + 'Stmt_Enum->scalarType' => [null, false, ' : ', null], + 'Stmt_EnumCase->expr' => [null, false, ' = ', null], + 'Expr_PrintableNewAnonClass->extends' => [null, false, ' extends ', null], + 'Stmt_Continue->num' => [\T_CONTINUE, false, ' ', null], + 'Stmt_Foreach->keyVar' => [\T_AS, false, null, ' => '], + 'Stmt_Function->returnType' => [')', false, ': ', null], + 'Stmt_If->else' => [null, false, ' ', null], + 'Stmt_Namespace->name' => [\T_NAMESPACE, false, ' ', null], + 'Stmt_Property->type' => [\T_VARIABLE, true, null, ' '], + 'PropertyItem->default' => [null, false, ' = ', null], + 'Stmt_Return->expr' => [\T_RETURN, false, ' ', null], + 'Stmt_StaticVar->default' => [null, false, ' = ', null], + //'Stmt_TraitUseAdaptation_Alias->newName' => [T_AS, false, ' ', null], // TODO + 'Stmt_TryCatch->finally' => [null, false, ' ', null], + + // 'Expr_Exit->expr': Complicated due to optional () + // 'Stmt_Case->cond': Conversion from default to case + // 'Stmt_Class->name': Unclear + // 'Stmt_Declare->stmts': Not a proper node + // 'Stmt_TraitUseAdaptation_Alias->newModifier': Not a proper node + ]; + } + + protected function initializeListInsertionMap(): void { + if (isset($this->listInsertionMap)) { + return; + } + + $this->listInsertionMap = [ + // special + //'Expr_ShellExec->parts' => '', // TODO These need to be treated more carefully + //'Scalar_InterpolatedString->parts' => '', + Stmt\Catch_::class . '->types' => '|', + UnionType::class . '->types' => '|', + IntersectionType::class . '->types' => '&', + Stmt\If_::class . '->elseifs' => ' ', + Stmt\TryCatch::class . '->catches' => ' ', + + // comma-separated lists + Expr\Array_::class . '->items' => ', ', + Expr\ArrowFunction::class . '->params' => ', ', + Expr\Closure::class . '->params' => ', ', + Expr\Closure::class . '->uses' => ', ', + Expr\FuncCall::class . '->args' => ', ', + Expr\Isset_::class . '->vars' => ', ', + Expr\List_::class . '->items' => ', ', + Expr\MethodCall::class . '->args' => ', ', + Expr\NullsafeMethodCall::class . '->args' => ', ', + Expr\New_::class . '->args' => ', ', + PrintableNewAnonClassNode::class . '->args' => ', ', + Expr\StaticCall::class . '->args' => ', ', + Stmt\ClassConst::class . '->consts' => ', ', + Stmt\ClassMethod::class . '->params' => ', ', + Stmt\Class_::class . '->implements' => ', ', + Stmt\Enum_::class . '->implements' => ', ', + PrintableNewAnonClassNode::class . '->implements' => ', ', + Stmt\Const_::class . '->consts' => ', ', + Stmt\Declare_::class . '->declares' => ', ', + Stmt\Echo_::class . '->exprs' => ', ', + Stmt\For_::class . '->init' => ', ', + Stmt\For_::class . '->cond' => ', ', + Stmt\For_::class . '->loop' => ', ', + Stmt\Function_::class . '->params' => ', ', + Stmt\Global_::class . '->vars' => ', ', + Stmt\GroupUse::class . '->uses' => ', ', + Stmt\Interface_::class . '->extends' => ', ', + Expr\Match_::class . '->arms' => ', ', + Stmt\Property::class . '->props' => ', ', + Stmt\StaticVar::class . '->vars' => ', ', + Stmt\TraitUse::class . '->traits' => ', ', + Stmt\TraitUseAdaptation\Precedence::class . '->insteadof' => ', ', + Stmt\Unset_::class . '->vars' => ', ', + Stmt\UseUse::class . '->uses' => ', ', + MatchArm::class . '->conds' => ', ', + AttributeGroup::class . '->attrs' => ', ', + + // statement lists + Expr\Closure::class . '->stmts' => "\n", + Stmt\Case_::class . '->stmts' => "\n", + Stmt\Catch_::class . '->stmts' => "\n", + Stmt\Class_::class . '->stmts' => "\n", + Stmt\Enum_::class . '->stmts' => "\n", + PrintableNewAnonClassNode::class . '->stmts' => "\n", + Stmt\Interface_::class . '->stmts' => "\n", + Stmt\Trait_::class . '->stmts' => "\n", + Stmt\ClassMethod::class . '->stmts' => "\n", + Stmt\Declare_::class . '->stmts' => "\n", + Stmt\Do_::class . '->stmts' => "\n", + Stmt\ElseIf_::class . '->stmts' => "\n", + Stmt\Else_::class . '->stmts' => "\n", + Stmt\Finally_::class . '->stmts' => "\n", + Stmt\Foreach_::class . '->stmts' => "\n", + Stmt\For_::class . '->stmts' => "\n", + Stmt\Function_::class . '->stmts' => "\n", + Stmt\If_::class . '->stmts' => "\n", + Stmt\Namespace_::class . '->stmts' => "\n", + Stmt\Block::class . '->stmts' => "\n", + + // Attribute groups + Stmt\Class_::class . '->attrGroups' => "\n", + Stmt\Enum_::class . '->attrGroups' => "\n", + Stmt\EnumCase::class . '->attrGroups' => "\n", + Stmt\Interface_::class . '->attrGroups' => "\n", + Stmt\Trait_::class . '->attrGroups' => "\n", + Stmt\Function_::class . '->attrGroups' => "\n", + Stmt\ClassMethod::class . '->attrGroups' => "\n", + Stmt\ClassConst::class . '->attrGroups' => "\n", + Stmt\Property::class . '->attrGroups' => "\n", + PrintableNewAnonClassNode::class . '->attrGroups' => ' ', + Expr\Closure::class . '->attrGroups' => ' ', + Expr\ArrowFunction::class . '->attrGroups' => ' ', + Param::class . '->attrGroups' => ' ', + Stmt\Switch_::class . '->cases' => "\n", + Stmt\TraitUse::class . '->adaptations' => "\n", + Stmt\TryCatch::class . '->stmts' => "\n", + Stmt\While_::class . '->stmts' => "\n", + + // dummy for top-level context + 'File->stmts' => "\n", + ]; + } + + protected function initializeEmptyListInsertionMap(): void { + if (isset($this->emptyListInsertionMap)) { + return; + } + + // TODO Insertion into empty statement lists. + + // [$find, $extraLeft, $extraRight] + $this->emptyListInsertionMap = [ + Expr\ArrowFunction::class . '->params' => ['(', '', ''], + Expr\Closure::class . '->uses' => [')', ' use (', ')'], + Expr\Closure::class . '->params' => ['(', '', ''], + Expr\FuncCall::class . '->args' => ['(', '', ''], + Expr\MethodCall::class . '->args' => ['(', '', ''], + Expr\NullsafeMethodCall::class . '->args' => ['(', '', ''], + Expr\New_::class . '->args' => ['(', '', ''], + PrintableNewAnonClassNode::class . '->args' => ['(', '', ''], + PrintableNewAnonClassNode::class . '->implements' => [null, ' implements ', ''], + Expr\StaticCall::class . '->args' => ['(', '', ''], + Stmt\Class_::class . '->implements' => [null, ' implements ', ''], + Stmt\Enum_::class . '->implements' => [null, ' implements ', ''], + Stmt\ClassMethod::class . '->params' => ['(', '', ''], + Stmt\Interface_::class . '->extends' => [null, ' extends ', ''], + Stmt\Function_::class . '->params' => ['(', '', ''], + Stmt\Interface_::class . '->attrGroups' => [null, '', "\n"], + Stmt\Class_::class . '->attrGroups' => [null, '', "\n"], + Stmt\ClassConst::class . '->attrGroups' => [null, '', "\n"], + Stmt\ClassMethod::class . '->attrGroups' => [null, '', "\n"], + Stmt\Function_::class . '->attrGroups' => [null, '', "\n"], + Stmt\Property::class . '->attrGroups' => [null, '', "\n"], + Stmt\Trait_::class . '->attrGroups' => [null, '', "\n"], + Expr\ArrowFunction::class . '->attrGroups' => [null, '', ' '], + Expr\Closure::class . '->attrGroups' => [null, '', ' '], + PrintableNewAnonClassNode::class . '->attrGroups' => [\T_NEW, ' ', ''], + + /* These cannot be empty to start with: + * Expr_Isset->vars + * Stmt_Catch->types + * Stmt_Const->consts + * Stmt_ClassConst->consts + * Stmt_Declare->declares + * Stmt_Echo->exprs + * Stmt_Global->vars + * Stmt_GroupUse->uses + * Stmt_Property->props + * Stmt_StaticVar->vars + * Stmt_TraitUse->traits + * Stmt_TraitUseAdaptation_Precedence->insteadof + * Stmt_Unset->vars + * Stmt_Use->uses + * UnionType->types + */ + + /* TODO + * Stmt_If->elseifs + * Stmt_TryCatch->catches + * Expr_Array->items + * Expr_List->items + * Stmt_For->init + * Stmt_For->cond + * Stmt_For->loop + */ + ]; + } + + protected function initializeModifierChangeMap(): void { + if (isset($this->modifierChangeMap)) { + return; + } + + $this->modifierChangeMap = [ + Stmt\ClassConst::class . '->flags' => ['pModifiers', \T_CONST], + Stmt\ClassMethod::class . '->flags' => ['pModifiers', \T_FUNCTION], + Stmt\Class_::class . '->flags' => ['pModifiers', \T_CLASS], + Stmt\Property::class . '->flags' => ['pModifiers', \T_VARIABLE], + PrintableNewAnonClassNode::class . '->flags' => ['pModifiers', \T_CLASS], + Param::class . '->flags' => ['pModifiers', \T_VARIABLE], + Expr\Closure::class . '->static' => ['pStatic', \T_FUNCTION], + Expr\ArrowFunction::class . '->static' => ['pStatic', \T_FN], + //Stmt\TraitUseAdaptation\Alias::class . '->newModifier' => 0, // TODO + ]; + + // List of integer subnodes that are not modifiers: + // Expr_Include->type + // Stmt_GroupUse->type + // Stmt_Use->type + // UseItem->type + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/Token.php b/vendor/nikic/php-parser/lib/PhpParser/Token.php new file mode 100644 index 00000000..6683310f --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/Token.php @@ -0,0 +1,18 @@ +pos + \strlen($this->text); + } + + /** Get 1-based end line number of the token. */ + public function getEndLine(): int { + return $this->line + \substr_count($this->text, "\n"); + } +} diff --git a/vendor/nikic/php-parser/lib/PhpParser/compatibility_tokens.php b/vendor/nikic/php-parser/lib/PhpParser/compatibility_tokens.php new file mode 100644 index 00000000..273271dd --- /dev/null +++ b/vendor/nikic/php-parser/lib/PhpParser/compatibility_tokens.php @@ -0,0 +1,63 @@ +redirectTo instead of just call it. +* Split the setting check methods. Now 1 method for IdP settings and other for SP settings. +* Let the setting object to avoid the IdP setting check. required if we want to publish SP SAML Metadata when the IdP data is still not provided. + +v.2.5.0 +------- +* Do accesible the ID of the object Logout Request (id attribute). +* Add note about the fact that PHP 5.3 is unssuported. +* Add fingerprint algorithm support. +* Add dependences to composer. + +v.2.4.0 +------- +* Fix wrong element order in generated metadata. +* Added SLO with nameID and SessionIndex in demo1. +* Improve isHTTPS method in order to support HTTP_X_FORWARDED_PORT. +* Set optional the XMLvalidation (enable/disable it with wantXMLValidation security setting). + +v.2.3.0 +------- +* Resolve namespace problem. Some IdPs uses saml2p:Response and saml2:Assertion instead of samlp:Response saml:Assertion. +* Improve test and documentation. +* Improve ADFS compatibility. +* Remove unnecessary XSDs files. +* Make available the reason for the saml message invalidation. +* Adding ability to set idp cert once the Setting object initialized. +* Fix status info issue. +* Reject SAML Response if not signed and strict = false. +* Support NameId and SessionIndex in LogoutRequest. +* Add ForceAuh and IsPassive support. + +v.2.2.0 +------- +* Fix bug with Encrypted nameID on LogoutRequest. +* Fixed usability bug. SP will inform about AuthFail status after process a Response. +* Added SessionIndex support on LogoutRequest, and know is accesible from the Auth class. +* LogoutRequest and LogoutResponse classes now accept non deflated xml. +* Improved the XML metadata/ Decrypted Assertion output. (prettyprint). +* Fix bug in formatPrivateKey method, the key could be not RSA. +* Explicit warning message for signed element problem. +* Decrypt method improved. +* Support more algorithm at the SigAlg in the Signed LogoutRequests and LogoutResponses +* AuthNRequest now stores ID (it can be retrieved later). +* Fixed a typo on the 'NameIdPolicy' attribute that appeared at the README and settings_example file. + + +v.2.1.0 +------- + +* The isValid method of the Logout Request is now non-static. (affects processSLO method of Auth.php). +* Logout Request constructor now accepts encoded logout requests. +* Now after validate a message, if fails a method getError of the object will return the cause. +* Fix typos. +* Added extra parameters option to login and logout methods. +* Improve Test (new test, use the new getError method for testing). +* Bugfix namespace problem when getting Attributes. + + +v.2.0.0 +------- + +* New PHP SAML Toolkit (SLO, Sign, Encryptation). + + +v.1.0.0 +------- + +* Old PHP SAML Toolkit. diff --git a/vendor/onelogin/php-saml/LICENSE b/vendor/onelogin/php-saml/LICENSE new file mode 100644 index 00000000..dbbca9c6 --- /dev/null +++ b/vendor/onelogin/php-saml/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2010-2016 OneLogin, Inc. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + diff --git a/vendor/onelogin/php-saml/README.md b/vendor/onelogin/php-saml/README.md new file mode 100644 index 00000000..d552ecb9 --- /dev/null +++ b/vendor/onelogin/php-saml/README.md @@ -0,0 +1,1554 @@ +# OneLogin's SAML PHP Toolkit Compatible with PHP 5.X & 7.X + +[![Build Status](https://api.travis-ci.org/onelogin/php-saml.png?branch=master)](http://travis-ci.org/onelogin/php-saml) [![Coverage Status](https://coveralls.io/repos/onelogin/php-saml/badge.png)](https://coveralls.io/r/onelogin/php-saml) [![License](https://poser.pugx.org/onelogin/php-saml/license.png)](https://packagist.org/packages/onelogin/php-saml) + +Add SAML support to your PHP software using this library. +Forget those complicated libraries and use this open source library provided +and supported by OneLogin Inc. + + +Warning +------- + +Version 3.4.0 introduces the 'rejectUnsolicitedResponsesWithInResponseTo' setting parameter, by default disabled, that will allow invalidate unsolicited SAMLResponse. This version as well will reject SAMLResponse if requestId was provided to the validator but the SAMLResponse does not contain a InResponseTo attribute. And an additional setting parameter 'destinationStrictlyMatches', by default disabled, that will force that the Destination URL should strictly match to the address that process the SAMLResponse. + +Version 3.3.1 updates xmlseclibs to 3.0.4 (CVE-2019-3465), but php-saml was not directly affected since it implements additional checks that prevent to exploit that vulnerability. + +Version 3.3.0 sets strict mode active by default + +Update php-saml to 3.1.0, this version includes a security patch related to XEE attacks. + +This version is compatible with PHP 7.X and does not include xmlseclibs (you will need to install it via composer, dependency described in composer.json) + +Security Guidelines +------------------- + +If you believe you have discovered a security vulnerability in this toolkit, please report it at https://www.onelogin.com/security with a description. We follow responsible disclosure guidelines, and will work with you to quickly find a resolution. + + +Why add SAML support to my software? +------------------------------------ + +SAML is an XML-based standard for web browser single sign-on and is defined by +the OASIS Security Services Technical Committee. The standard has been around +since 2002, but lately it is becoming popular due its advantages: + + * **Usability** - One-click access from portals or intranets, deep linking, + password elimination and automatically renewing sessions make life + easier for the user. + * **Security** - Based on strong digital signatures for authentication and + integrity, SAML is a secure single sign-on protocol that the largest + and most security conscious enterprises in the world rely on. + * **Speed** - SAML is fast. One browser redirect is all it takes to securely + sign a user into an application. + * **Phishing Prevention** - If you don’t have a password for an app, you + can’t be tricked into entering it on a fake login page. + * **IT Friendly** - SAML simplifies life for IT because it centralizes + authentication, provides greater visibility and makes directory + integration easier. + * **Opportunity** - B2B cloud vendor should support SAML to facilitate the + integration of their product. + + +General description +------------------- + +OneLogin's SAML PHP toolkit let you build a SP (Service Provider) over +your PHP application and connect it to any IdP (Identity Provider). + +Supports: + + * SSO and SLO (SP-Initiated and IdP-Initiated). + * Assertion and nameId encryption. + * Assertion signature. + * Message signature: AuthNRequest, LogoutRequest, LogoutResponses. + * Enable an Assertion Consumer Service endpoint. + * Enable a Single Logout Service endpoint. + * Publish the SP metadata (which can be signed). + +Key features: + + * **saml2int** - Implements the SAML 2.0 Web Browser SSO Profile. + * **Session-less** - Forget those common conflicts between the SP and + the final app, the toolkit delegate session in the final app. + * **Easy to use** - Programmer will be allowed to code high-level and + low-level programming, 2 easy to use APIs are available. + * **Tested** - Thoroughly tested. + * **Popular** - OneLogin's customers use it. Many PHP SAML plugins uses it. + +Integrate your PHP toolkit at OneLogin using this guide: [https://developers.onelogin.com/page/saml-toolkit-for-php](https://developers.onelogin.com/page/saml-toolkit-for-php) + +Installation +------------ + +### Dependencies ### + + * `php >= 5.4` and some core extensions like `php-xml`, `php-date`, `php-zlib`. + * `openssl`. Install the openssl library. It handles x509 certificates. + * `gettext`. Install that library and its php driver. It handles translations. + * `curl`. Install that library and its php driver if you plan to use the IdP Metadata parser. + +### Code ### + +#### Option 1. clone the repository from github #### + +git clone git@github.com:onelogin/php-saml.git + +Then pull the 3.X.X branch/tag + +#### Option 2. Download from github #### + +The toolkit is hosted on github. You can download it from: + + * https://github.com/onelogin/php-saml/releases + +Search for 3.X.X releases + +Copy the core of the library inside the php application. (each application has its +structure so take your time to locate the PHP SAML toolkit in the best place). +See the "Guide to add SAML support to my app" to know how. + +Take in mind that the compressed file only contains the main files. +If you plan to play with the demos, use the Option 1. + +#### Option 3. Composer #### + +The toolkit supports [composer](https://getcomposer.org/). You can find the `onelogin/php-saml` package at https://packagist.org/packages/onelogin/php-saml + +In order to import the saml toolkit to your current php project, execute +``` +composer require onelogin/php-saml +``` + +Remember to select the 3.X.X branch + +After installation has completed you will find at the `vendor/` folder a new folder named `onelogin` and inside the `php-saml`. Make sure you are including the autoloader provided by composer. It can be found at `vendor/autoload.php`. + +**Important** In this option, the x509 certs must be stored at `vendor/onelogin/php-saml/certs` +and settings file stored at `vendor/onelogin/php-saml`. + +Your settings are at risk of being deleted when updating packages using `composer update` or similar commands. So it is **highly** recommended that instead of using settings files, you pass the settings as an array directly to the constructor (explained later in this document). If you do not use this approach your settings are at risk of being deleted when updating packages using `composer update` or similar commands. + +Compatibility +------------- + +This 3.X.X supports PHP 7.X. but can be used with PHP >=5.4 as well (5.6.24+ recommended for security reasons). + +Namespaces +---------- + +If you are using the library with a framework like Symfony that contains +namespaces, remember that calls to the class must be done by adding a backslash (`\`) to the +start, for example to use the static method getSelfURLNoQuery use: + + \OneLogin\Saml2\Utils::getSelfURLNoQuery() + + +Security warning +---------------- + +In production, the `strict` parameter **MUST** be set as `"true"` and the +`signatureAlgorithm` and `digestAlgorithm` under `security` must be set to +something other than SHA1 (see https://shattered.io/ ). Otherwise your +environment is not secure and will be exposed to attacks. + +In production also we highly recommended to register on the settings the IdP certificate instead of using the fingerprint method. The fingerprint, is a hash, so at the end is open to a collision attack that can end on a signature validation bypass. Other SAML toolkits deprecated that mechanism, we maintain it for compatibility and also to be used on test environment. + +Getting started +--------------- + +### Knowing the toolkit ### + +The new OneLogin SAML Toolkit contains different folders (`certs`, `endpoints`, +`lib`, `demo`, etc.) and some files. + +Let's start describing the folders: + +#### `certs/` #### + +SAML requires a x509 cert to sign and encrypt elements like `NameID`, `Message`, +`Assertion`, `Metadata`. + +If our environment requires sign or encrypt support, this folder may contain +the x509 cert and the private key that the SP will use: + + * `sp.crt` - The public cert of the SP + * `sp.key` - The private key of the SP + +Or also we can provide those data in the setting file at the `$settings['sp']['x509cert']` +and the `$settings['sp']['privateKey']`. + +Sometimes we could need a signature on the metadata published by the SP, in +this case we could use the x509 cert previously mentioned or use a new x.509 +cert: `metadata.crt` and `metadata.key`. + +Use `sp_new.crt` if you are in a key rollover process and you want to +publish that x509 certificate on Service Provider metadata. + +#### `src/` #### + +This folder contains the heart of the toolkit, the libraries: + + * `Saml2` folder contains the new version of the classes and methods that + are described in a later section. + + +#### `doc/` #### + +This folder contains the API documentation of the toolkit. + + +#### `endpoints/` #### + +The toolkit has three endpoints: + + * `metadata.php` - Where the metadata of the SP is published. + * `acs.php` - Assertion Consumer Service. Processes the SAML Responses. + * `sls.php` - Single Logout Service. Processes Logout Requests and Logout + Responses. + +You can use the files provided by the toolkit or create your own endpoints +files when adding SAML support to your applications. Take in mind that those +endpoints files uses the setting file of the toolkit's base folder. + + +#### `locale/` #### + +Locale folder contains some translations: `en_US` and `es_ES` as a proof of concept. +Currently there are no translations but we will eventually localize the messages +and support multiple languages. + + +#### Other important files #### + +* `settings_example.php` - A template to be used in order to create a + settings.php file which contains the basic configuration info of the toolkit. +* `advanced_settings_example.php` - A template to be used in order to create a + advanced_settings.php file which contains extra configuration info related to + the security, the contact person, and the organization associated to the SP. +* `_toolkit_loader.php` - This file load the toolkit libraries (The SAML2 lib). + + +#### Miscellaneous #### + +* `tests/` - Contains the unit test of the toolkit. +* `demo1/` - Contains an example of a simple PHP app with SAML support. + Read the `Readme.txt` inside for more info. +* `demo2/` - Contains another example. + + +### How it works ### + +#### Settings #### + +First of all we need to configure the toolkit. The SP's info, the IdP's info, +and in some cases, configure advanced security issues like signatures and +encryption. + +There are two ways to provide the settings information: + + * Use a `settings.php` file that we should locate at the base folder of the + toolkit. + * Use an array with the setting data and provide it directly to the + constructor of the class. + + +There is a template file, `settings_example.php`, so you can make a copy of this +file, rename and edit it. + +```php + true, + + // Enable debug mode (to print errors). + 'debug' => false, + + // Set a BaseURL to be used instead of try to guess + // the BaseURL of the view that process the SAML Message. + // Ex http://sp.example.com/ + // http://example.com/sp/ + 'baseurl' => null, + + // Service Provider Data that we are deploying. + 'sp' => array( + // Identifier of the SP entity (must be a URI) + 'entityId' => '', + // Specifies info about where and how the message MUST be + // returned to the requester, in this case our SP. + 'assertionConsumerService' => array( + // URL Location where the from the IdP will be returned + 'url' => '', + // SAML protocol binding to be used when returning the + // message. OneLogin Toolkit supports this endpoint for the + // HTTP-POST binding only. + 'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST', + ), + // If you need to specify requested attributes, set a + // attributeConsumingService. nameFormat, attributeValue and + // friendlyName can be omitted + "attributeConsumingService"=> array( + "serviceName" => "SP test", + "serviceDescription" => "Test Service", + "requestedAttributes" => array( + array( + "name" => "", + "isRequired" => false, + "nameFormat" => "", + "friendlyName" => "", + "attributeValue" => array() + ) + ) + ), + // Specifies info about where and how the message MUST be + // returned to the requester, in this case our SP. + 'singleLogoutService' => array( + // URL Location where the from the IdP will be returned + 'url' => '', + // SAML protocol binding to be used when returning the + // message. OneLogin Toolkit supports the HTTP-Redirect binding + // only for this endpoint. + 'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect', + ), + // Specifies the constraints on the name identifier to be used to + // represent the requested subject. + // Take a look on lib/Saml2/Constants.php to see the NameIdFormat supported. + 'NameIDFormat' => 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress', + // Usually x509cert and privateKey of the SP are provided by files placed at + // the certs folder. But we can also provide them with the following parameters + 'x509cert' => '', + 'privateKey' => '', + + /* + * Key rollover + * If you plan to update the SP x509cert and privateKey + * you can define here the new x509cert and it will be + * published on the SP metadata so Identity Providers can + * read them and get ready for rollover. + */ + // 'x509certNew' => '', + ), + + // Identity Provider Data that we want connected with our SP. + 'idp' => array( + // Identifier of the IdP entity (must be a URI) + 'entityId' => '', + // SSO endpoint info of the IdP. (Authentication Request protocol) + 'singleSignOnService' => array( + // URL Target of the IdP where the Authentication Request Message + // will be sent. + 'url' => '', + // SAML protocol binding to be used when returning the + // message. OneLogin Toolkit supports the HTTP-Redirect binding + // only for this endpoint. + 'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect', + ), + // SLO endpoint info of the IdP. + 'singleLogoutService' => array( + // URL Location of the IdP where SLO Request will be sent. + 'url' => '', + // URL location of the IdP where SLO Response will be sent (ResponseLocation) + // if not set, url for the SLO Request will be used + 'responseUrl' => '', + // SAML protocol binding to be used when returning the + // message. OneLogin Toolkit supports the HTTP-Redirect binding + // only for this endpoint. + 'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect', + ), + // Public x509 certificate of the IdP + 'x509cert' => '', + /* + * Instead of use the whole x509cert you can use a fingerprint in order to + * validate a SAMLResponse, but we don't recommend to use that + * method on production since is exploitable by a collision attack. + * (openssl x509 -noout -fingerprint -in "idp.crt" to generate it, + * or add for example the -sha256 , -sha384 or -sha512 parameter) + * + * If a fingerprint is provided, then the certFingerprintAlgorithm is required in order to + * let the toolkit know which algorithm was used. Possible values: sha1, sha256, sha384 or sha512 + * 'sha1' is the default value. + * + * Notice that if you want to validate any SAML Message sent by the HTTP-Redirect binding, you + * will need to provide the whole x509cert. + */ + // 'certFingerprint' => '', + // 'certFingerprintAlgorithm' => 'sha1', + + /* In some scenarios the IdP uses different certificates for + * signing/encryption, or is under key rollover phase and + * more than one certificate is published on IdP metadata. + * In order to handle that the toolkit offers that parameter. + * (when used, 'x509cert' and 'certFingerprint' values are + * ignored). + */ + // 'x509certMulti' => array( + // 'signing' => array( + // 0 => '', + // ), + // 'encryption' => array( + // 0 => '', + // ) + // ), + ), +); +``` +In addition to the required settings data (IdP, SP), there is extra +information that could be defined. In the same way that a template exists +for the basic info, there is a template for that advanced info located +at the base folder of the toolkit and named `advanced_settings_example.php` +that you can copy and rename it as `advanced_settings.php` + +```php + array( + 'requests' => true, + 'responses' => true + ), + // Security settings + 'security' => array( + + /** signatures and encryptions offered */ + + // Indicates that the nameID of the sent by this SP + // will be encrypted. + 'nameIdEncrypted' => false, + + // Indicates whether the messages sent by this SP + // will be signed. [Metadata of the SP will offer this info] + 'authnRequestsSigned' => false, + + // Indicates whether the messages sent by this SP + // will be signed. + 'logoutRequestSigned' => false, + + // Indicates whether the messages sent by this SP + // will be signed. + 'logoutResponseSigned' => false, + + /* Sign the Metadata + False || True (use sp certs) || array ( + 'keyFileName' => 'metadata.key', + 'certFileName' => 'metadata.crt' + ) + || array ( + 'x509cert' => '', + 'privateKey' => '' + ) + */ + 'signMetadata' => false, + + /** signatures and encryptions required **/ + + // Indicates a requirement for the , + // and elements received by this SP to be signed. + 'wantMessagesSigned' => false, + + // Indicates a requirement for the elements received by + // this SP to be encrypted. + 'wantAssertionsEncrypted' => false, + + // Indicates a requirement for the elements received by + // this SP to be signed. [Metadata of the SP will offer this info] + 'wantAssertionsSigned' => false, + + // Indicates a requirement for the NameID element on the SAMLResponse + // received by this SP to be present. + 'wantNameId' => true, + + // Indicates a requirement for the NameID received by + // this SP to be encrypted. + 'wantNameIdEncrypted' => false, + + // Authentication context. + // Set to false and no AuthContext will be sent in the AuthNRequest. + // Set true or don't present this parameter and you will get an AuthContext 'exact' 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'. + // Set an array with the possible auth context values: array('urn:oasis:names:tc:SAML:2.0:ac:classes:Password', 'urn:oasis:names:tc:SAML:2.0:ac:classes:X509'). + 'requestedAuthnContext' => false, + + // Indicates if the SP will validate all received xmls. + // (In order to validate the xml, 'strict' and 'wantXMLValidation' must be true). + 'wantXMLValidation' => true, + + // If true, SAMLResponses with an empty value at its Destination + // attribute will not be rejected for this fact. + 'relaxDestinationValidation' => false, + + // If true, Destination URL should strictly match to the address to + // which the response has been sent. + // Notice that if 'relaxDestinationValidation' is true an empty Destintation + // will be accepted. + 'destinationStrictlyMatches' => false, + + // If true, the toolkit will not raised an error when the Statement Element + // contain atribute elements with name duplicated + 'allowRepeatAttributeName' => false, + + // If true, SAMLResponses with an InResponseTo value will be rejectd if not + // AuthNRequest ID provided to the validation method. + 'rejectUnsolicitedResponsesWithInResponseTo' => false, + + // Algorithm that the toolkit will use on signing process. Options: + // 'http://www.w3.org/2000/09/xmldsig#rsa-sha1' + // 'http://www.w3.org/2000/09/xmldsig#dsa-sha1' + // 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256' + // 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384' + // 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512' + // Notice that rsa-sha1 is a deprecated algorithm and should not be used + 'signatureAlgorithm' => 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256', + + // Algorithm that the toolkit will use on digest process. Options: + // 'http://www.w3.org/2000/09/xmldsig#sha1' + // 'http://www.w3.org/2001/04/xmlenc#sha256' + // 'http://www.w3.org/2001/04/xmldsig-more#sha384' + // 'http://www.w3.org/2001/04/xmlenc#sha512' + // Notice that sha1 is a deprecated algorithm and should not be used + 'digestAlgorithm' => 'http://www.w3.org/2001/04/xmlenc#sha256', + + // Algorithm that the toolkit will use for encryption process. Options: + // 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc' + // 'http://www.w3.org/2001/04/xmlenc#aes128-cbc' + // 'http://www.w3.org/2001/04/xmlenc#aes192-cbc' + // 'http://www.w3.org/2001/04/xmlenc#aes256-cbc' + // 'http://www.w3.org/2009/xmlenc11#aes128-gcm' + // 'http://www.w3.org/2009/xmlenc11#aes192-gcm' + // 'http://www.w3.org/2009/xmlenc11#aes256-gcm'; + // Notice that aes-cbc are not consider secure anymore so should not be used + 'encryption_algorithm' => 'http://www.w3.org/2009/xmlenc11#aes128-gcm', + + // ADFS URL-Encodes SAML data as lowercase, and the toolkit by default uses + // uppercase. Turn it True for ADFS compatibility on signature verification + 'lowercaseUrlencoding' => false, + ), + + // Contact information template, it is recommended to supply a + // technical and support contacts. + 'contactPerson' => array( + 'technical' => array( + 'givenName' => '', + 'emailAddress' => '' + ), + 'support' => array( + 'givenName' => '', + 'emailAddress' => '' + ), + ), + + // Organization information template, the info in en_US lang is + // recomended, add more if required. + 'organization' => array( + 'en-US' => array( + 'name' => '', + 'displayname' => '', + 'url' => '' + ), + ), +); +``` + +The compression settings allow you to instruct whether or not the IdP can accept +data that has been compressed using [gzip](gzip) ('requests' and 'responses'). +But if we provide a `$deflate` boolean parameter to the `getRequest` or `getResponse` method it will have priority over the compression settings. + +In the security section, you can set the way that the SP will handle the messages +and assertions. Contact the admin of the IdP and ask him what the IdP expects, +and decide what validations will handle the SP and what requirements the SP will have +and communicate them to the IdP's admin too. + +Once we know what kind of data could be configured, let's talk about the way +settings are handled within the toolkit. + +The settings files described (`settings.php` and `advanced_settings.php`) are loaded +by the toolkit if no other array with settings info is provided in the constructor of the toolkit. Let's see some examples. + +```php +// Initializes toolkit with settings.php & advanced_settings files. +$auth = new OneLogin\Saml2\Auth(); +//or +$settings = new OneLogin\Saml2\Settings(); + +// Initializes toolkit with the array provided. +$auth = new OneLogin\Saml2\Auth($settingsInfo); +//or +$settings = new OneLogin\Saml2\Settings($settingsInfo); +``` + +You can declare the `$settingsInfo` in the file that contains the constructor +execution or locate them in any file and load the file in order to get the +array available as we see in the following example: + +```php +login(); // Method that sent the AuthNRequest +``` + +The `AuthNRequest` will be sent signed or unsigned based on the security info +of the `advanced_settings.php` (`'authnRequestsSigned'`). + + +The IdP will then return the SAML Response to the user's client. The client is then forwarded to the Attribute Consumer Service of the SP with this information. If we do not set a `'url'` param in the login method and we are using the default ACS provided by the toolkit (`endpoints/acs.php`), then the ACS endpoint will redirect the user to the file that launched the SSO request. + +We can set a `'returnTo'` url to change the workflow and redirect the user to the other PHP file. + +```php +$newTargetUrl = 'http://example.com/consume2.php'; +$auth = new OneLogin\Saml2\Auth(); +$auth->login($newTargetUrl); +``` + +The login method can receive other six optional parameters: + +* `$parameters` - An array of parameters that will be added to the `GET` in the HTTP-Redirect. +* `$forceAuthn` - When true the `AuthNRequest` will set the `ForceAuthn='true'` +* `$isPassive` - When true the `AuthNRequest` will set the `Ispassive='true'` +* `$strict` - True if we want to stay (returns the url string) False to redirect +* `$setNameIdPolicy` - When true the AuthNRequest will set a nameIdPolicy element. +* `$nameIdValueReq` - Indicates to the IdP the subject that should be authenticated. + +If a match on the future SAMLResponse ID and the AuthNRequest ID to be sent is required, that AuthNRequest ID must to be extracted and saved. + +```php +$ssoBuiltUrl = $auth->login(null, array(), false, false, true); +$_SESSION['AuthNRequestID'] = $auth->getLastRequestID(); +header('Pragma: no-cache'); +header('Cache-Control: no-cache, must-revalidate'); +header('Location: ' . $ssoBuiltUrl); +exit(); +``` + +#### The SP Endpoints #### + +Related to the SP there are three important views: The metadata view, the ACS view and the SLS view. The toolkit +provides examples of those views in the endpoints directory. + +##### SP Metadata `endpoints/metadata.php` ##### + +This code will provide the XML metadata file of our SP, based on the info that we provided in the settings files. + +```php +getSettings(); + $metadata = $settings->getSPMetadata(); + $errors = $settings->validateMetadata($metadata); + if (empty($errors)) { + header('Content-Type: text/xml'); + echo $metadata; + } else { + throw new OneLogin\Saml2\Error( + 'Invalid SP metadata: '.implode(', ', $errors), + OneLogin\Saml2\Error::METADATA_SP_INVALID + ); + } +} catch (Exception $e) { + echo $e->getMessage(); +} +``` +The `getSPMetadata` will return the metadata signed or not based +on the security info of the `advanced_settings.php` (`'signMetadata'`). + +Before the XML metadata is exposed, a check takes place to ensure +that the info to be provided is valid. + +Instead of use the Auth object, you can directly use + +```php +$settings = new OneLogin\Saml2\Settings($settingsInfo, true); +``` +to get the settings object and with the true parameter we will avoid the IdP Settings validation. + + +##### Attribute Consumer Service(ACS) `endpoints/acs.php` ##### + +This code handles the SAML response that the IdP forwards to the SP through the user's client. + +```php +processResponse($requestID); +unset($_SESSION['AuthNRequestID']); + +$errors = $auth->getErrors(); + +if (!empty($errors)) { + echo '

' . implode(', ', $errors) . '

'; + exit(); +} + +if (!$auth->isAuthenticated()) { + echo "

Not authenticated

"; + exit(); +} + +$_SESSION['samlUserdata'] = $auth->getAttributes(); +$_SESSION['samlNameId'] = $auth->getNameId(); +$_SESSION['samlNameIdFormat'] = $auth->getNameIdFormat(); +$_SESSION['samlNameidNameQualifier'] = $auth->getNameIdNameQualifier(); +$_SESSION['samlNameidSPNameQualifier'] = $auth->getNameIdSPNameQualifier(); +$_SESSION['samlSessionIndex'] = $auth->getSessionIndex(); + +if (isset($_POST['RelayState']) && OneLogin\Saml2\Utils::getSelfURL() != $_POST['RelayState']) { + $auth->redirectTo($_POST['RelayState']); +} + +$attributes = $_SESSION['samlUserdata']; +$nameId = $_SESSION['samlNameId']; + +echo '

Identified user: '. htmlentities($nameId) .'

'; + +if (!empty($attributes)) { + echo '

' . _('User attributes:') . '

'; + echo ''; + foreach ($attributes as $attributeName => $attributeValues) { + echo ''; + } + echo '
' . _('Name') . '' . _('Values') . '
' . htmlentities($attributeName) . '
    '; + foreach ($attributeValues as $attributeValue) { + echo '
  • ' . htmlentities($attributeValue) . '
  • '; + } + echo '
'; +} else { + echo _('No attributes found.'); +} +``` + +The SAML response is processed and then checked that there are no errors. +It also verifies that the user is authenticated and stored the userdata in session. + +At that point there are two possible alternatives: + + 1. If no `RelayState` is provided, we could show the user data in this view + or however we wanted. + + 2. If `RelayState` is provided, a redirection takes place. + +Notice that we saved the user data in the session before the redirection to +have the user data available at the `RelayState` view. + + +###### The `getAttributes` method ###### + +In order to retrieve attributes we can use: + +```php +$attributes = $auth->getAttributes(); +``` + +With this method we get all the user data provided by the IdP in the Assertion +of the SAML Response. + +If we execute ```print_r($attributes)``` we could get: + +```php +Array +( + [cn] => Array + ( + [0] => John + ) + [sn] => Array + ( + [0] => Doe + ) + [mail] => Array + ( + [0] => john.doe@example.com + ) + [groups] => Array + ( + [0] => users + [1] => members + ) +) +``` + +Each attribute name can be used as an index into `$attributes` to obtain the value. Every attribute value +is an array - a single-valued attribute is an array of a single element. + + +The following code is equivalent: + +```php +$attributes = $auth->getAttributes(); +print_r($attributes['cn']); +``` + +```php +print_r($auth->getAttribute('cn')); +``` + + +Before trying to get an attribute, check that the user is +authenticated. If the user isn't authenticated or if there were +no attributes in the SAML assertion, an empty array will be +returned. For example, if we call to `getAttributes` before a +`$auth->processResponse`, the `getAttributes()` will return an +empty array. + + +##### Single Logout Service (SLS) `endpoints/sls.php` ##### + +This code handles the Logout Request and the Logout Responses. + +```php +processSLO(false, $requestID); + +$errors = $auth->getErrors(); + +if (empty($errors)) { + echo 'Sucessfully logged out'; +} else { + echo implode(', ', $errors); +} +``` + +If the SLS endpoints receives a Logout Response, the response is +validated and the session could be closed + + + +```php +// part of the processSLO method + +$logoutResponse = new OneLogin\Saml2\LogoutResponse($this->_settings, $_GET['SAMLResponse']); +if (!$logoutResponse->isValid($requestId)) { + $this->_errors[] = 'invalid_logout_response'; +} else if ($logoutResponse->getStatus() !== OneLogin\Saml2\Constants::STATUS_SUCCESS) { + $this->_errors[] = 'logout_not_success'; +} else { + if (!$keepLocalSession) { + OneLogin\Saml2\Utils::deleteLocalSession(); + } +} +``` + +If the SLS endpoints receives an Logout Request, the request is validated, +the session is closed and a Logout Response is sent to the SLS endpoint of +the IdP. + +```php +// part of the processSLO method + +$decoded = base64_decode($_GET['SAMLRequest']); +$request = gzinflate($decoded); +if (!OneLogin\Saml2\LogoutRequest::isValid($this->_settings, $request)) { + $this->_errors[] = 'invalid_logout_request'; +} else { + if (!$keepLocalSession) { + OneLogin\Saml2\Utils::deleteLocalSession(); + } + + $inResponseTo = $request->id; + $responseBuilder = new OneLogin\Saml2\LogoutResponse($this->_settings); + $responseBuilder->build($inResponseTo); + $logoutResponse = $responseBuilder->getResponse(); + + $parameters = array('SAMLResponse' => $logoutResponse); + if (isset($_GET['RelayState'])) { + $parameters['RelayState'] = $_GET['RelayState']; + } + + $security = $this->_settings->getSecurityData(); + if (isset($security['logoutResponseSigned']) && $security['logoutResponseSigned']) { + $signature = $this->buildResponseSignature($logoutResponse, $parameters['RelayState'], $security['signatureAlgorithm']); + $parameters['SigAlg'] = $security['signatureAlgorithm']; + $parameters['Signature'] = $signature; + } + + $this->redirectTo($this->getSLOurl(), $parameters); +} +``` + +If you aren't using the default PHP session, or otherwise need a manual +way to destroy the session, you can pass a callback method to the +`processSLO` method as the fourth parameter + +```php +$keepLocalSession = False; +$callback = function () { + // Destroy user session +}; + +$auth->processSLO($keepLocalSession, null, false, $callback); +``` + + +If we don't want that `processSLO` to destroy the session, pass a true +parameter to the `processSLO` method + +```php +$keepLocalSession = True; +$auth->processSLO($keepLocalSession); +``` + +#### Initiate SLO #### + +In order to send a Logout Request to the IdP: + +```php +logout(); // Method that sent the Logout Request. +``` + +Also there are eight optional parameters that can be set: +* `$returnTo` - The target URL the user should be returned to after logout. +* `$parameters` - Extra parameters to be added to the GET. +* `$name_id` - That will be used to build the LogoutRequest. If `name_id` parameter is not set and the auth object processed a +SAML Response with a `NameId`, then this `NameId` will be used. +* `$session_index` - SessionIndex that identifies the session of the user. +* `$stay` - True if we want to stay (returns the url string) False to redirect. +* `$nameIdFormat` - The NameID Format will be set in the LogoutRequest. +* `$nameIdNameQualifier` - The NameID NameQualifier will be set in the LogoutRequest. +* `$nameIdSPNameQualifier` - The NameID SP NameQualifier will be set in the LogoutRequest. + +The Logout Request will be sent signed or unsigned based on the security +info of the `advanced_settings.php` (`'logoutRequestSigned'`). + +The IdP will return the Logout Response through the user's client to the +Single Logout Service of the SP. +If we do not set a `'url'` param in the logout method and are using the +default SLS provided by the toolkit (`endpoints/sls.php`), then the SLS +endpoint will redirect the user to the file that launched the SLO request. + +We can set an `'returnTo'` url to change the workflow and redirect the user +to other php file. + +```php +$newTargetUrl = 'http://example.com/loggedOut.php'; +$auth = new OneLogin\Saml2\Auth(); +$auth->logout($newTargetUrl); +``` +A more complex logout with all the parameters: +``` +$auth = new OneLogin\Saml2\Auth(); +$returnTo = null; +$parameters = array(); +$nameId = null; +$sessionIndex = null; +$nameIdFormat = null; +$nameIdNameQualifier = null; +$nameIdSPNameQualifier = null; + +if (isset($_SESSION['samlNameId'])) { + $nameId = $_SESSION['samlNameId']; +} +if (isset($_SESSION['samlSessionIndex'])) { + $sessionIndex = $_SESSION['samlSessionIndex']; +} +if (isset($_SESSION['samlNameIdFormat'])) { + $nameIdFormat = $_SESSION['samlNameIdFormat']; +} +if (isset($_SESSION['samlNameIdNameQualifier'])) { + $nameIdNameQualifier = $_SESSION['samlNameIdNameQualifier']; +} +if (isset($_SESSION['samlNameIdSPNameQualifier'])) { + $nameIdSPNameQualifier = $_SESSION['samlNameIdSPNameQualifier']; +} +$auth->logout($returnTo, $parameters, $nameId, $sessionIndex, false, $nameIdFormat, $nameIdNameQualifier, $nameIdSPNameQualifier); +``` + +If a match on the future LogoutResponse ID and the LogoutRequest ID to be sent is required, that LogoutRequest ID must to be extracted and stored. + +```php +$sloBuiltUrl = $auth->logout(null, $parameters, $nameId, $sessionIndex, true); +$_SESSION['LogoutRequestID'] = $auth->getLastRequestID(); +header('Pragma: no-cache'); +header('Cache-Control: no-cache, must-revalidate'); +header('Location: ' . $sloBuiltUrl); +exit(); +``` + +#### Example of a view that initiates the SSO request and handles the response (is the acs target) #### + +We can code a unique file that initiates the SSO process, handle the response, get the attributes, initiate +the SLO and processes the logout response. + +Note: Review the `demo1` folder that contains that use case; in a later section we +explain the demo1 use case further in detail. + +```php +login(); +} else if (isset($_GET['sso2'])) { // Another SSO action + $returnTo = $spBaseUrl.'/demo1/attrs.php'; // but set a custom RelayState URL + $auth->login($returnTo); +} else if (isset($_GET['slo'])) { // SLO action. Will sent a Logout Request to IdP + $auth->logout(); +} else if (isset($_GET['acs'])) { // Assertion Consumer Service + $auth->processResponse(); // Process the Response of the IdP, get the + // attributes and put then at + // $_SESSION['samlUserdata'] + + $errors = $auth->getErrors(); // This method receives an array with the errors + // that could took place during the process + + if (!empty($errors)) { + echo '

' . implode(', ', $errors) . '

'; + } + // This check if the response was + if (!$auth->isAuthenticated()) { // sucessfully validated and the user + echo '

Not authenticated

'; // data retrieved or not + exit(); + } + + $_SESSION['samlUserdata'] = $auth->getAttributes(); // Retrieves user data + if (isset($_POST['RelayState']) && OneLogin\Saml2\Utils::getSelfURL() != $_POST['RelayState']) { + $auth->redirectTo($_POST['RelayState']); // Redirect if there is a + } // relayState set +} else if (isset($_GET['sls'])) { // Single Logout Service + $auth->processSLO(); // Process the Logout Request & Logout Response + $errors = $auth->getErrors(); // Retrieves possible validation errors + if (empty($errors)) { + echo '

Sucessfully logged out

'; + } else { + echo '

' . implode(', ', $errors) . '

'; + } +} + +if (isset($_SESSION['samlUserdata'])) { // If there is user data we print it. + if (!empty($_SESSION['samlUserdata'])) { + $attributes = $_SESSION['samlUserdata']; + echo 'You have the following attributes:
'; + echo ''; + foreach ($attributes as $attributeName => $attributeValues) { + echo ''; + } + echo '
NameValues
' . htmlentities($attributeName) . '
    '; + foreach ($attributeValues as $attributeValue) { + echo '
  • ' . htmlentities($attributeValue) . '
  • '; + } + echo '
'; + } else { // If there is not user data, we notify + echo "

You don't have any attribute

"; + } + + echo '

Logout

'; // Print some links with possible +} else { // actions + echo '

Login

'; + echo '

Login and access to attrs.php page

'; +} +``` + +#### URL-guessing methods #### + +php-saml toolkit uses a bunch of methods in OneLogin\Saml2\Utils that try to guess the URL where the SAML messages are processed. + +* `getSelfHost` Returns the current host. +* `getSelfPort` Return the port number used for the request +* `isHTTPS` Checks if the protocol is https or http. +* `getSelfURLhost` Returns the protocol + the current host + the port (if different than common ports). +* `getSelfURL` Returns the URL of the current host + current view + query. +* `getSelfURLNoQuery` Returns the URL of the current host + current view. +* `getSelfRoutedURLNoQuery` Returns the routed URL of the current host + current view. + +getSelfURLNoQuery and getSelfRoutedURLNoQuery are used to calculate the currentURL in order to validate SAML elements like Destination or Recipient. + +When the PHP application is behind a proxy or a load balancer we can execute `setProxyVars(true)` and `setSelfPort` and `isHTTPS` will take care of the `$_SERVER["HTTP_X_FORWARDED_PORT"]` and `$_SERVER['HTTP_X_FORWARDED_PROTO']` vars (otherwise they are ignored). + +Also a developer can use `setSelfProtocol`, `setSelfHost`, `setSelfPort` and `getBaseURLPath` to define a specific value to be returned by `isHTTPS`, `getSelfHost`, `getSelfPort` and `getBaseURLPath`. And define a `setBasePath` to be used on the `getSelfURL` and `getSelfRoutedURLNoQuery` to replace the data extracted from `$_SERVER["REQUEST_URI"]`. + +At the settings the developer will be able to set a `'baseurl'` parameter that automatically will use `setBaseURL` to set values for `setSelfProtocol`, `setSelfHost`, `setSelfPort` and `setBaseURLPath`. + + +### Working behind load balancer ### + +Is possible that asserting request URL and Destination attribute of SAML response fails when working behind load balancer with SSL offload. + +You should be able to workaround this by configuring your server so that it is aware of the proxy and returns the original url when requested. + +Or by using the method described on the previous section. + + +### SP Key rollover ### + +If you plan to update the SP x509cert and privateKey you can define the new x509cert as `$settings['sp']['x509certNew']` and it will be +published on the SP metadata so Identity Providers can read them and get ready for rollover. + + +### IdP with multiple certificates ### + +In some scenarios the IdP uses different certificates for +signing/encryption, or is under key rollover phase and more than one certificate is published on IdP metadata. + +In order to handle that the toolkit offers the `$settings['idp']['x509certMulti']` parameter. + +When that parameter is used, `'x509cert'` and `'certFingerprint'` values will be ignored by the toolkit. + +The `x509certMulti` is an array with 2 keys: +- `signing`. An array of certs that will be used to validate IdP signature +- `encryption` An array with one unique cert that will be used to encrypt data to be sent to the IdP + + +### Replay attacks ### + +In order to avoid replay attacks, you can store the ID of the SAML messages already processed, to avoid processing them twice. Since the Messages expires and will be invalidated due that fact, you don't need to store those IDs longer than the time frame that you currently accepting. + +Get the ID of the last processed message/assertion with the `getLastMessageId`/`getLastAssertionId` methods of the Auth object. + + +### Main classes and methods ### + +Described below are the main classes and methods that can be invoked. + +#### Saml2 library #### + +Lets describe now the classes and methods of the SAML2 library. + +##### OneLogin\Saml2\Auth - Auth.php ##### + +Main class of OneLogin PHP Toolkit + + * `Auth` - Initializes the SP SAML instance + * `login` - Initiates the SSO process. + * `logout` - Initiates the SLO process. + * `processResponse` - Process the SAML Response sent by the IdP. + * `processSLO` - Process the SAML Logout Response / Logout Request sent by the + IdP. + * `redirectTo` - Redirects the user to the url past by parameter or to the url + that we defined in our SSO Request. + * `isAuthenticated` - Checks if the user is authenticated or not. + * `getAttributes` - Returns the set of SAML attributes. + * `getAttribute` - Returns the requested SAML attribute + * `getNameId` - Returns the nameID + * `getNameIdFormat` - Gets the NameID Format provided by the SAML response from the IdP. + * `getNameIdNameQualifier` - Gets the NameID NameQualifier provided from the SAML Response String. + * `getNameIdSPNameQualifier` - Gets the NameID SP NameQualifier provided from the SAML Response String. + * `getSessionIndex` - Gets the SessionIndex from the AuthnStatement. + * `getErrors` - Returns if there were any error + * `getSSOurl` - Gets the SSO url. + * `getSLOurl` - Gets the SLO url. + * `getLastRequestID` - The ID of the last Request SAML message generated. + * `buildRequestSignature` - Generates the Signature for a SAML Request + * `buildResponseSignature` - Generates the Signature for a SAML Response + * `getSettings` - Returns the settings info + * `setStrict` - Set the strict mode active/disable + * `getLastRequestID` - Gets the ID of the last AuthNRequest or LogoutRequest generated by the Service Provider. + * `getLastRequestXML` - Returns the most recently-constructed/processed XML SAML request (AuthNRequest, LogoutRequest) + * `getLastResponseXML` - Returns the most recently-constructed/processed XML SAML response (SAMLResponse, LogoutResponse). If the SAMLResponse had an encrypted assertion, decrypts it. + + +##### OneLogin\Saml2\AuthnRequest - `AuthnRequest.php` ##### + +SAML 2 Authentication Request class + + * `AuthnRequest` - Constructs the `AuthnRequest` object. + * `getRequest` - Returns deflated, base64 encoded, unsigned `AuthnRequest`. + * `getId` - Returns the `AuthNRequest` ID. + * `getXML` - Returns the XML that will be sent as part of the request. + +##### OneLogin\Saml2\Response - `Response.php` ##### + +SAML 2 Authentication Response class + + * `Response` - Constructs the SAML Response object. + * `isValid` - Determines if the SAML Response is valid using the certificate. + * `checkStatus` - Checks if the Status is success. + * `getAudiences` - Gets the audiences. + * `getIssuers` - Gets the Issuers (from Response and Assertion) + * `getNameIdData` - Gets the NameID Data provided by the SAML response from the + IdP. + * `getNameId` - Gets the NameID provided by the SAML response from the IdP. + * `getNameIdFormat` - Gets the NameID Format provided by the SAML response from the IdP. + * `getNameIdNameQualifier` - Gets the NameID NameQualifier provided from the SAML Response String. + * `getNameIdSPNameQualifier` - Gets the NameID SP NameQualifier provided from the SAML Response String. + * `getSessionNotOnOrAfter` - Gets the SessionNotOnOrAfter from the + AuthnStatement + * `getSessionIndex` - Gets the SessionIndex from the AuthnStatement. + * `getAttributes` - Gets the Attributes from the AttributeStatement element. + * `validateNumAssertions` - Verifies that the document only contains a single + Assertion (encrypted or not). + * `validateTimestamps` - Verifies that the document is still valid according + Conditions Element. + * `getError` - After executing a validation process, if it fails, this method returns the cause + * `getXMLDocument` - Returns the SAML Response document (If contains an encrypted assertion, decrypts it) + +##### OneLogin\Saml2\LogoutRequest - `LogoutRequest.php` ##### + +SAML 2 Logout Request class + + * `LogoutRequest` - Constructs the Logout Request object. + * `getRequest` - Returns the Logout Request defated, base64encoded, unsigned + * `getID` - Returns the ID of the Logout Request. (If you have the object you can access to the id attribute) + * `getNameIdData` - Gets the NameID Data of the the Logout Request. + * `getNameId` - Gets the NameID of the Logout Request. + * `getIssuer` - Gets the Issuer of the Logout Request. + * `getSessionIndexes` - Gets the SessionIndexes from the Logout Request. + * `isValid` - Checks if the Logout Request received is valid. + * `getError` - After executing a validation process, if it fails, this method returns the cause + * `getXML` - Returns the XML that will be sent as part of the request or that was received at the SP. + +##### OneLogin\Saml2\LogoutResponse - `LogoutResponse.php` ##### + +SAML 2 Logout Response class + + * `LogoutResponse` - Constructs a Logout Response object + (Initialize params from settings and if provided load the Logout Response) + * `getIssuer` - Gets the Issuer of the Logout Response. + * `getStatus` - Gets the Status of the Logout Response. + * `isValid` - Determines if the SAML LogoutResponse is valid + * `build` - Generates a Logout Response object. + * `getResponse` - Returns a Logout Response object. + * `getError` - After executing a validation process, if it fails, this method returns the cause. + * `getXML` - Returns the XML that will be sent as part of the response or that was received at the SP. + +##### OneLogin\Saml2\Settings - `Settings.php` ##### + +Configuration of the OneLogin PHP Toolkit + + * `Settings` - Initializes the settings: Sets the paths of + the different folders and Loads settings info from settings file or + array/object provided + * `checkSettings` - Checks the settings info. + * `getBasePath` - Returns base path. + * `getCertPath` - Returns cert path. + * `getLibPath` - Returns lib path. + * `getExtLibPath` - Returns external lib path. + * `getSchemasPath` - Returns schema path. + * `checkSPCerts` - Checks if the x509 certs of the SP exists and are valid. + * `getSPkey` - Returns the x509 private key of the SP. + * `getSPcert` - Returns the x509 public cert of the SP. + * `getSPcertNew` - Returns the future x509 public cert of the SP. + * `getIdPData` - Gets the IdP data. + * `getSPData`Gets the SP data. + * `getSecurityData` - Gets security data. + * `getContacts` - Gets contact data. + * `getOrganization` - Gets organization data. + * `getSPMetadata` - Gets the SP metadata. The XML representation. + * `validateMetadata` - Validates an XML SP Metadata. + * `formatIdPCert` - Formats the IdP cert. + * `formatSPCert` - Formats the SP cert. + * `formatSPCertNew` - Formats the SP cert new. + * `formatSPKey` - Formats the SP private key. + * `getErrors` - Returns an array with the errors, the array is empty when + the settings is ok. + * `getLastErrorReason` - Returns the reason of the last error + * `getBaseURL` - Returns the baseurl set on the settings if any. + * `setBaseURL` - Set a baseurl value + * `setStrict` - Activates or deactivates the strict mode. + * `isStrict` - Returns if the 'strict' mode is active. + * `isDebugActive` - Returns if the debug is active. + +##### OneLogin\Saml2\Metadata - `Metadata.php` ##### + +A class that contains functionality related to the metadata of the SP + +* `builder` - Generates the metadata of the SP based on the settings. +* `signmetadata` - Signs the metadata with the key/cert provided +* `addX509KeyDescriptors` - Adds the x509 descriptors (sign/encriptation) to + the metadata + +##### OneLogin\Saml2\Utils - `Utils.php` ##### + +Auxiliary class that contains several methods + + * `validateXML` - This function attempts to validate an XML string against + the specified schema. + * `formatCert` - Returns a x509 cert (adding header & footer if required). + * `formatPrivateKey` - returns a RSA private key (adding header & footer if required). + * `redirect` - Executes a redirection to the provided url (or return the + target url). + * `isHTTPS` - Checks if https or http. + * `getSelfHost` - Returns the current host. + * `getSelfURLhost` - Returns the protocol + the current host + the port + (if different than common ports). + * `getSelfURLNoQuery` - Returns the URL of the current host + current view. + * `getSelfURL` - Returns the URL of the current host + current view + query. + * `generateUniqueID` - Generates a unique string (used for example as ID + for assertions). + * `parseTime2SAML` - Converts a UNIX timestamp to SAML2 timestamp on the + form `yyyy-mm-ddThh:mm:ss(\.s+)?Z`. + * `parseSAML2Time` - Converts a SAML2 timestamp on the form + `yyyy-mm-ddThh:mm:ss(\.s+)?Z` to a UNIX timestamp. The sub-second part is + ignored. + * `parseDuration` - Interprets a ISO8601 duration value relative to a given + timestamp. + * `getExpireTime` - Compares two dates and returns the earliest. + * `query` - Extracts nodes from the DOMDocument. + * `isSessionStarted` - Checks if the session is started or not. + * `deleteLocalSession` - Deletes the local session. + * `calculateX509Fingerprint` - Calculates the fingerprint of a x509cert. + * `formatFingerPrint` - Formats a fingerprint. + * `generateNameId` - Generates a `nameID`. + * `getStatus` - Gets Status from a Response. + * `decryptElement` - Decrypts an encrypted element. + * `castKey` - Converts a `XMLSecurityKey` to the correct algorithm. + * `addSign` - Adds signature key and senders certificate to an element + (Message or Assertion). + * `validateSign` - Validates a signature (Message or Assertion). + +##### OneLogin\Saml2\IdPMetadataParser - `IdPMetadataParser.php` ##### + +Auxiliary class that contains several methods to retrieve and process IdP metadata + + * `parseRemoteXML` - Get IdP Metadata Info from URL. + * `parseFileXML` - Get IdP Metadata Info from File. + * `parseXML` - Get IdP Metadata Info from XML. + * `injectIntoSettings` - Inject metadata info into php-saml settings array. + + +For more info, look at the source code; each method is documented and details +about what it does and how to use it are provided. Make sure to also check the doc folder where +HTML documentation about the classes and methods is provided for SAML and +SAML2. + + +Demos included in the toolkit +----------------------------- + +The toolkit includes three demo apps to teach how use the toolkit, take a look on it. + +Demos require that SP and IdP are well configured before test it. + +## Demo1 ## + +### SP setup ### + +The Onelogin's PHP Toolkit allows you to provide the settings info in two ways: + + * Use a `settings.php` file that we should locate at the base folder of the + toolkit. + * Use an array with the setting data. + +In this demo we provide the data in the second way, using a setting array named +`$settingsInfo`. This array users the `settings_example.php` included as a template +to create the `settings.php` settings and store it in the `demo1/` folder. +Configure the SP part and later review the metadata of the IdP and complete the IdP info. + +If you check the code of the index.php file you will see that the `settings.php` +file is loaded in order to get the `$settingsInfo` var to be used in order to initialize +the `Setting` class. + +Notice that in this demo, the `setting.php` file that could be defined at the base +folder of the toolkit is ignored and the libs are loaded using the +`_toolkit_loader.php` located at the base folder of the toolkit. + + +### IdP setup ### + +Once the SP is configured, the metadata of the SP is published at the +`metadata.php` file. Configure the IdP based on that information. + + +### How it works ### + + 1. First time you access to `index.php` view, you can select to login and return + to the same view or login and be redirected to the `attrs.php` view. + + 2. When you click: + + 2.1 in the first link, we access to (`index.php?sso`) an `AuthNRequest` + is sent to the IdP, we authenticate at the IdP and then a Response is sent + through the user's client to the SP, specifically the Assertion Consumer Service view: `index.php?acs`. + Notice that a `RelayState` parameter is set to the url that initiated the + process, the `index.php` view. + + 2.2 in the second link we access to (`attrs.php`) have the same process + described at 2.1 with the difference that as `RelayState` is set the `attrs.php`. + + 3. The SAML Response is processed in the ACS (`index.php?acs`), if the Response + is not valid, the process stops here and a message is shown. Otherwise we + are redirected to the RelayState view. a) `index.php` or b) `attrs.php`. + + 4. We are logged in the app and the user attributes are showed. + At this point, we can test the single log out functionality. + + 5. The single log out functionality could be tested by two ways. + + 5.1 SLO Initiated by SP. Click on the "logout" link at the SP, after that a + Logout Request is sent to the IdP, the session at the IdP is closed and + replies through the client to the SP with a Logout Response (sent to the + Single Logout Service endpoint). The SLS endpoint (`index.php?sls`) of the SP + process the Logout Response and if is valid, close the user session of the + local app. Notice that the SLO Workflow starts and ends at the SP. + + 5.2 SLO Initiated by IdP. In this case, the action takes place on the IdP + side, the logout process is initiated at the idP, sends a Logout + Request to the SP (SLS endpoint, `index.php?sls`). The SLS endpoint of the SP + process the Logout Request and if is valid, close the session of the user + at the local app and send a Logout Response to the IdP (to the SLS endpoint + of the IdP). The IdP receives the Logout Response, process it and close the + session at of the IdP. Notice that the SLO Workflow starts and ends at the IdP. + +Notice that all the SAML Requests and Responses are handled by a unique file, +the `index.php` file and how `GET` parameters are used to know the action that +must be done. + + +## Demo2 ## + +### SP setup ### + +The Onelogin's PHP Toolkit allows you to provide the settings info in two ways: + + * Use a `settings.php` file that we should locate at the base folder of the + toolkit. + * Use an array with the setting data. + +The first is the case of the demo2 app. The `setting.php` file and the +`setting_extended.php` file should be defined at the base folder of the toolkit. +Review the `setting_example.php` and the `advanced_settings_example.php` to +learn how to build them. + +In this case as Attribute Consume Service and Single Logout Service we are going to +use the files located in the endpoint folder (`acs.php` and `sls.php`). + + +### IdP setup ### + +Once the SP is configured, the metadata of the SP is published at the +`metadata.php` file. Based on that info, configure the IdP. + + +### How it works ### + +At demo1, we saw how all the SAML Request and Responses were handler at an +unique file, the `index.php` file. This demo1 uses high-level programming. + +At demo2, we have several views: `index.php`, `sso.php`, `slo.php`, `consume.php` +and `metadata.php`. As we said, we will use the endpoints that are defined +in the toolkit (`acs.php`, `sls.php` of the endpoints folder). This demo2 uses +low-level programming. + +Notice that the SSO action can be initiated at `index.php` or `sso.php`. + +The SAML workflow that take place is similar that the workflow defined in the +demo1, only changes the targets. + + 1. When you access `index.php` or `sso.php` for the first time, an `AuthNRequest` is + sent to the IdP automatically, (as `RelayState` is sent the origin url). + We authenticate at the IdP and then a `Response` is sent to the SP, to the + ACS endpoint, in this case `acs.php` of the endpoints folder. + + 2. The SAML Response is processed in the ACS, if the `Response` is not valid, + the process stops here and a message is shown. Otherwise we are redirected + to the `RelayState` view (`sso.php` or `index.php`). The `sso.php` detects if the + user is logged and redirects to `index.php`, so we will be in the + `index.php` at the end. + + 3. We are logged into the app and the user attributes (if any) are shown. + At this point, we can test the single log out functionality. + + 4. The single log out functionality could be tested by two ways. + + 4.1 SLO Initiated by SP. Click on the "logout" link at the SP, after that + we are redirected to the `slo.php` view and there a Logout Request is sent + to the IdP, the session at the IdP is closed and replies to the SP a + Logout Response (sent to the Single Logout Service endpoint). In this case + The SLS endpoint of the SP process the Logout Response and if is + valid, close the user session of the local app. Notice that the SLO + Workflow starts and ends at the SP. + + 4.2 SLO Initiated by IdP. In this case, the action takes place on the IdP + side, the logout process is initiated at the idP, sends a Logout + Request to the SP (SLS endpoint `sls.php` of the endpoint folder). + The SLS endpoint of the SP process the Logout Request and if is valid, + close the session of the user at the local app and sends a Logout Response + to the IdP (to the SLS endpoint of the IdP).The IdP receives the Logout + Response, process it and close the session at of the IdP. Notice that the + SLO Workflow starts and ends at the IdP. + diff --git a/vendor/onelogin/php-saml/_toolkit_loader.php b/vendor/onelogin/php-saml/_toolkit_loader.php new file mode 100644 index 00000000..c4649d76 --- /dev/null +++ b/vendor/onelogin/php-saml/_toolkit_loader.php @@ -0,0 +1,34 @@ + array( + 'requests' => true, + 'responses' => true + ), + + // Security settings + 'security' => array( + + /** signatures and encryptions offered */ + + // Indicates that the nameID of the sent by this SP + // will be encrypted. + 'nameIdEncrypted' => false, + + // Indicates whether the messages sent by this SP + // will be signed. [The Metadata of the SP will offer this info] + 'authnRequestsSigned' => false, + + // Indicates whether the messages sent by this SP + // will be signed. + 'logoutRequestSigned' => false, + + // Indicates whether the messages sent by this SP + // will be signed. + 'logoutResponseSigned' => false, + + /* Sign the Metadata + False || True (use sp certs) || array ( + 'keyFileName' => 'metadata.key', + 'certFileName' => 'metadata.crt' + ) + || array ( + 'x509cert' => '', + 'privateKey' => '' + ) + */ + 'signMetadata' => false, + + + /** signatures and encryptions required **/ + + // Indicates a requirement for the , and + // elements received by this SP to be signed. + 'wantMessagesSigned' => false, + + // Indicates a requirement for the elements received by + // this SP to be encrypted. + 'wantAssertionsEncrypted' => false, + + // Indicates a requirement for the elements received by + // this SP to be signed. [The Metadata of the SP will offer this info] + 'wantAssertionsSigned' => false, + + // Indicates a requirement for the NameID element on the SAMLResponse received + // by this SP to be present. + 'wantNameId' => true, + + // Indicates a requirement for the NameID received by + // this SP to be encrypted. + 'wantNameIdEncrypted' => false, + + // Authentication context. + // Set to false and no AuthContext will be sent in the AuthNRequest, + // Set true or don't present this parameter and you will get an AuthContext 'exact' 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport' + // Set an array with the possible auth context values: array('urn:oasis:names:tc:SAML:2.0:ac:classes:Password', 'urn:oasis:names:tc:SAML:2.0:ac:classes:X509'), + 'requestedAuthnContext' => false, + + // Allows the authn comparison parameter to be set, defaults to 'exact' if + // the setting is not present. + 'requestedAuthnContextComparison' => 'exact', + + // Indicates if the SP will validate all received xmls. + // (In order to validate the xml, 'strict' and 'wantXMLValidation' must be true). + 'wantXMLValidation' => true, + + // If true, SAMLResponses with an empty value at its Destination + // attribute will not be rejected for this fact. + 'relaxDestinationValidation' => false, + + // If true, Destination URL should strictly match to the address to + // which the response has been sent. + // Notice that if 'relaxDestinationValidation' is true an empty Destintation + // will be accepted. + 'destinationStrictlyMatches' => false, + + // If true, the toolkit will not raised an error when the Statement Element + // contain atribute elements with name duplicated + 'allowRepeatAttributeName' => false, + + // If true, SAMLResponses with an InResponseTo value will be rejectd if not + // AuthNRequest ID provided to the validation method. + 'rejectUnsolicitedResponsesWithInResponseTo' => false, + + // Algorithm that the toolkit will use on signing process. Options: + // 'http://www.w3.org/2000/09/xmldsig#rsa-sha1' + // 'http://www.w3.org/2000/09/xmldsig#dsa-sha1' + // 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256' + // 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384' + // 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512' + // Notice that rsa-sha1 is a deprecated algorithm and should not be used + 'signatureAlgorithm' => 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256', + + // Algorithm that the toolkit will use on digest process. Options: + // 'http://www.w3.org/2000/09/xmldsig#sha1' + // 'http://www.w3.org/2001/04/xmlenc#sha256' + // 'http://www.w3.org/2001/04/xmldsig-more#sha384' + // 'http://www.w3.org/2001/04/xmlenc#sha512' + // Notice that sha1 is a deprecated algorithm and should not be used + 'digestAlgorithm' => 'http://www.w3.org/2001/04/xmlenc#sha256', + + // Algorithm that the toolkit will use for encryption process. Options: + // 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc' + // 'http://www.w3.org/2001/04/xmlenc#aes128-cbc' + // 'http://www.w3.org/2001/04/xmlenc#aes192-cbc' + // 'http://www.w3.org/2001/04/xmlenc#aes256-cbc' + // 'http://www.w3.org/2009/xmlenc11#aes128-gcm' + // 'http://www.w3.org/2009/xmlenc11#aes192-gcm' + // 'http://www.w3.org/2009/xmlenc11#aes256-gcm'; + // Notice that aes-cbc are not consider secure anymore so should not be used + 'encryption_algorithm' => 'http://www.w3.org/2009/xmlenc11#aes128-gcm', + + // ADFS URL-Encodes SAML data as lowercase, and the toolkit by default uses + // uppercase. Turn it True for ADFS compatibility on signature verification + 'lowercaseUrlencoding' => false, + ), + + // Contact information template, it is recommended to suply a technical and support contacts + 'contactPerson' => array( + 'technical' => array( + 'givenName' => '', + 'emailAddress' => '' + ), + 'support' => array( + 'givenName' => '', + 'emailAddress' => '' + ), + ), + + // Organization information template, the info in en_US lang is recomended, add more if required + 'organization' => array( + 'en-US' => array( + 'name' => '', + 'displayname' => '', + 'url' => '' + ), + ), +); + + +/* Interoperable SAML 2.0 Web Browser SSO Profile [saml2int] http://saml2int.org/profile/current + + 'authnRequestsSigned' => false, // SP SHOULD NOT sign the , + // MUST NOT assume that the IdP validates the sign + 'wantAssertionsSigned' => true, + 'wantAssertionsEncrypted' => true, // MUST be enabled if SSL/HTTPs is disabled + 'wantNameIdEncrypted' => false, +*/ diff --git a/vendor/onelogin/php-saml/certs/README b/vendor/onelogin/php-saml/certs/README new file mode 100644 index 00000000..1616ebda --- /dev/null +++ b/vendor/onelogin/php-saml/certs/README @@ -0,0 +1,14 @@ +Take care of this folder that could contain private key. Be sure that this folder never is published. + +Onelogin PHP Toolkit expects certs for the SP stored at: + + * sp.key Private Key + * sp.crt Public cert + * sp_new.crt Future Public cert + +Also you can use other cert to sign the metadata of the SP using the: + + * metadata.key + * metadata.crt + +If you are using composer to install the php-saml toolkit, You should move the certs folder to vendor/onelogin/php-saml/certs diff --git a/vendor/onelogin/php-saml/composer.json b/vendor/onelogin/php-saml/composer.json new file mode 100644 index 00000000..3aa198da --- /dev/null +++ b/vendor/onelogin/php-saml/composer.json @@ -0,0 +1,34 @@ +{ + "name": "onelogin/php-saml", + "description": "OneLogin PHP SAML Toolkit", + "license": "MIT", + "homepage": "https://developers.onelogin.com/saml/php", + "keywords": ["saml", "saml2", "onelogin"], + "autoload": { + "psr-4": { + "OneLogin\\": "src/" + } + }, + "support": { + "email": "sixto.garcia@onelogin.com", + "issues": "https://github.com/onelogin/php-saml/issues", + "source": "https://github.com/onelogin/php-saml/" + }, + "require": { + "php": ">=5.4", + "robrichards/xmlseclibs": ">=3.1.1" + }, + "require-dev": { + "phpunit/phpunit": "<7.5.18", + "php-coveralls/php-coveralls": "^1.0.2 || ^2.0", + "sebastian/phpcpd": "^2.0 || ^3.0 || ^4.0", + "phploc/phploc": "^2.1 || ^3.0 || ^4.0", + "pdepend/pdepend": "^2.5.0", + "squizlabs/php_codesniffer": "^3.1.1" + }, + "suggest": { + "ext-openssl": "Install openssl lib in order to handle with x509 certs (require to support sign and encryption)", + "ext-curl": "Install curl lib to be able to use the IdPMetadataParser for parsing remote XMLs", + "ext-gettext": "Install gettext and php5-gettext libs to handle translations" + } +} diff --git a/vendor/onelogin/php-saml/phpunit.xml b/vendor/onelogin/php-saml/phpunit.xml new file mode 100644 index 00000000..3629f274 --- /dev/null +++ b/vendor/onelogin/php-saml/phpunit.xml @@ -0,0 +1,18 @@ + + + + ./tests/src + + + + + ./src + + + + + + + + + diff --git a/vendor/onelogin/php-saml/settings_example.php b/vendor/onelogin/php-saml/settings_example.php new file mode 100644 index 00000000..981a21a3 --- /dev/null +++ b/vendor/onelogin/php-saml/settings_example.php @@ -0,0 +1,137 @@ + true, + + // Enable debug mode (to print errors) + 'debug' => false, + + // Set a BaseURL to be used instead of try to guess + // the BaseURL of the view that process the SAML Message. + // Ex. http://sp.example.com/ + // http://example.com/sp/ + 'baseurl' => null, + + // Service Provider Data that we are deploying + 'sp' => array( + // Identifier of the SP entity (must be a URI) + 'entityId' => '', + // Specifies info about where and how the message MUST be + // returned to the requester, in this case our SP. + 'assertionConsumerService' => array( + // URL Location where the from the IdP will be returned + 'url' => '', + // SAML protocol binding to be used when returning the + // message. Onelogin Toolkit supports for this endpoint the + // HTTP-POST binding only + 'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST', + ), + // If you need to specify requested attributes, set a + // attributeConsumingService. nameFormat, attributeValue and + // friendlyName can be omitted. Otherwise remove this section. + "attributeConsumingService"=> array( + "serviceName" => "SP test", + "serviceDescription" => "Test Service", + "requestedAttributes" => array( + array( + "name" => "", + "isRequired" => false, + "nameFormat" => "", + "friendlyName" => "", + "attributeValue" => "" + ) + ) + ), + // Specifies info about where and how the message MUST be + // returned to the requester, in this case our SP. + 'singleLogoutService' => array( + // URL Location where the from the IdP will be returned + 'url' => '', + // SAML protocol binding to be used when returning the + // message. Onelogin Toolkit supports for this endpoint the + // HTTP-Redirect binding only + 'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect', + ), + // Specifies constraints on the name identifier to be used to + // represent the requested subject. + // Take a look on lib/Saml2/Constants.php to see the NameIdFormat supported + 'NameIDFormat' => 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified', + + // Usually x509cert and privateKey of the SP are provided by files placed at + // the certs folder. But we can also provide them with the following parameters + 'x509cert' => '', + 'privateKey' => '', + + /* + * Key rollover + * If you plan to update the SP x509cert and privateKey + * you can define here the new x509cert and it will be + * published on the SP metadata so Identity Providers can + * read them and get ready for rollover. + */ + // 'x509certNew' => '', + ), + + // Identity Provider Data that we want connect with our SP + 'idp' => array( + // Identifier of the IdP entity (must be a URI) + 'entityId' => '', + // SSO endpoint info of the IdP. (Authentication Request protocol) + 'singleSignOnService' => array( + // URL Target of the IdP where the SP will send the Authentication Request Message + 'url' => '', + // SAML protocol binding to be used when returning the + // message. Onelogin Toolkit supports for this endpoint the + // HTTP-Redirect binding only + 'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect', + ), + // SLO endpoint info of the IdP. + 'singleLogoutService' => array( + // URL Location of the IdP where the SP will send the SLO Request + 'url' => '', + // URL location of the IdP where the SP SLO Response will be sent (ResponseLocation) + // if not set, url for the SLO Request will be used + 'responseUrl' => '', + // SAML protocol binding to be used when returning the + // message. Onelogin Toolkit supports for this endpoint the + // HTTP-Redirect binding only + 'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect', + ), + // Public x509 certificate of the IdP + 'x509cert' => '', + /* + * Instead of use the whole x509cert you can use a fingerprint in + * order to validate the SAMLResponse, but we don't recommend to use + * that method on production since is exploitable by a collision + * attack. + * (openssl x509 -noout -fingerprint -in "idp.crt" to generate it, + * or add for example the -sha256 , -sha384 or -sha512 parameter) + * + * If a fingerprint is provided, then the certFingerprintAlgorithm is required in order to + * let the toolkit know which Algorithm was used. Possible values: sha1, sha256, sha384 or sha512 + * 'sha1' is the default value. + */ + // 'certFingerprint' => '', + // 'certFingerprintAlgorithm' => 'sha1', + + /* In some scenarios the IdP uses different certificates for + * signing/encryption, or is under key rollover phase and more + * than one certificate is published on IdP metadata. + * In order to handle that the toolkit offers that parameter. + * (when used, 'x509cert' and 'certFingerprint' values are + * ignored). + */ + // 'x509certMulti' => array( + // 'signing' => array( + // 0 => '', + // ), + // 'encryption' => array( + // 0 => '', + // ) + // ), + ), +); diff --git a/vendor/onelogin/php-saml/src/Saml2/Auth.php b/vendor/onelogin/php-saml/src/Saml2/Auth.php new file mode 100644 index 00000000..70a87152 --- /dev/null +++ b/vendor/onelogin/php-saml/src/Saml2/Auth.php @@ -0,0 +1,817 @@ + + * @license MIT https://github.com/onelogin/php-saml/blob/master/LICENSE + * @link https://github.com/onelogin/php-saml + */ + +namespace OneLogin\Saml2; + +use RobRichards\XMLSecLibs\XMLSecurityKey; + +use Exception; + +/** + * Main class of OneLogin's PHP Toolkit + */ +class Auth +{ + /** + * Settings data. + * + * @var Settings + */ + private $_settings; + + /** + * User attributes data. + * + * @var array + */ + private $_attributes = array(); + + /** + * User attributes data with FriendlyName index. + * + * @var array + */ + private $_attributesWithFriendlyName = array(); + + /** + * NameID + * + * @var string + */ + private $_nameid; + + /** + * NameID Format + * + * @var string + */ + private $_nameidFormat; + + /** + * NameID NameQualifier + * + * @var string + */ + private $_nameidNameQualifier; + + /** + * NameID SP NameQualifier + * + * @var string + */ + private $_nameidSPNameQualifier; + + /** + * If user is authenticated. + * + * @var bool + */ + private $_authenticated = false; + + + /** + * SessionIndex. When the user is logged, this stored it + * from the AuthnStatement of the SAML Response + * + * @var string + */ + private $_sessionIndex; + + /** + * SessionNotOnOrAfter. When the user is logged, this stored it + * from the AuthnStatement of the SAML Response + * + * @var int|null + */ + private $_sessionExpiration; + + /** + * The ID of the last message processed + * + * @var string + */ + private $_lastMessageId; + + /** + * The ID of the last assertion processed + * + * @var string + */ + private $_lastAssertionId; + + /** + * The NotOnOrAfter value of the valid SubjectConfirmationData + * node (if any) of the last assertion processed + * + * @var int + */ + private $_lastAssertionNotOnOrAfter; + + /** + * If any error. + * + * @var array + */ + private $_errors = array(); + + /** + * Last error object. + * + * @var Error|null + */ + private $_lastErrorException; + + /** + * Last error. + * + * @var string|null + */ + private $_lastError; + + /** + * Last AuthNRequest ID or LogoutRequest ID generated by this Service Provider + * + * @var string + */ + private $_lastRequestID; + + /** + * The most recently-constructed/processed XML SAML request + * (AuthNRequest, LogoutRequest) + * + * @var string + */ + private $_lastRequest; + + /** + * The most recently-constructed/processed XML SAML response + * (SAMLResponse, LogoutResponse). If the SAMLResponse was + * encrypted, by default tries to return the decrypted XML + * + * @var string|\DomDocument|null + */ + private $_lastResponse; + + /** + * Initializes the SP SAML instance. + * + * @param array|null $settings Setting data + * + * @throws Exception + * @throws Error + */ + public function __construct(array $settings = null) + { + $this->_settings = new Settings($settings); + } + + /** + * Returns the settings info + * + * @return Settings The settings data. + */ + public function getSettings() + { + return $this->_settings; + } + + /** + * Set the strict mode active/disable + * + * @param bool $value Strict parameter + * + * @throws Error + */ + public function setStrict($value) + { + if (!is_bool($value)) { + throw new Error( + 'Invalid value passed to setStrict()', + Error::SETTINGS_INVALID_SYNTAX + ); + } + + $this->_settings->setStrict($value); + } + + /** + * Set schemas path + * + * @param string $path + * @return $this + */ + public function setSchemasPath($path) + { + $this->_paths['schemas'] = $path; + } + + /** + * Process the SAML Response sent by the IdP. + * + * @param string|null $requestId The ID of the AuthNRequest sent by this SP to the IdP + * + * @throws Error + * @throws ValidationError + */ + public function processResponse($requestId = null) + { + $this->_errors = array(); + $this->_lastError = $this->_lastErrorException = null; + if (isset($_POST['SAMLResponse'])) { + // AuthnResponse -- HTTP_POST Binding + $response = new Response($this->_settings, $_POST['SAMLResponse']); + $this->_lastResponse = $response->getXMLDocument(); + + if ($response->isValid($requestId)) { + $this->_attributes = $response->getAttributes(); + $this->_attributesWithFriendlyName = $response->getAttributesWithFriendlyName(); + $this->_nameid = $response->getNameId(); + $this->_nameidFormat = $response->getNameIdFormat(); + $this->_nameidNameQualifier = $response->getNameIdNameQualifier(); + $this->_nameidSPNameQualifier = $response->getNameIdSPNameQualifier(); + $this->_authenticated = true; + $this->_sessionIndex = $response->getSessionIndex(); + $this->_sessionExpiration = $response->getSessionNotOnOrAfter(); + $this->_lastMessageId = $response->getId(); + $this->_lastAssertionId = $response->getAssertionId(); + $this->_lastAssertionNotOnOrAfter = $response->getAssertionNotOnOrAfter(); + } else { + $this->_errors[] = 'invalid_response'; + $this->_lastErrorException = $response->getErrorException(); + $this->_lastError = $response->getError(); + } + } else { + $this->_errors[] = 'invalid_binding'; + throw new Error( + 'SAML Response not found, Only supported HTTP_POST Binding', + Error::SAML_RESPONSE_NOT_FOUND + ); + } + } + + /** + * Process the SAML Logout Response / Logout Request sent by the IdP. + * + * @param bool $keepLocalSession When false will destroy the local session, otherwise will keep it + * @param string|null $requestId The ID of the LogoutRequest sent by this SP to the IdP + * @param bool $retrieveParametersFromServer True if we want to use parameters from $_SERVER to validate the signature + * @param callable $cbDeleteSession Callback to be executed to delete session + * @param bool $stay True if we want to stay (returns the url string) False to redirect + * + * @return string|null + * + * @throws Error + */ + public function processSLO($keepLocalSession = false, $requestId = null, $retrieveParametersFromServer = false, $cbDeleteSession = null, $stay = false) + { + $this->_errors = array(); + $this->_lastError = $this->_lastErrorException = null; + if (isset($_GET['SAMLResponse'])) { + $logoutResponse = new LogoutResponse($this->_settings, $_GET['SAMLResponse']); + $this->_lastResponse = $logoutResponse->getXML(); + if (!$logoutResponse->isValid($requestId, $retrieveParametersFromServer)) { + $this->_errors[] = 'invalid_logout_response'; + $this->_lastErrorException = $logoutResponse->getErrorException(); + $this->_lastError = $logoutResponse->getError(); + + } else if ($logoutResponse->getStatus() !== Constants::STATUS_SUCCESS) { + $this->_errors[] = 'logout_not_success'; + } else { + $this->_lastMessageId = $logoutResponse->id; + if (!$keepLocalSession) { + if ($cbDeleteSession === null) { + Utils::deleteLocalSession(); + } else { + call_user_func($cbDeleteSession); + } + } + } + } else if (isset($_GET['SAMLRequest'])) { + $logoutRequest = new LogoutRequest($this->_settings, $_GET['SAMLRequest']); + $this->_lastRequest = $logoutRequest->getXML(); + if (!$logoutRequest->isValid($retrieveParametersFromServer)) { + $this->_errors[] = 'invalid_logout_request'; + $this->_lastErrorException = $logoutRequest->getErrorException(); + $this->_lastError = $logoutRequest->getError(); + } else { + if (!$keepLocalSession) { + if ($cbDeleteSession === null) { + Utils::deleteLocalSession(); + } else { + call_user_func($cbDeleteSession); + } + } + $inResponseTo = $logoutRequest->id; + $this->_lastMessageId = $logoutRequest->id; + $responseBuilder = new LogoutResponse($this->_settings); + $responseBuilder->build($inResponseTo); + $this->_lastResponse = $responseBuilder->getXML(); + + $logoutResponse = $responseBuilder->getResponse(); + + $parameters = array('SAMLResponse' => $logoutResponse); + if (isset($_GET['RelayState'])) { + $parameters['RelayState'] = $_GET['RelayState']; + } + + $security = $this->_settings->getSecurityData(); + if (isset($security['logoutResponseSigned']) && $security['logoutResponseSigned']) { + $signature = $this->buildResponseSignature($logoutResponse, isset($parameters['RelayState'])? $parameters['RelayState']: null, $security['signatureAlgorithm']); + $parameters['SigAlg'] = $security['signatureAlgorithm']; + $parameters['Signature'] = $signature; + } + + return $this->redirectTo($this->getSLOResponseUrl(), $parameters, $stay); + } + } else { + $this->_errors[] = 'invalid_binding'; + throw new Error( + 'SAML LogoutRequest/LogoutResponse not found. Only supported HTTP_REDIRECT Binding', + Error::SAML_LOGOUTMESSAGE_NOT_FOUND + ); + } + } + + /** + * Redirects the user to the url past by parameter + * or to the url that we defined in our SSO Request. + * + * @param string $url The target URL to redirect the user. + * @param array $parameters Extra parameters to be passed as part of the url + * @param bool $stay True if we want to stay (returns the url string) False to redirect + * + * @return string|null + */ + public function redirectTo($url = '', array $parameters = array(), $stay = false) + { + assert(is_string($url)); + + if (empty($url) && isset($_REQUEST['RelayState'])) { + $url = $_REQUEST['RelayState']; + } + + return Utils::redirect($url, $parameters, $stay); + } + + /** + * Checks if the user is authenticated or not. + * + * @return bool True if the user is authenticated + */ + public function isAuthenticated() + { + return $this->_authenticated; + } + + /** + * Returns the set of SAML attributes. + * + * @return array Attributes of the user. + */ + public function getAttributes() + { + return $this->_attributes; + } + + + /** + * Returns the set of SAML attributes indexed by FriendlyName + * + * @return array Attributes of the user. + */ + public function getAttributesWithFriendlyName() + { + return $this->_attributesWithFriendlyName; + } + + /** + * Returns the nameID + * + * @return string The nameID of the assertion + */ + public function getNameId() + { + return $this->_nameid; + } + + /** + * Returns the nameID Format + * + * @return string The nameID Format of the assertion + */ + public function getNameIdFormat() + { + return $this->_nameidFormat; + } + + /** + * Returns the nameID NameQualifier + * + * @return string The nameID NameQualifier of the assertion + */ + public function getNameIdNameQualifier() + { + return $this->_nameidNameQualifier; + } + + /** + * Returns the nameID SP NameQualifier + * + * @return string The nameID SP NameQualifier of the assertion + */ + public function getNameIdSPNameQualifier() + { + return $this->_nameidSPNameQualifier; + } + + /** + * Returns the SessionIndex + * + * @return string|null The SessionIndex of the assertion + */ + public function getSessionIndex() + { + return $this->_sessionIndex; + } + + /** + * Returns the SessionNotOnOrAfter + * + * @return int|null The SessionNotOnOrAfter of the assertion + */ + public function getSessionExpiration() + { + return $this->_sessionExpiration; + } + + /** + * Returns if there were any error + * + * @return array Errors + */ + public function getErrors() + { + return $this->_errors; + } + + /** + * Returns the reason for the last error + * + * @return string|null Error reason + */ + public function getLastErrorReason() + { + return $this->_lastError; + } + + + /** + * Returns the last error + * + * @return Exception|null Error + */ + public function getLastErrorException() + { + return $this->_lastErrorException; + } + + /** + * Returns the requested SAML attribute + * + * @param string $name The requested attribute of the user. + * + * @return array|null Requested SAML attribute ($name). + */ + public function getAttribute($name) + { + assert(is_string($name)); + + $value = null; + if (isset($this->_attributes[$name])) { + return $this->_attributes[$name]; + } + return $value; + } + + /** + * Returns the requested SAML attribute indexed by FriendlyName + * + * @param string $friendlyName The requested attribute of the user. + * + * @return array|null Requested SAML attribute ($friendlyName). + */ + public function getAttributeWithFriendlyName($friendlyName) + { + assert(is_string($friendlyName)); + $value = null; + if (isset($this->_attributesWithFriendlyName[$friendlyName])) { + return $this->_attributesWithFriendlyName[$friendlyName]; + } + return $value; + } + + /** + * Initiates the SSO process. + * + * @param string|null $returnTo The target URL the user should be returned to after login. + * @param array $parameters Extra parameters to be added to the GET + * @param bool $forceAuthn When true the AuthNRequest will set the ForceAuthn='true' + * @param bool $isPassive When true the AuthNRequest will set the Ispassive='true' + * @param bool $stay True if we want to stay (returns the url string) False to redirect + * @param bool $setNameIdPolicy When true the AuthNRequest will set a nameIdPolicy element + * @param string $nameIdValueReq Indicates to the IdP the subject that should be authenticated + * + * @return string|null If $stay is True, it return a string with the SLO URL + LogoutRequest + parameters + * + * @throws Error + */ + public function login($returnTo = null, array $parameters = array(), $forceAuthn = false, $isPassive = false, $stay = false, $setNameIdPolicy = true, $nameIdValueReq = null) + { + $authnRequest = $this->buildAuthnRequest($this->_settings, $forceAuthn, $isPassive, $setNameIdPolicy, $nameIdValueReq); + + $this->_lastRequest = $authnRequest->getXML(); + $this->_lastRequestID = $authnRequest->getId(); + + $samlRequest = $authnRequest->getRequest(); + $parameters['SAMLRequest'] = $samlRequest; + + if (!empty($returnTo)) { + $parameters['RelayState'] = $returnTo; + } else { + $parameters['RelayState'] = Utils::getSelfRoutedURLNoQuery(); + } + + $security = $this->_settings->getSecurityData(); + if (isset($security['authnRequestsSigned']) && $security['authnRequestsSigned']) { + $signature = $this->buildRequestSignature($samlRequest, $parameters['RelayState'], $security['signatureAlgorithm']); + $parameters['SigAlg'] = $security['signatureAlgorithm']; + $parameters['Signature'] = $signature; + } + return $this->redirectTo($this->getSSOurl(), $parameters, $stay); + } + + /** + * Initiates the SLO process. + * + * @param string|null $returnTo The target URL the user should be returned to after logout. + * @param array $parameters Extra parameters to be added to the GET + * @param string|null $nameId The NameID that will be set in the LogoutRequest. + * @param string|null $sessionIndex The SessionIndex (taken from the SAML Response in the SSO process). + * @param bool $stay True if we want to stay (returns the url string) False to redirect + * @param string|null $nameIdFormat The NameID Format will be set in the LogoutRequest. + * @param string|null $nameIdNameQualifier The NameID NameQualifier will be set in the LogoutRequest. + * + * @return string|null If $stay is True, it return a string with the SLO URL + LogoutRequest + parameters + * + * @throws Error + */ + public function logout($returnTo = null, array $parameters = array(), $nameId = null, $sessionIndex = null, $stay = false, $nameIdFormat = null, $nameIdNameQualifier = null, $nameIdSPNameQualifier = null) + { + $sloUrl = $this->getSLOurl(); + if (empty($sloUrl)) { + throw new Error( + 'The IdP does not support Single Log Out', + Error::SAML_SINGLE_LOGOUT_NOT_SUPPORTED + ); + } + + if (empty($nameId) && !empty($this->_nameid)) { + $nameId = $this->_nameid; + } + if (empty($nameIdFormat) && !empty($this->_nameidFormat)) { + $nameIdFormat = $this->_nameidFormat; + } + + $logoutRequest = new LogoutRequest($this->_settings, null, $nameId, $sessionIndex, $nameIdFormat, $nameIdNameQualifier, $nameIdSPNameQualifier); + + $this->_lastRequest = $logoutRequest->getXML(); + $this->_lastRequestID = $logoutRequest->id; + + $samlRequest = $logoutRequest->getRequest(); + + $parameters['SAMLRequest'] = $samlRequest; + if (!empty($returnTo)) { + $parameters['RelayState'] = $returnTo; + } else { + $parameters['RelayState'] = Utils::getSelfRoutedURLNoQuery(); + } + + $security = $this->_settings->getSecurityData(); + if (isset($security['logoutRequestSigned']) && $security['logoutRequestSigned']) { + $signature = $this->buildRequestSignature($samlRequest, $parameters['RelayState'], $security['signatureAlgorithm']); + $parameters['SigAlg'] = $security['signatureAlgorithm']; + $parameters['Signature'] = $signature; + } + + return $this->redirectTo($sloUrl, $parameters, $stay); + } + + /** + * Gets the IdP SSO url. + * + * @return string The url of the IdP Single Sign On Service + */ + public function getSSOurl() + { + return $this->_settings->getIdPSSOUrl(); + } + + /** + * Gets the IdP SLO url. + * + * @return string|null The url of the IdP Single Logout Service + */ + public function getSLOurl() + { + return $this->_settings->getIdPSLOUrl(); + } + + /** + * Gets the IdP SLO response url. + * + * @return string|null The response url of the IdP Single Logout Service + */ + public function getSLOResponseUrl() + { + return $this->_settings->getIdPSLOResponseUrl(); + } + + + /** + * Gets the ID of the last AuthNRequest or LogoutRequest generated by the Service Provider. + * + * @return string The ID of the Request SAML message. + */ + public function getLastRequestID() + { + return $this->_lastRequestID; + } + + /** + * Creates an AuthnRequest + * + * @param Settings $settings Setting data + * @param bool $forceAuthn When true the AuthNRequest will set the ForceAuthn='true' + * @param bool $isPassive When true the AuthNRequest will set the Ispassive='true' + * @param bool $setNameIdPolicy When true the AuthNRequest will set a nameIdPolicy element + * @param string $nameIdValueReq Indicates to the IdP the subject that should be authenticated + * + * @return AuthnRequest The AuthnRequest object + */ + public function buildAuthnRequest($settings, $forceAuthn, $isPassive, $setNameIdPolicy, $nameIdValueReq = null) + { + return new AuthnRequest($settings, $forceAuthn, $isPassive, $setNameIdPolicy, $nameIdValueReq); + } + + /** + * Generates the Signature for a SAML Request + * + * @param string $samlRequest The SAML Request + * @param string $relayState The RelayState + * @param string $signAlgorithm Signature algorithm method + * + * @return string A base64 encoded signature + * + * @throws Exception + * @throws Error + */ + public function buildRequestSignature($samlRequest, $relayState, $signAlgorithm = XMLSecurityKey::RSA_SHA256) + { + return $this->buildMessageSignature($samlRequest, $relayState, $signAlgorithm, "SAMLRequest"); + } + + /** + * Generates the Signature for a SAML Response + * + * @param string $samlResponse The SAML Response + * @param string $relayState The RelayState + * @param string $signAlgorithm Signature algorithm method + * + * @return string A base64 encoded signature + * + * @throws Exception + * @throws Error + */ + public function buildResponseSignature($samlResponse, $relayState, $signAlgorithm = XMLSecurityKey::RSA_SHA256) + { + return $this->buildMessageSignature($samlResponse, $relayState, $signAlgorithm, "SAMLResponse"); + } + + /** + * Generates the Signature for a SAML Message + * + * @param string $samlMessage The SAML Message + * @param string $relayState The RelayState + * @param string $signAlgorithm Signature algorithm method + * @param string $type "SAMLRequest" or "SAMLResponse" + * + * @return string A base64 encoded signature + * + * @throws Exception + * @throws Error + */ + private function buildMessageSignature($samlMessage, $relayState, $signAlgorithm = XMLSecurityKey::RSA_SHA256, $type = "SAMLRequest") + { + $key = $this->_settings->getSPkey(); + if (empty($key)) { + if ($type == "SAMLRequest") { + $errorMsg = "Trying to sign the SAML Request but can't load the SP private key"; + } else { + $errorMsg = "Trying to sign the SAML Response but can't load the SP private key"; + } + + throw new Error($errorMsg, Error::PRIVATE_KEY_NOT_FOUND); + } + + $objKey = new XMLSecurityKey($signAlgorithm, array('type' => 'private')); + $objKey->loadKey($key, false); + + $security = $this->_settings->getSecurityData(); + if ($security['lowercaseUrlencoding']) { + $msg = $type.'='.rawurlencode($samlMessage); + if (isset($relayState)) { + $msg .= '&RelayState='.rawurlencode($relayState); + } + $msg .= '&SigAlg=' . rawurlencode($signAlgorithm); + } else { + $msg = $type.'='.urlencode($samlMessage); + if (isset($relayState)) { + $msg .= '&RelayState='.urlencode($relayState); + } + $msg .= '&SigAlg=' . urlencode($signAlgorithm); + } + $signature = $objKey->signData($msg); + return base64_encode($signature); + } + + /** + * @return string The ID of the last message processed + */ + public function getLastMessageId() + { + return $this->_lastMessageId; + } + + /** + * @return string The ID of the last assertion processed + */ + public function getLastAssertionId() + { + return $this->_lastAssertionId; + } + + /** + * @return int The NotOnOrAfter value of the valid + * SubjectConfirmationData node (if any) + * of the last assertion processed + */ + public function getLastAssertionNotOnOrAfter() + { + return $this->_lastAssertionNotOnOrAfter; + } + + /** + * Returns the most recently-constructed/processed + * XML SAML request (AuthNRequest, LogoutRequest) + * + * @return string|null The Request XML + */ + public function getLastRequestXML() + { + return $this->_lastRequest; + } + + /** + * Returns the most recently-constructed/processed + * XML SAML response (SAMLResponse, LogoutResponse). + * If the SAMLResponse was encrypted, by default tries + * to return the decrypted XML. + * + * @return string|null The Response XML + */ + public function getLastResponseXML() + { + $response = null; + if (isset($this->_lastResponse)) { + if (is_string($this->_lastResponse)) { + $response = $this->_lastResponse; + } else { + $response = $this->_lastResponse->saveXML(); + } + } + + return $response; + } +} diff --git a/vendor/onelogin/php-saml/src/Saml2/AuthnRequest.php b/vendor/onelogin/php-saml/src/Saml2/AuthnRequest.php new file mode 100644 index 00000000..fd9afb53 --- /dev/null +++ b/vendor/onelogin/php-saml/src/Saml2/AuthnRequest.php @@ -0,0 +1,214 @@ + + * @license MIT https://github.com/onelogin/php-saml/blob/master/LICENSE + * @link https://github.com/onelogin/php-saml + */ + +namespace OneLogin\Saml2; + +/** + * SAML 2 Authentication Request + */ +class AuthnRequest +{ + /** + * Object that represents the setting info + * + * @var Settings + */ + protected $_settings; + + /** + * SAML AuthNRequest string + * + * @var string + */ + private $_authnRequest; + + /** + * SAML AuthNRequest ID. + * + * @var string + */ + private $_id; + + /** + * Constructs the AuthnRequest object. + * + * @param Settings $settings SAML Toolkit Settings + * @param bool $forceAuthn When true the AuthNReuqest will set the ForceAuthn='true' + * @param bool $isPassive When true the AuthNReuqest will set the Ispassive='true' + * @param bool $setNameIdPolicy When true the AuthNReuqest will set a nameIdPolicy + * @param string $nameIdValueReq Indicates to the IdP the subject that should be authenticated + */ + public function __construct(\OneLogin\Saml2\Settings $settings, $forceAuthn = false, $isPassive = false, $setNameIdPolicy = true, $nameIdValueReq = null) + { + $this->_settings = $settings; + + $spData = $this->_settings->getSPData(); + $security = $this->_settings->getSecurityData(); + + $id = Utils::generateUniqueID(); + $issueInstant = Utils::parseTime2SAML(time()); + + $subjectStr = ""; + if (isset($nameIdValueReq)) { + $subjectStr = << + {$nameIdValueReq} + + +SUBJECT; + } + + $nameIdPolicyStr = ''; + if ($setNameIdPolicy) { + $nameIDPolicyFormat = $spData['NameIDFormat']; + if (isset($security['wantNameIdEncrypted']) && $security['wantNameIdEncrypted']) { + $nameIDPolicyFormat = Constants::NAMEID_ENCRYPTED; + } + + $nameIdPolicyStr = << +NAMEIDPOLICY; + } + + + $providerNameStr = ''; + $organizationData = $settings->getOrganization(); + if (!empty($organizationData)) { + $langs = array_keys($organizationData); + if (in_array('en-US', $langs)) { + $lang = 'en-US'; + } else { + $lang = $langs[0]; + } + if (isset($organizationData[$lang]['displayname']) && !empty($organizationData[$lang]['displayname'])) { + $providerNameStr = << + urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport + +REQUESTEDAUTHN; + } else { + $requestedAuthnStr .= " \n"; + foreach ($security['requestedAuthnContext'] as $contextValue) { + $requestedAuthnStr .= " ".$contextValue."\n"; + } + $requestedAuthnStr .= ' '; + } + } + + $spEntityId = htmlspecialchars($spData['entityId'], ENT_QUOTES); + $acsUrl = htmlspecialchars($spData['assertionConsumerService']['url'], ENT_QUOTES); + $destination = $this->_settings->getIdPSSOUrl(); + $request = << + {$spEntityId}{$subjectStr}{$nameIdPolicyStr}{$requestedAuthnStr} + +AUTHNREQUEST; + + $this->_id = $id; + $this->_authnRequest = $request; + } + + /** + * Returns deflated, base64 encoded, unsigned AuthnRequest. + * + * @param bool|null $deflate Whether or not we should 'gzdeflate' the request body before we return it. + * + * @return string + */ + public function getRequest($deflate = null) + { + $subject = $this->_authnRequest; + + if (is_null($deflate)) { + $deflate = $this->_settings->shouldCompressRequests(); + } + + if ($deflate) { + $subject = gzdeflate($this->_authnRequest); + } + + $base64Request = base64_encode($subject); + return $base64Request; + } + + /** + * Returns the AuthNRequest ID. + * + * @return string + */ + public function getId() + { + return $this->_id; + } + + /** + * Returns the XML that will be sent as part of the request + * + * @return string + */ + public function getXML() + { + return $this->_authnRequest; + } +} diff --git a/vendor/onelogin/php-saml/src/Saml2/Constants.php b/vendor/onelogin/php-saml/src/Saml2/Constants.php new file mode 100644 index 00000000..1b467dd6 --- /dev/null +++ b/vendor/onelogin/php-saml/src/Saml2/Constants.php @@ -0,0 +1,86 @@ + + * @license MIT https://github.com/onelogin/php-saml/blob/master/LICENSE + * @link https://github.com/onelogin/php-saml + */ + +namespace OneLogin\Saml2; + +/** + * Constants of OneLogin PHP Toolkit + * + * Defines all required constants + */ +class Constants +{ + // Value added to the current time in time condition validations + const ALLOWED_CLOCK_DRIFT = 180; // 3 min in seconds + + // NameID Formats + const NAMEID_EMAIL_ADDRESS = 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'; + const NAMEID_X509_SUBJECT_NAME = 'urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName'; + const NAMEID_WINDOWS_DOMAIN_QUALIFIED_NAME = 'urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName'; + const NAMEID_UNSPECIFIED = 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified'; + const NAMEID_KERBEROS = 'urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos'; + const NAMEID_ENTITY = 'urn:oasis:names:tc:SAML:2.0:nameid-format:entity'; + const NAMEID_TRANSIENT = 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'; + const NAMEID_PERSISTENT = 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent'; + const NAMEID_ENCRYPTED = 'urn:oasis:names:tc:SAML:2.0:nameid-format:encrypted'; + + // Attribute Name Formats + const ATTRNAME_FORMAT_UNSPECIFIED = 'urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified'; + const ATTRNAME_FORMAT_URI = 'urn:oasis:names:tc:SAML:2.0:attrname-format:uri'; + const ATTRNAME_FORMAT_BASIC = 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic'; + + // Namespaces + const NS_SAML = 'urn:oasis:names:tc:SAML:2.0:assertion'; + const NS_SAMLP = 'urn:oasis:names:tc:SAML:2.0:protocol'; + const NS_SOAP = 'http://schemas.xmlsoap.org/soap/envelope/'; + const NS_MD = 'urn:oasis:names:tc:SAML:2.0:metadata'; + const NS_XS = 'http://www.w3.org/2001/XMLSchema'; + const NS_XSI = 'http://www.w3.org/2001/XMLSchema-instance'; + const NS_XENC = 'http://www.w3.org/2001/04/xmlenc#'; + const NS_DS = 'http://www.w3.org/2000/09/xmldsig#'; + + // Bindings + const BINDING_HTTP_POST = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'; + const BINDING_HTTP_REDIRECT = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'; + const BINDING_HTTP_ARTIFACT = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact'; + const BINDING_SOAP = 'urn:oasis:names:tc:SAML:2.0:bindings:SOAP'; + const BINDING_DEFLATE = 'urn:oasis:names:tc:SAML:2.0:bindings:URL-Encoding:DEFLATE'; + + // Auth Context Class + const AC_UNSPECIFIED = 'urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified'; + const AC_PASSWORD = 'urn:oasis:names:tc:SAML:2.0:ac:classes:Password'; + const AC_PASSWORD_PROTECTED = 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'; + const AC_X509 = 'urn:oasis:names:tc:SAML:2.0:ac:classes:X509'; + const AC_SMARTCARD = 'urn:oasis:names:tc:SAML:2.0:ac:classes:Smartcard'; + const AC_SMARTCARD_PKI = 'urn:oasis:names:tc:SAML:2.0:ac:classes:SmartcardPKI'; + const AC_KERBEROS = 'urn:oasis:names:tc:SAML:2.0:ac:classes:Kerberos'; + const AC_WINDOWS = 'urn:federation:authentication:windows'; + const AC_TLS = 'urn:oasis:names:tc:SAML:2.0:ac:classes:TLSClient'; + const AC_RSATOKEN = 'urn:oasis:names:tc:SAML:2.0:ac:classes:TimeSyncToken'; + + // Subject Confirmation + const CM_BEARER = 'urn:oasis:names:tc:SAML:2.0:cm:bearer'; + const CM_HOLDER_KEY = 'urn:oasis:names:tc:SAML:2.0:cm:holder-of-key'; + const CM_SENDER_VOUCHES = 'urn:oasis:names:tc:SAML:2.0:cm:sender-vouches'; + + // Status Codes + const STATUS_SUCCESS = 'urn:oasis:names:tc:SAML:2.0:status:Success'; + const STATUS_REQUESTER = 'urn:oasis:names:tc:SAML:2.0:status:Requester'; + const STATUS_RESPONDER = 'urn:oasis:names:tc:SAML:2.0:status:Responder'; + const STATUS_VERSION_MISMATCH = 'urn:oasis:names:tc:SAML:2.0:status:VersionMismatch'; + const STATUS_NO_PASSIVE = 'urn:oasis:names:tc:SAML:2.0:status:NoPassive'; + const STATUS_PARTIAL_LOGOUT = 'urn:oasis:names:tc:SAML:2.0:status:PartialLogout'; + const STATUS_PROXY_COUNT_EXCEEDED = 'urn:oasis:names:tc:SAML:2.0:status:ProxyCountExceeded'; +} diff --git a/vendor/onelogin/php-saml/src/Saml2/Error.php b/vendor/onelogin/php-saml/src/Saml2/Error.php new file mode 100644 index 00000000..211acf48 --- /dev/null +++ b/vendor/onelogin/php-saml/src/Saml2/Error.php @@ -0,0 +1,66 @@ + + * @license MIT https://github.com/onelogin/php-saml/blob/master/LICENSE + * @link https://github.com/onelogin/php-saml + */ + +namespace OneLogin\Saml2; + +use Exception; + +/** + * Error class of OneLogin PHP Toolkit + * + * Defines the Error class + */ +class Error extends Exception +{ + // Errors + const SETTINGS_FILE_NOT_FOUND = 0; + const SETTINGS_INVALID_SYNTAX = 1; + const SETTINGS_INVALID = 2; + const METADATA_SP_INVALID = 3; + const SP_CERTS_NOT_FOUND = 4; + // SP_CERTS_NOT_FOUND is deprecated, use CERT_NOT_FOUND instead + const CERT_NOT_FOUND = 4; + const REDIRECT_INVALID_URL = 5; + const PUBLIC_CERT_FILE_NOT_FOUND = 6; + const PRIVATE_KEY_FILE_NOT_FOUND = 7; + const SAML_RESPONSE_NOT_FOUND = 8; + const SAML_LOGOUTMESSAGE_NOT_FOUND = 9; + const SAML_LOGOUTREQUEST_INVALID = 10; + const SAML_LOGOUTRESPONSE_INVALID = 11; + const SAML_SINGLE_LOGOUT_NOT_SUPPORTED = 12; + const PRIVATE_KEY_NOT_FOUND = 13; + const UNSUPPORTED_SETTINGS_OBJECT = 14; + + /** + * Constructor + * + * @param string $msg Describes the error. + * @param int $code The code error (defined in the error class). + * @param array|null $args Arguments used in the message that describes the error. + */ + public function __construct($msg, $code = 0, $args = array()) + { + assert(is_string($msg)); + assert(is_int($code)); + + if (!isset($args)) { + $args = array(); + } + $params = array_merge(array($msg), $args); + $message = call_user_func_array('sprintf', $params); + + parent::__construct($message, $code); + } +} diff --git a/vendor/onelogin/php-saml/src/Saml2/IdPMetadataParser.php b/vendor/onelogin/php-saml/src/Saml2/IdPMetadataParser.php new file mode 100644 index 00000000..947d6548 --- /dev/null +++ b/vendor/onelogin/php-saml/src/Saml2/IdPMetadataParser.php @@ -0,0 +1,243 @@ + + * @license MIT https://github.com/onelogin/php-saml/blob/master/LICENSE + * @link https://github.com/onelogin/php-saml + */ + +namespace OneLogin\Saml2; + +use DOMDocument; +use Exception; + +/** + * IdP Metadata Parser of OneLogin PHP Toolkit + */ +class IdPMetadataParser +{ + /** + * Get IdP Metadata Info from URL + * + * @param string $url URL where the IdP metadata is published + * @param string $entityId Entity Id of the desired IdP, if no + * entity Id is provided and the XML + * metadata contains more than one + * IDPSSODescriptor, the first is returned + * @param string $desiredNameIdFormat If available on IdP metadata, use that nameIdFormat + * @param string $desiredSSOBinding Parse specific binding SSO endpoint + * @param string $desiredSLOBinding Parse specific binding SLO endpoint + * + * @return array metadata info in php-saml settings format + */ + public static function parseRemoteXML($url, $entityId = null, $desiredNameIdFormat = null, $desiredSSOBinding = Constants::BINDING_HTTP_REDIRECT, $desiredSLOBinding = Constants::BINDING_HTTP_REDIRECT) + { + $metadataInfo = array(); + + try { + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "GET"); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); + curl_setopt($ch, CURLOPT_FAILONERROR, 1); + + $xml = curl_exec($ch); + if ($xml !== false) { + $metadataInfo = self::parseXML($xml, $entityId, $desiredNameIdFormat, $desiredSSOBinding, $desiredSLOBinding); + } else { + throw new Exception(curl_error($ch), curl_errno($ch)); + } + } catch (Exception $e) { + throw new Exception('Error on parseRemoteXML. '.$e->getMessage()); + } + return $metadataInfo; + } + + /** + * Get IdP Metadata Info from File + * + * @param string $filepath File path + * @param string $entityId Entity Id of the desired IdP, if no + * entity Id is provided and the XML + * metadata contains more than one + * IDPSSODescriptor, the first is returned + * @param string $desiredNameIdFormat If available on IdP metadata, use that nameIdFormat + * @param string $desiredSSOBinding Parse specific binding SSO endpoint + * @param string $desiredSLOBinding Parse specific binding SLO endpoint + * + * @return array metadata info in php-saml settings format + */ + public static function parseFileXML($filepath, $entityId = null, $desiredNameIdFormat = null, $desiredSSOBinding = Constants::BINDING_HTTP_REDIRECT, $desiredSLOBinding = Constants::BINDING_HTTP_REDIRECT) + { + $metadataInfo = array(); + + try { + if (file_exists($filepath)) { + $data = file_get_contents($filepath); + $metadataInfo = self::parseXML($data, $entityId, $desiredNameIdFormat, $desiredSSOBinding, $desiredSLOBinding); + } + } catch (Exception $e) { + throw new Exception('Error on parseFileXML. '.$e->getMessage()); + } + return $metadataInfo; + } + + /** + * Get IdP Metadata Info from URL + * + * @param string $xml XML that contains IdP metadata + * @param string $entityId Entity Id of the desired IdP, if no + * entity Id is provided and the XML + * metadata contains more than one + * IDPSSODescriptor, the first is returned + * @param string $desiredNameIdFormat If available on IdP metadata, use that nameIdFormat + * @param string $desiredSSOBinding Parse specific binding SSO endpoint + * @param string $desiredSLOBinding Parse specific binding SLO endpoint + * + * @return array metadata info in php-saml settings format + * + * @throws Exception + */ + public static function parseXML($xml, $entityId = null, $desiredNameIdFormat = null, $desiredSSOBinding = Constants::BINDING_HTTP_REDIRECT, $desiredSLOBinding = Constants::BINDING_HTTP_REDIRECT) + { + $metadataInfo = array(); + + $dom = new DOMDocument(); + $dom->preserveWhiteSpace = false; + $dom->formatOutput = true; + try { + $dom = Utils::loadXML($dom, $xml); + if (!$dom) { + throw new Exception('Error parsing metadata'); + } + + $customIdPStr = ''; + if (!empty($entityId)) { + $customIdPStr = '[@entityID="' . $entityId . '"]'; + } + $idpDescryptorXPath = '//md:EntityDescriptor' . $customIdPStr . '/md:IDPSSODescriptor'; + + $idpDescriptorNodes = Utils::query($dom, $idpDescryptorXPath); + + if (isset($idpDescriptorNodes) && $idpDescriptorNodes->length > 0) { + $metadataInfo['idp'] = array(); + + $idpDescriptor = $idpDescriptorNodes->item(0); + + if (empty($entityId) && $idpDescriptor->parentNode->hasAttribute('entityID')) { + $entityId = $idpDescriptor->parentNode->getAttribute('entityID'); + } + + if (!empty($entityId)) { + $metadataInfo['idp']['entityId'] = $entityId; + } + + $ssoNodes = Utils::query($dom, './md:SingleSignOnService[@Binding="'.$desiredSSOBinding.'"]', $idpDescriptor); + if ($ssoNodes->length < 1) { + $ssoNodes = Utils::query($dom, './md:SingleSignOnService', $idpDescriptor); + } + if ($ssoNodes->length > 0) { + $metadataInfo['idp']['singleSignOnService'] = array( + 'url' => $ssoNodes->item(0)->getAttribute('Location'), + 'binding' => $ssoNodes->item(0)->getAttribute('Binding') + ); + } + + $sloNodes = Utils::query($dom, './md:SingleLogoutService[@Binding="'.$desiredSLOBinding.'"]', $idpDescriptor); + if ($sloNodes->length < 1) { + $sloNodes = Utils::query($dom, './md:SingleLogoutService', $idpDescriptor); + } + if ($sloNodes->length > 0) { + $metadataInfo['idp']['singleLogoutService'] = array( + 'url' => $sloNodes->item(0)->getAttribute('Location'), + 'binding' => $sloNodes->item(0)->getAttribute('Binding') + ); + + if ($sloNodes->item(0)->hasAttribute('ResponseLocation')) { + $metadataInfo['idp']['singleLogoutService']['responseUrl'] = $sloNodes->item(0)->getAttribute('ResponseLocation'); + } + } + + $keyDescriptorCertSigningNodes = Utils::query($dom, './md:KeyDescriptor[not(contains(@use, "encryption"))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate', $idpDescriptor); + + $keyDescriptorCertEncryptionNodes = Utils::query($dom, './md:KeyDescriptor[not(contains(@use, "signing"))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate', $idpDescriptor); + + if (!empty($keyDescriptorCertSigningNodes) || !empty($keyDescriptorCertEncryptionNodes)) { + $metadataInfo['idp']['x509certMulti'] = array(); + if (!empty($keyDescriptorCertSigningNodes)) { + $idpInfo['x509certMulti']['signing'] = array(); + foreach ($keyDescriptorCertSigningNodes as $keyDescriptorCertSigningNode) { + $metadataInfo['idp']['x509certMulti']['signing'][] = Utils::formatCert($keyDescriptorCertSigningNode->nodeValue, false); + } + } + if (!empty($keyDescriptorCertEncryptionNodes)) { + $idpInfo['x509certMulti']['encryption'] = array(); + foreach ($keyDescriptorCertEncryptionNodes as $keyDescriptorCertEncryptionNode) { + $metadataInfo['idp']['x509certMulti']['encryption'][] = Utils::formatCert($keyDescriptorCertEncryptionNode->nodeValue, false); + } + } + + $idpCertdata = $metadataInfo['idp']['x509certMulti']; + if ((count($idpCertdata) == 1 and + ((isset($idpCertdata['signing']) and count($idpCertdata['signing']) == 1) or (isset($idpCertdata['encryption']) and count($idpCertdata['encryption']) == 1))) or + ((isset($idpCertdata['signing']) && count($idpCertdata['signing']) == 1) && isset($idpCertdata['encryption']) && count($idpCertdata['encryption']) == 1 && strcmp($idpCertdata['signing'][0], $idpCertdata['encryption'][0]) == 0)) { + if (isset($metadataInfo['idp']['x509certMulti']['signing'][0])) { + $metadataInfo['idp']['x509cert'] = $metadataInfo['idp']['x509certMulti']['signing'][0]; + } else { + $metadataInfo['idp']['x509cert'] = $metadataInfo['idp']['x509certMulti']['encryption'][0]; + } + unset($metadataInfo['idp']['x509certMulti']); + } + } + + $nameIdFormatNodes = Utils::query($dom, './md:NameIDFormat', $idpDescriptor); + if ($nameIdFormatNodes->length > 0) { + $metadataInfo['sp']['NameIDFormat'] = $nameIdFormatNodes->item(0)->nodeValue; + if (!empty($desiredNameIdFormat)) { + foreach ($nameIdFormatNodes as $nameIdFormatNode) { + if (strcmp($nameIdFormatNode->nodeValue, $desiredNameIdFormat) == 0) { + $metadataInfo['sp']['NameIDFormat'] = $nameIdFormatNode->nodeValue; + break; + } + } + } + } + } + } catch (Exception $e) { + throw new Exception('Error parsing metadata. '.$e->getMessage()); + } + + return $metadataInfo; + } + + /** + * Inject metadata info into php-saml settings array + * + * @param array $settings php-saml settings array + * @param array $metadataInfo array metadata info + * + * @return array settings + */ + public static function injectIntoSettings($settings, $metadataInfo) + { + if (isset($metadataInfo['idp']) && isset($settings['idp'])) { + if (isset($metadataInfo['idp']['x509certMulti']) && !empty($metadataInfo['idp']['x509certMulti']) && isset($settings['idp']['x509cert'])) { + unset($settings['idp']['x509cert']); + } + + if (isset($metadataInfo['idp']['x509cert']) && !empty($metadataInfo['idp']['x509cert']) && isset($settings['idp']['x509certMulti'])) { + unset($settings['idp']['x509certMulti']); + } + } + + return array_replace_recursive($settings, $metadataInfo); + } +} diff --git a/vendor/onelogin/php-saml/src/Saml2/LogoutRequest.php b/vendor/onelogin/php-saml/src/Saml2/LogoutRequest.php new file mode 100644 index 00000000..108c49be --- /dev/null +++ b/vendor/onelogin/php-saml/src/Saml2/LogoutRequest.php @@ -0,0 +1,494 @@ + + * @license MIT https://github.com/onelogin/php-saml/blob/master/LICENSE + * @link https://github.com/onelogin/php-saml + */ + +namespace OneLogin\Saml2; + +use RobRichards\XMLSecLibs\XMLSecurityKey; + +use DOMDocument; +use Exception; + +/** + * SAML 2 Logout Request + */ +class LogoutRequest +{ + /** + * Contains the ID of the Logout Request + * + * @var string + */ + public $id; + + /** + * Object that represents the setting info + * + * @var Settings + */ + protected $_settings; + + /** + * SAML Logout Request + * + * @var string + */ + protected $_logoutRequest; + + /** + * After execute a validation process, this var contains the cause + * + * @var Exception + */ + private $_error; + + /** + * Constructs the Logout Request object. + * + * @param Settings $settings Settings + * @param string|null $request A UUEncoded Logout Request. + * @param string|null $nameId The NameID that will be set in the LogoutRequest. + * @param string|null $sessionIndex The SessionIndex (taken from the SAML Response in the SSO process). + * @param string|null $nameIdFormat The NameID Format will be set in the LogoutRequest. + * @param string|null $nameIdNameQualifier The NameID NameQualifier will be set in the LogoutRequest. + * @param string|null $nameIdSPNameQualifier The NameID SP NameQualifier will be set in the LogoutRequest. + */ + public function __construct(\OneLogin\Saml2\Settings $settings, $request = null, $nameId = null, $sessionIndex = null, $nameIdFormat = null, $nameIdNameQualifier = null, $nameIdSPNameQualifier = null) + { + $this->_settings = $settings; + + $baseURL = $this->_settings->getBaseURL(); + if (!empty($baseURL)) { + Utils::setBaseURL($baseURL); + } + + if (!isset($request) || empty($request)) { + $spData = $this->_settings->getSPData(); + $idpData = $this->_settings->getIdPData(); + $security = $this->_settings->getSecurityData(); + + $id = Utils::generateUniqueID(); + $this->id = $id; + + $issueInstant = Utils::parseTime2SAML(time()); + + $cert = null; + if (isset($security['nameIdEncrypted']) && $security['nameIdEncrypted']) { + $existsMultiX509Enc = isset($idpData['x509certMulti']) && isset($idpData['x509certMulti']['encryption']) && !empty($idpData['x509certMulti']['encryption']); + + if ($existsMultiX509Enc) { + $cert = $idpData['x509certMulti']['encryption'][0]; + } else { + $cert = $idpData['x509cert']; + } + } + + if (!empty($nameId)) { + if (empty($nameIdFormat) + && $spData['NameIDFormat'] != Constants::NAMEID_UNSPECIFIED) { + $nameIdFormat = $spData['NameIDFormat']; + } + } else { + $nameId = $idpData['entityId']; + $nameIdFormat = Constants::NAMEID_ENTITY; + } + + /* From saml-core-2.0-os 8.3.6, when the entity Format is used: + "The NameQualifier, SPNameQualifier, and SPProvidedID attributes MUST be omitted. + */ + if (!empty($nameIdFormat) && $nameIdFormat == Constants::NAMEID_ENTITY) { + $nameIdNameQualifier = null; + $nameIdSPNameQualifier = null; + } + + // NameID Format UNSPECIFIED omitted + if (!empty($nameIdFormat) && $nameIdFormat == Constants::NAMEID_UNSPECIFIED) { + $nameIdFormat = null; + } + + $nameIdObj = Utils::generateNameId( + $nameId, + $nameIdSPNameQualifier, + $nameIdFormat, + $cert, + $nameIdNameQualifier, + $security['encryption_algorithm'] + ); + + $sessionIndexStr = isset($sessionIndex) ? "{$sessionIndex}" : ""; + + $spEntityId = htmlspecialchars($spData['entityId'], ENT_QUOTES); + $destination = $this->_settings->getIdPSLOUrl(); + $logoutRequest = << + {$spEntityId} + {$nameIdObj} + {$sessionIndexStr} + +LOGOUTREQUEST; + } else { + $decoded = base64_decode($request); + // We try to inflate + $inflated = @gzinflate($decoded); + if ($inflated != false) { + $logoutRequest = $inflated; + } else { + $logoutRequest = $decoded; + } + $this->id = static::getID($logoutRequest); + } + $this->_logoutRequest = $logoutRequest; + } + + /** + * Returns the Logout Request defated, base64encoded, unsigned + * + * @param bool|null $deflate Whether or not we should 'gzdeflate' the request body before we return it. + * + * @return string Deflated base64 encoded Logout Request + */ + public function getRequest($deflate = null) + { + $subject = $this->_logoutRequest; + + if (is_null($deflate)) { + $deflate = $this->_settings->shouldCompressRequests(); + } + + if ($deflate) { + $subject = gzdeflate($this->_logoutRequest); + } + + return base64_encode($subject); + } + + /** + * Returns the ID of the Logout Request. + * + * @param string|DOMDocument $request Logout Request Message + * + * @return string ID + * + * @throws Error + */ + public static function getID($request) + { + if ($request instanceof DOMDocument) { + $dom = $request; + } else { + $dom = new DOMDocument(); + $dom = Utils::loadXML($dom, $request); + } + + + if (false === $dom) { + throw new Error( + "LogoutRequest could not be processed", + Error::SAML_LOGOUTREQUEST_INVALID + ); + } + + $id = $dom->documentElement->getAttribute('ID'); + return $id; + } + + /** + * Gets the NameID Data of the the Logout Request. + * + * @param string|DOMDocument $request Logout Request Message + * @param string|null $key The SP key + * + * @return array Name ID Data (Value, Format, NameQualifier, SPNameQualifier) + * + * @throws Error + * @throws Exception + * @throws ValidationError + */ + public static function getNameIdData($request, $key = null) + { + if ($request instanceof DOMDocument) { + $dom = $request; + } else { + $dom = new DOMDocument(); + $dom = Utils::loadXML($dom, $request); + } + + $encryptedEntries = Utils::query($dom, '/samlp:LogoutRequest/saml:EncryptedID'); + + if ($encryptedEntries->length == 1) { + $encryptedDataNodes = $encryptedEntries->item(0)->getElementsByTagName('EncryptedData'); + $encryptedData = $encryptedDataNodes->item(0); + + if (empty($key)) { + throw new Error( + "Private Key is required in order to decrypt the NameID, check settings", + Error::PRIVATE_KEY_NOT_FOUND + ); + } + + $seckey = new XMLSecurityKey(XMLSecurityKey::RSA_1_5, array('type'=>'private')); + $seckey->loadKey($key); + + $nameId = Utils::decryptElement($encryptedData, $seckey); + + } else { + $entries = Utils::query($dom, '/samlp:LogoutRequest/saml:NameID'); + if ($entries->length == 1) { + $nameId = $entries->item(0); + } + } + + if (!isset($nameId)) { + throw new ValidationError( + "NameID not found in the Logout Request", + ValidationError::NO_NAMEID + ); + } + + $nameIdData = array(); + $nameIdData['Value'] = $nameId->nodeValue; + foreach (array('Format', 'SPNameQualifier', 'NameQualifier') as $attr) { + if ($nameId->hasAttribute($attr)) { + $nameIdData[$attr] = $nameId->getAttribute($attr); + } + } + + return $nameIdData; + } + + /** + * Gets the NameID of the Logout Request. + * + * @param string|DOMDocument $request Logout Request Message + * @param string|null $key The SP key + * + * @return string Name ID Value + * + * @throws Error + * @throws Exception + * @throws ValidationError + */ + public static function getNameId($request, $key = null) + { + $nameId = self::getNameIdData($request, $key); + return $nameId['Value']; + } + + /** + * Gets the Issuer of the Logout Request. + * + * @param string|DOMDocument $request Logout Request Message + * + * @return string|null $issuer The Issuer + * + * @throws Exception + */ + public static function getIssuer($request) + { + if ($request instanceof DOMDocument) { + $dom = $request; + } else { + $dom = new DOMDocument(); + $dom = Utils::loadXML($dom, $request); + } + + $issuer = null; + $issuerNodes = Utils::query($dom, '/samlp:LogoutRequest/saml:Issuer'); + if ($issuerNodes->length == 1) { + $issuer = $issuerNodes->item(0)->textContent; + } + return $issuer; + } + + /** + * Gets the SessionIndexes from the Logout Request. + * Notice: Our Constructor only support 1 SessionIndex but this parser + * extracts an array of all the SessionIndex found on a + * Logout Request, that could be many. + * + * @param string|DOMDocument $request Logout Request Message + * + * @return array The SessionIndex value + * + * @throws Exception + */ + public static function getSessionIndexes($request) + { + if ($request instanceof DOMDocument) { + $dom = $request; + } else { + $dom = new DOMDocument(); + $dom = Utils::loadXML($dom, $request); + } + + $sessionIndexes = array(); + $sessionIndexNodes = Utils::query($dom, '/samlp:LogoutRequest/samlp:SessionIndex'); + foreach ($sessionIndexNodes as $sessionIndexNode) { + $sessionIndexes[] = $sessionIndexNode->textContent; + } + return $sessionIndexes; + } + + /** + * Checks if the Logout Request recieved is valid. + * + * @param bool $retrieveParametersFromServer True if we want to use parameters from $_SERVER to validate the signature + * + * @return bool If the Logout Request is or not valid + * + * @throws Exception + * @throws ValidationError + */ + public function isValid($retrieveParametersFromServer = false) + { + $this->_error = null; + try { + $dom = new DOMDocument(); + $dom = Utils::loadXML($dom, $this->_logoutRequest); + + $idpData = $this->_settings->getIdPData(); + $idPEntityId = $idpData['entityId']; + + if ($this->_settings->isStrict()) { + $security = $this->_settings->getSecurityData(); + + if ($security['wantXMLValidation']) { + $res = Utils::validateXML($dom, 'saml-schema-protocol-2.0.xsd', $this->_settings->isDebugActive(), $this->_settings->getSchemasPath()); + if (!$res instanceof DOMDocument) { + throw new ValidationError( + "Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd", + ValidationError::INVALID_XML_FORMAT + ); + } + } + + $currentURL = Utils::getSelfRoutedURLNoQuery(); + + // Check NotOnOrAfter + if ($dom->documentElement->hasAttribute('NotOnOrAfter')) { + $na = Utils::parseSAML2Time($dom->documentElement->getAttribute('NotOnOrAfter')); + if ($na <= time()) { + throw new ValidationError( + "Could not validate timestamp: expired. Check system clock.", + ValidationError::RESPONSE_EXPIRED + ); + } + } + + // Check destination + if ($dom->documentElement->hasAttribute('Destination')) { + $destination = $dom->documentElement->getAttribute('Destination'); + if (empty($destination)) { + if (!$security['relaxDestinationValidation']) { + throw new ValidationError( + "The LogoutRequest has an empty Destination value", + ValidationError::EMPTY_DESTINATION + ); + } + } else { + $urlComparisonLength = $security['destinationStrictlyMatches'] ? strlen($destination) : strlen($currentURL); + if (strncmp($destination, $currentURL, $urlComparisonLength) !== 0) { + $currentURLNoRouted = Utils::getSelfURLNoQuery(); + $urlComparisonLength = $security['destinationStrictlyMatches'] ? strlen($destination) : strlen($currentURLNoRouted); + if (strncmp($destination, $currentURLNoRouted, $urlComparisonLength) !== 0) { + throw new ValidationError( + "The LogoutRequest was received at $currentURL instead of $destination", + ValidationError::WRONG_DESTINATION + ); + } + } + } + } + + $nameId = static::getNameId($dom, $this->_settings->getSPkey()); + + // Check issuer + $issuer = static::getIssuer($dom); + if (!empty($issuer) && $issuer != $idPEntityId) { + throw new ValidationError( + "Invalid issuer in the Logout Request", + ValidationError::WRONG_ISSUER + ); + } + + if ($security['wantMessagesSigned'] && !isset($_GET['Signature'])) { + throw new ValidationError( + "The Message of the Logout Request is not signed and the SP require it", + ValidationError::NO_SIGNED_MESSAGE + ); + } + } + + if (isset($_GET['Signature'])) { + $signatureValid = Utils::validateBinarySign("SAMLRequest", $_GET, $idpData, $retrieveParametersFromServer); + if (!$signatureValid) { + throw new ValidationError( + "Signature validation failed. Logout Request rejected", + ValidationError::INVALID_SIGNATURE + ); + } + } + + return true; + } catch (Exception $e) { + $this->_error = $e; + $debug = $this->_settings->isDebugActive(); + if ($debug) { + echo htmlentities($this->_error->getMessage()); + } + return false; + } + } + + /** + * After execute a validation process, if fails this method returns the Exception of the cause + * + * @return Exception Cause + */ + public function getErrorException() + { + return $this->_error; + } + + /** + * After execute a validation process, if fails this method returns the cause + * + * @return null|string Error reason + */ + public function getError() + { + $errorMsg = null; + if (isset($this->_error)) { + $errorMsg = htmlentities($this->_error->getMessage()); + } + return $errorMsg; + } + + /** + * Returns the XML that will be sent as part of the request + * or that was received at the SP + * + * @return string + */ + public function getXML() + { + return $this->_logoutRequest; + } +} diff --git a/vendor/onelogin/php-saml/src/Saml2/LogoutResponse.php b/vendor/onelogin/php-saml/src/Saml2/LogoutResponse.php new file mode 100644 index 00000000..9c3f020e --- /dev/null +++ b/vendor/onelogin/php-saml/src/Saml2/LogoutResponse.php @@ -0,0 +1,347 @@ + + * @license MIT https://github.com/onelogin/php-saml/blob/master/LICENSE + * @link https://github.com/onelogin/php-saml + */ + +namespace OneLogin\Saml2; + +use DOMDocument; +use DOMNodeList; +use Exception; + +/** + * SAML 2 Logout Response + */ +class LogoutResponse +{ + /** + * Contains the ID of the Logout Response + * + * @var string + */ + public $id; + + /** + * Object that represents the setting info + * + * @var Settings + */ + protected $_settings; + + /** + * The decoded, unprocessed XML response provided to the constructor. + * + * @var string|null + */ + protected $_logoutResponse; + + /** + * A DOMDocument class loaded from the SAML LogoutResponse. + * + * @var DOMDocument + */ + public $document; + + /** + * After execute a validation process, if it fails, this var contains the cause + * + * @var Exception|null + */ + private $_error; + + /** + * Constructs a Logout Response object (Initialize params from settings and if provided + * load the Logout Response. + * + * @param Settings $settings Settings. + * @param string|null $response An UUEncoded SAML Logout response from the IdP. + * + * @throws Error + * @throws Exception + */ + public function __construct(\OneLogin\Saml2\Settings $settings, $response = null) + { + $this->_settings = $settings; + + $baseURL = $this->_settings->getBaseURL(); + if (!empty($baseURL)) { + Utils::setBaseURL($baseURL); + } + + if ($response) { + $decoded = base64_decode($response); + $inflated = @gzinflate($decoded); + if ($inflated != false) { + $this->_logoutResponse = $inflated; + } else { + $this->_logoutResponse = $decoded; + } + $this->document = new DOMDocument(); + $this->document = Utils::loadXML($this->document, $this->_logoutResponse); + + if (false === $this->document) { + throw new Error( + "LogoutResponse could not be processed", + Error::SAML_LOGOUTRESPONSE_INVALID + ); + } + + if ($this->document->documentElement->hasAttribute('ID')) { + $this->id = $this->document->documentElement->getAttribute('ID'); + } + } + } + + /** + * Gets the Issuer of the Logout Response. + * + * @return string|null $issuer The Issuer + */ + public function getIssuer() + { + $issuer = null; + $issuerNodes = $this->_query('/samlp:LogoutResponse/saml:Issuer'); + if ($issuerNodes->length == 1) { + $issuer = $issuerNodes->item(0)->textContent; + } + return $issuer; + } + + /** + * Gets the Status of the Logout Response. + * + * @return string|null The Status + */ + public function getStatus() + { + $entries = $this->_query('/samlp:LogoutResponse/samlp:Status/samlp:StatusCode'); + if ($entries->length != 1) { + return null; + } + $status = $entries->item(0)->getAttribute('Value'); + return $status; + } + + /** + * Determines if the SAML LogoutResponse is valid + * + * @param string|null $requestId The ID of the LogoutRequest sent by this SP to the IdP + * @param bool $retrieveParametersFromServer True if we want to use parameters from $_SERVER to validate the signature + * + * @return bool Returns if the SAML LogoutResponse is or not valid + * + * @throws ValidationError + */ + public function isValid($requestId = null, $retrieveParametersFromServer = false) + { + $this->_error = null; + try { + $idpData = $this->_settings->getIdPData(); + $idPEntityId = $idpData['entityId']; + + if ($this->_settings->isStrict()) { + $security = $this->_settings->getSecurityData(); + + if ($security['wantXMLValidation']) { + $res = Utils::validateXML($this->document, 'saml-schema-protocol-2.0.xsd', $this->_settings->isDebugActive(), $this->_settings->getSchemasPath()); + if (!$res instanceof DOMDocument) { + throw new ValidationError( + "Invalid SAML Logout Response. Not match the saml-schema-protocol-2.0.xsd", + ValidationError::INVALID_XML_FORMAT + ); + } + } + + // Check if the InResponseTo of the Logout Response matchs the ID of the Logout Request (requestId) if provided + if (isset($requestId) && $this->document->documentElement->hasAttribute('InResponseTo')) { + $inResponseTo = $this->document->documentElement->getAttribute('InResponseTo'); + if ($requestId != $inResponseTo) { + throw new ValidationError( + "The InResponseTo of the Logout Response: $inResponseTo, does not match the ID of the Logout request sent by the SP: $requestId", + ValidationError::WRONG_INRESPONSETO + ); + } + } + + // Check issuer + $issuer = $this->getIssuer(); + if (!empty($issuer) && $issuer != $idPEntityId) { + throw new ValidationError( + "Invalid issuer in the Logout Response", + ValidationError::WRONG_ISSUER + ); + } + + $currentURL = Utils::getSelfRoutedURLNoQuery(); + + if ($this->document->documentElement->hasAttribute('Destination')) { + $destination = $this->document->documentElement->getAttribute('Destination'); + if (empty($destination)) { + if (!$security['relaxDestinationValidation']) { + throw new ValidationError( + "The LogoutResponse has an empty Destination value", + ValidationError::EMPTY_DESTINATION + ); + } + } else { + $urlComparisonLength = $security['destinationStrictlyMatches'] ? strlen($destination) : strlen($currentURL); + if (strncmp($destination, $currentURL, $urlComparisonLength) !== 0) { + $currentURLNoRouted = Utils::getSelfURLNoQuery(); + $urlComparisonLength = $security['destinationStrictlyMatches'] ? strlen($destination) : strlen($currentURLNoRouted); + if (strncmp($destination, $currentURLNoRouted, $urlComparisonLength) !== 0) { + throw new ValidationError( + "The LogoutResponse was received at $currentURL instead of $destination", + ValidationError::WRONG_DESTINATION + ); + } + } + } + } + + if ($security['wantMessagesSigned'] && !isset($_GET['Signature'])) { + throw new ValidationError( + "The Message of the Logout Response is not signed and the SP requires it", + ValidationError::NO_SIGNED_MESSAGE + ); + } + } + + if (isset($_GET['Signature'])) { + $signatureValid = Utils::validateBinarySign("SAMLResponse", $_GET, $idpData, $retrieveParametersFromServer); + if (!$signatureValid) { + throw new ValidationError( + "Signature validation failed. Logout Response rejected", + ValidationError::INVALID_SIGNATURE + ); + } + } + return true; + } catch (Exception $e) { + $this->_error = $e; + $debug = $this->_settings->isDebugActive(); + if ($debug) { + echo htmlentities($this->_error->getMessage()); + } + return false; + } + } + + /** + * Extracts a node from the DOMDocument (Logout Response Menssage) + * + * @param string $query Xpath Expression + * + * @return DOMNodeList The queried node + */ + private function _query($query) + { + return Utils::query($this->document, $query); + + } + + /** + * Generates a Logout Response object. + * + * @param string $inResponseTo InResponseTo value for the Logout Response. + */ + public function build($inResponseTo) + { + + $spData = $this->_settings->getSPData(); + + $this->id = Utils::generateUniqueID(); + $issueInstant = Utils::parseTime2SAML(time()); + $spEntityId = htmlspecialchars($spData['entityId'], ENT_QUOTES); + $destination = $this->_settings->getIdPSLOResponseUrl(); + $logoutResponse = << + {$spEntityId} + + + + +LOGOUTRESPONSE; + $this->_logoutResponse = $logoutResponse; + } + + /** + * Returns a Logout Response object. + * + * @param bool|null $deflate Whether or not we should 'gzdeflate' the response body before we return it. + * + * @return string Logout Response deflated and base64 encoded + */ + public function getResponse($deflate = null) + { + $logoutResponse = $this->_logoutResponse; + + if (is_null($deflate)) { + $deflate = $this->_settings->shouldCompressResponses(); + } + + if ($deflate) { + $logoutResponse = gzdeflate($this->_logoutResponse); + } + return base64_encode($logoutResponse); + } + + /** + * After execute a validation process, if fails this method returns the cause. + * + * @return Exception|null Cause + */ + public function getErrorException() + { + return $this->_error; + } + + /** + * After execute a validation process, if fails this method returns the cause + * + * @return null|string Error reason + */ + public function getError() + { + $errorMsg = null; + if (isset($this->_error)) { + $errorMsg = htmlentities($this->_error->getMessage()); + } + return $errorMsg; + } + + /** + * @return string the ID of the Response + */ + public function getId() + { + return $this->id; + } + + /** + * Returns the XML that will be sent as part of the response + * or that was received at the SP + * + * @return string|null + */ + public function getXML() + { + return $this->_logoutResponse; + } +} diff --git a/vendor/onelogin/php-saml/src/Saml2/Metadata.php b/vendor/onelogin/php-saml/src/Saml2/Metadata.php new file mode 100644 index 00000000..922ad60b --- /dev/null +++ b/vendor/onelogin/php-saml/src/Saml2/Metadata.php @@ -0,0 +1,267 @@ + + * @license MIT https://github.com/onelogin/php-saml/blob/master/LICENSE + * @link https://github.com/onelogin/php-saml + */ + +namespace OneLogin\Saml2; + +use RobRichards\XMLSecLibs\XMLSecurityKey; +use RobRichards\XMLSecLibs\XMLSecurityDSig; + +use DOMDocument; +use Exception; + +/** + * Metadata lib of OneLogin PHP Toolkit + */ +class Metadata +{ + const TIME_VALID = 172800; // 2 days + const TIME_CACHED = 604800; // 1 week + + /** + * Generates the metadata of the SP based on the settings + * + * @param array $sp The SP data + * @param bool|string $authnsign authnRequestsSigned attribute + * @param bool|string $wsign wantAssertionsSigned attribute + * @param int|null $validUntil Metadata's valid time + * @param int|null $cacheDuration Duration of the cache in seconds + * @param array $contacts Contacts info + * @param array $organization Organization ingo + * @param array $attributes + * + * @return string SAML Metadata XML + */ + public static function builder($sp, $authnsign = false, $wsign = false, $validUntil = null, $cacheDuration = null, $contacts = array(), $organization = array(), $attributes = array()) + { + + if (!isset($validUntil)) { + $validUntil = time() + self::TIME_VALID; + } + $validUntilTime = Utils::parseTime2SAML($validUntil); + + if (!isset($cacheDuration)) { + $cacheDuration = self::TIME_CACHED; + } + + $sls = ''; + + if (isset($sp['singleLogoutService'])) { + $slsUrl = htmlspecialchars($sp['singleLogoutService']['url'], ENT_QUOTES); + $sls = << + +SLS_TEMPLATE; + } + + if ($authnsign) { + $strAuthnsign = 'true'; + } else { + $strAuthnsign = 'false'; + } + + if ($wsign) { + $strWsign = 'true'; + } else { + $strWsign = 'false'; + } + + $strOrganization = ''; + + if (!empty($organization)) { + $organizationInfoNames = array(); + $organizationInfoDisplaynames = array(); + $organizationInfoUrls = array(); + foreach ($organization as $lang => $info) { + $organizationInfoNames[] = <<{$info['name']} +ORGANIZATION_NAME; + $organizationInfoDisplaynames[] = <<{$info['displayname']} +ORGANIZATION_DISPLAY; + $organizationInfoUrls[] = <<{$info['url']} +ORGANIZATION_URL; + } + $orgData = implode("\n", $organizationInfoNames)."\n".implode("\n", $organizationInfoDisplaynames)."\n".implode("\n", $organizationInfoUrls); + $strOrganization = << +{$orgData} + +ORGANIZATIONSTR; + } + + $strContacts = ''; + if (!empty($contacts)) { + $contactsInfo = array(); + foreach ($contacts as $type => $info) { + $contactsInfo[] = << + {$info['givenName']} + {$info['emailAddress']} + +CONTACT; + } + $strContacts = "\n".implode("\n", $contactsInfo); + } + + $strAttributeConsumingService = ''; + if (isset($sp['attributeConsumingService'])) { + $attrCsDesc = ''; + if (isset($sp['attributeConsumingService']['serviceDescription'])) { + $attrCsDesc = sprintf( + ' %s' . PHP_EOL, + $sp['attributeConsumingService']['serviceDescription'] + ); + } + if (!isset($sp['attributeConsumingService']['serviceName'])) { + $sp['attributeConsumingService']['serviceName'] = 'Service'; + } + $requestedAttributeData = array(); + foreach ($sp['attributeConsumingService']['requestedAttributes'] as $attribute) { + $requestedAttributeStr = sprintf(' {$attrValue} +ATTRIBUTEVALUE; + } + $reqAttrAuxStr .= "\n "; + } + + $requestedAttributeData[] = $requestedAttributeStr . $reqAttrAuxStr; + } + + $requestedAttributeStr = implode(PHP_EOL, $requestedAttributeData); + $strAttributeConsumingService = << + {$sp['attributeConsumingService']['serviceName']} +{$attrCsDesc}{$requestedAttributeStr} + +METADATA_TEMPLATE; + } + + $spEntityId = htmlspecialchars($sp['entityId'], ENT_QUOTES); + $acsUrl = htmlspecialchars($sp['assertionConsumerService']['url'], ENT_QUOTES); + $metadata = << + + +{$sls} {$sp['NameIDFormat']} + + {$strAttributeConsumingService} + {$strOrganization}{$strContacts} + +METADATA_TEMPLATE; + return $metadata; + } + + /** + * Signs the metadata with the key/cert provided + * + * @param string $metadata SAML Metadata XML + * @param string $key x509 key + * @param string $cert x509 cert + * @param string $signAlgorithm Signature algorithm method + * @param string $digestAlgorithm Digest algorithm method + * + * @return string Signed Metadata + * + * @throws Exception + */ + public static function signMetadata($metadata, $key, $cert, $signAlgorithm = XMLSecurityKey::RSA_SHA256, $digestAlgorithm = XMLSecurityDSig::SHA256) + { + return Utils::addSign($metadata, $key, $cert, $signAlgorithm, $digestAlgorithm); + } + + /** + * Adds the x509 descriptors (sign/encryption) to the metadata + * The same cert will be used for sign/encrypt + * + * @param string $metadata SAML Metadata XML + * @param string $cert x509 cert + * @param bool $wantsEncrypted Whether to include the KeyDescriptor for encryption + * + * @return string Metadata with KeyDescriptors + * + * @throws Exception + */ + public static function addX509KeyDescriptors($metadata, $cert, $wantsEncrypted = true) + { + $xml = new DOMDocument(); + $xml->preserveWhiteSpace = false; + $xml->formatOutput = true; + try { + $xml = Utils::loadXML($xml, $metadata); + if (!$xml) { + throw new Exception('Error parsing metadata'); + } + } catch (Exception $e) { + throw new Exception('Error parsing metadata. '.$e->getMessage()); + } + + $formatedCert = Utils::formatCert($cert, false); + $x509Certificate = $xml->createElementNS(Constants::NS_DS, 'X509Certificate', $formatedCert); + + $keyData = $xml->createElementNS(Constants::NS_DS, 'ds:X509Data'); + $keyData->appendChild($x509Certificate); + + $keyInfo = $xml->createElementNS(Constants::NS_DS, 'ds:KeyInfo'); + $keyInfo->appendChild($keyData); + + $keyDescriptor = $xml->createElementNS(Constants::NS_MD, "md:KeyDescriptor"); + + $SPSSODescriptor = $xml->getElementsByTagName('SPSSODescriptor')->item(0); + $SPSSODescriptor->insertBefore($keyDescriptor->cloneNode(), $SPSSODescriptor->firstChild); + if ($wantsEncrypted === true) { + $SPSSODescriptor->insertBefore($keyDescriptor->cloneNode(), $SPSSODescriptor->firstChild); + } + + $signing = $xml->getElementsByTagName('KeyDescriptor')->item(0); + $signing->setAttribute('use', 'signing'); + $signing->appendChild($keyInfo); + + if ($wantsEncrypted === true) { + $encryption = $xml->getElementsByTagName('KeyDescriptor')->item(1); + $encryption->setAttribute('use', 'encryption'); + + $encryption->appendChild($keyInfo->cloneNode(true)); + } + + return $xml->saveXML(); + } +} diff --git a/vendor/onelogin/php-saml/src/Saml2/Response.php b/vendor/onelogin/php-saml/src/Saml2/Response.php new file mode 100644 index 00000000..a2f8d6dd --- /dev/null +++ b/vendor/onelogin/php-saml/src/Saml2/Response.php @@ -0,0 +1,1237 @@ + + * @license MIT https://github.com/onelogin/php-saml/blob/master/LICENSE + * @link https://github.com/onelogin/php-saml + */ + +namespace OneLogin\Saml2; + +use RobRichards\XMLSecLibs\XMLSecurityKey; +use RobRichards\XMLSecLibs\XMLSecEnc; + +use DOMDocument; +use DOMNodeList; +use DOMXPath; +use Exception; + +/** + * SAML 2 Authentication Response + */ +class Response +{ + /** + * Settings + * + * @var Settings + */ + protected $_settings; + + /** + * The decoded, unprocessed XML response provided to the constructor. + * + * @var string + */ + public $response; + + /** + * A DOMDocument class loaded from the SAML Response. + * + * @var DOMDocument + */ + public $document; + + /** + * A DOMDocument class loaded from the SAML Response (Decrypted). + * + * @var DOMDocument + */ + public $decryptedDocument; + + /** + * The response contains an encrypted assertion. + * + * @var bool + */ + public $encrypted = false; + + /** + * After validation, if it fail this var has the cause of the problem + * + * @var Exception|null + */ + private $_error; + + /** + * NotOnOrAfter value of a valid SubjectConfirmationData node + * + * @var int + */ + private $_validSCDNotOnOrAfter; + + /** + * Constructs the SAML Response object. + * + * @param Settings $settings Settings. + * @param string $response A UUEncoded SAML response from the IdP. + * + * @throws Exception + * @throws ValidationError + */ + public function __construct(\OneLogin\Saml2\Settings $settings, $response) + { + $this->_settings = $settings; + + $baseURL = $this->_settings->getBaseURL(); + if (!empty($baseURL)) { + Utils::setBaseURL($baseURL); + } + + $this->response = base64_decode($response); + + $this->document = new DOMDocument(); + $this->document = Utils::loadXML($this->document, $this->response); + if (!$this->document) { + throw new ValidationError( + "SAML Response could not be processed", + ValidationError::INVALID_XML_FORMAT + ); + } + + // Quick check for the presence of EncryptedAssertion + $encryptedAssertionNodes = $this->document->getElementsByTagName('EncryptedAssertion'); + if ($encryptedAssertionNodes->length !== 0) { + $this->decryptedDocument = clone $this->document; + $this->encrypted = true; + $this->decryptedDocument = $this->decryptAssertion($this->decryptedDocument); + } + } + + /** + * Determines if the SAML Response is valid using the certificate. + * + * @param string|null $requestId The ID of the AuthNRequest sent by this SP to the IdP + * + * @return bool Validate the document + * + * @throws Exception + * @throws ValidationError + */ + public function isValid($requestId = null) + { + $this->_error = null; + try { + // Check SAML version + if ($this->document->documentElement->getAttribute('Version') != '2.0') { + throw new ValidationError( + "Unsupported SAML version", + ValidationError::UNSUPPORTED_SAML_VERSION + ); + } + + if (!$this->document->documentElement->hasAttribute('ID')) { + throw new ValidationError( + "Missing ID attribute on SAML Response", + ValidationError::MISSING_ID + ); + } + + $this->checkStatus(); + + $singleAssertion = $this->validateNumAssertions(); + if (!$singleAssertion) { + throw new ValidationError( + "SAML Response must contain 1 assertion", + ValidationError::WRONG_NUMBER_OF_ASSERTIONS + ); + } + + $idpData = $this->_settings->getIdPData(); + $idPEntityId = $idpData['entityId']; + $spData = $this->_settings->getSPData(); + $spEntityId = $spData['entityId']; + + $signedElements = $this->processSignedElements(); + + $responseTag = '{'.Constants::NS_SAMLP.'}Response'; + $assertionTag = '{'.Constants::NS_SAML.'}Assertion'; + + $hasSignedResponse = in_array($responseTag, $signedElements); + $hasSignedAssertion = in_array($assertionTag, $signedElements); + + if ($this->_settings->isStrict()) { + $security = $this->_settings->getSecurityData(); + + if ($security['wantXMLValidation']) { + $errorXmlMsg = "Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd"; + $res = Utils::validateXML($this->document, 'saml-schema-protocol-2.0.xsd', $this->_settings->isDebugActive(), $this->_settings->getSchemasPath()); + if (!$res instanceof DOMDocument) { + throw new ValidationError( + $errorXmlMsg, + ValidationError::INVALID_XML_FORMAT + ); + } + + // If encrypted, check also the decrypted document + if ($this->encrypted) { + $res = Utils::validateXML($this->decryptedDocument, 'saml-schema-protocol-2.0.xsd', $this->_settings->isDebugActive(), $this->_settings->getSchemasPath()); + if (!$res instanceof DOMDocument) { + throw new ValidationError( + $errorXmlMsg, + ValidationError::INVALID_XML_FORMAT + ); + } + } + + } + + $currentURL = Utils::getSelfRoutedURLNoQuery(); + + $responseInResponseTo = null; + if ($this->document->documentElement->hasAttribute('InResponseTo')) { + $responseInResponseTo = $this->document->documentElement->getAttribute('InResponseTo'); + } + + if (!isset($requestId) && isset($responseInResponseTo) && $security['rejectUnsolicitedResponsesWithInResponseTo']) { + throw new ValidationError( + "The Response has an InResponseTo attribute: " . $responseInResponseTo . " while no InResponseTo was expected", + ValidationError::WRONG_INRESPONSETO + ); + } + + // Check if the InResponseTo of the Response matchs the ID of the AuthNRequest (requestId) if provided + if (isset($requestId) && $requestId != $responseInResponseTo) { + if ($responseInResponseTo == null) { + throw new ValidationError( + "No InResponseTo at the Response, but it was provided the requestId related to the AuthNRequest sent by the SP: $requestId", + ValidationError::WRONG_INRESPONSETO + ); + } else { + throw new ValidationError( + "The InResponseTo of the Response: $responseInResponseTo, does not match the ID of the AuthNRequest sent by the SP: $requestId", + ValidationError::WRONG_INRESPONSETO + ); + } + } + + if (!$this->encrypted && $security['wantAssertionsEncrypted']) { + throw new ValidationError( + "The assertion of the Response is not encrypted and the SP requires it", + ValidationError::NO_ENCRYPTED_ASSERTION + ); + } + + if ($security['wantNameIdEncrypted']) { + $encryptedIdNodes = $this->_queryAssertion('/saml:Subject/saml:EncryptedID/xenc:EncryptedData'); + if ($encryptedIdNodes->length != 1) { + throw new ValidationError( + "The NameID of the Response is not encrypted and the SP requires it", + ValidationError::NO_ENCRYPTED_NAMEID + ); + } + } + + // Validate Conditions element exists + if (!$this->checkOneCondition()) { + throw new ValidationError( + "The Assertion must include a Conditions element", + ValidationError::MISSING_CONDITIONS + ); + } + + // Validate Asserion timestamps + $this->validateTimestamps(); + + // Validate AuthnStatement element exists and is unique + if (!$this->checkOneAuthnStatement()) { + throw new ValidationError( + "The Assertion must include an AuthnStatement element", + ValidationError::WRONG_NUMBER_OF_AUTHSTATEMENTS + ); + } + + // EncryptedAttributes are not supported + $encryptedAttributeNodes = $this->_queryAssertion('/saml:AttributeStatement/saml:EncryptedAttribute'); + if ($encryptedAttributeNodes->length > 0) { + throw new ValidationError( + "There is an EncryptedAttribute in the Response and this SP not support them", + ValidationError::ENCRYPTED_ATTRIBUTES + ); + } + + // Check destination + if ($this->document->documentElement->hasAttribute('Destination')) { + $destination = trim($this->document->documentElement->getAttribute('Destination')); + if (empty($destination)) { + if (!$security['relaxDestinationValidation']) { + throw new ValidationError( + "The response has an empty Destination value", + ValidationError::EMPTY_DESTINATION + ); + } + } else { + $urlComparisonLength = $security['destinationStrictlyMatches'] ? strlen($destination) : strlen($currentURL); + if (strncmp($destination, $currentURL, $urlComparisonLength) !== 0) { + $currentURLNoRouted = Utils::getSelfURLNoQuery(); + $urlComparisonLength = $security['destinationStrictlyMatches'] ? strlen($destination) : strlen($currentURLNoRouted); + if (strncmp($destination, $currentURLNoRouted, $urlComparisonLength) !== 0) { + throw new ValidationError( + "The response was received at $currentURL instead of $destination", + ValidationError::WRONG_DESTINATION + ); + } + } + } + } + + // Check audience + $validAudiences = $this->getAudiences(); + if (!empty($validAudiences) && !in_array($spEntityId, $validAudiences, true)) { + throw new ValidationError( + sprintf( + "Invalid audience for this Response (expected '%s', got '%s')", + $spEntityId, + implode(',', $validAudiences) + ), + ValidationError::WRONG_AUDIENCE + ); + } + + // Check the issuers + $issuers = $this->getIssuers(); + foreach ($issuers as $issuer) { + $trimmedIssuer = trim($issuer); + if (empty($trimmedIssuer) || $trimmedIssuer !== $idPEntityId) { + throw new ValidationError( + "Invalid issuer in the Assertion/Response (expected '$idPEntityId', got '$trimmedIssuer')", + ValidationError::WRONG_ISSUER + ); + } + } + + // Check the session Expiration + $sessionExpiration = $this->getSessionNotOnOrAfter(); + if (!empty($sessionExpiration) && $sessionExpiration + Constants::ALLOWED_CLOCK_DRIFT <= time()) { + throw new ValidationError( + "The attributes have expired, based on the SessionNotOnOrAfter of the AttributeStatement of this Response", + ValidationError::SESSION_EXPIRED + ); + } + + // Check the SubjectConfirmation, at least one SubjectConfirmation must be valid + $anySubjectConfirmation = false; + $subjectConfirmationNodes = $this->_queryAssertion('/saml:Subject/saml:SubjectConfirmation'); + foreach ($subjectConfirmationNodes as $scn) { + if ($scn->hasAttribute('Method') && $scn->getAttribute('Method') != Constants::CM_BEARER) { + continue; + } + $subjectConfirmationDataNodes = $scn->getElementsByTagName('SubjectConfirmationData'); + if ($subjectConfirmationDataNodes->length == 0) { + continue; + } else { + $scnData = $subjectConfirmationDataNodes->item(0); + if ($scnData->hasAttribute('InResponseTo')) { + $inResponseTo = $scnData->getAttribute('InResponseTo'); + if (isset($responseInResponseTo) && $responseInResponseTo != $inResponseTo) { + continue; + } + } + if ($scnData->hasAttribute('Recipient')) { + $recipient = $scnData->getAttribute('Recipient'); + if (!empty($recipient) && strpos($recipient, $currentURL) === false) { + continue; + } + } + if ($scnData->hasAttribute('NotOnOrAfter')) { + $noa = Utils::parseSAML2Time($scnData->getAttribute('NotOnOrAfter')); + if ($noa + Constants::ALLOWED_CLOCK_DRIFT <= time()) { + continue; + } + } + if ($scnData->hasAttribute('NotBefore')) { + $nb = Utils::parseSAML2Time($scnData->getAttribute('NotBefore')); + if ($nb > time() + Constants::ALLOWED_CLOCK_DRIFT) { + continue; + } + } + + // Save NotOnOrAfter value + if ($scnData->hasAttribute('NotOnOrAfter')) { + $this->_validSCDNotOnOrAfter = $noa; + } + $anySubjectConfirmation = true; + break; + } + } + + if (!$anySubjectConfirmation) { + throw new ValidationError( + "A valid SubjectConfirmation was not found on this Response", + ValidationError::WRONG_SUBJECTCONFIRMATION + ); + } + + if ($security['wantAssertionsSigned'] && !$hasSignedAssertion) { + throw new ValidationError( + "The Assertion of the Response is not signed and the SP requires it", + ValidationError::NO_SIGNED_ASSERTION + ); + } + + if ($security['wantMessagesSigned'] && !$hasSignedResponse) { + throw new ValidationError( + "The Message of the Response is not signed and the SP requires it", + ValidationError::NO_SIGNED_MESSAGE + ); + } + } + + // Detect case not supported + if ($this->encrypted) { + $encryptedIDNodes = Utils::query($this->decryptedDocument, '/samlp:Response/saml:Assertion/saml:Subject/saml:EncryptedID'); + if ($encryptedIDNodes->length > 0) { + throw new ValidationError( + 'SAML Response that contains an encrypted Assertion with encrypted nameId is not supported.', + ValidationError::NOT_SUPPORTED + ); + } + } + + if (empty($signedElements) || (!$hasSignedResponse && !$hasSignedAssertion)) { + throw new ValidationError( + 'No Signature found. SAML Response rejected', + ValidationError::NO_SIGNATURE_FOUND + ); + } else { + $cert = $idpData['x509cert']; + $fingerprint = $idpData['certFingerprint']; + $fingerprintalg = $idpData['certFingerprintAlgorithm']; + + $multiCerts = null; + $existsMultiX509Sign = isset($idpData['x509certMulti']) && isset($idpData['x509certMulti']['signing']) && !empty($idpData['x509certMulti']['signing']); + + if ($existsMultiX509Sign) { + $multiCerts = $idpData['x509certMulti']['signing']; + } + + // If find a Signature on the Response, validates it checking the original response + if ($hasSignedResponse && !Utils::validateSign($this->document, $cert, $fingerprint, $fingerprintalg, Utils::RESPONSE_SIGNATURE_XPATH, $multiCerts)) { + throw new ValidationError( + "Signature validation failed. SAML Response rejected", + ValidationError::INVALID_SIGNATURE + ); + } + + // If find a Signature on the Assertion (decrypted assertion if was encrypted) + $documentToCheckAssertion = $this->encrypted ? $this->decryptedDocument : $this->document; + if ($hasSignedAssertion && !Utils::validateSign($documentToCheckAssertion, $cert, $fingerprint, $fingerprintalg, Utils::ASSERTION_SIGNATURE_XPATH, $multiCerts)) { + throw new ValidationError( + "Signature validation failed. SAML Response rejected", + ValidationError::INVALID_SIGNATURE + ); + } + } + return true; + } catch (Exception $e) { + $this->_error = $e; + $debug = $this->_settings->isDebugActive(); + if ($debug) { + echo htmlentities($e->getMessage()); + } + return false; + } + } + + /** + * @return string|null the ID of the Response + */ + public function getId() + { + $id = null; + if ($this->document->documentElement->hasAttribute('ID')) { + $id = $this->document->documentElement->getAttribute('ID'); + } + return $id; + } + + /** + * @return string|null the ID of the assertion in the Response + * + * @throws ValidationError + */ + public function getAssertionId() + { + if (!$this->validateNumAssertions()) { + throw new ValidationError("SAML Response must contain 1 Assertion.", ValidationError::WRONG_NUMBER_OF_ASSERTIONS); + } + $assertionNodes = $this->_queryAssertion(""); + $id = null; + if ($assertionNodes->length == 1 && $assertionNodes->item(0)->hasAttribute('ID')) { + $id = $assertionNodes->item(0)->getAttribute('ID'); + } + return $id; + } + + /** + * @return int the NotOnOrAfter value of the valid SubjectConfirmationData + * node if any + */ + public function getAssertionNotOnOrAfter() + { + return $this->_validSCDNotOnOrAfter; + } + + /** + * Checks if the Status is success + * + * @throws ValidationError If status is not success + */ + public function checkStatus() + { + $status = Utils::getStatus($this->document); + + if (isset($status['code']) && $status['code'] !== Constants::STATUS_SUCCESS) { + $explodedCode = explode(':', $status['code']); + $printableCode = array_pop($explodedCode); + + $statusExceptionMsg = 'The status code of the Response was not Success, was '.$printableCode; + if (!empty($status['msg'])) { + $statusExceptionMsg .= ' -> '.$status['msg']; + } + throw new ValidationError( + $statusExceptionMsg, + ValidationError::STATUS_CODE_IS_NOT_SUCCESS + ); + } + } + + /** + * Checks that the samlp:Response/saml:Assertion/saml:Conditions element exists and is unique. + * + * @return boolean true if the Conditions element exists and is unique + */ + public function checkOneCondition() + { + $entries = $this->_queryAssertion("/saml:Conditions"); + if ($entries->length == 1) { + return true; + } else { + return false; + } + } + + /** + * Checks that the samlp:Response/saml:Assertion/saml:AuthnStatement element exists and is unique. + * + * @return boolean true if the AuthnStatement element exists and is unique + */ + public function checkOneAuthnStatement() + { + $entries = $this->_queryAssertion("/saml:AuthnStatement"); + if ($entries->length == 1) { + return true; + } else { + return false; + } + } + + /** + * Gets the audiences. + * + * @return array @audience The valid audiences of the response + */ + public function getAudiences() + { + $audiences = array(); + + $entries = $this->_queryAssertion('/saml:Conditions/saml:AudienceRestriction/saml:Audience'); + foreach ($entries as $entry) { + $value = trim($entry->textContent); + if (!empty($value)) { + $audiences[] = $value; + } + } + + return array_unique($audiences); + } + + /** + * Gets the Issuers (from Response and Assertion). + * + * @return array @issuers The issuers of the assertion/response + * + * @throws ValidationError + */ + public function getIssuers() + { + $issuers = array(); + + $responseIssuer = Utils::query($this->document, '/samlp:Response/saml:Issuer'); + if ($responseIssuer->length > 0) { + if ($responseIssuer->length == 1) { + $issuers[] = $responseIssuer->item(0)->textContent; + } else { + throw new ValidationError( + "Issuer of the Response is multiple.", + ValidationError::ISSUER_MULTIPLE_IN_RESPONSE + ); + } + } + + $assertionIssuer = $this->_queryAssertion('/saml:Issuer'); + if ($assertionIssuer->length == 1) { + $issuers[] = $assertionIssuer->item(0)->textContent; + } else { + throw new ValidationError( + "Issuer of the Assertion not found or multiple.", + ValidationError::ISSUER_NOT_FOUND_IN_ASSERTION + ); + } + + return array_unique($issuers); + } + + /** + * Gets the NameID Data provided by the SAML response from the IdP. + * + * @return array Name ID Data (Value, Format, NameQualifier, SPNameQualifier) + * + * @throws ValidationError + */ + public function getNameIdData() + { + $encryptedIdDataEntries = $this->_queryAssertion('/saml:Subject/saml:EncryptedID/xenc:EncryptedData'); + + if ($encryptedIdDataEntries->length == 1) { + $encryptedData = $encryptedIdDataEntries->item(0); + + $key = $this->_settings->getSPkey(); + $seckey = new XMLSecurityKey(XMLSecurityKey::RSA_1_5, array('type'=>'private')); + $seckey->loadKey($key); + + $nameId = Utils::decryptElement($encryptedData, $seckey); + + } else { + $entries = $this->_queryAssertion('/saml:Subject/saml:NameID'); + if ($entries->length == 1) { + $nameId = $entries->item(0); + } + } + + $nameIdData = array(); + + if (!isset($nameId)) { + $security = $this->_settings->getSecurityData(); + if ($security['wantNameId']) { + throw new ValidationError( + "NameID not found in the assertion of the Response", + ValidationError::NO_NAMEID + ); + } + } else { + if ($this->_settings->isStrict() && empty($nameId->nodeValue)) { + throw new ValidationError( + "An empty NameID value found", + ValidationError::EMPTY_NAMEID + ); + } + $nameIdData['Value'] = $nameId->nodeValue; + + foreach (array('Format', 'SPNameQualifier', 'NameQualifier') as $attr) { + if ($nameId->hasAttribute($attr)) { + if ($this->_settings->isStrict() && $attr == 'SPNameQualifier') { + $spData = $this->_settings->getSPData(); + $spEntityId = $spData['entityId']; + if ($spEntityId != $nameId->getAttribute($attr)) { + throw new ValidationError( + "The SPNameQualifier value mistmatch the SP entityID value.", + ValidationError::SP_NAME_QUALIFIER_NAME_MISMATCH + ); + } + } + $nameIdData[$attr] = $nameId->getAttribute($attr); + } + } + } + + return $nameIdData; + } + + /** + * Gets the NameID provided by the SAML response from the IdP. + * + * @return string|null Name ID Value + * + * @throws ValidationError + */ + public function getNameId() + { + $nameIdvalue = null; + $nameIdData = $this->getNameIdData(); + if (!empty($nameIdData) && isset($nameIdData['Value'])) { + $nameIdvalue = $nameIdData['Value']; + } + return $nameIdvalue; + } + + /** + * Gets the NameID Format provided by the SAML response from the IdP. + * + * @return string|null Name ID Format + * + * @throws ValidationError + */ + public function getNameIdFormat() + { + $nameIdFormat = null; + $nameIdData = $this->getNameIdData(); + if (!empty($nameIdData) && isset($nameIdData['Format'])) { + $nameIdFormat = $nameIdData['Format']; + } + return $nameIdFormat; + } + + /** + * Gets the NameID NameQualifier provided by the SAML response from the IdP. + * + * @return string|null Name ID NameQualifier + * + * @throws ValidationError + */ + public function getNameIdNameQualifier() + { + $nameIdNameQualifier = null; + $nameIdData = $this->getNameIdData(); + if (!empty($nameIdData) && isset($nameIdData['NameQualifier'])) { + $nameIdNameQualifier = $nameIdData['NameQualifier']; + } + return $nameIdNameQualifier; + } + + /** + * Gets the NameID SP NameQualifier provided by the SAML response from the IdP. + * + * @return string|null NameID SP NameQualifier + * + * @throws ValidationError + */ + public function getNameIdSPNameQualifier() + { + $nameIdSPNameQualifier = null; + $nameIdData = $this->getNameIdData(); + if (!empty($nameIdData) && isset($nameIdData['SPNameQualifier'])) { + $nameIdSPNameQualifier = $nameIdData['SPNameQualifier']; + } + return $nameIdSPNameQualifier; + } + + /** + * Gets the SessionNotOnOrAfter from the AuthnStatement. + * Could be used to set the local session expiration + * + * @return int|null The SessionNotOnOrAfter value + * + * @throws Exception + */ + public function getSessionNotOnOrAfter() + { + $notOnOrAfter = null; + $entries = $this->_queryAssertion('/saml:AuthnStatement[@SessionNotOnOrAfter]'); + if ($entries->length !== 0) { + $notOnOrAfter = Utils::parseSAML2Time($entries->item(0)->getAttribute('SessionNotOnOrAfter')); + } + return $notOnOrAfter; + } + + /** + * Gets the SessionIndex from the AuthnStatement. + * Could be used to be stored in the local session in order + * to be used in a future Logout Request that the SP could + * send to the SP, to set what specific session must be deleted + * + * @return string|null The SessionIndex value + */ + public function getSessionIndex() + { + $sessionIndex = null; + $entries = $this->_queryAssertion('/saml:AuthnStatement[@SessionIndex]'); + if ($entries->length !== 0) { + $sessionIndex = $entries->item(0)->getAttribute('SessionIndex'); + } + return $sessionIndex; + } + + /** + * Gets the Attributes from the AttributeStatement element. + * + * @return array The attributes of the SAML Assertion + * + * @throws ValidationError + */ + public function getAttributes() + { + return $this->_getAttributesByKeyName('Name'); + } + + /** + * Gets the Attributes from the AttributeStatement element using their FriendlyName. + * + * @return array The attributes of the SAML Assertion + * + * @throws ValidationError + */ + public function getAttributesWithFriendlyName() + { + return $this->_getAttributesByKeyName('FriendlyName'); + } + + /** + * @param string $keyName + * + * @return array + * + * @throws ValidationError + */ + private function _getAttributesByKeyName($keyName = "Name") + { + $attributes = array(); + $entries = $this->_queryAssertion('/saml:AttributeStatement/saml:Attribute'); + + $security = $this->_settings->getSecurityData(); + $allowRepeatAttributeName = $security['allowRepeatAttributeName']; + /** @var $entry DOMNode */ + foreach ($entries as $entry) { + $attributeKeyNode = $entry->attributes->getNamedItem($keyName); + if ($attributeKeyNode === null) { + continue; + } + $attributeKeyName = $attributeKeyNode->nodeValue; + if (in_array($attributeKeyName, array_keys($attributes))) { + if (!$allowRepeatAttributeName) { + throw new ValidationError( + "Found an Attribute element with duplicated ".$keyName, + ValidationError::DUPLICATED_ATTRIBUTE_NAME_FOUND + ); + } + } + $attributeValues = array(); + foreach ($entry->childNodes as $childNode) { + $tagName = ($childNode->prefix ? $childNode->prefix.':' : '') . 'AttributeValue'; + if ($childNode->nodeType == XML_ELEMENT_NODE && $childNode->tagName === $tagName) { + $attributeValues[] = $childNode->nodeValue; + } + } + + if (in_array($attributeKeyName, array_keys($attributes))) { + $attributes[$attributeKeyName] = array_merge($attributes[$attributeKeyName], $attributeValues); + } else { + $attributes[$attributeKeyName] = $attributeValues; + } + } + return $attributes; + } + + /** + * Verifies that the document only contains a single Assertion (encrypted or not). + * + * @return bool TRUE if the document passes. + */ + public function validateNumAssertions() + { + $encryptedAssertionNodes = $this->document->getElementsByTagName('EncryptedAssertion'); + $assertionNodes = $this->document->getElementsByTagName('Assertion'); + + $valid = $assertionNodes->length + $encryptedAssertionNodes->length == 1; + + if ($this->encrypted) { + $assertionNodes = $this->decryptedDocument->getElementsByTagName('Assertion'); + $valid = $valid && $assertionNodes->length == 1; + } + + return $valid; + } + + /** + * Verifies the signature nodes: + * - Checks that are Response or Assertion + * - Check that IDs and reference URI are unique and consistent. + * + * @return array Signed element tags + * + * @throws ValidationError + */ + public function processSignedElements() + { + $signedElements = array(); + $verifiedSeis = array(); + $verifiedIds = array(); + + if ($this->encrypted) { + $signNodes = $this->decryptedDocument->getElementsByTagName('Signature'); + } else { + $signNodes = $this->document->getElementsByTagName('Signature'); + } + foreach ($signNodes as $signNode) { + $responseTag = '{'.Constants::NS_SAMLP.'}Response'; + $assertionTag = '{'.Constants::NS_SAML.'}Assertion'; + + $signedElement = '{'.$signNode->parentNode->namespaceURI.'}'.$signNode->parentNode->localName; + + if ($signedElement != $responseTag && $signedElement != $assertionTag) { + throw new ValidationError( + "Invalid Signature Element $signedElement SAML Response rejected", + ValidationError::WRONG_SIGNED_ELEMENT + ); + } + + // Check that reference URI matches the parent ID and no duplicate References or IDs + $idValue = $signNode->parentNode->getAttribute('ID'); + if (empty($idValue)) { + throw new ValidationError( + 'Signed Element must contain an ID. SAML Response rejected', + ValidationError::ID_NOT_FOUND_IN_SIGNED_ELEMENT + ); + } + + if (in_array($idValue, $verifiedIds)) { + throw new ValidationError( + 'Duplicated ID. SAML Response rejected', + ValidationError::DUPLICATED_ID_IN_SIGNED_ELEMENTS + ); + } + $verifiedIds[] = $idValue; + + $ref = $signNode->getElementsByTagName('Reference'); + if ($ref->length == 1) { + $ref = $ref->item(0); + $sei = $ref->getAttribute('URI'); + if (!empty($sei)) { + $sei = substr($sei, 1); + + if ($sei != $idValue) { + throw new ValidationError( + 'Found an invalid Signed Element. SAML Response rejected', + ValidationError::INVALID_SIGNED_ELEMENT + ); + } + + if (in_array($sei, $verifiedSeis)) { + throw new ValidationError( + 'Duplicated Reference URI. SAML Response rejected', + ValidationError::DUPLICATED_REFERENCE_IN_SIGNED_ELEMENTS + ); + } + $verifiedSeis[] = $sei; + } + } else { + throw new ValidationError( + 'Unexpected number of Reference nodes found for signature. SAML Response rejected.', + ValidationError::UNEXPECTED_REFERENCE + ); + } + $signedElements[] = $signedElement; + } + + // Check SignedElements + if (!empty($signedElements) && !$this->validateSignedElements($signedElements)) { + throw new ValidationError( + 'Found an unexpected Signature Element. SAML Response rejected', + ValidationError::UNEXPECTED_SIGNED_ELEMENTS + ); + } + return $signedElements; + } + + /** + * Verifies that the document is still valid according Conditions Element. + * + * @return bool + * + * @throws Exception + * @throws ValidationError + */ + public function validateTimestamps() + { + if ($this->encrypted) { + $document = $this->decryptedDocument; + } else { + $document = $this->document; + } + + $timestampNodes = $document->getElementsByTagName('Conditions'); + for ($i = 0; $i < $timestampNodes->length; $i++) { + $nbAttribute = $timestampNodes->item($i)->attributes->getNamedItem("NotBefore"); + $naAttribute = $timestampNodes->item($i)->attributes->getNamedItem("NotOnOrAfter"); + if ($nbAttribute && Utils::parseSAML2Time($nbAttribute->textContent) > time() + Constants::ALLOWED_CLOCK_DRIFT) { + throw new ValidationError( + 'Could not validate timestamp: not yet valid. Check system clock.', + ValidationError::ASSERTION_TOO_EARLY + ); + } + if ($naAttribute && Utils::parseSAML2Time($naAttribute->textContent) + Constants::ALLOWED_CLOCK_DRIFT <= time()) { + throw new ValidationError( + 'Could not validate timestamp: expired. Check system clock.', + ValidationError::ASSERTION_EXPIRED + ); + } + } + return true; + } + + /** + * Verifies that the document has the expected signed nodes. + * + * @param array $signedElements Signed elements + * + * @return bool + * + * @throws ValidationError + */ + public function validateSignedElements($signedElements) + { + if (count($signedElements) > 2) { + return false; + } + + $responseTag = '{'.Constants::NS_SAMLP.'}Response'; + $assertionTag = '{'.Constants::NS_SAML.'}Assertion'; + + $ocurrence = array_count_values($signedElements); + if ((in_array($responseTag, $signedElements) && $ocurrence[$responseTag] > 1) + || (in_array($assertionTag, $signedElements) && $ocurrence[$assertionTag] > 1) + || !in_array($responseTag, $signedElements) && !in_array($assertionTag, $signedElements) + ) { + return false; + } + + // Check that the signed elements found here, are the ones that will be verified + // by Utils->validateSign() + if (in_array($responseTag, $signedElements)) { + $expectedSignatureNodes = Utils::query($this->document, Utils::RESPONSE_SIGNATURE_XPATH); + if ($expectedSignatureNodes->length != 1) { + throw new ValidationError( + "Unexpected number of Response signatures found. SAML Response rejected.", + ValidationError::WRONG_NUMBER_OF_SIGNATURES_IN_RESPONSE + ); + } + } + + if (in_array($assertionTag, $signedElements)) { + $expectedSignatureNodes = $this->_query(Utils::ASSERTION_SIGNATURE_XPATH); + if ($expectedSignatureNodes->length != 1) { + throw new ValidationError( + "Unexpected number of Assertion signatures found. SAML Response rejected.", + ValidationError::WRONG_NUMBER_OF_SIGNATURES_IN_ASSERTION + ); + } + } + + return true; + } + + /** + * Extracts a node from the DOMDocument (Assertion). + * + * @param string $assertionXpath Xpath Expression + * + * @return DOMNodeList The queried node + */ + protected function _queryAssertion($assertionXpath) + { + if ($this->encrypted) { + $xpath = new DOMXPath($this->decryptedDocument); + } else { + $xpath = new DOMXPath($this->document); + } + + $xpath->registerNamespace('samlp', Constants::NS_SAMLP); + $xpath->registerNamespace('saml', Constants::NS_SAML); + $xpath->registerNamespace('ds', Constants::NS_DS); + $xpath->registerNamespace('xenc', Constants::NS_XENC); + + $assertionNode = '/samlp:Response/saml:Assertion'; + $signatureQuery = $assertionNode . '/ds:Signature/ds:SignedInfo/ds:Reference'; + $assertionReferenceNode = $xpath->query($signatureQuery)->item(0); + if (!$assertionReferenceNode) { + // is the response signed as a whole? + $signatureQuery = '/samlp:Response/ds:Signature/ds:SignedInfo/ds:Reference'; + $responseReferenceNode = $xpath->query($signatureQuery)->item(0); + if ($responseReferenceNode) { + $uri = $responseReferenceNode->attributes->getNamedItem('URI')->nodeValue; + if (empty($uri)) { + $id = $responseReferenceNode->parentNode->parentNode->parentNode->attributes->getNamedItem('ID')->nodeValue; + } else { + $id = substr($responseReferenceNode->attributes->getNamedItem('URI')->nodeValue, 1); + } + $nameQuery = "/samlp:Response[@ID='$id']/saml:Assertion" . $assertionXpath; + } else { + $nameQuery = "/samlp:Response/saml:Assertion" . $assertionXpath; + } + } else { + $uri = $assertionReferenceNode->attributes->getNamedItem('URI')->nodeValue; + if (empty($uri)) { + $id = $assertionReferenceNode->parentNode->parentNode->parentNode->attributes->getNamedItem('ID')->nodeValue; + } else { + $id = substr($assertionReferenceNode->attributes->getNamedItem('URI')->nodeValue, 1); + } + $nameQuery = $assertionNode."[@ID='$id']" . $assertionXpath; + } + + return $xpath->query($nameQuery); + } + + /** + * Extracts nodes that match the query from the DOMDocument (Response Menssage) + * + * @param string $query Xpath Expression + * + * @return DOMNodeList The queried nodes + */ + private function _query($query) + { + if ($this->encrypted) { + return Utils::query($this->decryptedDocument, $query); + } else { + return Utils::query($this->document, $query); + } + } + + /** + * Decrypts the Assertion (DOMDocument) + * + * @param \DomNode $dom DomDocument + * + * @return DOMDocument Decrypted Assertion + * + * @throws Exception + * @throws ValidationError + */ + protected function decryptAssertion(\DomNode $dom) + { + $pem = $this->_settings->getSPkey(); + + if (empty($pem)) { + throw new Error( + "No private key available, check settings", + Error::PRIVATE_KEY_NOT_FOUND + ); + } + + $objenc = new XMLSecEnc(); + $encData = $objenc->locateEncryptedData($dom); + if (!$encData) { + throw new ValidationError( + "Cannot locate encrypted assertion", + ValidationError::MISSING_ENCRYPTED_ELEMENT + ); + } + + $objenc->setNode($encData); + $objenc->type = $encData->getAttribute("Type"); + if (!$objKey = $objenc->locateKey()) { + throw new ValidationError( + "Unknown algorithm", + ValidationError::KEY_ALGORITHM_ERROR + ); + } + + $key = null; + if ($objKeyInfo = $objenc->locateKeyInfo($objKey)) { + if ($objKeyInfo->isEncrypted) { + $objencKey = $objKeyInfo->encryptedCtx; + $objKeyInfo->loadKey($pem, false, false); + $key = $objencKey->decryptKey($objKeyInfo); + } else { + // symmetric encryption key support + $objKeyInfo->loadKey($pem, false, false); + } + } + + if (empty($objKey->key)) { + $objKey->loadKey($key); + } + + $decryptedXML = $objenc->decryptNode($objKey, false); + $decrypted = new DOMDocument(); + $check = Utils::loadXML($decrypted, $decryptedXML); + if ($check === false) { + throw new Exception('Error: string from decrypted assertion could not be loaded into a XML document'); + } + if ($encData->parentNode instanceof DOMDocument) { + return $decrypted; + } else { + $decrypted = $decrypted->documentElement; + $encryptedAssertion = $encData->parentNode; + $container = $encryptedAssertion->parentNode; + + // Fix possible issue with saml namespace + if (!$decrypted->hasAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:saml') + && !$decrypted->hasAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:saml2') + && !$decrypted->hasAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns') + && !$container->hasAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:saml') + && !$container->hasAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:saml2') + ) { + if (strpos($encryptedAssertion->tagName, 'saml2:') !== false) { + $ns = 'xmlns:saml2'; + } else if (strpos($encryptedAssertion->tagName, 'saml:') !== false) { + $ns = 'xmlns:saml'; + } else { + $ns = 'xmlns'; + } + $decrypted->setAttributeNS('http://www.w3.org/2000/xmlns/', $ns, Constants::NS_SAML); + } + + Utils::treeCopyReplace($encryptedAssertion, $decrypted); + + // Rebuild the DOM will fix issues with namespaces as well + $dom = new DOMDocument(); + return Utils::loadXML($dom, $container->ownerDocument->saveXML()); + } + } + + /** + * After execute a validation process, if fails this method returns the cause + * + * @return Exception|null Cause + */ + public function getErrorException() + { + return $this->_error; + } + + /** + * After execute a validation process, if fails this method returns the cause + * + * @return null|string Error reason + */ + public function getError() + { + $errorMsg = null; + if (isset($this->_error)) { + $errorMsg = htmlentities($this->_error->getMessage()); + } + return $errorMsg; + } + + /** + * Returns the SAML Response document (If contains an encrypted assertion, decrypts it) + * + * @return DomDocument SAML Response + */ + public function getXMLDocument() + { + if ($this->encrypted) { + return $this->decryptedDocument; + } else { + return $this->document; + } + } +} diff --git a/vendor/onelogin/php-saml/src/Saml2/Settings.php b/vendor/onelogin/php-saml/src/Saml2/Settings.php new file mode 100644 index 00000000..43457bad --- /dev/null +++ b/vendor/onelogin/php-saml/src/Saml2/Settings.php @@ -0,0 +1,1166 @@ + + * @license MIT https://github.com/onelogin/php-saml/blob/master/LICENSE + * @link https://github.com/onelogin/php-saml + */ + +namespace OneLogin\Saml2; + +use RobRichards\XMLSecLibs\XMLSecurityKey; +use RobRichards\XMLSecLibs\XMLSecurityDSig; + +use DOMDocument; +use Exception; + +/** + * Configuration of the OneLogin PHP Toolkit + */ +class Settings +{ + /** + * List of paths. + * + * @var array + */ + private $_paths = array(); + + /** + * @var string + */ + private $_baseurl; + + /** + * Strict. If active, PHP Toolkit will reject unsigned or unencrypted messages + * if it expects them signed or encrypted. If not, the messages will be accepted + * and some security issues will be also relaxed. + * + * @var bool + */ + private $_strict = true; + + /** + * Activate debug mode + * + * @var bool + */ + private $_debug = false; + + /** + * SP data. + * + * @var array + */ + private $_sp = array(); + + /** + * IdP data. + * + * @var array + */ + private $_idp = array(); + + /** + * Compression settings that determine + * whether gzip compression should be used. + * + * @var array + */ + private $_compress = array(); + + /** + * Security Info related to the SP. + * + * @var array + */ + private $_security = array(); + + /** + * Setting contacts. + * + * @var array + */ + private $_contacts = array(); + + /** + * Setting organization. + * + * @var array + */ + private $_organization = array(); + + /** + * Setting errors. + * + * @var array + */ + private $_errors = array(); + + /** + * Valitate SP data only flag + * + * @var bool + */ + private $_spValidationOnly = false; + + /** + * Initializes the settings: + * - Sets the paths of the different folders + * - Loads settings info from settings file or array/object provided + * + * @param array|null $settings SAML Toolkit Settings + * @param bool $spValidationOnly Validate or not the IdP data + * + * @throws Error If any settings parameter is invalid + * @throws Exception If Settings is incorrectly supplied + */ + public function __construct(array $settings = null, $spValidationOnly = false) + { + $this->_spValidationOnly = $spValidationOnly; + $this->_loadPaths(); + + if (!isset($settings)) { + if (!$this->_loadSettingsFromFile()) { + throw new Error( + 'Invalid file settings: %s', + Error::SETTINGS_INVALID, + array(implode(', ', $this->_errors)) + ); + } + $this->_addDefaultValues(); + } else { + if (!$this->_loadSettingsFromArray($settings)) { + throw new Error( + 'Invalid array settings: %s', + Error::SETTINGS_INVALID, + array(implode(', ', $this->_errors)) + ); + } + } + + $this->formatIdPCert(); + $this->formatSPCert(); + $this->formatSPKey(); + $this->formatSPCertNew(); + $this->formatIdPCertMulti(); + } + + /** + * Sets the paths of the different folders + * @suppress PhanUndeclaredConstant + */ + private function _loadPaths() + { + $basePath = dirname(dirname(__DIR__)) . '/'; + $this->_paths = array( + 'base' => $basePath, + 'config' => $basePath, + 'cert' => $basePath.'certs/', + 'lib' => __DIR__ . '/', + ); + + if (defined('ONELOGIN_CUSTOMPATH')) { + $this->_paths['config'] = ONELOGIN_CUSTOMPATH; + $this->_paths['cert'] = ONELOGIN_CUSTOMPATH . 'certs/'; + } + } + + /** + * Returns base path. + * + * @return string The base toolkit folder path + */ + public function getBasePath() + { + return $this->_paths['base']; + } + + /** + * Returns cert path. + * + * @return string The cert folder path + */ + public function getCertPath() + { + return $this->_paths['cert']; + } + + /** + * Returns config path. + * + * @return string The config folder path + */ + public function getConfigPath() + { + return $this->_paths['config']; + } + + /** + * Returns lib path. + * + * @return string The library folder path + */ + public function getLibPath() + { + return $this->_paths['lib']; + } + + /** + * Returns schema path. + * + * @return string The external library folder path + */ + public function getSchemasPath() + { + if (isset($this->_paths['schemas'])) { + return $this->_paths['schemas']; + } + return __DIR__ . '/schemas/'; + } + + /** + * Set schemas path + * + * @param string $path + * @return $this + */ + public function setSchemasPath($path) + { + $this->_paths['schemas'] = $path; + } + + /** + * Loads settings info from a settings Array + * + * @param array $settings SAML Toolkit Settings + * + * @return bool True if the settings info is valid + */ + private function _loadSettingsFromArray(array $settings) + { + if (isset($settings['sp'])) { + $this->_sp = $settings['sp']; + } + if (isset($settings['idp'])) { + $this->_idp = $settings['idp']; + } + + $errors = $this->checkSettings($settings); + if (empty($errors)) { + $this->_errors = array(); + + if (isset($settings['strict'])) { + $this->_strict = $settings['strict']; + } + if (isset($settings['debug'])) { + $this->_debug = $settings['debug']; + } + + if (isset($settings['baseurl'])) { + $this->_baseurl = $settings['baseurl']; + } + + if (isset($settings['compress'])) { + $this->_compress = $settings['compress']; + } + + if (isset($settings['security'])) { + $this->_security = $settings['security']; + } + + if (isset($settings['contactPerson'])) { + $this->_contacts = $settings['contactPerson']; + } + + if (isset($settings['organization'])) { + $this->_organization = $settings['organization']; + } + + $this->_addDefaultValues(); + return true; + } else { + $this->_errors = $errors; + return false; + } + } + + /** + * Loads settings info from the settings file + * + * @return bool True if the settings info is valid + * + * @throws Error + * + * @suppress PhanUndeclaredVariable + */ + private function _loadSettingsFromFile() + { + $filename = $this->getConfigPath().'settings.php'; + + if (!file_exists($filename)) { + throw new Error( + 'Settings file not found: %s', + Error::SETTINGS_FILE_NOT_FOUND, + array($filename) + ); + } + + /** @var array $settings */ + include $filename; + + // Add advance_settings if exists + $advancedFilename = $this->getConfigPath().'advanced_settings.php'; + + if (file_exists($advancedFilename)) { + /** @var array $advancedSettings */ + include $advancedFilename; + $settings = array_merge($settings, $advancedSettings); + } + + + return $this->_loadSettingsFromArray($settings); + } + + /** + * Add default values if the settings info is not complete + */ + private function _addDefaultValues() + { + if (!isset($this->_sp['assertionConsumerService']['binding'])) { + $this->_sp['assertionConsumerService']['binding'] = Constants::BINDING_HTTP_POST; + } + if (isset($this->_sp['singleLogoutService']) && !isset($this->_sp['singleLogoutService']['binding'])) { + $this->_sp['singleLogoutService']['binding'] = Constants::BINDING_HTTP_REDIRECT; + } + + if (!isset($this->_compress['requests'])) { + $this->_compress['requests'] = true; + } + + if (!isset($this->_compress['responses'])) { + $this->_compress['responses'] = true; + } + + // Related to nameID + if (!isset($this->_sp['NameIDFormat'])) { + $this->_sp['NameIDFormat'] = Constants::NAMEID_UNSPECIFIED; + } + if (!isset($this->_security['nameIdEncrypted'])) { + $this->_security['nameIdEncrypted'] = false; + } + if (!isset($this->_security['requestedAuthnContext'])) { + $this->_security['requestedAuthnContext'] = true; + } + + // sign provided + if (!isset($this->_security['authnRequestsSigned'])) { + $this->_security['authnRequestsSigned'] = false; + } + if (!isset($this->_security['logoutRequestSigned'])) { + $this->_security['logoutRequestSigned'] = false; + } + if (!isset($this->_security['logoutResponseSigned'])) { + $this->_security['logoutResponseSigned'] = false; + } + if (!isset($this->_security['signMetadata'])) { + $this->_security['signMetadata'] = false; + } + + // sign expected + if (!isset($this->_security['wantMessagesSigned'])) { + $this->_security['wantMessagesSigned'] = false; + } + if (!isset($this->_security['wantAssertionsSigned'])) { + $this->_security['wantAssertionsSigned'] = false; + } + + // NameID element expected + if (!isset($this->_security['wantNameId'])) { + $this->_security['wantNameId'] = true; + } + + // Relax Destination validation + if (!isset($this->_security['relaxDestinationValidation'])) { + $this->_security['relaxDestinationValidation'] = false; + } + + // Strict Destination match validation + if (!isset($this->_security['destinationStrictlyMatches'])) { + $this->_security['destinationStrictlyMatches'] = false; + } + + // Allow duplicated Attribute Names + if (!isset($this->_security['allowRepeatAttributeName'])) { + $this->_security['allowRepeatAttributeName'] = false; + } + + // InResponseTo + if (!isset($this->_security['rejectUnsolicitedResponsesWithInResponseTo'])) { + $this->_security['rejectUnsolicitedResponsesWithInResponseTo'] = false; + } + + // encrypt expected + if (!isset($this->_security['wantAssertionsEncrypted'])) { + $this->_security['wantAssertionsEncrypted'] = false; + } + if (!isset($this->_security['wantNameIdEncrypted'])) { + $this->_security['wantNameIdEncrypted'] = false; + } + + // XML validation + if (!isset($this->_security['wantXMLValidation'])) { + $this->_security['wantXMLValidation'] = true; + } + + // SignatureAlgorithm + if (!isset($this->_security['signatureAlgorithm'])) { + $this->_security['signatureAlgorithm'] = XMLSecurityKey::RSA_SHA256; + } + + // DigestAlgorithm + if (!isset($this->_security['digestAlgorithm'])) { + $this->_security['digestAlgorithm'] = XMLSecurityDSig::SHA256; + } + + // EncryptionAlgorithm + if (!isset($this->_security['encryption_algorithm'])) { + $this->_security['encryption_algorithm'] = XMLSecurityKey::AES128_CBC; + } + + if (!isset($this->_security['lowercaseUrlencoding'])) { + $this->_security['lowercaseUrlencoding'] = false; + } + + // Certificates / Private key /Fingerprint + if (!isset($this->_idp['x509cert'])) { + $this->_idp['x509cert'] = ''; + } + if (!isset($this->_idp['certFingerprint'])) { + $this->_idp['certFingerprint'] = ''; + } + if (!isset($this->_idp['certFingerprintAlgorithm'])) { + $this->_idp['certFingerprintAlgorithm'] = 'sha1'; + } + + if (!isset($this->_sp['x509cert'])) { + $this->_sp['x509cert'] = ''; + } + if (!isset($this->_sp['privateKey'])) { + $this->_sp['privateKey'] = ''; + } + } + + /** + * Checks the settings info. + * + * @param array $settings Array with settings data + * + * @return array $errors Errors found on the settings data + */ + public function checkSettings(array $settings) + { + if (empty($settings)) { + $errors = array('invalid_syntax'); + } else { + $errors = array(); + if (!$this->_spValidationOnly) { + $idpErrors = $this->checkIdPSettings($settings); + $errors = array_merge($idpErrors, $errors); + } + $spErrors = $this->checkSPSettings($settings); + $errors = array_merge($spErrors, $errors); + + $compressErrors = $this->checkCompressionSettings($settings); + $errors = array_merge($compressErrors, $errors); + } + + return $errors; + } + + /** + * Checks the compression settings info. + * + * @param array $settings Array with settings data + * + * @return array $errors Errors found on the settings data + */ + public function checkCompressionSettings($settings) + { + $errors = array(); + + if (isset($settings['compress'])) { + if (!is_array($settings['compress'])) { + $errors[] = "invalid_syntax"; + } else if (isset($settings['compress']['requests']) + && $settings['compress']['requests'] !== true + && $settings['compress']['requests'] !== false + ) { + $errors[] = "'compress'=>'requests' values must be true or false."; + } else if (isset($settings['compress']['responses']) + && $settings['compress']['responses'] !== true + && $settings['compress']['responses'] !== false + ) { + $errors[] = "'compress'=>'responses' values must be true or false."; + } + } + return $errors; + } + + /** + * Checks the IdP settings info. + * + * @param array $settings Array with settings data + * + * @return array $errors Errors found on the IdP settings data + */ + public function checkIdPSettings(array $settings) + { + if (empty($settings)) { + return array('invalid_syntax'); + } + + $errors = array(); + + if (!isset($settings['idp']) || empty($settings['idp'])) { + $errors[] = 'idp_not_found'; + } else { + $idp = $settings['idp']; + if (!isset($idp['entityId']) || empty($idp['entityId'])) { + $errors[] = 'idp_entityId_not_found'; + } + + if (!isset($idp['singleSignOnService']) + || !isset($idp['singleSignOnService']['url']) + || empty($idp['singleSignOnService']['url']) + ) { + $errors[] = 'idp_sso_not_found'; + } else if (!filter_var($idp['singleSignOnService']['url'], FILTER_VALIDATE_URL)) { + $errors[] = 'idp_sso_url_invalid'; + } + + if (isset($idp['singleLogoutService']) + && isset($idp['singleLogoutService']['url']) + && !empty($idp['singleLogoutService']['url']) + && !filter_var($idp['singleLogoutService']['url'], FILTER_VALIDATE_URL) + ) { + $errors[] = 'idp_slo_url_invalid'; + } + + if (isset($idp['singleLogoutService']) + && isset($idp['singleLogoutService']['responseUrl']) + && !empty($idp['singleLogoutService']['responseUrl']) + && !filter_var($idp['singleLogoutService']['responseUrl'], FILTER_VALIDATE_URL) + ) { + $errors[] = 'idp_slo_response_url_invalid'; + } + + $existsX509 = isset($idp['x509cert']) && !empty($idp['x509cert']); + $existsMultiX509Sign = isset($idp['x509certMulti']) && isset($idp['x509certMulti']['signing']) && !empty($idp['x509certMulti']['signing']); + $existsFingerprint = isset($idp['certFingerprint']) && !empty($idp['certFingerprint']); + if (!($existsX509 || $existsFingerprint || $existsMultiX509Sign) + ) { + $errors[] = 'idp_cert_or_fingerprint_not_found_and_required'; + } + + if (isset($settings['security'])) { + $existsMultiX509Enc = isset($idp['x509certMulti']) && isset($idp['x509certMulti']['encryption']) && !empty($idp['x509certMulti']['encryption']); + + if ((isset($settings['security']['nameIdEncrypted']) && $settings['security']['nameIdEncrypted'] == true) + && !($existsX509 || $existsMultiX509Enc) + ) { + $errors[] = 'idp_cert_not_found_and_required'; + } + } + } + + return $errors; + } + + /** + * Checks the SP settings info. + * + * @param array $settings Array with settings data + * + * @return array $errors Errors found on the SP settings data + */ + public function checkSPSettings(array $settings) + { + if (empty($settings)) { + return array('invalid_syntax'); + } + + $errors = array(); + + if (!isset($settings['sp']) || empty($settings['sp'])) { + $errors[] = 'sp_not_found'; + } else { + $sp = $settings['sp']; + $security = array(); + if (isset($settings['security'])) { + $security = $settings['security']; + } + + if (!isset($sp['entityId']) || empty($sp['entityId'])) { + $errors[] = 'sp_entityId_not_found'; + } + + if (!isset($sp['assertionConsumerService']) + || !isset($sp['assertionConsumerService']['url']) + || empty($sp['assertionConsumerService']['url']) + ) { + $errors[] = 'sp_acs_not_found'; + } else if (!filter_var($sp['assertionConsumerService']['url'], FILTER_VALIDATE_URL)) { + $errors[] = 'sp_acs_url_invalid'; + } + + if (isset($sp['singleLogoutService']) + && isset($sp['singleLogoutService']['url']) + && !filter_var($sp['singleLogoutService']['url'], FILTER_VALIDATE_URL) + ) { + $errors[] = 'sp_sls_url_invalid'; + } + + if (isset($security['signMetadata']) && is_array($security['signMetadata'])) { + if ((!isset($security['signMetadata']['keyFileName']) + || !isset($security['signMetadata']['certFileName'])) && + (!isset($security['signMetadata']['privateKey']) + || !isset($security['signMetadata']['x509cert'])) + ) { + $errors[] = 'sp_signMetadata_invalid'; + } + } + + if (((isset($security['authnRequestsSigned']) && $security['authnRequestsSigned'] == true) + || (isset($security['logoutRequestSigned']) && $security['logoutRequestSigned'] == true) + || (isset($security['logoutResponseSigned']) && $security['logoutResponseSigned'] == true) + || (isset($security['wantAssertionsEncrypted']) && $security['wantAssertionsEncrypted'] == true) + || (isset($security['wantNameIdEncrypted']) && $security['wantNameIdEncrypted'] == true)) + && !$this->checkSPCerts() + ) { + $errors[] = 'sp_certs_not_found_and_required'; + } + } + + if (isset($settings['contactPerson'])) { + $types = array_keys($settings['contactPerson']); + $validTypes = array('technical', 'support', 'administrative', 'billing', 'other'); + foreach ($types as $type) { + if (!in_array($type, $validTypes)) { + $errors[] = 'contact_type_invalid'; + break; + } + } + + foreach ($settings['contactPerson'] as $type => $contact) { + if (!isset($contact['givenName']) || empty($contact['givenName']) + || !isset($contact['emailAddress']) || empty($contact['emailAddress']) + ) { + $errors[] = 'contact_not_enought_data'; + break; + } + } + } + + if (isset($settings['organization'])) { + foreach ($settings['organization'] as $organization) { + if (!isset($organization['name']) || empty($organization['name']) + || !isset($organization['displayname']) || empty($organization['displayname']) + || !isset($organization['url']) || empty($organization['url']) + ) { + $errors[] = 'organization_not_enought_data'; + break; + } + } + } + + return $errors; + } + + /** + * Checks if the x509 certs of the SP exists and are valid. + * + * @return bool + */ + public function checkSPCerts() + { + $key = $this->getSPkey(); + $cert = $this->getSPcert(); + return (!empty($key) && !empty($cert)); + } + + /** + * Returns the x509 private key of the SP. + * + * @return string SP private key + */ + public function getSPkey() + { + $key = null; + if (isset($this->_sp['privateKey']) && !empty($this->_sp['privateKey'])) { + $key = $this->_sp['privateKey']; + } else { + $keyFile = $this->_paths['cert'].'sp.key'; + + if (file_exists($keyFile)) { + $key = file_get_contents($keyFile); + } + } + return $key; + } + + /** + * Returns the x509 public cert of the SP. + * + * @return string SP public cert + */ + public function getSPcert() + { + $cert = null; + + if (isset($this->_sp['x509cert']) && !empty($this->_sp['x509cert'])) { + $cert = $this->_sp['x509cert']; + } else { + $certFile = $this->_paths['cert'].'sp.crt'; + + if (file_exists($certFile)) { + $cert = file_get_contents($certFile); + } + } + return $cert; + } + + /** + * Returns the x509 public of the SP that is + * planed to be used soon instead the other + * public cert + * + * @return string SP public cert New + */ + public function getSPcertNew() + { + $cert = null; + + if (isset($this->_sp['x509certNew']) && !empty($this->_sp['x509certNew'])) { + $cert = $this->_sp['x509certNew']; + } else { + $certFile = $this->_paths['cert'].'sp_new.crt'; + + if (file_exists($certFile)) { + $cert = file_get_contents($certFile); + } + } + return $cert; + } + + /** + * Gets the IdP data. + * + * @return array IdP info + */ + public function getIdPData() + { + return $this->_idp; + } + + /** + * Gets the SP data. + * + * @return array SP info + */ + public function getSPData() + { + return $this->_sp; + } + + /** + * Gets security data. + * + * @return array SP info + */ + public function getSecurityData() + { + return $this->_security; + } + + /** + * Gets contact data. + * + * @return array SP info + */ + public function getContacts() + { + return $this->_contacts; + } + + /** + * Gets organization data. + * + * @return array SP info + */ + public function getOrganization() + { + return $this->_organization; + } + + /** + * Should SAML requests be compressed? + * + * @return bool Yes/No as True/False + */ + public function shouldCompressRequests() + { + return $this->_compress['requests']; + } + + /** + * Should SAML responses be compressed? + * + * @return bool Yes/No as True/False + */ + public function shouldCompressResponses() + { + return $this->_compress['responses']; + } + + /** + * Gets the IdP SSO url. + * + * @return string|null The url of the IdP Single Sign On Service + */ + public function getIdPSSOUrl() + { + $ssoUrl = null; + if (isset($this->_idp['singleSignOnService']) && isset($this->_idp['singleSignOnService']['url'])) { + $ssoUrl = $this->_idp['singleSignOnService']['url']; + } + return $ssoUrl; + } + + /** + * Gets the IdP SLO url. + * + * @return string|null The request url of the IdP Single Logout Service + */ + public function getIdPSLOUrl() + { + $sloUrl = null; + if (isset($this->_idp['singleLogoutService']) && isset($this->_idp['singleLogoutService']['url'])) { + $sloUrl = $this->_idp['singleLogoutService']['url']; + } + return $sloUrl; + } + + /** + * Gets the IdP SLO response url. + * + * @return string|null The response url of the IdP Single Logout Service + */ + public function getIdPSLOResponseUrl() + { + if (isset($this->_idp['singleLogoutService']) && isset($this->_idp['singleLogoutService']['responseUrl'])) { + return $this->_idp['singleLogoutService']['responseUrl']; + } + return $this->getIdPSLOUrl(); + } + + /** + * Gets the SP metadata. The XML representation. + * + * @param bool $alwaysPublishEncryptionCert When 'true', the returned + * metadata will always include an 'encryption' KeyDescriptor. Otherwise, + * the 'encryption' KeyDescriptor will only be included if + * $advancedSettings['security']['wantNameIdEncrypted'] or + * $advancedSettings['security']['wantAssertionsEncrypted'] are enabled. + * @param int|null $validUntil Metadata's valid time + * @param int|null $cacheDuration Duration of the cache in seconds + * + * @return string SP metadata (xml) + * @throws Exception + * @throws Error + */ + public function getSPMetadata($alwaysPublishEncryptionCert = false, $validUntil = null, $cacheDuration = null) + { + $metadata = Metadata::builder($this->_sp, $this->_security['authnRequestsSigned'], $this->_security['wantAssertionsSigned'], $validUntil, $cacheDuration, $this->getContacts(), $this->getOrganization()); + + $certNew = $this->getSPcertNew(); + if (!empty($certNew)) { + $metadata = Metadata::addX509KeyDescriptors( + $metadata, + $certNew, + $alwaysPublishEncryptionCert || $this->_security['wantNameIdEncrypted'] || $this->_security['wantAssertionsEncrypted'] + ); + } + + $cert = $this->getSPcert(); + if (!empty($cert)) { + $metadata = Metadata::addX509KeyDescriptors( + $metadata, + $cert, + $alwaysPublishEncryptionCert || $this->_security['wantNameIdEncrypted'] || $this->_security['wantAssertionsEncrypted'] + ); + } + + //Sign Metadata + if (isset($this->_security['signMetadata']) && $this->_security['signMetadata'] != false) { + if ($this->_security['signMetadata'] === true) { + $keyMetadata = $this->getSPkey(); + $certMetadata = $cert; + + if (!$keyMetadata) { + throw new Error( + 'SP Private key not found.', + Error::PRIVATE_KEY_FILE_NOT_FOUND + ); + } + + if (!$certMetadata) { + throw new Error( + 'SP Public cert not found.', + Error::PUBLIC_CERT_FILE_NOT_FOUND + ); + } + } else if (isset($this->_security['signMetadata']['keyFileName']) && + isset($this->_security['signMetadata']['certFileName'])) { + $keyFileName = $this->_security['signMetadata']['keyFileName']; + $certFileName = $this->_security['signMetadata']['certFileName']; + + $keyMetadataFile = $this->_paths['cert'].$keyFileName; + $certMetadataFile = $this->_paths['cert'].$certFileName; + + if (!file_exists($keyMetadataFile)) { + throw new Error( + 'SP Private key file not found: %s', + Error::PRIVATE_KEY_FILE_NOT_FOUND, + array($keyMetadataFile) + ); + } + + if (!file_exists($certMetadataFile)) { + throw new Error( + 'SP Public cert file not found: %s', + Error::PUBLIC_CERT_FILE_NOT_FOUND, + array($certMetadataFile) + ); + } + $keyMetadata = file_get_contents($keyMetadataFile); + $certMetadata = file_get_contents($certMetadataFile); + } else if (isset($this->_security['signMetadata']['privateKey']) && + isset($this->_security['signMetadata']['x509cert'])) { + $keyMetadata = Utils::formatPrivateKey($this->_security['signMetadata']['privateKey']); + $certMetadata = Utils::formatCert($this->_security['signMetadata']['x509cert']); + if (!$keyMetadata) { + throw new Error( + 'Private key not found.', + Error::PRIVATE_KEY_FILE_NOT_FOUND + ); + } + + if (!$certMetadata) { + throw new Error( + 'Public cert not found.', + Error::PUBLIC_CERT_FILE_NOT_FOUND + ); + } + } else { + throw new Error( + 'Invalid Setting: signMetadata value of the sp is not valid', + Error::SETTINGS_INVALID_SYNTAX + ); + + } + + $signatureAlgorithm = $this->_security['signatureAlgorithm']; + $digestAlgorithm = $this->_security['digestAlgorithm']; + $metadata = Metadata::signMetadata($metadata, $keyMetadata, $certMetadata, $signatureAlgorithm, $digestAlgorithm); + } + return $metadata; + } + + /** + * Validates an XML SP Metadata. + * + * @param string $xml Metadata's XML that will be validate + * + * @return array The list of found errors + * + * @throws Exception + */ + public function validateMetadata($xml) + { + assert(is_string($xml)); + + $errors = array(); + $res = Utils::validateXML($xml, 'saml-schema-metadata-2.0.xsd', $this->_debug, $this->getSchemasPath()); + if (!$res instanceof DOMDocument) { + $errors[] = $res; + } else { + $dom = $res; + $element = $dom->documentElement; + if ($element->tagName !== 'md:EntityDescriptor') { + $errors[] = 'noEntityDescriptor_xml'; + } else { + $validUntil = $cacheDuration = $expireTime = null; + + if ($element->hasAttribute('validUntil')) { + $validUntil = Utils::parseSAML2Time($element->getAttribute('validUntil')); + } + if ($element->hasAttribute('cacheDuration')) { + $cacheDuration = $element->getAttribute('cacheDuration'); + } + + $expireTime = Utils::getExpireTime($cacheDuration, $validUntil); + if (isset($expireTime) && time() > $expireTime) { + $errors[] = 'expired_xml'; + } + } + } + + // TODO: Support Metadata Sign Validation + + return $errors; + } + + /** + * Formats the IdP cert. + */ + public function formatIdPCert() + { + if (isset($this->_idp['x509cert'])) { + $this->_idp['x509cert'] = Utils::formatCert($this->_idp['x509cert']); + } + } + + /** + * Formats the Multple IdP certs. + */ + public function formatIdPCertMulti() + { + if (isset($this->_idp['x509certMulti'])) { + if (isset($this->_idp['x509certMulti']['signing'])) { + foreach ($this->_idp['x509certMulti']['signing'] as $i => $cert) { + $this->_idp['x509certMulti']['signing'][$i] = Utils::formatCert($cert); + } + } + if (isset($this->_idp['x509certMulti']['encryption'])) { + foreach ($this->_idp['x509certMulti']['encryption'] as $i => $cert) { + $this->_idp['x509certMulti']['encryption'][$i] = Utils::formatCert($cert); + } + } + } + } + + /** + * Formats the SP cert. + */ + public function formatSPCert() + { + if (isset($this->_sp['x509cert'])) { + $this->_sp['x509cert'] = Utils::formatCert($this->_sp['x509cert']); + } + } + + /** + * Formats the SP cert. + */ + public function formatSPCertNew() + { + if (isset($this->_sp['x509certNew'])) { + $this->_sp['x509certNew'] = Utils::formatCert($this->_sp['x509certNew']); + } + } + + /** + * Formats the SP private key. + */ + public function formatSPKey() + { + if (isset($this->_sp['privateKey'])) { + $this->_sp['privateKey'] = Utils::formatPrivateKey($this->_sp['privateKey']); + } + } + + /** + * Returns an array with the errors, the array is empty when the settings is ok. + * + * @return array Errors + */ + public function getErrors() + { + return $this->_errors; + } + + /** + * Activates or deactivates the strict mode. + * + * @param bool $value Strict parameter + * + * @throws Exception + */ + public function setStrict($value) + { + if (!is_bool($value)) { + throw new Exception('Invalid value passed to setStrict()'); + } + + $this->_strict = $value; + } + + /** + * Returns if the 'strict' mode is active. + * + * @return bool Strict parameter + */ + public function isStrict() + { + return $this->_strict; + } + + /** + * Returns if the debug is active. + * + * @return bool Debug parameter + */ + public function isDebugActive() + { + return $this->_debug; + } + + /** + * Set a baseurl value. + * + * @param string $baseurl Base URL. + */ + public function setBaseURL($baseurl) + { + $this->_baseurl = $baseurl; + } + + /** + * Returns the baseurl set on the settings if any. + * + * @return null|string The baseurl + */ + public function getBaseURL() + { + return $this->_baseurl; + } + + /** + * Sets the IdP certificate. + * + * @param string $cert IdP certificate + */ + public function setIdPCert($cert) + { + $this->_idp['x509cert'] = $cert; + $this->formatIdPCert(); + } +} diff --git a/vendor/onelogin/php-saml/src/Saml2/Utils.php b/vendor/onelogin/php-saml/src/Saml2/Utils.php new file mode 100644 index 00000000..582c117b --- /dev/null +++ b/vendor/onelogin/php-saml/src/Saml2/Utils.php @@ -0,0 +1,1579 @@ + + * @license MIT https://github.com/onelogin/php-saml/blob/master/LICENSE + * @link https://github.com/onelogin/php-saml + */ + +namespace OneLogin\Saml2; + +use RobRichards\XMLSecLibs\XMLSecurityKey; +use RobRichards\XMLSecLibs\XMLSecurityDSig; +use RobRichards\XMLSecLibs\XMLSecEnc; + +use DOMDocument; +use DOMElement; +use DOMNodeList; +use DomNode; +use DOMXPath; +use Exception; + +/** + * Utils of OneLogin PHP Toolkit + * + * Defines several often used methods + */ +class Utils +{ + const RESPONSE_SIGNATURE_XPATH = "/samlp:Response/ds:Signature"; + const ASSERTION_SIGNATURE_XPATH = "/samlp:Response/saml:Assertion/ds:Signature"; + + /** + * @var bool Control if the `Forwarded-For-*` headers are used + */ + private static $_proxyVars = false; + + /** + * @var string|null + */ + private static $_host; + + /** + * @var string|null + */ + private static $_protocol; + + /** + * @var string + */ + private static $_protocolRegex = '@^https?://@i'; + + /** + * @var int|null + */ + private static $_port; + + /** + * @var string|null + */ + private static $_baseurlpath; + + /** + * This function load an XML string in a save way. + * Prevent XEE/XXE Attacks + * + * @param DOMDocument $dom The document where load the xml. + * @param string $xml The XML string to be loaded. + * + * @return DOMDocument|false $dom The result of load the XML at the DOMDocument + * + * @throws Exception + */ + public static function loadXML(DOMDocument $dom, $xml) + { + assert($dom instanceof DOMDocument); + assert(is_string($xml)); + + $oldEntityLoader = null; + if (PHP_VERSION_ID < 80000) { + $oldEntityLoader = libxml_disable_entity_loader(true); + } + + $res = $dom->loadXML($xml); + + if (PHP_VERSION_ID < 80000) { + libxml_disable_entity_loader($oldEntityLoader); + } + + foreach ($dom->childNodes as $child) { + if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) { + throw new Exception( + 'Detected use of DOCTYPE/ENTITY in XML, disabled to prevent XXE/XEE attacks' + ); + } + } + + if (!$res) { + return false; + } else { + return $dom; + } + } + + /** + * This function attempts to validate an XML string against the specified schema. + * + * It will parse the string into a DOMDocument and validate this document against the schema. + * + * @param string|DOMDocument $xml The XML string or document which should be validated. + * @param string $schema The schema filename which should be used. + * @param bool $debug To disable/enable the debug mode + * @param string $schemaPath Change schema path + * + * @return string|DOMDocument $dom string that explains the problem or the DOMDocument + * + * @throws Exception + */ + public static function validateXML($xml, $schema, $debug = false, $schemaPath = null) + { + assert(is_string($xml) || $xml instanceof DOMDocument); + assert(is_string($schema)); + + libxml_clear_errors(); + libxml_use_internal_errors(true); + + if ($xml instanceof DOMDocument) { + $dom = $xml; + } else { + $dom = new DOMDocument; + $dom = self::loadXML($dom, $xml); + if (!$dom) { + return 'unloaded_xml'; + } + } + + if (isset($schemaPath)) { + $schemaFile = $schemaPath . $schema; + } else { + $schemaFile = __DIR__ . '/schemas/' . $schema; + } + + $oldEntityLoader = null; + if (PHP_VERSION_ID < 80000) { + $oldEntityLoader = libxml_disable_entity_loader(false); + } + $res = $dom->schemaValidate($schemaFile); + if (PHP_VERSION_ID < 80000) { + libxml_disable_entity_loader($oldEntityLoader); + } + if (!$res) { + $xmlErrors = libxml_get_errors(); + syslog(LOG_INFO, 'Error validating the metadata: '.var_export($xmlErrors, true)); + + if ($debug) { + foreach ($xmlErrors as $error) { + echo htmlentities($error->message)."\n"; + } + } + return 'invalid_xml'; + } + + return $dom; + } + + /** + * Import a node tree into a target document + * Copy it before a reference node as a sibling + * and at the end of the copy remove + * the reference node in the target document + * As it were 'replacing' it + * Leaving nested default namespaces alone + * (Standard importNode with deep copy + * mangles nested default namespaces) + * + * The reference node must not be a DomDocument + * It CAN be the top element of a document + * Returns the copied node in the target document + * + * @param DomNode $targetNode + * @param DomNode $sourceNode + * @param bool $recurse + * @return DOMNode + * @throws Exception + */ + public static function treeCopyReplace(DomNode $targetNode, DomNode $sourceNode, $recurse = false) + { + if ($targetNode->parentNode === null) { + throw new Exception('Illegal argument targetNode. It has no parentNode.'); + } + $clonedNode = $targetNode->ownerDocument->importNode($sourceNode, false); + if ($recurse) { + $resultNode = $targetNode->appendChild($clonedNode); + } else { + $resultNode = $targetNode->parentNode->insertBefore($clonedNode, $targetNode); + } + if ($sourceNode->childNodes !== null) { + foreach ($sourceNode->childNodes as $child) { + self::treeCopyReplace($resultNode, $child, true); + } + } + if (!$recurse) { + $targetNode->parentNode->removeChild($targetNode); + } + return $resultNode; + } + + /** + * Returns a x509 cert (adding header & footer if required). + * + * @param string $cert A x509 unformated cert + * @param bool $heads True if we want to include head and footer + * + * @return string $x509 Formatted cert + */ + public static function formatCert($cert, $heads = true) + { + $x509cert = str_replace(array("\x0D", "\r", "\n"), "", $cert); + if (!empty($x509cert)) { + $x509cert = str_replace('-----BEGIN CERTIFICATE-----', "", $x509cert); + $x509cert = str_replace('-----END CERTIFICATE-----', "", $x509cert); + $x509cert = str_replace(' ', '', $x509cert); + + if ($heads) { + $x509cert = "-----BEGIN CERTIFICATE-----\n".chunk_split($x509cert, 64, "\n")."-----END CERTIFICATE-----\n"; + } + + } + return $x509cert; + } + + /** + * Returns a private key (adding header & footer if required). + * + * @param string $key A private key + * @param bool $heads True if we want to include head and footer + * + * @return string $rsaKey Formatted private key + */ + public static function formatPrivateKey($key, $heads = true) + { + $key = str_replace(array("\x0D", "\r", "\n"), "", $key); + if (!empty($key)) { + if (strpos($key, '-----BEGIN PRIVATE KEY-----') !== false) { + $key = Utils::getStringBetween($key, '-----BEGIN PRIVATE KEY-----', '-----END PRIVATE KEY-----'); + $key = str_replace(' ', '', $key); + + if ($heads) { + $key = "-----BEGIN PRIVATE KEY-----\n".chunk_split($key, 64, "\n")."-----END PRIVATE KEY-----\n"; + } + } else if (strpos($key, '-----BEGIN RSA PRIVATE KEY-----') !== false) { + $key = Utils::getStringBetween($key, '-----BEGIN RSA PRIVATE KEY-----', '-----END RSA PRIVATE KEY-----'); + $key = str_replace(' ', '', $key); + + if ($heads) { + $key = "-----BEGIN RSA PRIVATE KEY-----\n".chunk_split($key, 64, "\n")."-----END RSA PRIVATE KEY-----\n"; + } + } else { + $key = str_replace(' ', '', $key); + + if ($heads) { + $key = "-----BEGIN RSA PRIVATE KEY-----\n".chunk_split($key, 64, "\n")."-----END RSA PRIVATE KEY-----\n"; + } + } + } + return $key; + } + + /** + * Extracts a substring between 2 marks + * + * @param string $str The target string + * @param string $start The initial mark + * @param string $end The end mark + * + * @return string A substring or an empty string if is not able to find the marks + * or if there is no string between the marks + */ + public static function getStringBetween($str, $start, $end) + { + $str = ' ' . $str; + $ini = strpos($str, $start); + + if ($ini == 0) { + return ''; + } + + $ini += strlen($start); + $len = strpos($str, $end, $ini) - $ini; + return substr($str, $ini, $len); + } + + /** + * Executes a redirection to the provided url (or return the target url). + * + * @param string $url The target url + * @param array $parameters Extra parameters to be passed as part of the url + * @param bool $stay True if we want to stay (returns the url string) False to redirect + * + * @return string|null $url + * + * @throws Error + */ + public static function redirect($url, array $parameters = array(), $stay = false) + { + assert(is_string($url)); + + if (substr($url, 0, 1) === '/') { + $url = self::getSelfURLhost() . $url; + } + + /** + * Verify that the URL matches the regex for the protocol. + * By default this will check for http and https + */ + $wrongProtocol = !preg_match(self::$_protocolRegex, $url); + $url = filter_var($url, FILTER_VALIDATE_URL); + if ($wrongProtocol || empty($url)) { + throw new Error( + 'Redirect to invalid URL: ' . $url, + Error::REDIRECT_INVALID_URL + ); + } + + /* Add encoded parameters */ + if (strpos($url, '?') === false) { + $paramPrefix = '?'; + } else { + $paramPrefix = '&'; + } + + foreach ($parameters as $name => $value) { + if ($value === null) { + $param = urlencode($name); + } else if (is_array($value)) { + $param = ""; + foreach ($value as $val) { + $param .= urlencode($name) . "[]=" . urlencode($val). '&'; + } + if (!empty($param)) { + $param = substr($param, 0, -1); + } + } else { + $param = urlencode($name) . '=' . urlencode($value); + } + + if (!empty($param)) { + $url .= $paramPrefix . $param; + $paramPrefix = '&'; + } + } + + if ($stay) { + return $url; + } + + header('Pragma: no-cache'); + header('Cache-Control: no-cache, must-revalidate'); + header('Location: ' . $url); + exit(); + } + + /** + * @param $protocolRegex string + */ + public static function setProtocolRegex($protocolRegex) + { + if (!empty($protocolRegex)) { + self::$_protocolRegex = $protocolRegex; + } + } + + /** + * Set the Base URL value. + * + * @param string $baseurl The base url to be used when constructing URLs + */ + public static function setBaseURL($baseurl) + { + if (!empty($baseurl)) { + $baseurlpath = '/'; + $matches = array(); + if (preg_match('#^https?://([^/]*)/?(.*)#i', $baseurl, $matches)) { + if (strpos($baseurl, 'https://') === false) { + self::setSelfProtocol('http'); + $port = '80'; + } else { + self::setSelfProtocol('https'); + $port = '443'; + } + + $currentHost = $matches[1]; + if (false !== strpos($currentHost, ':')) { + list($currentHost, $possiblePort) = explode(':', $matches[1], 2); + if (is_numeric($possiblePort)) { + $port = $possiblePort; + } + } + + if (isset($matches[2]) && !empty($matches[2])) { + $baseurlpath = $matches[2]; + } + + self::setSelfHost($currentHost); + self::setSelfPort($port); + self::setBaseURLPath($baseurlpath); + } + } else { + self::$_host = null; + self::$_protocol = null; + self::$_port = null; + self::$_baseurlpath = null; + } + } + + /** + * @param bool $proxyVars Whether to use `X-Forwarded-*` headers to determine port/domain/protocol + */ + public static function setProxyVars($proxyVars) + { + self::$_proxyVars = (bool)$proxyVars; + } + + /** + * @return bool + */ + public static function getProxyVars() + { + return self::$_proxyVars; + } + + /** + * Returns the protocol + the current host + the port (if different than + * common ports). + * + * @return string The URL + */ + public static function getSelfURLhost() + { + $currenthost = self::getSelfHost(); + + $port = ''; + + if (self::isHTTPS()) { + $protocol = 'https'; + } else { + $protocol = 'http'; + } + + $portnumber = self::getSelfPort(); + + if (isset($portnumber) && ($portnumber != '80') && ($portnumber != '443')) { + $port = ':' . $portnumber; + } + + return $protocol."://" . $currenthost . $port; + } + + /** + * @param string $host The host to use when constructing URLs + */ + public static function setSelfHost($host) + { + self::$_host = $host; + } + + /** + * @param string $baseurlpath The baseurl path to use when constructing URLs + */ + public static function setBaseURLPath($baseurlpath) + { + if (empty($baseurlpath)) { + self::$_baseurlpath = null; + } else if ($baseurlpath == '/') { + self::$_baseurlpath = '/'; + } else { + self::$_baseurlpath = '/' . trim($baseurlpath, '/') . '/'; + } + } + + /** + * @return string The baseurlpath to be used when constructing URLs + */ + public static function getBaseURLPath() + { + return self::$_baseurlpath; + } + + /** + * @return string The raw host name + */ + protected static function getRawHost() + { + if (self::$_host) { + $currentHost = self::$_host; + } elseif (self::getProxyVars() && array_key_exists('HTTP_X_FORWARDED_HOST', $_SERVER)) { + $currentHost = $_SERVER['HTTP_X_FORWARDED_HOST']; + } elseif (array_key_exists('HTTP_HOST', $_SERVER)) { + $currentHost = $_SERVER['HTTP_HOST']; + } elseif (array_key_exists('SERVER_NAME', $_SERVER)) { + $currentHost = $_SERVER['SERVER_NAME']; + } else { + if (function_exists('gethostname')) { + $currentHost = gethostname(); + } else { + $currentHost = php_uname("n"); + } + } + return $currentHost; + } + + /** + * @param int $port The port number to use when constructing URLs + */ + public static function setSelfPort($port) + { + self::$_port = $port; + } + + /** + * @param string $protocol The protocol to identify as using, usually http or https + */ + public static function setSelfProtocol($protocol) + { + self::$_protocol = $protocol; + } + + /** + * @return string http|https + */ + public static function getSelfProtocol() + { + $protocol = 'http'; + if (self::$_protocol) { + $protocol = self::$_protocol; + } elseif (self::getSelfPort() == 443) { + $protocol = 'https'; + } elseif (self::getProxyVars() && isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) { + $protocol = $_SERVER['HTTP_X_FORWARDED_PROTO']; + } elseif (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') { + $protocol = 'https'; + } + return $protocol; + } + + /** + * Returns the current host. + * + * @return string $currentHost The current host + */ + public static function getSelfHost() + { + $currentHost = self::getRawHost(); + + // strip the port + if (false !== strpos($currentHost, ':')) { + list($currentHost, $port) = explode(':', $currentHost, 2); + } + + return $currentHost; + } + + /** + * @return null|string The port number used for the request + */ + public static function getSelfPort() + { + $portnumber = null; + if (self::$_port) { + $portnumber = self::$_port; + } else if (self::getProxyVars() && isset($_SERVER["HTTP_X_FORWARDED_PORT"])) { + $portnumber = $_SERVER["HTTP_X_FORWARDED_PORT"]; + } else if (isset($_SERVER["SERVER_PORT"])) { + $portnumber = $_SERVER["SERVER_PORT"]; + } else { + $currentHost = self::getRawHost(); + + // strip the port + if (false !== strpos($currentHost, ':')) { + list($currentHost, $port) = explode(':', $currentHost, 2); + if (is_numeric($port)) { + $portnumber = $port; + } + } + } + return $portnumber; + } + + /** + * Checks if https or http. + * + * @return bool $isHttps False if https is not active + */ + public static function isHTTPS() + { + return self::getSelfProtocol() == 'https'; + } + + /** + * Returns the URL of the current host + current view. + * + * @return string + */ + public static function getSelfURLNoQuery() + { + $selfURLNoQuery = self::getSelfURLhost(); + + $infoWithBaseURLPath = self::buildWithBaseURLPath($_SERVER['SCRIPT_NAME']); + if (!empty($infoWithBaseURLPath)) { + $selfURLNoQuery .= $infoWithBaseURLPath; + } else { + $selfURLNoQuery .= $_SERVER['SCRIPT_NAME']; + } + + if (isset($_SERVER['PATH_INFO'])) { + $selfURLNoQuery .= $_SERVER['PATH_INFO']; + } + + return $selfURLNoQuery; + } + + /** + * Returns the routed URL of the current host + current view. + * + * @return string + */ + public static function getSelfRoutedURLNoQuery() + { + $selfURLhost = self::getSelfURLhost(); + $route = ''; + + if (!empty($_SERVER['REQUEST_URI'])) { + $route = $_SERVER['REQUEST_URI']; + if (!empty($_SERVER['QUERY_STRING'])) { + $route = self::strLreplace($_SERVER['QUERY_STRING'], '', $route); + if (substr($route, -1) == '?') { + $route = substr($route, 0, -1); + } + } + } + + $infoWithBaseURLPath = self::buildWithBaseURLPath($route); + if (!empty($infoWithBaseURLPath)) { + $route = $infoWithBaseURLPath; + } + + $selfRoutedURLNoQuery = $selfURLhost . $route; + + $pos = strpos($selfRoutedURLNoQuery, "?"); + if ($pos !== false) { + $selfRoutedURLNoQuery = substr($selfRoutedURLNoQuery, 0, $pos); + } + + return $selfRoutedURLNoQuery; + } + + public static function strLreplace($search, $replace, $subject) + { + $pos = strrpos($subject, $search); + + if ($pos !== false) { + $subject = substr_replace($subject, $replace, $pos, strlen($search)); + } + + return $subject; + } + + /** + * Returns the URL of the current host + current view + query. + * + * @return string + */ + public static function getSelfURL() + { + $selfURLhost = self::getSelfURLhost(); + + $requestURI = ''; + if (!empty($_SERVER['REQUEST_URI'])) { + $requestURI = $_SERVER['REQUEST_URI']; + $matches = array(); + if ($requestURI[0] !== '/' && preg_match('#^https?://[^/]*(/.*)#i', $requestURI, $matches)) { + $requestURI = $matches[1]; + } + } + + $infoWithBaseURLPath = self::buildWithBaseURLPath($requestURI); + if (!empty($infoWithBaseURLPath)) { + $requestURI = $infoWithBaseURLPath; + } + + return $selfURLhost . $requestURI; + } + + /** + * Returns the part of the URL with the BaseURLPath. + * + * @param string $info Contains path info + * + * @return string + */ + protected static function buildWithBaseURLPath($info) + { + $result = ''; + $baseURLPath = self::getBaseURLPath(); + if (!empty($baseURLPath)) { + $result = $baseURLPath; + if (!empty($info)) { + $path = explode('/', $info); + $extractedInfo = array_pop($path); + if (!empty($extractedInfo)) { + $result .= $extractedInfo; + } + } + } + return $result; + } + + /** + * Extract a query param - as it was sent - from $_SERVER[QUERY_STRING] + * + * @param string $name The param to-be extracted + * + * @return string + */ + public static function extractOriginalQueryParam($name) + { + $index = strpos($_SERVER['QUERY_STRING'], $name.'='); + $substring = substr($_SERVER['QUERY_STRING'], $index + strlen($name) + 1); + $end = strpos($substring, '&'); + return $end ? substr($substring, 0, strpos($substring, '&')) : $substring; + } + + /** + * Generates an unique string (used for example as ID for assertions). + * + * @return string A unique string + */ + public static function generateUniqueID() + { + return 'ONELOGIN_' . sha1(uniqid((string)mt_rand(), true)); + } + + /** + * Converts a UNIX timestamp to SAML2 timestamp on the form + * yyyy-mm-ddThh:mm:ss(\.s+)?Z. + * + * @param string|int $time The time we should convert (DateTime). + * + * @return string $timestamp SAML2 timestamp. + */ + public static function parseTime2SAML($time) + { + $date = new \DateTime("@$time", new \DateTimeZone('UTC')); + $timestamp = $date->format("Y-m-d\TH:i:s\Z"); + return $timestamp; + } + + /** + * Converts a SAML2 timestamp on the form yyyy-mm-ddThh:mm:ss(\.s+)?Z + * to a UNIX timestamp. The sub-second part is ignored. + * + * @param string $time The time we should convert (SAML Timestamp). + * + * @return int $timestamp Converted to a unix timestamp. + * + * @throws Exception + */ + public static function parseSAML2Time($time) + { + $matches = array(); + + /* We use a very strict regex to parse the timestamp. */ + $exp1 = '/^(\\d\\d\\d\\d)-(\\d\\d)-(\\d\\d)'; + $exp2 = 'T(\\d\\d):(\\d\\d):(\\d\\d)(?:\\.\\d+)?Z$/D'; + if (preg_match($exp1 . $exp2, $time, $matches) == 0) { + throw new Exception( + 'Invalid SAML2 timestamp passed to' . + ' parseSAML2Time: ' . $time + ); + } + + /* Extract the different components of the time from the + * matches in the regex. int cast will ignore leading zeroes + * in the string. + */ + $year = (int) $matches[1]; + $month = (int) $matches[2]; + $day = (int) $matches[3]; + $hour = (int) $matches[4]; + $minute = (int) $matches[5]; + $second = (int) $matches[6]; + + /* We use gmmktime because the timestamp will always be given + * in UTC. + */ + $ts = gmmktime($hour, $minute, $second, $month, $day, $year); + + return $ts; + } + + + /** + * Interprets a ISO8601 duration value relative to a given timestamp. + * + * @param string $duration The duration, as a string. + * @param int|null $timestamp The unix timestamp we should apply the + * duration to. Optional, default to the + * current time. + * + * @return int The new timestamp, after the duration is applied. + * + * @throws Exception + */ + public static function parseDuration($duration, $timestamp = null) + { + assert(is_string($duration)); + assert(is_null($timestamp) || is_int($timestamp)); + + $matches = array(); + + /* Parse the duration. We use a very strict pattern. */ + $durationRegEx = '#^(-?)P(?:(?:(?:(\\d+)Y)?(?:(\\d+)M)?(?:(\\d+)D)?(?:T(?:(\\d+)H)?(?:(\\d+)M)?(?:(\\d+)S)?)?)|(?:(\\d+)W))$#D'; + if (!preg_match($durationRegEx, $duration, $matches)) { + throw new Exception('Invalid ISO 8601 duration: ' . $duration); + } + + $durYears = (empty($matches[2]) ? 0 : (int)$matches[2]); + $durMonths = (empty($matches[3]) ? 0 : (int)$matches[3]); + $durDays = (empty($matches[4]) ? 0 : (int)$matches[4]); + $durHours = (empty($matches[5]) ? 0 : (int)$matches[5]); + $durMinutes = (empty($matches[6]) ? 0 : (int)$matches[6]); + $durSeconds = (empty($matches[7]) ? 0 : (int)$matches[7]); + $durWeeks = (empty($matches[8]) ? 0 : (int)$matches[8]); + + if (!empty($matches[1])) { + /* Negative */ + $durYears = -$durYears; + $durMonths = -$durMonths; + $durDays = -$durDays; + $durHours = -$durHours; + $durMinutes = -$durMinutes; + $durSeconds = -$durSeconds; + $durWeeks = -$durWeeks; + } + + if ($timestamp === null) { + $timestamp = time(); + } + + if ($durYears !== 0 || $durMonths !== 0) { + /* Special handling of months and years, since they aren't a specific interval, but + * instead depend on the current time. + */ + + /* We need the year and month from the timestamp. Unfortunately, PHP doesn't have the + * gmtime function. Instead we use the gmdate function, and split the result. + */ + $yearmonth = explode(':', gmdate('Y:n', $timestamp)); + $year = (int)$yearmonth[0]; + $month = (int)$yearmonth[1]; + + /* Remove the year and month from the timestamp. */ + $timestamp -= gmmktime(0, 0, 0, $month, 1, $year); + + /* Add years and months, and normalize the numbers afterwards. */ + $year += $durYears; + $month += $durMonths; + while ($month > 12) { + $year += 1; + $month -= 12; + } + while ($month < 1) { + $year -= 1; + $month += 12; + } + + /* Add year and month back into timestamp. */ + $timestamp += gmmktime(0, 0, 0, $month, 1, $year); + } + + /* Add the other elements. */ + $timestamp += $durWeeks * 7 * 24 * 60 * 60; + $timestamp += $durDays * 24 * 60 * 60; + $timestamp += $durHours * 60 * 60; + $timestamp += $durMinutes * 60; + $timestamp += $durSeconds; + + return $timestamp; + } + + /** + * Compares 2 dates and returns the earliest. + * + * @param string|null $cacheDuration The duration, as a string. + * @param string|int|null $validUntil The valid until date, as a string or as a timestamp + * + * @return int|null $expireTime The expiration time. + * + * @throws Exception + */ + public static function getExpireTime($cacheDuration = null, $validUntil = null) + { + $expireTime = null; + + if ($cacheDuration !== null) { + $expireTime = self::parseDuration($cacheDuration, time()); + } + + if ($validUntil !== null) { + if (is_int($validUntil)) { + $validUntilTime = $validUntil; + } else { + $validUntilTime = self::parseSAML2Time($validUntil); + } + if ($expireTime === null || $expireTime > $validUntilTime) { + $expireTime = $validUntilTime; + } + } + + return $expireTime; + } + + + /** + * Extracts nodes from the DOMDocument. + * + * @param DOMDocument $dom The DOMDocument + * @param string $query \Xpath Expression + * @param DOMElement|null $context Context Node (DOMElement) + * + * @return DOMNodeList The queried nodes + */ + public static function query(DOMDocument $dom, $query, DOMElement $context = null) + { + $xpath = new DOMXPath($dom); + $xpath->registerNamespace('samlp', Constants::NS_SAMLP); + $xpath->registerNamespace('saml', Constants::NS_SAML); + $xpath->registerNamespace('ds', Constants::NS_DS); + $xpath->registerNamespace('xenc', Constants::NS_XENC); + $xpath->registerNamespace('xsi', Constants::NS_XSI); + $xpath->registerNamespace('xs', Constants::NS_XS); + $xpath->registerNamespace('md', Constants::NS_MD); + + if (isset($context)) { + $res = $xpath->query($query, $context); + } else { + $res = $xpath->query($query); + } + return $res; + } + + /** + * Checks if the session is started or not. + * + * @return bool true if the sessíon is started + */ + public static function isSessionStarted() + { + if (PHP_VERSION_ID >= 50400) { + return session_status() === PHP_SESSION_ACTIVE ? true : false; + } else { + return session_id() === '' ? false : true; + } + } + + /** + * Deletes the local session. + */ + public static function deleteLocalSession() + { + if (Utils::isSessionStarted()) { + session_unset(); + session_destroy(); + } else { + $_SESSION = array(); + } + } + + /** + * Calculates the fingerprint of a x509cert. + * + * @param string $x509cert x509 cert formatted + * @param string $alg Algorithm to be used in order to calculate the fingerprint + * + * @return null|string Formatted fingerprint + */ + public static function calculateX509Fingerprint($x509cert, $alg = 'sha1') + { + assert(is_string($x509cert)); + + $arCert = explode("\n", $x509cert); + $data = ''; + $inData = false; + + foreach ($arCert as $curData) { + if (! $inData) { + if (strncmp($curData, '-----BEGIN CERTIFICATE', 22) == 0) { + $inData = true; + } elseif ((strncmp($curData, '-----BEGIN PUBLIC KEY', 21) == 0) || (strncmp($curData, '-----BEGIN RSA PRIVATE KEY', 26) == 0)) { + /* This isn't an X509 certificate. */ + return null; + } + } else { + if (strncmp($curData, '-----END CERTIFICATE', 20) == 0) { + break; + } + $data .= trim($curData); + } + } + + if (empty($data)) { + return null; + } + + $decodedData = base64_decode($data); + + switch ($alg) { + case 'sha512': + case 'sha384': + case 'sha256': + $fingerprint = hash($alg, $decodedData, false); + break; + case 'sha1': + default: + $fingerprint = strtolower(sha1($decodedData)); + break; + } + return $fingerprint; + } + + /** + * Formates a fingerprint. + * + * @param string $fingerprint fingerprint + * + * @return string Formatted fingerprint + */ + public static function formatFingerPrint($fingerprint) + { + $formatedFingerprint = str_replace(':', '', $fingerprint); + $formatedFingerprint = strtolower($formatedFingerprint); + return $formatedFingerprint; + } + + /** + * Generates a nameID. + * + * @param string $value fingerprint + * @param string $spnq SP Name Qualifier + * @param string|null $format SP Format + * @param string|null $cert IdP Public cert to encrypt the nameID + * @param string|null $nq IdP Name Qualifier + * @param string|null $encAlg Encryption algorithm + * + * @return string $nameIDElement DOMElement | XMLSec nameID + * + * @throws Exception + */ + public static function generateNameId($value, $spnq, $format = null, $cert = null, $nq = null, $encAlg = XMLSecurityKey::AES128_CBC) + { + + $doc = new DOMDocument(); + + $nameId = $doc->createElement('saml:NameID'); + if (isset($spnq)) { + $nameId->setAttribute('SPNameQualifier', $spnq); + } + if (isset($nq)) { + $nameId->setAttribute('NameQualifier', $nq); + } + if (isset($format)) { + $nameId->setAttribute('Format', $format); + } + $nameId->appendChild($doc->createTextNode($value)); + + $doc->appendChild($nameId); + + if (!empty($cert)) { + if ($encAlg == XMLSecurityKey::AES128_CBC) { + $seckey = new XMLSecurityKey(XMLSecurityKey::RSA_1_5, array('type'=>'public')); + } else { + $seckey = new XMLSecurityKey(XMLSecurityKey::RSA_OAEP_MGF1P, array('type'=>'public')); + } + $seckey->loadKey($cert); + + $enc = new XMLSecEnc(); + $enc->setNode($nameId); + $enc->type = XMLSecEnc::Element; + + $symmetricKey = new XMLSecurityKey($encAlg); + $symmetricKey->generateSessionKey(); + $enc->encryptKey($seckey, $symmetricKey); + + $encryptedData = $enc->encryptNode($symmetricKey); + + $newdoc = new DOMDocument(); + + $encryptedID = $newdoc->createElement('saml:EncryptedID'); + + $newdoc->appendChild($encryptedID); + + $encryptedID->appendChild($encryptedID->ownerDocument->importNode($encryptedData, true)); + + return $newdoc->saveXML($encryptedID); + } else { + return $doc->saveXML($nameId); + } + } + + + /** + * Gets Status from a Response. + * + * @param DOMDocument $dom The Response as XML + * + * @return array $status The Status, an array with the code and a message. + * + * @throws ValidationError + */ + public static function getStatus(DOMDocument $dom) + { + $status = array(); + + $statusEntry = self::query($dom, '/samlp:Response/samlp:Status'); + if ($statusEntry->length != 1) { + throw new ValidationError( + "Missing Status on response", + ValidationError::MISSING_STATUS + ); + } + + $codeEntry = self::query($dom, '/samlp:Response/samlp:Status/samlp:StatusCode', $statusEntry->item(0)); + if ($codeEntry->length != 1) { + throw new ValidationError( + "Missing Status Code on response", + ValidationError::MISSING_STATUS_CODE + ); + } + $code = $codeEntry->item(0)->getAttribute('Value'); + $status['code'] = $code; + + $status['msg'] = ''; + $messageEntry = self::query($dom, '/samlp:Response/samlp:Status/samlp:StatusMessage', $statusEntry->item(0)); + if ($messageEntry->length == 0) { + $subCodeEntry = self::query($dom, '/samlp:Response/samlp:Status/samlp:StatusCode/samlp:StatusCode', $statusEntry->item(0)); + if ($subCodeEntry->length == 1) { + $status['msg'] = $subCodeEntry->item(0)->getAttribute('Value'); + } + } else if ($messageEntry->length == 1) { + $msg = $messageEntry->item(0)->textContent; + $status['msg'] = $msg; + } + + return $status; + } + + /** + * Decrypts an encrypted element. + * + * @param DOMElement $encryptedData The encrypted data. + * @param XMLSecurityKey $inputKey The decryption key. + * @param bool $formatOutput Format or not the output. + * + * @return DOMElement The decrypted element. + * + * @throws ValidationError + */ + public static function decryptElement(DOMElement $encryptedData, XMLSecurityKey $inputKey, $formatOutput = true) + { + + $enc = new XMLSecEnc(); + + $enc->setNode($encryptedData); + $enc->type = $encryptedData->getAttribute("Type"); + + $symmetricKey = $enc->locateKey($encryptedData); + if (!$symmetricKey) { + throw new ValidationError( + 'Could not locate key algorithm in encrypted data.', + ValidationError::KEY_ALGORITHM_ERROR + ); + } + + $symmetricKeyInfo = $enc->locateKeyInfo($symmetricKey); + if (!$symmetricKeyInfo) { + throw new ValidationError( + "Could not locate for the encrypted key.", + ValidationError::KEYINFO_NOT_FOUND_IN_ENCRYPTED_DATA + ); + } + + $inputKeyAlgo = $inputKey->getAlgorithm(); + if ($symmetricKeyInfo->isEncrypted) { + $symKeyInfoAlgo = $symmetricKeyInfo->getAlgorithm(); + + if ($symKeyInfoAlgo === XMLSecurityKey::RSA_OAEP_MGF1P && $inputKeyAlgo === XMLSecurityKey::RSA_1_5) { + $inputKeyAlgo = XMLSecurityKey::RSA_OAEP_MGF1P; + } + + if ($inputKeyAlgo !== $symKeyInfoAlgo) { + throw new ValidationError( + 'Algorithm mismatch between input key and key used to encrypt ' . + ' the symmetric key for the message. Key was: ' . + var_export($inputKeyAlgo, true) . '; message was: ' . + var_export($symKeyInfoAlgo, true), + ValidationError::KEY_ALGORITHM_ERROR + ); + } + + $encKey = $symmetricKeyInfo->encryptedCtx; + $symmetricKeyInfo->key = $inputKey->key; + $keySize = $symmetricKey->getSymmetricKeySize(); + if ($keySize === null) { + // To protect against "key oracle" attacks + throw new ValidationError( + 'Unknown key size for encryption algorithm: ' . var_export($symmetricKey->type, true), + ValidationError::KEY_ALGORITHM_ERROR + ); + } + + $key = $encKey->decryptKey($symmetricKeyInfo); + if (strlen($key) != $keySize) { + $encryptedKey = $encKey->getCipherValue(); + $pkey = openssl_pkey_get_details($symmetricKeyInfo->key); + $pkey = sha1(serialize($pkey), true); + $key = sha1($encryptedKey . $pkey, true); + + /* Make sure that the key has the correct length. */ + if (strlen($key) > $keySize) { + $key = substr($key, 0, $keySize); + } elseif (strlen($key) < $keySize) { + $key = str_pad($key, $keySize); + } + } + $symmetricKey->loadKey($key); + } else { + $symKeyAlgo = $symmetricKey->getAlgorithm(); + if ($inputKeyAlgo !== $symKeyAlgo) { + throw new ValidationError( + 'Algorithm mismatch between input key and key in message. ' . + 'Key was: ' . var_export($inputKeyAlgo, true) . '; message was: ' . + var_export($symKeyAlgo, true), + ValidationError::KEY_ALGORITHM_ERROR + ); + } + $symmetricKey = $inputKey; + } + + $decrypted = $enc->decryptNode($symmetricKey, false); + + $xml = ''.$decrypted.''; + $newDoc = new DOMDocument(); + if ($formatOutput) { + $newDoc->preserveWhiteSpace = false; + $newDoc->formatOutput = true; + } + $newDoc = self::loadXML($newDoc, $xml); + if (!$newDoc) { + throw new ValidationError( + 'Failed to parse decrypted XML.', + ValidationError::INVALID_XML_FORMAT + ); + } + + $decryptedElement = $newDoc->firstChild->firstChild; + if ($decryptedElement === null) { + throw new ValidationError( + 'Missing encrypted element.', + ValidationError::MISSING_ENCRYPTED_ELEMENT + ); + } + + return $decryptedElement; + } + + /** + * Converts a XMLSecurityKey to the correct algorithm. + * + * @param XMLSecurityKey $key The key. + * @param string $algorithm The desired algorithm. + * @param string $type Public or private key, defaults to public. + * + * @return XMLSecurityKey The new key. + * + * @throws Exception + */ + public static function castKey(XMLSecurityKey $key, $algorithm, $type = 'public') + { + assert(is_string($algorithm)); + assert($type === 'public' || $type === 'private'); + + // do nothing if algorithm is already the type of the key + if ($key->type === $algorithm) { + return $key; + } + + if (!Utils::isSupportedSigningAlgorithm($algorithm)) { + throw new Exception('Unsupported signing algorithm.'); + } + + $keyInfo = openssl_pkey_get_details($key->key); + if ($keyInfo === false) { + throw new Exception('Unable to get key details from XMLSecurityKey.'); + } + if (!isset($keyInfo['key'])) { + throw new Exception('Missing key in public key details.'); + } + $newKey = new XMLSecurityKey($algorithm, array('type'=>$type)); + $newKey->loadKey($keyInfo['key']); + return $newKey; + } + + /** + * @param $algorithm + * + * @return bool + */ + public static function isSupportedSigningAlgorithm($algorithm) + { + return in_array( + $algorithm, + array( + XMLSecurityKey::RSA_1_5, + XMLSecurityKey::RSA_SHA1, + XMLSecurityKey::RSA_SHA256, + XMLSecurityKey::RSA_SHA384, + XMLSecurityKey::RSA_SHA512 + ) + ); + } + + /** + * Adds signature key and senders certificate to an element (Message or Assertion). + * + * @param string|DOMDocument $xml The element we should sign + * @param string $key The private key + * @param string $cert The public + * @param string $signAlgorithm Signature algorithm method + * @param string $digestAlgorithm Digest algorithm method + * + * @return string + * + * @throws Exception + */ + public static function addSign($xml, $key, $cert, $signAlgorithm = XMLSecurityKey::RSA_SHA256, $digestAlgorithm = XMLSecurityDSig::SHA256) + { + if ($xml instanceof DOMDocument) { + $dom = $xml; + } else { + $dom = new DOMDocument(); + $dom = self::loadXML($dom, $xml); + if (!$dom) { + throw new Exception('Error parsing xml string'); + } + } + + /* Load the private key. */ + $objKey = new XMLSecurityKey($signAlgorithm, array('type' => 'private')); + $objKey->loadKey($key, false); + + /* Get the EntityDescriptor node we should sign. */ + $rootNode = $dom->firstChild; + + /* Sign the metadata with our private key. */ + $objXMLSecDSig = new XMLSecurityDSig(); + $objXMLSecDSig->setCanonicalMethod(XMLSecurityDSig::EXC_C14N); + + $objXMLSecDSig->addReferenceList( + array($rootNode), + $digestAlgorithm, + array('http://www.w3.org/2000/09/xmldsig#enveloped-signature', XMLSecurityDSig::EXC_C14N), + array('id_name' => 'ID') + ); + + $objXMLSecDSig->sign($objKey); + + /* Add the certificate to the signature. */ + $objXMLSecDSig->add509Cert($cert, true); + + $insertBefore = $rootNode->firstChild; + $messageTypes = array('AuthnRequest', 'Response', 'LogoutRequest','LogoutResponse'); + if (in_array($rootNode->localName, $messageTypes)) { + $issuerNodes = self::query($dom, '/'.$rootNode->tagName.'/saml:Issuer'); + if ($issuerNodes->length == 1) { + $insertBefore = $issuerNodes->item(0)->nextSibling; + } + } + + /* Add the signature. */ + $objXMLSecDSig->insertSignature($rootNode, $insertBefore); + + /* Return the DOM tree as a string. */ + $signedxml = $dom->saveXML(); + + return $signedxml; + } + + /** + * Validates a signature (Message or Assertion). + * + * @param string|\DomNode $xml The element we should validate + * @param string|null $cert The public cert + * @param string|null $fingerprint The fingerprint of the public cert + * @param string|null $fingerprintalg The algorithm used to get the fingerprint + * @param string|null $xpath The xpath of the signed element + * @param array|null $multiCerts Multiple public certs + * + * @return bool + * + * @throws Exception + */ + public static function validateSign($xml, $cert = null, $fingerprint = null, $fingerprintalg = 'sha1', $xpath = null, $multiCerts = null) + { + if ($xml instanceof DOMDocument) { + $dom = clone $xml; + } else if ($xml instanceof DOMElement) { + $dom = clone $xml->ownerDocument; + } else { + $dom = new DOMDocument(); + $dom = self::loadXML($dom, $xml); + } + + $objXMLSecDSig = new XMLSecurityDSig(); + $objXMLSecDSig->idKeys = array('ID'); + + if ($xpath) { + $nodeset = Utils::query($dom, $xpath); + $objDSig = $nodeset->item(0); + $objXMLSecDSig->sigNode = $objDSig; + } else { + $objDSig = $objXMLSecDSig->locateSignature($dom); + } + + if (!$objDSig) { + throw new Exception('Cannot locate Signature Node'); + } + + $objKey = $objXMLSecDSig->locateKey(); + if (!$objKey) { + throw new Exception('We have no idea about the key'); + } + + if (!Utils::isSupportedSigningAlgorithm($objKey->type)) { + throw new Exception('Unsupported signing algorithm.'); + } + + $objXMLSecDSig->canonicalizeSignedInfo(); + + try { + $retVal = $objXMLSecDSig->validateReference(); + } catch (Exception $e) { + throw $e; + } + + XMLSecEnc::staticLocateKeyInfo($objKey, $objDSig); + + if (!empty($multiCerts)) { + // If multiple certs are provided, I may ignore $cert and + // $fingerprint provided by the method and just check the + // certs on the array + $fingerprint = null; + } else { + // else I add the cert to the array in order to check + // validate signatures with it and the with it and the + // $fingerprint value + $multiCerts = array($cert); + } + + $valid = false; + foreach ($multiCerts as $cert) { + if (!empty($cert)) { + $objKey->loadKey($cert, false, true); + if ($objXMLSecDSig->verify($objKey) === 1) { + $valid = true; + break; + } + } else { + if (!empty($fingerprint)) { + $domCert = $objKey->getX509Certificate(); + $domCertFingerprint = Utils::calculateX509Fingerprint($domCert, $fingerprintalg); + if (Utils::formatFingerPrint($fingerprint) == $domCertFingerprint) { + $objKey->loadKey($domCert, false, true); + if ($objXMLSecDSig->verify($objKey) === 1) { + $valid = true; + break; + } + } + } + } + } + return $valid; + } + + /** + * Validates a binary signature + * + * @param string $messageType Type of SAML Message + * @param array $getData HTTP GET array + * @param array $idpData IdP setting data + * @param bool $retrieveParametersFromServer Indicates where to get the values in order to validate the Sign, from getData or from $_SERVER + * + * @return bool + * + * @throws Exception + */ + public static function validateBinarySign($messageType, $getData, $idpData, $retrieveParametersFromServer = false) + { + if (!isset($getData['SigAlg'])) { + $signAlg = XMLSecurityKey::RSA_SHA1; + } else { + $signAlg = $getData['SigAlg']; + } + + if ($retrieveParametersFromServer) { + $signedQuery = $messageType.'='.Utils::extractOriginalQueryParam($messageType); + if (isset($getData['RelayState'])) { + $signedQuery .= '&RelayState='.Utils::extractOriginalQueryParam('RelayState'); + } + $signedQuery .= '&SigAlg='.Utils::extractOriginalQueryParam('SigAlg'); + } else { + $signedQuery = $messageType.'='.urlencode($getData[$messageType]); + if (isset($getData['RelayState'])) { + $signedQuery .= '&RelayState='.urlencode($getData['RelayState']); + } + $signedQuery .= '&SigAlg='.urlencode($signAlg); + } + + if ($messageType == "SAMLRequest") { + $strMessageType = "Logout Request"; + } else { + $strMessageType = "Logout Response"; + } + $existsMultiX509Sign = isset($idpData['x509certMulti']) && isset($idpData['x509certMulti']['signing']) && !empty($idpData['x509certMulti']['signing']); + if ((!isset($idpData['x509cert']) || empty($idpData['x509cert'])) && !$existsMultiX509Sign) { + throw new Error( + "In order to validate the sign on the ".$strMessageType.", the x509cert of the IdP is required", + Error::CERT_NOT_FOUND + ); + } + + if ($existsMultiX509Sign) { + $multiCerts = $idpData['x509certMulti']['signing']; + } else { + $multiCerts = array($idpData['x509cert']); + } + + $signatureValid = false; + foreach ($multiCerts as $cert) { + $objKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type' => 'public')); + $objKey->loadKey($cert, false, true); + + if ($signAlg != XMLSecurityKey::RSA_SHA1) { + try { + $objKey = Utils::castKey($objKey, $signAlg, 'public'); + } catch (Exception $e) { + $ex = new ValidationError( + "Invalid signAlg in the recieved ".$strMessageType, + ValidationError::INVALID_SIGNATURE + ); + if (count($multiCerts) == 1) { + throw $ex; + } + } + } + + if ($objKey->verifySignature($signedQuery, base64_decode($getData['Signature'])) === 1) { + $signatureValid = true; + break; + } + } + return $signatureValid; + } +} diff --git a/vendor/onelogin/php-saml/src/Saml2/ValidationError.php b/vendor/onelogin/php-saml/src/Saml2/ValidationError.php new file mode 100644 index 00000000..889f531c --- /dev/null +++ b/vendor/onelogin/php-saml/src/Saml2/ValidationError.php @@ -0,0 +1,100 @@ + + * @license MIT https://github.com/onelogin/php-saml/blob/master/LICENSE + * @link https://github.com/onelogin/php-saml + */ + +namespace OneLogin\Saml2; + +use Exception; + +/** + * ValidationError class of OneLogin PHP Toolkit + * + * This class implements another custom Exception handler, + * related to exceptions that happens during validation process. + */ +class ValidationError extends Exception +{ + // Validation Errors + const UNSUPPORTED_SAML_VERSION = 0; + const MISSING_ID = 1; + const WRONG_NUMBER_OF_ASSERTIONS = 2; + const MISSING_STATUS = 3; + const MISSING_STATUS_CODE = 4; + const STATUS_CODE_IS_NOT_SUCCESS = 5; + const WRONG_SIGNED_ELEMENT = 6; + const ID_NOT_FOUND_IN_SIGNED_ELEMENT = 7; + const DUPLICATED_ID_IN_SIGNED_ELEMENTS = 8; + const INVALID_SIGNED_ELEMENT = 9; + const DUPLICATED_REFERENCE_IN_SIGNED_ELEMENTS = 10; + const UNEXPECTED_SIGNED_ELEMENTS = 11; + const WRONG_NUMBER_OF_SIGNATURES_IN_RESPONSE = 12; + const WRONG_NUMBER_OF_SIGNATURES_IN_ASSERTION = 13; + const INVALID_XML_FORMAT = 14; + const WRONG_INRESPONSETO = 15; + const NO_ENCRYPTED_ASSERTION = 16; + const NO_ENCRYPTED_NAMEID = 17; + const MISSING_CONDITIONS = 18; + const ASSERTION_TOO_EARLY = 19; + const ASSERTION_EXPIRED = 20; + const WRONG_NUMBER_OF_AUTHSTATEMENTS = 21; + const NO_ATTRIBUTESTATEMENT = 22; + const ENCRYPTED_ATTRIBUTES = 23; + const WRONG_DESTINATION = 24; + const EMPTY_DESTINATION = 25; + const WRONG_AUDIENCE = 26; + const ISSUER_MULTIPLE_IN_RESPONSE = 27; + const ISSUER_NOT_FOUND_IN_ASSERTION = 28; + const WRONG_ISSUER = 29; + const SESSION_EXPIRED = 30; + const WRONG_SUBJECTCONFIRMATION = 31; + const NO_SIGNED_MESSAGE = 32; + const NO_SIGNED_ASSERTION = 33; + const NO_SIGNATURE_FOUND = 34; + const KEYINFO_NOT_FOUND_IN_ENCRYPTED_DATA = 35; + const CHILDREN_NODE_NOT_FOUND_IN_KEYINFO = 36; + const UNSUPPORTED_RETRIEVAL_METHOD = 37; + const NO_NAMEID = 38; + const EMPTY_NAMEID = 39; + const SP_NAME_QUALIFIER_NAME_MISMATCH = 40; + const DUPLICATED_ATTRIBUTE_NAME_FOUND = 41; + const INVALID_SIGNATURE = 42; + const WRONG_NUMBER_OF_SIGNATURES = 43; + const RESPONSE_EXPIRED = 44; + const UNEXPECTED_REFERENCE = 45; + const NOT_SUPPORTED = 46; + const KEY_ALGORITHM_ERROR = 47; + const MISSING_ENCRYPTED_ELEMENT = 48; + + + /** + * Constructor + * + * @param string $msg Describes the error. + * @param int $code The code error (defined in the error class). + * @param array|null $args Arguments used in the message that describes the error. + */ + public function __construct($msg, $code = 0, $args = array()) + { + assert(is_string($msg)); + assert(is_int($code)); + + if (!isset($args)) { + $args = array(); + } + $params = array_merge(array($msg), $args); + $message = call_user_func_array('sprintf', $params); + + parent::__construct($message, $code); + } +} diff --git a/vendor/onelogin/php-saml/src/Saml2/schemas/saml-schema-assertion-2.0.xsd b/vendor/onelogin/php-saml/src/Saml2/schemas/saml-schema-assertion-2.0.xsd new file mode 100644 index 00000000..2b2f7b80 --- /dev/null +++ b/vendor/onelogin/php-saml/src/Saml2/schemas/saml-schema-assertion-2.0.xsd @@ -0,0 +1,283 @@ + + + + + + + Document identifier: saml-schema-assertion-2.0 + Location: http://docs.oasis-open.org/security/saml/v2.0/ + Revision history: + V1.0 (November, 2002): + Initial Standard Schema. + V1.1 (September, 2003): + Updates within the same V1.0 namespace. + V2.0 (March, 2005): + New assertion schema for SAML V2.0 namespace. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/onelogin/php-saml/src/Saml2/schemas/saml-schema-authn-context-2.0.xsd b/vendor/onelogin/php-saml/src/Saml2/schemas/saml-schema-authn-context-2.0.xsd new file mode 100644 index 00000000..e4754faf --- /dev/null +++ b/vendor/onelogin/php-saml/src/Saml2/schemas/saml-schema-authn-context-2.0.xsd @@ -0,0 +1,23 @@ + + + + + + Document identifier: saml-schema-authn-context-2.0 + Location: http://docs.oasis-open.org/security/saml/v2.0/ + Revision history: + V2.0 (March, 2005): + New core authentication context schema for SAML V2.0. + This is just an include of all types from the schema + referred to in the include statement below. + + + + + + \ No newline at end of file diff --git a/vendor/onelogin/php-saml/src/Saml2/schemas/saml-schema-authn-context-types-2.0.xsd b/vendor/onelogin/php-saml/src/Saml2/schemas/saml-schema-authn-context-types-2.0.xsd new file mode 100644 index 00000000..8513959a --- /dev/null +++ b/vendor/onelogin/php-saml/src/Saml2/schemas/saml-schema-authn-context-types-2.0.xsd @@ -0,0 +1,821 @@ + + + + + + Document identifier: saml-schema-authn-context-types-2.0 + Location: http://docs.oasis-open.org/security/saml/v2.0/ + Revision history: + V2.0 (March, 2005): + New core authentication context schema types for SAML V2.0. + + + + + + + A particular assertion on an identity + provider's part with respect to the authentication + context associated with an authentication assertion. + + + + + + + + Refers to those characteristics that describe the + processes and mechanisms + the Authentication Authority uses to initially create + an association between a Principal + and the identity (or name) by which the Principal will + be known + + + + + + + + This element indicates that identification has been + performed in a physical + face-to-face meeting with the principal and not in an + online manner. + + + + + + + + + + + + + + + + + + + + Refers to those characterstics that describe how the + 'secret' (the knowledge or possession + of which allows the Principal to authenticate to the + Authentication Authority) is kept secure + + + + + + + + This element indicates the types and strengths of + facilities + of a UA used to protect a shared secret key from + unauthorized access and/or use. + + + + + + + + This element indicates the types and strengths of + facilities + of a UA used to protect a private key from + unauthorized access and/or use. + + + + + + + The actions that must be performed + before the private key can be used. + + + + + + Whether or not the private key is shared + with the certificate authority. + + + + + + + In which medium is the key stored. + memory - the key is stored in memory. + smartcard - the key is stored in a smartcard. + token - the key is stored in a hardware token. + MobileDevice - the key is stored in a mobile device. + MobileAuthCard - the key is stored in a mobile + authentication card. + + + + + + + + + + + This element indicates that a password (or passphrase) + has been used to + authenticate the Principal to a remote system. + + + + + + + + This element indicates that a Pin (Personal + Identification Number) has been used to authenticate the Principal to + some local system in order to activate a key. + + + + + + + + This element indicates that a hardware or software + token is used + as a method of identifying the Principal. + + + + + + + + This element indicates that a time synchronization + token is used to identify the Principal. hardware - + the time synchonization + token has been implemented in hardware. software - the + time synchronization + token has been implemented in software. SeedLength - + the length, in bits, of the + random seed used in the time synchronization token. + + + + + + + + This element indicates that a smartcard is used to + identity the Principal. + + + + + + + + This element indicates the minimum and/or maximum + ASCII length of the password which is enforced (by the UA or the + IdP). In other words, this is the minimum and/or maximum number of + ASCII characters required to represent a valid password. + min - the minimum number of ASCII characters required + in a valid password, as enforced by the UA or the IdP. + max - the maximum number of ASCII characters required + in a valid password, as enforced by the UA or the IdP. + + + + + + + + This element indicates the length of time for which an + PIN-based authentication is valid. + + + + + + + + Indicates whether the password was chosen by the + Principal or auto-supplied by the Authentication Authority. + principalchosen - the Principal is allowed to choose + the value of the password. This is true even if + the initial password is chosen at random by the UA or + the IdP and the Principal is then free to change + the password. + automatic - the password is chosen by the UA or the + IdP to be cryptographically strong in some sense, + or to satisfy certain password rules, and that the + Principal is not free to change it or to choose a new password. + + + + + + + + + + + + + + + + + + + Refers to those characteristics that define the + mechanisms by which the Principal authenticates to the Authentication + Authority. + + + + + + + + The method that a Principal employs to perform + authentication to local system components. + + + + + + + + The method applied to validate a principal's + authentication across a network + + + + + + + + Supports Authenticators with nested combinations of + additional complexity. + + + + + + + + Indicates that the Principal has been strongly + authenticated in a previous session during which the IdP has set a + cookie in the UA. During the present session the Principal has only + been authenticated by the UA returning the cookie to the IdP. + + + + + + + + Rather like PreviousSession but using stronger + security. A secret that was established in a previous session with + the Authentication Authority has been cached by the local system and + is now re-used (e.g. a Master Secret is used to derive new session + keys in TLS, SSL, WTLS). + + + + + + + + This element indicates that the Principal has been + authenticated by a zero knowledge technique as specified in ISO/IEC + 9798-5. + + + + + + + + + + This element indicates that the Principal has been + authenticated by a challenge-response protocol utilizing shared secret + keys and symmetric cryptography. + + + + + + + + + + + + This element indicates that the Principal has been + authenticated by a mechanism which involves the Principal computing a + digital signature over at least challenge data provided by the IdP. + + + + + + + + The local system has a private key but it is used + in decryption mode, rather than signature mode. For example, the + Authentication Authority generates a secret and encrypts it using the + local system's public key: the local system then proves it has + decrypted the secret. + + + + + + + + The local system has a private key and uses it for + shared secret key agreement with the Authentication Authority (e.g. + via Diffie Helman). + + + + + + + + + + + + + + + This element indicates that the Principal has been + authenticated through connection from a particular IP address. + + + + + + + + The local system and Authentication Authority + share a secret key. The local system uses this to encrypt a + randomised string to pass to the Authentication Authority. + + + + + + + + The protocol across which Authenticator information is + transferred to an Authentication Authority verifier. + + + + + + + + This element indicates that the Authenticator has been + transmitted using bare HTTP utilizing no additional security + protocols. + + + + + + + + This element indicates that the Authenticator has been + transmitted using a transport mechanism protected by an IPSEC session. + + + + + + + + This element indicates that the Authenticator has been + transmitted using a transport mechanism protected by a WTLS session. + + + + + + + + This element indicates that the Authenticator has been + transmitted solely across a mobile network using no additional + security mechanism. + + + + + + + + + + + This element indicates that the Authenticator has been + transmitted using a transport mechnanism protected by an SSL or TLS + session. + + + + + + + + + + + + Refers to those characteristics that describe + procedural security controls employed by the Authentication Authority. + + + + + + + + + + + + Provides a mechanism for linking to external (likely + human readable) documents in which additional business agreements, + (e.g. liability constraints, obligations, etc) can be placed. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This attribute indicates whether or not the + Identification mechanisms allow the actions of the Principal to be + linked to an actual end user. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This element indicates that the Key Activation Limit is + defined as a specific duration of time. + + + + + + + + This element indicates that the Key Activation Limit is + defined as a number of usages. + + + + + + + + This element indicates that the Key Activation Limit is + the session. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/onelogin/php-saml/src/Saml2/schemas/saml-schema-metadata-2.0.xsd b/vendor/onelogin/php-saml/src/Saml2/schemas/saml-schema-metadata-2.0.xsd new file mode 100644 index 00000000..86e58f9b --- /dev/null +++ b/vendor/onelogin/php-saml/src/Saml2/schemas/saml-schema-metadata-2.0.xsd @@ -0,0 +1,336 @@ + + + + + + + + + Document identifier: saml-schema-metadata-2.0 + Location: http://docs.oasis-open.org/security/saml/v2.0/ + Revision history: + V2.0 (March, 2005): + Schema for SAML metadata, first published in SAML 2.0. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/onelogin/php-saml/src/Saml2/schemas/saml-schema-protocol-2.0.xsd b/vendor/onelogin/php-saml/src/Saml2/schemas/saml-schema-protocol-2.0.xsd new file mode 100644 index 00000000..7fa6f489 --- /dev/null +++ b/vendor/onelogin/php-saml/src/Saml2/schemas/saml-schema-protocol-2.0.xsd @@ -0,0 +1,302 @@ + + + + + + + Document identifier: saml-schema-protocol-2.0 + Location: http://docs.oasis-open.org/security/saml/v2.0/ + Revision history: + V1.0 (November, 2002): + Initial Standard Schema. + V1.1 (September, 2003): + Updates within the same V1.0 namespace. + V2.0 (March, 2005): + New protocol schema based in a SAML V2.0 namespace. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/onelogin/php-saml/src/Saml2/schemas/sstc-metadata-attr.xsd b/vendor/onelogin/php-saml/src/Saml2/schemas/sstc-metadata-attr.xsd new file mode 100644 index 00000000..f23e462a --- /dev/null +++ b/vendor/onelogin/php-saml/src/Saml2/schemas/sstc-metadata-attr.xsd @@ -0,0 +1,35 @@ + + + + + + Document title: SAML V2.0 Metadata Extention for Entity Attributes Schema + Document identifier: sstc-metadata-attr.xsd + Location: http://www.oasis-open.org/committees/documents.php?wg_abbrev=security + Revision history: + V1.0 (November 2008): + Initial version. + + + + + + + + + + + + + + + diff --git a/vendor/onelogin/php-saml/src/Saml2/schemas/sstc-saml-attribute-ext.xsd b/vendor/onelogin/php-saml/src/Saml2/schemas/sstc-saml-attribute-ext.xsd new file mode 100644 index 00000000..ad309c14 --- /dev/null +++ b/vendor/onelogin/php-saml/src/Saml2/schemas/sstc-saml-attribute-ext.xsd @@ -0,0 +1,25 @@ + + + + + + Document title: SAML V2.0 Attribute Extension Schema + Document identifier: sstc-saml-attribute-ext.xsd + Location: http://www.oasis-open.org/committees/documents.php?wg_abbrev=security + Revision history: + V1.0 (October 2008): + Initial version. + + + + + + + + diff --git a/vendor/onelogin/php-saml/src/Saml2/schemas/sstc-saml-metadata-algsupport-v1.0.xsd b/vendor/onelogin/php-saml/src/Saml2/schemas/sstc-saml-metadata-algsupport-v1.0.xsd new file mode 100644 index 00000000..3236ffcd --- /dev/null +++ b/vendor/onelogin/php-saml/src/Saml2/schemas/sstc-saml-metadata-algsupport-v1.0.xsd @@ -0,0 +1,41 @@ + + + + + + Document title: Metadata Extension Schema for SAML V2.0 Metadata Profile for Algorithm Support Version 1.0 + Document identifier: sstc-saml-metadata-algsupport.xsd + Location: http://docs.oasis-open.org/security/saml/Post2.0/ + Revision history: + V1.0 (June 2010): + Initial version. + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/onelogin/php-saml/src/Saml2/schemas/sstc-saml-metadata-ui-v1.0.xsd b/vendor/onelogin/php-saml/src/Saml2/schemas/sstc-saml-metadata-ui-v1.0.xsd new file mode 100644 index 00000000..de0b754a --- /dev/null +++ b/vendor/onelogin/php-saml/src/Saml2/schemas/sstc-saml-metadata-ui-v1.0.xsd @@ -0,0 +1,89 @@ + + + + + + Document title: Metadata Extension Schema for SAML V2.0 Metadata Extensions for Login and Discovery User Interface Version 1.0 + Document identifier: sstc-saml-metadata-ui-v1.0.xsd + Location: http://docs.oasis-open.org/security/saml/Post2.0/ + Revision history: + 16 November 2010: + Added Keywords element/type. + 01 November 2010 + Changed filename. + September 2010: + Initial version. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/onelogin/php-saml/src/Saml2/schemas/xenc-schema.xsd b/vendor/onelogin/php-saml/src/Saml2/schemas/xenc-schema.xsd new file mode 100644 index 00000000..d6d79103 --- /dev/null +++ b/vendor/onelogin/php-saml/src/Saml2/schemas/xenc-schema.xsd @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/onelogin/php-saml/src/Saml2/schemas/xml.xsd b/vendor/onelogin/php-saml/src/Saml2/schemas/xml.xsd new file mode 100644 index 00000000..aea7d0db --- /dev/null +++ b/vendor/onelogin/php-saml/src/Saml2/schemas/xml.xsd @@ -0,0 +1,287 @@ + + + + + + +
+

About the XML namespace

+ +
+

+ This schema document describes the XML namespace, in a form + suitable for import by other schema documents. +

+

+ See + http://www.w3.org/XML/1998/namespace.html and + + http://www.w3.org/TR/REC-xml for information + about this namespace. +

+

+ Note that local names in this namespace are intended to be + defined only by the World Wide Web Consortium or its subgroups. + The names currently defined in this namespace are listed below. + They should not be used with conflicting semantics by any Working + Group, specification, or document instance. +

+

+ See further below in this document for more information about how to refer to this schema document from your own + XSD schema documents and about the + namespace-versioning policy governing this schema document. +

+
+
+
+
+ + + + +
+ +

lang (as an attribute name)

+

+ denotes an attribute whose value + is a language code for the natural language of the content of + any element; its value is inherited. This name is reserved + by virtue of its definition in the XML specification.

+ +
+
+

Notes

+

+ Attempting to install the relevant ISO 2- and 3-letter + codes as the enumerated possible values is probably never + going to be a realistic possibility. +

+

+ See BCP 47 at + http://www.rfc-editor.org/rfc/bcp/bcp47.txt + and the IANA language subtag registry at + + http://www.iana.org/assignments/language-subtag-registry + for further information. +

+

+ The union allows for the 'un-declaration' of xml:lang with + the empty string. +

+
+
+
+ + + + + + + + + +
+ + + + +
+ +

space (as an attribute name)

+

+ denotes an attribute whose + value is a keyword indicating what whitespace processing + discipline is intended for the content of the element; its + value is inherited. This name is reserved by virtue of its + definition in the XML specification.

+ +
+
+
+ + + + + + +
+ + + +
+ +

base (as an attribute name)

+

+ denotes an attribute whose value + provides a URI to be used as the base for interpreting any + relative URIs in the scope of the element on which it + appears; its value is inherited. This name is reserved + by virtue of its definition in the XML Base specification.

+ +

+ See http://www.w3.org/TR/xmlbase/ + for information about this attribute. +

+
+
+
+
+ + + + +
+ +

id (as an attribute name)

+

+ denotes an attribute whose value + should be interpreted as if declared to be of type ID. + This name is reserved by virtue of its definition in the + xml:id specification.

+ +

+ See http://www.w3.org/TR/xml-id/ + for information about this attribute. +

+
+
+
+
+ + + + + + + + + + +
+ +

Father (in any context at all)

+ +
+

+ denotes Jon Bosak, the chair of + the original XML Working Group. This name is reserved by + the following decision of the W3C XML Plenary and + XML Coordination groups: +

+
+

+ In appreciation for his vision, leadership and + dedication the W3C XML Plenary on this 10th day of + February, 2000, reserves for Jon Bosak in perpetuity + the XML name "xml:Father". +

+
+
+
+
+
+ + + +
+

About this schema document

+ +
+

+ This schema defines attributes and an attribute group suitable + for use by schemas wishing to allow xml:base, + xml:lang, xml:space or + xml:id attributes on elements they define. +

+

+ To enable this, such a schema must import this schema for + the XML namespace, e.g. as follows: +

+
+          <schema . . .>
+           . . .
+           <import namespace="http://www.w3.org/XML/1998/namespace"
+                      schemaLocation="http://www.w3.org/2001/xml.xsd"/>
+     
+

+ or +

+
+           <import namespace="http://www.w3.org/XML/1998/namespace"
+                      schemaLocation="http://www.w3.org/2009/01/xml.xsd"/>
+     
+

+ Subsequently, qualified reference to any of the attributes or the + group defined below will have the desired effect, e.g. +

+
+          <type . . .>
+           . . .
+           <attributeGroup ref="xml:specialAttrs"/>
+     
+

+ will define a type which will schema-validate an instance element + with any of those attributes. +

+
+
+
+
+ + + +
+

Versioning policy for this schema document

+
+

+ In keeping with the XML Schema WG's standard versioning + policy, this schema document will persist at + + http://www.w3.org/2009/01/xml.xsd. +

+

+ At the date of issue it can also be found at + + http://www.w3.org/2001/xml.xsd. +

+

+ The schema document at that URI may however change in the future, + in order to remain compatible with the latest version of XML + Schema itself, or with the XML namespace itself. In other words, + if the XML Schema or XML namespaces change, the version of this + document at + http://www.w3.org/2001/xml.xsd + + will change accordingly; the version at + + http://www.w3.org/2009/01/xml.xsd + + will not change. +

+

+ Previous dated (and unchanging) versions of this schema + document are at: +

+ +
+
+
+
+ +
+ diff --git a/vendor/onelogin/php-saml/src/Saml2/schemas/xmldsig-core-schema.xsd b/vendor/onelogin/php-saml/src/Saml2/schemas/xmldsig-core-schema.xsd new file mode 100644 index 00000000..6f5acc75 --- /dev/null +++ b/vendor/onelogin/php-saml/src/Saml2/schemas/xmldsig-core-schema.xsd @@ -0,0 +1,309 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/onelogin/php-saml/src/Saml2/version.json b/vendor/onelogin/php-saml/src/Saml2/version.json new file mode 100644 index 00000000..2367ebec --- /dev/null +++ b/vendor/onelogin/php-saml/src/Saml2/version.json @@ -0,0 +1,7 @@ +{ + "php-saml": { + "version": "3.6.1", + "released": "02/03/2021" + } +} + diff --git a/vendor/paragonie/random_compat/LICENSE b/vendor/paragonie/random_compat/LICENSE new file mode 100644 index 00000000..45c7017d --- /dev/null +++ b/vendor/paragonie/random_compat/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Paragon Initiative Enterprises + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/paragonie/random_compat/build-phar.sh b/vendor/paragonie/random_compat/build-phar.sh new file mode 100755 index 00000000..b4a5ba31 --- /dev/null +++ b/vendor/paragonie/random_compat/build-phar.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +basedir=$( dirname $( readlink -f ${BASH_SOURCE[0]} ) ) + +php -dphar.readonly=0 "$basedir/other/build_phar.php" $* \ No newline at end of file diff --git a/vendor/paragonie/random_compat/composer.json b/vendor/paragonie/random_compat/composer.json new file mode 100644 index 00000000..f2b9c4e5 --- /dev/null +++ b/vendor/paragonie/random_compat/composer.json @@ -0,0 +1,34 @@ +{ + "name": "paragonie/random_compat", + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "random", + "polyfill", + "pseudorandom" + ], + "license": "MIT", + "type": "library", + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "support": { + "issues": "https://github.com/paragonie/random_compat/issues", + "email": "info@paragonie.com", + "source": "https://github.com/paragonie/random_compat" + }, + "require": { + "php": ">= 7" + }, + "require-dev": { + "vimeo/psalm": "^1", + "phpunit/phpunit": "4.*|5.*" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + } +} diff --git a/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey b/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey new file mode 100644 index 00000000..eb50ebfc --- /dev/null +++ b/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEEd+wCqJDrx5B4OldM0dQE0ZMX+lx1ZWm +pui0SUqD4G29L3NGsz9UhJ/0HjBdbnkhIK5xviT0X5vtjacF6ajgcCArbTB+ds+p ++h7Q084NuSuIpNb6YPfoUFgC/CL9kAoc +-----END PUBLIC KEY----- diff --git a/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc b/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc new file mode 100644 index 00000000..6a1d7f30 --- /dev/null +++ b/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc @@ -0,0 +1,11 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v2.0.22 (MingW32) + +iQEcBAABAgAGBQJWtW1hAAoJEGuXocKCZATaJf0H+wbZGgskK1dcRTsuVJl9IWip +QwGw/qIKI280SD6/ckoUMxKDCJiFuPR14zmqnS36k7N5UNPnpdTJTS8T11jttSpg +1LCmgpbEIpgaTah+cELDqFCav99fS+bEiAL5lWDAHBTE/XPjGVCqeehyPYref4IW +NDBIEsvnHPHPLsn6X5jq4+Yj5oUixgxaMPiR+bcO4Sh+RzOVB6i2D0upWfRXBFXA +NNnsg9/zjvoC7ZW73y9uSH+dPJTt/Vgfeiv52/v41XliyzbUyLalf02GNPY+9goV +JHG1ulEEBJOCiUD9cE1PUIJwHA/HqyhHIvV350YoEFiHl8iSwm7SiZu5kPjaq74= +=B6+8 +-----END PGP SIGNATURE----- diff --git a/vendor/paragonie/random_compat/lib/random.php b/vendor/paragonie/random_compat/lib/random.php new file mode 100644 index 00000000..c7731a56 --- /dev/null +++ b/vendor/paragonie/random_compat/lib/random.php @@ -0,0 +1,32 @@ +buildFromDirectory(dirname(__DIR__).'/lib'); +rename( + dirname(__DIR__).'/lib/index.php', + dirname(__DIR__).'/lib/random.php' +); + +/** + * If we pass an (optional) path to a private key as a second argument, we will + * sign the Phar with OpenSSL. + * + * If you leave this out, it will produce an unsigned .phar! + */ +if ($argc > 1) { + if (!@is_readable($argv[1])) { + echo 'Could not read the private key file:', $argv[1], "\n"; + exit(255); + } + $pkeyFile = file_get_contents($argv[1]); + + $private = openssl_get_privatekey($pkeyFile); + if ($private !== false) { + $pkey = ''; + openssl_pkey_export($private, $pkey); + $phar->setSignatureAlgorithm(Phar::OPENSSL, $pkey); + + /** + * Save the corresponding public key to the file + */ + if (!@is_readable($dist.'/random_compat.phar.pubkey')) { + $details = openssl_pkey_get_details($private); + file_put_contents( + $dist.'/random_compat.phar.pubkey', + $details['key'] + ); + } + } else { + echo 'An error occurred reading the private key from OpenSSL.', "\n"; + exit(255); + } +} diff --git a/vendor/paragonie/random_compat/psalm-autoload.php b/vendor/paragonie/random_compat/psalm-autoload.php new file mode 100644 index 00000000..d71d1b81 --- /dev/null +++ b/vendor/paragonie/random_compat/psalm-autoload.php @@ -0,0 +1,9 @@ + + + + + + + + + + + + + + + diff --git a/vendor/phar-io/manifest/.github/FUNDING.yml b/vendor/phar-io/manifest/.github/FUNDING.yml new file mode 100644 index 00000000..70388edb --- /dev/null +++ b/vendor/phar-io/manifest/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: [theseer] diff --git a/vendor/phar-io/manifest/.github/workflows/ci.yml b/vendor/phar-io/manifest/.github/workflows/ci.yml new file mode 100644 index 00000000..c9e7feb0 --- /dev/null +++ b/vendor/phar-io/manifest/.github/workflows/ci.yml @@ -0,0 +1,86 @@ +name: "CI" + +on: + push: + branches: + - "master" + pull_request: null + +jobs: + qa: + name: "QA" + + runs-on: "ubuntu-latest" + + steps: + - name: "Checkout" + uses: "actions/checkout@v3.5.2" + + - name: "Set up PHP" + uses: "shivammathur/setup-php@2.25.1" + with: + coverage: "none" + php-version: "8.0" + tools: "phive" + + - name: "Install dependencies with composer" + run: "composer install --no-interaction --optimize-autoloader --prefer-dist" + + - name: "Install dependencies with phive" + env: + GITHUB_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: "ant install-tools" + + - name: "Run php-cs-fixer" + run: "ant php-cs-fixer" + + - name: "Run psalm" + run: "ant psalm" + + tests: + name: "Tests" + + runs-on: "ubuntu-latest" + + strategy: + fail-fast: false + + matrix: + php-versions: + - "7.2" + - "7.3" + - "7.4" + - "8.0" + - "8.1" + - "8.2" + + steps: + - name: "Checkout" + uses: "actions/checkout@v3.5.2" + + - name: "Set up PHP" + uses: "shivammathur/setup-php@2.25.1" + env: + COMPOSER_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + with: + coverage: "pcov" + extensions: "${{ env.extensions }}" + ini-values: "display_errors=On, error_reporting=-1, memory_limit=2G" + php-version: "${{ matrix.php-versions }}" + tools: "phive" + + - name: "Install dependencies with composer" + run: "composer install --no-interaction --optimize-autoloader --prefer-dist" + + - name: "Install dependencies with phive" + env: + GITHUB_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: "ant install-tools" + + - name: "Run PHPUnit" + run: "tools/phpunit --coverage-clover build/logs/clover.xml" + + - name: "Send code coverage report to codecov.io" + uses: "codecov/codecov-action@v3.1.4" + with: + files: "build/logs/clover.xml" diff --git a/vendor/phar-io/manifest/.php-cs-fixer.dist.php b/vendor/phar-io/manifest/.php-cs-fixer.dist.php new file mode 100644 index 00000000..ca965141 --- /dev/null +++ b/vendor/phar-io/manifest/.php-cs-fixer.dist.php @@ -0,0 +1,223 @@ +registerCustomFixers([ + new \PharIo\CSFixer\PhpdocSingleLineVarFixer() + ]) + ->setRiskyAllowed(true) + ->setRules( + [ + 'PharIo/phpdoc_single_line_var_fixer' => true, + + 'align_multiline_comment' => true, + 'array_indentation' => true, + 'array_syntax' => ['syntax' => 'short'], + 'binary_operator_spaces' => [ + 'operators' => [ + '=' => 'align', + '=>' => 'align', + ], + ], + 'blank_line_after_namespace' => true, + 'blank_line_after_opening_tag' => false, + 'blank_line_before_statement' => [ + 'statements' => [ + 'break', + 'continue', + 'declare', + 'do', + 'for', + 'foreach', + 'if', + 'include', + 'include_once', + 'require', + 'require_once', + 'return', + 'switch', + 'throw', + 'try', + 'while', + 'yield', + ], + ], + 'braces' => [ + 'allow_single_line_closure' => false, + 'position_after_anonymous_constructs' => 'same', + 'position_after_control_structures' => 'same', + 'position_after_functions_and_oop_constructs' => 'same' + ], + 'cast_spaces' => ['space' => 'none'], + + // This fixer removes the blank line at class start, no way to disable that, so we disable the fixer :( + //'class_attributes_separation' => ['elements' => ['const', 'method', 'property']], + + 'combine_consecutive_issets' => true, + 'combine_consecutive_unsets' => true, + 'compact_nullable_typehint' => true, + 'concat_space' => ['spacing' => 'one'], + 'date_time_immutable' => true, + 'declare_equal_normalize' => ['space' => 'single'], + 'declare_strict_types' => true, + 'dir_constant' => true, + 'elseif' => true, + 'encoding' => true, + 'full_opening_tag' => true, + 'fully_qualified_strict_types' => true, + 'function_declaration' => [ + 'closure_function_spacing' => 'one' + ], + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'header_comment' => ['header' => $header, 'separate' => 'none'], + 'indentation_type' => true, + 'is_null' => true, + 'line_ending' => true, + 'list_syntax' => ['syntax' => 'short'], + 'logical_operators' => true, + 'lowercase_cast' => true, + 'constant_case' => ['case' => 'lower'], + 'lowercase_keywords' => true, + 'lowercase_static_reference' => true, + 'magic_constant_casing' => true, + 'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'], + 'modernize_types_casting' => true, + 'multiline_comment_opening_closing' => true, + 'multiline_whitespace_before_semicolons' => true, + 'new_with_braces' => false, + 'no_alias_functions' => true, + 'no_alternative_syntax' => true, + 'no_blank_lines_after_class_opening' => false, + 'no_blank_lines_after_phpdoc' => true, + 'no_blank_lines_before_namespace' => true, + 'no_closing_tag' => true, + 'no_empty_comment' => true, + 'no_empty_phpdoc' => true, + 'no_empty_statement' => true, + 'no_extra_blank_lines' => true, + 'no_homoglyph_names' => true, + 'no_leading_import_slash' => true, + 'no_leading_namespace_whitespace' => true, + 'no_mixed_echo_print' => ['use' => 'print'], + 'no_multiline_whitespace_around_double_arrow' => true, + 'no_null_property_initialization' => true, + 'no_php4_constructor' => true, + 'no_short_bool_cast' => true, + 'echo_tag_syntax' => ['format' => 'long'], + 'no_singleline_whitespace_before_semicolons' => true, + 'no_spaces_after_function_name' => true, + 'no_spaces_inside_parenthesis' => true, + 'no_superfluous_elseif' => true, + 'no_superfluous_phpdoc_tags' => true, + 'no_trailing_comma_in_list_call' => true, + 'no_trailing_comma_in_singleline_array' => true, + 'no_trailing_whitespace' => true, + 'no_trailing_whitespace_in_comment' => true, + 'no_unneeded_control_parentheses' => false, + 'no_unneeded_curly_braces' => false, + 'no_unneeded_final_method' => true, + 'no_unreachable_default_argument_value' => true, + 'no_unset_on_property' => true, + 'no_unused_imports' => true, + 'no_useless_else' => true, + 'no_useless_return' => true, + 'no_whitespace_before_comma_in_array' => true, + 'no_whitespace_in_blank_line' => true, + 'non_printable_character' => true, + 'normalize_index_brace' => true, + 'object_operator_without_whitespace' => true, + 'ordered_class_elements' => [ + 'order' => [ + 'use_trait', + 'constant_public', + 'constant_protected', + 'constant_private', + 'property_public_static', + 'property_protected_static', + 'property_private_static', + 'property_public', + 'property_protected', + 'property_private', + 'method_public_static', + 'construct', + 'destruct', + 'magic', + 'phpunit', + 'method_public', + 'method_protected', + 'method_private', + 'method_protected_static', + 'method_private_static', + ], + ], + 'ordered_imports' => [ + 'imports_order' => [ + PhpCsFixer\Fixer\Import\OrderedImportsFixer::IMPORT_TYPE_CLASS, + PhpCsFixer\Fixer\Import\OrderedImportsFixer::IMPORT_TYPE_CONST, + PhpCsFixer\Fixer\Import\OrderedImportsFixer::IMPORT_TYPE_FUNCTION, + ] + ], + 'phpdoc_add_missing_param_annotation' => true, + 'phpdoc_align' => true, + 'phpdoc_annotation_without_dot' => true, + 'phpdoc_indent' => true, + 'phpdoc_no_access' => true, + 'phpdoc_no_empty_return' => true, + 'phpdoc_no_package' => true, + 'phpdoc_order' => true, + 'phpdoc_return_self_reference' => true, + 'phpdoc_scalar' => true, + 'phpdoc_separation' => true, + 'phpdoc_single_line_var_spacing' => true, + 'phpdoc_to_comment' => true, + 'phpdoc_trim' => true, + 'phpdoc_trim_consecutive_blank_line_separation' => true, + 'phpdoc_types' => ['groups' => ['simple', 'meta']], + 'phpdoc_types_order' => true, + 'phpdoc_to_return_type' => true, + 'phpdoc_var_without_name' => true, + 'pow_to_exponentiation' => true, + 'protected_to_private' => true, + 'return_assignment' => true, + 'return_type_declaration' => ['space_before' => 'none'], + 'self_accessor' => false, + 'semicolon_after_instruction' => true, + 'set_type_to_cast' => true, + 'short_scalar_cast' => true, + 'simplified_null_return' => true, + 'single_blank_line_at_eof' => true, + 'single_import_per_statement' => true, + 'single_line_after_imports' => true, + 'single_quote' => true, + 'standardize_not_equals' => true, + 'ternary_to_null_coalescing' => true, + 'trailing_comma_in_multiline' => false, + 'trim_array_spaces' => true, + 'unary_operator_spaces' => true, + 'visibility_required' => [ + 'elements' => [ + 'const', + 'method', + 'property', + ], + ], + 'void_return' => true, + 'whitespace_after_comma_in_array' => true, + 'yoda_style' => false + ] + ) + ->setFinder( + PhpCsFixer\Finder::create() + ->files() + ->in(__DIR__ . '/build') + ->in(__DIR__ . '/src') + ->in(__DIR__ . '/tests') + ->notName('autoload.php') + ); diff --git a/vendor/phar-io/manifest/CHANGELOG.md b/vendor/phar-io/manifest/CHANGELOG.md new file mode 100644 index 00000000..f363b169 --- /dev/null +++ b/vendor/phar-io/manifest/CHANGELOG.md @@ -0,0 +1,45 @@ +# Changelog + +All notable changes to phar-io/manifest are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. + +## [2.0.4] - 03-03-2024 + +### Changed + +- Make `EMail` an optional attribute for author +- Stick with PHP 7.2 compatibilty +- Do not use implict nullable type (thanks @sebastianbergmann), this should make things work on PHP 8.4 + +## [2.0.3] - 20.07.2021 + +- Fixed PHP 7.2 / PHP 7.3 incompatibility introduced in previous release + +## [2.0.2] - 20.07.2021 + +- Fixed PHP 8.1 deprecation notice + +## [2.0.1] - 27.06.2020 + +This release now supports the use of PHP 7.2+ and ^8.0 + +## [2.0.0] - 10.05.2020 + +This release now requires PHP 7.2+ + +### Changed + +- Upgraded to phar-io/version 3.0 + - Version strings `v1.2.3` will now be converted to valid semantic version strings `1.2.3` + - Abreviated strings like `1.0` will get expaneded to `1.0.0` + +### Unreleased + +[Unreleased]: https://github.com/phar-io/manifest/compare/2.1.0...HEAD +[2.1.0]: https://github.com/phar-io/manifest/compare/2.0.3...2.1.0 +[2.0.3]: https://github.com/phar-io/manifest/compare/2.0.2...2.0.3 +[2.0.2]: https://github.com/phar-io/manifest/compare/2.0.1...2.0.2 +[2.0.1]: https://github.com/phar-io/manifest/compare/2.0.0...2.0.1 +[2.0.0]: https://github.com/phar-io/manifest/compare/1.0.1...2.0.0 +[1.0.3]: https://github.com/phar-io/manifest/compare/1.0.2...1.0.3 +[1.0.2]: https://github.com/phar-io/manifest/compare/1.0.1...1.0.2 +[1.0.1]: https://github.com/phar-io/manifest/compare/1.0.0...1.0.1 diff --git a/vendor/phar-io/manifest/LICENSE b/vendor/phar-io/manifest/LICENSE new file mode 100644 index 00000000..64690cf2 --- /dev/null +++ b/vendor/phar-io/manifest/LICENSE @@ -0,0 +1,31 @@ +Phar.io - Manifest + +Copyright (c) 2016-2019 Arne Blankerts , Sebastian Heuer , Sebastian Bergmann , and contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of Arne Blankerts nor the names of contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, +OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + diff --git a/vendor/phar-io/manifest/README.md b/vendor/phar-io/manifest/README.md new file mode 100644 index 00000000..fae2c9a7 --- /dev/null +++ b/vendor/phar-io/manifest/README.md @@ -0,0 +1,178 @@ +# Manifest + +Component for reading [phar.io](https://phar.io/) manifest information from a [PHP Archive (PHAR)](http://php.net/phar). + +## Installation + +You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): + + composer require phar-io/manifest + +If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency: + + composer require --dev phar-io/manifest + +## Usage Examples + +### Read from `manifest.xml` +```php +use PharIo\Manifest\ManifestLoader; +use PharIo\Manifest\ManifestSerializer; + +$manifest = ManifestLoader::fromFile('manifest.xml'); + +var_dump($manifest); + +echo (new ManifestSerializer)->serializeToString($manifest); +``` + +
+ Output + +```shell +object(PharIo\Manifest\Manifest)#14 (6) { + ["name":"PharIo\Manifest\Manifest":private]=> + object(PharIo\Manifest\ApplicationName)#10 (1) { + ["name":"PharIo\Manifest\ApplicationName":private]=> + string(12) "some/library" + } + ["version":"PharIo\Manifest\Manifest":private]=> + object(PharIo\Version\Version)#12 (5) { + ["originalVersionString":"PharIo\Version\Version":private]=> + string(5) "1.0.0" + ["major":"PharIo\Version\Version":private]=> + object(PharIo\Version\VersionNumber)#13 (1) { + ["value":"PharIo\Version\VersionNumber":private]=> + int(1) + } + ["minor":"PharIo\Version\Version":private]=> + object(PharIo\Version\VersionNumber)#23 (1) { + ["value":"PharIo\Version\VersionNumber":private]=> + int(0) + } + ["patch":"PharIo\Version\Version":private]=> + object(PharIo\Version\VersionNumber)#22 (1) { + ["value":"PharIo\Version\VersionNumber":private]=> + int(0) + } + ["preReleaseSuffix":"PharIo\Version\Version":private]=> + NULL + } + ["type":"PharIo\Manifest\Manifest":private]=> + object(PharIo\Manifest\Library)#6 (0) { + } + ["copyrightInformation":"PharIo\Manifest\Manifest":private]=> + object(PharIo\Manifest\CopyrightInformation)#19 (2) { + ["authors":"PharIo\Manifest\CopyrightInformation":private]=> + object(PharIo\Manifest\AuthorCollection)#9 (1) { + ["authors":"PharIo\Manifest\AuthorCollection":private]=> + array(1) { + [0]=> + object(PharIo\Manifest\Author)#15 (2) { + ["name":"PharIo\Manifest\Author":private]=> + string(13) "Reiner Zufall" + ["email":"PharIo\Manifest\Author":private]=> + object(PharIo\Manifest\Email)#16 (1) { + ["email":"PharIo\Manifest\Email":private]=> + string(16) "reiner@zufall.de" + } + } + } + } + ["license":"PharIo\Manifest\CopyrightInformation":private]=> + object(PharIo\Manifest\License)#11 (2) { + ["name":"PharIo\Manifest\License":private]=> + string(12) "BSD-3-Clause" + ["url":"PharIo\Manifest\License":private]=> + object(PharIo\Manifest\Url)#18 (1) { + ["url":"PharIo\Manifest\Url":private]=> + string(26) "https://domain.tld/LICENSE" + } + } + } + ["requirements":"PharIo\Manifest\Manifest":private]=> + object(PharIo\Manifest\RequirementCollection)#17 (1) { + ["requirements":"PharIo\Manifest\RequirementCollection":private]=> + array(1) { + [0]=> + object(PharIo\Manifest\PhpVersionRequirement)#20 (1) { + ["versionConstraint":"PharIo\Manifest\PhpVersionRequirement":private]=> + object(PharIo\Version\SpecificMajorAndMinorVersionConstraint)#24 (3) { + ["originalValue":"PharIo\Version\AbstractVersionConstraint":private]=> + string(3) "7.0" + ["major":"PharIo\Version\SpecificMajorAndMinorVersionConstraint":private]=> + int(7) + ["minor":"PharIo\Version\SpecificMajorAndMinorVersionConstraint":private]=> + int(0) + } + } + } + } + ["bundledComponents":"PharIo\Manifest\Manifest":private]=> + object(PharIo\Manifest\BundledComponentCollection)#8 (1) { + ["bundledComponents":"PharIo\Manifest\BundledComponentCollection":private]=> + array(0) { + } + } +} + + + + + + + + + + + +``` +
+ +### Create via API +```php +$bundled = new \PharIo\Manifest\BundledComponentCollection(); +$bundled->add( + new \PharIo\Manifest\BundledComponent('vendor/packageA', new \PharIo\Version\Version('1.2.3-dev') + ) +); + +$manifest = new PharIo\Manifest\Manifest( + new \PharIo\Manifest\ApplicationName('vendor/package'), + new \PharIo\Version\Version('1.0.0'), + new \PharIo\Manifest\Library(), + new \PharIo\Manifest\CopyrightInformation( + new \PharIo\Manifest\AuthorCollection(), + new \PharIo\Manifest\License( + 'BSD-3-Clause', + new \PharIo\Manifest\Url('https://spdx.org/licenses/BSD-3-Clause.html') + ) + ), + new \PharIo\Manifest\RequirementCollection(), + $bundled +); + +echo (new ManifestSerializer)->serializeToString($manifest); +``` + +
+ Output + +```xml + + + + + + + + + + + + + +``` + +
+ diff --git a/vendor/phar-io/manifest/composer.json b/vendor/phar-io/manifest/composer.json new file mode 100644 index 00000000..dc5fa458 --- /dev/null +++ b/vendor/phar-io/manifest/composer.json @@ -0,0 +1,43 @@ +{ + "name": "phar-io/manifest", + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "support": { + "issues": "https://github.com/phar-io/manifest/issues" + }, + "require": { + "php": "^7.2 || ^8.0", + "ext-dom": "*", + "ext-phar": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1" + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + } +} diff --git a/vendor/phar-io/manifest/composer.lock b/vendor/phar-io/manifest/composer.lock new file mode 100644 index 00000000..fe18e08b --- /dev/null +++ b/vendor/phar-io/manifest/composer.lock @@ -0,0 +1,76 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "279b3c4fe44357abd924fdcc0cfa5664", + "packages": [ + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^7.2 || ^8.0", + "ext-dom": "*", + "ext-phar": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/vendor/phar-io/manifest/manifest.xsd b/vendor/phar-io/manifest/manifest.xsd new file mode 100644 index 00000000..63e3f1cb --- /dev/null +++ b/vendor/phar-io/manifest/manifest.xsd @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/phar-io/manifest/src/ManifestDocumentMapper.php b/vendor/phar-io/manifest/src/ManifestDocumentMapper.php new file mode 100644 index 00000000..3da6403f --- /dev/null +++ b/vendor/phar-io/manifest/src/ManifestDocumentMapper.php @@ -0,0 +1,151 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +use PharIo\Version\Exception as VersionException; +use PharIo\Version\Version; +use PharIo\Version\VersionConstraintParser; +use Throwable; +use function sprintf; + +class ManifestDocumentMapper { + public function map(ManifestDocument $document): Manifest { + try { + $contains = $document->getContainsElement(); + $type = $this->mapType($contains); + $copyright = $this->mapCopyright($document->getCopyrightElement()); + $requirements = $this->mapRequirements($document->getRequiresElement()); + $bundledComponents = $this->mapBundledComponents($document); + + return new Manifest( + new ApplicationName($contains->getName()), + new Version($contains->getVersion()), + $type, + $copyright, + $requirements, + $bundledComponents + ); + } catch (Throwable $e) { + throw new ManifestDocumentMapperException($e->getMessage(), (int)$e->getCode(), $e); + } + } + + private function mapType(ContainsElement $contains): Type { + switch ($contains->getType()) { + case 'application': + return Type::application(); + case 'library': + return Type::library(); + case 'extension': + return $this->mapExtension($contains->getExtensionElement()); + } + + throw new ManifestDocumentMapperException( + sprintf('Unsupported type %s', $contains->getType()) + ); + } + + private function mapCopyright(CopyrightElement $copyright): CopyrightInformation { + $authors = new AuthorCollection(); + + foreach ($copyright->getAuthorElements() as $authorElement) { + $authors->add( + new Author( + $authorElement->getName(), + $authorElement->hasEMail() ? new Email($authorElement->getEmail()) : null + ) + ); + } + + $licenseElement = $copyright->getLicenseElement(); + $license = new License( + $licenseElement->getType(), + new Url($licenseElement->getUrl()) + ); + + return new CopyrightInformation( + $authors, + $license + ); + } + + private function mapRequirements(RequiresElement $requires): RequirementCollection { + $collection = new RequirementCollection(); + $phpElement = $requires->getPHPElement(); + $parser = new VersionConstraintParser; + + try { + $versionConstraint = $parser->parse($phpElement->getVersion()); + } catch (VersionException $e) { + throw new ManifestDocumentMapperException( + sprintf('Unsupported version constraint - %s', $e->getMessage()), + (int)$e->getCode(), + $e + ); + } + + $collection->add( + new PhpVersionRequirement( + $versionConstraint + ) + ); + + if (!$phpElement->hasExtElements()) { + return $collection; + } + + foreach ($phpElement->getExtElements() as $extElement) { + $collection->add( + new PhpExtensionRequirement($extElement->getName()) + ); + } + + return $collection; + } + + private function mapBundledComponents(ManifestDocument $document): BundledComponentCollection { + $collection = new BundledComponentCollection(); + + if (!$document->hasBundlesElement()) { + return $collection; + } + + foreach ($document->getBundlesElement()->getComponentElements() as $componentElement) { + $collection->add( + new BundledComponent( + $componentElement->getName(), + new Version( + $componentElement->getVersion() + ) + ) + ); + } + + return $collection; + } + + private function mapExtension(ExtensionElement $extension): Extension { + try { + $versionConstraint = (new VersionConstraintParser)->parse($extension->getCompatible()); + + return Type::extension( + new ApplicationName($extension->getFor()), + $versionConstraint + ); + } catch (VersionException $e) { + throw new ManifestDocumentMapperException( + sprintf('Unsupported version constraint - %s', $e->getMessage()), + (int)$e->getCode(), + $e + ); + } + } +} diff --git a/vendor/phar-io/manifest/src/ManifestLoader.php b/vendor/phar-io/manifest/src/ManifestLoader.php new file mode 100644 index 00000000..f467d2d3 --- /dev/null +++ b/vendor/phar-io/manifest/src/ManifestLoader.php @@ -0,0 +1,47 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +use function sprintf; + +class ManifestLoader { + public static function fromFile(string $filename): Manifest { + try { + return (new ManifestDocumentMapper())->map( + ManifestDocument::fromFile($filename) + ); + } catch (Exception $e) { + throw new ManifestLoaderException( + sprintf('Loading %s failed.', $filename), + (int)$e->getCode(), + $e + ); + } + } + + public static function fromPhar(string $filename): Manifest { + return self::fromFile('phar://' . $filename . '/manifest.xml'); + } + + public static function fromString(string $manifest): Manifest { + try { + return (new ManifestDocumentMapper())->map( + ManifestDocument::fromString($manifest) + ); + } catch (Exception $e) { + throw new ManifestLoaderException( + 'Processing string failed', + (int)$e->getCode(), + $e + ); + } + } +} diff --git a/vendor/phar-io/manifest/src/ManifestSerializer.php b/vendor/phar-io/manifest/src/ManifestSerializer.php new file mode 100644 index 00000000..48b8efdd --- /dev/null +++ b/vendor/phar-io/manifest/src/ManifestSerializer.php @@ -0,0 +1,172 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +use PharIo\Version\AnyVersionConstraint; +use PharIo\Version\Version; +use PharIo\Version\VersionConstraint; +use XMLWriter; +use function count; +use function file_put_contents; +use function str_repeat; + +/** @psalm-suppress MissingConstructor */ +class ManifestSerializer { + /** @var XMLWriter */ + private $xmlWriter; + + public function serializeToFile(Manifest $manifest, string $filename): void { + file_put_contents( + $filename, + $this->serializeToString($manifest) + ); + } + + public function serializeToString(Manifest $manifest): string { + $this->startDocument(); + + $this->addContains($manifest->getName(), $manifest->getVersion(), $manifest->getType()); + $this->addCopyright($manifest->getCopyrightInformation()); + $this->addRequirements($manifest->getRequirements()); + $this->addBundles($manifest->getBundledComponents()); + + return $this->finishDocument(); + } + + private function startDocument(): void { + $xmlWriter = new XMLWriter(); + $xmlWriter->openMemory(); + $xmlWriter->setIndent(true); + $xmlWriter->setIndentString(str_repeat(' ', 4)); + $xmlWriter->startDocument('1.0', 'UTF-8'); + $xmlWriter->startElement('phar'); + $xmlWriter->writeAttribute('xmlns', 'https://phar.io/xml/manifest/1.0'); + + $this->xmlWriter = $xmlWriter; + } + + private function finishDocument(): string { + $this->xmlWriter->endElement(); + $this->xmlWriter->endDocument(); + + return $this->xmlWriter->outputMemory(); + } + + private function addContains(ApplicationName $name, Version $version, Type $type): void { + $this->xmlWriter->startElement('contains'); + $this->xmlWriter->writeAttribute('name', $name->asString()); + $this->xmlWriter->writeAttribute('version', $version->getVersionString()); + + switch (true) { + case $type->isApplication(): { + $this->xmlWriter->writeAttribute('type', 'application'); + + break; + } + + case $type->isLibrary(): { + $this->xmlWriter->writeAttribute('type', 'library'); + + break; + } + + case $type->isExtension(): { + $this->xmlWriter->writeAttribute('type', 'extension'); + /* @var $type Extension */ + $this->addExtension( + $type->getApplicationName(), + $type->getVersionConstraint() + ); + + break; + } + + default: { + $this->xmlWriter->writeAttribute('type', 'custom'); + } + } + + $this->xmlWriter->endElement(); + } + + private function addCopyright(CopyrightInformation $copyrightInformation): void { + $this->xmlWriter->startElement('copyright'); + + foreach ($copyrightInformation->getAuthors() as $author) { + $this->xmlWriter->startElement('author'); + $this->xmlWriter->writeAttribute('name', $author->getName()); + $this->xmlWriter->writeAttribute('email', $author->getEmail()->asString()); + $this->xmlWriter->endElement(); + } + + $license = $copyrightInformation->getLicense(); + + $this->xmlWriter->startElement('license'); + $this->xmlWriter->writeAttribute('type', $license->getName()); + $this->xmlWriter->writeAttribute('url', $license->getUrl()->asString()); + $this->xmlWriter->endElement(); + + $this->xmlWriter->endElement(); + } + + private function addRequirements(RequirementCollection $requirementCollection): void { + $phpRequirement = new AnyVersionConstraint(); + $extensions = []; + + foreach ($requirementCollection as $requirement) { + if ($requirement instanceof PhpVersionRequirement) { + $phpRequirement = $requirement->getVersionConstraint(); + + continue; + } + + if ($requirement instanceof PhpExtensionRequirement) { + $extensions[] = $requirement->asString(); + } + } + + $this->xmlWriter->startElement('requires'); + $this->xmlWriter->startElement('php'); + $this->xmlWriter->writeAttribute('version', $phpRequirement->asString()); + + foreach ($extensions as $extension) { + $this->xmlWriter->startElement('ext'); + $this->xmlWriter->writeAttribute('name', $extension); + $this->xmlWriter->endElement(); + } + + $this->xmlWriter->endElement(); + $this->xmlWriter->endElement(); + } + + private function addBundles(BundledComponentCollection $bundledComponentCollection): void { + if (count($bundledComponentCollection) === 0) { + return; + } + $this->xmlWriter->startElement('bundles'); + + foreach ($bundledComponentCollection as $bundledComponent) { + $this->xmlWriter->startElement('component'); + $this->xmlWriter->writeAttribute('name', $bundledComponent->getName()); + $this->xmlWriter->writeAttribute('version', $bundledComponent->getVersion()->getVersionString()); + $this->xmlWriter->endElement(); + } + + $this->xmlWriter->endElement(); + } + + private function addExtension(ApplicationName $applicationName, VersionConstraint $versionConstraint): void { + $this->xmlWriter->startElement('extension'); + $this->xmlWriter->writeAttribute('for', $applicationName->asString()); + $this->xmlWriter->writeAttribute('compatible', $versionConstraint->asString()); + $this->xmlWriter->endElement(); + } +} diff --git a/vendor/phar-io/manifest/src/exceptions/ElementCollectionException.php b/vendor/phar-io/manifest/src/exceptions/ElementCollectionException.php new file mode 100644 index 00000000..7528afc8 --- /dev/null +++ b/vendor/phar-io/manifest/src/exceptions/ElementCollectionException.php @@ -0,0 +1,16 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +use InvalidArgumentException; + +class ElementCollectionException extends InvalidArgumentException implements Exception { +} diff --git a/vendor/phar-io/manifest/src/exceptions/Exception.php b/vendor/phar-io/manifest/src/exceptions/Exception.php new file mode 100644 index 00000000..0c135d3c --- /dev/null +++ b/vendor/phar-io/manifest/src/exceptions/Exception.php @@ -0,0 +1,16 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +use Throwable; + +interface Exception extends Throwable { +} diff --git a/vendor/phar-io/manifest/src/exceptions/InvalidApplicationNameException.php b/vendor/phar-io/manifest/src/exceptions/InvalidApplicationNameException.php new file mode 100644 index 00000000..ecfe5142 --- /dev/null +++ b/vendor/phar-io/manifest/src/exceptions/InvalidApplicationNameException.php @@ -0,0 +1,17 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +use InvalidArgumentException; + +class InvalidApplicationNameException extends InvalidArgumentException implements Exception { + public const InvalidFormat = 2; +} diff --git a/vendor/phar-io/manifest/src/exceptions/InvalidEmailException.php b/vendor/phar-io/manifest/src/exceptions/InvalidEmailException.php new file mode 100644 index 00000000..24240551 --- /dev/null +++ b/vendor/phar-io/manifest/src/exceptions/InvalidEmailException.php @@ -0,0 +1,16 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +use InvalidArgumentException; + +class InvalidEmailException extends InvalidArgumentException implements Exception { +} diff --git a/vendor/phar-io/manifest/src/exceptions/InvalidUrlException.php b/vendor/phar-io/manifest/src/exceptions/InvalidUrlException.php new file mode 100644 index 00000000..c8b192b1 --- /dev/null +++ b/vendor/phar-io/manifest/src/exceptions/InvalidUrlException.php @@ -0,0 +1,16 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +use InvalidArgumentException; + +class InvalidUrlException extends InvalidArgumentException implements Exception { +} diff --git a/vendor/phar-io/manifest/src/exceptions/ManifestDocumentException.php b/vendor/phar-io/manifest/src/exceptions/ManifestDocumentException.php new file mode 100644 index 00000000..0a158e6e --- /dev/null +++ b/vendor/phar-io/manifest/src/exceptions/ManifestDocumentException.php @@ -0,0 +1,16 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +use RuntimeException; + +class ManifestDocumentException extends RuntimeException implements Exception { +} diff --git a/vendor/phar-io/manifest/src/exceptions/ManifestDocumentLoadingException.php b/vendor/phar-io/manifest/src/exceptions/ManifestDocumentLoadingException.php new file mode 100644 index 00000000..816af120 --- /dev/null +++ b/vendor/phar-io/manifest/src/exceptions/ManifestDocumentLoadingException.php @@ -0,0 +1,47 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +use LibXMLError; +use function sprintf; + +class ManifestDocumentLoadingException extends \Exception implements Exception { + /** @var LibXMLError[] */ + private $libxmlErrors; + + /** + * ManifestDocumentLoadingException constructor. + * + * @param LibXMLError[] $libxmlErrors + */ + public function __construct(array $libxmlErrors) { + $this->libxmlErrors = $libxmlErrors; + $first = $this->libxmlErrors[0]; + + parent::__construct( + sprintf( + '%s (Line: %d / Column: %d / File: %s)', + $first->message, + $first->line, + $first->column, + $first->file + ), + $first->code + ); + } + + /** + * @return LibXMLError[] + */ + public function getLibxmlErrors(): array { + return $this->libxmlErrors; + } +} diff --git a/vendor/phar-io/manifest/src/exceptions/ManifestDocumentMapperException.php b/vendor/phar-io/manifest/src/exceptions/ManifestDocumentMapperException.php new file mode 100644 index 00000000..0d1a5f5a --- /dev/null +++ b/vendor/phar-io/manifest/src/exceptions/ManifestDocumentMapperException.php @@ -0,0 +1,16 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +use RuntimeException; + +class ManifestDocumentMapperException extends RuntimeException implements Exception { +} diff --git a/vendor/phar-io/manifest/src/exceptions/ManifestElementException.php b/vendor/phar-io/manifest/src/exceptions/ManifestElementException.php new file mode 100644 index 00000000..46f82e32 --- /dev/null +++ b/vendor/phar-io/manifest/src/exceptions/ManifestElementException.php @@ -0,0 +1,16 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +use RuntimeException; + +class ManifestElementException extends RuntimeException implements Exception { +} diff --git a/vendor/phar-io/manifest/src/exceptions/ManifestLoaderException.php b/vendor/phar-io/manifest/src/exceptions/ManifestLoaderException.php new file mode 100644 index 00000000..d00ed190 --- /dev/null +++ b/vendor/phar-io/manifest/src/exceptions/ManifestLoaderException.php @@ -0,0 +1,14 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +class ManifestLoaderException extends \Exception implements Exception { +} diff --git a/vendor/phar-io/manifest/src/exceptions/NoEmailAddressException.php b/vendor/phar-io/manifest/src/exceptions/NoEmailAddressException.php new file mode 100644 index 00000000..27913126 --- /dev/null +++ b/vendor/phar-io/manifest/src/exceptions/NoEmailAddressException.php @@ -0,0 +1,16 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +use InvalidArgumentException; + +class NoEmailAddressException extends InvalidArgumentException implements Exception { +} diff --git a/vendor/phar-io/manifest/src/values/Application.php b/vendor/phar-io/manifest/src/values/Application.php new file mode 100644 index 00000000..11a44d9c --- /dev/null +++ b/vendor/phar-io/manifest/src/values/Application.php @@ -0,0 +1,17 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +class Application extends Type { + public function isApplication(): bool { + return true; + } +} diff --git a/vendor/phar-io/manifest/src/values/ApplicationName.php b/vendor/phar-io/manifest/src/values/ApplicationName.php new file mode 100644 index 00000000..1a0ad1e2 --- /dev/null +++ b/vendor/phar-io/manifest/src/values/ApplicationName.php @@ -0,0 +1,41 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +use function preg_match; +use function sprintf; + +class ApplicationName { + /** @var string */ + private $name; + + public function __construct(string $name) { + $this->ensureValidFormat($name); + $this->name = $name; + } + + public function asString(): string { + return $this->name; + } + + public function isEqual(ApplicationName $name): bool { + return $this->name === $name->name; + } + + private function ensureValidFormat(string $name): void { + if (!preg_match('#\w/\w#', $name)) { + throw new InvalidApplicationNameException( + sprintf('Format of name "%s" is not valid - expected: vendor/packagename', $name), + InvalidApplicationNameException::InvalidFormat + ); + } + } +} diff --git a/vendor/phar-io/manifest/src/values/Author.php b/vendor/phar-io/manifest/src/values/Author.php new file mode 100644 index 00000000..7b243aac --- /dev/null +++ b/vendor/phar-io/manifest/src/values/Author.php @@ -0,0 +1,57 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +use function sprintf; + +class Author { + /** @var string */ + private $name; + + /** @var null|Email */ + private $email; + + public function __construct(string $name, ?Email $email = null) { + $this->name = $name; + $this->email = $email; + } + + public function asString(): string { + if (!$this->hasEmail()) { + return $this->name; + } + + return sprintf( + '%s <%s>', + $this->name, + $this->email->asString() + ); + } + + public function getName(): string { + return $this->name; + } + + /** + * @psalm-assert-if-true Email $this->email + */ + public function hasEmail(): bool { + return $this->email !== null; + } + + public function getEmail(): Email { + if (!$this->hasEmail()) { + throw new NoEmailAddressException(); + } + + return $this->email; + } +} diff --git a/vendor/phar-io/manifest/src/values/AuthorCollection.php b/vendor/phar-io/manifest/src/values/AuthorCollection.php new file mode 100644 index 00000000..549876da --- /dev/null +++ b/vendor/phar-io/manifest/src/values/AuthorCollection.php @@ -0,0 +1,40 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +use Countable; +use IteratorAggregate; +use function count; + +/** @template-implements IteratorAggregate */ +class AuthorCollection implements Countable, IteratorAggregate { + /** @var Author[] */ + private $authors = []; + + public function add(Author $author): void { + $this->authors[] = $author; + } + + /** + * @return Author[] + */ + public function getAuthors(): array { + return $this->authors; + } + + public function count(): int { + return count($this->authors); + } + + public function getIterator(): AuthorCollectionIterator { + return new AuthorCollectionIterator($this); + } +} diff --git a/vendor/phar-io/manifest/src/values/AuthorCollectionIterator.php b/vendor/phar-io/manifest/src/values/AuthorCollectionIterator.php new file mode 100644 index 00000000..36fee9f7 --- /dev/null +++ b/vendor/phar-io/manifest/src/values/AuthorCollectionIterator.php @@ -0,0 +1,47 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +use Iterator; +use function count; + +/** @template-implements Iterator */ +class AuthorCollectionIterator implements Iterator { + /** @var Author[] */ + private $authors; + + /** @var int */ + private $position = 0; + + public function __construct(AuthorCollection $authors) { + $this->authors = $authors->getAuthors(); + } + + public function rewind(): void { + $this->position = 0; + } + + public function valid(): bool { + return $this->position < count($this->authors); + } + + public function key(): int { + return $this->position; + } + + public function current(): Author { + return $this->authors[$this->position]; + } + + public function next(): void { + $this->position++; + } +} diff --git a/vendor/phar-io/manifest/src/values/BundledComponent.php b/vendor/phar-io/manifest/src/values/BundledComponent.php new file mode 100644 index 00000000..58170368 --- /dev/null +++ b/vendor/phar-io/manifest/src/values/BundledComponent.php @@ -0,0 +1,34 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +use PharIo\Version\Version; + +class BundledComponent { + /** @var string */ + private $name; + + /** @var Version */ + private $version; + + public function __construct(string $name, Version $version) { + $this->name = $name; + $this->version = $version; + } + + public function getName(): string { + return $this->name; + } + + public function getVersion(): Version { + return $this->version; + } +} diff --git a/vendor/phar-io/manifest/src/values/BundledComponentCollection.php b/vendor/phar-io/manifest/src/values/BundledComponentCollection.php new file mode 100644 index 00000000..28aaa06c --- /dev/null +++ b/vendor/phar-io/manifest/src/values/BundledComponentCollection.php @@ -0,0 +1,40 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +use Countable; +use IteratorAggregate; +use function count; + +/** @template-implements IteratorAggregate */ +class BundledComponentCollection implements Countable, IteratorAggregate { + /** @var BundledComponent[] */ + private $bundledComponents = []; + + public function add(BundledComponent $bundledComponent): void { + $this->bundledComponents[] = $bundledComponent; + } + + /** + * @return BundledComponent[] + */ + public function getBundledComponents(): array { + return $this->bundledComponents; + } + + public function count(): int { + return count($this->bundledComponents); + } + + public function getIterator(): BundledComponentCollectionIterator { + return new BundledComponentCollectionIterator($this); + } +} diff --git a/vendor/phar-io/manifest/src/values/BundledComponentCollectionIterator.php b/vendor/phar-io/manifest/src/values/BundledComponentCollectionIterator.php new file mode 100644 index 00000000..5c72817d --- /dev/null +++ b/vendor/phar-io/manifest/src/values/BundledComponentCollectionIterator.php @@ -0,0 +1,47 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +use Iterator; +use function count; + +/** @template-implements Iterator */ +class BundledComponentCollectionIterator implements Iterator { + /** @var BundledComponent[] */ + private $bundledComponents; + + /** @var int */ + private $position = 0; + + public function __construct(BundledComponentCollection $bundledComponents) { + $this->bundledComponents = $bundledComponents->getBundledComponents(); + } + + public function rewind(): void { + $this->position = 0; + } + + public function valid(): bool { + return $this->position < count($this->bundledComponents); + } + + public function key(): int { + return $this->position; + } + + public function current(): BundledComponent { + return $this->bundledComponents[$this->position]; + } + + public function next(): void { + $this->position++; + } +} diff --git a/vendor/phar-io/manifest/src/values/CopyrightInformation.php b/vendor/phar-io/manifest/src/values/CopyrightInformation.php new file mode 100644 index 00000000..b4468ed7 --- /dev/null +++ b/vendor/phar-io/manifest/src/values/CopyrightInformation.php @@ -0,0 +1,32 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +class CopyrightInformation { + /** @var AuthorCollection */ + private $authors; + + /** @var License */ + private $license; + + public function __construct(AuthorCollection $authors, License $license) { + $this->authors = $authors; + $this->license = $license; + } + + public function getAuthors(): AuthorCollection { + return $this->authors; + } + + public function getLicense(): License { + return $this->license; + } +} diff --git a/vendor/phar-io/manifest/src/values/Email.php b/vendor/phar-io/manifest/src/values/Email.php new file mode 100644 index 00000000..dbaff84a --- /dev/null +++ b/vendor/phar-io/manifest/src/values/Email.php @@ -0,0 +1,35 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +use const FILTER_VALIDATE_EMAIL; +use function filter_var; + +class Email { + /** @var string */ + private $email; + + public function __construct(string $email) { + $this->ensureEmailIsValid($email); + + $this->email = $email; + } + + public function asString(): string { + return $this->email; + } + + private function ensureEmailIsValid(string $url): void { + if (filter_var($url, FILTER_VALIDATE_EMAIL) === false) { + throw new InvalidEmailException; + } + } +} diff --git a/vendor/phar-io/manifest/src/values/Extension.php b/vendor/phar-io/manifest/src/values/Extension.php new file mode 100644 index 00000000..abcd2f89 --- /dev/null +++ b/vendor/phar-io/manifest/src/values/Extension.php @@ -0,0 +1,47 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +use PharIo\Version\Version; +use PharIo\Version\VersionConstraint; + +class Extension extends Type { + /** @var ApplicationName */ + private $application; + + /** @var VersionConstraint */ + private $versionConstraint; + + public function __construct(ApplicationName $application, VersionConstraint $versionConstraint) { + $this->application = $application; + $this->versionConstraint = $versionConstraint; + } + + public function getApplicationName(): ApplicationName { + return $this->application; + } + + public function getVersionConstraint(): VersionConstraint { + return $this->versionConstraint; + } + + public function isExtension(): bool { + return true; + } + + public function isExtensionFor(ApplicationName $name): bool { + return $this->application->isEqual($name); + } + + public function isCompatibleWith(ApplicationName $name, Version $version): bool { + return $this->isExtensionFor($name) && $this->versionConstraint->complies($version); + } +} diff --git a/vendor/phar-io/manifest/src/values/Library.php b/vendor/phar-io/manifest/src/values/Library.php new file mode 100644 index 00000000..97c292dc --- /dev/null +++ b/vendor/phar-io/manifest/src/values/Library.php @@ -0,0 +1,17 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +class Library extends Type { + public function isLibrary(): bool { + return true; + } +} diff --git a/vendor/phar-io/manifest/src/values/License.php b/vendor/phar-io/manifest/src/values/License.php new file mode 100644 index 00000000..c2d94299 --- /dev/null +++ b/vendor/phar-io/manifest/src/values/License.php @@ -0,0 +1,32 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +class License { + /** @var string */ + private $name; + + /** @var Url */ + private $url; + + public function __construct(string $name, Url $url) { + $this->name = $name; + $this->url = $url; + } + + public function getName(): string { + return $this->name; + } + + public function getUrl(): Url { + return $this->url; + } +} diff --git a/vendor/phar-io/manifest/src/values/Manifest.php b/vendor/phar-io/manifest/src/values/Manifest.php new file mode 100644 index 00000000..36466820 --- /dev/null +++ b/vendor/phar-io/manifest/src/values/Manifest.php @@ -0,0 +1,93 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +use PharIo\Version\Version; + +class Manifest { + /** @var ApplicationName */ + private $name; + + /** @var Version */ + private $version; + + /** @var Type */ + private $type; + + /** @var CopyrightInformation */ + private $copyrightInformation; + + /** @var RequirementCollection */ + private $requirements; + + /** @var BundledComponentCollection */ + private $bundledComponents; + + public function __construct(ApplicationName $name, Version $version, Type $type, CopyrightInformation $copyrightInformation, RequirementCollection $requirements, BundledComponentCollection $bundledComponents) { + $this->name = $name; + $this->version = $version; + $this->type = $type; + $this->copyrightInformation = $copyrightInformation; + $this->requirements = $requirements; + $this->bundledComponents = $bundledComponents; + } + + public function getName(): ApplicationName { + return $this->name; + } + + public function getVersion(): Version { + return $this->version; + } + + public function getType(): Type { + return $this->type; + } + + public function getCopyrightInformation(): CopyrightInformation { + return $this->copyrightInformation; + } + + public function getRequirements(): RequirementCollection { + return $this->requirements; + } + + public function getBundledComponents(): BundledComponentCollection { + return $this->bundledComponents; + } + + public function isApplication(): bool { + return $this->type->isApplication(); + } + + public function isLibrary(): bool { + return $this->type->isLibrary(); + } + + public function isExtension(): bool { + return $this->type->isExtension(); + } + + public function isExtensionFor(ApplicationName $application, ?Version $version = null): bool { + if (!$this->isExtension()) { + return false; + } + + /** @var Extension $type */ + $type = $this->type; + + if ($version !== null) { + return $type->isCompatibleWith($application, $version); + } + + return $type->isExtensionFor($application); + } +} diff --git a/vendor/phar-io/manifest/src/values/PhpExtensionRequirement.php b/vendor/phar-io/manifest/src/values/PhpExtensionRequirement.php new file mode 100644 index 00000000..f81bd259 --- /dev/null +++ b/vendor/phar-io/manifest/src/values/PhpExtensionRequirement.php @@ -0,0 +1,24 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +class PhpExtensionRequirement implements Requirement { + /** @var string */ + private $extension; + + public function __construct(string $extension) { + $this->extension = $extension; + } + + public function asString(): string { + return $this->extension; + } +} diff --git a/vendor/phar-io/manifest/src/values/PhpVersionRequirement.php b/vendor/phar-io/manifest/src/values/PhpVersionRequirement.php new file mode 100644 index 00000000..fb30c3b8 --- /dev/null +++ b/vendor/phar-io/manifest/src/values/PhpVersionRequirement.php @@ -0,0 +1,26 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +use PharIo\Version\VersionConstraint; + +class PhpVersionRequirement implements Requirement { + /** @var VersionConstraint */ + private $versionConstraint; + + public function __construct(VersionConstraint $versionConstraint) { + $this->versionConstraint = $versionConstraint; + } + + public function getVersionConstraint(): VersionConstraint { + return $this->versionConstraint; + } +} diff --git a/vendor/phar-io/manifest/src/values/Requirement.php b/vendor/phar-io/manifest/src/values/Requirement.php new file mode 100644 index 00000000..d4b46401 --- /dev/null +++ b/vendor/phar-io/manifest/src/values/Requirement.php @@ -0,0 +1,14 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +interface Requirement { +} diff --git a/vendor/phar-io/manifest/src/values/RequirementCollection.php b/vendor/phar-io/manifest/src/values/RequirementCollection.php new file mode 100644 index 00000000..e4fe2a11 --- /dev/null +++ b/vendor/phar-io/manifest/src/values/RequirementCollection.php @@ -0,0 +1,40 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +use Countable; +use IteratorAggregate; +use function count; + +/** @template-implements IteratorAggregate */ +class RequirementCollection implements Countable, IteratorAggregate { + /** @var Requirement[] */ + private $requirements = []; + + public function add(Requirement $requirement): void { + $this->requirements[] = $requirement; + } + + /** + * @return Requirement[] + */ + public function getRequirements(): array { + return $this->requirements; + } + + public function count(): int { + return count($this->requirements); + } + + public function getIterator(): RequirementCollectionIterator { + return new RequirementCollectionIterator($this); + } +} diff --git a/vendor/phar-io/manifest/src/values/RequirementCollectionIterator.php b/vendor/phar-io/manifest/src/values/RequirementCollectionIterator.php new file mode 100644 index 00000000..a587468c --- /dev/null +++ b/vendor/phar-io/manifest/src/values/RequirementCollectionIterator.php @@ -0,0 +1,47 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +use Iterator; +use function count; + +/** @template-implements Iterator */ +class RequirementCollectionIterator implements Iterator { + /** @var Requirement[] */ + private $requirements; + + /** @var int */ + private $position = 0; + + public function __construct(RequirementCollection $requirements) { + $this->requirements = $requirements->getRequirements(); + } + + public function rewind(): void { + $this->position = 0; + } + + public function valid(): bool { + return $this->position < count($this->requirements); + } + + public function key(): int { + return $this->position; + } + + public function current(): Requirement { + return $this->requirements[$this->position]; + } + + public function next(): void { + $this->position++; + } +} diff --git a/vendor/phar-io/manifest/src/values/Type.php b/vendor/phar-io/manifest/src/values/Type.php new file mode 100644 index 00000000..231e7fd9 --- /dev/null +++ b/vendor/phar-io/manifest/src/values/Type.php @@ -0,0 +1,42 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +use PharIo\Version\VersionConstraint; + +abstract class Type { + public static function application(): Application { + return new Application; + } + + public static function library(): Library { + return new Library; + } + + public static function extension(ApplicationName $application, VersionConstraint $versionConstraint): Extension { + return new Extension($application, $versionConstraint); + } + + /** @psalm-assert-if-true Application $this */ + public function isApplication(): bool { + return false; + } + + /** @psalm-assert-if-true Library $this */ + public function isLibrary(): bool { + return false; + } + + /** @psalm-assert-if-true Extension $this */ + public function isExtension(): bool { + return false; + } +} diff --git a/vendor/phar-io/manifest/src/values/Url.php b/vendor/phar-io/manifest/src/values/Url.php new file mode 100644 index 00000000..98061554 --- /dev/null +++ b/vendor/phar-io/manifest/src/values/Url.php @@ -0,0 +1,38 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +use const FILTER_VALIDATE_URL; +use function filter_var; + +class Url { + /** @var string */ + private $url; + + public function __construct(string $url) { + $this->ensureUrlIsValid($url); + + $this->url = $url; + } + + public function asString(): string { + return $this->url; + } + + /** + * @throws InvalidUrlException + */ + private function ensureUrlIsValid(string $url): void { + if (filter_var($url, FILTER_VALIDATE_URL) === false) { + throw new InvalidUrlException; + } + } +} diff --git a/vendor/phar-io/manifest/src/xml/AuthorElement.php b/vendor/phar-io/manifest/src/xml/AuthorElement.php new file mode 100644 index 00000000..b33eb3ca --- /dev/null +++ b/vendor/phar-io/manifest/src/xml/AuthorElement.php @@ -0,0 +1,25 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +class AuthorElement extends ManifestElement { + public function getName(): string { + return $this->getAttributeValue('name'); + } + + public function getEmail(): string { + return $this->getAttributeValue('email'); + } + + public function hasEMail(): bool { + return $this->hasAttribute('email'); + } +} diff --git a/vendor/phar-io/manifest/src/xml/AuthorElementCollection.php b/vendor/phar-io/manifest/src/xml/AuthorElementCollection.php new file mode 100644 index 00000000..0a2a2a38 --- /dev/null +++ b/vendor/phar-io/manifest/src/xml/AuthorElementCollection.php @@ -0,0 +1,19 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +class AuthorElementCollection extends ElementCollection { + public function current(): AuthorElement { + return new AuthorElement( + $this->getCurrentElement() + ); + } +} diff --git a/vendor/phar-io/manifest/src/xml/BundlesElement.php b/vendor/phar-io/manifest/src/xml/BundlesElement.php new file mode 100644 index 00000000..ef721a66 --- /dev/null +++ b/vendor/phar-io/manifest/src/xml/BundlesElement.php @@ -0,0 +1,19 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +class BundlesElement extends ManifestElement { + public function getComponentElements(): ComponentElementCollection { + return new ComponentElementCollection( + $this->getChildrenByName('component') + ); + } +} diff --git a/vendor/phar-io/manifest/src/xml/ComponentElement.php b/vendor/phar-io/manifest/src/xml/ComponentElement.php new file mode 100644 index 00000000..84373c47 --- /dev/null +++ b/vendor/phar-io/manifest/src/xml/ComponentElement.php @@ -0,0 +1,21 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +class ComponentElement extends ManifestElement { + public function getName(): string { + return $this->getAttributeValue('name'); + } + + public function getVersion(): string { + return $this->getAttributeValue('version'); + } +} diff --git a/vendor/phar-io/manifest/src/xml/ComponentElementCollection.php b/vendor/phar-io/manifest/src/xml/ComponentElementCollection.php new file mode 100644 index 00000000..cd9ad5dd --- /dev/null +++ b/vendor/phar-io/manifest/src/xml/ComponentElementCollection.php @@ -0,0 +1,19 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +class ComponentElementCollection extends ElementCollection { + public function current(): ComponentElement { + return new ComponentElement( + $this->getCurrentElement() + ); + } +} diff --git a/vendor/phar-io/manifest/src/xml/ContainsElement.php b/vendor/phar-io/manifest/src/xml/ContainsElement.php new file mode 100644 index 00000000..55a9c605 --- /dev/null +++ b/vendor/phar-io/manifest/src/xml/ContainsElement.php @@ -0,0 +1,31 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +class ContainsElement extends ManifestElement { + public function getName(): string { + return $this->getAttributeValue('name'); + } + + public function getVersion(): string { + return $this->getAttributeValue('version'); + } + + public function getType(): string { + return $this->getAttributeValue('type'); + } + + public function getExtensionElement(): ExtensionElement { + return new ExtensionElement( + $this->getChildByName('extension') + ); + } +} diff --git a/vendor/phar-io/manifest/src/xml/CopyrightElement.php b/vendor/phar-io/manifest/src/xml/CopyrightElement.php new file mode 100644 index 00000000..c11415a5 --- /dev/null +++ b/vendor/phar-io/manifest/src/xml/CopyrightElement.php @@ -0,0 +1,25 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +class CopyrightElement extends ManifestElement { + public function getAuthorElements(): AuthorElementCollection { + return new AuthorElementCollection( + $this->getChildrenByName('author') + ); + } + + public function getLicenseElement(): LicenseElement { + return new LicenseElement( + $this->getChildByName('license') + ); + } +} diff --git a/vendor/phar-io/manifest/src/xml/ElementCollection.php b/vendor/phar-io/manifest/src/xml/ElementCollection.php new file mode 100644 index 00000000..9e1de569 --- /dev/null +++ b/vendor/phar-io/manifest/src/xml/ElementCollection.php @@ -0,0 +1,68 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +use DOMElement; +use DOMNodeList; +use Iterator; +use ReturnTypeWillChange; +use function count; +use function get_class; +use function sprintf; + +/** @template-implements Iterator */ +abstract class ElementCollection implements Iterator { + /** @var DOMElement[] */ + private $nodes = []; + + /** @var int */ + private $position; + + public function __construct(DOMNodeList $nodeList) { + $this->position = 0; + $this->importNodes($nodeList); + } + + #[ReturnTypeWillChange] + abstract public function current(); + + public function next(): void { + $this->position++; + } + + public function key(): int { + return $this->position; + } + + public function valid(): bool { + return $this->position < count($this->nodes); + } + + public function rewind(): void { + $this->position = 0; + } + + protected function getCurrentElement(): DOMElement { + return $this->nodes[$this->position]; + } + + private function importNodes(DOMNodeList $nodeList): void { + foreach ($nodeList as $node) { + if (!$node instanceof DOMElement) { + throw new ElementCollectionException( + sprintf('\DOMElement expected, got \%s', get_class($node)) + ); + } + + $this->nodes[] = $node; + } + } +} diff --git a/vendor/phar-io/manifest/src/xml/ExtElement.php b/vendor/phar-io/manifest/src/xml/ExtElement.php new file mode 100644 index 00000000..6a88a05d --- /dev/null +++ b/vendor/phar-io/manifest/src/xml/ExtElement.php @@ -0,0 +1,17 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +class ExtElement extends ManifestElement { + public function getName(): string { + return $this->getAttributeValue('name'); + } +} diff --git a/vendor/phar-io/manifest/src/xml/ExtElementCollection.php b/vendor/phar-io/manifest/src/xml/ExtElementCollection.php new file mode 100644 index 00000000..3eec9463 --- /dev/null +++ b/vendor/phar-io/manifest/src/xml/ExtElementCollection.php @@ -0,0 +1,19 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +class ExtElementCollection extends ElementCollection { + public function current(): ExtElement { + return new ExtElement( + $this->getCurrentElement() + ); + } +} diff --git a/vendor/phar-io/manifest/src/xml/ExtensionElement.php b/vendor/phar-io/manifest/src/xml/ExtensionElement.php new file mode 100644 index 00000000..22016a01 --- /dev/null +++ b/vendor/phar-io/manifest/src/xml/ExtensionElement.php @@ -0,0 +1,21 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +class ExtensionElement extends ManifestElement { + public function getFor(): string { + return $this->getAttributeValue('for'); + } + + public function getCompatible(): string { + return $this->getAttributeValue('compatible'); + } +} diff --git a/vendor/phar-io/manifest/src/xml/LicenseElement.php b/vendor/phar-io/manifest/src/xml/LicenseElement.php new file mode 100644 index 00000000..d9f4cb26 --- /dev/null +++ b/vendor/phar-io/manifest/src/xml/LicenseElement.php @@ -0,0 +1,21 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +class LicenseElement extends ManifestElement { + public function getType(): string { + return $this->getAttributeValue('type'); + } + + public function getUrl(): string { + return $this->getAttributeValue('url'); + } +} diff --git a/vendor/phar-io/manifest/src/xml/ManifestDocument.php b/vendor/phar-io/manifest/src/xml/ManifestDocument.php new file mode 100644 index 00000000..87458686 --- /dev/null +++ b/vendor/phar-io/manifest/src/xml/ManifestDocument.php @@ -0,0 +1,115 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +use DOMDocument; +use DOMElement; +use Throwable; +use function count; +use function file_get_contents; +use function is_file; +use function libxml_clear_errors; +use function libxml_get_errors; +use function libxml_use_internal_errors; +use function sprintf; + +class ManifestDocument { + public const XMLNS = 'https://phar.io/xml/manifest/1.0'; + + /** @var DOMDocument */ + private $dom; + + public static function fromFile(string $filename): ManifestDocument { + if (!is_file($filename)) { + throw new ManifestDocumentException( + sprintf('File "%s" not found', $filename) + ); + } + + return self::fromString( + file_get_contents($filename) + ); + } + + public static function fromString(string $xmlString): ManifestDocument { + $prev = libxml_use_internal_errors(true); + libxml_clear_errors(); + + try { + $dom = new DOMDocument(); + $dom->loadXML($xmlString); + $errors = libxml_get_errors(); + libxml_use_internal_errors($prev); + } catch (Throwable $t) { + throw new ManifestDocumentException($t->getMessage(), 0, $t); + } + + if (count($errors) !== 0) { + throw new ManifestDocumentLoadingException($errors); + } + + return new self($dom); + } + + private function __construct(DOMDocument $dom) { + $this->ensureCorrectDocumentType($dom); + + $this->dom = $dom; + } + + public function getContainsElement(): ContainsElement { + return new ContainsElement( + $this->fetchElementByName('contains') + ); + } + + public function getCopyrightElement(): CopyrightElement { + return new CopyrightElement( + $this->fetchElementByName('copyright') + ); + } + + public function getRequiresElement(): RequiresElement { + return new RequiresElement( + $this->fetchElementByName('requires') + ); + } + + public function hasBundlesElement(): bool { + return $this->dom->getElementsByTagNameNS(self::XMLNS, 'bundles')->length === 1; + } + + public function getBundlesElement(): BundlesElement { + return new BundlesElement( + $this->fetchElementByName('bundles') + ); + } + + private function ensureCorrectDocumentType(DOMDocument $dom): void { + $root = $dom->documentElement; + + if ($root->localName !== 'phar' || $root->namespaceURI !== self::XMLNS) { + throw new ManifestDocumentException('Not a phar.io manifest document'); + } + } + + private function fetchElementByName(string $elementName): DOMElement { + $element = $this->dom->getElementsByTagNameNS(self::XMLNS, $elementName)->item(0); + + if (!$element instanceof DOMElement) { + throw new ManifestDocumentException( + sprintf('Element %s missing', $elementName) + ); + } + + return $element; + } +} diff --git a/vendor/phar-io/manifest/src/xml/ManifestElement.php b/vendor/phar-io/manifest/src/xml/ManifestElement.php new file mode 100644 index 00000000..461ba0c9 --- /dev/null +++ b/vendor/phar-io/manifest/src/xml/ManifestElement.php @@ -0,0 +1,72 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +use DOMElement; +use DOMNodeList; +use function sprintf; + +class ManifestElement { + public const XMLNS = 'https://phar.io/xml/manifest/1.0'; + + /** @var DOMElement */ + private $element; + + public function __construct(DOMElement $element) { + $this->element = $element; + } + + protected function getAttributeValue(string $name): string { + if (!$this->element->hasAttribute($name)) { + throw new ManifestElementException( + sprintf( + 'Attribute %s not set on element %s', + $name, + $this->element->localName + ) + ); + } + + return $this->element->getAttribute($name); + } + + protected function hasAttribute(string $name): bool { + return $this->element->hasAttribute($name); + } + + protected function getChildByName(string $elementName): DOMElement { + $element = $this->element->getElementsByTagNameNS(self::XMLNS, $elementName)->item(0); + + if (!$element instanceof DOMElement) { + throw new ManifestElementException( + sprintf('Element %s missing', $elementName) + ); + } + + return $element; + } + + protected function getChildrenByName(string $elementName): DOMNodeList { + $elementList = $this->element->getElementsByTagNameNS(self::XMLNS, $elementName); + + if ($elementList->length === 0) { + throw new ManifestElementException( + sprintf('Element(s) %s missing', $elementName) + ); + } + + return $elementList; + } + + protected function hasChild(string $elementName): bool { + return $this->element->getElementsByTagNameNS(self::XMLNS, $elementName)->length !== 0; + } +} diff --git a/vendor/phar-io/manifest/src/xml/PhpElement.php b/vendor/phar-io/manifest/src/xml/PhpElement.php new file mode 100644 index 00000000..9340c2e6 --- /dev/null +++ b/vendor/phar-io/manifest/src/xml/PhpElement.php @@ -0,0 +1,27 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +class PhpElement extends ManifestElement { + public function getVersion(): string { + return $this->getAttributeValue('version'); + } + + public function hasExtElements(): bool { + return $this->hasChild('ext'); + } + + public function getExtElements(): ExtElementCollection { + return new ExtElementCollection( + $this->getChildrenByName('ext') + ); + } +} diff --git a/vendor/phar-io/manifest/src/xml/RequiresElement.php b/vendor/phar-io/manifest/src/xml/RequiresElement.php new file mode 100644 index 00000000..73ba54ca --- /dev/null +++ b/vendor/phar-io/manifest/src/xml/RequiresElement.php @@ -0,0 +1,19 @@ +, Sebastian Heuer , Sebastian Bergmann and contributors + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ +namespace PharIo\Manifest; + +class RequiresElement extends ManifestElement { + public function getPHPElement(): PhpElement { + return new PhpElement( + $this->getChildByName('php') + ); + } +} diff --git a/vendor/phar-io/manifest/tools/php-cs-fixer.d/PhpdocSingleLineVarFixer.php b/vendor/phar-io/manifest/tools/php-cs-fixer.d/PhpdocSingleLineVarFixer.php new file mode 100644 index 00000000..ea5414e4 --- /dev/null +++ b/vendor/phar-io/manifest/tools/php-cs-fixer.d/PhpdocSingleLineVarFixer.php @@ -0,0 +1,72 @@ +isTokenKindFound(T_DOC_COMMENT); + } + + public function isRisky(): bool { + return false; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void { + foreach($tokens as $index => $token) { + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + if (\stripos($token->getContent(), '@var') === false) { + continue; + } + + if (preg_match('#^/\*\*[\s\*]+(@var[^\r\n]+)[\s\*]*\*\/$#u', $token->getContent(), $matches) !== 1) { + continue; + } + $newContent = '/** ' . \rtrim($matches[1]) . ' */'; + if ($newContent === $token->getContent()) { + continue; + } + $tokens[$index] = new Token([T_DOC_COMMENT, $newContent]); + } + } + + public function getPriority(): int { + return 0; + } + + public function getName(): string { + return 'PharIo/phpdoc_single_line_var_fixer'; + } + + public function supports(\SplFileInfo $file): bool { + return true; + } + +} diff --git a/vendor/phar-io/manifest/tools/php-cs-fixer.d/header.txt b/vendor/phar-io/manifest/tools/php-cs-fixer.d/header.txt new file mode 100644 index 00000000..dc8c4ffc --- /dev/null +++ b/vendor/phar-io/manifest/tools/php-cs-fixer.d/header.txt @@ -0,0 +1,6 @@ +This file is part of PharIo\Manifest. + +Copyright (c) Arne Blankerts , Sebastian Heuer , Sebastian Bergmann and contributors + +For the full copyright and license information, please view the LICENSE +file that was distributed with this source code. diff --git a/vendor/phar-io/version/CHANGELOG.md b/vendor/phar-io/version/CHANGELOG.md new file mode 100644 index 00000000..4c0edfa7 --- /dev/null +++ b/vendor/phar-io/version/CHANGELOG.md @@ -0,0 +1,142 @@ +# Changelog + +All notable changes to phar-io/version are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. + +## [3.2.1] - 2022-02-21 + +### Fixed + +- Have ExactVersionConstraint honor build metadata (added in 3.2.0) + + +## [3.2.0] - 2022-02-21 + +### Added + +- Build metadata is now supported and considered for equality checks only + + +## [3.1.1] - 2022-02-07 + +### Fixed + +- [#28](https://github.com/phar-io/version/issues/28): `VersionConstraintParser` does not support logical OR represented by single pipe (|) (Thanks @llaville) + + +## [3.1.0] - 2021-02-23 + +### Changed + +- Internal Refactoring +- More scalar types + +### Added + +- [#24](https://github.com/phar-io/version/issues/24): `Version::getOriginalString()` added (Thanks @addshore) +- Version constraints using the caret operator (`^`) now honor pre-1.0 releases, e.g. `^0.3` translates to `0.3.*`) +- Various integration tests for version constraint processing + +### Fixed + +- [#23](https://github.com/phar-io/version/pull/23): Tilde operator without patch level + + + +## [3.0.4] - 14.12.2020 + +### Fixed + +- [#22](https://github.com/phar-io/version/pull/22): make dev suffix rank works for uppercase too + +## [3.0.3] - 30.11.2020 + +### Added + +- Comparator method `Version::equals()` added + + +## [3.0.2] - 27.06.2020 + +This release now supports PHP 7.2+ and PHP ^8.0. No other changes included. + + +## [3.0.1] - 09.05.2020 + +__Potential BC Break Notice:__ +`Version::getVersionString()` no longer returns `v` prefixes in case the "input" +string contained one. These are not part of the semver specs +(see https://semver.org/#is-v123-a-semantic-version) and get stripped out. +As of Version 3.1.0 `Version::getOriginalString()` can be used to still +retrieve it as given. + +### Changed + +- Internal Refactoring +- More scalar types + +### Fixed + +- Fixed Constraint processing Regression for ^1.2 and ~1.2 + + +## [3.0.0] - 05.05.2020 + +### Changed + +- Require PHP 7.2+ +- All code now uses strict mode +- Scalar types have been added as needed + +### Added + +- The technically invalid format using 'v' prefix ("v1.2.3") is now properly supported + + +## [2.0.1] - 08.07.2018 + +### Fixed + +- Versions without a pre-release suffix are now always considered greater +than versions with a pre-release suffix. Example: `3.0.0 > 3.0.0-alpha.1` + + +## [2.0.0] - 23.06.2018 + +Changes to public API: + +- `PreReleaseSuffix::construct()`: optional parameter `$number` removed +- `PreReleaseSuffix::isGreaterThan()`: introduced +- `Version::hasPreReleaseSuffix()`: introduced + +### Added + +- [#11](https://github.com/phar-io/version/issues/11): Added support for pre-release version suffixes. Supported values are: + - `dev` + - `beta` (also abbreviated form `b`) + - `rc` + - `alpha` (also abbreviated form `a`) + - `patch` (also abbreviated form `p`) + + All values can be followed by a number, e.g. `beta3`. + + When comparing versions, the pre-release suffix is taken into account. Example: +`1.5.0 > 1.5.0-beta1 > 1.5.0-alpha3 > 1.5.0-alpha2 > 1.5.0-dev11` + +### Changed + +- reorganized the source directories + +### Fixed + +- [#10](https://github.com/phar-io/version/issues/10): Version numbers containing +a numeric suffix as seen in Debian packages are now supported. + + +[3.1.0]: https://github.com/phar-io/version/compare/3.0.4...3.1.0 +[3.0.4]: https://github.com/phar-io/version/compare/3.0.3...3.0.4 +[3.0.3]: https://github.com/phar-io/version/compare/3.0.2...3.0.3 +[3.0.2]: https://github.com/phar-io/version/compare/3.0.1...3.0.2 +[3.0.1]: https://github.com/phar-io/version/compare/3.0.0...3.0.1 +[3.0.0]: https://github.com/phar-io/version/compare/2.0.1...3.0.0 +[2.0.1]: https://github.com/phar-io/version/compare/2.0.0...2.0.1 +[2.0.0]: https://github.com/phar-io/version/compare/1.0.1...2.0.0 diff --git a/vendor/phar-io/version/LICENSE b/vendor/phar-io/version/LICENSE new file mode 100644 index 00000000..ce32758a --- /dev/null +++ b/vendor/phar-io/version/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2016-2017 Arne Blankerts , Sebastian Heuer and contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, +OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + diff --git a/vendor/phar-io/version/README.md b/vendor/phar-io/version/README.md new file mode 100644 index 00000000..76e6e985 --- /dev/null +++ b/vendor/phar-io/version/README.md @@ -0,0 +1,61 @@ +# Version + +Library for handling version information and constraints + +[![Build Status](https://travis-ci.org/phar-io/version.svg?branch=master)](https://travis-ci.org/phar-io/version) + +## Installation + +You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): + + composer require phar-io/version + +If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency: + + composer require --dev phar-io/version + +## Version constraints + +A Version constraint describes a range of versions or a discrete version number. The format of version numbers follows the schema of [semantic versioning](http://semver.org): `..`. A constraint might contain an operator that describes the range. + +Beside the typical mathematical operators like `<=`, `>=`, there are two special operators: + +*Caret operator*: `^1.0` +can be written as `>=1.0.0 <2.0.0` and read as »every Version within major version `1`«. + +*Tilde operator*: `~1.0.0` +can be written as `>=1.0.0 <1.1.0` and read as »every version within minor version `1.1`. The behavior of tilde operator depends on whether a patch level version is provided or not. If no patch level is provided, tilde operator behaves like the caret operator: `~1.0` is identical to `^1.0`. + +## Usage examples + +Parsing version constraints and check discrete versions for compliance: + +```php + +use PharIo\Version\Version; +use PharIo\Version\VersionConstraintParser; + +$parser = new VersionConstraintParser(); +$caret_constraint = $parser->parse( '^7.0' ); + +$caret_constraint->complies( new Version( '7.0.17' ) ); // true +$caret_constraint->complies( new Version( '7.1.0' ) ); // true +$caret_constraint->complies( new Version( '6.4.34' ) ); // false + +$tilde_constraint = $parser->parse( '~1.1.0' ); + +$tilde_constraint->complies( new Version( '1.1.4' ) ); // true +$tilde_constraint->complies( new Version( '1.2.0' ) ); // false +``` + +As of version 2.0.0, pre-release labels are supported and taken into account when comparing versions: + +```php + +$leftVersion = new PharIo\Version\Version('3.0.0-alpha.1'); +$rightVersion = new PharIo\Version\Version('3.0.0-alpha.2'); + +$leftVersion->isGreaterThan($rightVersion); // false +$rightVersion->isGreaterThan($leftVersion); // true + +``` diff --git a/vendor/phar-io/version/composer.json b/vendor/phar-io/version/composer.json new file mode 100644 index 00000000..22687dcd --- /dev/null +++ b/vendor/phar-io/version/composer.json @@ -0,0 +1,34 @@ +{ + "name": "phar-io/version", + "description": "Library for handling version information and constraints", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "support": { + "issues": "https://github.com/phar-io/version/issues" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "autoload": { + "classmap": [ + "src/" + ] + } +} + diff --git a/vendor/phar-io/version/src/BuildMetaData.php b/vendor/phar-io/version/src/BuildMetaData.php new file mode 100644 index 00000000..d42f0363 --- /dev/null +++ b/vendor/phar-io/version/src/BuildMetaData.php @@ -0,0 +1,28 @@ +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PharIo\Version; + +class BuildMetaData { + + /** @var string */ + private $value; + + public function __construct(string $value) { + $this->value = $value; + } + + public function asString(): string { + return $this->value; + } + + public function equals(BuildMetaData $other): bool { + return $this->asString() === $other->asString(); + } +} diff --git a/vendor/phar-io/version/src/PreReleaseSuffix.php b/vendor/phar-io/version/src/PreReleaseSuffix.php new file mode 100644 index 00000000..00563008 --- /dev/null +++ b/vendor/phar-io/version/src/PreReleaseSuffix.php @@ -0,0 +1,82 @@ + 0, + 'a' => 1, + 'alpha' => 1, + 'b' => 2, + 'beta' => 2, + 'rc' => 3, + 'p' => 4, + 'pl' => 4, + 'patch' => 4, + ]; + + /** @var string */ + private $value; + + /** @var int */ + private $valueScore; + + /** @var int */ + private $number = 0; + + /** @var string */ + private $full; + + /** + * @throws InvalidPreReleaseSuffixException + */ + public function __construct(string $value) { + $this->parseValue($value); + } + + public function asString(): string { + return $this->full; + } + + public function getValue(): string { + return $this->value; + } + + public function getNumber(): ?int { + return $this->number; + } + + public function isGreaterThan(PreReleaseSuffix $suffix): bool { + if ($this->valueScore > $suffix->valueScore) { + return true; + } + + if ($this->valueScore < $suffix->valueScore) { + return false; + } + + return $this->getNumber() > $suffix->getNumber(); + } + + private function mapValueToScore(string $value): int { + $value = \strtolower($value); + + return self::valueScoreMap[$value]; + } + + private function parseValue(string $value): void { + $regex = '/-?((dev|beta|b|rc|alpha|a|patch|p|pl)\.?(\d*)).*$/i'; + + if (\preg_match($regex, $value, $matches) !== 1) { + throw new InvalidPreReleaseSuffixException(\sprintf('Invalid label %s', $value)); + } + + $this->full = $matches[1]; + $this->value = $matches[2]; + + if ($matches[3] !== '') { + $this->number = (int)$matches[3]; + } + + $this->valueScore = $this->mapValueToScore($matches[2]); + } +} diff --git a/vendor/phar-io/version/src/Version.php b/vendor/phar-io/version/src/Version.php new file mode 100644 index 00000000..644af5ca --- /dev/null +++ b/vendor/phar-io/version/src/Version.php @@ -0,0 +1,208 @@ +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PharIo\Version; + +class Version { + /** @var string */ + private $originalVersionString; + + /** @var VersionNumber */ + private $major; + + /** @var VersionNumber */ + private $minor; + + /** @var VersionNumber */ + private $patch; + + /** @var null|PreReleaseSuffix */ + private $preReleaseSuffix; + + /** @var null|BuildMetaData */ + private $buildMetadata; + + public function __construct(string $versionString) { + $this->ensureVersionStringIsValid($versionString); + $this->originalVersionString = $versionString; + } + + /** + * @throws NoPreReleaseSuffixException + */ + public function getPreReleaseSuffix(): PreReleaseSuffix { + if ($this->preReleaseSuffix === null) { + throw new NoPreReleaseSuffixException('No pre-release suffix set'); + } + + return $this->preReleaseSuffix; + } + + public function getOriginalString(): string { + return $this->originalVersionString; + } + + public function getVersionString(): string { + $str = \sprintf( + '%d.%d.%d', + $this->getMajor()->getValue() ?? 0, + $this->getMinor()->getValue() ?? 0, + $this->getPatch()->getValue() ?? 0 + ); + + if (!$this->hasPreReleaseSuffix()) { + return $str; + } + + return $str . '-' . $this->getPreReleaseSuffix()->asString(); + } + + public function hasPreReleaseSuffix(): bool { + return $this->preReleaseSuffix !== null; + } + + public function equals(Version $other): bool { + if ($this->getVersionString() !== $other->getVersionString()) { + return false; + } + + if ($this->hasBuildMetaData() !== $other->hasBuildMetaData()) { + return false; + } + + if ($this->hasBuildMetaData() && $other->hasBuildMetaData() && + !$this->getBuildMetaData()->equals($other->getBuildMetaData())) { + return false; + } + + return true; + } + + public function isGreaterThan(Version $version): bool { + if ($version->getMajor()->getValue() > $this->getMajor()->getValue()) { + return false; + } + + if ($version->getMajor()->getValue() < $this->getMajor()->getValue()) { + return true; + } + + if ($version->getMinor()->getValue() > $this->getMinor()->getValue()) { + return false; + } + + if ($version->getMinor()->getValue() < $this->getMinor()->getValue()) { + return true; + } + + if ($version->getPatch()->getValue() > $this->getPatch()->getValue()) { + return false; + } + + if ($version->getPatch()->getValue() < $this->getPatch()->getValue()) { + return true; + } + + if (!$version->hasPreReleaseSuffix() && !$this->hasPreReleaseSuffix()) { + return false; + } + + if ($version->hasPreReleaseSuffix() && !$this->hasPreReleaseSuffix()) { + return true; + } + + if (!$version->hasPreReleaseSuffix() && $this->hasPreReleaseSuffix()) { + return false; + } + + return $this->getPreReleaseSuffix()->isGreaterThan($version->getPreReleaseSuffix()); + } + + public function getMajor(): VersionNumber { + return $this->major; + } + + public function getMinor(): VersionNumber { + return $this->minor; + } + + public function getPatch(): VersionNumber { + return $this->patch; + } + + /** + * @psalm-assert-if-true BuildMetaData $this->buildMetadata + * @psalm-assert-if-true BuildMetaData $this->getBuildMetaData() + */ + public function hasBuildMetaData(): bool { + return $this->buildMetadata !== null; + } + + /** + * @throws NoBuildMetaDataException + */ + public function getBuildMetaData(): BuildMetaData { + if (!$this->hasBuildMetaData()) { + throw new NoBuildMetaDataException('No build metadata set'); + } + + return $this->buildMetadata; + } + + /** + * @param string[] $matches + * + * @throws InvalidPreReleaseSuffixException + */ + private function parseVersion(array $matches): void { + $this->major = new VersionNumber((int)$matches['Major']); + $this->minor = new VersionNumber((int)$matches['Minor']); + $this->patch = isset($matches['Patch']) ? new VersionNumber((int)$matches['Patch']) : new VersionNumber(0); + + if (isset($matches['PreReleaseSuffix']) && $matches['PreReleaseSuffix'] !== '') { + $this->preReleaseSuffix = new PreReleaseSuffix($matches['PreReleaseSuffix']); + } + + if (isset($matches['BuildMetadata'])) { + $this->buildMetadata = new BuildMetaData($matches['BuildMetadata']); + } + } + + /** + * @param string $version + * + * @throws InvalidVersionException + */ + private function ensureVersionStringIsValid($version): void { + $regex = '/^v? + (?P0|[1-9]\d*) + \\. + (?P0|[1-9]\d*) + (\\. + (?P0|[1-9]\d*) + )? + (?: + - + (?(?:(dev|beta|b|rc|alpha|a|patch|p|pl)\.?\d*)) + )? + (?: + \\+ + (?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-@]+)*) + )? + $/xi'; + + if (\preg_match($regex, $version, $matches) !== 1) { + throw new InvalidVersionException( + \sprintf("Version string '%s' does not follow SemVer semantics", $version) + ); + } + + $this->parseVersion($matches); + } +} diff --git a/vendor/phar-io/version/src/VersionConstraintParser.php b/vendor/phar-io/version/src/VersionConstraintParser.php new file mode 100644 index 00000000..03d6a095 --- /dev/null +++ b/vendor/phar-io/version/src/VersionConstraintParser.php @@ -0,0 +1,115 @@ +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PharIo\Version; + +class VersionConstraintParser { + /** + * @throws UnsupportedVersionConstraintException + */ + public function parse(string $value): VersionConstraint { + if (\strpos($value, '|') !== false) { + return $this->handleOrGroup($value); + } + + if (!\preg_match('/^[\^~*]?v?[\d.*]+(?:-.*)?$/i', $value)) { + throw new UnsupportedVersionConstraintException( + \sprintf('Version constraint %s is not supported.', $value) + ); + } + + switch ($value[0]) { + case '~': + return $this->handleTildeOperator($value); + case '^': + return $this->handleCaretOperator($value); + } + + $constraint = new VersionConstraintValue($value); + + if ($constraint->getMajor()->isAny()) { + return new AnyVersionConstraint(); + } + + if ($constraint->getMinor()->isAny()) { + return new SpecificMajorVersionConstraint( + $constraint->getVersionString(), + $constraint->getMajor()->getValue() ?? 0 + ); + } + + if ($constraint->getPatch()->isAny()) { + return new SpecificMajorAndMinorVersionConstraint( + $constraint->getVersionString(), + $constraint->getMajor()->getValue() ?? 0, + $constraint->getMinor()->getValue() ?? 0 + ); + } + + return new ExactVersionConstraint($constraint->getVersionString()); + } + + private function handleOrGroup(string $value): OrVersionConstraintGroup { + $constraints = []; + + foreach (\preg_split('{\s*\|\|?\s*}', \trim($value)) as $groupSegment) { + $constraints[] = $this->parse(\trim($groupSegment)); + } + + return new OrVersionConstraintGroup($value, $constraints); + } + + private function handleTildeOperator(string $value): AndVersionConstraintGroup { + $constraintValue = new VersionConstraintValue(\substr($value, 1)); + + if ($constraintValue->getPatch()->isAny()) { + return $this->handleCaretOperator($value); + } + + $constraints = [ + new GreaterThanOrEqualToVersionConstraint( + $value, + new Version(\substr($value, 1)) + ), + new SpecificMajorAndMinorVersionConstraint( + $value, + $constraintValue->getMajor()->getValue() ?? 0, + $constraintValue->getMinor()->getValue() ?? 0 + ) + ]; + + return new AndVersionConstraintGroup($value, $constraints); + } + + private function handleCaretOperator(string $value): AndVersionConstraintGroup { + $constraintValue = new VersionConstraintValue(\substr($value, 1)); + + $constraints = [ + new GreaterThanOrEqualToVersionConstraint($value, new Version(\substr($value, 1))) + ]; + + if ($constraintValue->getMajor()->getValue() === 0) { + $constraints[] = new SpecificMajorAndMinorVersionConstraint( + $value, + $constraintValue->getMajor()->getValue() ?? 0, + $constraintValue->getMinor()->getValue() ?? 0 + ); + } else { + $constraints[] = new SpecificMajorVersionConstraint( + $value, + $constraintValue->getMajor()->getValue() ?? 0 + ); + } + + return new AndVersionConstraintGroup( + $value, + $constraints + ); + } +} diff --git a/vendor/phar-io/version/src/VersionConstraintValue.php b/vendor/phar-io/version/src/VersionConstraintValue.php new file mode 100644 index 00000000..0762e7c0 --- /dev/null +++ b/vendor/phar-io/version/src/VersionConstraintValue.php @@ -0,0 +1,88 @@ +versionString = $versionString; + + $this->parseVersion($versionString); + } + + public function getLabel(): string { + return $this->label; + } + + public function getBuildMetaData(): string { + return $this->buildMetaData; + } + + public function getVersionString(): string { + return $this->versionString; + } + + public function getMajor(): VersionNumber { + return $this->major; + } + + public function getMinor(): VersionNumber { + return $this->minor; + } + + public function getPatch(): VersionNumber { + return $this->patch; + } + + private function parseVersion(string $versionString): void { + $this->extractBuildMetaData($versionString); + $this->extractLabel($versionString); + $this->stripPotentialVPrefix($versionString); + + $versionSegments = \explode('.', $versionString); + $this->major = new VersionNumber(\is_numeric($versionSegments[0]) ? (int)$versionSegments[0] : null); + + $minorValue = isset($versionSegments[1]) && \is_numeric($versionSegments[1]) ? (int)$versionSegments[1] : null; + $patchValue = isset($versionSegments[2]) && \is_numeric($versionSegments[2]) ? (int)$versionSegments[2] : null; + + $this->minor = new VersionNumber($minorValue); + $this->patch = new VersionNumber($patchValue); + } + + private function extractBuildMetaData(string &$versionString): void { + if (\preg_match('/\+(.*)/', $versionString, $matches) === 1) { + $this->buildMetaData = $matches[1]; + $versionString = \str_replace($matches[0], '', $versionString); + } + } + + private function extractLabel(string &$versionString): void { + if (\preg_match('/-(.*)/', $versionString, $matches) === 1) { + $this->label = $matches[1]; + $versionString = \str_replace($matches[0], '', $versionString); + } + } + + private function stripPotentialVPrefix(string &$versionString): void { + if ($versionString[0] !== 'v') { + return; + } + $versionString = \substr($versionString, 1); + } +} diff --git a/vendor/phar-io/version/src/VersionNumber.php b/vendor/phar-io/version/src/VersionNumber.php new file mode 100644 index 00000000..4833a9b0 --- /dev/null +++ b/vendor/phar-io/version/src/VersionNumber.php @@ -0,0 +1,28 @@ +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PharIo\Version; + +class VersionNumber { + + /** @var ?int */ + private $value; + + public function __construct(?int $value) { + $this->value = $value; + } + + public function isAny(): bool { + return $this->value === null; + } + + public function getValue(): ?int { + return $this->value; + } +} diff --git a/vendor/phar-io/version/src/constraints/AbstractVersionConstraint.php b/vendor/phar-io/version/src/constraints/AbstractVersionConstraint.php new file mode 100644 index 00000000..66201a14 --- /dev/null +++ b/vendor/phar-io/version/src/constraints/AbstractVersionConstraint.php @@ -0,0 +1,23 @@ +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PharIo\Version; + +abstract class AbstractVersionConstraint implements VersionConstraint { + /** @var string */ + private $originalValue; + + public function __construct(string $originalValue) { + $this->originalValue = $originalValue; + } + + public function asString(): string { + return $this->originalValue; + } +} diff --git a/vendor/phar-io/version/src/constraints/AndVersionConstraintGroup.php b/vendor/phar-io/version/src/constraints/AndVersionConstraintGroup.php new file mode 100644 index 00000000..5096f2fb --- /dev/null +++ b/vendor/phar-io/version/src/constraints/AndVersionConstraintGroup.php @@ -0,0 +1,34 @@ +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PharIo\Version; + +class AndVersionConstraintGroup extends AbstractVersionConstraint { + /** @var VersionConstraint[] */ + private $constraints = []; + + /** + * @param VersionConstraint[] $constraints + */ + public function __construct(string $originalValue, array $constraints) { + parent::__construct($originalValue); + + $this->constraints = $constraints; + } + + public function complies(Version $version): bool { + foreach ($this->constraints as $constraint) { + if (!$constraint->complies($version)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/phar-io/version/src/constraints/AnyVersionConstraint.php b/vendor/phar-io/version/src/constraints/AnyVersionConstraint.php new file mode 100644 index 00000000..1499f071 --- /dev/null +++ b/vendor/phar-io/version/src/constraints/AnyVersionConstraint.php @@ -0,0 +1,20 @@ +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PharIo\Version; + +class AnyVersionConstraint implements VersionConstraint { + public function complies(Version $version): bool { + return true; + } + + public function asString(): string { + return '*'; + } +} diff --git a/vendor/phar-io/version/src/constraints/ExactVersionConstraint.php b/vendor/phar-io/version/src/constraints/ExactVersionConstraint.php new file mode 100644 index 00000000..1d675c9c --- /dev/null +++ b/vendor/phar-io/version/src/constraints/ExactVersionConstraint.php @@ -0,0 +1,22 @@ +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PharIo\Version; + +class ExactVersionConstraint extends AbstractVersionConstraint { + public function complies(Version $version): bool { + $other = $version->getVersionString(); + + if ($version->hasBuildMetaData()) { + $other .= '+' . $version->getBuildMetaData()->asString(); + } + + return $this->asString() === $other; + } +} diff --git a/vendor/phar-io/version/src/constraints/GreaterThanOrEqualToVersionConstraint.php b/vendor/phar-io/version/src/constraints/GreaterThanOrEqualToVersionConstraint.php new file mode 100644 index 00000000..ec371723 --- /dev/null +++ b/vendor/phar-io/version/src/constraints/GreaterThanOrEqualToVersionConstraint.php @@ -0,0 +1,26 @@ +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PharIo\Version; + +class GreaterThanOrEqualToVersionConstraint extends AbstractVersionConstraint { + /** @var Version */ + private $minimalVersion; + + public function __construct(string $originalValue, Version $minimalVersion) { + parent::__construct($originalValue); + + $this->minimalVersion = $minimalVersion; + } + + public function complies(Version $version): bool { + return $version->getVersionString() === $this->minimalVersion->getVersionString() + || $version->isGreaterThan($this->minimalVersion); + } +} diff --git a/vendor/phar-io/version/src/constraints/OrVersionConstraintGroup.php b/vendor/phar-io/version/src/constraints/OrVersionConstraintGroup.php new file mode 100644 index 00000000..59fd382f --- /dev/null +++ b/vendor/phar-io/version/src/constraints/OrVersionConstraintGroup.php @@ -0,0 +1,35 @@ +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PharIo\Version; + +class OrVersionConstraintGroup extends AbstractVersionConstraint { + /** @var VersionConstraint[] */ + private $constraints = []; + + /** + * @param string $originalValue + * @param VersionConstraint[] $constraints + */ + public function __construct($originalValue, array $constraints) { + parent::__construct($originalValue); + + $this->constraints = $constraints; + } + + public function complies(Version $version): bool { + foreach ($this->constraints as $constraint) { + if ($constraint->complies($version)) { + return true; + } + } + + return false; + } +} diff --git a/vendor/phar-io/version/src/constraints/SpecificMajorAndMinorVersionConstraint.php b/vendor/phar-io/version/src/constraints/SpecificMajorAndMinorVersionConstraint.php new file mode 100644 index 00000000..302aa311 --- /dev/null +++ b/vendor/phar-io/version/src/constraints/SpecificMajorAndMinorVersionConstraint.php @@ -0,0 +1,33 @@ +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PharIo\Version; + +class SpecificMajorAndMinorVersionConstraint extends AbstractVersionConstraint { + /** @var int */ + private $major; + + /** @var int */ + private $minor; + + public function __construct(string $originalValue, int $major, int $minor) { + parent::__construct($originalValue); + + $this->major = $major; + $this->minor = $minor; + } + + public function complies(Version $version): bool { + if ($version->getMajor()->getValue() !== $this->major) { + return false; + } + + return $version->getMinor()->getValue() === $this->minor; + } +} diff --git a/vendor/phar-io/version/src/constraints/SpecificMajorVersionConstraint.php b/vendor/phar-io/version/src/constraints/SpecificMajorVersionConstraint.php new file mode 100644 index 00000000..968b809c --- /dev/null +++ b/vendor/phar-io/version/src/constraints/SpecificMajorVersionConstraint.php @@ -0,0 +1,25 @@ +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PharIo\Version; + +class SpecificMajorVersionConstraint extends AbstractVersionConstraint { + /** @var int */ + private $major; + + public function __construct(string $originalValue, int $major) { + parent::__construct($originalValue); + + $this->major = $major; + } + + public function complies(Version $version): bool { + return $version->getMajor()->getValue() === $this->major; + } +} diff --git a/vendor/phar-io/version/src/constraints/VersionConstraint.php b/vendor/phar-io/version/src/constraints/VersionConstraint.php new file mode 100644 index 00000000..e94f9e00 --- /dev/null +++ b/vendor/phar-io/version/src/constraints/VersionConstraint.php @@ -0,0 +1,16 @@ +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PharIo\Version; + +interface VersionConstraint { + public function complies(Version $version): bool; + + public function asString(): string; +} diff --git a/vendor/phar-io/version/src/exceptions/Exception.php b/vendor/phar-io/version/src/exceptions/Exception.php new file mode 100644 index 00000000..3ea458f3 --- /dev/null +++ b/vendor/phar-io/version/src/exceptions/Exception.php @@ -0,0 +1,15 @@ +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PharIo\Version; + +use Throwable; + +interface Exception extends Throwable { +} diff --git a/vendor/phar-io/version/src/exceptions/InvalidPreReleaseSuffixException.php b/vendor/phar-io/version/src/exceptions/InvalidPreReleaseSuffixException.php new file mode 100644 index 00000000..bc0b0c3e --- /dev/null +++ b/vendor/phar-io/version/src/exceptions/InvalidPreReleaseSuffixException.php @@ -0,0 +1,5 @@ +, Sebastian Heuer , Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PharIo\Version; + +final class UnsupportedVersionConstraintException extends \RuntimeException implements Exception { +} diff --git a/vendor/phpunit/php-code-coverage/ChangeLog-9.2.md b/vendor/phpunit/php-code-coverage/ChangeLog-9.2.md new file mode 100644 index 00000000..9ef6be33 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/ChangeLog-9.2.md @@ -0,0 +1,577 @@ +# ChangeLog + +All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. + +## [9.2.31] - 2024-03-02 + +### Changed + +* Do not use implicitly nullable parameters + +## [9.2.30] - 2023-12-22 + +### Changed + +* This component is now compatible with `nikic/php-parser` 5.0 + +## [9.2.29] - 2023-09-19 + +### Fixed + +* [#1012](https://github.com/sebastianbergmann/php-code-coverage/issues/1012): Cobertura report pulls functions from report scope, not the individual element + +## [9.2.28] - 2023-09-12 + +### Changed + +* [#1011](https://github.com/sebastianbergmann/php-code-coverage/pull/1011): Avoid serialization of cache data in PHP report + +## [9.2.27] - 2023-07-26 + +### Changed + +* The result of `CodeCoverage::getReport()` is now cached + +### Fixed + +* Static analysis cache keys do not include configuration settings that affect source code parsing +* The Clover, Cobertura, Crap4j, and PHP report writers no longer create a `php:` directory when they should write to `php://stdout`, for instance + +## [9.2.26] - 2023-03-06 + +### Changed + +* Improved the legend on the file pages of the HTML code coverage report + +## [9.2.25] - 2023-02-25 + +### Fixed + +* [#981](https://github.com/sebastianbergmann/php-code-coverage/issues/981): `CodeUnitFindingVisitor` does not support DNF types + +## [9.2.24] - 2023-01-26 + +### Changed + +* [#970](https://github.com/sebastianbergmann/php-code-coverage/issues/970): CSS and JavaScript assets are now referenced using `?v=%s` URLs in the HTML report to avoid cache issues + +## [9.2.23] - 2022-12-28 + +### Fixed + +* [#971](https://github.com/sebastianbergmann/php-code-coverage/issues/971): PHP report does not handle serialized code coverage data larger than 2 GB +* [#974](https://github.com/sebastianbergmann/php-code-coverage/issues/974): Executable line analysis fails for declarations with enumerations and unions + +## [9.2.22] - 2022-12-18 + +### Fixed + +* [#969](https://github.com/sebastianbergmann/php-code-coverage/pull/969): Fixed identifying line with `throw` as executable + +## [9.2.21] - 2022-12-14 + +### Changed + +* [#964](https://github.com/sebastianbergmann/php-code-coverage/pull/964): Changed how executable lines are identified + +## [9.2.20] - 2022-12-13 + +### Fixed + +* [#960](https://github.com/sebastianbergmann/php-code-coverage/issues/960): New body font-size is way too big + +## [9.2.19] - 2022-11-18 + +### Fixed + +* [#949](https://github.com/sebastianbergmann/php-code-coverage/pull/949): Various issues related to identifying executable lines + +### Changed + +* Tweaked CSS for HTML report +* Updated bundled CSS/JavaScript components used for HTML report: Bootstrap 4.6.2 and jQuery 3.6.1 + +## [9.2.18] - 2022-10-27 + +### Fixed + +* [#935](https://github.com/sebastianbergmann/php-code-coverage/pull/935): Cobertura package name attribute is always empty +* [#946](https://github.com/sebastianbergmann/php-code-coverage/issues/946): `return` with multiline constant expression must only contain the last line + +## [9.2.17] - 2022-08-30 + +### Changed + +* [#928](https://github.com/sebastianbergmann/php-code-coverage/pull/928): Avoid unnecessary `is_file()` calls +* [#931](https://github.com/sebastianbergmann/php-code-coverage/pull/931): Use MD5 instead of CRC32 for static analysis cache file identifier + +### Fixed + +* [#926](https://github.com/sebastianbergmann/php-code-coverage/pull/926): Static Analysis cache does not work with `open_basedir` + +## [9.2.16] - 2022-08-20 + +### Fixed + +* [#926](https://github.com/sebastianbergmann/php-code-coverage/issues/926): File view has wrong colouring for the first column + +## [9.2.15] - 2022-03-07 + +### Fixed + +* [#885](https://github.com/sebastianbergmann/php-code-coverage/issues/885): Files that have only `\r` (CR, 0x0d) EOL characters are not handled correctly +* [#907](https://github.com/sebastianbergmann/php-code-coverage/issues/907): Line with only `return [` is not recognized as executable + +## [9.2.14] - 2022-02-28 + +### Fixed + +* [#904](https://github.com/sebastianbergmann/php-code-coverage/issues/904): Lines of code containing the `match` keyword were not recognized as executable correctly +* [#905](https://github.com/sebastianbergmann/php-code-coverage/issues/905): Lines of code in constructors were not recognized as executable correctly when constructor property promotion is used + +## [9.2.13] - 2022-02-23 + +### Changed + +* The contents of the static analysis sourcecode files is now used to generate the static analysis cache version identifier + +### Fixed + +* Reverted rename of `SebastianBergmann\CodeCoverage\ProcessedCodeCoverageData` to `SebastianBergmann\CodeCoverage\Data\ProcessedCodeCoverageData` (this class is marked as `@internal` and not covered by the backward compatibility promise, but it is (still) used directly by PHPUnit) +* Reverted rename of `SebastianBergmann\CodeCoverage\RawCodeCoverageData` to `SebastianBergmann\CodeCoverage\Data\RawCodeCoverageData` (this class is marked as `@internal` and not covered by the backward compatibility promise, but it is (still) used directly by PHPUnit) +* The `ArrayDim`, `Cast`, and `MethodCall` nodes are now considered when determining whether a line of code is executable or not + +## [9.2.12] - 2022-02-23 [YANKED] + +### Changed + +* [#898](https://github.com/sebastianbergmann/php-code-coverage/pull/898): Use content hash instead of `filemtime()` to determine cache hit/miss + +### Fixed + +* [#736](https://github.com/sebastianbergmann/php-code-coverage/issues/736): HTML report generator allows invalid values for low upper bound and high lower bound +* [#854](https://github.com/sebastianbergmann/php-code-coverage/issues/854): "Class Coverage Distribution" and "Class Complexity" graphs are not displayed at full width +* [#897](https://github.com/sebastianbergmann/php-code-coverage/issues/897): `declare(strict_types=1)` marked as uncovered + +## [9.2.11] - 2022-02-18 + +### Changed + +* `CoveredFileAnalyser` and `UncoveredFileAnalyser` have been combined to `FileAnalyser` +* Updated bundled CSS/JavaScript components used for HTML report: Bootstrap 4.6.1, jQuery 3.6.0, and popper.js 1.16.1 + +### Fixed + +* [#889](https://github.com/sebastianbergmann/php-code-coverage/issues/889): Code Coverage depends on autoload order + +## [9.2.10] - 2021-12-05 + +### Fixed + +* [#887](https://github.com/sebastianbergmann/php-code-coverage/issues/887): Document return type of `CodeUnitFindingVisitor::enterNode()` so that Symfony's DebugClassLoader does not trigger a deprecation warning + +## [9.2.9] - 2021-11-19 + +### Fixed + +* [#882](https://github.com/sebastianbergmann/php-code-coverage/issues/882): PHPUnit 9.2.8 has wrong version number + +## [9.2.8] - 2021-10-30 + +### Fixed + +* [#866](https://github.com/sebastianbergmann/php-code-coverage/issues/866): `CodeUnitFindingVisitor` does not handle `enum` type introduced in PHP 8.1 +* [#868](https://github.com/sebastianbergmann/php-code-coverage/pull/868): Uncovered files should be ignored unless requested +* [#876](https://github.com/sebastianbergmann/php-code-coverage/issues/876): PCOV driver causes 2x slowdown after upgrade to PHPUnit 9.5 + +## [9.2.7] - 2021-09-17 + +### Fixed + +* [#860](https://github.com/sebastianbergmann/php-code-coverage/pull/860): Empty value for `XDEBUG_MODE` environment variable is not handled correctly + +## [9.2.6] - 2021-03-28 + +### Fixed + +* [#846](https://github.com/sebastianbergmann/php-code-coverage/issues/846): Method name should not appear in the method signature attribute of Cobertura XML + +## [9.2.5] - 2020-11-28 + +### Fixed + +* [#831](https://github.com/sebastianbergmann/php-code-coverage/issues/831): Files that do not contain a newline are not handled correctly + +## [9.2.4] - 2020-11-27 + +### Added + +* [#834](https://github.com/sebastianbergmann/php-code-coverage/issues/834): Support `XDEBUG_MODE` environment variable + +## [9.2.3] - 2020-10-30 + +### Changed + +* Bumped required version of `nikic/php-parser` + +## [9.2.2] - 2020-10-28 + +### Fixed + +* [#820](https://github.com/sebastianbergmann/php-code-coverage/issues/820): Hidden dependency on PHPUnit + +## [9.2.1] - 2020-10-26 + +### Fixed + +* `SebastianBergmann\CodeCoverage\Exception` now correctly extends `\Throwable` + +## [9.2.0] - 2020-10-02 + +### Added + +* [#812](https://github.com/sebastianbergmann/php-code-coverage/pull/812): Support for Cobertura XML report format + +### Changed + +* Reduced the number of I/O operations performed by the static analysis cache + +## [9.1.11] - 2020-09-19 + +### Fixed + +* [#811](https://github.com/sebastianbergmann/php-code-coverage/issues/811): `T_FN` constant is used on PHP 7.3 where it is not available + +## [9.1.10] - 2020-09-18 + +### Added + +* `SebastianBergmann\CodeCoverage\Driver\Selector::forLineCoverage()` and `SebastianBergmann\CodeCoverage\Driver\Selector::forLineAndPathCoverage()` have been added + +### Fixed + +* [#810](https://github.com/sebastianbergmann/php-code-coverage/issues/810): `SebastianBergmann\CodeCoverage\Driver\Driver::forLineCoverage()` and `SebastianBergmann\CodeCoverage\Driver\Driver::forLineAndPathCoverage()` are marked as internal + +### Removed + +* `SebastianBergmann\CodeCoverage\Driver\Driver::forLineCoverage()` and `SebastianBergmann\CodeCoverage\Driver\Driver::forLineAndPathCoverage()` are now deprecated + +## [9.1.9] - 2020-09-15 + +### Fixed + +* [#808](https://github.com/sebastianbergmann/php-code-coverage/issues/808): `PHP Warning: Use of undefined constant T_MATCH` + +## [9.1.8] - 2020-09-07 + +### Changed + +* [#800](https://github.com/sebastianbergmann/php-code-coverage/pull/800): All files on the inclusion list are no longer loaded when `SebastianBergmann\CodeCoverage::start()` is called for the first time and `processUncoveredFiles` is set to `true` + +### Fixed + +* [#799](https://github.com/sebastianbergmann/php-code-coverage/issues/799): Uncovered new line at end of file + +## [9.1.7] - 2020-09-03 + +### Fixed + +* Fixed regressions introduced in versions 9.1.5 and 9.1.6 + +## [9.1.6] - 2020-08-31 + +### Fixed + +* [#799](https://github.com/sebastianbergmann/php-code-coverage/issues/799): Uncovered new line at end of file +* [#803](https://github.com/sebastianbergmann/php-code-coverage/issues/803): HTML report does not sort directories and files anymore + +## [9.1.5] - 2020-08-27 + +### Changed + +* [#800](https://github.com/sebastianbergmann/php-code-coverage/pull/800): All files on the inclusion list are no longer loaded when `SebastianBergmann\CodeCoverage::start()` is called for the first time and `processUncoveredFiles` is set to `true` + +### Fixed + +* [#797](https://github.com/sebastianbergmann/php-code-coverage/pull/797): Class name is wrongly removed from namespace name + +## [9.1.4] - 2020-08-13 + +### Fixed + +* [#793](https://github.com/sebastianbergmann/php-code-coverage/issues/793): Lines with `::class` constant are not covered + +## [9.1.3] - 2020-08-10 + +### Changed + +* Changed PHP-Parser usage to parse sourcecode according to the PHP version we are currently running on instead of using emulative lexing + +## [9.1.2] - 2020-08-10 + +### Fixed + +* [#791](https://github.com/sebastianbergmann/php-code-coverage/pull/791): Cache Warmer does not warm all caches + +## [9.1.1] - 2020-08-10 + +### Added + +* Added `SebastianBergmann\CodeCoverage::cacheDirectory()` method for querying where the cache writes its files + +## [9.1.0] - 2020-08-10 + +### Added + +* Implemented a persistent cache for information gathered using PHP-Parser based static analysis (hereinafter referred to as "cache") +* Added `SebastianBergmann\CodeCoverage::cacheStaticAnalysis(string $cacheDirectory)` method for enabling the cache; it will write its files to `$directory` +* Added `SebastianBergmann\CodeCoverage::doNotCacheStaticAnalysis` method for disabling the cache +* Added `SebastianBergmann\CodeCoverage::cachesStaticAnalysis()` method for querying whether the cache is enabled +* Added `SebastianBergmann\CodeCoverage\StaticAnalysis\CacheWarmer::warmCache()` method for warming the cache + +## [9.0.0] - 2020-08-07 + +### Added + +* [#761](https://github.com/sebastianbergmann/php-code-coverage/pull/761): Support for Branch Coverage and Path Coverage +* Added `SebastianBergmann\CodeCoverage\Driver\Driver::forLineCoverage()` for selecting the best available driver for line coverage +* Added `SebastianBergmann\CodeCoverage\Driver\Driver::forLineAndPathCoverage()` for selecting the best available driver for path coverage +* This component is now supported on PHP 8 +* This component now supports Xdebug 3 + +### Changed + +* [#746](https://github.com/sebastianbergmann/php-code-coverage/pull/746): Remove some ancient workarounds for very old Xdebug versions +* [#747](https://github.com/sebastianbergmann/php-code-coverage/pull/747): Use native filtering in PCOV and Xdebug drivers +* [#748](https://github.com/sebastianbergmann/php-code-coverage/pull/748): Store raw code coverage in value objects instead of arrays +* [#749](https://github.com/sebastianbergmann/php-code-coverage/pull/749): Store processed code coverage in value objects instead of arrays +* [#752](https://github.com/sebastianbergmann/php-code-coverage/pull/752): Rework how code coverage settings are propagated to the driver +* [#754](https://github.com/sebastianbergmann/php-code-coverage/pull/754): Implement collection of raw branch and path coverage +* [#755](https://github.com/sebastianbergmann/php-code-coverage/pull/755): Implement processing of raw branch and path coverage +* [#756](https://github.com/sebastianbergmann/php-code-coverage/pull/756): Improve handling of uncovered files +* `SebastianBergmann\CodeCoverage\Filter::addDirectoryToWhitelist()` has been renamed to `SebastianBergmann\CodeCoverage\Filter::includeDirectory()` +* `SebastianBergmann\CodeCoverage\Filter::addFilesToWhitelist()` has been renamed to `SebastianBergmann\CodeCoverage\Filter::includeFiles()` +* `SebastianBergmann\CodeCoverage\Filter::addFileToWhitelist()` has been renamed to `SebastianBergmann\CodeCoverage\Filter::includeFile()` +* `SebastianBergmann\CodeCoverage\Filter::removeDirectoryFromWhitelist()` has been renamed to `SebastianBergmann\CodeCoverage\Filter::excludeDirectory()` +* `SebastianBergmann\CodeCoverage\Filter::removeFileFromWhitelist()` has been renamed to `SebastianBergmann\CodeCoverage\Filter::excludeFile()` +* `SebastianBergmann\CodeCoverage\Filter::isFiltered()` has been renamed to `SebastianBergmann\CodeCoverage\Filter::isExcluded()` +* `SebastianBergmann\CodeCoverage\Filter::getWhitelist()` has been renamed to `SebastianBergmann\CodeCoverage\Filter::files()` +* The arguments for `CodeCoverage::__construct()` are no longer optional + +### Fixed + +* [#700](https://github.com/sebastianbergmann/php-code-coverage/pull/700): Throw an exception if code coverage fails to write to disk + +### Removed + +* `SebastianBergmann\CodeCoverage\CodeCoverage::setCacheTokens()` and `SebastianBergmann\CodeCoverage\CodeCoverage::getCacheTokens()` have been removed +* `SebastianBergmann\CodeCoverage\CodeCoverage::setCheckForUnintentionallyCoveredCode()` has been removed, please use `SebastianBergmann\CodeCoverage\CodeCoverage::enableCheckForUnintentionallyCoveredCode()` or `SebastianBergmann\CodeCoverage\CodeCoverage::disableCheckForUnintentionallyCoveredCode()` instead +* `SebastianBergmann\CodeCoverage\CodeCoverage::setSubclassesExcludedFromUnintentionallyCoveredCodeCheck()` has been removed, please use `SebastianBergmann\CodeCoverage\CodeCoverage::excludeSubclassesOfThisClassFromUnintentionallyCoveredCodeCheck()` instead +* `SebastianBergmann\CodeCoverage\CodeCoverage::setAddUncoveredFilesFromWhitelist()` has been removed, please use `SebastianBergmann\CodeCoverage\CodeCoverage::includeUncoveredFiles()` or `SebastianBergmann\CodeCoverage\CodeCoverage::excludeUncoveredFiles()` instead +* `SebastianBergmann\CodeCoverage\CodeCoverage::setProcessUncoveredFiles()` has been removed, please use `SebastianBergmann\CodeCoverage\CodeCoverage::processUncoveredFiles()` or `SebastianBergmann\CodeCoverage\CodeCoverage::doNotProcessUncoveredFiles()` instead +* `SebastianBergmann\CodeCoverage\CodeCoverage::setIgnoreDeprecatedCode()` has been removed, please use `SebastianBergmann\CodeCoverage\CodeCoverage::ignoreDeprecatedCode()` or `SebastianBergmann\CodeCoverage\CodeCoverage::doNotIgnoreDeprecatedCode()` instead +* `SebastianBergmann\CodeCoverage\CodeCoverage::setDisableIgnoredLines()` has been removed, please use `SebastianBergmann\CodeCoverage\CodeCoverage::enableAnnotationsForIgnoringCode()` or `SebastianBergmann\CodeCoverage\CodeCoverage::disableAnnotationsForIgnoringCode()` instead +* `SebastianBergmann\CodeCoverage\CodeCoverage::setCheckForMissingCoversAnnotation()` has been removed +* `SebastianBergmann\CodeCoverage\CodeCoverage::setCheckForUnexecutedCoveredCode()` has been removed +* `SebastianBergmann\CodeCoverage\CodeCoverage::setForceCoversAnnotation()` has been removed +* `SebastianBergmann\CodeCoverage\Filter::hasWhitelist()` has been removed, please use `SebastianBergmann\CodeCoverage\Filter::isEmpty()` instead +* `SebastianBergmann\CodeCoverage\Filter::getWhitelistedFiles()` has been removed +* `SebastianBergmann\CodeCoverage\Filter::setWhitelistedFiles()` has been removed + +## [8.0.2] - 2020-05-23 + +### Fixed + +* [#750](https://github.com/sebastianbergmann/php-code-coverage/pull/750): Inconsistent handling of namespaces +* [#751](https://github.com/sebastianbergmann/php-code-coverage/pull/751): Dead code is not highlighted correctly +* [#753](https://github.com/sebastianbergmann/php-code-coverage/issues/753): Do not use `$_SERVER['REQUEST_TIME']` because the test(ed) code might unset it + +## [8.0.1] - 2020-02-19 + +### Fixed + +* [#731](https://github.com/sebastianbergmann/php-code-coverage/pull/731): Confusing footer in the HTML report + +## [8.0.0] - 2020-02-07 + +### Fixed + +* [#721](https://github.com/sebastianbergmann/php-code-coverage/pull/721): Workaround for PHP bug [#79191](https://bugs.php.net/bug.php?id=79191) + +### Removed + +* This component is no longer supported on PHP 7.2 + +## [7.0.15] - 2021-07-26 + +### Changed + +* Bumped required version of php-token-stream + +## [7.0.14] - 2020-12-02 + +### Changed + +* [#837](https://github.com/sebastianbergmann/php-code-coverage/issues/837): Allow version 4 of php-token-stream + +## [7.0.13] - 2020-11-30 + +### Changed + +* Changed PHP version constraint in `composer.json` from `^7.2` to `>=7.2` to allow installation of this version of this library on PHP 8. However, this version of this library does not work on PHP 8. PHPUnit 8.5, which uses this version of this library, does not call into this library and instead shows a message that code coverage functionality is not available for PHPUnit 8.5 on PHP 8. + +## [7.0.12] - 2020-11-27 + +### Added + +* [#834](https://github.com/sebastianbergmann/php-code-coverage/issues/834): Support `XDEBUG_MODE` environment variable + +## [7.0.11] - 2020-11-27 + +### Added + +* Support for Xdebug 3 + +## [7.0.10] - 2019-11-20 + +### Fixed + +* [#710](https://github.com/sebastianbergmann/php-code-coverage/pull/710): Code Coverage does not work in PhpStorm + +## [7.0.9] - 2019-11-20 + +### Changed + +* [#709](https://github.com/sebastianbergmann/php-code-coverage/pull/709): Prioritize PCOV over Xdebug + +## [7.0.8] - 2019-09-17 + +### Changed + +* Updated bundled CSS/JavaScript components used for HTML report: Bootstrap 4.3.1, jQuery 3.4.1, and popper.js 1.15.0 + +## [7.0.7] - 2019-07-25 + +### Changed + +* Bumped required version of php-token-stream + +## [7.0.6] - 2019-07-08 + +### Changed + +* Bumped required version of php-token-stream + +## [7.0.5] - 2019-06-06 + +### Fixed + +* [#681](https://github.com/sebastianbergmann/php-code-coverage/pull/681): `use function` statements are not ignored + +## [7.0.4] - 2019-05-29 + +### Fixed + +* [#682](https://github.com/sebastianbergmann/php-code-coverage/pull/682): Code that is not executed is reported as being executed when using PCOV + +## [7.0.3] - 2019-02-26 + +### Fixed + +* [#671](https://github.com/sebastianbergmann/php-code-coverage/issues/671): `TypeError` when directory name is a number + +## [7.0.2] - 2019-02-15 + +### Changed + +* Updated bundled CSS/JavaScript components used for HTML report: Bootstrap 4.3.0 + +### Fixed + +* [#667](https://github.com/sebastianbergmann/php-code-coverage/pull/667): `TypeError` in PHP reporter + +## [7.0.1] - 2019-02-01 + +### Fixed + +* [#664](https://github.com/sebastianbergmann/php-code-coverage/issues/664): `TypeError` when whitelisted file does not exist + +## [7.0.0] - 2019-02-01 + +### Added + +* [#663](https://github.com/sebastianbergmann/php-code-coverage/pull/663): Support for PCOV + +### Fixed + +* [#654](https://github.com/sebastianbergmann/php-code-coverage/issues/654): HTML report fails to load assets +* [#655](https://github.com/sebastianbergmann/php-code-coverage/issues/655): Popin pops in outside of screen + +### Removed + +* This component is no longer supported on PHP 7.1 + +[9.2.31]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.30...9.2.31 +[9.2.30]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.29...9.2.30 +[9.2.29]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.28...9.2.29 +[9.2.28]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.27...9.2.28 +[9.2.27]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.26...9.2.27 +[9.2.26]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.25...9.2.26 +[9.2.25]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.24...9.2.25 +[9.2.24]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.23...9.2.24 +[9.2.23]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.22...9.2.23 +[9.2.22]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.21...9.2.22 +[9.2.21]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.20...9.2.21 +[9.2.20]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.19...9.2.20 +[9.2.19]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.18...9.2.19 +[9.2.18]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.17...9.2.18 +[9.2.17]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.16...9.2.17 +[9.2.16]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.15...9.2.16 +[9.2.15]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.14...9.2.15 +[9.2.14]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.13...9.2.14 +[9.2.13]: https://github.com/sebastianbergmann/php-code-coverage/compare/c011a0b6aaa4acd2f39b7f51fb4ad4442b6ec631...9.2.13 +[9.2.12]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.11...c011a0b6aaa4acd2f39b7f51fb4ad4442b6ec631 +[9.2.11]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.10...9.2.11 +[9.2.10]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.9...9.2.10 +[9.2.9]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.8...9.2.9 +[9.2.8]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.7...9.2.8 +[9.2.7]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.6...9.2.7 +[9.2.6]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.5...9.2.6 +[9.2.5]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.4...9.2.5 +[9.2.4]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.3...9.2.4 +[9.2.3]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.2...9.2.3 +[9.2.2]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.1...9.2.2 +[9.2.1]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.0...9.2.1 +[9.2.0]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.1.11...9.2.0 +[9.1.11]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.1.10...9.1.11 +[9.1.10]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.1.9...9.1.10 +[9.1.9]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.1.8...9.1.9 +[9.1.8]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.1.7...9.1.8 +[9.1.7]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.1.6...9.1.7 +[9.1.6]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.1.5...9.1.6 +[9.1.5]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.1.4...9.1.5 +[9.1.4]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.1.3...9.1.4 +[9.1.3]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.1.2...9.1.3 +[9.1.2]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.1.1...9.1.2 +[9.1.1]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.1.0...9.1.1 +[9.1.0]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.0.0...9.1.0 +[9.0.0]: https://github.com/sebastianbergmann/php-code-coverage/compare/8.0...9.0.0 +[8.0.2]: https://github.com/sebastianbergmann/php-code-coverage/compare/8.0.1...8.0.2 +[8.0.1]: https://github.com/sebastianbergmann/php-code-coverage/compare/8.0.0...8.0.1 +[8.0.0]: https://github.com/sebastianbergmann/php-code-coverage/compare/7.0.10...8.0.0 +[7.0.15]: https://github.com/sebastianbergmann/php-code-coverage/compare/7.0.14...7.0.15 +[7.0.14]: https://github.com/sebastianbergmann/php-code-coverage/compare/7.0.13...7.0.14 +[7.0.13]: https://github.com/sebastianbergmann/php-code-coverage/compare/7.0.12...7.0.13 +[7.0.12]: https://github.com/sebastianbergmann/php-code-coverage/compare/7.0.11...7.0.12 +[7.0.11]: https://github.com/sebastianbergmann/php-code-coverage/compare/7.0.10...7.0.11 +[7.0.10]: https://github.com/sebastianbergmann/php-code-coverage/compare/7.0.9...7.0.10 +[7.0.9]: https://github.com/sebastianbergmann/php-code-coverage/compare/7.0.8...7.0.9 +[7.0.8]: https://github.com/sebastianbergmann/php-code-coverage/compare/7.0.7...7.0.8 +[7.0.7]: https://github.com/sebastianbergmann/php-code-coverage/compare/7.0.6...7.0.7 +[7.0.6]: https://github.com/sebastianbergmann/php-code-coverage/compare/7.0.5...7.0.6 +[7.0.5]: https://github.com/sebastianbergmann/php-code-coverage/compare/7.0.4...7.0.5 +[7.0.4]: https://github.com/sebastianbergmann/php-code-coverage/compare/7.0.3...7.0.4 +[7.0.3]: https://github.com/sebastianbergmann/php-code-coverage/compare/7.0.2...7.0.3 +[7.0.2]: https://github.com/sebastianbergmann/php-code-coverage/compare/7.0.1...7.0.2 +[7.0.1]: https://github.com/sebastianbergmann/php-code-coverage/compare/7.0.0...7.0.1 +[7.0.0]: https://github.com/sebastianbergmann/php-code-coverage/compare/6.1.4...7.0.0 diff --git a/vendor/phpunit/php-code-coverage/LICENSE b/vendor/phpunit/php-code-coverage/LICENSE new file mode 100644 index 00000000..80e09f7b --- /dev/null +++ b/vendor/phpunit/php-code-coverage/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2009-2023, Sebastian Bergmann +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/phpunit/php-code-coverage/README.md b/vendor/phpunit/php-code-coverage/README.md new file mode 100644 index 00000000..53ce9b33 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/README.md @@ -0,0 +1,48 @@ +# phpunit/php-code-coverage + +[![Latest Stable Version](https://poser.pugx.org/phpunit/php-code-coverage/v/stable.png)](https://packagist.org/packages/phpunit/php-code-coverage) +[![CI Status](https://github.com/sebastianbergmann/php-code-coverage/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/php-code-coverage/actions) +[![Type Coverage](https://shepherd.dev/github/sebastianbergmann/php-code-coverage/coverage.svg)](https://shepherd.dev/github/sebastianbergmann/php-code-coverage) + +Provides collection, processing, and rendering functionality for PHP code coverage information. + +## Installation + +You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): + +``` +composer require phpunit/php-code-coverage +``` + +If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency: + +``` +composer require --dev phpunit/php-code-coverage +``` + +## Usage + +```php +includeDirectory('/path/to/directory'); + +$coverage = new CodeCoverage( + (new Selector)->forLineCoverage($filter), + $filter +); + +$coverage->start(''); + +// ... + +$coverage->stop(); + + +(new HtmlReport)->process($coverage, '/tmp/code-coverage-report'); +``` diff --git a/vendor/phpunit/php-code-coverage/composer.json b/vendor/phpunit/php-code-coverage/composer.json new file mode 100644 index 00000000..3646210f --- /dev/null +++ b/vendor/phpunit/php-code-coverage/composer.json @@ -0,0 +1,69 @@ +{ + "name": "phpunit/php-code-coverage", + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "type": "library", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy" + }, + "config": { + "platform": { + "php": "7.3.0" + }, + "optimize-autoloader": true, + "sort-packages": true + }, + "prefer-stable": true, + "require": { + "php": ">=7.3", + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.18 || ^5.0", + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0.3", + "sebastian/version": "^3.0.1", + "theseer/tokenizer": "^1.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "autoload-dev": { + "files": [ + "tests/TestCase.php", + "tests/_files/BankAccountTest.php" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "9.2-dev" + } + } +} diff --git a/vendor/phpunit/php-code-coverage/src/CodeCoverage.php b/vendor/phpunit/php-code-coverage/src/CodeCoverage.php new file mode 100644 index 00000000..ba4f1f8c --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/CodeCoverage.php @@ -0,0 +1,709 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage; + +use function array_diff; +use function array_diff_key; +use function array_flip; +use function array_keys; +use function array_merge; +use function array_unique; +use function array_values; +use function count; +use function explode; +use function get_class; +use function is_array; +use function sort; +use PHPUnit\Framework\TestCase; +use PHPUnit\Runner\PhptTestCase; +use PHPUnit\Util\Test; +use ReflectionClass; +use SebastianBergmann\CodeCoverage\Driver\Driver; +use SebastianBergmann\CodeCoverage\Node\Builder; +use SebastianBergmann\CodeCoverage\Node\Directory; +use SebastianBergmann\CodeCoverage\StaticAnalysis\CachingFileAnalyser; +use SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser; +use SebastianBergmann\CodeCoverage\StaticAnalysis\ParsingFileAnalyser; +use SebastianBergmann\CodeUnitReverseLookup\Wizard; + +/** + * Provides collection functionality for PHP code coverage information. + */ +final class CodeCoverage +{ + private const UNCOVERED_FILES = 'UNCOVERED_FILES'; + + /** + * @var Driver + */ + private $driver; + + /** + * @var Filter + */ + private $filter; + + /** + * @var Wizard + */ + private $wizard; + + /** + * @var bool + */ + private $checkForUnintentionallyCoveredCode = false; + + /** + * @var bool + */ + private $includeUncoveredFiles = true; + + /** + * @var bool + */ + private $processUncoveredFiles = false; + + /** + * @var bool + */ + private $ignoreDeprecatedCode = false; + + /** + * @var null|PhptTestCase|string|TestCase + */ + private $currentId; + + /** + * Code coverage data. + * + * @var ProcessedCodeCoverageData + */ + private $data; + + /** + * @var bool + */ + private $useAnnotationsForIgnoringCode = true; + + /** + * Test data. + * + * @var array + */ + private $tests = []; + + /** + * @psalm-var list + */ + private $parentClassesExcludedFromUnintentionallyCoveredCodeCheck = []; + + /** + * @var ?FileAnalyser + */ + private $analyser; + + /** + * @var ?string + */ + private $cacheDirectory; + + /** + * @var ?Directory + */ + private $cachedReport; + + public function __construct(Driver $driver, Filter $filter) + { + $this->driver = $driver; + $this->filter = $filter; + $this->data = new ProcessedCodeCoverageData; + $this->wizard = new Wizard; + } + + /** + * Returns the code coverage information as a graph of node objects. + */ + public function getReport(): Directory + { + if ($this->cachedReport === null) { + $this->cachedReport = (new Builder($this->analyser()))->build($this); + } + + return $this->cachedReport; + } + + /** + * Clears collected code coverage data. + */ + public function clear(): void + { + $this->currentId = null; + $this->data = new ProcessedCodeCoverageData; + $this->tests = []; + $this->cachedReport = null; + } + + /** + * @internal + */ + public function clearCache(): void + { + $this->cachedReport = null; + } + + /** + * Returns the filter object used. + */ + public function filter(): Filter + { + return $this->filter; + } + + /** + * Returns the collected code coverage data. + */ + public function getData(bool $raw = false): ProcessedCodeCoverageData + { + if (!$raw) { + if ($this->processUncoveredFiles) { + $this->processUncoveredFilesFromFilter(); + } elseif ($this->includeUncoveredFiles) { + $this->addUncoveredFilesFromFilter(); + } + } + + return $this->data; + } + + /** + * Sets the coverage data. + */ + public function setData(ProcessedCodeCoverageData $data): void + { + $this->data = $data; + } + + /** + * Returns the test data. + */ + public function getTests(): array + { + return $this->tests; + } + + /** + * Sets the test data. + */ + public function setTests(array $tests): void + { + $this->tests = $tests; + } + + /** + * Start collection of code coverage information. + * + * @param PhptTestCase|string|TestCase $id + */ + public function start($id, bool $clear = false): void + { + if ($clear) { + $this->clear(); + } + + $this->currentId = $id; + + $this->driver->start(); + + $this->cachedReport = null; + } + + /** + * Stop collection of code coverage information. + * + * @param array|false $linesToBeCovered + */ + public function stop(bool $append = true, $linesToBeCovered = [], array $linesToBeUsed = []): RawCodeCoverageData + { + if (!is_array($linesToBeCovered) && $linesToBeCovered !== false) { + throw new InvalidArgumentException( + '$linesToBeCovered must be an array or false' + ); + } + + $data = $this->driver->stop(); + $this->append($data, null, $append, $linesToBeCovered, $linesToBeUsed); + + $this->currentId = null; + $this->cachedReport = null; + + return $data; + } + + /** + * Appends code coverage data. + * + * @param PhptTestCase|string|TestCase $id + * @param array|false $linesToBeCovered + * + * @throws ReflectionException + * @throws TestIdMissingException + * @throws UnintentionallyCoveredCodeException + */ + public function append(RawCodeCoverageData $rawData, $id = null, bool $append = true, $linesToBeCovered = [], array $linesToBeUsed = []): void + { + if ($id === null) { + $id = $this->currentId; + } + + if ($id === null) { + throw new TestIdMissingException; + } + + $this->cachedReport = null; + + $this->applyFilter($rawData); + + $this->applyExecutableLinesFilter($rawData); + + if ($this->useAnnotationsForIgnoringCode) { + $this->applyIgnoredLinesFilter($rawData); + } + + $this->data->initializeUnseenData($rawData); + + if (!$append) { + return; + } + + if ($id !== self::UNCOVERED_FILES) { + $this->applyCoversAnnotationFilter( + $rawData, + $linesToBeCovered, + $linesToBeUsed + ); + + if (empty($rawData->lineCoverage())) { + return; + } + + $size = 'unknown'; + $status = -1; + $fromTestcase = false; + + if ($id instanceof TestCase) { + $fromTestcase = true; + $_size = $id->getSize(); + + if ($_size === Test::SMALL) { + $size = 'small'; + } elseif ($_size === Test::MEDIUM) { + $size = 'medium'; + } elseif ($_size === Test::LARGE) { + $size = 'large'; + } + + $status = $id->getStatus(); + $id = get_class($id) . '::' . $id->getName(); + } elseif ($id instanceof PhptTestCase) { + $fromTestcase = true; + $size = 'large'; + $id = $id->getName(); + } + + $this->tests[$id] = ['size' => $size, 'status' => $status, 'fromTestcase' => $fromTestcase]; + + $this->data->markCodeAsExecutedByTestCase($id, $rawData); + } + } + + /** + * Merges the data from another instance. + */ + public function merge(self $that): void + { + $this->filter->includeFiles( + $that->filter()->files() + ); + + $this->data->merge($that->data); + + $this->tests = array_merge($this->tests, $that->getTests()); + + $this->cachedReport = null; + } + + public function enableCheckForUnintentionallyCoveredCode(): void + { + $this->checkForUnintentionallyCoveredCode = true; + } + + public function disableCheckForUnintentionallyCoveredCode(): void + { + $this->checkForUnintentionallyCoveredCode = false; + } + + public function includeUncoveredFiles(): void + { + $this->includeUncoveredFiles = true; + } + + public function excludeUncoveredFiles(): void + { + $this->includeUncoveredFiles = false; + } + + public function processUncoveredFiles(): void + { + $this->processUncoveredFiles = true; + } + + public function doNotProcessUncoveredFiles(): void + { + $this->processUncoveredFiles = false; + } + + public function enableAnnotationsForIgnoringCode(): void + { + $this->useAnnotationsForIgnoringCode = true; + } + + public function disableAnnotationsForIgnoringCode(): void + { + $this->useAnnotationsForIgnoringCode = false; + } + + public function ignoreDeprecatedCode(): void + { + $this->ignoreDeprecatedCode = true; + } + + public function doNotIgnoreDeprecatedCode(): void + { + $this->ignoreDeprecatedCode = false; + } + + /** + * @psalm-assert-if-true !null $this->cacheDirectory + */ + public function cachesStaticAnalysis(): bool + { + return $this->cacheDirectory !== null; + } + + public function cacheStaticAnalysis(string $directory): void + { + $this->cacheDirectory = $directory; + } + + public function doNotCacheStaticAnalysis(): void + { + $this->cacheDirectory = null; + } + + /** + * @throws StaticAnalysisCacheNotConfiguredException + */ + public function cacheDirectory(): string + { + if (!$this->cachesStaticAnalysis()) { + throw new StaticAnalysisCacheNotConfiguredException( + 'The static analysis cache is not configured' + ); + } + + return $this->cacheDirectory; + } + + /** + * @psalm-param class-string $className + */ + public function excludeSubclassesOfThisClassFromUnintentionallyCoveredCodeCheck(string $className): void + { + $this->parentClassesExcludedFromUnintentionallyCoveredCodeCheck[] = $className; + } + + public function enableBranchAndPathCoverage(): void + { + $this->driver->enableBranchAndPathCoverage(); + } + + public function disableBranchAndPathCoverage(): void + { + $this->driver->disableBranchAndPathCoverage(); + } + + public function collectsBranchAndPathCoverage(): bool + { + return $this->driver->collectsBranchAndPathCoverage(); + } + + public function detectsDeadCode(): bool + { + return $this->driver->detectsDeadCode(); + } + + /** + * Applies the @covers annotation filtering. + * + * @param array|false $linesToBeCovered + * + * @throws ReflectionException + * @throws UnintentionallyCoveredCodeException + */ + private function applyCoversAnnotationFilter(RawCodeCoverageData $rawData, $linesToBeCovered, array $linesToBeUsed): void + { + if ($linesToBeCovered === false) { + $rawData->clear(); + + return; + } + + if (empty($linesToBeCovered)) { + return; + } + + if ($this->checkForUnintentionallyCoveredCode && + (!$this->currentId instanceof TestCase || + (!$this->currentId->isMedium() && !$this->currentId->isLarge()))) { + $this->performUnintentionallyCoveredCodeCheck($rawData, $linesToBeCovered, $linesToBeUsed); + } + + $rawLineData = $rawData->lineCoverage(); + $filesWithNoCoverage = array_diff_key($rawLineData, $linesToBeCovered); + + foreach (array_keys($filesWithNoCoverage) as $fileWithNoCoverage) { + $rawData->removeCoverageDataForFile($fileWithNoCoverage); + } + + if (is_array($linesToBeCovered)) { + foreach ($linesToBeCovered as $fileToBeCovered => $includedLines) { + $rawData->keepLineCoverageDataOnlyForLines($fileToBeCovered, $includedLines); + $rawData->keepFunctionCoverageDataOnlyForLines($fileToBeCovered, $includedLines); + } + } + } + + private function applyFilter(RawCodeCoverageData $data): void + { + if ($this->filter->isEmpty()) { + return; + } + + foreach (array_keys($data->lineCoverage()) as $filename) { + if ($this->filter->isExcluded($filename)) { + $data->removeCoverageDataForFile($filename); + } + } + } + + private function applyExecutableLinesFilter(RawCodeCoverageData $data): void + { + foreach (array_keys($data->lineCoverage()) as $filename) { + if (!$this->filter->isFile($filename)) { + continue; + } + + $linesToBranchMap = $this->analyser()->executableLinesIn($filename); + + $data->keepLineCoverageDataOnlyForLines( + $filename, + array_keys($linesToBranchMap) + ); + + $data->markExecutableLineByBranch( + $filename, + $linesToBranchMap + ); + } + } + + private function applyIgnoredLinesFilter(RawCodeCoverageData $data): void + { + foreach (array_keys($data->lineCoverage()) as $filename) { + if (!$this->filter->isFile($filename)) { + continue; + } + + $data->removeCoverageDataForLines( + $filename, + $this->analyser()->ignoredLinesFor($filename) + ); + } + } + + /** + * @throws UnintentionallyCoveredCodeException + */ + private function addUncoveredFilesFromFilter(): void + { + $uncoveredFiles = array_diff( + $this->filter->files(), + $this->data->coveredFiles() + ); + + foreach ($uncoveredFiles as $uncoveredFile) { + if ($this->filter->isFile($uncoveredFile)) { + $this->append( + RawCodeCoverageData::fromUncoveredFile( + $uncoveredFile, + $this->analyser() + ), + self::UNCOVERED_FILES + ); + } + } + } + + /** + * @throws UnintentionallyCoveredCodeException + */ + private function processUncoveredFilesFromFilter(): void + { + $uncoveredFiles = array_diff( + $this->filter->files(), + $this->data->coveredFiles() + ); + + $this->driver->start(); + + foreach ($uncoveredFiles as $uncoveredFile) { + if ($this->filter->isFile($uncoveredFile)) { + include_once $uncoveredFile; + } + } + + $this->append($this->driver->stop(), self::UNCOVERED_FILES); + } + + /** + * @throws ReflectionException + * @throws UnintentionallyCoveredCodeException + */ + private function performUnintentionallyCoveredCodeCheck(RawCodeCoverageData $data, array $linesToBeCovered, array $linesToBeUsed): void + { + $allowedLines = $this->getAllowedLines( + $linesToBeCovered, + $linesToBeUsed + ); + + $unintentionallyCoveredUnits = []; + + foreach ($data->lineCoverage() as $file => $_data) { + foreach ($_data as $line => $flag) { + if ($flag === 1 && !isset($allowedLines[$file][$line])) { + $unintentionallyCoveredUnits[] = $this->wizard->lookup($file, $line); + } + } + } + + $unintentionallyCoveredUnits = $this->processUnintentionallyCoveredUnits($unintentionallyCoveredUnits); + + if (!empty($unintentionallyCoveredUnits)) { + throw new UnintentionallyCoveredCodeException( + $unintentionallyCoveredUnits + ); + } + } + + private function getAllowedLines(array $linesToBeCovered, array $linesToBeUsed): array + { + $allowedLines = []; + + foreach (array_keys($linesToBeCovered) as $file) { + if (!isset($allowedLines[$file])) { + $allowedLines[$file] = []; + } + + $allowedLines[$file] = array_merge( + $allowedLines[$file], + $linesToBeCovered[$file] + ); + } + + foreach (array_keys($linesToBeUsed) as $file) { + if (!isset($allowedLines[$file])) { + $allowedLines[$file] = []; + } + + $allowedLines[$file] = array_merge( + $allowedLines[$file], + $linesToBeUsed[$file] + ); + } + + foreach (array_keys($allowedLines) as $file) { + $allowedLines[$file] = array_flip( + array_unique($allowedLines[$file]) + ); + } + + return $allowedLines; + } + + /** + * @throws ReflectionException + */ + private function processUnintentionallyCoveredUnits(array $unintentionallyCoveredUnits): array + { + $unintentionallyCoveredUnits = array_unique($unintentionallyCoveredUnits); + sort($unintentionallyCoveredUnits); + + foreach (array_keys($unintentionallyCoveredUnits) as $k => $v) { + $unit = explode('::', $unintentionallyCoveredUnits[$k]); + + if (count($unit) !== 2) { + continue; + } + + try { + $class = new ReflectionClass($unit[0]); + + foreach ($this->parentClassesExcludedFromUnintentionallyCoveredCodeCheck as $parentClass) { + if ($class->isSubclassOf($parentClass)) { + unset($unintentionallyCoveredUnits[$k]); + + break; + } + } + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + $e->getCode(), + $e + ); + } + } + + return array_values($unintentionallyCoveredUnits); + } + + private function analyser(): FileAnalyser + { + if ($this->analyser !== null) { + return $this->analyser; + } + + $this->analyser = new ParsingFileAnalyser( + $this->useAnnotationsForIgnoringCode, + $this->ignoreDeprecatedCode + ); + + if ($this->cachesStaticAnalysis()) { + $this->analyser = new CachingFileAnalyser( + $this->cacheDirectory, + $this->analyser, + $this->useAnnotationsForIgnoringCode, + $this->ignoreDeprecatedCode + ); + } + + return $this->analyser; + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Driver/Driver.php b/vendor/phpunit/php-code-coverage/src/Driver/Driver.php new file mode 100644 index 00000000..dc2de68f --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Driver/Driver.php @@ -0,0 +1,167 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Driver; + +use function sprintf; +use SebastianBergmann\CodeCoverage\BranchAndPathCoverageNotSupportedException; +use SebastianBergmann\CodeCoverage\DeadCodeDetectionNotSupportedException; +use SebastianBergmann\CodeCoverage\Filter; +use SebastianBergmann\CodeCoverage\NoCodeCoverageDriverAvailableException; +use SebastianBergmann\CodeCoverage\NoCodeCoverageDriverWithPathCoverageSupportAvailableException; +use SebastianBergmann\CodeCoverage\RawCodeCoverageData; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +abstract class Driver +{ + /** + * @var int + * + * @see http://xdebug.org/docs/code_coverage + */ + public const LINE_NOT_EXECUTABLE = -2; + + /** + * @var int + * + * @see http://xdebug.org/docs/code_coverage + */ + public const LINE_NOT_EXECUTED = -1; + + /** + * @var int + * + * @see http://xdebug.org/docs/code_coverage + */ + public const LINE_EXECUTED = 1; + + /** + * @var int + * + * @see http://xdebug.org/docs/code_coverage + */ + public const BRANCH_NOT_HIT = 0; + + /** + * @var int + * + * @see http://xdebug.org/docs/code_coverage + */ + public const BRANCH_HIT = 1; + + /** + * @var bool + */ + private $collectBranchAndPathCoverage = false; + + /** + * @var bool + */ + private $detectDeadCode = false; + + /** + * @throws NoCodeCoverageDriverAvailableException + * @throws PcovNotAvailableException + * @throws PhpdbgNotAvailableException + * @throws Xdebug2NotEnabledException + * @throws Xdebug3NotEnabledException + * @throws XdebugNotAvailableException + * + * @deprecated Use DriverSelector::forLineCoverage() instead + */ + public static function forLineCoverage(Filter $filter): self + { + return (new Selector)->forLineCoverage($filter); + } + + /** + * @throws NoCodeCoverageDriverWithPathCoverageSupportAvailableException + * @throws Xdebug2NotEnabledException + * @throws Xdebug3NotEnabledException + * @throws XdebugNotAvailableException + * + * @deprecated Use DriverSelector::forLineAndPathCoverage() instead + */ + public static function forLineAndPathCoverage(Filter $filter): self + { + return (new Selector)->forLineAndPathCoverage($filter); + } + + public function canCollectBranchAndPathCoverage(): bool + { + return false; + } + + public function collectsBranchAndPathCoverage(): bool + { + return $this->collectBranchAndPathCoverage; + } + + /** + * @throws BranchAndPathCoverageNotSupportedException + */ + public function enableBranchAndPathCoverage(): void + { + if (!$this->canCollectBranchAndPathCoverage()) { + throw new BranchAndPathCoverageNotSupportedException( + sprintf( + '%s does not support branch and path coverage', + $this->nameAndVersion() + ) + ); + } + + $this->collectBranchAndPathCoverage = true; + } + + public function disableBranchAndPathCoverage(): void + { + $this->collectBranchAndPathCoverage = false; + } + + public function canDetectDeadCode(): bool + { + return false; + } + + public function detectsDeadCode(): bool + { + return $this->detectDeadCode; + } + + /** + * @throws DeadCodeDetectionNotSupportedException + */ + public function enableDeadCodeDetection(): void + { + if (!$this->canDetectDeadCode()) { + throw new DeadCodeDetectionNotSupportedException( + sprintf( + '%s does not support dead code detection', + $this->nameAndVersion() + ) + ); + } + + $this->detectDeadCode = true; + } + + public function disableDeadCodeDetection(): void + { + $this->detectDeadCode = false; + } + + abstract public function nameAndVersion(): string; + + abstract public function start(): void; + + abstract public function stop(): RawCodeCoverageData; +} diff --git a/vendor/phpunit/php-code-coverage/src/Driver/PcovDriver.php b/vendor/phpunit/php-code-coverage/src/Driver/PcovDriver.php new file mode 100644 index 00000000..c30b30c4 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Driver/PcovDriver.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Driver; + +use const pcov\inclusive; +use function array_intersect; +use function extension_loaded; +use function pcov\clear; +use function pcov\collect; +use function pcov\start; +use function pcov\stop; +use function pcov\waiting; +use function phpversion; +use SebastianBergmann\CodeCoverage\Filter; +use SebastianBergmann\CodeCoverage\RawCodeCoverageData; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class PcovDriver extends Driver +{ + /** + * @var Filter + */ + private $filter; + + /** + * @throws PcovNotAvailableException + */ + public function __construct(Filter $filter) + { + if (!extension_loaded('pcov')) { + throw new PcovNotAvailableException; + } + + $this->filter = $filter; + } + + public function start(): void + { + start(); + } + + public function stop(): RawCodeCoverageData + { + stop(); + + $filesToCollectCoverageFor = waiting(); + $collected = []; + + if ($filesToCollectCoverageFor) { + if (!$this->filter->isEmpty()) { + $filesToCollectCoverageFor = array_intersect($filesToCollectCoverageFor, $this->filter->files()); + } + + $collected = collect(inclusive, $filesToCollectCoverageFor); + + clear(); + } + + return RawCodeCoverageData::fromXdebugWithoutPathCoverage($collected); + } + + public function nameAndVersion(): string + { + return 'PCOV ' . phpversion('pcov'); + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Driver/PhpdbgDriver.php b/vendor/phpunit/php-code-coverage/src/Driver/PhpdbgDriver.php new file mode 100644 index 00000000..7ee13b00 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Driver/PhpdbgDriver.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Driver; + +use const PHP_SAPI; +use const PHP_VERSION; +use function array_diff; +use function array_keys; +use function array_merge; +use function get_included_files; +use function phpdbg_end_oplog; +use function phpdbg_get_executable; +use function phpdbg_start_oplog; +use SebastianBergmann\CodeCoverage\RawCodeCoverageData; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class PhpdbgDriver extends Driver +{ + /** + * @throws PhpdbgNotAvailableException + */ + public function __construct() + { + if (PHP_SAPI !== 'phpdbg') { + throw new PhpdbgNotAvailableException; + } + } + + public function start(): void + { + phpdbg_start_oplog(); + } + + public function stop(): RawCodeCoverageData + { + static $fetchedLines = []; + + $dbgData = phpdbg_end_oplog(); + + if ($fetchedLines === []) { + $sourceLines = phpdbg_get_executable(); + } else { + $newFiles = array_diff(get_included_files(), array_keys($fetchedLines)); + + $sourceLines = []; + + if ($newFiles) { + $sourceLines = phpdbg_get_executable(['files' => $newFiles]); + } + } + + foreach ($sourceLines as $file => $lines) { + foreach ($lines as $lineNo => $numExecuted) { + $sourceLines[$file][$lineNo] = self::LINE_NOT_EXECUTED; + } + } + + $fetchedLines = array_merge($fetchedLines, $sourceLines); + + return RawCodeCoverageData::fromXdebugWithoutPathCoverage( + $this->detectExecutedLines($fetchedLines, $dbgData) + ); + } + + public function nameAndVersion(): string + { + return 'PHPDBG ' . PHP_VERSION; + } + + private function detectExecutedLines(array $sourceLines, array $dbgData): array + { + foreach ($dbgData as $file => $coveredLines) { + foreach ($coveredLines as $lineNo => $numExecuted) { + // phpdbg also reports $lineNo=0 when e.g. exceptions get thrown. + // make sure we only mark lines executed which are actually executable. + if (isset($sourceLines[$file][$lineNo])) { + $sourceLines[$file][$lineNo] = self::LINE_EXECUTED; + } + } + } + + return $sourceLines; + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Driver/Selector.php b/vendor/phpunit/php-code-coverage/src/Driver/Selector.php new file mode 100644 index 00000000..936ee898 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Driver/Selector.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Driver; + +use function phpversion; +use function version_compare; +use SebastianBergmann\CodeCoverage\Filter; +use SebastianBergmann\CodeCoverage\NoCodeCoverageDriverAvailableException; +use SebastianBergmann\CodeCoverage\NoCodeCoverageDriverWithPathCoverageSupportAvailableException; +use SebastianBergmann\Environment\Runtime; + +final class Selector +{ + /** + * @throws NoCodeCoverageDriverAvailableException + * @throws PcovNotAvailableException + * @throws PhpdbgNotAvailableException + * @throws Xdebug2NotEnabledException + * @throws Xdebug3NotEnabledException + * @throws XdebugNotAvailableException + */ + public function forLineCoverage(Filter $filter): Driver + { + $runtime = new Runtime; + + if ($runtime->hasPHPDBGCodeCoverage()) { + return new PhpdbgDriver; + } + + if ($runtime->hasPCOV()) { + return new PcovDriver($filter); + } + + if ($runtime->hasXdebug()) { + if (version_compare(phpversion('xdebug'), '3', '>=')) { + $driver = new Xdebug3Driver($filter); + } else { + $driver = new Xdebug2Driver($filter); + } + + $driver->enableDeadCodeDetection(); + + return $driver; + } + + throw new NoCodeCoverageDriverAvailableException; + } + + /** + * @throws NoCodeCoverageDriverWithPathCoverageSupportAvailableException + * @throws Xdebug2NotEnabledException + * @throws Xdebug3NotEnabledException + * @throws XdebugNotAvailableException + */ + public function forLineAndPathCoverage(Filter $filter): Driver + { + if ((new Runtime)->hasXdebug()) { + if (version_compare(phpversion('xdebug'), '3', '>=')) { + $driver = new Xdebug3Driver($filter); + } else { + $driver = new Xdebug2Driver($filter); + } + + $driver->enableDeadCodeDetection(); + $driver->enableBranchAndPathCoverage(); + + return $driver; + } + + throw new NoCodeCoverageDriverWithPathCoverageSupportAvailableException; + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Driver/Xdebug2Driver.php b/vendor/phpunit/php-code-coverage/src/Driver/Xdebug2Driver.php new file mode 100644 index 00000000..74cbbfbc --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Driver/Xdebug2Driver.php @@ -0,0 +1,128 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Driver; + +use const XDEBUG_CC_BRANCH_CHECK; +use const XDEBUG_CC_DEAD_CODE; +use const XDEBUG_CC_UNUSED; +use const XDEBUG_FILTER_CODE_COVERAGE; +use const XDEBUG_PATH_INCLUDE; +use const XDEBUG_PATH_WHITELIST; +use function defined; +use function extension_loaded; +use function ini_get; +use function phpversion; +use function sprintf; +use function version_compare; +use function xdebug_get_code_coverage; +use function xdebug_set_filter; +use function xdebug_start_code_coverage; +use function xdebug_stop_code_coverage; +use SebastianBergmann\CodeCoverage\Filter; +use SebastianBergmann\CodeCoverage\RawCodeCoverageData; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class Xdebug2Driver extends Driver +{ + /** + * @var bool + */ + private $pathCoverageIsMixedCoverage; + + /** + * @throws WrongXdebugVersionException + * @throws Xdebug2NotEnabledException + * @throws XdebugNotAvailableException + */ + public function __construct(Filter $filter) + { + if (!extension_loaded('xdebug')) { + throw new XdebugNotAvailableException; + } + + if (version_compare(phpversion('xdebug'), '3', '>=')) { + throw new WrongXdebugVersionException( + sprintf( + 'This driver requires Xdebug 2 but version %s is loaded', + phpversion('xdebug') + ) + ); + } + + if (!ini_get('xdebug.coverage_enable')) { + throw new Xdebug2NotEnabledException; + } + + if (!$filter->isEmpty()) { + if (defined('XDEBUG_PATH_WHITELIST')) { + $listType = XDEBUG_PATH_WHITELIST; + } else { + $listType = XDEBUG_PATH_INCLUDE; + } + + xdebug_set_filter( + XDEBUG_FILTER_CODE_COVERAGE, + $listType, + $filter->files() + ); + } + + $this->pathCoverageIsMixedCoverage = version_compare(phpversion('xdebug'), '2.9.6', '<'); + } + + public function canCollectBranchAndPathCoverage(): bool + { + return true; + } + + public function canDetectDeadCode(): bool + { + return true; + } + + public function start(): void + { + $flags = XDEBUG_CC_UNUSED; + + if ($this->detectsDeadCode() || $this->collectsBranchAndPathCoverage()) { + $flags |= XDEBUG_CC_DEAD_CODE; + } + + if ($this->collectsBranchAndPathCoverage()) { + $flags |= XDEBUG_CC_BRANCH_CHECK; + } + + xdebug_start_code_coverage($flags); + } + + public function stop(): RawCodeCoverageData + { + $data = xdebug_get_code_coverage(); + + xdebug_stop_code_coverage(); + + if ($this->collectsBranchAndPathCoverage()) { + if ($this->pathCoverageIsMixedCoverage) { + return RawCodeCoverageData::fromXdebugWithMixedCoverage($data); + } + + return RawCodeCoverageData::fromXdebugWithPathCoverage($data); + } + + return RawCodeCoverageData::fromXdebugWithoutPathCoverage($data); + } + + public function nameAndVersion(): string + { + return 'Xdebug ' . phpversion('xdebug'); + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Driver/Xdebug3Driver.php b/vendor/phpunit/php-code-coverage/src/Driver/Xdebug3Driver.php new file mode 100644 index 00000000..b85db403 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Driver/Xdebug3Driver.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Driver; + +use const XDEBUG_CC_BRANCH_CHECK; +use const XDEBUG_CC_DEAD_CODE; +use const XDEBUG_CC_UNUSED; +use const XDEBUG_FILTER_CODE_COVERAGE; +use const XDEBUG_PATH_INCLUDE; +use function explode; +use function extension_loaded; +use function getenv; +use function in_array; +use function ini_get; +use function phpversion; +use function sprintf; +use function version_compare; +use function xdebug_get_code_coverage; +use function xdebug_set_filter; +use function xdebug_start_code_coverage; +use function xdebug_stop_code_coverage; +use SebastianBergmann\CodeCoverage\Filter; +use SebastianBergmann\CodeCoverage\RawCodeCoverageData; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class Xdebug3Driver extends Driver +{ + /** + * @throws WrongXdebugVersionException + * @throws Xdebug3NotEnabledException + * @throws XdebugNotAvailableException + */ + public function __construct(Filter $filter) + { + if (!extension_loaded('xdebug')) { + throw new XdebugNotAvailableException; + } + + if (version_compare(phpversion('xdebug'), '3', '<')) { + throw new WrongXdebugVersionException( + sprintf( + 'This driver requires Xdebug 3 but version %s is loaded', + phpversion('xdebug') + ) + ); + } + + $mode = getenv('XDEBUG_MODE'); + + if ($mode === false || $mode === '') { + $mode = ini_get('xdebug.mode'); + } + + if ($mode === false || + !in_array('coverage', explode(',', $mode), true)) { + throw new Xdebug3NotEnabledException; + } + + if (!$filter->isEmpty()) { + xdebug_set_filter( + XDEBUG_FILTER_CODE_COVERAGE, + XDEBUG_PATH_INCLUDE, + $filter->files() + ); + } + } + + public function canCollectBranchAndPathCoverage(): bool + { + return true; + } + + public function canDetectDeadCode(): bool + { + return true; + } + + public function start(): void + { + $flags = XDEBUG_CC_UNUSED; + + if ($this->detectsDeadCode() || $this->collectsBranchAndPathCoverage()) { + $flags |= XDEBUG_CC_DEAD_CODE; + } + + if ($this->collectsBranchAndPathCoverage()) { + $flags |= XDEBUG_CC_BRANCH_CHECK; + } + + xdebug_start_code_coverage($flags); + } + + public function stop(): RawCodeCoverageData + { + $data = xdebug_get_code_coverage(); + + xdebug_stop_code_coverage(); + + if ($this->collectsBranchAndPathCoverage()) { + return RawCodeCoverageData::fromXdebugWithPathCoverage($data); + } + + return RawCodeCoverageData::fromXdebugWithoutPathCoverage($data); + } + + public function nameAndVersion(): string + { + return 'Xdebug ' . phpversion('xdebug'); + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Exception/BranchAndPathCoverageNotSupportedException.php b/vendor/phpunit/php-code-coverage/src/Exception/BranchAndPathCoverageNotSupportedException.php new file mode 100644 index 00000000..ab208919 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Exception/BranchAndPathCoverageNotSupportedException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage; + +use RuntimeException; + +final class BranchAndPathCoverageNotSupportedException extends RuntimeException implements Exception +{ +} diff --git a/vendor/phpunit/php-code-coverage/src/Exception/DeadCodeDetectionNotSupportedException.php b/vendor/phpunit/php-code-coverage/src/Exception/DeadCodeDetectionNotSupportedException.php new file mode 100644 index 00000000..d3600648 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Exception/DeadCodeDetectionNotSupportedException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage; + +use RuntimeException; + +final class DeadCodeDetectionNotSupportedException extends RuntimeException implements Exception +{ +} diff --git a/vendor/phpunit/php-code-coverage/src/Exception/DirectoryCouldNotBeCreatedException.php b/vendor/phpunit/php-code-coverage/src/Exception/DirectoryCouldNotBeCreatedException.php new file mode 100644 index 00000000..fdd9bfdf --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Exception/DirectoryCouldNotBeCreatedException.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Util; + +use RuntimeException; +use SebastianBergmann\CodeCoverage\Exception; + +final class DirectoryCouldNotBeCreatedException extends RuntimeException implements Exception +{ +} diff --git a/vendor/phpunit/php-code-coverage/src/Exception/Exception.php b/vendor/phpunit/php-code-coverage/src/Exception/Exception.php new file mode 100644 index 00000000..28dc48b8 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Exception/Exception.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage; + +use Throwable; + +interface Exception extends Throwable +{ +} diff --git a/vendor/phpunit/php-code-coverage/src/Exception/InvalidArgumentException.php b/vendor/phpunit/php-code-coverage/src/Exception/InvalidArgumentException.php new file mode 100644 index 00000000..17e4b707 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Exception/InvalidArgumentException.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage; + +final class InvalidArgumentException extends \InvalidArgumentException implements Exception +{ +} diff --git a/vendor/phpunit/php-code-coverage/src/Exception/NoCodeCoverageDriverAvailableException.php b/vendor/phpunit/php-code-coverage/src/Exception/NoCodeCoverageDriverAvailableException.php new file mode 100644 index 00000000..b1494e26 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Exception/NoCodeCoverageDriverAvailableException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage; + +use RuntimeException; + +final class NoCodeCoverageDriverAvailableException extends RuntimeException implements Exception +{ + public function __construct() + { + parent::__construct('No code coverage driver available'); + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Exception/NoCodeCoverageDriverWithPathCoverageSupportAvailableException.php b/vendor/phpunit/php-code-coverage/src/Exception/NoCodeCoverageDriverWithPathCoverageSupportAvailableException.php new file mode 100644 index 00000000..0065b740 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Exception/NoCodeCoverageDriverWithPathCoverageSupportAvailableException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage; + +use RuntimeException; + +final class NoCodeCoverageDriverWithPathCoverageSupportAvailableException extends RuntimeException implements Exception +{ + public function __construct() + { + parent::__construct('No code coverage driver with path coverage support available'); + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Exception/ParserException.php b/vendor/phpunit/php-code-coverage/src/Exception/ParserException.php new file mode 100644 index 00000000..a907e34e --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Exception/ParserException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage; + +use RuntimeException; + +final class ParserException extends RuntimeException implements Exception +{ +} diff --git a/vendor/phpunit/php-code-coverage/src/Exception/PathExistsButIsNotDirectoryException.php b/vendor/phpunit/php-code-coverage/src/Exception/PathExistsButIsNotDirectoryException.php new file mode 100644 index 00000000..54bd73f5 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Exception/PathExistsButIsNotDirectoryException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Driver; + +use function sprintf; +use RuntimeException; +use SebastianBergmann\CodeCoverage\Exception; + +final class PathExistsButIsNotDirectoryException extends RuntimeException implements Exception +{ + public function __construct(string $path) + { + parent::__construct(sprintf('"%s" exists but is not a directory', $path)); + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Exception/PcovNotAvailableException.php b/vendor/phpunit/php-code-coverage/src/Exception/PcovNotAvailableException.php new file mode 100644 index 00000000..2f0a66e5 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Exception/PcovNotAvailableException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Driver; + +use RuntimeException; +use SebastianBergmann\CodeCoverage\Exception; + +final class PcovNotAvailableException extends RuntimeException implements Exception +{ + public function __construct() + { + parent::__construct('The PCOV extension is not available'); + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Exception/PhpdbgNotAvailableException.php b/vendor/phpunit/php-code-coverage/src/Exception/PhpdbgNotAvailableException.php new file mode 100644 index 00000000..bfb183d5 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Exception/PhpdbgNotAvailableException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Driver; + +use RuntimeException; +use SebastianBergmann\CodeCoverage\Exception; + +final class PhpdbgNotAvailableException extends RuntimeException implements Exception +{ + public function __construct() + { + parent::__construct('The PHPDBG SAPI is not available'); + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Exception/ReflectionException.php b/vendor/phpunit/php-code-coverage/src/Exception/ReflectionException.php new file mode 100644 index 00000000..78db430b --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Exception/ReflectionException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage; + +use RuntimeException; + +final class ReflectionException extends RuntimeException implements Exception +{ +} diff --git a/vendor/phpunit/php-code-coverage/src/Exception/ReportAlreadyFinalizedException.php b/vendor/phpunit/php-code-coverage/src/Exception/ReportAlreadyFinalizedException.php new file mode 100644 index 00000000..0481f161 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Exception/ReportAlreadyFinalizedException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage; + +use RuntimeException; + +final class ReportAlreadyFinalizedException extends RuntimeException implements Exception +{ + public function __construct() + { + parent::__construct('The code coverage report has already been finalized'); + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Exception/StaticAnalysisCacheNotConfiguredException.php b/vendor/phpunit/php-code-coverage/src/Exception/StaticAnalysisCacheNotConfiguredException.php new file mode 100644 index 00000000..fd58fd6b --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Exception/StaticAnalysisCacheNotConfiguredException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage; + +use RuntimeException; + +final class StaticAnalysisCacheNotConfiguredException extends RuntimeException implements Exception +{ +} diff --git a/vendor/phpunit/php-code-coverage/src/Exception/TestIdMissingException.php b/vendor/phpunit/php-code-coverage/src/Exception/TestIdMissingException.php new file mode 100644 index 00000000..4cc3e0c2 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Exception/TestIdMissingException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage; + +use RuntimeException; + +final class TestIdMissingException extends RuntimeException implements Exception +{ + public function __construct() + { + parent::__construct('Test ID is missing'); + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Exception/UnintentionallyCoveredCodeException.php b/vendor/phpunit/php-code-coverage/src/Exception/UnintentionallyCoveredCodeException.php new file mode 100644 index 00000000..cb7a975f --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Exception/UnintentionallyCoveredCodeException.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage; + +use RuntimeException; + +final class UnintentionallyCoveredCodeException extends RuntimeException implements Exception +{ + /** + * @var array + */ + private $unintentionallyCoveredUnits; + + public function __construct(array $unintentionallyCoveredUnits) + { + $this->unintentionallyCoveredUnits = $unintentionallyCoveredUnits; + + parent::__construct($this->toString()); + } + + public function getUnintentionallyCoveredUnits(): array + { + return $this->unintentionallyCoveredUnits; + } + + private function toString(): string + { + $message = ''; + + foreach ($this->unintentionallyCoveredUnits as $unit) { + $message .= '- ' . $unit . "\n"; + } + + return $message; + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Exception/WriteOperationFailedException.php b/vendor/phpunit/php-code-coverage/src/Exception/WriteOperationFailedException.php new file mode 100644 index 00000000..be549e17 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Exception/WriteOperationFailedException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Driver; + +use function sprintf; +use RuntimeException; +use SebastianBergmann\CodeCoverage\Exception; + +final class WriteOperationFailedException extends RuntimeException implements Exception +{ + public function __construct(string $path) + { + parent::__construct(sprintf('Cannot write to "%s"', $path)); + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Exception/WrongXdebugVersionException.php b/vendor/phpunit/php-code-coverage/src/Exception/WrongXdebugVersionException.php new file mode 100644 index 00000000..6e8f10a9 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Exception/WrongXdebugVersionException.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Driver; + +use RuntimeException; +use SebastianBergmann\CodeCoverage\Exception; + +final class WrongXdebugVersionException extends RuntimeException implements Exception +{ +} diff --git a/vendor/phpunit/php-code-coverage/src/Exception/Xdebug2NotEnabledException.php b/vendor/phpunit/php-code-coverage/src/Exception/Xdebug2NotEnabledException.php new file mode 100644 index 00000000..3039e77c --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Exception/Xdebug2NotEnabledException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Driver; + +use RuntimeException; +use SebastianBergmann\CodeCoverage\Exception; + +final class Xdebug2NotEnabledException extends RuntimeException implements Exception +{ + public function __construct() + { + parent::__construct('xdebug.coverage_enable=On has to be set'); + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Exception/Xdebug3NotEnabledException.php b/vendor/phpunit/php-code-coverage/src/Exception/Xdebug3NotEnabledException.php new file mode 100644 index 00000000..5d3b106c --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Exception/Xdebug3NotEnabledException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Driver; + +use RuntimeException; +use SebastianBergmann\CodeCoverage\Exception; + +final class Xdebug3NotEnabledException extends RuntimeException implements Exception +{ + public function __construct() + { + parent::__construct('XDEBUG_MODE=coverage or xdebug.mode=coverage has to be set'); + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Exception/XdebugNotAvailableException.php b/vendor/phpunit/php-code-coverage/src/Exception/XdebugNotAvailableException.php new file mode 100644 index 00000000..1622c5a6 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Exception/XdebugNotAvailableException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Driver; + +use RuntimeException; +use SebastianBergmann\CodeCoverage\Exception; + +final class XdebugNotAvailableException extends RuntimeException implements Exception +{ + public function __construct() + { + parent::__construct('The Xdebug extension is not available'); + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Exception/XmlException.php b/vendor/phpunit/php-code-coverage/src/Exception/XmlException.php new file mode 100644 index 00000000..31e4623d --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Exception/XmlException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage; + +use RuntimeException; + +final class XmlException extends RuntimeException implements Exception +{ +} diff --git a/vendor/phpunit/php-code-coverage/src/Filter.php b/vendor/phpunit/php-code-coverage/src/Filter.php new file mode 100644 index 00000000..5a0a142a --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Filter.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage; + +use function array_keys; +use function is_file; +use function realpath; +use function strpos; +use SebastianBergmann\FileIterator\Facade as FileIteratorFacade; + +final class Filter +{ + /** + * @psalm-var array + */ + private $files = []; + + /** + * @psalm-var array + */ + private $isFileCache = []; + + public function includeDirectory(string $directory, string $suffix = '.php', string $prefix = ''): void + { + foreach ((new FileIteratorFacade)->getFilesAsArray($directory, $suffix, $prefix) as $file) { + $this->includeFile($file); + } + } + + /** + * @psalm-param list $files + */ + public function includeFiles(array $filenames): void + { + foreach ($filenames as $filename) { + $this->includeFile($filename); + } + } + + public function includeFile(string $filename): void + { + $filename = realpath($filename); + + if (!$filename) { + return; + } + + $this->files[$filename] = true; + } + + public function excludeDirectory(string $directory, string $suffix = '.php', string $prefix = ''): void + { + foreach ((new FileIteratorFacade)->getFilesAsArray($directory, $suffix, $prefix) as $file) { + $this->excludeFile($file); + } + } + + public function excludeFile(string $filename): void + { + $filename = realpath($filename); + + if (!$filename || !isset($this->files[$filename])) { + return; + } + + unset($this->files[$filename]); + } + + public function isFile(string $filename): bool + { + if (isset($this->isFileCache[$filename])) { + return $this->isFileCache[$filename]; + } + + if ($filename === '-' || + strpos($filename, 'vfs://') === 0 || + strpos($filename, 'xdebug://debug-eval') !== false || + strpos($filename, 'eval()\'d code') !== false || + strpos($filename, 'runtime-created function') !== false || + strpos($filename, 'runkit created function') !== false || + strpos($filename, 'assert code') !== false || + strpos($filename, 'regexp code') !== false || + strpos($filename, 'Standard input code') !== false) { + $isFile = false; + } else { + $isFile = is_file($filename); + } + + $this->isFileCache[$filename] = $isFile; + + return $isFile; + } + + public function isExcluded(string $filename): bool + { + return !isset($this->files[$filename]) || !$this->isFile($filename); + } + + /** + * @psalm-return list + */ + public function files(): array + { + return array_keys($this->files); + } + + public function isEmpty(): bool + { + return empty($this->files); + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Node/AbstractNode.php b/vendor/phpunit/php-code-coverage/src/Node/AbstractNode.php new file mode 100644 index 00000000..330fb573 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Node/AbstractNode.php @@ -0,0 +1,253 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Node; + +use const DIRECTORY_SEPARATOR; +use function array_merge; +use function str_replace; +use function substr; +use Countable; +use SebastianBergmann\CodeCoverage\Util\Percentage; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +abstract class AbstractNode implements Countable +{ + /** + * @var string + */ + private $name; + + /** + * @var string + */ + private $pathAsString; + + /** + * @var array + */ + private $pathAsArray; + + /** + * @var AbstractNode + */ + private $parent; + + /** + * @var string + */ + private $id; + + public function __construct(string $name, ?self $parent = null) + { + if (substr($name, -1) === DIRECTORY_SEPARATOR) { + $name = substr($name, 0, -1); + } + + $this->name = $name; + $this->parent = $parent; + } + + public function name(): string + { + return $this->name; + } + + public function id(): string + { + if ($this->id === null) { + $parent = $this->parent(); + + if ($parent === null) { + $this->id = 'index'; + } else { + $parentId = $parent->id(); + + if ($parentId === 'index') { + $this->id = str_replace(':', '_', $this->name); + } else { + $this->id = $parentId . '/' . $this->name; + } + } + } + + return $this->id; + } + + public function pathAsString(): string + { + if ($this->pathAsString === null) { + if ($this->parent === null) { + $this->pathAsString = $this->name; + } else { + $this->pathAsString = $this->parent->pathAsString() . DIRECTORY_SEPARATOR . $this->name; + } + } + + return $this->pathAsString; + } + + public function pathAsArray(): array + { + if ($this->pathAsArray === null) { + if ($this->parent === null) { + $this->pathAsArray = []; + } else { + $this->pathAsArray = $this->parent->pathAsArray(); + } + + $this->pathAsArray[] = $this; + } + + return $this->pathAsArray; + } + + public function parent(): ?self + { + return $this->parent; + } + + public function percentageOfTestedClasses(): Percentage + { + return Percentage::fromFractionAndTotal( + $this->numberOfTestedClasses(), + $this->numberOfClasses(), + ); + } + + public function percentageOfTestedTraits(): Percentage + { + return Percentage::fromFractionAndTotal( + $this->numberOfTestedTraits(), + $this->numberOfTraits(), + ); + } + + public function percentageOfTestedClassesAndTraits(): Percentage + { + return Percentage::fromFractionAndTotal( + $this->numberOfTestedClassesAndTraits(), + $this->numberOfClassesAndTraits(), + ); + } + + public function percentageOfTestedFunctions(): Percentage + { + return Percentage::fromFractionAndTotal( + $this->numberOfTestedFunctions(), + $this->numberOfFunctions(), + ); + } + + public function percentageOfTestedMethods(): Percentage + { + return Percentage::fromFractionAndTotal( + $this->numberOfTestedMethods(), + $this->numberOfMethods(), + ); + } + + public function percentageOfTestedFunctionsAndMethods(): Percentage + { + return Percentage::fromFractionAndTotal( + $this->numberOfTestedFunctionsAndMethods(), + $this->numberOfFunctionsAndMethods(), + ); + } + + public function percentageOfExecutedLines(): Percentage + { + return Percentage::fromFractionAndTotal( + $this->numberOfExecutedLines(), + $this->numberOfExecutableLines(), + ); + } + + public function percentageOfExecutedBranches(): Percentage + { + return Percentage::fromFractionAndTotal( + $this->numberOfExecutedBranches(), + $this->numberOfExecutableBranches() + ); + } + + public function percentageOfExecutedPaths(): Percentage + { + return Percentage::fromFractionAndTotal( + $this->numberOfExecutedPaths(), + $this->numberOfExecutablePaths() + ); + } + + public function numberOfClassesAndTraits(): int + { + return $this->numberOfClasses() + $this->numberOfTraits(); + } + + public function numberOfTestedClassesAndTraits(): int + { + return $this->numberOfTestedClasses() + $this->numberOfTestedTraits(); + } + + public function classesAndTraits(): array + { + return array_merge($this->classes(), $this->traits()); + } + + public function numberOfFunctionsAndMethods(): int + { + return $this->numberOfFunctions() + $this->numberOfMethods(); + } + + public function numberOfTestedFunctionsAndMethods(): int + { + return $this->numberOfTestedFunctions() + $this->numberOfTestedMethods(); + } + + abstract public function classes(): array; + + abstract public function traits(): array; + + abstract public function functions(): array; + + /** + * @psalm-return array{linesOfCode: int, commentLinesOfCode: int, nonCommentLinesOfCode: int} + */ + abstract public function linesOfCode(): array; + + abstract public function numberOfExecutableLines(): int; + + abstract public function numberOfExecutedLines(): int; + + abstract public function numberOfExecutableBranches(): int; + + abstract public function numberOfExecutedBranches(): int; + + abstract public function numberOfExecutablePaths(): int; + + abstract public function numberOfExecutedPaths(): int; + + abstract public function numberOfClasses(): int; + + abstract public function numberOfTestedClasses(): int; + + abstract public function numberOfTraits(): int; + + abstract public function numberOfTestedTraits(): int; + + abstract public function numberOfMethods(): int; + + abstract public function numberOfTestedMethods(): int; + + abstract public function numberOfFunctions(): int; + + abstract public function numberOfTestedFunctions(): int; +} diff --git a/vendor/phpunit/php-code-coverage/src/Node/Builder.php b/vendor/phpunit/php-code-coverage/src/Node/Builder.php new file mode 100644 index 00000000..6d11c779 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Node/Builder.php @@ -0,0 +1,264 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Node; + +use const DIRECTORY_SEPARATOR; +use function array_shift; +use function basename; +use function count; +use function dirname; +use function explode; +use function implode; +use function is_file; +use function str_replace; +use function strpos; +use function substr; +use SebastianBergmann\CodeCoverage\CodeCoverage; +use SebastianBergmann\CodeCoverage\ProcessedCodeCoverageData; +use SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class Builder +{ + /** + * @var FileAnalyser + */ + private $analyser; + + public function __construct(FileAnalyser $analyser) + { + $this->analyser = $analyser; + } + + public function build(CodeCoverage $coverage): Directory + { + $data = clone $coverage->getData(); // clone because path munging is destructive to the original data + $commonPath = $this->reducePaths($data); + $root = new Directory( + $commonPath, + null + ); + + $this->addItems( + $root, + $this->buildDirectoryStructure($data), + $coverage->getTests() + ); + + return $root; + } + + private function addItems(Directory $root, array $items, array $tests): void + { + foreach ($items as $key => $value) { + $key = (string) $key; + + if (substr($key, -2) === '/f') { + $key = substr($key, 0, -2); + $filename = $root->pathAsString() . DIRECTORY_SEPARATOR . $key; + + if (is_file($filename)) { + $root->addFile( + new File( + $key, + $root, + $value['lineCoverage'], + $value['functionCoverage'], + $tests, + $this->analyser->classesIn($filename), + $this->analyser->traitsIn($filename), + $this->analyser->functionsIn($filename), + $this->analyser->linesOfCodeFor($filename) + ) + ); + } + } else { + $child = $root->addDirectory($key); + + $this->addItems($child, $value, $tests); + } + } + } + + /** + * Builds an array representation of the directory structure. + * + * For instance, + * + * + * Array + * ( + * [Money.php] => Array + * ( + * ... + * ) + * + * [MoneyBag.php] => Array + * ( + * ... + * ) + * ) + * + * + * is transformed into + * + * + * Array + * ( + * [.] => Array + * ( + * [Money.php] => Array + * ( + * ... + * ) + * + * [MoneyBag.php] => Array + * ( + * ... + * ) + * ) + * ) + * + */ + private function buildDirectoryStructure(ProcessedCodeCoverageData $data): array + { + $result = []; + + foreach ($data->coveredFiles() as $originalPath) { + $path = explode(DIRECTORY_SEPARATOR, $originalPath); + $pointer = &$result; + $max = count($path); + + for ($i = 0; $i < $max; $i++) { + $type = ''; + + if ($i === ($max - 1)) { + $type = '/f'; + } + + $pointer = &$pointer[$path[$i] . $type]; + } + + $pointer = [ + 'lineCoverage' => $data->lineCoverage()[$originalPath] ?? [], + 'functionCoverage' => $data->functionCoverage()[$originalPath] ?? [], + ]; + } + + return $result; + } + + /** + * Reduces the paths by cutting the longest common start path. + * + * For instance, + * + * + * Array + * ( + * [/home/sb/Money/Money.php] => Array + * ( + * ... + * ) + * + * [/home/sb/Money/MoneyBag.php] => Array + * ( + * ... + * ) + * ) + * + * + * is reduced to + * + * + * Array + * ( + * [Money.php] => Array + * ( + * ... + * ) + * + * [MoneyBag.php] => Array + * ( + * ... + * ) + * ) + * + */ + private function reducePaths(ProcessedCodeCoverageData $coverage): string + { + if (empty($coverage->coveredFiles())) { + return '.'; + } + + $commonPath = ''; + $paths = $coverage->coveredFiles(); + + if (count($paths) === 1) { + $commonPath = dirname($paths[0]) . DIRECTORY_SEPARATOR; + $coverage->renameFile($paths[0], basename($paths[0])); + + return $commonPath; + } + + $max = count($paths); + + for ($i = 0; $i < $max; $i++) { + // strip phar:// prefixes + if (strpos($paths[$i], 'phar://') === 0) { + $paths[$i] = substr($paths[$i], 7); + $paths[$i] = str_replace('/', DIRECTORY_SEPARATOR, $paths[$i]); + } + $paths[$i] = explode(DIRECTORY_SEPARATOR, $paths[$i]); + + if (empty($paths[$i][0])) { + $paths[$i][0] = DIRECTORY_SEPARATOR; + } + } + + $done = false; + $max = count($paths); + + while (!$done) { + for ($i = 0; $i < $max - 1; $i++) { + if (!isset($paths[$i][0]) || + !isset($paths[$i + 1][0]) || + $paths[$i][0] !== $paths[$i + 1][0]) { + $done = true; + + break; + } + } + + if (!$done) { + $commonPath .= $paths[0][0]; + + if ($paths[0][0] !== DIRECTORY_SEPARATOR) { + $commonPath .= DIRECTORY_SEPARATOR; + } + + for ($i = 0; $i < $max; $i++) { + array_shift($paths[$i]); + } + } + } + + $original = $coverage->coveredFiles(); + $max = count($original); + + for ($i = 0; $i < $max; $i++) { + $coverage->renameFile($original[$i], implode(DIRECTORY_SEPARATOR, $paths[$i])); + } + + return substr($commonPath, 0, -1); + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Node/CrapIndex.php b/vendor/phpunit/php-code-coverage/src/Node/CrapIndex.php new file mode 100644 index 00000000..30b86b7d --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Node/CrapIndex.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Node; + +use function sprintf; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class CrapIndex +{ + /** + * @var int + */ + private $cyclomaticComplexity; + + /** + * @var float + */ + private $codeCoverage; + + public function __construct(int $cyclomaticComplexity, float $codeCoverage) + { + $this->cyclomaticComplexity = $cyclomaticComplexity; + $this->codeCoverage = $codeCoverage; + } + + public function asString(): string + { + if ($this->codeCoverage === 0.0) { + return (string) ($this->cyclomaticComplexity ** 2 + $this->cyclomaticComplexity); + } + + if ($this->codeCoverage >= 95) { + return (string) $this->cyclomaticComplexity; + } + + return sprintf( + '%01.2F', + $this->cyclomaticComplexity ** 2 * (1 - $this->codeCoverage / 100) ** 3 + $this->cyclomaticComplexity + ); + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Node/Directory.php b/vendor/phpunit/php-code-coverage/src/Node/Directory.php new file mode 100644 index 00000000..d6ee07e4 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Node/Directory.php @@ -0,0 +1,440 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Node; + +use function array_merge; +use function count; +use IteratorAggregate; +use RecursiveIteratorIterator; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class Directory extends AbstractNode implements IteratorAggregate +{ + /** + * @var AbstractNode[] + */ + private $children = []; + + /** + * @var Directory[] + */ + private $directories = []; + + /** + * @var File[] + */ + private $files = []; + + /** + * @var array + */ + private $classes; + + /** + * @var array + */ + private $traits; + + /** + * @var array + */ + private $functions; + + /** + * @psalm-var null|array{linesOfCode: int, commentLinesOfCode: int, nonCommentLinesOfCode: int} + */ + private $linesOfCode; + + /** + * @var int + */ + private $numFiles = -1; + + /** + * @var int + */ + private $numExecutableLines = -1; + + /** + * @var int + */ + private $numExecutedLines = -1; + + /** + * @var int + */ + private $numExecutableBranches = -1; + + /** + * @var int + */ + private $numExecutedBranches = -1; + + /** + * @var int + */ + private $numExecutablePaths = -1; + + /** + * @var int + */ + private $numExecutedPaths = -1; + + /** + * @var int + */ + private $numClasses = -1; + + /** + * @var int + */ + private $numTestedClasses = -1; + + /** + * @var int + */ + private $numTraits = -1; + + /** + * @var int + */ + private $numTestedTraits = -1; + + /** + * @var int + */ + private $numMethods = -1; + + /** + * @var int + */ + private $numTestedMethods = -1; + + /** + * @var int + */ + private $numFunctions = -1; + + /** + * @var int + */ + private $numTestedFunctions = -1; + + public function count(): int + { + if ($this->numFiles === -1) { + $this->numFiles = 0; + + foreach ($this->children as $child) { + $this->numFiles += count($child); + } + } + + return $this->numFiles; + } + + public function getIterator(): RecursiveIteratorIterator + { + return new RecursiveIteratorIterator( + new Iterator($this), + RecursiveIteratorIterator::SELF_FIRST + ); + } + + public function addDirectory(string $name): self + { + $directory = new self($name, $this); + + $this->children[] = $directory; + $this->directories[] = &$this->children[count($this->children) - 1]; + + return $directory; + } + + public function addFile(File $file): void + { + $this->children[] = $file; + $this->files[] = &$this->children[count($this->children) - 1]; + + $this->numExecutableLines = -1; + $this->numExecutedLines = -1; + } + + public function directories(): array + { + return $this->directories; + } + + public function files(): array + { + return $this->files; + } + + public function children(): array + { + return $this->children; + } + + public function classes(): array + { + if ($this->classes === null) { + $this->classes = []; + + foreach ($this->children as $child) { + $this->classes = array_merge( + $this->classes, + $child->classes() + ); + } + } + + return $this->classes; + } + + public function traits(): array + { + if ($this->traits === null) { + $this->traits = []; + + foreach ($this->children as $child) { + $this->traits = array_merge( + $this->traits, + $child->traits() + ); + } + } + + return $this->traits; + } + + public function functions(): array + { + if ($this->functions === null) { + $this->functions = []; + + foreach ($this->children as $child) { + $this->functions = array_merge( + $this->functions, + $child->functions() + ); + } + } + + return $this->functions; + } + + /** + * @psalm-return array{linesOfCode: int, commentLinesOfCode: int, nonCommentLinesOfCode: int} + */ + public function linesOfCode(): array + { + if ($this->linesOfCode === null) { + $this->linesOfCode = [ + 'linesOfCode' => 0, + 'commentLinesOfCode' => 0, + 'nonCommentLinesOfCode' => 0, + ]; + + foreach ($this->children as $child) { + $childLinesOfCode = $child->linesOfCode(); + + $this->linesOfCode['linesOfCode'] += $childLinesOfCode['linesOfCode']; + $this->linesOfCode['commentLinesOfCode'] += $childLinesOfCode['commentLinesOfCode']; + $this->linesOfCode['nonCommentLinesOfCode'] += $childLinesOfCode['nonCommentLinesOfCode']; + } + } + + return $this->linesOfCode; + } + + public function numberOfExecutableLines(): int + { + if ($this->numExecutableLines === -1) { + $this->numExecutableLines = 0; + + foreach ($this->children as $child) { + $this->numExecutableLines += $child->numberOfExecutableLines(); + } + } + + return $this->numExecutableLines; + } + + public function numberOfExecutedLines(): int + { + if ($this->numExecutedLines === -1) { + $this->numExecutedLines = 0; + + foreach ($this->children as $child) { + $this->numExecutedLines += $child->numberOfExecutedLines(); + } + } + + return $this->numExecutedLines; + } + + public function numberOfExecutableBranches(): int + { + if ($this->numExecutableBranches === -1) { + $this->numExecutableBranches = 0; + + foreach ($this->children as $child) { + $this->numExecutableBranches += $child->numberOfExecutableBranches(); + } + } + + return $this->numExecutableBranches; + } + + public function numberOfExecutedBranches(): int + { + if ($this->numExecutedBranches === -1) { + $this->numExecutedBranches = 0; + + foreach ($this->children as $child) { + $this->numExecutedBranches += $child->numberOfExecutedBranches(); + } + } + + return $this->numExecutedBranches; + } + + public function numberOfExecutablePaths(): int + { + if ($this->numExecutablePaths === -1) { + $this->numExecutablePaths = 0; + + foreach ($this->children as $child) { + $this->numExecutablePaths += $child->numberOfExecutablePaths(); + } + } + + return $this->numExecutablePaths; + } + + public function numberOfExecutedPaths(): int + { + if ($this->numExecutedPaths === -1) { + $this->numExecutedPaths = 0; + + foreach ($this->children as $child) { + $this->numExecutedPaths += $child->numberOfExecutedPaths(); + } + } + + return $this->numExecutedPaths; + } + + public function numberOfClasses(): int + { + if ($this->numClasses === -1) { + $this->numClasses = 0; + + foreach ($this->children as $child) { + $this->numClasses += $child->numberOfClasses(); + } + } + + return $this->numClasses; + } + + public function numberOfTestedClasses(): int + { + if ($this->numTestedClasses === -1) { + $this->numTestedClasses = 0; + + foreach ($this->children as $child) { + $this->numTestedClasses += $child->numberOfTestedClasses(); + } + } + + return $this->numTestedClasses; + } + + public function numberOfTraits(): int + { + if ($this->numTraits === -1) { + $this->numTraits = 0; + + foreach ($this->children as $child) { + $this->numTraits += $child->numberOfTraits(); + } + } + + return $this->numTraits; + } + + public function numberOfTestedTraits(): int + { + if ($this->numTestedTraits === -1) { + $this->numTestedTraits = 0; + + foreach ($this->children as $child) { + $this->numTestedTraits += $child->numberOfTestedTraits(); + } + } + + return $this->numTestedTraits; + } + + public function numberOfMethods(): int + { + if ($this->numMethods === -1) { + $this->numMethods = 0; + + foreach ($this->children as $child) { + $this->numMethods += $child->numberOfMethods(); + } + } + + return $this->numMethods; + } + + public function numberOfTestedMethods(): int + { + if ($this->numTestedMethods === -1) { + $this->numTestedMethods = 0; + + foreach ($this->children as $child) { + $this->numTestedMethods += $child->numberOfTestedMethods(); + } + } + + return $this->numTestedMethods; + } + + public function numberOfFunctions(): int + { + if ($this->numFunctions === -1) { + $this->numFunctions = 0; + + foreach ($this->children as $child) { + $this->numFunctions += $child->numberOfFunctions(); + } + } + + return $this->numFunctions; + } + + public function numberOfTestedFunctions(): int + { + if ($this->numTestedFunctions === -1) { + $this->numTestedFunctions = 0; + + foreach ($this->children as $child) { + $this->numTestedFunctions += $child->numberOfTestedFunctions(); + } + } + + return $this->numTestedFunctions; + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Node/File.php b/vendor/phpunit/php-code-coverage/src/Node/File.php new file mode 100644 index 00000000..af3764e4 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Node/File.php @@ -0,0 +1,651 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Node; + +use function array_filter; +use function count; +use function range; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class File extends AbstractNode +{ + /** + * @var array + */ + private $lineCoverageData; + + /** + * @var array + */ + private $functionCoverageData; + + /** + * @var array + */ + private $testData; + + /** + * @var int + */ + private $numExecutableLines = 0; + + /** + * @var int + */ + private $numExecutedLines = 0; + + /** + * @var int + */ + private $numExecutableBranches = 0; + + /** + * @var int + */ + private $numExecutedBranches = 0; + + /** + * @var int + */ + private $numExecutablePaths = 0; + + /** + * @var int + */ + private $numExecutedPaths = 0; + + /** + * @var array + */ + private $classes = []; + + /** + * @var array + */ + private $traits = []; + + /** + * @var array + */ + private $functions = []; + + /** + * @psalm-var array{linesOfCode: int, commentLinesOfCode: int, nonCommentLinesOfCode: int} + */ + private $linesOfCode; + + /** + * @var int + */ + private $numClasses; + + /** + * @var int + */ + private $numTestedClasses = 0; + + /** + * @var int + */ + private $numTraits; + + /** + * @var int + */ + private $numTestedTraits = 0; + + /** + * @var int + */ + private $numMethods; + + /** + * @var int + */ + private $numTestedMethods; + + /** + * @var int + */ + private $numTestedFunctions; + + /** + * @var array + */ + private $codeUnitsByLine = []; + + /** + * @psalm-param array{linesOfCode: int, commentLinesOfCode: int, nonCommentLinesOfCode: int} $linesOfCode + */ + public function __construct(string $name, AbstractNode $parent, array $lineCoverageData, array $functionCoverageData, array $testData, array $classes, array $traits, array $functions, array $linesOfCode) + { + parent::__construct($name, $parent); + + $this->lineCoverageData = $lineCoverageData; + $this->functionCoverageData = $functionCoverageData; + $this->testData = $testData; + $this->linesOfCode = $linesOfCode; + + $this->calculateStatistics($classes, $traits, $functions); + } + + public function count(): int + { + return 1; + } + + public function lineCoverageData(): array + { + return $this->lineCoverageData; + } + + public function functionCoverageData(): array + { + return $this->functionCoverageData; + } + + public function testData(): array + { + return $this->testData; + } + + public function classes(): array + { + return $this->classes; + } + + public function traits(): array + { + return $this->traits; + } + + public function functions(): array + { + return $this->functions; + } + + /** + * @psalm-return array{linesOfCode: int, commentLinesOfCode: int, nonCommentLinesOfCode: int} + */ + public function linesOfCode(): array + { + return $this->linesOfCode; + } + + public function numberOfExecutableLines(): int + { + return $this->numExecutableLines; + } + + public function numberOfExecutedLines(): int + { + return $this->numExecutedLines; + } + + public function numberOfExecutableBranches(): int + { + return $this->numExecutableBranches; + } + + public function numberOfExecutedBranches(): int + { + return $this->numExecutedBranches; + } + + public function numberOfExecutablePaths(): int + { + return $this->numExecutablePaths; + } + + public function numberOfExecutedPaths(): int + { + return $this->numExecutedPaths; + } + + public function numberOfClasses(): int + { + if ($this->numClasses === null) { + $this->numClasses = 0; + + foreach ($this->classes as $class) { + foreach ($class['methods'] as $method) { + if ($method['executableLines'] > 0) { + $this->numClasses++; + + continue 2; + } + } + } + } + + return $this->numClasses; + } + + public function numberOfTestedClasses(): int + { + return $this->numTestedClasses; + } + + public function numberOfTraits(): int + { + if ($this->numTraits === null) { + $this->numTraits = 0; + + foreach ($this->traits as $trait) { + foreach ($trait['methods'] as $method) { + if ($method['executableLines'] > 0) { + $this->numTraits++; + + continue 2; + } + } + } + } + + return $this->numTraits; + } + + public function numberOfTestedTraits(): int + { + return $this->numTestedTraits; + } + + public function numberOfMethods(): int + { + if ($this->numMethods === null) { + $this->numMethods = 0; + + foreach ($this->classes as $class) { + foreach ($class['methods'] as $method) { + if ($method['executableLines'] > 0) { + $this->numMethods++; + } + } + } + + foreach ($this->traits as $trait) { + foreach ($trait['methods'] as $method) { + if ($method['executableLines'] > 0) { + $this->numMethods++; + } + } + } + } + + return $this->numMethods; + } + + public function numberOfTestedMethods(): int + { + if ($this->numTestedMethods === null) { + $this->numTestedMethods = 0; + + foreach ($this->classes as $class) { + foreach ($class['methods'] as $method) { + if ($method['executableLines'] > 0 && + $method['coverage'] === 100) { + $this->numTestedMethods++; + } + } + } + + foreach ($this->traits as $trait) { + foreach ($trait['methods'] as $method) { + if ($method['executableLines'] > 0 && + $method['coverage'] === 100) { + $this->numTestedMethods++; + } + } + } + } + + return $this->numTestedMethods; + } + + public function numberOfFunctions(): int + { + return count($this->functions); + } + + public function numberOfTestedFunctions(): int + { + if ($this->numTestedFunctions === null) { + $this->numTestedFunctions = 0; + + foreach ($this->functions as $function) { + if ($function['executableLines'] > 0 && + $function['coverage'] === 100) { + $this->numTestedFunctions++; + } + } + } + + return $this->numTestedFunctions; + } + + private function calculateStatistics(array $classes, array $traits, array $functions): void + { + foreach (range(1, $this->linesOfCode['linesOfCode']) as $lineNumber) { + $this->codeUnitsByLine[$lineNumber] = []; + } + + $this->processClasses($classes); + $this->processTraits($traits); + $this->processFunctions($functions); + + foreach (range(1, $this->linesOfCode['linesOfCode']) as $lineNumber) { + if (isset($this->lineCoverageData[$lineNumber])) { + foreach ($this->codeUnitsByLine[$lineNumber] as &$codeUnit) { + $codeUnit['executableLines']++; + } + + unset($codeUnit); + + $this->numExecutableLines++; + + if (count($this->lineCoverageData[$lineNumber]) > 0) { + foreach ($this->codeUnitsByLine[$lineNumber] as &$codeUnit) { + $codeUnit['executedLines']++; + } + + unset($codeUnit); + + $this->numExecutedLines++; + } + } + } + + foreach ($this->traits as &$trait) { + foreach ($trait['methods'] as &$method) { + $methodLineCoverage = $method['executableLines'] ? ($method['executedLines'] / $method['executableLines']) * 100 : 100; + $methodBranchCoverage = $method['executableBranches'] ? ($method['executedBranches'] / $method['executableBranches']) * 100 : 0; + $methodPathCoverage = $method['executablePaths'] ? ($method['executedPaths'] / $method['executablePaths']) * 100 : 0; + + $method['coverage'] = $methodBranchCoverage ?: $methodLineCoverage; + $method['crap'] = (new CrapIndex($method['ccn'], $methodPathCoverage ?: $methodLineCoverage))->asString(); + + $trait['ccn'] += $method['ccn']; + } + + unset($method); + + $traitLineCoverage = $trait['executableLines'] ? ($trait['executedLines'] / $trait['executableLines']) * 100 : 100; + $traitBranchCoverage = $trait['executableBranches'] ? ($trait['executedBranches'] / $trait['executableBranches']) * 100 : 0; + $traitPathCoverage = $trait['executablePaths'] ? ($trait['executedPaths'] / $trait['executablePaths']) * 100 : 0; + + $trait['coverage'] = $traitBranchCoverage ?: $traitLineCoverage; + $trait['crap'] = (new CrapIndex($trait['ccn'], $traitPathCoverage ?: $traitLineCoverage))->asString(); + + if ($trait['executableLines'] > 0 && $trait['coverage'] === 100) { + $this->numTestedClasses++; + } + } + + unset($trait); + + foreach ($this->classes as &$class) { + foreach ($class['methods'] as &$method) { + $methodLineCoverage = $method['executableLines'] ? ($method['executedLines'] / $method['executableLines']) * 100 : 100; + $methodBranchCoverage = $method['executableBranches'] ? ($method['executedBranches'] / $method['executableBranches']) * 100 : 0; + $methodPathCoverage = $method['executablePaths'] ? ($method['executedPaths'] / $method['executablePaths']) * 100 : 0; + + $method['coverage'] = $methodBranchCoverage ?: $methodLineCoverage; + $method['crap'] = (new CrapIndex($method['ccn'], $methodPathCoverage ?: $methodLineCoverage))->asString(); + + $class['ccn'] += $method['ccn']; + } + + unset($method); + + $classLineCoverage = $class['executableLines'] ? ($class['executedLines'] / $class['executableLines']) * 100 : 100; + $classBranchCoverage = $class['executableBranches'] ? ($class['executedBranches'] / $class['executableBranches']) * 100 : 0; + $classPathCoverage = $class['executablePaths'] ? ($class['executedPaths'] / $class['executablePaths']) * 100 : 0; + + $class['coverage'] = $classBranchCoverage ?: $classLineCoverage; + $class['crap'] = (new CrapIndex($class['ccn'], $classPathCoverage ?: $classLineCoverage))->asString(); + + if ($class['executableLines'] > 0 && $class['coverage'] === 100) { + $this->numTestedClasses++; + } + } + + unset($class); + + foreach ($this->functions as &$function) { + $functionLineCoverage = $function['executableLines'] ? ($function['executedLines'] / $function['executableLines']) * 100 : 100; + $functionBranchCoverage = $function['executableBranches'] ? ($function['executedBranches'] / $function['executableBranches']) * 100 : 0; + $functionPathCoverage = $function['executablePaths'] ? ($function['executedPaths'] / $function['executablePaths']) * 100 : 0; + + $function['coverage'] = $functionBranchCoverage ?: $functionLineCoverage; + $function['crap'] = (new CrapIndex($function['ccn'], $functionPathCoverage ?: $functionLineCoverage))->asString(); + + if ($function['coverage'] === 100) { + $this->numTestedFunctions++; + } + } + } + + private function processClasses(array $classes): void + { + $link = $this->id() . '.html#'; + + foreach ($classes as $className => $class) { + $this->classes[$className] = [ + 'className' => $className, + 'namespace' => $class['namespace'], + 'methods' => [], + 'startLine' => $class['startLine'], + 'executableLines' => 0, + 'executedLines' => 0, + 'executableBranches' => 0, + 'executedBranches' => 0, + 'executablePaths' => 0, + 'executedPaths' => 0, + 'ccn' => 0, + 'coverage' => 0, + 'crap' => 0, + 'link' => $link . $class['startLine'], + ]; + + foreach ($class['methods'] as $methodName => $method) { + $methodData = $this->newMethod($className, $methodName, $method, $link); + $this->classes[$className]['methods'][$methodName] = $methodData; + + $this->classes[$className]['executableBranches'] += $methodData['executableBranches']; + $this->classes[$className]['executedBranches'] += $methodData['executedBranches']; + $this->classes[$className]['executablePaths'] += $methodData['executablePaths']; + $this->classes[$className]['executedPaths'] += $methodData['executedPaths']; + + $this->numExecutableBranches += $methodData['executableBranches']; + $this->numExecutedBranches += $methodData['executedBranches']; + $this->numExecutablePaths += $methodData['executablePaths']; + $this->numExecutedPaths += $methodData['executedPaths']; + + foreach (range($method['startLine'], $method['endLine']) as $lineNumber) { + $this->codeUnitsByLine[$lineNumber] = [ + &$this->classes[$className], + &$this->classes[$className]['methods'][$methodName], + ]; + } + } + } + } + + private function processTraits(array $traits): void + { + $link = $this->id() . '.html#'; + + foreach ($traits as $traitName => $trait) { + $this->traits[$traitName] = [ + 'traitName' => $traitName, + 'namespace' => $trait['namespace'], + 'methods' => [], + 'startLine' => $trait['startLine'], + 'executableLines' => 0, + 'executedLines' => 0, + 'executableBranches' => 0, + 'executedBranches' => 0, + 'executablePaths' => 0, + 'executedPaths' => 0, + 'ccn' => 0, + 'coverage' => 0, + 'crap' => 0, + 'link' => $link . $trait['startLine'], + ]; + + foreach ($trait['methods'] as $methodName => $method) { + $methodData = $this->newMethod($traitName, $methodName, $method, $link); + $this->traits[$traitName]['methods'][$methodName] = $methodData; + + $this->traits[$traitName]['executableBranches'] += $methodData['executableBranches']; + $this->traits[$traitName]['executedBranches'] += $methodData['executedBranches']; + $this->traits[$traitName]['executablePaths'] += $methodData['executablePaths']; + $this->traits[$traitName]['executedPaths'] += $methodData['executedPaths']; + + $this->numExecutableBranches += $methodData['executableBranches']; + $this->numExecutedBranches += $methodData['executedBranches']; + $this->numExecutablePaths += $methodData['executablePaths']; + $this->numExecutedPaths += $methodData['executedPaths']; + + foreach (range($method['startLine'], $method['endLine']) as $lineNumber) { + $this->codeUnitsByLine[$lineNumber] = [ + &$this->traits[$traitName], + &$this->traits[$traitName]['methods'][$methodName], + ]; + } + } + } + } + + private function processFunctions(array $functions): void + { + $link = $this->id() . '.html#'; + + foreach ($functions as $functionName => $function) { + $this->functions[$functionName] = [ + 'functionName' => $functionName, + 'namespace' => $function['namespace'], + 'signature' => $function['signature'], + 'startLine' => $function['startLine'], + 'endLine' => $function['endLine'], + 'executableLines' => 0, + 'executedLines' => 0, + 'executableBranches' => 0, + 'executedBranches' => 0, + 'executablePaths' => 0, + 'executedPaths' => 0, + 'ccn' => $function['ccn'], + 'coverage' => 0, + 'crap' => 0, + 'link' => $link . $function['startLine'], + ]; + + foreach (range($function['startLine'], $function['endLine']) as $lineNumber) { + $this->codeUnitsByLine[$lineNumber] = [&$this->functions[$functionName]]; + } + + if (isset($this->functionCoverageData[$functionName]['branches'])) { + $this->functions[$functionName]['executableBranches'] = count( + $this->functionCoverageData[$functionName]['branches'] + ); + + $this->functions[$functionName]['executedBranches'] = count( + array_filter( + $this->functionCoverageData[$functionName]['branches'], + static function (array $branch) + { + return (bool) $branch['hit']; + } + ) + ); + } + + if (isset($this->functionCoverageData[$functionName]['paths'])) { + $this->functions[$functionName]['executablePaths'] = count( + $this->functionCoverageData[$functionName]['paths'] + ); + + $this->functions[$functionName]['executedPaths'] = count( + array_filter( + $this->functionCoverageData[$functionName]['paths'], + static function (array $path) + { + return (bool) $path['hit']; + } + ) + ); + } + + $this->numExecutableBranches += $this->functions[$functionName]['executableBranches']; + $this->numExecutedBranches += $this->functions[$functionName]['executedBranches']; + $this->numExecutablePaths += $this->functions[$functionName]['executablePaths']; + $this->numExecutedPaths += $this->functions[$functionName]['executedPaths']; + } + } + + private function newMethod(string $className, string $methodName, array $method, string $link): array + { + $methodData = [ + 'methodName' => $methodName, + 'visibility' => $method['visibility'], + 'signature' => $method['signature'], + 'startLine' => $method['startLine'], + 'endLine' => $method['endLine'], + 'executableLines' => 0, + 'executedLines' => 0, + 'executableBranches' => 0, + 'executedBranches' => 0, + 'executablePaths' => 0, + 'executedPaths' => 0, + 'ccn' => $method['ccn'], + 'coverage' => 0, + 'crap' => 0, + 'link' => $link . $method['startLine'], + ]; + + $key = $className . '->' . $methodName; + + if (isset($this->functionCoverageData[$key]['branches'])) { + $methodData['executableBranches'] = count( + $this->functionCoverageData[$key]['branches'] + ); + + $methodData['executedBranches'] = count( + array_filter( + $this->functionCoverageData[$key]['branches'], + static function (array $branch) + { + return (bool) $branch['hit']; + } + ) + ); + } + + if (isset($this->functionCoverageData[$key]['paths'])) { + $methodData['executablePaths'] = count( + $this->functionCoverageData[$key]['paths'] + ); + + $methodData['executedPaths'] = count( + array_filter( + $this->functionCoverageData[$key]['paths'], + static function (array $path) + { + return (bool) $path['hit']; + } + ) + ); + } + + return $methodData; + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Node/Iterator.php b/vendor/phpunit/php-code-coverage/src/Node/Iterator.php new file mode 100644 index 00000000..0d1c7354 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Node/Iterator.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Node; + +use function count; +use RecursiveIterator; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class Iterator implements RecursiveIterator +{ + /** + * @var int + */ + private $position; + + /** + * @var AbstractNode[] + */ + private $nodes; + + public function __construct(Directory $node) + { + $this->nodes = $node->children(); + } + + /** + * Rewinds the Iterator to the first element. + */ + public function rewind(): void + { + $this->position = 0; + } + + /** + * Checks if there is a current element after calls to rewind() or next(). + */ + public function valid(): bool + { + return $this->position < count($this->nodes); + } + + /** + * Returns the key of the current element. + */ + public function key(): int + { + return $this->position; + } + + /** + * Returns the current element. + */ + public function current(): ?AbstractNode + { + return $this->valid() ? $this->nodes[$this->position] : null; + } + + /** + * Moves forward to next element. + */ + public function next(): void + { + $this->position++; + } + + /** + * Returns the sub iterator for the current element. + */ + public function getChildren(): self + { + return new self($this->nodes[$this->position]); + } + + /** + * Checks whether the current element has children. + */ + public function hasChildren(): bool + { + return $this->nodes[$this->position] instanceof Directory; + } +} diff --git a/vendor/phpunit/php-code-coverage/src/ProcessedCodeCoverageData.php b/vendor/phpunit/php-code-coverage/src/ProcessedCodeCoverageData.php new file mode 100644 index 00000000..1ed29ad5 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/ProcessedCodeCoverageData.php @@ -0,0 +1,255 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage; + +use function array_key_exists; +use function array_keys; +use function array_merge; +use function array_unique; +use function count; +use function is_array; +use function ksort; +use SebastianBergmann\CodeCoverage\Driver\Driver; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class ProcessedCodeCoverageData +{ + /** + * Line coverage data. + * An array of filenames, each having an array of linenumbers, each executable line having an array of testcase ids. + * + * @var array + */ + private $lineCoverage = []; + + /** + * Function coverage data. + * Maintains base format of raw data (@see https://xdebug.org/docs/code_coverage), but each 'hit' entry is an array + * of testcase ids. + * + * @var array + */ + private $functionCoverage = []; + + public function initializeUnseenData(RawCodeCoverageData $rawData): void + { + foreach ($rawData->lineCoverage() as $file => $lines) { + if (!isset($this->lineCoverage[$file])) { + $this->lineCoverage[$file] = []; + + foreach ($lines as $k => $v) { + $this->lineCoverage[$file][$k] = $v === Driver::LINE_NOT_EXECUTABLE ? null : []; + } + } + } + + foreach ($rawData->functionCoverage() as $file => $functions) { + foreach ($functions as $functionName => $functionData) { + if (isset($this->functionCoverage[$file][$functionName])) { + $this->initPreviouslySeenFunction($file, $functionName, $functionData); + } else { + $this->initPreviouslyUnseenFunction($file, $functionName, $functionData); + } + } + } + } + + public function markCodeAsExecutedByTestCase(string $testCaseId, RawCodeCoverageData $executedCode): void + { + foreach ($executedCode->lineCoverage() as $file => $lines) { + foreach ($lines as $k => $v) { + if ($v === Driver::LINE_EXECUTED) { + $this->lineCoverage[$file][$k][] = $testCaseId; + } + } + } + + foreach ($executedCode->functionCoverage() as $file => $functions) { + foreach ($functions as $functionName => $functionData) { + foreach ($functionData['branches'] as $branchId => $branchData) { + if ($branchData['hit'] === Driver::BRANCH_HIT) { + $this->functionCoverage[$file][$functionName]['branches'][$branchId]['hit'][] = $testCaseId; + } + } + + foreach ($functionData['paths'] as $pathId => $pathData) { + if ($pathData['hit'] === Driver::BRANCH_HIT) { + $this->functionCoverage[$file][$functionName]['paths'][$pathId]['hit'][] = $testCaseId; + } + } + } + } + } + + public function setLineCoverage(array $lineCoverage): void + { + $this->lineCoverage = $lineCoverage; + } + + public function lineCoverage(): array + { + ksort($this->lineCoverage); + + return $this->lineCoverage; + } + + public function setFunctionCoverage(array $functionCoverage): void + { + $this->functionCoverage = $functionCoverage; + } + + public function functionCoverage(): array + { + ksort($this->functionCoverage); + + return $this->functionCoverage; + } + + public function coveredFiles(): array + { + ksort($this->lineCoverage); + + return array_keys($this->lineCoverage); + } + + public function renameFile(string $oldFile, string $newFile): void + { + $this->lineCoverage[$newFile] = $this->lineCoverage[$oldFile]; + + if (isset($this->functionCoverage[$oldFile])) { + $this->functionCoverage[$newFile] = $this->functionCoverage[$oldFile]; + } + + unset($this->lineCoverage[$oldFile], $this->functionCoverage[$oldFile]); + } + + public function merge(self $newData): void + { + foreach ($newData->lineCoverage as $file => $lines) { + if (!isset($this->lineCoverage[$file])) { + $this->lineCoverage[$file] = $lines; + + continue; + } + + // we should compare the lines if any of two contains data + $compareLineNumbers = array_unique( + array_merge( + array_keys($this->lineCoverage[$file]), + array_keys($newData->lineCoverage[$file]) + ) + ); + + foreach ($compareLineNumbers as $line) { + $thatPriority = $this->priorityForLine($newData->lineCoverage[$file], $line); + $thisPriority = $this->priorityForLine($this->lineCoverage[$file], $line); + + if ($thatPriority > $thisPriority) { + $this->lineCoverage[$file][$line] = $newData->lineCoverage[$file][$line]; + } elseif ($thatPriority === $thisPriority && is_array($this->lineCoverage[$file][$line])) { + $this->lineCoverage[$file][$line] = array_unique( + array_merge($this->lineCoverage[$file][$line], $newData->lineCoverage[$file][$line]) + ); + } + } + } + + foreach ($newData->functionCoverage as $file => $functions) { + if (!isset($this->functionCoverage[$file])) { + $this->functionCoverage[$file] = $functions; + + continue; + } + + foreach ($functions as $functionName => $functionData) { + if (isset($this->functionCoverage[$file][$functionName])) { + $this->initPreviouslySeenFunction($file, $functionName, $functionData); + } else { + $this->initPreviouslyUnseenFunction($file, $functionName, $functionData); + } + + foreach ($functionData['branches'] as $branchId => $branchData) { + $this->functionCoverage[$file][$functionName]['branches'][$branchId]['hit'] = array_unique(array_merge($this->functionCoverage[$file][$functionName]['branches'][$branchId]['hit'], $branchData['hit'])); + } + + foreach ($functionData['paths'] as $pathId => $pathData) { + $this->functionCoverage[$file][$functionName]['paths'][$pathId]['hit'] = array_unique(array_merge($this->functionCoverage[$file][$functionName]['paths'][$pathId]['hit'], $pathData['hit'])); + } + } + } + } + + /** + * Determine the priority for a line. + * + * 1 = the line is not set + * 2 = the line has not been tested + * 3 = the line is dead code + * 4 = the line has been tested + * + * During a merge, a higher number is better. + */ + private function priorityForLine(array $data, int $line): int + { + if (!array_key_exists($line, $data)) { + return 1; + } + + if (is_array($data[$line]) && count($data[$line]) === 0) { + return 2; + } + + if ($data[$line] === null) { + return 3; + } + + return 4; + } + + /** + * For a function we have never seen before, copy all data over and simply init the 'hit' array. + */ + private function initPreviouslyUnseenFunction(string $file, string $functionName, array $functionData): void + { + $this->functionCoverage[$file][$functionName] = $functionData; + + foreach (array_keys($functionData['branches']) as $branchId) { + $this->functionCoverage[$file][$functionName]['branches'][$branchId]['hit'] = []; + } + + foreach (array_keys($functionData['paths']) as $pathId) { + $this->functionCoverage[$file][$functionName]['paths'][$pathId]['hit'] = []; + } + } + + /** + * For a function we have seen before, only copy over and init the 'hit' array for any unseen branches and paths. + * Techniques such as mocking and where the contents of a file are different vary during tests (e.g. compiling + * containers) mean that the functions inside a file cannot be relied upon to be static. + */ + private function initPreviouslySeenFunction(string $file, string $functionName, array $functionData): void + { + foreach ($functionData['branches'] as $branchId => $branchData) { + if (!isset($this->functionCoverage[$file][$functionName]['branches'][$branchId])) { + $this->functionCoverage[$file][$functionName]['branches'][$branchId] = $branchData; + $this->functionCoverage[$file][$functionName]['branches'][$branchId]['hit'] = []; + } + } + + foreach ($functionData['paths'] as $pathId => $pathData) { + if (!isset($this->functionCoverage[$file][$functionName]['paths'][$pathId])) { + $this->functionCoverage[$file][$functionName]['paths'][$pathId] = $pathData; + $this->functionCoverage[$file][$functionName]['paths'][$pathId]['hit'] = []; + } + } + } +} diff --git a/vendor/phpunit/php-code-coverage/src/RawCodeCoverageData.php b/vendor/phpunit/php-code-coverage/src/RawCodeCoverageData.php new file mode 100644 index 00000000..9cb20e73 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/RawCodeCoverageData.php @@ -0,0 +1,274 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage; + +use function array_diff; +use function array_diff_key; +use function array_flip; +use function array_intersect; +use function array_intersect_key; +use function count; +use function explode; +use function file_get_contents; +use function in_array; +use function is_file; +use function range; +use function trim; +use SebastianBergmann\CodeCoverage\Driver\Driver; +use SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class RawCodeCoverageData +{ + /** + * @var array> + */ + private static $emptyLineCache = []; + + /** + * @var array + * + * @see https://xdebug.org/docs/code_coverage for format + */ + private $lineCoverage; + + /** + * @var array + * + * @see https://xdebug.org/docs/code_coverage for format + */ + private $functionCoverage; + + public static function fromXdebugWithoutPathCoverage(array $rawCoverage): self + { + return new self($rawCoverage, []); + } + + public static function fromXdebugWithPathCoverage(array $rawCoverage): self + { + $lineCoverage = []; + $functionCoverage = []; + + foreach ($rawCoverage as $file => $fileCoverageData) { + $lineCoverage[$file] = $fileCoverageData['lines']; + $functionCoverage[$file] = $fileCoverageData['functions']; + } + + return new self($lineCoverage, $functionCoverage); + } + + public static function fromXdebugWithMixedCoverage(array $rawCoverage): self + { + $lineCoverage = []; + $functionCoverage = []; + + foreach ($rawCoverage as $file => $fileCoverageData) { + if (!isset($fileCoverageData['functions'])) { + // Current file does not have functions, so line coverage + // is stored in $fileCoverageData, not in $fileCoverageData['lines'] + $lineCoverage[$file] = $fileCoverageData; + + continue; + } + + $lineCoverage[$file] = $fileCoverageData['lines']; + $functionCoverage[$file] = $fileCoverageData['functions']; + } + + return new self($lineCoverage, $functionCoverage); + } + + public static function fromUncoveredFile(string $filename, FileAnalyser $analyser): self + { + $lineCoverage = []; + + foreach ($analyser->executableLinesIn($filename) as $line => $branch) { + $lineCoverage[$line] = Driver::LINE_NOT_EXECUTED; + } + + return new self([$filename => $lineCoverage], []); + } + + private function __construct(array $lineCoverage, array $functionCoverage) + { + $this->lineCoverage = $lineCoverage; + $this->functionCoverage = $functionCoverage; + + $this->skipEmptyLines(); + } + + public function clear(): void + { + $this->lineCoverage = $this->functionCoverage = []; + } + + public function lineCoverage(): array + { + return $this->lineCoverage; + } + + public function functionCoverage(): array + { + return $this->functionCoverage; + } + + public function removeCoverageDataForFile(string $filename): void + { + unset($this->lineCoverage[$filename], $this->functionCoverage[$filename]); + } + + /** + * @param int[] $lines + */ + public function keepLineCoverageDataOnlyForLines(string $filename, array $lines): void + { + if (!isset($this->lineCoverage[$filename])) { + return; + } + + $this->lineCoverage[$filename] = array_intersect_key( + $this->lineCoverage[$filename], + array_flip($lines) + ); + } + + /** + * @param int[] $linesToBranchMap + */ + public function markExecutableLineByBranch(string $filename, array $linesToBranchMap): void + { + if (!isset($this->lineCoverage[$filename])) { + return; + } + + $linesByBranch = []; + + foreach ($linesToBranchMap as $line => $branch) { + $linesByBranch[$branch][] = $line; + } + + foreach ($this->lineCoverage[$filename] as $line => $lineStatus) { + if (!isset($linesToBranchMap[$line])) { + continue; + } + + $branch = $linesToBranchMap[$line]; + + if (!isset($linesByBranch[$branch])) { + continue; + } + + foreach ($linesByBranch[$branch] as $lineInBranch) { + $this->lineCoverage[$filename][$lineInBranch] = $lineStatus; + } + + if (Driver::LINE_EXECUTED === $lineStatus) { + unset($linesByBranch[$branch]); + } + } + } + + /** + * @param int[] $lines + */ + public function keepFunctionCoverageDataOnlyForLines(string $filename, array $lines): void + { + if (!isset($this->functionCoverage[$filename])) { + return; + } + + foreach ($this->functionCoverage[$filename] as $functionName => $functionData) { + foreach ($functionData['branches'] as $branchId => $branch) { + if (count(array_diff(range($branch['line_start'], $branch['line_end']), $lines)) > 0) { + unset($this->functionCoverage[$filename][$functionName]['branches'][$branchId]); + + foreach ($functionData['paths'] as $pathId => $path) { + if (in_array($branchId, $path['path'], true)) { + unset($this->functionCoverage[$filename][$functionName]['paths'][$pathId]); + } + } + } + } + } + } + + /** + * @param int[] $lines + */ + public function removeCoverageDataForLines(string $filename, array $lines): void + { + if (empty($lines)) { + return; + } + + if (!isset($this->lineCoverage[$filename])) { + return; + } + + $this->lineCoverage[$filename] = array_diff_key( + $this->lineCoverage[$filename], + array_flip($lines) + ); + + if (isset($this->functionCoverage[$filename])) { + foreach ($this->functionCoverage[$filename] as $functionName => $functionData) { + foreach ($functionData['branches'] as $branchId => $branch) { + if (count(array_intersect($lines, range($branch['line_start'], $branch['line_end']))) > 0) { + unset($this->functionCoverage[$filename][$functionName]['branches'][$branchId]); + + foreach ($functionData['paths'] as $pathId => $path) { + if (in_array($branchId, $path['path'], true)) { + unset($this->functionCoverage[$filename][$functionName]['paths'][$pathId]); + } + } + } + } + } + } + } + + /** + * At the end of a file, the PHP interpreter always sees an implicit return. Where this occurs in a file that has + * e.g. a class definition, that line cannot be invoked from a test and results in confusing coverage. This engine + * implementation detail therefore needs to be masked which is done here by simply ensuring that all empty lines + * are skipped over for coverage purposes. + * + * @see https://github.com/sebastianbergmann/php-code-coverage/issues/799 + */ + private function skipEmptyLines(): void + { + foreach ($this->lineCoverage as $filename => $coverage) { + foreach ($this->getEmptyLinesForFile($filename) as $emptyLine) { + unset($this->lineCoverage[$filename][$emptyLine]); + } + } + } + + private function getEmptyLinesForFile(string $filename): array + { + if (!isset(self::$emptyLineCache[$filename])) { + self::$emptyLineCache[$filename] = []; + + if (is_file($filename)) { + $sourceLines = explode("\n", file_get_contents($filename)); + + foreach ($sourceLines as $line => $source) { + if (trim($source) === '') { + self::$emptyLineCache[$filename][] = ($line + 1); + } + } + } + } + + return self::$emptyLineCache[$filename]; + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Report/Clover.php b/vendor/phpunit/php-code-coverage/src/Report/Clover.php new file mode 100644 index 00000000..d80ab4de --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Clover.php @@ -0,0 +1,258 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Report; + +use function count; +use function dirname; +use function file_put_contents; +use function is_string; +use function ksort; +use function max; +use function range; +use function strpos; +use function time; +use DOMDocument; +use SebastianBergmann\CodeCoverage\CodeCoverage; +use SebastianBergmann\CodeCoverage\Driver\WriteOperationFailedException; +use SebastianBergmann\CodeCoverage\Node\File; +use SebastianBergmann\CodeCoverage\Util\Filesystem; + +final class Clover +{ + /** + * @throws WriteOperationFailedException + */ + public function process(CodeCoverage $coverage, ?string $target = null, ?string $name = null): string + { + $time = (string) time(); + + $xmlDocument = new DOMDocument('1.0', 'UTF-8'); + $xmlDocument->formatOutput = true; + + $xmlCoverage = $xmlDocument->createElement('coverage'); + $xmlCoverage->setAttribute('generated', $time); + $xmlDocument->appendChild($xmlCoverage); + + $xmlProject = $xmlDocument->createElement('project'); + $xmlProject->setAttribute('timestamp', $time); + + if (is_string($name)) { + $xmlProject->setAttribute('name', $name); + } + + $xmlCoverage->appendChild($xmlProject); + + $packages = []; + $report = $coverage->getReport(); + + foreach ($report as $item) { + if (!$item instanceof File) { + continue; + } + + /* @var File $item */ + + $xmlFile = $xmlDocument->createElement('file'); + $xmlFile->setAttribute('name', $item->pathAsString()); + + $classes = $item->classesAndTraits(); + $coverageData = $item->lineCoverageData(); + $lines = []; + $namespace = 'global'; + + foreach ($classes as $className => $class) { + $classStatements = 0; + $coveredClassStatements = 0; + $coveredMethods = 0; + $classMethods = 0; + + foreach ($class['methods'] as $methodName => $method) { + if ($method['executableLines'] == 0) { + continue; + } + + $classMethods++; + $classStatements += $method['executableLines']; + $coveredClassStatements += $method['executedLines']; + + if ($method['coverage'] == 100) { + $coveredMethods++; + } + + $methodCount = 0; + + foreach (range($method['startLine'], $method['endLine']) as $line) { + if (isset($coverageData[$line]) && ($coverageData[$line] !== null)) { + $methodCount = max($methodCount, count($coverageData[$line])); + } + } + + $lines[$method['startLine']] = [ + 'ccn' => $method['ccn'], + 'count' => $methodCount, + 'crap' => $method['crap'], + 'type' => 'method', + 'visibility' => $method['visibility'], + 'name' => $methodName, + ]; + } + + if (!empty($class['package']['namespace'])) { + $namespace = $class['package']['namespace']; + } + + $xmlClass = $xmlDocument->createElement('class'); + $xmlClass->setAttribute('name', $className); + $xmlClass->setAttribute('namespace', $namespace); + + if (!empty($class['package']['fullPackage'])) { + $xmlClass->setAttribute( + 'fullPackage', + $class['package']['fullPackage'] + ); + } + + if (!empty($class['package']['category'])) { + $xmlClass->setAttribute( + 'category', + $class['package']['category'] + ); + } + + if (!empty($class['package']['package'])) { + $xmlClass->setAttribute( + 'package', + $class['package']['package'] + ); + } + + if (!empty($class['package']['subpackage'])) { + $xmlClass->setAttribute( + 'subpackage', + $class['package']['subpackage'] + ); + } + + $xmlFile->appendChild($xmlClass); + + $xmlMetrics = $xmlDocument->createElement('metrics'); + $xmlMetrics->setAttribute('complexity', (string) $class['ccn']); + $xmlMetrics->setAttribute('methods', (string) $classMethods); + $xmlMetrics->setAttribute('coveredmethods', (string) $coveredMethods); + $xmlMetrics->setAttribute('conditionals', (string) $class['executableBranches']); + $xmlMetrics->setAttribute('coveredconditionals', (string) $class['executedBranches']); + $xmlMetrics->setAttribute('statements', (string) $classStatements); + $xmlMetrics->setAttribute('coveredstatements', (string) $coveredClassStatements); + $xmlMetrics->setAttribute('elements', (string) ($classMethods + $classStatements + $class['executableBranches'])); + $xmlMetrics->setAttribute('coveredelements', (string) ($coveredMethods + $coveredClassStatements + $class['executedBranches'])); + $xmlClass->appendChild($xmlMetrics); + } + + foreach ($coverageData as $line => $data) { + if ($data === null || isset($lines[$line])) { + continue; + } + + $lines[$line] = [ + 'count' => count($data), 'type' => 'stmt', + ]; + } + + ksort($lines); + + foreach ($lines as $line => $data) { + $xmlLine = $xmlDocument->createElement('line'); + $xmlLine->setAttribute('num', (string) $line); + $xmlLine->setAttribute('type', $data['type']); + + if (isset($data['name'])) { + $xmlLine->setAttribute('name', $data['name']); + } + + if (isset($data['visibility'])) { + $xmlLine->setAttribute('visibility', $data['visibility']); + } + + if (isset($data['ccn'])) { + $xmlLine->setAttribute('complexity', (string) $data['ccn']); + } + + if (isset($data['crap'])) { + $xmlLine->setAttribute('crap', (string) $data['crap']); + } + + $xmlLine->setAttribute('count', (string) $data['count']); + $xmlFile->appendChild($xmlLine); + } + + $linesOfCode = $item->linesOfCode(); + + $xmlMetrics = $xmlDocument->createElement('metrics'); + $xmlMetrics->setAttribute('loc', (string) $linesOfCode['linesOfCode']); + $xmlMetrics->setAttribute('ncloc', (string) $linesOfCode['nonCommentLinesOfCode']); + $xmlMetrics->setAttribute('classes', (string) $item->numberOfClassesAndTraits()); + $xmlMetrics->setAttribute('methods', (string) $item->numberOfMethods()); + $xmlMetrics->setAttribute('coveredmethods', (string) $item->numberOfTestedMethods()); + $xmlMetrics->setAttribute('conditionals', (string) $item->numberOfExecutableBranches()); + $xmlMetrics->setAttribute('coveredconditionals', (string) $item->numberOfExecutedBranches()); + $xmlMetrics->setAttribute('statements', (string) $item->numberOfExecutableLines()); + $xmlMetrics->setAttribute('coveredstatements', (string) $item->numberOfExecutedLines()); + $xmlMetrics->setAttribute('elements', (string) ($item->numberOfMethods() + $item->numberOfExecutableLines() + $item->numberOfExecutableBranches())); + $xmlMetrics->setAttribute('coveredelements', (string) ($item->numberOfTestedMethods() + $item->numberOfExecutedLines() + $item->numberOfExecutedBranches())); + $xmlFile->appendChild($xmlMetrics); + + if ($namespace === 'global') { + $xmlProject->appendChild($xmlFile); + } else { + if (!isset($packages[$namespace])) { + $packages[$namespace] = $xmlDocument->createElement( + 'package' + ); + + $packages[$namespace]->setAttribute('name', $namespace); + $xmlProject->appendChild($packages[$namespace]); + } + + $packages[$namespace]->appendChild($xmlFile); + } + } + + $linesOfCode = $report->linesOfCode(); + + $xmlMetrics = $xmlDocument->createElement('metrics'); + $xmlMetrics->setAttribute('files', (string) count($report)); + $xmlMetrics->setAttribute('loc', (string) $linesOfCode['linesOfCode']); + $xmlMetrics->setAttribute('ncloc', (string) $linesOfCode['nonCommentLinesOfCode']); + $xmlMetrics->setAttribute('classes', (string) $report->numberOfClassesAndTraits()); + $xmlMetrics->setAttribute('methods', (string) $report->numberOfMethods()); + $xmlMetrics->setAttribute('coveredmethods', (string) $report->numberOfTestedMethods()); + $xmlMetrics->setAttribute('conditionals', (string) $report->numberOfExecutableBranches()); + $xmlMetrics->setAttribute('coveredconditionals', (string) $report->numberOfExecutedBranches()); + $xmlMetrics->setAttribute('statements', (string) $report->numberOfExecutableLines()); + $xmlMetrics->setAttribute('coveredstatements', (string) $report->numberOfExecutedLines()); + $xmlMetrics->setAttribute('elements', (string) ($report->numberOfMethods() + $report->numberOfExecutableLines() + $report->numberOfExecutableBranches())); + $xmlMetrics->setAttribute('coveredelements', (string) ($report->numberOfTestedMethods() + $report->numberOfExecutedLines() + $report->numberOfExecutedBranches())); + $xmlProject->appendChild($xmlMetrics); + + $buffer = $xmlDocument->saveXML(); + + if ($target !== null) { + if (!strpos($target, '://') !== false) { + Filesystem::createDirectory(dirname($target)); + } + + if (@file_put_contents($target, $buffer) === false) { + throw new WriteOperationFailedException($target); + } + } + + return $buffer; + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Report/Cobertura.php b/vendor/phpunit/php-code-coverage/src/Report/Cobertura.php new file mode 100644 index 00000000..138a31eb --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Cobertura.php @@ -0,0 +1,309 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Report; + +use function basename; +use function count; +use function dirname; +use function file_put_contents; +use function preg_match; +use function range; +use function str_replace; +use function strpos; +use function time; +use DOMImplementation; +use SebastianBergmann\CodeCoverage\CodeCoverage; +use SebastianBergmann\CodeCoverage\Driver\WriteOperationFailedException; +use SebastianBergmann\CodeCoverage\Node\File; +use SebastianBergmann\CodeCoverage\Util\Filesystem; + +final class Cobertura +{ + /** + * @throws WriteOperationFailedException + */ + public function process(CodeCoverage $coverage, ?string $target = null): string + { + $time = (string) time(); + + $report = $coverage->getReport(); + + $implementation = new DOMImplementation; + + $documentType = $implementation->createDocumentType( + 'coverage', + '', + 'http://cobertura.sourceforge.net/xml/coverage-04.dtd' + ); + + $document = $implementation->createDocument('', '', $documentType); + $document->xmlVersion = '1.0'; + $document->encoding = 'UTF-8'; + $document->formatOutput = true; + + $coverageElement = $document->createElement('coverage'); + + $linesValid = $report->numberOfExecutableLines(); + $linesCovered = $report->numberOfExecutedLines(); + $lineRate = $linesValid === 0 ? 0 : ($linesCovered / $linesValid); + $coverageElement->setAttribute('line-rate', (string) $lineRate); + + $branchesValid = $report->numberOfExecutableBranches(); + $branchesCovered = $report->numberOfExecutedBranches(); + $branchRate = $branchesValid === 0 ? 0 : ($branchesCovered / $branchesValid); + $coverageElement->setAttribute('branch-rate', (string) $branchRate); + + $coverageElement->setAttribute('lines-covered', (string) $report->numberOfExecutedLines()); + $coverageElement->setAttribute('lines-valid', (string) $report->numberOfExecutableLines()); + $coverageElement->setAttribute('branches-covered', (string) $report->numberOfExecutedBranches()); + $coverageElement->setAttribute('branches-valid', (string) $report->numberOfExecutableBranches()); + $coverageElement->setAttribute('complexity', ''); + $coverageElement->setAttribute('version', '0.4'); + $coverageElement->setAttribute('timestamp', $time); + + $document->appendChild($coverageElement); + + $sourcesElement = $document->createElement('sources'); + $coverageElement->appendChild($sourcesElement); + + $sourceElement = $document->createElement('source', $report->pathAsString()); + $sourcesElement->appendChild($sourceElement); + + $packagesElement = $document->createElement('packages'); + $coverageElement->appendChild($packagesElement); + + $complexity = 0; + + foreach ($report as $item) { + if (!$item instanceof File) { + continue; + } + + $packageElement = $document->createElement('package'); + $packageComplexity = 0; + + $packageElement->setAttribute('name', str_replace($report->pathAsString() . DIRECTORY_SEPARATOR, '', $item->pathAsString())); + + $linesValid = $item->numberOfExecutableLines(); + $linesCovered = $item->numberOfExecutedLines(); + $lineRate = $linesValid === 0 ? 0 : ($linesCovered / $linesValid); + + $packageElement->setAttribute('line-rate', (string) $lineRate); + + $branchesValid = $item->numberOfExecutableBranches(); + $branchesCovered = $item->numberOfExecutedBranches(); + $branchRate = $branchesValid === 0 ? 0 : ($branchesCovered / $branchesValid); + + $packageElement->setAttribute('branch-rate', (string) $branchRate); + + $packageElement->setAttribute('complexity', ''); + $packagesElement->appendChild($packageElement); + + $classesElement = $document->createElement('classes'); + + $packageElement->appendChild($classesElement); + + $classes = $item->classesAndTraits(); + $coverageData = $item->lineCoverageData(); + + foreach ($classes as $className => $class) { + $complexity += $class['ccn']; + $packageComplexity += $class['ccn']; + + if (!empty($class['package']['namespace'])) { + $className = $class['package']['namespace'] . '\\' . $className; + } + + $linesValid = $class['executableLines']; + $linesCovered = $class['executedLines']; + $lineRate = $linesValid === 0 ? 0 : ($linesCovered / $linesValid); + + $branchesValid = $class['executableBranches']; + $branchesCovered = $class['executedBranches']; + $branchRate = $branchesValid === 0 ? 0 : ($branchesCovered / $branchesValid); + + $classElement = $document->createElement('class'); + + $classElement->setAttribute('name', $className); + $classElement->setAttribute('filename', str_replace($report->pathAsString() . DIRECTORY_SEPARATOR, '', $item->pathAsString())); + $classElement->setAttribute('line-rate', (string) $lineRate); + $classElement->setAttribute('branch-rate', (string) $branchRate); + $classElement->setAttribute('complexity', (string) $class['ccn']); + + $classesElement->appendChild($classElement); + + $methodsElement = $document->createElement('methods'); + + $classElement->appendChild($methodsElement); + + $classLinesElement = $document->createElement('lines'); + + $classElement->appendChild($classLinesElement); + + foreach ($class['methods'] as $methodName => $method) { + if ($method['executableLines'] === 0) { + continue; + } + + preg_match("/\((.*?)\)/", $method['signature'], $signature); + + $linesValid = $method['executableLines']; + $linesCovered = $method['executedLines']; + $lineRate = $linesValid === 0 ? 0 : ($linesCovered / $linesValid); + + $branchesValid = $method['executableBranches']; + $branchesCovered = $method['executedBranches']; + $branchRate = $branchesValid === 0 ? 0 : ($branchesCovered / $branchesValid); + + $methodElement = $document->createElement('method'); + + $methodElement->setAttribute('name', $methodName); + $methodElement->setAttribute('signature', $signature[1]); + $methodElement->setAttribute('line-rate', (string) $lineRate); + $methodElement->setAttribute('branch-rate', (string) $branchRate); + $methodElement->setAttribute('complexity', (string) $method['ccn']); + + $methodLinesElement = $document->createElement('lines'); + + $methodElement->appendChild($methodLinesElement); + + foreach (range($method['startLine'], $method['endLine']) as $line) { + if (!isset($coverageData[$line]) || $coverageData[$line] === null) { + continue; + } + $methodLineElement = $document->createElement('line'); + + $methodLineElement->setAttribute('number', (string) $line); + $methodLineElement->setAttribute('hits', (string) count($coverageData[$line])); + + $methodLinesElement->appendChild($methodLineElement); + + $classLineElement = $methodLineElement->cloneNode(); + + $classLinesElement->appendChild($classLineElement); + } + + $methodsElement->appendChild($methodElement); + } + } + + if ($item->numberOfFunctions() === 0) { + $packageElement->setAttribute('complexity', (string) $packageComplexity); + + continue; + } + + $functionsComplexity = 0; + $functionsLinesValid = 0; + $functionsLinesCovered = 0; + $functionsBranchesValid = 0; + $functionsBranchesCovered = 0; + + $classElement = $document->createElement('class'); + $classElement->setAttribute('name', basename($item->pathAsString())); + $classElement->setAttribute('filename', str_replace($report->pathAsString() . DIRECTORY_SEPARATOR, '', $item->pathAsString())); + + $methodsElement = $document->createElement('methods'); + + $classElement->appendChild($methodsElement); + + $classLinesElement = $document->createElement('lines'); + + $classElement->appendChild($classLinesElement); + + $functions = $item->functions(); + + foreach ($functions as $functionName => $function) { + if ($function['executableLines'] === 0) { + continue; + } + + $complexity += $function['ccn']; + $packageComplexity += $function['ccn']; + $functionsComplexity += $function['ccn']; + + $linesValid = $function['executableLines']; + $linesCovered = $function['executedLines']; + $lineRate = $linesValid === 0 ? 0 : ($linesCovered / $linesValid); + + $functionsLinesValid += $linesValid; + $functionsLinesCovered += $linesCovered; + + $branchesValid = $function['executableBranches']; + $branchesCovered = $function['executedBranches']; + $branchRate = $branchesValid === 0 ? 0 : ($branchesCovered / $branchesValid); + + $functionsBranchesValid += $branchesValid; + $functionsBranchesCovered += $branchesValid; + + $methodElement = $document->createElement('method'); + + $methodElement->setAttribute('name', $functionName); + $methodElement->setAttribute('signature', $function['signature']); + $methodElement->setAttribute('line-rate', (string) $lineRate); + $methodElement->setAttribute('branch-rate', (string) $branchRate); + $methodElement->setAttribute('complexity', (string) $function['ccn']); + + $methodLinesElement = $document->createElement('lines'); + + $methodElement->appendChild($methodLinesElement); + + foreach (range($function['startLine'], $function['endLine']) as $line) { + if (!isset($coverageData[$line]) || $coverageData[$line] === null) { + continue; + } + $methodLineElement = $document->createElement('line'); + + $methodLineElement->setAttribute('number', (string) $line); + $methodLineElement->setAttribute('hits', (string) count($coverageData[$line])); + + $methodLinesElement->appendChild($methodLineElement); + + $classLineElement = $methodLineElement->cloneNode(); + + $classLinesElement->appendChild($classLineElement); + } + + $methodsElement->appendChild($methodElement); + } + + $packageElement->setAttribute('complexity', (string) $packageComplexity); + + if ($functionsLinesValid === 0) { + continue; + } + + $lineRate = $functionsLinesCovered / $functionsLinesValid; + $branchRate = $functionsBranchesValid === 0 ? 0 : ($functionsBranchesCovered / $functionsBranchesValid); + + $classElement->setAttribute('line-rate', (string) $lineRate); + $classElement->setAttribute('branch-rate', (string) $branchRate); + $classElement->setAttribute('complexity', (string) $functionsComplexity); + + $classesElement->appendChild($classElement); + } + + $coverageElement->setAttribute('complexity', (string) $complexity); + + $buffer = $document->saveXML(); + + if ($target !== null) { + if (!strpos($target, '://') !== false) { + Filesystem::createDirectory(dirname($target)); + } + + if (@file_put_contents($target, $buffer) === false) { + throw new WriteOperationFailedException($target); + } + } + + return $buffer; + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Report/Crap4j.php b/vendor/phpunit/php-code-coverage/src/Report/Crap4j.php new file mode 100644 index 00000000..2d91567a --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Crap4j.php @@ -0,0 +1,156 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Report; + +use function date; +use function dirname; +use function file_put_contents; +use function htmlspecialchars; +use function is_string; +use function round; +use function strpos; +use DOMDocument; +use SebastianBergmann\CodeCoverage\CodeCoverage; +use SebastianBergmann\CodeCoverage\Driver\WriteOperationFailedException; +use SebastianBergmann\CodeCoverage\Node\File; +use SebastianBergmann\CodeCoverage\Util\Filesystem; + +final class Crap4j +{ + /** + * @var int + */ + private $threshold; + + public function __construct(int $threshold = 30) + { + $this->threshold = $threshold; + } + + /** + * @throws WriteOperationFailedException + */ + public function process(CodeCoverage $coverage, ?string $target = null, ?string $name = null): string + { + $document = new DOMDocument('1.0', 'UTF-8'); + $document->formatOutput = true; + + $root = $document->createElement('crap_result'); + $document->appendChild($root); + + $project = $document->createElement('project', is_string($name) ? $name : ''); + $root->appendChild($project); + $root->appendChild($document->createElement('timestamp', date('Y-m-d H:i:s'))); + + $stats = $document->createElement('stats'); + $methodsNode = $document->createElement('methods'); + + $report = $coverage->getReport(); + unset($coverage); + + $fullMethodCount = 0; + $fullCrapMethodCount = 0; + $fullCrapLoad = 0; + $fullCrap = 0; + + foreach ($report as $item) { + $namespace = 'global'; + + if (!$item instanceof File) { + continue; + } + + $file = $document->createElement('file'); + $file->setAttribute('name', $item->pathAsString()); + + $classes = $item->classesAndTraits(); + + foreach ($classes as $className => $class) { + foreach ($class['methods'] as $methodName => $method) { + $crapLoad = $this->crapLoad((float) $method['crap'], $method['ccn'], $method['coverage']); + + $fullCrap += $method['crap']; + $fullCrapLoad += $crapLoad; + $fullMethodCount++; + + if ($method['crap'] >= $this->threshold) { + $fullCrapMethodCount++; + } + + $methodNode = $document->createElement('method'); + + if (!empty($class['namespace'])) { + $namespace = $class['namespace']; + } + + $methodNode->appendChild($document->createElement('package', $namespace)); + $methodNode->appendChild($document->createElement('className', $className)); + $methodNode->appendChild($document->createElement('methodName', $methodName)); + $methodNode->appendChild($document->createElement('methodSignature', htmlspecialchars($method['signature']))); + $methodNode->appendChild($document->createElement('fullMethod', htmlspecialchars($method['signature']))); + $methodNode->appendChild($document->createElement('crap', (string) $this->roundValue((float) $method['crap']))); + $methodNode->appendChild($document->createElement('complexity', (string) $method['ccn'])); + $methodNode->appendChild($document->createElement('coverage', (string) $this->roundValue($method['coverage']))); + $methodNode->appendChild($document->createElement('crapLoad', (string) round($crapLoad))); + + $methodsNode->appendChild($methodNode); + } + } + } + + $stats->appendChild($document->createElement('name', 'Method Crap Stats')); + $stats->appendChild($document->createElement('methodCount', (string) $fullMethodCount)); + $stats->appendChild($document->createElement('crapMethodCount', (string) $fullCrapMethodCount)); + $stats->appendChild($document->createElement('crapLoad', (string) round($fullCrapLoad))); + $stats->appendChild($document->createElement('totalCrap', (string) $fullCrap)); + + $crapMethodPercent = 0; + + if ($fullMethodCount > 0) { + $crapMethodPercent = $this->roundValue((100 * $fullCrapMethodCount) / $fullMethodCount); + } + + $stats->appendChild($document->createElement('crapMethodPercent', (string) $crapMethodPercent)); + + $root->appendChild($stats); + $root->appendChild($methodsNode); + + $buffer = $document->saveXML(); + + if ($target !== null) { + if (!strpos($target, '://') !== false) { + Filesystem::createDirectory(dirname($target)); + } + + if (@file_put_contents($target, $buffer) === false) { + throw new WriteOperationFailedException($target); + } + } + + return $buffer; + } + + private function crapLoad(float $crapValue, int $cyclomaticComplexity, float $coveragePercent): float + { + $crapLoad = 0; + + if ($crapValue >= $this->threshold) { + $crapLoad += $cyclomaticComplexity * (1.0 - $coveragePercent / 100); + $crapLoad += $cyclomaticComplexity / $this->threshold; + } + + return $crapLoad; + } + + private function roundValue(float $value): float + { + return round($value, 2); + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Facade.php b/vendor/phpunit/php-code-coverage/src/Report/Html/Facade.php new file mode 100644 index 00000000..69935d73 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Facade.php @@ -0,0 +1,147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Report\Html; + +use const DIRECTORY_SEPARATOR; +use function copy; +use function date; +use function dirname; +use function substr; +use SebastianBergmann\CodeCoverage\CodeCoverage; +use SebastianBergmann\CodeCoverage\InvalidArgumentException; +use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode; +use SebastianBergmann\CodeCoverage\Util\Filesystem; + +final class Facade +{ + /** + * @var string + */ + private $templatePath; + + /** + * @var string + */ + private $generator; + + /** + * @var int + */ + private $lowUpperBound; + + /** + * @var int + */ + private $highLowerBound; + + public function __construct(int $lowUpperBound = 50, int $highLowerBound = 90, string $generator = '') + { + if ($lowUpperBound > $highLowerBound) { + throw new InvalidArgumentException( + '$lowUpperBound must not be larger than $highLowerBound' + ); + } + + $this->generator = $generator; + $this->highLowerBound = $highLowerBound; + $this->lowUpperBound = $lowUpperBound; + $this->templatePath = __DIR__ . '/Renderer/Template/'; + } + + public function process(CodeCoverage $coverage, string $target): void + { + $target = $this->directory($target); + $report = $coverage->getReport(); + $date = date('D M j G:i:s T Y'); + + $dashboard = new Dashboard( + $this->templatePath, + $this->generator, + $date, + $this->lowUpperBound, + $this->highLowerBound, + $coverage->collectsBranchAndPathCoverage() + ); + + $directory = new Directory( + $this->templatePath, + $this->generator, + $date, + $this->lowUpperBound, + $this->highLowerBound, + $coverage->collectsBranchAndPathCoverage() + ); + + $file = new File( + $this->templatePath, + $this->generator, + $date, + $this->lowUpperBound, + $this->highLowerBound, + $coverage->collectsBranchAndPathCoverage() + ); + + $directory->render($report, $target . 'index.html'); + $dashboard->render($report, $target . 'dashboard.html'); + + foreach ($report as $node) { + $id = $node->id(); + + if ($node instanceof DirectoryNode) { + Filesystem::createDirectory($target . $id); + + $directory->render($node, $target . $id . '/index.html'); + $dashboard->render($node, $target . $id . '/dashboard.html'); + } else { + $dir = dirname($target . $id); + + Filesystem::createDirectory($dir); + + $file->render($node, $target . $id); + } + } + + $this->copyFiles($target); + } + + private function copyFiles(string $target): void + { + $dir = $this->directory($target . '_css'); + + copy($this->templatePath . 'css/bootstrap.min.css', $dir . 'bootstrap.min.css'); + copy($this->templatePath . 'css/nv.d3.min.css', $dir . 'nv.d3.min.css'); + copy($this->templatePath . 'css/style.css', $dir . 'style.css'); + copy($this->templatePath . 'css/custom.css', $dir . 'custom.css'); + copy($this->templatePath . 'css/octicons.css', $dir . 'octicons.css'); + + $dir = $this->directory($target . '_icons'); + copy($this->templatePath . 'icons/file-code.svg', $dir . 'file-code.svg'); + copy($this->templatePath . 'icons/file-directory.svg', $dir . 'file-directory.svg'); + + $dir = $this->directory($target . '_js'); + copy($this->templatePath . 'js/bootstrap.min.js', $dir . 'bootstrap.min.js'); + copy($this->templatePath . 'js/popper.min.js', $dir . 'popper.min.js'); + copy($this->templatePath . 'js/d3.min.js', $dir . 'd3.min.js'); + copy($this->templatePath . 'js/jquery.min.js', $dir . 'jquery.min.js'); + copy($this->templatePath . 'js/nv.d3.min.js', $dir . 'nv.d3.min.js'); + copy($this->templatePath . 'js/file.js', $dir . 'file.js'); + } + + private function directory(string $directory): string + { + if (substr($directory, -1, 1) != DIRECTORY_SEPARATOR) { + $directory .= DIRECTORY_SEPARATOR; + } + + Filesystem::createDirectory($directory); + + return $directory; + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer.php b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer.php new file mode 100644 index 00000000..fe285b18 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer.php @@ -0,0 +1,314 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Report\Html; + +use function array_pop; +use function count; +use function sprintf; +use function str_repeat; +use function substr_count; +use SebastianBergmann\CodeCoverage\Node\AbstractNode; +use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode; +use SebastianBergmann\CodeCoverage\Node\File as FileNode; +use SebastianBergmann\CodeCoverage\Version; +use SebastianBergmann\Environment\Runtime; +use SebastianBergmann\Template\Template; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +abstract class Renderer +{ + /** + * @var string + */ + protected $templatePath; + + /** + * @var string + */ + protected $generator; + + /** + * @var string + */ + protected $date; + + /** + * @var int + */ + protected $lowUpperBound; + + /** + * @var int + */ + protected $highLowerBound; + + /** + * @var bool + */ + protected $hasBranchCoverage; + + /** + * @var string + */ + protected $version; + + public function __construct(string $templatePath, string $generator, string $date, int $lowUpperBound, int $highLowerBound, bool $hasBranchCoverage) + { + $this->templatePath = $templatePath; + $this->generator = $generator; + $this->date = $date; + $this->lowUpperBound = $lowUpperBound; + $this->highLowerBound = $highLowerBound; + $this->version = Version::id(); + $this->hasBranchCoverage = $hasBranchCoverage; + } + + protected function renderItemTemplate(Template $template, array $data): string + { + $numSeparator = ' / '; + + if (isset($data['numClasses']) && $data['numClasses'] > 0) { + $classesLevel = $this->colorLevel($data['testedClassesPercent']); + + $classesNumber = $data['numTestedClasses'] . $numSeparator . + $data['numClasses']; + + $classesBar = $this->coverageBar( + $data['testedClassesPercent'] + ); + } else { + $classesLevel = ''; + $classesNumber = '0' . $numSeparator . '0'; + $classesBar = ''; + $data['testedClassesPercentAsString'] = 'n/a'; + } + + if ($data['numMethods'] > 0) { + $methodsLevel = $this->colorLevel($data['testedMethodsPercent']); + + $methodsNumber = $data['numTestedMethods'] . $numSeparator . + $data['numMethods']; + + $methodsBar = $this->coverageBar( + $data['testedMethodsPercent'] + ); + } else { + $methodsLevel = ''; + $methodsNumber = '0' . $numSeparator . '0'; + $methodsBar = ''; + $data['testedMethodsPercentAsString'] = 'n/a'; + } + + if ($data['numExecutableLines'] > 0) { + $linesLevel = $this->colorLevel($data['linesExecutedPercent']); + + $linesNumber = $data['numExecutedLines'] . $numSeparator . + $data['numExecutableLines']; + + $linesBar = $this->coverageBar( + $data['linesExecutedPercent'] + ); + } else { + $linesLevel = ''; + $linesNumber = '0' . $numSeparator . '0'; + $linesBar = ''; + $data['linesExecutedPercentAsString'] = 'n/a'; + } + + if ($data['numExecutablePaths'] > 0) { + $pathsLevel = $this->colorLevel($data['pathsExecutedPercent']); + + $pathsNumber = $data['numExecutedPaths'] . $numSeparator . + $data['numExecutablePaths']; + + $pathsBar = $this->coverageBar( + $data['pathsExecutedPercent'] + ); + } else { + $pathsLevel = ''; + $pathsNumber = '0' . $numSeparator . '0'; + $pathsBar = ''; + $data['pathsExecutedPercentAsString'] = 'n/a'; + } + + if ($data['numExecutableBranches'] > 0) { + $branchesLevel = $this->colorLevel($data['branchesExecutedPercent']); + + $branchesNumber = $data['numExecutedBranches'] . $numSeparator . + $data['numExecutableBranches']; + + $branchesBar = $this->coverageBar( + $data['branchesExecutedPercent'] + ); + } else { + $branchesLevel = ''; + $branchesNumber = '0' . $numSeparator . '0'; + $branchesBar = ''; + $data['branchesExecutedPercentAsString'] = 'n/a'; + } + + $template->setVar( + [ + 'icon' => $data['icon'] ?? '', + 'crap' => $data['crap'] ?? '', + 'name' => $data['name'], + 'lines_bar' => $linesBar, + 'lines_executed_percent' => $data['linesExecutedPercentAsString'], + 'lines_level' => $linesLevel, + 'lines_number' => $linesNumber, + 'paths_bar' => $pathsBar, + 'paths_executed_percent' => $data['pathsExecutedPercentAsString'], + 'paths_level' => $pathsLevel, + 'paths_number' => $pathsNumber, + 'branches_bar' => $branchesBar, + 'branches_executed_percent' => $data['branchesExecutedPercentAsString'], + 'branches_level' => $branchesLevel, + 'branches_number' => $branchesNumber, + 'methods_bar' => $methodsBar, + 'methods_tested_percent' => $data['testedMethodsPercentAsString'], + 'methods_level' => $methodsLevel, + 'methods_number' => $methodsNumber, + 'classes_bar' => $classesBar, + 'classes_tested_percent' => $data['testedClassesPercentAsString'] ?? '', + 'classes_level' => $classesLevel, + 'classes_number' => $classesNumber, + ] + ); + + return $template->render(); + } + + protected function setCommonTemplateVariables(Template $template, AbstractNode $node): void + { + $template->setVar( + [ + 'id' => $node->id(), + 'full_path' => $node->pathAsString(), + 'path_to_root' => $this->pathToRoot($node), + 'breadcrumbs' => $this->breadcrumbs($node), + 'date' => $this->date, + 'version' => $this->version, + 'runtime' => $this->runtimeString(), + 'generator' => $this->generator, + 'low_upper_bound' => $this->lowUpperBound, + 'high_lower_bound' => $this->highLowerBound, + ] + ); + } + + protected function breadcrumbs(AbstractNode $node): string + { + $breadcrumbs = ''; + $path = $node->pathAsArray(); + $pathToRoot = []; + $max = count($path); + + if ($node instanceof FileNode) { + $max--; + } + + for ($i = 0; $i < $max; $i++) { + $pathToRoot[] = str_repeat('../', $i); + } + + foreach ($path as $step) { + if ($step !== $node) { + $breadcrumbs .= $this->inactiveBreadcrumb( + $step, + array_pop($pathToRoot) + ); + } else { + $breadcrumbs .= $this->activeBreadcrumb($step); + } + } + + return $breadcrumbs; + } + + protected function activeBreadcrumb(AbstractNode $node): string + { + $buffer = sprintf( + ' ' . "\n", + $node->name() + ); + + if ($node instanceof DirectoryNode) { + $buffer .= ' ' . "\n"; + } + + return $buffer; + } + + protected function inactiveBreadcrumb(AbstractNode $node, string $pathToRoot): string + { + return sprintf( + ' ' . "\n", + $pathToRoot, + $node->name() + ); + } + + protected function pathToRoot(AbstractNode $node): string + { + $id = $node->id(); + $depth = substr_count($id, '/'); + + if ($id !== 'index' && + $node instanceof DirectoryNode) { + $depth++; + } + + return str_repeat('../', $depth); + } + + protected function coverageBar(float $percent): string + { + $level = $this->colorLevel($percent); + + $templateName = $this->templatePath . ($this->hasBranchCoverage ? 'coverage_bar_branch.html' : 'coverage_bar.html'); + $template = new Template( + $templateName, + '{{', + '}}' + ); + + $template->setVar(['level' => $level, 'percent' => sprintf('%.2F', $percent)]); + + return $template->render(); + } + + protected function colorLevel(float $percent): string + { + if ($percent <= $this->lowUpperBound) { + return 'danger'; + } + + if ($percent > $this->lowUpperBound && + $percent < $this->highLowerBound) { + return 'warning'; + } + + return 'success'; + } + + private function runtimeString(): string + { + $runtime = new Runtime; + + return sprintf( + '%s %s', + $runtime->getVendorUrl(), + $runtime->getName(), + $runtime->getVersion() + ); + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Dashboard.php b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Dashboard.php new file mode 100644 index 00000000..b44870b5 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Dashboard.php @@ -0,0 +1,288 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Report\Html; + +use function array_values; +use function arsort; +use function asort; +use function count; +use function explode; +use function floor; +use function json_encode; +use function sprintf; +use function str_replace; +use SebastianBergmann\CodeCoverage\Node\AbstractNode; +use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode; +use SebastianBergmann\Template\Template; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class Dashboard extends Renderer +{ + public function render(DirectoryNode $node, string $file): void + { + $classes = $node->classesAndTraits(); + $templateName = $this->templatePath . ($this->hasBranchCoverage ? 'dashboard_branch.html' : 'dashboard.html'); + $template = new Template( + $templateName, + '{{', + '}}' + ); + + $this->setCommonTemplateVariables($template, $node); + + $baseLink = $node->id() . '/'; + $complexity = $this->complexity($classes, $baseLink); + $coverageDistribution = $this->coverageDistribution($classes); + $insufficientCoverage = $this->insufficientCoverage($classes, $baseLink); + $projectRisks = $this->projectRisks($classes, $baseLink); + + $template->setVar( + [ + 'insufficient_coverage_classes' => $insufficientCoverage['class'], + 'insufficient_coverage_methods' => $insufficientCoverage['method'], + 'project_risks_classes' => $projectRisks['class'], + 'project_risks_methods' => $projectRisks['method'], + 'complexity_class' => $complexity['class'], + 'complexity_method' => $complexity['method'], + 'class_coverage_distribution' => $coverageDistribution['class'], + 'method_coverage_distribution' => $coverageDistribution['method'], + ] + ); + + $template->renderTo($file); + } + + protected function activeBreadcrumb(AbstractNode $node): string + { + return sprintf( + ' ' . "\n" . + ' ' . "\n", + $node->name() + ); + } + + /** + * Returns the data for the Class/Method Complexity charts. + */ + private function complexity(array $classes, string $baseLink): array + { + $result = ['class' => [], 'method' => []]; + + foreach ($classes as $className => $class) { + foreach ($class['methods'] as $methodName => $method) { + if ($className !== '*') { + $methodName = $className . '::' . $methodName; + } + + $result['method'][] = [ + $method['coverage'], + $method['ccn'], + sprintf( + '%s', + str_replace($baseLink, '', $method['link']), + $methodName + ), + ]; + } + + $result['class'][] = [ + $class['coverage'], + $class['ccn'], + sprintf( + '%s', + str_replace($baseLink, '', $class['link']), + $className + ), + ]; + } + + return [ + 'class' => json_encode($result['class']), + 'method' => json_encode($result['method']), + ]; + } + + /** + * Returns the data for the Class / Method Coverage Distribution chart. + */ + private function coverageDistribution(array $classes): array + { + $result = [ + 'class' => [ + '0%' => 0, + '0-10%' => 0, + '10-20%' => 0, + '20-30%' => 0, + '30-40%' => 0, + '40-50%' => 0, + '50-60%' => 0, + '60-70%' => 0, + '70-80%' => 0, + '80-90%' => 0, + '90-100%' => 0, + '100%' => 0, + ], + 'method' => [ + '0%' => 0, + '0-10%' => 0, + '10-20%' => 0, + '20-30%' => 0, + '30-40%' => 0, + '40-50%' => 0, + '50-60%' => 0, + '60-70%' => 0, + '70-80%' => 0, + '80-90%' => 0, + '90-100%' => 0, + '100%' => 0, + ], + ]; + + foreach ($classes as $class) { + foreach ($class['methods'] as $methodName => $method) { + if ($method['coverage'] === 0) { + $result['method']['0%']++; + } elseif ($method['coverage'] === 100) { + $result['method']['100%']++; + } else { + $key = floor($method['coverage'] / 10) * 10; + $key = $key . '-' . ($key + 10) . '%'; + $result['method'][$key]++; + } + } + + if ($class['coverage'] === 0) { + $result['class']['0%']++; + } elseif ($class['coverage'] === 100) { + $result['class']['100%']++; + } else { + $key = floor($class['coverage'] / 10) * 10; + $key = $key . '-' . ($key + 10) . '%'; + $result['class'][$key]++; + } + } + + return [ + 'class' => json_encode(array_values($result['class'])), + 'method' => json_encode(array_values($result['method'])), + ]; + } + + /** + * Returns the classes / methods with insufficient coverage. + */ + private function insufficientCoverage(array $classes, string $baseLink): array + { + $leastTestedClasses = []; + $leastTestedMethods = []; + $result = ['class' => '', 'method' => '']; + + foreach ($classes as $className => $class) { + foreach ($class['methods'] as $methodName => $method) { + if ($method['coverage'] < $this->highLowerBound) { + $key = $methodName; + + if ($className !== '*') { + $key = $className . '::' . $methodName; + } + + $leastTestedMethods[$key] = $method['coverage']; + } + } + + if ($class['coverage'] < $this->highLowerBound) { + $leastTestedClasses[$className] = $class['coverage']; + } + } + + asort($leastTestedClasses); + asort($leastTestedMethods); + + foreach ($leastTestedClasses as $className => $coverage) { + $result['class'] .= sprintf( + ' %s%d%%' . "\n", + str_replace($baseLink, '', $classes[$className]['link']), + $className, + $coverage + ); + } + + foreach ($leastTestedMethods as $methodName => $coverage) { + [$class, $method] = explode('::', $methodName); + + $result['method'] .= sprintf( + ' %s%d%%' . "\n", + str_replace($baseLink, '', $classes[$class]['methods'][$method]['link']), + $methodName, + $method, + $coverage + ); + } + + return $result; + } + + /** + * Returns the project risks according to the CRAP index. + */ + private function projectRisks(array $classes, string $baseLink): array + { + $classRisks = []; + $methodRisks = []; + $result = ['class' => '', 'method' => '']; + + foreach ($classes as $className => $class) { + foreach ($class['methods'] as $methodName => $method) { + if ($method['coverage'] < $this->highLowerBound && $method['ccn'] > 1) { + $key = $methodName; + + if ($className !== '*') { + $key = $className . '::' . $methodName; + } + + $methodRisks[$key] = $method['crap']; + } + } + + if ($class['coverage'] < $this->highLowerBound && + $class['ccn'] > count($class['methods'])) { + $classRisks[$className] = $class['crap']; + } + } + + arsort($classRisks); + arsort($methodRisks); + + foreach ($classRisks as $className => $crap) { + $result['class'] .= sprintf( + ' %s%d' . "\n", + str_replace($baseLink, '', $classes[$className]['link']), + $className, + $crap + ); + } + + foreach ($methodRisks as $methodName => $crap) { + [$class, $method] = explode('::', $methodName); + + $result['method'] .= sprintf( + ' %s%d' . "\n", + str_replace($baseLink, '', $classes[$class]['methods'][$method]['link']), + $methodName, + $method, + $crap + ); + } + + return $result; + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Directory.php b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Directory.php new file mode 100644 index 00000000..faacbc31 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Directory.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Report\Html; + +use function count; +use function sprintf; +use function str_repeat; +use SebastianBergmann\CodeCoverage\Node\AbstractNode as Node; +use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode; +use SebastianBergmann\Template\Template; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class Directory extends Renderer +{ + public function render(DirectoryNode $node, string $file): void + { + $templateName = $this->templatePath . ($this->hasBranchCoverage ? 'directory_branch.html' : 'directory.html'); + $template = new Template($templateName, '{{', '}}'); + + $this->setCommonTemplateVariables($template, $node); + + $items = $this->renderItem($node, true); + + foreach ($node->directories() as $item) { + $items .= $this->renderItem($item); + } + + foreach ($node->files() as $item) { + $items .= $this->renderItem($item); + } + + $template->setVar( + [ + 'id' => $node->id(), + 'items' => $items, + ] + ); + + $template->renderTo($file); + } + + private function renderItem(Node $node, bool $total = false): string + { + $data = [ + 'numClasses' => $node->numberOfClassesAndTraits(), + 'numTestedClasses' => $node->numberOfTestedClassesAndTraits(), + 'numMethods' => $node->numberOfFunctionsAndMethods(), + 'numTestedMethods' => $node->numberOfTestedFunctionsAndMethods(), + 'linesExecutedPercent' => $node->percentageOfExecutedLines()->asFloat(), + 'linesExecutedPercentAsString' => $node->percentageOfExecutedLines()->asString(), + 'numExecutedLines' => $node->numberOfExecutedLines(), + 'numExecutableLines' => $node->numberOfExecutableLines(), + 'branchesExecutedPercent' => $node->percentageOfExecutedBranches()->asFloat(), + 'branchesExecutedPercentAsString' => $node->percentageOfExecutedBranches()->asString(), + 'numExecutedBranches' => $node->numberOfExecutedBranches(), + 'numExecutableBranches' => $node->numberOfExecutableBranches(), + 'pathsExecutedPercent' => $node->percentageOfExecutedPaths()->asFloat(), + 'pathsExecutedPercentAsString' => $node->percentageOfExecutedPaths()->asString(), + 'numExecutedPaths' => $node->numberOfExecutedPaths(), + 'numExecutablePaths' => $node->numberOfExecutablePaths(), + 'testedMethodsPercent' => $node->percentageOfTestedFunctionsAndMethods()->asFloat(), + 'testedMethodsPercentAsString' => $node->percentageOfTestedFunctionsAndMethods()->asString(), + 'testedClassesPercent' => $node->percentageOfTestedClassesAndTraits()->asFloat(), + 'testedClassesPercentAsString' => $node->percentageOfTestedClassesAndTraits()->asString(), + ]; + + if ($total) { + $data['name'] = 'Total'; + } else { + $up = str_repeat('../', count($node->pathAsArray()) - 2); + $data['icon'] = sprintf('', $up); + + if ($node instanceof DirectoryNode) { + $data['name'] = sprintf( + '%s', + $node->name(), + $node->name() + ); + $data['icon'] = sprintf('', $up); + } elseif ($this->hasBranchCoverage) { + $data['name'] = sprintf( + '%s [line] [branch] [path]', + $node->name(), + $node->name(), + $node->name(), + $node->name() + ); + } else { + $data['name'] = sprintf( + '%s', + $node->name(), + $node->name() + ); + } + } + + $templateName = $this->templatePath . ($this->hasBranchCoverage ? 'directory_item_branch.html' : 'directory_item.html'); + + return $this->renderItemTemplate( + new Template($templateName, '{{', '}}'), + $data + ); + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/File.php b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/File.php new file mode 100644 index 00000000..b59dc89d --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/File.php @@ -0,0 +1,1162 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Report\Html; + +use const ENT_COMPAT; +use const ENT_HTML401; +use const ENT_SUBSTITUTE; +use const T_ABSTRACT; +use const T_ARRAY; +use const T_AS; +use const T_BREAK; +use const T_CALLABLE; +use const T_CASE; +use const T_CATCH; +use const T_CLASS; +use const T_CLONE; +use const T_COMMENT; +use const T_CONST; +use const T_CONTINUE; +use const T_DECLARE; +use const T_DEFAULT; +use const T_DO; +use const T_DOC_COMMENT; +use const T_ECHO; +use const T_ELSE; +use const T_ELSEIF; +use const T_EMPTY; +use const T_ENDDECLARE; +use const T_ENDFOR; +use const T_ENDFOREACH; +use const T_ENDIF; +use const T_ENDSWITCH; +use const T_ENDWHILE; +use const T_EVAL; +use const T_EXIT; +use const T_EXTENDS; +use const T_FINAL; +use const T_FINALLY; +use const T_FOR; +use const T_FOREACH; +use const T_FUNCTION; +use const T_GLOBAL; +use const T_GOTO; +use const T_HALT_COMPILER; +use const T_IF; +use const T_IMPLEMENTS; +use const T_INCLUDE; +use const T_INCLUDE_ONCE; +use const T_INLINE_HTML; +use const T_INSTANCEOF; +use const T_INSTEADOF; +use const T_INTERFACE; +use const T_ISSET; +use const T_LIST; +use const T_NAMESPACE; +use const T_NEW; +use const T_PRINT; +use const T_PRIVATE; +use const T_PROTECTED; +use const T_PUBLIC; +use const T_REQUIRE; +use const T_REQUIRE_ONCE; +use const T_RETURN; +use const T_STATIC; +use const T_SWITCH; +use const T_THROW; +use const T_TRAIT; +use const T_TRY; +use const T_UNSET; +use const T_USE; +use const T_VAR; +use const T_WHILE; +use const T_YIELD; +use const T_YIELD_FROM; +use function array_key_exists; +use function array_keys; +use function array_merge; +use function array_pop; +use function array_unique; +use function constant; +use function count; +use function defined; +use function explode; +use function file_get_contents; +use function htmlspecialchars; +use function is_string; +use function ksort; +use function range; +use function sort; +use function sprintf; +use function str_replace; +use function substr; +use function token_get_all; +use function trim; +use PHPUnit\Runner\BaseTestRunner; +use SebastianBergmann\CodeCoverage\Node\File as FileNode; +use SebastianBergmann\CodeCoverage\Util\Percentage; +use SebastianBergmann\Template\Template; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class File extends Renderer +{ + /** + * @psalm-var array + */ + private static $keywordTokens = []; + + /** + * @var array + */ + private static $formattedSourceCache = []; + + /** + * @var int + */ + private $htmlSpecialCharsFlags = ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE; + + public function render(FileNode $node, string $file): void + { + $templateName = $this->templatePath . ($this->hasBranchCoverage ? 'file_branch.html' : 'file.html'); + $template = new Template($templateName, '{{', '}}'); + $this->setCommonTemplateVariables($template, $node); + + $template->setVar( + [ + 'items' => $this->renderItems($node), + 'lines' => $this->renderSourceWithLineCoverage($node), + 'legend' => '

Covered by small (and larger) testsCovered by medium (and large) testsCovered by large tests (and tests of unknown size)Not coveredNot coverable

', + 'structure' => '', + ] + ); + + $template->renderTo($file . '.html'); + + if ($this->hasBranchCoverage) { + $template->setVar( + [ + 'items' => $this->renderItems($node), + 'lines' => $this->renderSourceWithBranchCoverage($node), + 'legend' => '

Fully coveredPartially coveredNot covered

', + 'structure' => $this->renderBranchStructure($node), + ] + ); + + $template->renderTo($file . '_branch.html'); + + $template->setVar( + [ + 'items' => $this->renderItems($node), + 'lines' => $this->renderSourceWithPathCoverage($node), + 'legend' => '

Fully coveredPartially coveredNot covered

', + 'structure' => $this->renderPathStructure($node), + ] + ); + + $template->renderTo($file . '_path.html'); + } + } + + private function renderItems(FileNode $node): string + { + $templateName = $this->templatePath . ($this->hasBranchCoverage ? 'file_item_branch.html' : 'file_item.html'); + $template = new Template($templateName, '{{', '}}'); + + $methodTemplateName = $this->templatePath . ($this->hasBranchCoverage ? 'method_item_branch.html' : 'method_item.html'); + $methodItemTemplate = new Template( + $methodTemplateName, + '{{', + '}}' + ); + + $items = $this->renderItemTemplate( + $template, + [ + 'name' => 'Total', + 'numClasses' => $node->numberOfClassesAndTraits(), + 'numTestedClasses' => $node->numberOfTestedClassesAndTraits(), + 'numMethods' => $node->numberOfFunctionsAndMethods(), + 'numTestedMethods' => $node->numberOfTestedFunctionsAndMethods(), + 'linesExecutedPercent' => $node->percentageOfExecutedLines()->asFloat(), + 'linesExecutedPercentAsString' => $node->percentageOfExecutedLines()->asString(), + 'numExecutedLines' => $node->numberOfExecutedLines(), + 'numExecutableLines' => $node->numberOfExecutableLines(), + 'branchesExecutedPercent' => $node->percentageOfExecutedBranches()->asFloat(), + 'branchesExecutedPercentAsString' => $node->percentageOfExecutedBranches()->asString(), + 'numExecutedBranches' => $node->numberOfExecutedBranches(), + 'numExecutableBranches' => $node->numberOfExecutableBranches(), + 'pathsExecutedPercent' => $node->percentageOfExecutedPaths()->asFloat(), + 'pathsExecutedPercentAsString' => $node->percentageOfExecutedPaths()->asString(), + 'numExecutedPaths' => $node->numberOfExecutedPaths(), + 'numExecutablePaths' => $node->numberOfExecutablePaths(), + 'testedMethodsPercent' => $node->percentageOfTestedFunctionsAndMethods()->asFloat(), + 'testedMethodsPercentAsString' => $node->percentageOfTestedFunctionsAndMethods()->asString(), + 'testedClassesPercent' => $node->percentageOfTestedClassesAndTraits()->asFloat(), + 'testedClassesPercentAsString' => $node->percentageOfTestedClassesAndTraits()->asString(), + 'crap' => 'CRAP', + ] + ); + + $items .= $this->renderFunctionItems( + $node->functions(), + $methodItemTemplate + ); + + $items .= $this->renderTraitOrClassItems( + $node->traits(), + $template, + $methodItemTemplate + ); + + $items .= $this->renderTraitOrClassItems( + $node->classes(), + $template, + $methodItemTemplate + ); + + return $items; + } + + private function renderTraitOrClassItems(array $items, Template $template, Template $methodItemTemplate): string + { + $buffer = ''; + + if (empty($items)) { + return $buffer; + } + + foreach ($items as $name => $item) { + $numMethods = 0; + $numTestedMethods = 0; + + foreach ($item['methods'] as $method) { + if ($method['executableLines'] > 0) { + $numMethods++; + + if ($method['executedLines'] === $method['executableLines']) { + $numTestedMethods++; + } + } + } + + if ($item['executableLines'] > 0) { + $numClasses = 1; + $numTestedClasses = $numTestedMethods === $numMethods ? 1 : 0; + $linesExecutedPercentAsString = Percentage::fromFractionAndTotal( + $item['executedLines'], + $item['executableLines'] + )->asString(); + $branchesExecutedPercentAsString = Percentage::fromFractionAndTotal( + $item['executedBranches'], + $item['executableBranches'] + )->asString(); + $pathsExecutedPercentAsString = Percentage::fromFractionAndTotal( + $item['executedPaths'], + $item['executablePaths'] + )->asString(); + } else { + $numClasses = 0; + $numTestedClasses = 0; + $linesExecutedPercentAsString = 'n/a'; + $branchesExecutedPercentAsString = 'n/a'; + $pathsExecutedPercentAsString = 'n/a'; + } + + $testedMethodsPercentage = Percentage::fromFractionAndTotal( + $numTestedMethods, + $numMethods + ); + + $testedClassesPercentage = Percentage::fromFractionAndTotal( + $numTestedMethods === $numMethods ? 1 : 0, + 1 + ); + + $buffer .= $this->renderItemTemplate( + $template, + [ + 'name' => $this->abbreviateClassName($name), + 'numClasses' => $numClasses, + 'numTestedClasses' => $numTestedClasses, + 'numMethods' => $numMethods, + 'numTestedMethods' => $numTestedMethods, + 'linesExecutedPercent' => Percentage::fromFractionAndTotal( + $item['executedLines'], + $item['executableLines'], + )->asFloat(), + 'linesExecutedPercentAsString' => $linesExecutedPercentAsString, + 'numExecutedLines' => $item['executedLines'], + 'numExecutableLines' => $item['executableLines'], + 'branchesExecutedPercent' => Percentage::fromFractionAndTotal( + $item['executedBranches'], + $item['executableBranches'], + )->asFloat(), + 'branchesExecutedPercentAsString' => $branchesExecutedPercentAsString, + 'numExecutedBranches' => $item['executedBranches'], + 'numExecutableBranches' => $item['executableBranches'], + 'pathsExecutedPercent' => Percentage::fromFractionAndTotal( + $item['executedPaths'], + $item['executablePaths'] + )->asFloat(), + 'pathsExecutedPercentAsString' => $pathsExecutedPercentAsString, + 'numExecutedPaths' => $item['executedPaths'], + 'numExecutablePaths' => $item['executablePaths'], + 'testedMethodsPercent' => $testedMethodsPercentage->asFloat(), + 'testedMethodsPercentAsString' => $testedMethodsPercentage->asString(), + 'testedClassesPercent' => $testedClassesPercentage->asFloat(), + 'testedClassesPercentAsString' => $testedClassesPercentage->asString(), + 'crap' => $item['crap'], + ] + ); + + foreach ($item['methods'] as $method) { + $buffer .= $this->renderFunctionOrMethodItem( + $methodItemTemplate, + $method, + ' ' + ); + } + } + + return $buffer; + } + + private function renderFunctionItems(array $functions, Template $template): string + { + if (empty($functions)) { + return ''; + } + + $buffer = ''; + + foreach ($functions as $function) { + $buffer .= $this->renderFunctionOrMethodItem( + $template, + $function + ); + } + + return $buffer; + } + + private function renderFunctionOrMethodItem(Template $template, array $item, string $indent = ''): string + { + $numMethods = 0; + $numTestedMethods = 0; + + if ($item['executableLines'] > 0) { + $numMethods = 1; + + if ($item['executedLines'] === $item['executableLines']) { + $numTestedMethods = 1; + } + } + + $executedLinesPercentage = Percentage::fromFractionAndTotal( + $item['executedLines'], + $item['executableLines'] + ); + + $executedBranchesPercentage = Percentage::fromFractionAndTotal( + $item['executedBranches'], + $item['executableBranches'] + ); + + $executedPathsPercentage = Percentage::fromFractionAndTotal( + $item['executedPaths'], + $item['executablePaths'] + ); + + $testedMethodsPercentage = Percentage::fromFractionAndTotal( + $numTestedMethods, + 1 + ); + + return $this->renderItemTemplate( + $template, + [ + 'name' => sprintf( + '%s%s', + $indent, + $item['startLine'], + htmlspecialchars($item['signature'], $this->htmlSpecialCharsFlags), + $item['functionName'] ?? $item['methodName'] + ), + 'numMethods' => $numMethods, + 'numTestedMethods' => $numTestedMethods, + 'linesExecutedPercent' => $executedLinesPercentage->asFloat(), + 'linesExecutedPercentAsString' => $executedLinesPercentage->asString(), + 'numExecutedLines' => $item['executedLines'], + 'numExecutableLines' => $item['executableLines'], + 'branchesExecutedPercent' => $executedBranchesPercentage->asFloat(), + 'branchesExecutedPercentAsString' => $executedBranchesPercentage->asString(), + 'numExecutedBranches' => $item['executedBranches'], + 'numExecutableBranches' => $item['executableBranches'], + 'pathsExecutedPercent' => $executedPathsPercentage->asFloat(), + 'pathsExecutedPercentAsString' => $executedPathsPercentage->asString(), + 'numExecutedPaths' => $item['executedPaths'], + 'numExecutablePaths' => $item['executablePaths'], + 'testedMethodsPercent' => $testedMethodsPercentage->asFloat(), + 'testedMethodsPercentAsString' => $testedMethodsPercentage->asString(), + 'crap' => $item['crap'], + ] + ); + } + + private function renderSourceWithLineCoverage(FileNode $node): string + { + $linesTemplate = new Template($this->templatePath . 'lines.html.dist', '{{', '}}'); + $singleLineTemplate = new Template($this->templatePath . 'line.html.dist', '{{', '}}'); + + $coverageData = $node->lineCoverageData(); + $testData = $node->testData(); + $codeLines = $this->loadFile($node->pathAsString()); + $lines = ''; + $i = 1; + + foreach ($codeLines as $line) { + $trClass = ''; + $popoverContent = ''; + $popoverTitle = ''; + + if (array_key_exists($i, $coverageData)) { + $numTests = ($coverageData[$i] ? count($coverageData[$i]) : 0); + + if ($coverageData[$i] === null) { + $trClass = 'warning'; + } elseif ($numTests === 0) { + $trClass = 'danger'; + } else { + if ($numTests > 1) { + $popoverTitle = $numTests . ' tests cover line ' . $i; + } else { + $popoverTitle = '1 test covers line ' . $i; + } + + $lineCss = 'covered-by-large-tests'; + $popoverContent = '
    '; + + foreach ($coverageData[$i] as $test) { + if ($lineCss === 'covered-by-large-tests' && $testData[$test]['size'] === 'medium') { + $lineCss = 'covered-by-medium-tests'; + } elseif ($testData[$test]['size'] === 'small') { + $lineCss = 'covered-by-small-tests'; + } + + $popoverContent .= $this->createPopoverContentForTest($test, $testData[$test]); + } + + $popoverContent .= '
'; + $trClass = $lineCss . ' popin'; + } + } + + $popover = ''; + + if (!empty($popoverTitle)) { + $popover = sprintf( + ' data-title="%s" data-content="%s" data-placement="top" data-html="true"', + $popoverTitle, + htmlspecialchars($popoverContent, $this->htmlSpecialCharsFlags) + ); + } + + $lines .= $this->renderLine($singleLineTemplate, $i, $line, $trClass, $popover); + + $i++; + } + + $linesTemplate->setVar(['lines' => $lines]); + + return $linesTemplate->render(); + } + + private function renderSourceWithBranchCoverage(FileNode $node): string + { + $linesTemplate = new Template($this->templatePath . 'lines.html.dist', '{{', '}}'); + $singleLineTemplate = new Template($this->templatePath . 'line.html.dist', '{{', '}}'); + + $functionCoverageData = $node->functionCoverageData(); + $testData = $node->testData(); + $codeLines = $this->loadFile($node->pathAsString()); + + $lineData = []; + + /** @var int $line */ + foreach (array_keys($codeLines) as $line) { + $lineData[$line + 1] = [ + 'includedInBranches' => 0, + 'includedInHitBranches' => 0, + 'tests' => [], + ]; + } + + foreach ($functionCoverageData as $method) { + foreach ($method['branches'] as $branch) { + foreach (range($branch['line_start'], $branch['line_end']) as $line) { + if (!isset($lineData[$line])) { // blank line at end of file is sometimes included here + continue; + } + + $lineData[$line]['includedInBranches']++; + + if ($branch['hit']) { + $lineData[$line]['includedInHitBranches']++; + $lineData[$line]['tests'] = array_unique(array_merge($lineData[$line]['tests'], $branch['hit'])); + } + } + } + } + + $lines = ''; + $i = 1; + + /** @var string $line */ + foreach ($codeLines as $line) { + $trClass = ''; + $popover = ''; + + if ($lineData[$i]['includedInBranches'] > 0) { + $lineCss = 'success'; + + if ($lineData[$i]['includedInHitBranches'] === 0) { + $lineCss = 'danger'; + } elseif ($lineData[$i]['includedInHitBranches'] !== $lineData[$i]['includedInBranches']) { + $lineCss = 'warning'; + } + + $popoverContent = '
    '; + + if (count($lineData[$i]['tests']) === 1) { + $popoverTitle = '1 test covers line ' . $i; + } else { + $popoverTitle = count($lineData[$i]['tests']) . ' tests cover line ' . $i; + } + $popoverTitle .= '. These are covering ' . $lineData[$i]['includedInHitBranches'] . ' out of the ' . $lineData[$i]['includedInBranches'] . ' code branches.'; + + foreach ($lineData[$i]['tests'] as $test) { + $popoverContent .= $this->createPopoverContentForTest($test, $testData[$test]); + } + + $popoverContent .= '
'; + $trClass = $lineCss . ' popin'; + + $popover = sprintf( + ' data-title="%s" data-content="%s" data-placement="top" data-html="true"', + $popoverTitle, + htmlspecialchars($popoverContent, $this->htmlSpecialCharsFlags) + ); + } + + $lines .= $this->renderLine($singleLineTemplate, $i, $line, $trClass, $popover); + + $i++; + } + + $linesTemplate->setVar(['lines' => $lines]); + + return $linesTemplate->render(); + } + + private function renderSourceWithPathCoverage(FileNode $node): string + { + $linesTemplate = new Template($this->templatePath . 'lines.html.dist', '{{', '}}'); + $singleLineTemplate = new Template($this->templatePath . 'line.html.dist', '{{', '}}'); + + $functionCoverageData = $node->functionCoverageData(); + $testData = $node->testData(); + $codeLines = $this->loadFile($node->pathAsString()); + + $lineData = []; + + /** @var int $line */ + foreach (array_keys($codeLines) as $line) { + $lineData[$line + 1] = [ + 'includedInPaths' => [], + 'includedInHitPaths' => [], + 'tests' => [], + ]; + } + + foreach ($functionCoverageData as $method) { + foreach ($method['paths'] as $pathId => $path) { + foreach ($path['path'] as $branchTaken) { + foreach (range($method['branches'][$branchTaken]['line_start'], $method['branches'][$branchTaken]['line_end']) as $line) { + if (!isset($lineData[$line])) { + continue; + } + $lineData[$line]['includedInPaths'][] = $pathId; + + if ($path['hit']) { + $lineData[$line]['includedInHitPaths'][] = $pathId; + $lineData[$line]['tests'] = array_unique(array_merge($lineData[$line]['tests'], $path['hit'])); + } + } + } + } + } + + $lines = ''; + $i = 1; + + /** @var string $line */ + foreach ($codeLines as $line) { + $trClass = ''; + $popover = ''; + $includedInPathsCount = count(array_unique($lineData[$i]['includedInPaths'])); + $includedInHitPathsCount = count(array_unique($lineData[$i]['includedInHitPaths'])); + + if ($includedInPathsCount > 0) { + $lineCss = 'success'; + + if ($includedInHitPathsCount === 0) { + $lineCss = 'danger'; + } elseif ($includedInHitPathsCount !== $includedInPathsCount) { + $lineCss = 'warning'; + } + + $popoverContent = '
    '; + + if (count($lineData[$i]['tests']) === 1) { + $popoverTitle = '1 test covers line ' . $i; + } else { + $popoverTitle = count($lineData[$i]['tests']) . ' tests cover line ' . $i; + } + $popoverTitle .= '. These are covering ' . $includedInHitPathsCount . ' out of the ' . $includedInPathsCount . ' code paths.'; + + foreach ($lineData[$i]['tests'] as $test) { + $popoverContent .= $this->createPopoverContentForTest($test, $testData[$test]); + } + + $popoverContent .= '
'; + $trClass = $lineCss . ' popin'; + + $popover = sprintf( + ' data-title="%s" data-content="%s" data-placement="top" data-html="true"', + $popoverTitle, + htmlspecialchars($popoverContent, $this->htmlSpecialCharsFlags) + ); + } + + $lines .= $this->renderLine($singleLineTemplate, $i, $line, $trClass, $popover); + + $i++; + } + + $linesTemplate->setVar(['lines' => $lines]); + + return $linesTemplate->render(); + } + + private function renderBranchStructure(FileNode $node): string + { + $branchesTemplate = new Template($this->templatePath . 'branches.html.dist', '{{', '}}'); + + $coverageData = $node->functionCoverageData(); + $testData = $node->testData(); + $codeLines = $this->loadFile($node->pathAsString()); + $branches = ''; + + ksort($coverageData); + + foreach ($coverageData as $methodName => $methodData) { + if (!$methodData['branches']) { + continue; + } + + $branchStructure = ''; + + foreach ($methodData['branches'] as $branch) { + $branchStructure .= $this->renderBranchLines($branch, $codeLines, $testData); + } + + if ($branchStructure !== '') { // don't show empty branches + $branches .= '
' . $this->abbreviateMethodName($methodName) . '
' . "\n"; + $branches .= $branchStructure; + } + } + + $branchesTemplate->setVar(['branches' => $branches]); + + return $branchesTemplate->render(); + } + + private function renderBranchLines(array $branch, array $codeLines, array $testData): string + { + $linesTemplate = new Template($this->templatePath . 'lines.html.dist', '{{', '}}'); + $singleLineTemplate = new Template($this->templatePath . 'line.html.dist', '{{', '}}'); + + $lines = ''; + + $branchLines = range($branch['line_start'], $branch['line_end']); + sort($branchLines); // sometimes end_line < start_line + + /** @var int $line */ + foreach ($branchLines as $line) { + if (!isset($codeLines[$line])) { // blank line at end of file is sometimes included here + continue; + } + + $popoverContent = ''; + $popoverTitle = ''; + + $numTests = count($branch['hit']); + + if ($numTests === 0) { + $trClass = 'danger'; + } else { + $lineCss = 'covered-by-large-tests'; + $popoverContent = '
    '; + + if ($numTests > 1) { + $popoverTitle = $numTests . ' tests cover this branch'; + } else { + $popoverTitle = '1 test covers this branch'; + } + + foreach ($branch['hit'] as $test) { + if ($lineCss === 'covered-by-large-tests' && $testData[$test]['size'] === 'medium') { + $lineCss = 'covered-by-medium-tests'; + } elseif ($testData[$test]['size'] === 'small') { + $lineCss = 'covered-by-small-tests'; + } + + $popoverContent .= $this->createPopoverContentForTest($test, $testData[$test]); + } + $trClass = $lineCss . ' popin'; + } + + $popover = ''; + + if (!empty($popoverTitle)) { + $popover = sprintf( + ' data-title="%s" data-content="%s" data-placement="top" data-html="true"', + $popoverTitle, + htmlspecialchars($popoverContent, $this->htmlSpecialCharsFlags) + ); + } + + $lines .= $this->renderLine($singleLineTemplate, $line, $codeLines[$line - 1], $trClass, $popover); + } + + if ($lines === '') { + return ''; + } + + $linesTemplate->setVar(['lines' => $lines]); + + return $linesTemplate->render(); + } + + private function renderPathStructure(FileNode $node): string + { + $pathsTemplate = new Template($this->templatePath . 'paths.html.dist', '{{', '}}'); + + $coverageData = $node->functionCoverageData(); + $testData = $node->testData(); + $codeLines = $this->loadFile($node->pathAsString()); + $paths = ''; + + ksort($coverageData); + + foreach ($coverageData as $methodName => $methodData) { + if (!$methodData['paths']) { + continue; + } + + $pathStructure = ''; + + if (count($methodData['paths']) > 100) { + $pathStructure .= '

    ' . count($methodData['paths']) . ' is too many paths to sensibly render, consider refactoring your code to bring this number down.

    '; + + continue; + } + + foreach ($methodData['paths'] as $path) { + $pathStructure .= $this->renderPathLines($path, $methodData['branches'], $codeLines, $testData); + } + + if ($pathStructure !== '') { + $paths .= '
    ' . $this->abbreviateMethodName($methodName) . '
    ' . "\n"; + $paths .= $pathStructure; + } + } + + $pathsTemplate->setVar(['paths' => $paths]); + + return $pathsTemplate->render(); + } + + private function renderPathLines(array $path, array $branches, array $codeLines, array $testData): string + { + $linesTemplate = new Template($this->templatePath . 'lines.html.dist', '{{', '}}'); + $singleLineTemplate = new Template($this->templatePath . 'line.html.dist', '{{', '}}'); + + $lines = ''; + $first = true; + + foreach ($path['path'] as $branchId) { + if ($first) { + $first = false; + } else { + $lines .= '  ' . "\n"; + } + + $branchLines = range($branches[$branchId]['line_start'], $branches[$branchId]['line_end']); + sort($branchLines); // sometimes end_line < start_line + + /** @var int $line */ + foreach ($branchLines as $line) { + if (!isset($codeLines[$line])) { // blank line at end of file is sometimes included here + continue; + } + + $popoverContent = ''; + $popoverTitle = ''; + + $numTests = count($path['hit']); + + if ($numTests === 0) { + $trClass = 'danger'; + } else { + $lineCss = 'covered-by-large-tests'; + $popoverContent = '
      '; + + if ($numTests > 1) { + $popoverTitle = $numTests . ' tests cover this path'; + } else { + $popoverTitle = '1 test covers this path'; + } + + foreach ($path['hit'] as $test) { + if ($lineCss === 'covered-by-large-tests' && $testData[$test]['size'] === 'medium') { + $lineCss = 'covered-by-medium-tests'; + } elseif ($testData[$test]['size'] === 'small') { + $lineCss = 'covered-by-small-tests'; + } + + $popoverContent .= $this->createPopoverContentForTest($test, $testData[$test]); + } + + $trClass = $lineCss . ' popin'; + } + + $popover = ''; + + if (!empty($popoverTitle)) { + $popover = sprintf( + ' data-title="%s" data-content="%s" data-placement="top" data-html="true"', + $popoverTitle, + htmlspecialchars($popoverContent, $this->htmlSpecialCharsFlags) + ); + } + + $lines .= $this->renderLine($singleLineTemplate, $line, $codeLines[$line - 1], $trClass, $popover); + } + } + + if ($lines === '') { + return ''; + } + + $linesTemplate->setVar(['lines' => $lines]); + + return $linesTemplate->render(); + } + + private function renderLine(Template $template, int $lineNumber, string $lineContent, string $class, string $popover): string + { + $template->setVar( + [ + 'lineNumber' => $lineNumber, + 'lineContent' => $lineContent, + 'class' => $class, + 'popover' => $popover, + ] + ); + + return $template->render(); + } + + private function loadFile(string $file): array + { + if (isset(self::$formattedSourceCache[$file])) { + return self::$formattedSourceCache[$file]; + } + + $buffer = file_get_contents($file); + $tokens = token_get_all($buffer); + $result = ['']; + $i = 0; + $stringFlag = false; + $fileEndsWithNewLine = substr($buffer, -1) === "\n"; + + unset($buffer); + + foreach ($tokens as $j => $token) { + if (is_string($token)) { + if ($token === '"' && $tokens[$j - 1] !== '\\') { + $result[$i] .= sprintf( + '%s', + htmlspecialchars($token, $this->htmlSpecialCharsFlags) + ); + + $stringFlag = !$stringFlag; + } else { + $result[$i] .= sprintf( + '%s', + htmlspecialchars($token, $this->htmlSpecialCharsFlags) + ); + } + + continue; + } + + [$token, $value] = $token; + + $value = str_replace( + ["\t", ' '], + ['    ', ' '], + htmlspecialchars($value, $this->htmlSpecialCharsFlags) + ); + + if ($value === "\n") { + $result[++$i] = ''; + } else { + $lines = explode("\n", $value); + + foreach ($lines as $jj => $line) { + $line = trim($line); + + if ($line !== '') { + if ($stringFlag) { + $colour = 'string'; + } else { + $colour = 'default'; + + if ($this->isInlineHtml($token)) { + $colour = 'html'; + } elseif ($this->isComment($token)) { + $colour = 'comment'; + } elseif ($this->isKeyword($token)) { + $colour = 'keyword'; + } + } + + $result[$i] .= sprintf( + '%s', + $colour, + $line + ); + } + + if (isset($lines[$jj + 1])) { + $result[++$i] = ''; + } + } + } + } + + if ($fileEndsWithNewLine) { + unset($result[count($result) - 1]); + } + + self::$formattedSourceCache[$file] = $result; + + return $result; + } + + private function abbreviateClassName(string $className): string + { + $tmp = explode('\\', $className); + + if (count($tmp) > 1) { + $className = sprintf( + '%s', + $className, + array_pop($tmp) + ); + } + + return $className; + } + + private function abbreviateMethodName(string $methodName): string + { + $parts = explode('->', $methodName); + + if (count($parts) === 2) { + return $this->abbreviateClassName($parts[0]) . '->' . $parts[1]; + } + + return $methodName; + } + + private function createPopoverContentForTest(string $test, array $testData): string + { + $testCSS = ''; + + if ($testData['fromTestcase']) { + switch ($testData['status']) { + case BaseTestRunner::STATUS_PASSED: + switch ($testData['size']) { + case 'small': + $testCSS = ' class="covered-by-small-tests"'; + + break; + + case 'medium': + $testCSS = ' class="covered-by-medium-tests"'; + + break; + + default: + $testCSS = ' class="covered-by-large-tests"'; + + break; + } + + break; + + case BaseTestRunner::STATUS_SKIPPED: + case BaseTestRunner::STATUS_INCOMPLETE: + case BaseTestRunner::STATUS_RISKY: + case BaseTestRunner::STATUS_WARNING: + $testCSS = ' class="warning"'; + + break; + + case BaseTestRunner::STATUS_FAILURE: + case BaseTestRunner::STATUS_ERROR: + $testCSS = ' class="danger"'; + + break; + } + } + + return sprintf( + '%s', + $testCSS, + htmlspecialchars($test, $this->htmlSpecialCharsFlags) + ); + } + + private function isComment(int $token): bool + { + return $token === T_COMMENT || $token === T_DOC_COMMENT; + } + + private function isInlineHtml(int $token): bool + { + return $token === T_INLINE_HTML; + } + + private function isKeyword(int $token): bool + { + return isset(self::keywordTokens()[$token]); + } + + /** + * @psalm-return array + */ + private static function keywordTokens(): array + { + if (self::$keywordTokens !== []) { + return self::$keywordTokens; + } + + self::$keywordTokens = [ + T_ABSTRACT => true, + T_ARRAY => true, + T_AS => true, + T_BREAK => true, + T_CALLABLE => true, + T_CASE => true, + T_CATCH => true, + T_CLASS => true, + T_CLONE => true, + T_CONST => true, + T_CONTINUE => true, + T_DECLARE => true, + T_DEFAULT => true, + T_DO => true, + T_ECHO => true, + T_ELSE => true, + T_ELSEIF => true, + T_EMPTY => true, + T_ENDDECLARE => true, + T_ENDFOR => true, + T_ENDFOREACH => true, + T_ENDIF => true, + T_ENDSWITCH => true, + T_ENDWHILE => true, + T_EVAL => true, + T_EXIT => true, + T_EXTENDS => true, + T_FINAL => true, + T_FINALLY => true, + T_FOR => true, + T_FOREACH => true, + T_FUNCTION => true, + T_GLOBAL => true, + T_GOTO => true, + T_HALT_COMPILER => true, + T_IF => true, + T_IMPLEMENTS => true, + T_INCLUDE => true, + T_INCLUDE_ONCE => true, + T_INSTANCEOF => true, + T_INSTEADOF => true, + T_INTERFACE => true, + T_ISSET => true, + T_LIST => true, + T_NAMESPACE => true, + T_NEW => true, + T_PRINT => true, + T_PRIVATE => true, + T_PROTECTED => true, + T_PUBLIC => true, + T_REQUIRE => true, + T_REQUIRE_ONCE => true, + T_RETURN => true, + T_STATIC => true, + T_SWITCH => true, + T_THROW => true, + T_TRAIT => true, + T_TRY => true, + T_UNSET => true, + T_USE => true, + T_VAR => true, + T_WHILE => true, + T_YIELD => true, + T_YIELD_FROM => true, + ]; + + if (defined('T_FN')) { + self::$keywordTokens[constant('T_FN')] = true; + } + + if (defined('T_MATCH')) { + self::$keywordTokens[constant('T_MATCH')] = true; + } + + if (defined('T_ENUM')) { + self::$keywordTokens[constant('T_ENUM')] = true; + } + + if (defined('T_READONLY')) { + self::$keywordTokens[constant('T_READONLY')] = true; + } + + return self::$keywordTokens; + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/branches.html.dist b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/branches.html.dist new file mode 100644 index 00000000..54770262 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/branches.html.dist @@ -0,0 +1,9 @@ +
      +

      Branches

      +

      + Below are the source code lines that represent each code branch as identified by Xdebug. Please note a branch is not + necessarily coterminous with a line, a line may contain multiple branches and therefore show up more than once. + Please also be aware that some branches may be implicit rather than explicit, e.g. an if statement + always has an else as part of its logical flow even if you didn't write one. +

      +{{branches}} diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/coverage_bar.html.dist b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/coverage_bar.html.dist new file mode 100644 index 00000000..7fcf6f49 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/coverage_bar.html.dist @@ -0,0 +1,5 @@ +
      +
      + {{percent}}% covered ({{level}}) +
      +
      diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/coverage_bar_branch.html.dist b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/coverage_bar_branch.html.dist new file mode 100644 index 00000000..7fcf6f49 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/coverage_bar_branch.html.dist @@ -0,0 +1,5 @@ +
      +
      + {{percent}}% covered ({{level}}) +
      +
      diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/css/bootstrap.min.css b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/css/bootstrap.min.css new file mode 100644 index 00000000..83a71b1f --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/css/bootstrap.min.css @@ -0,0 +1,7 @@ +/*! + * Bootstrap v4.6.2 (https://getbootstrap.com/) + * Copyright 2011-2022 The Bootstrap Authors + * Copyright 2011-2022 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([class]){color:inherit;text-decoration:none}a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit;text-align:-webkit-match-parent}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:.875em;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:.875em;color:#6c757d}.blockquote-footer::before{content:"\2014\00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#6c757d}code{font-size:87.5%;color:#e83e8c;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}.row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-ms-flex-order:-1;order:-1}.order-last{-ms-flex-order:13;order:13}.order-0{-ms-flex-order:0;order:0}.order-1{-ms-flex-order:1;order:1}.order-2{-ms-flex-order:2;order:2}.order-3{-ms-flex-order:3;order:3}.order-4{-ms-flex-order:4;order:4}.order-5{-ms-flex-order:5;order:5}.order-6{-ms-flex-order:6;order:6}.order-7{-ms-flex-order:7;order:7}.order-8{-ms-flex-order:8;order:8}.order-9{-ms-flex-order:9;order:9}.order-10{-ms-flex-order:10;order:10}.order-11{-ms-flex-order:11;order:11}.order-12{-ms-flex-order:12;order:12}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media (min-width:576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-sm-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-sm-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-sm-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-sm-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-sm-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-sm-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-ms-flex-order:-1;order:-1}.order-sm-last{-ms-flex-order:13;order:13}.order-sm-0{-ms-flex-order:0;order:0}.order-sm-1{-ms-flex-order:1;order:1}.order-sm-2{-ms-flex-order:2;order:2}.order-sm-3{-ms-flex-order:3;order:3}.order-sm-4{-ms-flex-order:4;order:4}.order-sm-5{-ms-flex-order:5;order:5}.order-sm-6{-ms-flex-order:6;order:6}.order-sm-7{-ms-flex-order:7;order:7}.order-sm-8{-ms-flex-order:8;order:8}.order-sm-9{-ms-flex-order:9;order:9}.order-sm-10{-ms-flex-order:10;order:10}.order-sm-11{-ms-flex-order:11;order:11}.order-sm-12{-ms-flex-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-md-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-md-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-md-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-md-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-md-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-md-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-md-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-ms-flex-order:-1;order:-1}.order-md-last{-ms-flex-order:13;order:13}.order-md-0{-ms-flex-order:0;order:0}.order-md-1{-ms-flex-order:1;order:1}.order-md-2{-ms-flex-order:2;order:2}.order-md-3{-ms-flex-order:3;order:3}.order-md-4{-ms-flex-order:4;order:4}.order-md-5{-ms-flex-order:5;order:5}.order-md-6{-ms-flex-order:6;order:6}.order-md-7{-ms-flex-order:7;order:7}.order-md-8{-ms-flex-order:8;order:8}.order-md-9{-ms-flex-order:9;order:9}.order-md-10{-ms-flex-order:10;order:10}.order-md-11{-ms-flex-order:11;order:11}.order-md-12{-ms-flex-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-lg-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-lg-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-lg-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-lg-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-lg-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-lg-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-ms-flex-order:-1;order:-1}.order-lg-last{-ms-flex-order:13;order:13}.order-lg-0{-ms-flex-order:0;order:0}.order-lg-1{-ms-flex-order:1;order:1}.order-lg-2{-ms-flex-order:2;order:2}.order-lg-3{-ms-flex-order:3;order:3}.order-lg-4{-ms-flex-order:4;order:4}.order-lg-5{-ms-flex-order:5;order:5}.order-lg-6{-ms-flex-order:6;order:6}.order-lg-7{-ms-flex-order:7;order:7}.order-lg-8{-ms-flex-order:8;order:8}.order-lg-9{-ms-flex-order:9;order:9}.order-lg-10{-ms-flex-order:10;order:10}.order-lg-11{-ms-flex-order:11;order:11}.order-lg-12{-ms-flex-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-xl-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-xl-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-xl-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-xl-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-xl-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-xl-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-ms-flex-order:-1;order:-1}.order-xl-last{-ms-flex-order:13;order:13}.order-xl-0{-ms-flex-order:0;order:0}.order-xl-1{-ms-flex-order:1;order:1}.order-xl-2{-ms-flex-order:2;order:2}.order-xl-3{-ms-flex-order:3;order:3}.order-xl-4{-ms-flex-order:4;order:4}.order-xl-5{-ms-flex-order:5;order:5}.order-xl-6{-ms-flex-order:6;order:6}.order-xl-7{-ms-flex-order:7;order:7}.order-xl-8{-ms-flex-order:8;order:8}.order-xl-9{-ms-flex-order:9;order:9}.order-xl-10{-ms-flex-order:10;order:10}.order-xl-11{-ms-flex-order:11;order:11}.order-xl-12{-ms-flex-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}.table{width:100%;margin-bottom:1rem;color:#212529}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{color:#212529;background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#b8daff}.table-primary tbody+tbody,.table-primary td,.table-primary th,.table-primary thead th{border-color:#7abaff}.table-hover .table-primary:hover{background-color:#9fcdff}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#9fcdff}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#d6d8db}.table-secondary tbody+tbody,.table-secondary td,.table-secondary th,.table-secondary thead th{border-color:#b3b7bb}.table-hover .table-secondary:hover{background-color:#c8cbcf}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#c8cbcf}.table-success,.table-success>td,.table-success>th{background-color:#c3e6cb}.table-success tbody+tbody,.table-success td,.table-success th,.table-success thead th{border-color:#8fd19e}.table-hover .table-success:hover{background-color:#b1dfbb}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b1dfbb}.table-info,.table-info>td,.table-info>th{background-color:#bee5eb}.table-info tbody+tbody,.table-info td,.table-info th,.table-info thead th{border-color:#86cfda}.table-hover .table-info:hover{background-color:#abdde5}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abdde5}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#ffdf7e}.table-hover .table-warning:hover{background-color:#ffe8a1}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#f5c6cb}.table-danger tbody+tbody,.table-danger td,.table-danger th,.table-danger thead th{border-color:#ed969e}.table-hover .table-danger:hover{background-color:#f1b0b7}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1b0b7}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-light tbody+tbody,.table-light td,.table-light th,.table-light thead th{border-color:#fbfcfc}.table-hover .table-light:hover{background-color:#ececf6}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#95999c}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#343a40;border-color:#454d55}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#dee2e6}.table-dark{color:#fff;background-color:#343a40}.table-dark td,.table-dark th,.table-dark thead th{border-color:#454d55}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{color:#fff;background-color:rgba(255,255,255,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-control::-webkit-input-placeholder{color:#6c757d;opacity:1}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}input[type=date].form-control,input[type=datetime-local].form-control,input[type=month].form-control,input[type=time].form-control{-webkit-appearance:none;-moz-appearance:none;appearance:none}select.form-control:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;font-size:1rem;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.form-control-lg{height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}select.form-control[multiple],select.form-control[size]{height:auto}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#28a745}.valid-tooltip{position:absolute;top:100%;left:0;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(40,167,69,.9);border-radius:.25rem}.form-row>.col>.valid-tooltip,.form-row>[class*=col-]>.valid-tooltip{left:5px}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#28a745;padding-right:calc(1.5em + .75rem)!important;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.was-validated select.form-control:valid,select.form-control.is-valid{padding-right:3rem!important;background-position:right 1.5rem center}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-valid,.was-validated .custom-select:valid{border-color:#28a745;padding-right:calc(.75em + 2.3125rem)!important;background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right .75rem center/8px 10px no-repeat,#fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem) no-repeat}.custom-select.is-valid:focus,.was-validated .custom-select:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#28a745}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#28a745}.custom-control-input.is-valid~.custom-control-label::before,.was-validated .custom-control-input:valid~.custom-control-label::before{border-color:#28a745}.custom-control-input.is-valid:checked~.custom-control-label::before,.was-validated .custom-control-input:valid:checked~.custom-control-label::before{border-color:#34ce57;background-color:#34ce57}.custom-control-input.is-valid:focus~.custom-control-label::before,.was-validated .custom-control-input:valid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label::before{border-color:#28a745}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#28a745}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;left:0;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.form-row>.col>.invalid-tooltip,.form-row>[class*=col-]>.invalid-tooltip{left:5px}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem)!important;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.was-validated select.form-control:invalid,select.form-control.is-invalid{padding-right:3rem!important;background-position:right 1.5rem center}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-invalid,.was-validated .custom-select:invalid{border-color:#dc3545;padding-right:calc(.75em + 2.3125rem)!important;background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right .75rem center/8px 10px no-repeat,#fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e") center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem) no-repeat}.custom-select.is-invalid:focus,.was-validated .custom-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#dc3545}.custom-control-input.is-invalid~.custom-control-label::before,.was-validated .custom-control-input:invalid~.custom-control-label::before{border-color:#dc3545}.custom-control-input.is-invalid:checked~.custom-control-label::before,.was-validated .custom-control-input:invalid:checked~.custom-control-label::before{border-color:#e4606d;background-color:#e4606d}.custom-control-input.is-invalid:focus~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label::before{border-color:#dc3545}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#dc3545}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-inline{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;-ms-flex-negative:0;flex-shrink:0;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;color:#212529;text-align:center;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529;text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.btn.disabled,.btn:disabled{opacity:.65}.btn:not(:disabled):not(.disabled){cursor:pointer}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#0069d9;border-color:#0062cc;box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5a6268;border-color:#545b62}.btn-secondary.focus,.btn-secondary:focus{color:#fff;background-color:#5a6268;border-color:#545b62;box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#545b62;border-color:#4e555b}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{color:#fff;background-color:#218838;border-color:#1e7e34}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#218838;border-color:#1e7e34;box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#1e7e34;border-color:#1c7430}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{color:#fff;background-color:#138496;border-color:#117a8b}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#138496;border-color:#117a8b;box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#117a8b;border-color:#10707f}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#212529;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{color:#212529;background-color:#e0a800;border-color:#d39e00;box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#212529;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c82333;border-color:#bd2130;box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light.focus,.btn-light:focus{color:#212529;background-color:#e2e6ea;border-color:#dae0e5;box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{color:#fff;background-color:#23272b;border-color:#1d2124;box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-outline-primary{color:#007bff;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-success{color:#28a745;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-info{color:#17a2b8;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{color:#343a40;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#007bff;text-decoration:none}.btn-link:hover{color:#0056b3;text-decoration:underline}.btn-link.focus,.btn-link:focus{text-decoration:underline}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.width{width:0;height:auto;transition:width .35s ease}@media (prefers-reduced-motion:reduce){.collapsing.width{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu-left{right:auto;left:0}.dropdown-menu-right{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-left{right:auto;left:0}.dropdown-menu-sm-right{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-left{right:auto;left:0}.dropdown-menu-md-right{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-left{right:auto;left:0}.dropdown-menu-lg-right{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-left{right:auto;left:0}.dropdown-menu-xl-right{right:0;left:auto}}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#007bff}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#212529}.btn-group,.btn-group-vertical{position:relative;display:-ms-inline-flexbox;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;-ms-flex:1 1 auto;flex:1 1 auto}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:1}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:1}.btn-toolbar{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:start;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropright .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropleft .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{-ms-flex-direction:column;flex-direction:column;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:center;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:stretch;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control,.input-group>.form-control-plaintext{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;width:1%;min-width:0;margin-bottom:0}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control,.input-group>.form-control-plaintext+.custom-file,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::after{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group:not(.has-validation)>.custom-file:not(:last-child) .custom-file-label,.input-group:not(.has-validation)>.custom-file:not(:last-child) .custom-file-label::after,.input-group:not(.has-validation)>.custom-select:not(:last-child),.input-group:not(.has-validation)>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.custom-file:nth-last-child(n+3) .custom-file-label,.input-group.has-validation>.custom-file:nth-last-child(n+3) .custom-file-label::after,.input-group.has-validation>.custom-select:nth-last-child(n+3),.input-group.has-validation>.form-control:nth-last-child(n+3){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-append,.input-group-prepend{display:-ms-flexbox;display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn:focus,.input-group-prepend .btn:focus{z-index:3}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.custom-select,.input-group-lg>.form-control:not(textarea){height:calc(1.5em + 1rem + 2px)}.input-group-lg>.custom-select,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.input-group-sm>.custom-select,.input-group-sm>.form-control:not(textarea){height:calc(1.5em + .5rem + 2px)}.input-group-sm>.custom-select,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group.has-validation>.input-group-append:nth-last-child(n+3)>.btn,.input-group.has-validation>.input-group-append:nth-last-child(n+3)>.input-group-text,.input-group:not(.has-validation)>.input-group-append:not(:last-child)>.btn,.input-group:not(.has-validation)>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;z-index:1;display:block;min-height:1.5rem;padding-left:1.5rem;-webkit-print-color-adjust:exact;color-adjust:exact;print-color-adjust:exact}.custom-control-inline{display:-ms-inline-flexbox;display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;left:0;z-index:-1;width:1rem;height:1.25rem;opacity:0}.custom-control-input:checked~.custom-control-label::before{color:#fff;border-color:#007bff;background-color:#007bff}.custom-control-input:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-control-input:focus:not(:checked)~.custom-control-label::before{border-color:#80bdff}.custom-control-input:not(:disabled):active~.custom-control-label::before{color:#fff;background-color:#b3d7ff;border-color:#b3d7ff}.custom-control-input:disabled~.custom-control-label,.custom-control-input[disabled]~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label::before,.custom-control-input[disabled]~.custom-control-label::before{background-color:#e9ecef}.custom-control-label{position:relative;margin-bottom:0;vertical-align:top}.custom-control-label::before{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;pointer-events:none;content:"";background-color:#fff;border:1px solid #adb5bd}.custom-control-label::after{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:"";background:50%/50% 50% no-repeat}.custom-checkbox .custom-control-label::before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::before{border-color:#007bff;background-color:#007bff}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label::before{left:-2.25rem;width:1.75rem;pointer-events:all;border-radius:.5rem}.custom-switch .custom-control-label::after{top:calc(.25rem + 2px);left:calc(-2.25rem + 2px);width:calc(1rem - 4px);height:calc(1rem - 4px);background-color:#adb5bd;border-radius:.5rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-switch .custom-control-label::after{transition:none}}.custom-switch .custom-control-input:checked~.custom-control-label::after{background-color:#fff;-webkit-transform:translateX(.75rem);transform:translateX(.75rem)}.custom-switch .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-select{display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem 1.75rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;vertical-align:middle;background:#fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right .75rem center/8px 10px no-repeat;border:1px solid #ced4da;border-radius:.25rem;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-select:focus{border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{display:none}.custom-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.custom-select-sm{height:calc(1.5em + .5rem + 2px);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.custom-select-lg{height:calc(1.5em + 1rem + 2px);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.custom-file{position:relative;display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(1.5em + .75rem + 2px);margin:0;overflow:hidden;opacity:0}.custom-file-input:focus~.custom-file-label{border-color:#80bdff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-file-input:disabled~.custom-file-label,.custom-file-input[disabled]~.custom-file-label{background-color:#e9ecef}.custom-file-input:lang(en)~.custom-file-label::after{content:"Browse"}.custom-file-input~.custom-file-label[data-browse]::after{content:attr(data-browse)}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;overflow:hidden;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:calc(1.5em + .75rem);padding:.375rem .75rem;line-height:1.5;color:#495057;content:"Browse";background-color:#e9ecef;border-left:inherit;border-radius:0 .25rem .25rem 0}.custom-range{width:100%;height:1.4rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-range:focus{outline:0}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#007bff;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#b3d7ff}.custom-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#007bff;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{-moz-transition:none;transition:none}}.custom-range::-moz-range-thumb:active{background-color:#b3d7ff}.custom-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:.2rem;margin-left:.2rem;background-color:#007bff;border:0;border-radius:1rem;-ms-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-ms-thumb{-ms-transition:none;transition:none}}.custom-range::-ms-thumb:active{background-color:#b3d7ff}.custom-range::-ms-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:.5rem}.custom-range::-ms-fill-lower{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px;background-color:#dee2e6;border-radius:1rem}.custom-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#adb5bd}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#adb5bd}.custom-control-label::before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-control-label::before,.custom-file-label,.custom-select{transition:none}}.nav{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;background-color:transparent;border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{isolation:isolate;border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{background:0 0;border:0;border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#007bff}.nav-fill .nav-item,.nav-fill>.nav-link{-ms-flex:1 1 auto;flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;padding:.5rem 1rem}.navbar .container,.navbar .container-fluid,.navbar .container-lg,.navbar .container-md,.navbar .container-sm,.navbar .container-xl{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{-ms-flex-preferred-size:100%;flex-basis:100%;-ms-flex-positive:1;flex-grow:1;-ms-flex-align:center;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:50%/100% 100% no-repeat}.navbar-nav-scroll{max-height:75vh;overflow-y:auto}@media (max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{padding-right:0;padding-left:0}}@media (min-width:576px){.navbar-expand-sm{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-sm .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{padding-right:0;padding-left:0}}@media (min-width:768px){.navbar-expand-md{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-md .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-lg .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-xl .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.5);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a{color:rgba(0,0,0,.9)}.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.5)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{-ms-flex:1 1 auto;flex:1 1 auto;min-height:1px;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem;border-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom,.card-img-top{-ms-flex-negative:0;flex-shrink:0;width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-deck .card{margin-bottom:15px}@media (min-width:576px){.card-deck{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{-ms-flex:1 0 0%;flex:1 0 0%;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group>.card{margin-bottom:15px}@media (min-width:576px){.card-group{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap}.card-group>.card{-ms-flex:1 0 0%;flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:576px){.card-columns{-webkit-column-count:3;-moz-column-count:3;column-count:3;-webkit-column-gap:1.25rem;-moz-column-gap:1.25rem;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion{overflow-anchor:none}.accordion>.card{overflow:hidden}.accordion>.card:not(:last-of-type){border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion>.card:not(:first-of-type){border-top-left-radius:0;border-top-right-radius:0}.accordion>.card>.card-header{border-radius:0;margin-bottom:-1px}.breadcrumb{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:.25rem}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:-ms-flexbox;display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#007bff;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{z-index:2;color:#0056b3;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link{z-index:3;color:#fff;background-color:#007bff;border-color:#007bff}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.badge{transition:none}}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#007bff}a.badge-primary:focus,a.badge-primary:hover{color:#fff;background-color:#0062cc}a.badge-primary.focus,a.badge-primary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.badge-secondary{color:#fff;background-color:#6c757d}a.badge-secondary:focus,a.badge-secondary:hover{color:#fff;background-color:#545b62}a.badge-secondary.focus,a.badge-secondary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.badge-success{color:#fff;background-color:#28a745}a.badge-success:focus,a.badge-success:hover{color:#fff;background-color:#1e7e34}a.badge-success.focus,a.badge-success:focus{outline:0;box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.badge-info{color:#fff;background-color:#17a2b8}a.badge-info:focus,a.badge-info:hover{color:#fff;background-color:#117a8b}a.badge-info.focus,a.badge-info:focus{outline:0;box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.badge-warning{color:#212529;background-color:#ffc107}a.badge-warning:focus,a.badge-warning:hover{color:#212529;background-color:#d39e00}a.badge-warning.focus,a.badge-warning:focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.badge-danger{color:#fff;background-color:#dc3545}a.badge-danger:focus,a.badge-danger:hover{color:#fff;background-color:#bd2130}a.badge-danger.focus,a.badge-danger:focus{outline:0;box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.badge-light{color:#212529;background-color:#f8f9fa}a.badge-light:focus,a.badge-light:hover{color:#212529;background-color:#dae0e5}a.badge-light.focus,a.badge-light:focus{outline:0;box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.badge-dark{color:#fff;background-color:#343a40}a.badge-dark:focus,a.badge-dark:hover{color:#fff;background-color:#1d2124}a.badge-dark.focus,a.badge-dark:focus{outline:0;box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:.3rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;z-index:2;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#004085;background-color:#cce5ff;border-color:#b8daff}.alert-primary hr{border-top-color:#9fcdff}.alert-primary .alert-link{color:#002752}.alert-secondary{color:#383d41;background-color:#e2e3e5;border-color:#d6d8db}.alert-secondary hr{border-top-color:#c8cbcf}.alert-secondary .alert-link{color:#202326}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-success hr{border-top-color:#b1dfbb}.alert-success .alert-link{color:#0b2e13}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-info hr{border-top-color:#abdde5}.alert-info .alert-link{color:#062c33}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#533f03}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.alert-danger hr{border-top-color:#f1b0b7}.alert-danger .alert-link{color:#491217}.alert-light{color:#818182;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@-webkit-keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:-ms-flexbox;display:flex;height:1rem;overflow:hidden;line-height:0;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#007bff;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:1s linear infinite progress-bar-stripes;animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.media{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start}.media-body{-ms-flex:1;flex:1}.list-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#007bff;border-color:#007bff}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#004085;background-color:#b8daff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#004085;background-color:#9fcdff}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#004085;border-color:#004085}.list-group-item-secondary{color:#383d41;background-color:#d6d8db}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#383d41;background-color:#c8cbcf}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#383d41;border-color:#383d41}.list-group-item-success{color:#155724;background-color:#c3e6cb}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#155724;background-color:#b1dfbb}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#155724;border-color:#155724}.list-group-item-info{color:#0c5460;background-color:#bee5eb}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#0c5460;background-color:#abdde5}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#0c5460;border-color:#0c5460}.list-group-item-warning{color:#856404;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#856404;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#856404;border-color:#856404}.list-group-item-danger{color:#721c24;background-color:#f5c6cb}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#721c24;background-color:#f1b0b7}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#721c24;border-color:#721c24}.list-group-item-light{color:#818182;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#818182;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#818182;border-color:#818182}.list-group-item-dark{color:#1b1e21;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#1b1e21;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:hover{color:#000;text-decoration:none}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{padding:0;background-color:transparent;border:0}a.close.disabled{pointer-events:none}.toast{-ms-flex-preferred-size:350px;flex-basis:350px;max-width:350px;font-size:.875rem;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .25rem .75rem rgba(0,0,0,.1);opacity:0;border-radius:.25rem}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.25rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out;-webkit-transform:translate(0,-50px);transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{-webkit-transform:none;transform:none}.modal.modal-static .modal-dialog{-webkit-transform:scale(1.02);transform:scale(1.02)}.modal-dialog-scrollable{display:-ms-flexbox;display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-footer,.modal-dialog-scrollable .modal-header{-ms-flex-negative:0;flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered::before{display:block;height:calc(100vh - 1rem);height:-webkit-min-content;height:-moz-min-content;height:min-content;content:""}.modal-dialog-centered.modal-dialog-scrollable{-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable::before{content:none}.modal-content{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:justify;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .close{padding:1rem 1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem}.modal-footer{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:end;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered::before{height:calc(100vh - 3.5rem);height:-webkit-min-content;height:-moz-min-content;height:min-content}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow::before,.bs-tooltip-top .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow::before,.bs-tooltip-right .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow::before,.bs-tooltip-bottom .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow::before,.bs-tooltip-left .arrow::before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow::after,.popover .arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top]>.arrow,.bs-popover-top>.arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=top]>.arrow::before,.bs-popover-top>.arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=top]>.arrow::after,.bs-popover-top>.arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right]>.arrow,.bs-popover-right>.arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=right]>.arrow::before,.bs-popover-right>.arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=right]>.arrow::after,.bs-popover-right>.arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom]>.arrow,.bs-popover-bottom>.arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=bottom]>.arrow::before,.bs-popover-bottom>.arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=bottom]>.arrow::after,.bs-popover-bottom>.arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left]>.arrow,.bs-popover-left>.arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=left]>.arrow::before,.bs-popover-left>.arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=left]>.arrow::after,.bs-popover-left>.arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{-ms-touch-action:pan-y;touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out;transition:transform .6s ease-in-out,-webkit-transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-right,.carousel-item-next:not(.carousel-item-left){-webkit-transform:translateX(100%);transform:translateX(100%)}.active.carousel-item-left,.carousel-item-prev:not(.carousel-item-right){-webkit-transform:translateX(-100%);transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;-webkit-transform:none;transform:none}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:50%/100% 100% no-repeat}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:15;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{box-sizing:content-box;-ms-flex:0 1 auto;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}@-webkit-keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;border:.25em solid currentcolor;border-right-color:transparent;border-radius:50%;-webkit-animation:.75s linear infinite spinner-border;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1;-webkit-transform:none;transform:none}}@keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1;-webkit-transform:none;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;background-color:currentcolor;border-radius:50%;opacity:0;-webkit-animation:.75s linear infinite spinner-grow;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{-webkit-animation-duration:1.5s;animation-duration:1.5s}}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#007bff!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#0062cc!important}.bg-secondary{background-color:#6c757d!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#545b62!important}.bg-success{background-color:#28a745!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34!important}.bg-info{background-color:#17a2b8!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#117a8b!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#dc3545!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #dee2e6!important}.border-top{border-top:1px solid #dee2e6!important}.border-right{border-right:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-left{border-left:1px solid #dee2e6!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#007bff!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#28a745!important}.border-info{border-color:#17a2b8!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded-sm{border-radius:.2rem!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-right{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-lg{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-0{border-radius:0!important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:-ms-flexbox!important;display:flex!important}.d-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:-ms-flexbox!important;display:flex!important}.d-sm-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:-ms-flexbox!important;display:flex!important}.d-md-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:-ms-flexbox!important;display:flex!important}.d-lg-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:-ms-flexbox!important;display:flex!important}.d-xl-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:-ms-flexbox!important;display:flex!important}.d-print-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.857143%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-center{-ms-flex-align:center!important;align-items:center!important}.align-items-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}@media (min-width:576px){.flex-sm-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-sm-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-sm-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-sm-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-sm-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-sm-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-sm-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-sm-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-sm-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-sm-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-sm-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-sm-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-sm-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-sm-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-sm-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-sm-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-sm-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-sm-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-sm-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-sm-center{-ms-flex-align:center!important;align-items:center!important}.align-items-sm-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-sm-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-sm-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-sm-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-sm-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-sm-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-sm-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-sm-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-sm-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-sm-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-sm-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-sm-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-sm-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-sm-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:768px){.flex-md-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-md-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-md-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-md-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-md-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-md-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-md-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-md-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-md-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-md-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-md-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-md-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-md-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-md-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-md-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-md-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-md-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-md-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-md-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-md-center{-ms-flex-align:center!important;align-items:center!important}.align-items-md-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-md-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-md-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-md-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-md-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-md-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-md-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-md-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-md-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-md-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-md-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-md-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-md-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-md-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-lg-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-lg-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-lg-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-lg-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-lg-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-lg-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-lg-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-lg-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-lg-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-lg-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-lg-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-lg-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-lg-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-lg-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-lg-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-lg-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-lg-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-lg-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-lg-center{-ms-flex-align:center!important;align-items:center!important}.align-items-lg-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-lg-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-lg-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-lg-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-lg-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-lg-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-lg-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-lg-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-lg-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-lg-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-lg-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-lg-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-lg-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-lg-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-xl-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-xl-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-xl-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-xl-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-xl-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-xl-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-xl-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-xl-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-xl-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-xl-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-xl-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-xl-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-xl-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-xl-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-xl-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-xl-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-xl-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-xl-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-xl-center{-ms-flex-align:center!important;align-items:center!important}.align-items-xl-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-xl-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-xl-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-xl-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-xl-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-xl-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-xl-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-xl-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-xl-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-xl-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-xl-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-xl-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-xl-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-xl-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;-ms-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;-ms-user-select:none!important;user-select:none!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports ((position:-webkit-sticky) or (position:sticky)){.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.min-vw-100{min-width:100vw!important}.min-vh-100{min-height:100vh!important}.vw-100{width:100vw!important}.vh-100{height:100vh!important}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;pointer-events:auto;content:"";background-color:rgba(0,0,0,0)}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace!important}.text-justify{text-align:justify!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-lighter{font-weight:lighter!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-weight-bolder{font-weight:bolder!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#007bff!important}a.text-primary:focus,a.text-primary:hover{color:#0056b3!important}.text-secondary{color:#6c757d!important}a.text-secondary:focus,a.text-secondary:hover{color:#494f54!important}.text-success{color:#28a745!important}a.text-success:focus,a.text-success:hover{color:#19692c!important}.text-info{color:#17a2b8!important}a.text-info:focus,a.text-info:hover{color:#0f6674!important}.text-warning{color:#ffc107!important}a.text-warning:focus,a.text-warning:hover{color:#ba8b00!important}.text-danger{color:#dc3545!important}a.text-danger:focus,a.text-danger:hover{color:#a71d2a!important}.text-light{color:#f8f9fa!important}a.text-light:focus,a.text-light:hover{color:#cbd3da!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#121416!important}.text-body{color:#212529!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-decoration-none{text-decoration:none!important}.text-break{word-break:break-word!important;word-wrap:break-word!important}.text-reset{color:inherit!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #adb5bd;page-break-inside:avoid}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dee2e6!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#dee2e6}.table .thead-dark th{color:inherit;border-color:#dee2e6}} +/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/css/custom.css b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/css/custom.css new file mode 100644 index 00000000..e69de29b diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/css/nv.d3.min.css b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/css/nv.d3.min.css new file mode 100644 index 00000000..7a6f7fe9 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/css/nv.d3.min.css @@ -0,0 +1 @@ +.nvd3 .nv-axis{pointer-events:none;opacity:1}.nvd3 .nv-axis path{fill:none;stroke:#000;stroke-opacity:.75;shape-rendering:crispEdges}.nvd3 .nv-axis path.domain{stroke-opacity:.75}.nvd3 .nv-axis.nv-x path.domain{stroke-opacity:0}.nvd3 .nv-axis line{fill:none;stroke:#e5e5e5;shape-rendering:crispEdges}.nvd3 .nv-axis .zero line,.nvd3 .nv-axis line.zero{stroke-opacity:.75}.nvd3 .nv-axis .nv-axisMaxMin text{font-weight:700}.nvd3 .x .nv-axis .nv-axisMaxMin text,.nvd3 .x2 .nv-axis .nv-axisMaxMin text,.nvd3 .x3 .nv-axis .nv-axisMaxMin text{text-anchor:middle}.nvd3 .nv-axis.nv-disabled{opacity:0}.nvd3 .nv-bars rect{fill-opacity:.75;transition:fill-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear}.nvd3 .nv-bars rect.hover{fill-opacity:1}.nvd3 .nv-bars .hover rect{fill:#add8e6}.nvd3 .nv-bars text{fill:rgba(0,0,0,0)}.nvd3 .nv-bars .hover text{fill:rgba(0,0,0,1)}.nvd3 .nv-multibar .nv-groups rect,.nvd3 .nv-multibarHorizontal .nv-groups rect,.nvd3 .nv-discretebar .nv-groups rect{stroke-opacity:0;transition:fill-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear}.nvd3 .nv-multibar .nv-groups rect:hover,.nvd3 .nv-multibarHorizontal .nv-groups rect:hover,.nvd3 .nv-candlestickBar .nv-ticks rect:hover,.nvd3 .nv-discretebar .nv-groups rect:hover{fill-opacity:1}.nvd3 .nv-discretebar .nv-groups text,.nvd3 .nv-multibarHorizontal .nv-groups text{font-weight:700;fill:rgba(0,0,0,1);stroke:rgba(0,0,0,0)}.nvd3 .nv-boxplot circle{fill-opacity:.5}.nvd3 .nv-boxplot circle:hover{fill-opacity:1}.nvd3 .nv-boxplot rect:hover{fill-opacity:1}.nvd3 line.nv-boxplot-median{stroke:#000}.nv-boxplot-tick:hover{stroke-width:2.5px}.nvd3.nv-bullet{font:10px sans-serif}.nvd3.nv-bullet .nv-measure{fill-opacity:.8}.nvd3.nv-bullet .nv-measure:hover{fill-opacity:1}.nvd3.nv-bullet .nv-marker{stroke:#000;stroke-width:2px}.nvd3.nv-bullet .nv-markerTriangle{stroke:#000;fill:#fff;stroke-width:1.5px}.nvd3.nv-bullet .nv-tick line{stroke:#666;stroke-width:.5px}.nvd3.nv-bullet .nv-range.nv-s0{fill:#eee}.nvd3.nv-bullet .nv-range.nv-s1{fill:#ddd}.nvd3.nv-bullet .nv-range.nv-s2{fill:#ccc}.nvd3.nv-bullet .nv-title{font-size:14px;font-weight:700}.nvd3.nv-bullet .nv-subtitle{fill:#999}.nvd3.nv-bullet .nv-range{fill:#bababa;fill-opacity:.4}.nvd3.nv-bullet .nv-range:hover{fill-opacity:.7}.nvd3.nv-candlestickBar .nv-ticks .nv-tick{stroke-width:1px}.nvd3.nv-candlestickBar .nv-ticks .nv-tick.hover{stroke-width:2px}.nvd3.nv-candlestickBar .nv-ticks .nv-tick.positive rect{stroke:#2ca02c;fill:#2ca02c}.nvd3.nv-candlestickBar .nv-ticks .nv-tick.negative rect{stroke:#d62728;fill:#d62728}.with-transitions .nv-candlestickBar .nv-ticks .nv-tick{transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-candlestickBar .nv-ticks line{stroke:#333}.nvd3 .nv-legend .nv-disabled rect{}.nvd3 .nv-check-box .nv-box{fill-opacity:0;stroke-width:2}.nvd3 .nv-check-box .nv-check{fill-opacity:0;stroke-width:4}.nvd3 .nv-series.nv-disabled .nv-check-box .nv-check{fill-opacity:0;stroke-opacity:0}.nvd3 .nv-controlsWrap .nv-legend .nv-check-box .nv-check{opacity:0}.nvd3.nv-linePlusBar .nv-bar rect{fill-opacity:.75}.nvd3.nv-linePlusBar .nv-bar rect:hover{fill-opacity:1}.nvd3 .nv-groups path.nv-line{fill:none}.nvd3 .nv-groups path.nv-area{stroke:none}.nvd3.nv-line .nvd3.nv-scatter .nv-groups .nv-point{fill-opacity:0;stroke-opacity:0}.nvd3.nv-scatter.nv-single-point .nv-groups .nv-point{fill-opacity:.5!important;stroke-opacity:.5!important}.with-transitions .nvd3 .nv-groups .nv-point{transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-scatter .nv-groups .nv-point.hover,.nvd3 .nv-groups .nv-point.hover{stroke-width:7px;fill-opacity:.95!important;stroke-opacity:.95!important}.nvd3 .nv-point-paths path{stroke:#aaa;stroke-opacity:0;fill:#eee;fill-opacity:0}.nvd3 .nv-indexLine{cursor:ew-resize}svg.nvd3-svg{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-ms-user-select:none;-moz-user-select:none;user-select:none;display:block;width:100%;height:100%}.nvtooltip.with-3d-shadow,.with-3d-shadow .nvtooltip{-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.nvd3 text{font:400 12px Arial}.nvd3 .title{font:700 14px Arial}.nvd3 .nv-background{fill:#fff;fill-opacity:0}.nvd3.nv-noData{font-size:18px;font-weight:700}.nv-brush .extent{fill-opacity:.125;shape-rendering:crispEdges}.nv-brush .resize path{fill:#eee;stroke:#666}.nvd3 .nv-legend .nv-series{cursor:pointer}.nvd3 .nv-legend .nv-disabled circle{fill-opacity:0}.nvd3 .nv-brush .extent{fill-opacity:0!important}.nvd3 .nv-brushBackground rect{stroke:#000;stroke-width:.4;fill:#fff;fill-opacity:.7}.nvd3.nv-ohlcBar .nv-ticks .nv-tick{stroke-width:1px}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.hover{stroke-width:2px}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.positive{stroke:#2ca02c}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.negative{stroke:#d62728}.nvd3 .background path{fill:none;stroke:#EEE;stroke-opacity:.4;shape-rendering:crispEdges}.nvd3 .foreground path{fill:none;stroke-opacity:.7}.nvd3 .nv-parallelCoordinates-brush .extent{fill:#fff;fill-opacity:.6;stroke:gray;shape-rendering:crispEdges}.nvd3 .nv-parallelCoordinates .hover{fill-opacity:1;stroke-width:3px}.nvd3 .missingValuesline line{fill:none;stroke:#000;stroke-width:1;stroke-opacity:1;stroke-dasharray:5,5}.nvd3.nv-pie path{stroke-opacity:0;transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-pie .nv-pie-title{font-size:24px;fill:rgba(19,196,249,.59)}.nvd3.nv-pie .nv-slice text{stroke:#000;stroke-width:0}.nvd3.nv-pie path{stroke:#fff;stroke-width:1px;stroke-opacity:1}.nvd3.nv-pie .hover path{fill-opacity:.7}.nvd3.nv-pie .nv-label{pointer-events:none}.nvd3.nv-pie .nv-label rect{fill-opacity:0;stroke-opacity:0}.nvd3 .nv-groups .nv-point.hover{stroke-width:20px;stroke-opacity:.5}.nvd3 .nv-scatter .nv-point.hover{fill-opacity:1}.nv-noninteractive{pointer-events:none}.nv-distx,.nv-disty{pointer-events:none}.nvd3.nv-sparkline path{fill:none}.nvd3.nv-sparklineplus g.nv-hoverValue{pointer-events:none}.nvd3.nv-sparklineplus .nv-hoverValue line{stroke:#333;stroke-width:1.5px}.nvd3.nv-sparklineplus,.nvd3.nv-sparklineplus g{pointer-events:all}.nvd3 .nv-hoverArea{fill-opacity:0;stroke-opacity:0}.nvd3.nv-sparklineplus .nv-xValue,.nvd3.nv-sparklineplus .nv-yValue{stroke-width:0;font-size:.9em;font-weight:400}.nvd3.nv-sparklineplus .nv-yValue{stroke:#f66}.nvd3.nv-sparklineplus .nv-maxValue{stroke:#2ca02c;fill:#2ca02c}.nvd3.nv-sparklineplus .nv-minValue{stroke:#d62728;fill:#d62728}.nvd3.nv-sparklineplus .nv-currentValue{font-weight:700;font-size:1.1em}.nvd3.nv-stackedarea path.nv-area{fill-opacity:.7;stroke-opacity:0;transition:fill-opacity 250ms linear,stroke-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear,stroke-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-stackedarea path.nv-area.hover{fill-opacity:.9}.nvd3.nv-stackedarea .nv-groups .nv-point{stroke-opacity:0;fill-opacity:0}.nvtooltip{position:absolute;background-color:rgba(255,255,255,1);color:rgba(0,0,0,1);padding:1px;border:1px solid rgba(0,0,0,.2);z-index:10000;display:block;font-family:Arial;font-size:13px;text-align:left;pointer-events:none;white-space:nowrap;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.nvtooltip{background:rgba(255,255,255,.8);border:1px solid rgba(0,0,0,.5);border-radius:4px}.nvtooltip.with-transitions,.with-transitions .nvtooltip{transition:opacity 50ms linear;-moz-transition:opacity 50ms linear;-webkit-transition:opacity 50ms linear;transition-delay:200ms;-moz-transition-delay:200ms;-webkit-transition-delay:200ms}.nvtooltip.x-nvtooltip,.nvtooltip.y-nvtooltip{padding:8px}.nvtooltip h3{margin:0;padding:4px 14px;line-height:18px;font-weight:400;background-color:rgba(247,247,247,.75);color:rgba(0,0,0,1);text-align:center;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.nvtooltip p{margin:0;padding:5px 14px;text-align:center}.nvtooltip span{display:inline-block;margin:2px 0}.nvtooltip table{margin:6px;border-spacing:0}.nvtooltip table td{padding:2px 9px 2px 0;vertical-align:middle}.nvtooltip table td.key{font-weight:400}.nvtooltip table td.value{text-align:right;font-weight:700}.nvtooltip table tr.highlight td{padding:1px 9px 1px 0;border-bottom-style:solid;border-bottom-width:1px;border-top-style:solid;border-top-width:1px}.nvtooltip table td.legend-color-guide div{width:8px;height:8px;vertical-align:middle}.nvtooltip table td.legend-color-guide div{width:12px;height:12px;border:1px solid #999}.nvtooltip .footer{padding:3px;text-align:center}.nvtooltip-pending-removal{pointer-events:none;display:none}.nvd3 .nv-interactiveGuideLine{pointer-events:none}.nvd3 line.nv-guideline{stroke:#ccc} \ No newline at end of file diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/css/octicons.css b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/css/octicons.css new file mode 100644 index 00000000..31d97867 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/css/octicons.css @@ -0,0 +1,5 @@ +.octicon { + display: inline-block; + vertical-align: text-top; + fill: currentColor; +} diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/css/style.css b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/css/style.css new file mode 100644 index 00000000..2edd6097 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/css/style.css @@ -0,0 +1,158 @@ +body { + font-family: sans-serif; + font-size: 1em; + font-kerning: normal; + font-variant-ligatures: common-ligatures; + text-rendering: optimizeLegibility; + padding-top: 10px; +} + +.popover { + max-width: none; +} + +.octicon { + margin-right:.25em; + vertical-align: baseline; + width: 0.75em; +} + +.table-bordered>thead>tr>td { + border-bottom-width: 1px; +} + +.table tbody>tr>td, .table thead>tr>td { + padding-top: 3px; + padding-bottom: 3px; +} + +.table-condensed tbody>tr>td { + padding-top: 0; + padding-bottom: 0; +} + +.table .progress { + margin-bottom: inherit; +} + +.table-borderless th, .table-borderless td { + border: 0 !important; +} + +.table tbody tr.covered-by-large-tests, li.covered-by-large-tests, tr.success, td.success, li.success, span.success { + background-color: #dff0d8; +} + +.table tbody tr.covered-by-medium-tests, li.covered-by-medium-tests { + background-color: #c3e3b5; +} + +.table tbody tr.covered-by-small-tests, li.covered-by-small-tests { + background-color: #99cb84; +} + +.table tbody tr.danger, .table tbody td.danger, li.danger, span.danger { + background-color: #f2dede; +} + +.table tbody tr.warning, .table tbody td.warning, li.warning, span.warning { + background-color: #fcf8e3; +} + +.table tbody td.info { + background-color: #d9edf7; +} + +td.big { + vertical-align: middle; + width: 117px; +} + +td.small { +} + +td.codeLine { + font-family: "Source Code Pro", "SFMono-Regular", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + white-space: pre-wrap; +} + +td span.comment { + color: #888a85; +} + +td span.default { + color: #2e3436; +} + +td span.html { + color: #888a85; +} + +td span.keyword { + color: #2e3436; + font-weight: bold; +} + +pre span.string { + color: #2e3436; +} + +span.success, span.warning, span.danger { + margin-right: 2px; + padding-left: 10px; + padding-right: 10px; + text-align: center; +} + +#toplink { + position: fixed; + left: 5px; + bottom: 5px; + outline: 0; +} + +svg text { + font-family: "Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif; + font-size: 11px; + color: #666; + fill: #666; +} + +.scrollbox { + height:245px; + overflow-x:scroll; + overflow-y:scroll; +} + +table + .structure-heading { + border-top: 1px solid lightgrey; + padding-top: 0.5em; +} + +.legend { + font-weight: bold; + margin-right: 2px; + padding-left: 10px; + padding-right: 10px; + text-align: center; +} + +.covered-by-small-tests { + background-color: #99cb84; +} + +.covered-by-medium-tests { + background-color: #c3e3b5; +} + +.covered-by-large-tests { + background-color: #dff0d8; +} + +.not-covered { + background-color: #f2dede; +} + +.not-coverable { + background-color: #fcf8e3; +} diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/dashboard.html.dist b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/dashboard.html.dist new file mode 100644 index 00000000..60e66d5b --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/dashboard.html.dist @@ -0,0 +1,281 @@ + + + + + Dashboard for {{full_path}} + + + + + + + +
      +
      +
      +
      + +
      +
      +
      +
      +
      +
      +
      +

      Classes

      +
      +
      +
      +
      +

      Coverage Distribution

      +
      + +
      +
      +
      +

      Complexity

      +
      + +
      +
      +
      +
      +
      +

      Insufficient Coverage

      +
      + + + + + + + + +{{insufficient_coverage_classes}} + +
      ClassCoverage
      +
      +
      +
      +

      Project Risks

      +
      + + + + + + + + +{{project_risks_classes}} + +
      ClassCRAP
      +
      +
      +
      +
      +
      +

      Methods

      +
      +
      +
      +
      +

      Coverage Distribution

      +
      + +
      +
      +
      +

      Complexity

      +
      + +
      +
      +
      +
      +
      +

      Insufficient Coverage

      +
      + + + + + + + + +{{insufficient_coverage_methods}} + +
      MethodCoverage
      +
      +
      +
      +

      Project Risks

      +
      + + + + + + + + +{{project_risks_methods}} + +
      MethodCRAP
      +
      +
      +
      + +
      + + + + + + diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/dashboard_branch.html.dist b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/dashboard_branch.html.dist new file mode 100644 index 00000000..60e66d5b --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/dashboard_branch.html.dist @@ -0,0 +1,281 @@ + + + + + Dashboard for {{full_path}} + + + + + + + +
      +
      +
      +
      + +
      +
      +
      +
      +
      +
      +
      +

      Classes

      +
      +
      +
      +
      +

      Coverage Distribution

      +
      + +
      +
      +
      +

      Complexity

      +
      + +
      +
      +
      +
      +
      +

      Insufficient Coverage

      +
      + + + + + + + + +{{insufficient_coverage_classes}} + +
      ClassCoverage
      +
      +
      +
      +

      Project Risks

      +
      + + + + + + + + +{{project_risks_classes}} + +
      ClassCRAP
      +
      +
      +
      +
      +
      +

      Methods

      +
      +
      +
      +
      +

      Coverage Distribution

      +
      + +
      +
      +
      +

      Complexity

      +
      + +
      +
      +
      +
      +
      +

      Insufficient Coverage

      +
      + + + + + + + + +{{insufficient_coverage_methods}} + +
      MethodCoverage
      +
      +
      +
      +

      Project Risks

      +
      + + + + + + + + +{{project_risks_methods}} + +
      MethodCRAP
      +
      +
      +
      + +
      + + + + + + diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/directory.html.dist b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/directory.html.dist new file mode 100644 index 00000000..f769d2ca --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/directory.html.dist @@ -0,0 +1,60 @@ + + + + + Code Coverage for {{full_path}} + + + + + + + +
      +
      +
      +
      + +
      +
      +
      +
      +
      +
      + + + + + + + + + + + + + + +{{items}} + +
       
      Code Coverage
       
      Lines
      Functions and Methods
      Classes and Traits
      +
      +
      +
      +

      Legend

      +

      + Low: 0% to {{low_upper_bound}}% + Medium: {{low_upper_bound}}% to {{high_lower_bound}}% + High: {{high_lower_bound}}% to 100% +

      +

      + Generated by php-code-coverage {{version}} using {{runtime}}{{generator}} at {{date}}. +

      +
      +
      + + diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/directory_branch.html.dist b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/directory_branch.html.dist new file mode 100644 index 00000000..a40c2e12 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/directory_branch.html.dist @@ -0,0 +1,62 @@ + + + + + Code Coverage for {{full_path}} + + + + + + + +
      +
      +
      +
      + +
      +
      +
      +
      +
      +
      + + + + + + + + + + + + + + + + +{{items}} + +
       
      Code Coverage
       
      Lines
      Branches
      Paths
      Functions and Methods
      Classes and Traits
      +
      +
      +
      +

      Legend

      +

      + Low: 0% to {{low_upper_bound}}% + Medium: {{low_upper_bound}}% to {{high_lower_bound}}% + High: {{high_lower_bound}}% to 100% +

      +

      + Generated by php-code-coverage {{version}} using {{runtime}}{{generator}} at {{date}}. +

      +
      +
      + + diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/directory_item.html.dist b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/directory_item.html.dist new file mode 100644 index 00000000..f6941a43 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/directory_item.html.dist @@ -0,0 +1,13 @@ + + {{icon}}{{name}} + {{lines_bar}} +
      {{lines_executed_percent}}
      +
      {{lines_number}}
      + {{methods_bar}} +
      {{methods_tested_percent}}
      +
      {{methods_number}}
      + {{classes_bar}} +
      {{classes_tested_percent}}
      +
      {{classes_number}}
      + + diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/directory_item_branch.html.dist b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/directory_item_branch.html.dist new file mode 100644 index 00000000..532a436c --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/directory_item_branch.html.dist @@ -0,0 +1,19 @@ + + {{icon}}{{name}} + {{lines_bar}} +
      {{lines_executed_percent}}
      +
      {{lines_number}}
      + {{branches_bar}} +
      {{branches_executed_percent}}
      +
      {{branches_number}}
      + {{paths_bar}} +
      {{paths_executed_percent}}
      +
      {{paths_number}}
      + {{methods_bar}} +
      {{methods_tested_percent}}
      +
      {{methods_number}}
      + {{classes_bar}} +
      {{classes_tested_percent}}
      +
      {{classes_number}}
      + + diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/file.html.dist b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/file.html.dist new file mode 100644 index 00000000..a022f5c2 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/file.html.dist @@ -0,0 +1,65 @@ + + + + + Code Coverage for {{full_path}} + + + + + + + +
      +
      +
      +
      + +
      +
      +
      +
      +
      +
      + + + + + + + + + + + + + + +{{items}} + +
       
      Code Coverage
       
      Lines
      Functions and Methods
      Classes and Traits
      +
      +{{lines}} +{{structure}} + +
      + + + + + + diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/file_branch.html.dist b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/file_branch.html.dist new file mode 100644 index 00000000..f48ebf12 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/file_branch.html.dist @@ -0,0 +1,67 @@ + + + + + Code Coverage for {{full_path}} + + + + + + + +
      +
      +
      +
      + +
      +
      +
      +
      +
      +
      + + + + + + + + + + + + + + + + +{{items}} + +
       
      Code Coverage
       
      Lines
      Branches
      Paths
      Functions and Methods
      Classes and Traits
      +
      +{{lines}} +{{structure}} + +
      + + + + + + diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/file_item.html.dist b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/file_item.html.dist new file mode 100644 index 00000000..b1c0fca4 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/file_item.html.dist @@ -0,0 +1,14 @@ + + {{name}} + {{lines_bar}} +
      {{lines_executed_percent}}
      +
      {{lines_number}}
      + {{methods_bar}} +
      {{methods_tested_percent}}
      +
      {{methods_number}}
      + {{crap}} + {{classes_bar}} +
      {{classes_tested_percent}}
      +
      {{classes_number}}
      + + diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/file_item_branch.html.dist b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/file_item_branch.html.dist new file mode 100644 index 00000000..50502517 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/file_item_branch.html.dist @@ -0,0 +1,20 @@ + + {{name}} + {{lines_bar}} +
      {{lines_executed_percent}}
      +
      {{lines_number}}
      + {{branches_bar}} +
      {{branches_executed_percent}}
      +
      {{branches_number}}
      + {{paths_bar}} +
      {{paths_executed_percent}}
      +
      {{paths_number}}
      + {{methods_bar}} +
      {{methods_tested_percent}}
      +
      {{methods_number}}
      + {{crap}} + {{classes_bar}} +
      {{classes_tested_percent}}
      +
      {{classes_number}}
      + + diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/icons/file-code.svg b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/icons/file-code.svg new file mode 100644 index 00000000..5b4b1995 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/icons/file-code.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/icons/file-directory.svg b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/icons/file-directory.svg new file mode 100644 index 00000000..4bf1f1ca --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/icons/file-directory.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/bootstrap.min.js b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/bootstrap.min.js new file mode 100644 index 00000000..97206dcd --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v4.6.2 (https://getbootstrap.com/) + * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jquery"),require("popper.js")):"function"==typeof define&&define.amd?define(["exports","jquery","popper.js"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap={},t.jQuery,t.Popper)}(this,(function(t,e,n){"use strict";function i(t){return t&&"object"==typeof t&&"default"in t?t:{default:t}}var o=i(e),a=i(n);function s(t,e){for(var n=0;n=4)throw new Error("Bootstrap's JavaScript requires at least jQuery v1.9.1 but less than v4.0.0")}};d.jQueryDetection(),o.default.fn.emulateTransitionEnd=function(t){var e=this,n=!1;return o.default(this).one(d.TRANSITION_END,(function(){n=!0})),setTimeout((function(){n||d.triggerTransitionEnd(e)}),t),this},o.default.event.special[d.TRANSITION_END]={bindType:f,delegateType:f,handle:function(t){if(o.default(t.target).is(this))return t.handleObj.handler.apply(this,arguments)}};var c="bs.alert",h=o.default.fn.alert,g=function(){function t(t){this._element=t}var e=t.prototype;return e.close=function(t){var e=this._element;t&&(e=this._getRootElement(t)),this._triggerCloseEvent(e).isDefaultPrevented()||this._removeElement(e)},e.dispose=function(){o.default.removeData(this._element,c),this._element=null},e._getRootElement=function(t){var e=d.getSelectorFromElement(t),n=!1;return e&&(n=document.querySelector(e)),n||(n=o.default(t).closest(".alert")[0]),n},e._triggerCloseEvent=function(t){var e=o.default.Event("close.bs.alert");return o.default(t).trigger(e),e},e._removeElement=function(t){var e=this;if(o.default(t).removeClass("show"),o.default(t).hasClass("fade")){var n=d.getTransitionDurationFromElement(t);o.default(t).one(d.TRANSITION_END,(function(n){return e._destroyElement(t,n)})).emulateTransitionEnd(n)}else this._destroyElement(t)},e._destroyElement=function(t){o.default(t).detach().trigger("closed.bs.alert").remove()},t._jQueryInterface=function(e){return this.each((function(){var n=o.default(this),i=n.data(c);i||(i=new t(this),n.data(c,i)),"close"===e&&i[e](this)}))},t._handleDismiss=function(t){return function(e){e&&e.preventDefault(),t.close(this)}},l(t,null,[{key:"VERSION",get:function(){return"4.6.2"}}]),t}();o.default(document).on("click.bs.alert.data-api",'[data-dismiss="alert"]',g._handleDismiss(new g)),o.default.fn.alert=g._jQueryInterface,o.default.fn.alert.Constructor=g,o.default.fn.alert.noConflict=function(){return o.default.fn.alert=h,g._jQueryInterface};var m="bs.button",p=o.default.fn.button,_="active",v='[data-toggle^="button"]',y='input:not([type="hidden"])',b=".btn",E=function(){function t(t){this._element=t,this.shouldAvoidTriggerChange=!1}var e=t.prototype;return e.toggle=function(){var t=!0,e=!0,n=o.default(this._element).closest('[data-toggle="buttons"]')[0];if(n){var i=this._element.querySelector(y);if(i){if("radio"===i.type)if(i.checked&&this._element.classList.contains(_))t=!1;else{var a=n.querySelector(".active");a&&o.default(a).removeClass(_)}t&&("checkbox"!==i.type&&"radio"!==i.type||(i.checked=!this._element.classList.contains(_)),this.shouldAvoidTriggerChange||o.default(i).trigger("change")),i.focus(),e=!1}}this._element.hasAttribute("disabled")||this._element.classList.contains("disabled")||(e&&this._element.setAttribute("aria-pressed",!this._element.classList.contains(_)),t&&o.default(this._element).toggleClass(_))},e.dispose=function(){o.default.removeData(this._element,m),this._element=null},t._jQueryInterface=function(e,n){return this.each((function(){var i=o.default(this),a=i.data(m);a||(a=new t(this),i.data(m,a)),a.shouldAvoidTriggerChange=n,"toggle"===e&&a[e]()}))},l(t,null,[{key:"VERSION",get:function(){return"4.6.2"}}]),t}();o.default(document).on("click.bs.button.data-api",v,(function(t){var e=t.target,n=e;if(o.default(e).hasClass("btn")||(e=o.default(e).closest(b)[0]),!e||e.hasAttribute("disabled")||e.classList.contains("disabled"))t.preventDefault();else{var i=e.querySelector(y);if(i&&(i.hasAttribute("disabled")||i.classList.contains("disabled")))return void t.preventDefault();"INPUT"!==n.tagName&&"LABEL"===e.tagName||E._jQueryInterface.call(o.default(e),"toggle","INPUT"===n.tagName)}})).on("focus.bs.button.data-api blur.bs.button.data-api",v,(function(t){var e=o.default(t.target).closest(b)[0];o.default(e).toggleClass("focus",/^focus(in)?$/.test(t.type))})),o.default(window).on("load.bs.button.data-api",(function(){for(var t=[].slice.call(document.querySelectorAll('[data-toggle="buttons"] .btn')),e=0,n=t.length;e0,this._pointerEvent=Boolean(window.PointerEvent||window.MSPointerEvent),this._addEventListeners()}var e=t.prototype;return e.next=function(){this._isSliding||this._slide(N)},e.nextWhenVisible=function(){var t=o.default(this._element);!document.hidden&&t.is(":visible")&&"hidden"!==t.css("visibility")&&this.next()},e.prev=function(){this._isSliding||this._slide(D)},e.pause=function(t){t||(this._isPaused=!0),this._element.querySelector(".carousel-item-next, .carousel-item-prev")&&(d.triggerTransitionEnd(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null},e.cycle=function(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config.interval&&!this._isPaused&&(this._updateInterval(),this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))},e.to=function(t){var e=this;this._activeElement=this._element.querySelector(I);var n=this._getItemIndex(this._activeElement);if(!(t>this._items.length-1||t<0))if(this._isSliding)o.default(this._element).one(A,(function(){return e.to(t)}));else{if(n===t)return this.pause(),void this.cycle();var i=t>n?N:D;this._slide(i,this._items[t])}},e.dispose=function(){o.default(this._element).off(".bs.carousel"),o.default.removeData(this._element,w),this._items=null,this._config=null,this._element=null,this._interval=null,this._isPaused=null,this._isSliding=null,this._activeElement=null,this._indicatorsElement=null},e._getConfig=function(t){return t=r({},k,t),d.typeCheckConfig(T,t,O),t},e._handleSwipe=function(){var t=Math.abs(this.touchDeltaX);if(!(t<=40)){var e=t/this.touchDeltaX;this.touchDeltaX=0,e>0&&this.prev(),e<0&&this.next()}},e._addEventListeners=function(){var t=this;this._config.keyboard&&o.default(this._element).on("keydown.bs.carousel",(function(e){return t._keydown(e)})),"hover"===this._config.pause&&o.default(this._element).on("mouseenter.bs.carousel",(function(e){return t.pause(e)})).on("mouseleave.bs.carousel",(function(e){return t.cycle(e)})),this._config.touch&&this._addTouchEventListeners()},e._addTouchEventListeners=function(){var t=this;if(this._touchSupported){var e=function(e){t._pointerEvent&&j[e.originalEvent.pointerType.toUpperCase()]?t.touchStartX=e.originalEvent.clientX:t._pointerEvent||(t.touchStartX=e.originalEvent.touches[0].clientX)},n=function(e){t._pointerEvent&&j[e.originalEvent.pointerType.toUpperCase()]&&(t.touchDeltaX=e.originalEvent.clientX-t.touchStartX),t._handleSwipe(),"hover"===t._config.pause&&(t.pause(),t.touchTimeout&&clearTimeout(t.touchTimeout),t.touchTimeout=setTimeout((function(e){return t.cycle(e)}),500+t._config.interval))};o.default(this._element.querySelectorAll(".carousel-item img")).on("dragstart.bs.carousel",(function(t){return t.preventDefault()})),this._pointerEvent?(o.default(this._element).on("pointerdown.bs.carousel",(function(t){return e(t)})),o.default(this._element).on("pointerup.bs.carousel",(function(t){return n(t)})),this._element.classList.add("pointer-event")):(o.default(this._element).on("touchstart.bs.carousel",(function(t){return e(t)})),o.default(this._element).on("touchmove.bs.carousel",(function(e){return function(e){t.touchDeltaX=e.originalEvent.touches&&e.originalEvent.touches.length>1?0:e.originalEvent.touches[0].clientX-t.touchStartX}(e)})),o.default(this._element).on("touchend.bs.carousel",(function(t){return n(t)})))}},e._keydown=function(t){if(!/input|textarea/i.test(t.target.tagName))switch(t.which){case 37:t.preventDefault(),this.prev();break;case 39:t.preventDefault(),this.next()}},e._getItemIndex=function(t){return this._items=t&&t.parentNode?[].slice.call(t.parentNode.querySelectorAll(".carousel-item")):[],this._items.indexOf(t)},e._getItemByDirection=function(t,e){var n=t===N,i=t===D,o=this._getItemIndex(e),a=this._items.length-1;if((i&&0===o||n&&o===a)&&!this._config.wrap)return e;var s=(o+(t===D?-1:1))%this._items.length;return-1===s?this._items[this._items.length-1]:this._items[s]},e._triggerSlideEvent=function(t,e){var n=this._getItemIndex(t),i=this._getItemIndex(this._element.querySelector(I)),a=o.default.Event("slide.bs.carousel",{relatedTarget:t,direction:e,from:i,to:n});return o.default(this._element).trigger(a),a},e._setActiveIndicatorElement=function(t){if(this._indicatorsElement){var e=[].slice.call(this._indicatorsElement.querySelectorAll(".active"));o.default(e).removeClass(S);var n=this._indicatorsElement.children[this._getItemIndex(t)];n&&o.default(n).addClass(S)}},e._updateInterval=function(){var t=this._activeElement||this._element.querySelector(I);if(t){var e=parseInt(t.getAttribute("data-interval"),10);e?(this._config.defaultInterval=this._config.defaultInterval||this._config.interval,this._config.interval=e):this._config.interval=this._config.defaultInterval||this._config.interval}},e._slide=function(t,e){var n,i,a,s=this,l=this._element.querySelector(I),r=this._getItemIndex(l),u=e||l&&this._getItemByDirection(t,l),f=this._getItemIndex(u),c=Boolean(this._interval);if(t===N?(n="carousel-item-left",i="carousel-item-next",a="left"):(n="carousel-item-right",i="carousel-item-prev",a="right"),u&&o.default(u).hasClass(S))this._isSliding=!1;else if(!this._triggerSlideEvent(u,a).isDefaultPrevented()&&l&&u){this._isSliding=!0,c&&this.pause(),this._setActiveIndicatorElement(u),this._activeElement=u;var h=o.default.Event(A,{relatedTarget:u,direction:a,from:r,to:f});if(o.default(this._element).hasClass("slide")){o.default(u).addClass(i),d.reflow(u),o.default(l).addClass(n),o.default(u).addClass(n);var g=d.getTransitionDurationFromElement(l);o.default(l).one(d.TRANSITION_END,(function(){o.default(u).removeClass(n+" "+i).addClass(S),o.default(l).removeClass("active "+i+" "+n),s._isSliding=!1,setTimeout((function(){return o.default(s._element).trigger(h)}),0)})).emulateTransitionEnd(g)}else o.default(l).removeClass(S),o.default(u).addClass(S),this._isSliding=!1,o.default(this._element).trigger(h);c&&this.cycle()}},t._jQueryInterface=function(e){return this.each((function(){var n=o.default(this).data(w),i=r({},k,o.default(this).data());"object"==typeof e&&(i=r({},i,e));var a="string"==typeof e?e:i.slide;if(n||(n=new t(this,i),o.default(this).data(w,n)),"number"==typeof e)n.to(e);else if("string"==typeof a){if("undefined"==typeof n[a])throw new TypeError('No method named "'+a+'"');n[a]()}else i.interval&&i.ride&&(n.pause(),n.cycle())}))},t._dataApiClickHandler=function(e){var n=d.getSelectorFromElement(this);if(n){var i=o.default(n)[0];if(i&&o.default(i).hasClass("carousel")){var a=r({},o.default(i).data(),o.default(this).data()),s=this.getAttribute("data-slide-to");s&&(a.interval=!1),t._jQueryInterface.call(o.default(i),a),s&&o.default(i).data(w).to(s),e.preventDefault()}}},l(t,null,[{key:"VERSION",get:function(){return"4.6.2"}},{key:"Default",get:function(){return k}}]),t}();o.default(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",P._dataApiClickHandler),o.default(window).on("load.bs.carousel.data-api",(function(){for(var t=[].slice.call(document.querySelectorAll('[data-ride="carousel"]')),e=0,n=t.length;e0&&(this._selector=s,this._triggerArray.push(a))}this._parent=this._config.parent?this._getParent():null,this._config.parent||this._addAriaAndCollapsedClass(this._element,this._triggerArray),this._config.toggle&&this.toggle()}var e=t.prototype;return e.toggle=function(){o.default(this._element).hasClass(q)?this.hide():this.show()},e.show=function(){var e,n,i=this;if(!(this._isTransitioning||o.default(this._element).hasClass(q)||(this._parent&&0===(e=[].slice.call(this._parent.querySelectorAll(".show, .collapsing")).filter((function(t){return"string"==typeof i._config.parent?t.getAttribute("data-parent")===i._config.parent:t.classList.contains(F)}))).length&&(e=null),e&&(n=o.default(e).not(this._selector).data(R))&&n._isTransitioning))){var a=o.default.Event("show.bs.collapse");if(o.default(this._element).trigger(a),!a.isDefaultPrevented()){e&&(t._jQueryInterface.call(o.default(e).not(this._selector),"hide"),n||o.default(e).data(R,null));var s=this._getDimension();o.default(this._element).removeClass(F).addClass(Q),this._element.style[s]=0,this._triggerArray.length&&o.default(this._triggerArray).removeClass(B).attr("aria-expanded",!0),this.setTransitioning(!0);var l="scroll"+(s[0].toUpperCase()+s.slice(1)),r=d.getTransitionDurationFromElement(this._element);o.default(this._element).one(d.TRANSITION_END,(function(){o.default(i._element).removeClass(Q).addClass("collapse show"),i._element.style[s]="",i.setTransitioning(!1),o.default(i._element).trigger("shown.bs.collapse")})).emulateTransitionEnd(r),this._element.style[s]=this._element[l]+"px"}}},e.hide=function(){var t=this;if(!this._isTransitioning&&o.default(this._element).hasClass(q)){var e=o.default.Event("hide.bs.collapse");if(o.default(this._element).trigger(e),!e.isDefaultPrevented()){var n=this._getDimension();this._element.style[n]=this._element.getBoundingClientRect()[n]+"px",d.reflow(this._element),o.default(this._element).addClass(Q).removeClass("collapse show");var i=this._triggerArray.length;if(i>0)for(var a=0;a0},e._getOffset=function(){var t=this,e={};return"function"==typeof this._config.offset?e.fn=function(e){return e.offsets=r({},e.offsets,t._config.offset(e.offsets,t._element)),e}:e.offset=this._config.offset,e},e._getPopperConfig=function(){var t={placement:this._getPlacement(),modifiers:{offset:this._getOffset(),flip:{enabled:this._config.flip},preventOverflow:{boundariesElement:this._config.boundary}}};return"static"===this._config.display&&(t.modifiers.applyStyle={enabled:!1}),r({},t,this._config.popperConfig)},t._jQueryInterface=function(e){return this.each((function(){var n=o.default(this).data(K);if(n||(n=new t(this,"object"==typeof e?e:null),o.default(this).data(K,n)),"string"==typeof e){if("undefined"==typeof n[e])throw new TypeError('No method named "'+e+'"');n[e]()}}))},t._clearMenus=function(e){if(!e||3!==e.which&&("keyup"!==e.type||9===e.which))for(var n=[].slice.call(document.querySelectorAll(it)),i=0,a=n.length;i0&&s--,40===e.which&&sdocument.documentElement.clientHeight;n||(this._element.style.overflowY="hidden"),this._element.classList.add(ht);var i=d.getTransitionDurationFromElement(this._dialog);o.default(this._element).off(d.TRANSITION_END),o.default(this._element).one(d.TRANSITION_END,(function(){t._element.classList.remove(ht),n||o.default(t._element).one(d.TRANSITION_END,(function(){t._element.style.overflowY=""})).emulateTransitionEnd(t._element,i)})).emulateTransitionEnd(i),this._element.focus()}},e._showElement=function(t){var e=this,n=o.default(this._element).hasClass(dt),i=this._dialog?this._dialog.querySelector(".modal-body"):null;this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE||document.body.appendChild(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),o.default(this._dialog).hasClass("modal-dialog-scrollable")&&i?i.scrollTop=0:this._element.scrollTop=0,n&&d.reflow(this._element),o.default(this._element).addClass(ct),this._config.focus&&this._enforceFocus();var a=o.default.Event("shown.bs.modal",{relatedTarget:t}),s=function(){e._config.focus&&e._element.focus(),e._isTransitioning=!1,o.default(e._element).trigger(a)};if(n){var l=d.getTransitionDurationFromElement(this._dialog);o.default(this._dialog).one(d.TRANSITION_END,s).emulateTransitionEnd(l)}else s()},e._enforceFocus=function(){var t=this;o.default(document).off(pt).on(pt,(function(e){document!==e.target&&t._element!==e.target&&0===o.default(t._element).has(e.target).length&&t._element.focus()}))},e._setEscapeEvent=function(){var t=this;this._isShown?o.default(this._element).on(yt,(function(e){t._config.keyboard&&27===e.which?(e.preventDefault(),t.hide()):t._config.keyboard||27!==e.which||t._triggerBackdropTransition()})):this._isShown||o.default(this._element).off(yt)},e._setResizeEvent=function(){var t=this;this._isShown?o.default(window).on(_t,(function(e){return t.handleUpdate(e)})):o.default(window).off(_t)},e._hideModal=function(){var t=this;this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._showBackdrop((function(){o.default(document.body).removeClass(ft),t._resetAdjustments(),t._resetScrollbar(),o.default(t._element).trigger(gt)}))},e._removeBackdrop=function(){this._backdrop&&(o.default(this._backdrop).remove(),this._backdrop=null)},e._showBackdrop=function(t){var e=this,n=o.default(this._element).hasClass(dt)?dt:"";if(this._isShown&&this._config.backdrop){if(this._backdrop=document.createElement("div"),this._backdrop.className="modal-backdrop",n&&this._backdrop.classList.add(n),o.default(this._backdrop).appendTo(document.body),o.default(this._element).on(vt,(function(t){e._ignoreBackdropClick?e._ignoreBackdropClick=!1:t.target===t.currentTarget&&("static"===e._config.backdrop?e._triggerBackdropTransition():e.hide())})),n&&d.reflow(this._backdrop),o.default(this._backdrop).addClass(ct),!t)return;if(!n)return void t();var i=d.getTransitionDurationFromElement(this._backdrop);o.default(this._backdrop).one(d.TRANSITION_END,t).emulateTransitionEnd(i)}else if(!this._isShown&&this._backdrop){o.default(this._backdrop).removeClass(ct);var a=function(){e._removeBackdrop(),t&&t()};if(o.default(this._element).hasClass(dt)){var s=d.getTransitionDurationFromElement(this._backdrop);o.default(this._backdrop).one(d.TRANSITION_END,a).emulateTransitionEnd(s)}else a()}else t&&t()},e._adjustDialog=function(){var t=this._element.scrollHeight>document.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},e._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},e._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=Math.round(t.left+t.right)
      ',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent",customClass:"",sanitize:!0,sanitizeFn:null,whiteList:{"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},popperConfig:null},Ut={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(number|string|function)",container:"(string|element|boolean)",fallbackPlacement:"(string|array)",boundary:"(string|element)",customClass:"(string|function)",sanitize:"boolean",sanitizeFn:"(null|function)",whiteList:"object",popperConfig:"(null|object)"},Mt={HIDE:"hide.bs.tooltip",HIDDEN:"hidden.bs.tooltip",SHOW:"show.bs.tooltip",SHOWN:"shown.bs.tooltip",INSERTED:"inserted.bs.tooltip",CLICK:"click.bs.tooltip",FOCUSIN:"focusin.bs.tooltip",FOCUSOUT:"focusout.bs.tooltip",MOUSEENTER:"mouseenter.bs.tooltip",MOUSELEAVE:"mouseleave.bs.tooltip"},Wt=function(){function t(t,e){if("undefined"==typeof a.default)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var e=t.prototype;return e.enable=function(){this._isEnabled=!0},e.disable=function(){this._isEnabled=!1},e.toggleEnabled=function(){this._isEnabled=!this._isEnabled},e.toggle=function(t){if(this._isEnabled)if(t){var e=this.constructor.DATA_KEY,n=o.default(t.currentTarget).data(e);n||(n=new this.constructor(t.currentTarget,this._getDelegateConfig()),o.default(t.currentTarget).data(e,n)),n._activeTrigger.click=!n._activeTrigger.click,n._isWithActiveTrigger()?n._enter(null,n):n._leave(null,n)}else{if(o.default(this.getTipElement()).hasClass(Rt))return void this._leave(null,this);this._enter(null,this)}},e.dispose=function(){clearTimeout(this._timeout),o.default.removeData(this.element,this.constructor.DATA_KEY),o.default(this.element).off(this.constructor.EVENT_KEY),o.default(this.element).closest(".modal").off("hide.bs.modal",this._hideModalHandler),this.tip&&o.default(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,this._activeTrigger=null,this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},e.show=function(){var t=this;if("none"===o.default(this.element).css("display"))throw new Error("Please use show on visible elements");var e=o.default.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){o.default(this.element).trigger(e);var n=d.findShadowRoot(this.element),i=o.default.contains(null!==n?n:this.element.ownerDocument.documentElement,this.element);if(e.isDefaultPrevented()||!i)return;var s=this.getTipElement(),l=d.getUID(this.constructor.NAME);s.setAttribute("id",l),this.element.setAttribute("aria-describedby",l),this.setContent(),this.config.animation&&o.default(s).addClass(Lt);var r="function"==typeof this.config.placement?this.config.placement.call(this,s,this.element):this.config.placement,u=this._getAttachment(r);this.addAttachmentClass(u);var f=this._getContainer();o.default(s).data(this.constructor.DATA_KEY,this),o.default.contains(this.element.ownerDocument.documentElement,this.tip)||o.default(s).appendTo(f),o.default(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new a.default(this.element,s,this._getPopperConfig(u)),o.default(s).addClass(Rt),o.default(s).addClass(this.config.customClass),"ontouchstart"in document.documentElement&&o.default(document.body).children().on("mouseover",null,o.default.noop);var c=function(){t.config.animation&&t._fixTransition();var e=t._hoverState;t._hoverState=null,o.default(t.element).trigger(t.constructor.Event.SHOWN),e===qt&&t._leave(null,t)};if(o.default(this.tip).hasClass(Lt)){var h=d.getTransitionDurationFromElement(this.tip);o.default(this.tip).one(d.TRANSITION_END,c).emulateTransitionEnd(h)}else c()}},e.hide=function(t){var e=this,n=this.getTipElement(),i=o.default.Event(this.constructor.Event.HIDE),a=function(){e._hoverState!==xt&&n.parentNode&&n.parentNode.removeChild(n),e._cleanTipClass(),e.element.removeAttribute("aria-describedby"),o.default(e.element).trigger(e.constructor.Event.HIDDEN),null!==e._popper&&e._popper.destroy(),t&&t()};if(o.default(this.element).trigger(i),!i.isDefaultPrevented()){if(o.default(n).removeClass(Rt),"ontouchstart"in document.documentElement&&o.default(document.body).children().off("mouseover",null,o.default.noop),this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1,o.default(this.tip).hasClass(Lt)){var s=d.getTransitionDurationFromElement(n);o.default(n).one(d.TRANSITION_END,a).emulateTransitionEnd(s)}else a();this._hoverState=""}},e.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},e.isWithContent=function(){return Boolean(this.getTitle())},e.addAttachmentClass=function(t){o.default(this.getTipElement()).addClass("bs-tooltip-"+t)},e.getTipElement=function(){return this.tip=this.tip||o.default(this.config.template)[0],this.tip},e.setContent=function(){var t=this.getTipElement();this.setElementContent(o.default(t.querySelectorAll(".tooltip-inner")),this.getTitle()),o.default(t).removeClass("fade show")},e.setElementContent=function(t,e){"object"!=typeof e||!e.nodeType&&!e.jquery?this.config.html?(this.config.sanitize&&(e=At(e,this.config.whiteList,this.config.sanitizeFn)),t.html(e)):t.text(e):this.config.html?o.default(e).parent().is(t)||t.empty().append(e):t.text(o.default(e).text())},e.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},e._getPopperConfig=function(t){var e=this;return r({},{placement:t,modifiers:{offset:this._getOffset(),flip:{behavior:this.config.fallbackPlacement},arrow:{element:".arrow"},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){return e._handlePopperPlacementChange(t)}},this.config.popperConfig)},e._getOffset=function(){var t=this,e={};return"function"==typeof this.config.offset?e.fn=function(e){return e.offsets=r({},e.offsets,t.config.offset(e.offsets,t.element)),e}:e.offset=this.config.offset,e},e._getContainer=function(){return!1===this.config.container?document.body:d.isElement(this.config.container)?o.default(this.config.container):o.default(document).find(this.config.container)},e._getAttachment=function(t){return Bt[t.toUpperCase()]},e._setListeners=function(){var t=this;this.config.trigger.split(" ").forEach((function(e){if("click"===e)o.default(t.element).on(t.constructor.Event.CLICK,t.config.selector,(function(e){return t.toggle(e)}));else if("manual"!==e){var n=e===Ft?t.constructor.Event.MOUSEENTER:t.constructor.Event.FOCUSIN,i=e===Ft?t.constructor.Event.MOUSELEAVE:t.constructor.Event.FOCUSOUT;o.default(t.element).on(n,t.config.selector,(function(e){return t._enter(e)})).on(i,t.config.selector,(function(e){return t._leave(e)}))}})),this._hideModalHandler=function(){t.element&&t.hide()},o.default(this.element).closest(".modal").on("hide.bs.modal",this._hideModalHandler),this.config.selector?this.config=r({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},e._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},e._enter=function(t,e){var n=this.constructor.DATA_KEY;(e=e||o.default(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),o.default(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusin"===t.type?Qt:Ft]=!0),o.default(e.getTipElement()).hasClass(Rt)||e._hoverState===xt?e._hoverState=xt:(clearTimeout(e._timeout),e._hoverState=xt,e.config.delay&&e.config.delay.show?e._timeout=setTimeout((function(){e._hoverState===xt&&e.show()}),e.config.delay.show):e.show())},e._leave=function(t,e){var n=this.constructor.DATA_KEY;(e=e||o.default(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),o.default(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusout"===t.type?Qt:Ft]=!1),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=qt,e.config.delay&&e.config.delay.hide?e._timeout=setTimeout((function(){e._hoverState===qt&&e.hide()}),e.config.delay.hide):e.hide())},e._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},e._getConfig=function(t){var e=o.default(this.element).data();return Object.keys(e).forEach((function(t){-1!==Pt.indexOf(t)&&delete e[t]})),"number"==typeof(t=r({},this.constructor.Default,e,"object"==typeof t&&t?t:{})).delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),d.typeCheckConfig(It,t,this.constructor.DefaultType),t.sanitize&&(t.template=At(t.template,t.whiteList,t.sanitizeFn)),t},e._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},e._cleanTipClass=function(){var t=o.default(this.getTipElement()),e=t.attr("class").match(jt);null!==e&&e.length&&t.removeClass(e.join(""))},e._handlePopperPlacementChange=function(t){this.tip=t.instance.popper,this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},e._fixTransition=function(){var t=this.getTipElement(),e=this.config.animation;null===t.getAttribute("x-placement")&&(o.default(t).removeClass(Lt),this.config.animation=!1,this.hide(),this.show(),this.config.animation=e)},t._jQueryInterface=function(e){return this.each((function(){var n=o.default(this),i=n.data(kt),a="object"==typeof e&&e;if((i||!/dispose|hide/.test(e))&&(i||(i=new t(this,a),n.data(kt,i)),"string"==typeof e)){if("undefined"==typeof i[e])throw new TypeError('No method named "'+e+'"');i[e]()}}))},l(t,null,[{key:"VERSION",get:function(){return"4.6.2"}},{key:"Default",get:function(){return Ht}},{key:"NAME",get:function(){return It}},{key:"DATA_KEY",get:function(){return kt}},{key:"Event",get:function(){return Mt}},{key:"EVENT_KEY",get:function(){return".bs.tooltip"}},{key:"DefaultType",get:function(){return Ut}}]),t}();o.default.fn.tooltip=Wt._jQueryInterface,o.default.fn.tooltip.Constructor=Wt,o.default.fn.tooltip.noConflict=function(){return o.default.fn.tooltip=Ot,Wt._jQueryInterface};var Vt="bs.popover",zt=o.default.fn.popover,Kt=new RegExp("(^|\\s)bs-popover\\S+","g"),Xt=r({},Wt.Default,{placement:"right",trigger:"click",content:"",template:''}),Yt=r({},Wt.DefaultType,{content:"(string|element|function)"}),$t={HIDE:"hide.bs.popover",HIDDEN:"hidden.bs.popover",SHOW:"show.bs.popover",SHOWN:"shown.bs.popover",INSERTED:"inserted.bs.popover",CLICK:"click.bs.popover",FOCUSIN:"focusin.bs.popover",FOCUSOUT:"focusout.bs.popover",MOUSEENTER:"mouseenter.bs.popover",MOUSELEAVE:"mouseleave.bs.popover"},Jt=function(t){var e,n;function i(){return t.apply(this,arguments)||this}n=t,(e=i).prototype=Object.create(n.prototype),e.prototype.constructor=e,u(e,n);var a=i.prototype;return a.isWithContent=function(){return this.getTitle()||this._getContent()},a.addAttachmentClass=function(t){o.default(this.getTipElement()).addClass("bs-popover-"+t)},a.getTipElement=function(){return this.tip=this.tip||o.default(this.config.template)[0],this.tip},a.setContent=function(){var t=o.default(this.getTipElement());this.setElementContent(t.find(".popover-header"),this.getTitle());var e=this._getContent();"function"==typeof e&&(e=e.call(this.element)),this.setElementContent(t.find(".popover-body"),e),t.removeClass("fade show")},a._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},a._cleanTipClass=function(){var t=o.default(this.getTipElement()),e=t.attr("class").match(Kt);null!==e&&e.length>0&&t.removeClass(e.join(""))},i._jQueryInterface=function(t){return this.each((function(){var e=o.default(this).data(Vt),n="object"==typeof t?t:null;if((e||!/dispose|hide/.test(t))&&(e||(e=new i(this,n),o.default(this).data(Vt,e)),"string"==typeof t)){if("undefined"==typeof e[t])throw new TypeError('No method named "'+t+'"');e[t]()}}))},l(i,null,[{key:"VERSION",get:function(){return"4.6.2"}},{key:"Default",get:function(){return Xt}},{key:"NAME",get:function(){return"popover"}},{key:"DATA_KEY",get:function(){return Vt}},{key:"Event",get:function(){return $t}},{key:"EVENT_KEY",get:function(){return".bs.popover"}},{key:"DefaultType",get:function(){return Yt}}]),i}(Wt);o.default.fn.popover=Jt._jQueryInterface,o.default.fn.popover.Constructor=Jt,o.default.fn.popover.noConflict=function(){return o.default.fn.popover=zt,Jt._jQueryInterface};var Gt="scrollspy",Zt="bs.scrollspy",te=o.default.fn[Gt],ee="active",ne="position",ie=".nav, .list-group",oe={offset:10,method:"auto",target:""},ae={offset:"number",method:"string",target:"(string|element)"},se=function(){function t(t,e){var n=this;this._element=t,this._scrollElement="BODY"===t.tagName?window:t,this._config=this._getConfig(e),this._selector=this._config.target+" .nav-link,"+this._config.target+" .list-group-item,"+this._config.target+" .dropdown-item",this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,o.default(this._scrollElement).on("scroll.bs.scrollspy",(function(t){return n._process(t)})),this.refresh(),this._process()}var e=t.prototype;return e.refresh=function(){var t=this,e=this._scrollElement===this._scrollElement.window?"offset":ne,n="auto"===this._config.method?e:this._config.method,i=n===ne?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),[].slice.call(document.querySelectorAll(this._selector)).map((function(t){var e,a=d.getSelectorFromElement(t);if(a&&(e=document.querySelector(a)),e){var s=e.getBoundingClientRect();if(s.width||s.height)return[o.default(e)[n]().top+i,a]}return null})).filter(Boolean).sort((function(t,e){return t[0]-e[0]})).forEach((function(e){t._offsets.push(e[0]),t._targets.push(e[1])}))},e.dispose=function(){o.default.removeData(this._element,Zt),o.default(this._scrollElement).off(".bs.scrollspy"),this._element=null,this._scrollElement=null,this._config=null,this._selector=null,this._offsets=null,this._targets=null,this._activeTarget=null,this._scrollHeight=null},e._getConfig=function(t){if("string"!=typeof(t=r({},oe,"object"==typeof t&&t?t:{})).target&&d.isElement(t.target)){var e=o.default(t.target).attr("id");e||(e=d.getUID(Gt),o.default(t.target).attr("id",e)),t.target="#"+e}return d.typeCheckConfig(Gt,t,ae),t},e._getScrollTop=function(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop},e._getScrollHeight=function(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)},e._getOffsetHeight=function(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height},e._process=function(){var t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),n=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=n){var i=this._targets[this._targets.length-1];this._activeTarget!==i&&this._activate(i)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(var o=this._offsets.length;o--;)this._activeTarget!==this._targets[o]&&t>=this._offsets[o]&&("undefined"==typeof this._offsets[o+1]||t li > .active",ge=function(){function t(t){this._element=t}var e=t.prototype;return e.show=function(){var t=this;if(!(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&o.default(this._element).hasClass(ue)||o.default(this._element).hasClass("disabled")||this._element.hasAttribute("disabled"))){var e,n,i=o.default(this._element).closest(".nav, .list-group")[0],a=d.getSelectorFromElement(this._element);if(i){var s="UL"===i.nodeName||"OL"===i.nodeName?he:ce;n=(n=o.default.makeArray(o.default(i).find(s)))[n.length-1]}var l=o.default.Event("hide.bs.tab",{relatedTarget:this._element}),r=o.default.Event("show.bs.tab",{relatedTarget:n});if(n&&o.default(n).trigger(l),o.default(this._element).trigger(r),!r.isDefaultPrevented()&&!l.isDefaultPrevented()){a&&(e=document.querySelector(a)),this._activate(this._element,i);var u=function(){var e=o.default.Event("hidden.bs.tab",{relatedTarget:t._element}),i=o.default.Event("shown.bs.tab",{relatedTarget:n});o.default(n).trigger(e),o.default(t._element).trigger(i)};e?this._activate(e,e.parentNode,u):u()}}},e.dispose=function(){o.default.removeData(this._element,le),this._element=null},e._activate=function(t,e,n){var i=this,a=(!e||"UL"!==e.nodeName&&"OL"!==e.nodeName?o.default(e).children(ce):o.default(e).find(he))[0],s=n&&a&&o.default(a).hasClass(fe),l=function(){return i._transitionComplete(t,a,n)};if(a&&s){var r=d.getTransitionDurationFromElement(a);o.default(a).removeClass(de).one(d.TRANSITION_END,l).emulateTransitionEnd(r)}else l()},e._transitionComplete=function(t,e,n){if(e){o.default(e).removeClass(ue);var i=o.default(e.parentNode).find("> .dropdown-menu .active")[0];i&&o.default(i).removeClass(ue),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!1)}o.default(t).addClass(ue),"tab"===t.getAttribute("role")&&t.setAttribute("aria-selected",!0),d.reflow(t),t.classList.contains(fe)&&t.classList.add(de);var a=t.parentNode;if(a&&"LI"===a.nodeName&&(a=a.parentNode),a&&o.default(a).hasClass("dropdown-menu")){var s=o.default(t).closest(".dropdown")[0];if(s){var l=[].slice.call(s.querySelectorAll(".dropdown-toggle"));o.default(l).addClass(ue)}t.setAttribute("aria-expanded",!0)}n&&n()},t._jQueryInterface=function(e){return this.each((function(){var n=o.default(this),i=n.data(le);if(i||(i=new t(this),n.data(le,i)),"string"==typeof e){if("undefined"==typeof i[e])throw new TypeError('No method named "'+e+'"');i[e]()}}))},l(t,null,[{key:"VERSION",get:function(){return"4.6.2"}}]),t}();o.default(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"], [data-toggle="list"]',(function(t){t.preventDefault(),ge._jQueryInterface.call(o.default(this),"show")})),o.default.fn.tab=ge._jQueryInterface,o.default.fn.tab.Constructor=ge,o.default.fn.tab.noConflict=function(){return o.default.fn.tab=re,ge._jQueryInterface};var me="bs.toast",pe=o.default.fn.toast,_e="hide",ve="show",ye="showing",be="click.dismiss.bs.toast",Ee={animation:!0,autohide:!0,delay:500},Te={animation:"boolean",autohide:"boolean",delay:"number"},we=function(){function t(t,e){this._element=t,this._config=this._getConfig(e),this._timeout=null,this._setListeners()}var e=t.prototype;return e.show=function(){var t=this,e=o.default.Event("show.bs.toast");if(o.default(this._element).trigger(e),!e.isDefaultPrevented()){this._clearTimeout(),this._config.animation&&this._element.classList.add("fade");var n=function(){t._element.classList.remove(ye),t._element.classList.add(ve),o.default(t._element).trigger("shown.bs.toast"),t._config.autohide&&(t._timeout=setTimeout((function(){t.hide()}),t._config.delay))};if(this._element.classList.remove(_e),d.reflow(this._element),this._element.classList.add(ye),this._config.animation){var i=d.getTransitionDurationFromElement(this._element);o.default(this._element).one(d.TRANSITION_END,n).emulateTransitionEnd(i)}else n()}},e.hide=function(){if(this._element.classList.contains(ve)){var t=o.default.Event("hide.bs.toast");o.default(this._element).trigger(t),t.isDefaultPrevented()||this._close()}},e.dispose=function(){this._clearTimeout(),this._element.classList.contains(ve)&&this._element.classList.remove(ve),o.default(this._element).off(be),o.default.removeData(this._element,me),this._element=null,this._config=null},e._getConfig=function(t){return t=r({},Ee,o.default(this._element).data(),"object"==typeof t&&t?t:{}),d.typeCheckConfig("toast",t,this.constructor.DefaultType),t},e._setListeners=function(){var t=this;o.default(this._element).on(be,'[data-dismiss="toast"]',(function(){return t.hide()}))},e._close=function(){var t=this,e=function(){t._element.classList.add(_e),o.default(t._element).trigger("hidden.bs.toast")};if(this._element.classList.remove(ve),this._config.animation){var n=d.getTransitionDurationFromElement(this._element);o.default(this._element).one(d.TRANSITION_END,e).emulateTransitionEnd(n)}else e()},e._clearTimeout=function(){clearTimeout(this._timeout),this._timeout=null},t._jQueryInterface=function(e){return this.each((function(){var n=o.default(this),i=n.data(me);if(i||(i=new t(this,"object"==typeof e&&e),n.data(me,i)),"string"==typeof e){if("undefined"==typeof i[e])throw new TypeError('No method named "'+e+'"');i[e](this)}}))},l(t,null,[{key:"VERSION",get:function(){return"4.6.2"}},{key:"DefaultType",get:function(){return Te}},{key:"Default",get:function(){return Ee}}]),t}();o.default.fn.toast=we._jQueryInterface,o.default.fn.toast.Constructor=we,o.default.fn.toast.noConflict=function(){return o.default.fn.toast=pe,we._jQueryInterface},t.Alert=g,t.Button=E,t.Carousel=P,t.Collapse=V,t.Dropdown=lt,t.Modal=Ct,t.Popover=Jt,t.Scrollspy=se,t.Tab=ge,t.Toast=we,t.Tooltip=Wt,t.Util=d,Object.defineProperty(t,"__esModule",{value:!0})})); +//# sourceMappingURL=bootstrap.min.js.map \ No newline at end of file diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/d3.min.js b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/d3.min.js new file mode 100644 index 00000000..16648730 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/d3.min.js @@ -0,0 +1,5 @@ +!function(){function n(n){return n&&(n.ownerDocument||n.document||n).documentElement}function t(n){return n&&(n.ownerDocument&&n.ownerDocument.defaultView||n.document&&n||n.defaultView)}function e(n,t){return t>n?-1:n>t?1:n>=t?0:NaN}function r(n){return null===n?NaN:+n}function i(n){return!isNaN(n)}function u(n){return{left:function(t,e,r,i){for(arguments.length<3&&(r=0),arguments.length<4&&(i=t.length);i>r;){var u=r+i>>>1;n(t[u],e)<0?r=u+1:i=u}return r},right:function(t,e,r,i){for(arguments.length<3&&(r=0),arguments.length<4&&(i=t.length);i>r;){var u=r+i>>>1;n(t[u],e)>0?i=u:r=u+1}return r}}}function o(n){return n.length}function a(n){for(var t=1;n*t%1;)t*=10;return t}function l(n,t){for(var e in t)Object.defineProperty(n.prototype,e,{value:t[e],enumerable:!1})}function c(){this._=Object.create(null)}function f(n){return(n+="")===bo||n[0]===_o?_o+n:n}function s(n){return(n+="")[0]===_o?n.slice(1):n}function h(n){return f(n)in this._}function p(n){return(n=f(n))in this._&&delete this._[n]}function g(){var n=[];for(var t in this._)n.push(s(t));return n}function v(){var n=0;for(var t in this._)++n;return n}function d(){for(var n in this._)return!1;return!0}function y(){this._=Object.create(null)}function m(n){return n}function M(n,t,e){return function(){var r=e.apply(t,arguments);return r===t?n:r}}function x(n,t){if(t in n)return t;t=t.charAt(0).toUpperCase()+t.slice(1);for(var e=0,r=wo.length;r>e;++e){var i=wo[e]+t;if(i in n)return i}}function b(){}function _(){}function w(n){function t(){for(var t,r=e,i=-1,u=r.length;++ie;e++)for(var i,u=n[e],o=0,a=u.length;a>o;o++)(i=u[o])&&t(i,o,e);return n}function Z(n){return ko(n,qo),n}function V(n){var t,e;return function(r,i,u){var o,a=n[u].update,l=a.length;for(u!=e&&(e=u,t=0),i>=t&&(t=i+1);!(o=a[t])&&++t0&&(n=n.slice(0,a));var c=To.get(n);return c&&(n=c,l=B),a?t?i:r:t?b:u}function $(n,t){return function(e){var r=ao.event;ao.event=e,t[0]=this.__data__;try{n.apply(this,t)}finally{ao.event=r}}}function B(n,t){var e=$(n,t);return function(n){var t=this,r=n.relatedTarget;r&&(r===t||8&r.compareDocumentPosition(t))||e.call(t,n)}}function W(e){var r=".dragsuppress-"+ ++Do,i="click"+r,u=ao.select(t(e)).on("touchmove"+r,S).on("dragstart"+r,S).on("selectstart"+r,S);if(null==Ro&&(Ro="onselectstart"in e?!1:x(e.style,"userSelect")),Ro){var o=n(e).style,a=o[Ro];o[Ro]="none"}return function(n){if(u.on(r,null),Ro&&(o[Ro]=a),n){var t=function(){u.on(i,null)};u.on(i,function(){S(),t()},!0),setTimeout(t,0)}}}function J(n,e){e.changedTouches&&(e=e.changedTouches[0]);var r=n.ownerSVGElement||n;if(r.createSVGPoint){var i=r.createSVGPoint();if(0>Po){var u=t(n);if(u.scrollX||u.scrollY){r=ao.select("body").append("svg").style({position:"absolute",top:0,left:0,margin:0,padding:0,border:"none"},"important");var o=r[0][0].getScreenCTM();Po=!(o.f||o.e),r.remove()}}return Po?(i.x=e.pageX,i.y=e.pageY):(i.x=e.clientX,i.y=e.clientY),i=i.matrixTransform(n.getScreenCTM().inverse()),[i.x,i.y]}var a=n.getBoundingClientRect();return[e.clientX-a.left-n.clientLeft,e.clientY-a.top-n.clientTop]}function G(){return ao.event.changedTouches[0].identifier}function K(n){return n>0?1:0>n?-1:0}function Q(n,t,e){return(t[0]-n[0])*(e[1]-n[1])-(t[1]-n[1])*(e[0]-n[0])}function nn(n){return n>1?0:-1>n?Fo:Math.acos(n)}function tn(n){return n>1?Io:-1>n?-Io:Math.asin(n)}function en(n){return((n=Math.exp(n))-1/n)/2}function rn(n){return((n=Math.exp(n))+1/n)/2}function un(n){return((n=Math.exp(2*n))-1)/(n+1)}function on(n){return(n=Math.sin(n/2))*n}function an(){}function ln(n,t,e){return this instanceof ln?(this.h=+n,this.s=+t,void(this.l=+e)):arguments.length<2?n instanceof ln?new ln(n.h,n.s,n.l):_n(""+n,wn,ln):new ln(n,t,e)}function cn(n,t,e){function r(n){return n>360?n-=360:0>n&&(n+=360),60>n?u+(o-u)*n/60:180>n?o:240>n?u+(o-u)*(240-n)/60:u}function i(n){return Math.round(255*r(n))}var u,o;return n=isNaN(n)?0:(n%=360)<0?n+360:n,t=isNaN(t)?0:0>t?0:t>1?1:t,e=0>e?0:e>1?1:e,o=.5>=e?e*(1+t):e+t-e*t,u=2*e-o,new mn(i(n+120),i(n),i(n-120))}function fn(n,t,e){return this instanceof fn?(this.h=+n,this.c=+t,void(this.l=+e)):arguments.length<2?n instanceof fn?new fn(n.h,n.c,n.l):n instanceof hn?gn(n.l,n.a,n.b):gn((n=Sn((n=ao.rgb(n)).r,n.g,n.b)).l,n.a,n.b):new fn(n,t,e)}function sn(n,t,e){return isNaN(n)&&(n=0),isNaN(t)&&(t=0),new hn(e,Math.cos(n*=Yo)*t,Math.sin(n)*t)}function hn(n,t,e){return this instanceof hn?(this.l=+n,this.a=+t,void(this.b=+e)):arguments.length<2?n instanceof hn?new hn(n.l,n.a,n.b):n instanceof fn?sn(n.h,n.c,n.l):Sn((n=mn(n)).r,n.g,n.b):new hn(n,t,e)}function pn(n,t,e){var r=(n+16)/116,i=r+t/500,u=r-e/200;return i=vn(i)*na,r=vn(r)*ta,u=vn(u)*ea,new mn(yn(3.2404542*i-1.5371385*r-.4985314*u),yn(-.969266*i+1.8760108*r+.041556*u),yn(.0556434*i-.2040259*r+1.0572252*u))}function gn(n,t,e){return n>0?new fn(Math.atan2(e,t)*Zo,Math.sqrt(t*t+e*e),n):new fn(NaN,NaN,n)}function vn(n){return n>.206893034?n*n*n:(n-4/29)/7.787037}function dn(n){return n>.008856?Math.pow(n,1/3):7.787037*n+4/29}function yn(n){return Math.round(255*(.00304>=n?12.92*n:1.055*Math.pow(n,1/2.4)-.055))}function mn(n,t,e){return this instanceof mn?(this.r=~~n,this.g=~~t,void(this.b=~~e)):arguments.length<2?n instanceof mn?new mn(n.r,n.g,n.b):_n(""+n,mn,cn):new mn(n,t,e)}function Mn(n){return new mn(n>>16,n>>8&255,255&n)}function xn(n){return Mn(n)+""}function bn(n){return 16>n?"0"+Math.max(0,n).toString(16):Math.min(255,n).toString(16)}function _n(n,t,e){var r,i,u,o=0,a=0,l=0;if(r=/([a-z]+)\((.*)\)/.exec(n=n.toLowerCase()))switch(i=r[2].split(","),r[1]){case"hsl":return e(parseFloat(i[0]),parseFloat(i[1])/100,parseFloat(i[2])/100);case"rgb":return t(Nn(i[0]),Nn(i[1]),Nn(i[2]))}return(u=ua.get(n))?t(u.r,u.g,u.b):(null==n||"#"!==n.charAt(0)||isNaN(u=parseInt(n.slice(1),16))||(4===n.length?(o=(3840&u)>>4,o=o>>4|o,a=240&u,a=a>>4|a,l=15&u,l=l<<4|l):7===n.length&&(o=(16711680&u)>>16,a=(65280&u)>>8,l=255&u)),t(o,a,l))}function wn(n,t,e){var r,i,u=Math.min(n/=255,t/=255,e/=255),o=Math.max(n,t,e),a=o-u,l=(o+u)/2;return a?(i=.5>l?a/(o+u):a/(2-o-u),r=n==o?(t-e)/a+(e>t?6:0):t==o?(e-n)/a+2:(n-t)/a+4,r*=60):(r=NaN,i=l>0&&1>l?0:r),new ln(r,i,l)}function Sn(n,t,e){n=kn(n),t=kn(t),e=kn(e);var r=dn((.4124564*n+.3575761*t+.1804375*e)/na),i=dn((.2126729*n+.7151522*t+.072175*e)/ta),u=dn((.0193339*n+.119192*t+.9503041*e)/ea);return hn(116*i-16,500*(r-i),200*(i-u))}function kn(n){return(n/=255)<=.04045?n/12.92:Math.pow((n+.055)/1.055,2.4)}function Nn(n){var t=parseFloat(n);return"%"===n.charAt(n.length-1)?Math.round(2.55*t):t}function En(n){return"function"==typeof n?n:function(){return n}}function An(n){return function(t,e,r){return 2===arguments.length&&"function"==typeof e&&(r=e,e=null),Cn(t,e,n,r)}}function Cn(n,t,e,r){function i(){var n,t=l.status;if(!t&&Ln(l)||t>=200&&300>t||304===t){try{n=e.call(u,l)}catch(r){return void o.error.call(u,r)}o.load.call(u,n)}else o.error.call(u,l)}var u={},o=ao.dispatch("beforesend","progress","load","error"),a={},l=new XMLHttpRequest,c=null;return!this.XDomainRequest||"withCredentials"in l||!/^(http(s)?:)?\/\//.test(n)||(l=new XDomainRequest),"onload"in l?l.onload=l.onerror=i:l.onreadystatechange=function(){l.readyState>3&&i()},l.onprogress=function(n){var t=ao.event;ao.event=n;try{o.progress.call(u,l)}finally{ao.event=t}},u.header=function(n,t){return n=(n+"").toLowerCase(),arguments.length<2?a[n]:(null==t?delete a[n]:a[n]=t+"",u)},u.mimeType=function(n){return arguments.length?(t=null==n?null:n+"",u):t},u.responseType=function(n){return arguments.length?(c=n,u):c},u.response=function(n){return e=n,u},["get","post"].forEach(function(n){u[n]=function(){return u.send.apply(u,[n].concat(co(arguments)))}}),u.send=function(e,r,i){if(2===arguments.length&&"function"==typeof r&&(i=r,r=null),l.open(e,n,!0),null==t||"accept"in a||(a.accept=t+",*/*"),l.setRequestHeader)for(var f in a)l.setRequestHeader(f,a[f]);return null!=t&&l.overrideMimeType&&l.overrideMimeType(t),null!=c&&(l.responseType=c),null!=i&&u.on("error",i).on("load",function(n){i(null,n)}),o.beforesend.call(u,l),l.send(null==r?null:r),u},u.abort=function(){return l.abort(),u},ao.rebind(u,o,"on"),null==r?u:u.get(zn(r))}function zn(n){return 1===n.length?function(t,e){n(null==t?e:null)}:n}function Ln(n){var t=n.responseType;return t&&"text"!==t?n.response:n.responseText}function qn(n,t,e){var r=arguments.length;2>r&&(t=0),3>r&&(e=Date.now());var i=e+t,u={c:n,t:i,n:null};return aa?aa.n=u:oa=u,aa=u,la||(ca=clearTimeout(ca),la=1,fa(Tn)),u}function Tn(){var n=Rn(),t=Dn()-n;t>24?(isFinite(t)&&(clearTimeout(ca),ca=setTimeout(Tn,t)),la=0):(la=1,fa(Tn))}function Rn(){for(var n=Date.now(),t=oa;t;)n>=t.t&&t.c(n-t.t)&&(t.c=null),t=t.n;return n}function Dn(){for(var n,t=oa,e=1/0;t;)t.c?(t.t8?function(n){return n/e}:function(n){return n*e},symbol:n}}function jn(n){var t=n.decimal,e=n.thousands,r=n.grouping,i=n.currency,u=r&&e?function(n,t){for(var i=n.length,u=[],o=0,a=r[0],l=0;i>0&&a>0&&(l+a+1>t&&(a=Math.max(1,t-l)),u.push(n.substring(i-=a,i+a)),!((l+=a+1)>t));)a=r[o=(o+1)%r.length];return u.reverse().join(e)}:m;return function(n){var e=ha.exec(n),r=e[1]||" ",o=e[2]||">",a=e[3]||"-",l=e[4]||"",c=e[5],f=+e[6],s=e[7],h=e[8],p=e[9],g=1,v="",d="",y=!1,m=!0;switch(h&&(h=+h.substring(1)),(c||"0"===r&&"="===o)&&(c=r="0",o="="),p){case"n":s=!0,p="g";break;case"%":g=100,d="%",p="f";break;case"p":g=100,d="%",p="r";break;case"b":case"o":case"x":case"X":"#"===l&&(v="0"+p.toLowerCase());case"c":m=!1;case"d":y=!0,h=0;break;case"s":g=-1,p="r"}"$"===l&&(v=i[0],d=i[1]),"r"!=p||h||(p="g"),null!=h&&("g"==p?h=Math.max(1,Math.min(21,h)):"e"!=p&&"f"!=p||(h=Math.max(0,Math.min(20,h)))),p=pa.get(p)||Fn;var M=c&&s;return function(n){var e=d;if(y&&n%1)return"";var i=0>n||0===n&&0>1/n?(n=-n,"-"):"-"===a?"":a;if(0>g){var l=ao.formatPrefix(n,h);n=l.scale(n),e=l.symbol+d}else n*=g;n=p(n,h);var x,b,_=n.lastIndexOf(".");if(0>_){var w=m?n.lastIndexOf("e"):-1;0>w?(x=n,b=""):(x=n.substring(0,w),b=n.substring(w))}else x=n.substring(0,_),b=t+n.substring(_+1);!c&&s&&(x=u(x,1/0));var S=v.length+x.length+b.length+(M?0:i.length),k=f>S?new Array(S=f-S+1).join(r):"";return M&&(x=u(k+x,k.length?f-b.length:1/0)),i+=v,n=x+b,("<"===o?i+n+k:">"===o?k+i+n:"^"===o?k.substring(0,S>>=1)+i+n+k.substring(S):i+(M?n:k+n))+e}}}function Fn(n){return n+""}function Hn(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function On(n,t,e){function r(t){var e=n(t),r=u(e,1);return r-t>t-e?e:r}function i(e){return t(e=n(new va(e-1)),1),e}function u(n,e){return t(n=new va(+n),e),n}function o(n,r,u){var o=i(n),a=[];if(u>1)for(;r>o;)e(o)%u||a.push(new Date(+o)),t(o,1);else for(;r>o;)a.push(new Date(+o)),t(o,1);return a}function a(n,t,e){try{va=Hn;var r=new Hn;return r._=n,o(r,t,e)}finally{va=Date}}n.floor=n,n.round=r,n.ceil=i,n.offset=u,n.range=o;var l=n.utc=In(n);return l.floor=l,l.round=In(r),l.ceil=In(i),l.offset=In(u),l.range=a,n}function In(n){return function(t,e){try{va=Hn;var r=new Hn;return r._=t,n(r,e)._}finally{va=Date}}}function Yn(n){function t(n){function t(t){for(var e,i,u,o=[],a=-1,l=0;++aa;){if(r>=c)return-1;if(i=t.charCodeAt(a++),37===i){if(o=t.charAt(a++),u=C[o in ya?t.charAt(a++):o],!u||(r=u(n,e,r))<0)return-1}else if(i!=e.charCodeAt(r++))return-1}return r}function r(n,t,e){_.lastIndex=0;var r=_.exec(t.slice(e));return r?(n.w=w.get(r[0].toLowerCase()),e+r[0].length):-1}function i(n,t,e){x.lastIndex=0;var r=x.exec(t.slice(e));return r?(n.w=b.get(r[0].toLowerCase()),e+r[0].length):-1}function u(n,t,e){N.lastIndex=0;var r=N.exec(t.slice(e));return r?(n.m=E.get(r[0].toLowerCase()),e+r[0].length):-1}function o(n,t,e){S.lastIndex=0;var r=S.exec(t.slice(e));return r?(n.m=k.get(r[0].toLowerCase()),e+r[0].length):-1}function a(n,t,r){return e(n,A.c.toString(),t,r)}function l(n,t,r){return e(n,A.x.toString(),t,r)}function c(n,t,r){return e(n,A.X.toString(),t,r)}function f(n,t,e){var r=M.get(t.slice(e,e+=2).toLowerCase());return null==r?-1:(n.p=r,e)}var s=n.dateTime,h=n.date,p=n.time,g=n.periods,v=n.days,d=n.shortDays,y=n.months,m=n.shortMonths;t.utc=function(n){function e(n){try{va=Hn;var t=new va;return t._=n,r(t)}finally{va=Date}}var r=t(n);return e.parse=function(n){try{va=Hn;var t=r.parse(n);return t&&t._}finally{va=Date}},e.toString=r.toString,e},t.multi=t.utc.multi=ct;var M=ao.map(),x=Vn(v),b=Xn(v),_=Vn(d),w=Xn(d),S=Vn(y),k=Xn(y),N=Vn(m),E=Xn(m);g.forEach(function(n,t){M.set(n.toLowerCase(),t)});var A={a:function(n){return d[n.getDay()]},A:function(n){return v[n.getDay()]},b:function(n){return m[n.getMonth()]},B:function(n){return y[n.getMonth()]},c:t(s),d:function(n,t){return Zn(n.getDate(),t,2)},e:function(n,t){return Zn(n.getDate(),t,2)},H:function(n,t){return Zn(n.getHours(),t,2)},I:function(n,t){return Zn(n.getHours()%12||12,t,2)},j:function(n,t){return Zn(1+ga.dayOfYear(n),t,3)},L:function(n,t){return Zn(n.getMilliseconds(),t,3)},m:function(n,t){return Zn(n.getMonth()+1,t,2)},M:function(n,t){return Zn(n.getMinutes(),t,2)},p:function(n){return g[+(n.getHours()>=12)]},S:function(n,t){return Zn(n.getSeconds(),t,2)},U:function(n,t){return Zn(ga.sundayOfYear(n),t,2)},w:function(n){return n.getDay()},W:function(n,t){return Zn(ga.mondayOfYear(n),t,2)},x:t(h),X:t(p),y:function(n,t){return Zn(n.getFullYear()%100,t,2)},Y:function(n,t){return Zn(n.getFullYear()%1e4,t,4)},Z:at,"%":function(){return"%"}},C={a:r,A:i,b:u,B:o,c:a,d:tt,e:tt,H:rt,I:rt,j:et,L:ot,m:nt,M:it,p:f,S:ut,U:Bn,w:$n,W:Wn,x:l,X:c,y:Gn,Y:Jn,Z:Kn,"%":lt};return t}function Zn(n,t,e){var r=0>n?"-":"",i=(r?-n:n)+"",u=i.length;return r+(e>u?new Array(e-u+1).join(t)+i:i)}function Vn(n){return new RegExp("^(?:"+n.map(ao.requote).join("|")+")","i")}function Xn(n){for(var t=new c,e=-1,r=n.length;++e68?1900:2e3)}function nt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.m=r[0]-1,e+r[0].length):-1}function tt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.d=+r[0],e+r[0].length):-1}function et(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+3));return r?(n.j=+r[0],e+r[0].length):-1}function rt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.H=+r[0],e+r[0].length):-1}function it(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.M=+r[0],e+r[0].length):-1}function ut(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.S=+r[0],e+r[0].length):-1}function ot(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+3));return r?(n.L=+r[0],e+r[0].length):-1}function at(n){var t=n.getTimezoneOffset(),e=t>0?"-":"+",r=xo(t)/60|0,i=xo(t)%60;return e+Zn(r,"0",2)+Zn(i,"0",2)}function lt(n,t,e){Ma.lastIndex=0;var r=Ma.exec(t.slice(e,e+1));return r?e+r[0].length:-1}function ct(n){for(var t=n.length,e=-1;++e=0?1:-1,a=o*e,l=Math.cos(t),c=Math.sin(t),f=u*c,s=i*l+f*Math.cos(a),h=f*o*Math.sin(a);ka.add(Math.atan2(h,s)),r=n,i=l,u=c}var t,e,r,i,u;Na.point=function(o,a){Na.point=n,r=(t=o)*Yo,i=Math.cos(a=(e=a)*Yo/2+Fo/4),u=Math.sin(a)},Na.lineEnd=function(){n(t,e)}}function dt(n){var t=n[0],e=n[1],r=Math.cos(e);return[r*Math.cos(t),r*Math.sin(t),Math.sin(e)]}function yt(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]}function mt(n,t){return[n[1]*t[2]-n[2]*t[1],n[2]*t[0]-n[0]*t[2],n[0]*t[1]-n[1]*t[0]]}function Mt(n,t){n[0]+=t[0],n[1]+=t[1],n[2]+=t[2]}function xt(n,t){return[n[0]*t,n[1]*t,n[2]*t]}function bt(n){var t=Math.sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);n[0]/=t,n[1]/=t,n[2]/=t}function _t(n){return[Math.atan2(n[1],n[0]),tn(n[2])]}function wt(n,t){return xo(n[0]-t[0])a;++a)i.point((e=n[a])[0],e[1]);return void i.lineEnd()}var l=new Tt(e,n,null,!0),c=new Tt(e,null,l,!1);l.o=c,u.push(l),o.push(c),l=new Tt(r,n,null,!1),c=new Tt(r,null,l,!0),l.o=c,u.push(l),o.push(c)}}),o.sort(t),qt(u),qt(o),u.length){for(var a=0,l=e,c=o.length;c>a;++a)o[a].e=l=!l;for(var f,s,h=u[0];;){for(var p=h,g=!0;p.v;)if((p=p.n)===h)return;f=p.z,i.lineStart();do{if(p.v=p.o.v=!0,p.e){if(g)for(var a=0,c=f.length;c>a;++a)i.point((s=f[a])[0],s[1]);else r(p.x,p.n.x,1,i);p=p.n}else{if(g){f=p.p.z;for(var a=f.length-1;a>=0;--a)i.point((s=f[a])[0],s[1])}else r(p.x,p.p.x,-1,i);p=p.p}p=p.o,f=p.z,g=!g}while(!p.v);i.lineEnd()}}}function qt(n){if(t=n.length){for(var t,e,r=0,i=n[0];++r0){for(b||(u.polygonStart(),b=!0),u.lineStart();++o1&&2&t&&e.push(e.pop().concat(e.shift())),p.push(e.filter(Dt))}var p,g,v,d=t(u),y=i.invert(r[0],r[1]),m={point:o,lineStart:l,lineEnd:c,polygonStart:function(){m.point=f,m.lineStart=s,m.lineEnd=h,p=[],g=[]},polygonEnd:function(){m.point=o,m.lineStart=l,m.lineEnd=c,p=ao.merge(p);var n=Ot(y,g);p.length?(b||(u.polygonStart(),b=!0),Lt(p,Ut,n,e,u)):n&&(b||(u.polygonStart(),b=!0),u.lineStart(),e(null,null,1,u),u.lineEnd()),b&&(u.polygonEnd(),b=!1),p=g=null},sphere:function(){u.polygonStart(),u.lineStart(),e(null,null,1,u),u.lineEnd(),u.polygonEnd()}},M=Pt(),x=t(M),b=!1;return m}}function Dt(n){return n.length>1}function Pt(){var n,t=[];return{lineStart:function(){t.push(n=[])},point:function(t,e){n.push([t,e])},lineEnd:b,buffer:function(){var e=t;return t=[],n=null,e},rejoin:function(){t.length>1&&t.push(t.pop().concat(t.shift()))}}}function Ut(n,t){return((n=n.x)[0]<0?n[1]-Io-Uo:Io-n[1])-((t=t.x)[0]<0?t[1]-Io-Uo:Io-t[1])}function jt(n){var t,e=NaN,r=NaN,i=NaN;return{lineStart:function(){n.lineStart(),t=1},point:function(u,o){var a=u>0?Fo:-Fo,l=xo(u-e);xo(l-Fo)0?Io:-Io),n.point(i,r),n.lineEnd(),n.lineStart(),n.point(a,r),n.point(u,r),t=0):i!==a&&l>=Fo&&(xo(e-i)Uo?Math.atan((Math.sin(t)*(u=Math.cos(r))*Math.sin(e)-Math.sin(r)*(i=Math.cos(t))*Math.sin(n))/(i*u*o)):(t+r)/2}function Ht(n,t,e,r){var i;if(null==n)i=e*Io,r.point(-Fo,i),r.point(0,i),r.point(Fo,i),r.point(Fo,0),r.point(Fo,-i),r.point(0,-i),r.point(-Fo,-i),r.point(-Fo,0),r.point(-Fo,i);else if(xo(n[0]-t[0])>Uo){var u=n[0]a;++a){var c=t[a],f=c.length;if(f)for(var s=c[0],h=s[0],p=s[1]/2+Fo/4,g=Math.sin(p),v=Math.cos(p),d=1;;){d===f&&(d=0),n=c[d];var y=n[0],m=n[1]/2+Fo/4,M=Math.sin(m),x=Math.cos(m),b=y-h,_=b>=0?1:-1,w=_*b,S=w>Fo,k=g*M;if(ka.add(Math.atan2(k*_*Math.sin(w),v*x+k*Math.cos(w))),u+=S?b+_*Ho:b,S^h>=e^y>=e){var N=mt(dt(s),dt(n));bt(N);var E=mt(i,N);bt(E);var A=(S^b>=0?-1:1)*tn(E[2]);(r>A||r===A&&(N[0]||N[1]))&&(o+=S^b>=0?1:-1)}if(!d++)break;h=y,g=M,v=x,s=n}}return(-Uo>u||Uo>u&&-Uo>ka)^1&o}function It(n){function t(n,t){return Math.cos(n)*Math.cos(t)>u}function e(n){var e,u,l,c,f;return{lineStart:function(){c=l=!1,f=1},point:function(s,h){var p,g=[s,h],v=t(s,h),d=o?v?0:i(s,h):v?i(s+(0>s?Fo:-Fo),h):0;if(!e&&(c=l=v)&&n.lineStart(),v!==l&&(p=r(e,g),(wt(e,p)||wt(g,p))&&(g[0]+=Uo,g[1]+=Uo,v=t(g[0],g[1]))),v!==l)f=0,v?(n.lineStart(),p=r(g,e),n.point(p[0],p[1])):(p=r(e,g),n.point(p[0],p[1]),n.lineEnd()),e=p;else if(a&&e&&o^v){var y;d&u||!(y=r(g,e,!0))||(f=0,o?(n.lineStart(),n.point(y[0][0],y[0][1]),n.point(y[1][0],y[1][1]),n.lineEnd()):(n.point(y[1][0],y[1][1]),n.lineEnd(),n.lineStart(),n.point(y[0][0],y[0][1])))}!v||e&&wt(e,g)||n.point(g[0],g[1]),e=g,l=v,u=d},lineEnd:function(){l&&n.lineEnd(),e=null},clean:function(){return f|(c&&l)<<1}}}function r(n,t,e){var r=dt(n),i=dt(t),o=[1,0,0],a=mt(r,i),l=yt(a,a),c=a[0],f=l-c*c;if(!f)return!e&&n;var s=u*l/f,h=-u*c/f,p=mt(o,a),g=xt(o,s),v=xt(a,h);Mt(g,v);var d=p,y=yt(g,d),m=yt(d,d),M=y*y-m*(yt(g,g)-1);if(!(0>M)){var x=Math.sqrt(M),b=xt(d,(-y-x)/m);if(Mt(b,g),b=_t(b),!e)return b;var _,w=n[0],S=t[0],k=n[1],N=t[1];w>S&&(_=w,w=S,S=_);var E=S-w,A=xo(E-Fo)E;if(!A&&k>N&&(_=k,k=N,N=_),C?A?k+N>0^b[1]<(xo(b[0]-w)Fo^(w<=b[0]&&b[0]<=S)){var z=xt(d,(-y+x)/m);return Mt(z,g),[b,_t(z)]}}}function i(t,e){var r=o?n:Fo-n,i=0;return-r>t?i|=1:t>r&&(i|=2),-r>e?i|=4:e>r&&(i|=8),i}var u=Math.cos(n),o=u>0,a=xo(u)>Uo,l=ve(n,6*Yo);return Rt(t,e,l,o?[0,-n]:[-Fo,n-Fo])}function Yt(n,t,e,r){return function(i){var u,o=i.a,a=i.b,l=o.x,c=o.y,f=a.x,s=a.y,h=0,p=1,g=f-l,v=s-c;if(u=n-l,g||!(u>0)){if(u/=g,0>g){if(h>u)return;p>u&&(p=u)}else if(g>0){if(u>p)return;u>h&&(h=u)}if(u=e-l,g||!(0>u)){if(u/=g,0>g){if(u>p)return;u>h&&(h=u)}else if(g>0){if(h>u)return;p>u&&(p=u)}if(u=t-c,v||!(u>0)){if(u/=v,0>v){if(h>u)return;p>u&&(p=u)}else if(v>0){if(u>p)return;u>h&&(h=u)}if(u=r-c,v||!(0>u)){if(u/=v,0>v){if(u>p)return;u>h&&(h=u)}else if(v>0){if(h>u)return;p>u&&(p=u)}return h>0&&(i.a={x:l+h*g,y:c+h*v}),1>p&&(i.b={x:l+p*g,y:c+p*v}),i}}}}}}function Zt(n,t,e,r){function i(r,i){return xo(r[0]-n)0?0:3:xo(r[0]-e)0?2:1:xo(r[1]-t)0?1:0:i>0?3:2}function u(n,t){return o(n.x,t.x)}function o(n,t){var e=i(n,1),r=i(t,1);return e!==r?e-r:0===e?t[1]-n[1]:1===e?n[0]-t[0]:2===e?n[1]-t[1]:t[0]-n[0]}return function(a){function l(n){for(var t=0,e=d.length,r=n[1],i=0;e>i;++i)for(var u,o=1,a=d[i],l=a.length,c=a[0];l>o;++o)u=a[o],c[1]<=r?u[1]>r&&Q(c,u,n)>0&&++t:u[1]<=r&&Q(c,u,n)<0&&--t,c=u;return 0!==t}function c(u,a,l,c){var f=0,s=0;if(null==u||(f=i(u,l))!==(s=i(a,l))||o(u,a)<0^l>0){do c.point(0===f||3===f?n:e,f>1?r:t);while((f=(f+l+4)%4)!==s)}else c.point(a[0],a[1])}function f(i,u){return i>=n&&e>=i&&u>=t&&r>=u}function s(n,t){f(n,t)&&a.point(n,t)}function h(){C.point=g,d&&d.push(y=[]),S=!0,w=!1,b=_=NaN}function p(){v&&(g(m,M),x&&w&&E.rejoin(),v.push(E.buffer())),C.point=s,w&&a.lineEnd()}function g(n,t){n=Math.max(-Ha,Math.min(Ha,n)),t=Math.max(-Ha,Math.min(Ha,t));var e=f(n,t);if(d&&y.push([n,t]),S)m=n,M=t,x=e,S=!1,e&&(a.lineStart(),a.point(n,t));else if(e&&w)a.point(n,t);else{var r={a:{x:b,y:_},b:{x:n,y:t}};A(r)?(w||(a.lineStart(),a.point(r.a.x,r.a.y)),a.point(r.b.x,r.b.y),e||a.lineEnd(),k=!1):e&&(a.lineStart(),a.point(n,t),k=!1)}b=n,_=t,w=e}var v,d,y,m,M,x,b,_,w,S,k,N=a,E=Pt(),A=Yt(n,t,e,r),C={point:s,lineStart:h,lineEnd:p,polygonStart:function(){a=E,v=[],d=[],k=!0},polygonEnd:function(){a=N,v=ao.merge(v);var t=l([n,r]),e=k&&t,i=v.length;(e||i)&&(a.polygonStart(),e&&(a.lineStart(),c(null,null,1,a),a.lineEnd()),i&&Lt(v,u,t,c,a),a.polygonEnd()),v=d=y=null}};return C}}function Vt(n){var t=0,e=Fo/3,r=ae(n),i=r(t,e);return i.parallels=function(n){return arguments.length?r(t=n[0]*Fo/180,e=n[1]*Fo/180):[t/Fo*180,e/Fo*180]},i}function Xt(n,t){function e(n,t){var e=Math.sqrt(u-2*i*Math.sin(t))/i;return[e*Math.sin(n*=i),o-e*Math.cos(n)]}var r=Math.sin(n),i=(r+Math.sin(t))/2,u=1+r*(2*i-r),o=Math.sqrt(u)/i;return e.invert=function(n,t){var e=o-t;return[Math.atan2(n,e)/i,tn((u-(n*n+e*e)*i*i)/(2*i))]},e}function $t(){function n(n,t){Ia+=i*n-r*t,r=n,i=t}var t,e,r,i;$a.point=function(u,o){$a.point=n,t=r=u,e=i=o},$a.lineEnd=function(){n(t,e)}}function Bt(n,t){Ya>n&&(Ya=n),n>Va&&(Va=n),Za>t&&(Za=t),t>Xa&&(Xa=t)}function Wt(){function n(n,t){o.push("M",n,",",t,u)}function t(n,t){o.push("M",n,",",t),a.point=e}function e(n,t){o.push("L",n,",",t)}function r(){a.point=n}function i(){o.push("Z")}var u=Jt(4.5),o=[],a={point:n,lineStart:function(){a.point=t},lineEnd:r,polygonStart:function(){a.lineEnd=i},polygonEnd:function(){a.lineEnd=r,a.point=n},pointRadius:function(n){return u=Jt(n),a},result:function(){if(o.length){var n=o.join("");return o=[],n}}};return a}function Jt(n){return"m0,"+n+"a"+n+","+n+" 0 1,1 0,"+-2*n+"a"+n+","+n+" 0 1,1 0,"+2*n+"z"}function Gt(n,t){Ca+=n,za+=t,++La}function Kt(){function n(n,r){var i=n-t,u=r-e,o=Math.sqrt(i*i+u*u);qa+=o*(t+n)/2,Ta+=o*(e+r)/2,Ra+=o,Gt(t=n,e=r)}var t,e;Wa.point=function(r,i){Wa.point=n,Gt(t=r,e=i)}}function Qt(){Wa.point=Gt}function ne(){function n(n,t){var e=n-r,u=t-i,o=Math.sqrt(e*e+u*u);qa+=o*(r+n)/2,Ta+=o*(i+t)/2,Ra+=o,o=i*n-r*t,Da+=o*(r+n),Pa+=o*(i+t),Ua+=3*o,Gt(r=n,i=t)}var t,e,r,i;Wa.point=function(u,o){Wa.point=n,Gt(t=r=u,e=i=o)},Wa.lineEnd=function(){n(t,e)}}function te(n){function t(t,e){n.moveTo(t+o,e),n.arc(t,e,o,0,Ho)}function e(t,e){n.moveTo(t,e),a.point=r}function r(t,e){n.lineTo(t,e)}function i(){a.point=t}function u(){n.closePath()}var o=4.5,a={point:t,lineStart:function(){a.point=e},lineEnd:i,polygonStart:function(){a.lineEnd=u},polygonEnd:function(){a.lineEnd=i,a.point=t},pointRadius:function(n){return o=n,a},result:b};return a}function ee(n){function t(n){return(a?r:e)(n)}function e(t){return ue(t,function(e,r){e=n(e,r),t.point(e[0],e[1])})}function r(t){function e(e,r){e=n(e,r),t.point(e[0],e[1])}function r(){M=NaN,S.point=u,t.lineStart()}function u(e,r){var u=dt([e,r]),o=n(e,r);i(M,x,m,b,_,w,M=o[0],x=o[1],m=e,b=u[0],_=u[1],w=u[2],a,t),t.point(M,x)}function o(){S.point=e,t.lineEnd()}function l(){ +r(),S.point=c,S.lineEnd=f}function c(n,t){u(s=n,h=t),p=M,g=x,v=b,d=_,y=w,S.point=u}function f(){i(M,x,m,b,_,w,p,g,s,v,d,y,a,t),S.lineEnd=o,o()}var s,h,p,g,v,d,y,m,M,x,b,_,w,S={point:e,lineStart:r,lineEnd:o,polygonStart:function(){t.polygonStart(),S.lineStart=l},polygonEnd:function(){t.polygonEnd(),S.lineStart=r}};return S}function i(t,e,r,a,l,c,f,s,h,p,g,v,d,y){var m=f-t,M=s-e,x=m*m+M*M;if(x>4*u&&d--){var b=a+p,_=l+g,w=c+v,S=Math.sqrt(b*b+_*_+w*w),k=Math.asin(w/=S),N=xo(xo(w)-1)u||xo((m*z+M*L)/x-.5)>.3||o>a*p+l*g+c*v)&&(i(t,e,r,a,l,c,A,C,N,b/=S,_/=S,w,d,y),y.point(A,C),i(A,C,N,b,_,w,f,s,h,p,g,v,d,y))}}var u=.5,o=Math.cos(30*Yo),a=16;return t.precision=function(n){return arguments.length?(a=(u=n*n)>0&&16,t):Math.sqrt(u)},t}function re(n){var t=ee(function(t,e){return n([t*Zo,e*Zo])});return function(n){return le(t(n))}}function ie(n){this.stream=n}function ue(n,t){return{point:t,sphere:function(){n.sphere()},lineStart:function(){n.lineStart()},lineEnd:function(){n.lineEnd()},polygonStart:function(){n.polygonStart()},polygonEnd:function(){n.polygonEnd()}}}function oe(n){return ae(function(){return n})()}function ae(n){function t(n){return n=a(n[0]*Yo,n[1]*Yo),[n[0]*h+l,c-n[1]*h]}function e(n){return n=a.invert((n[0]-l)/h,(c-n[1])/h),n&&[n[0]*Zo,n[1]*Zo]}function r(){a=Ct(o=se(y,M,x),u);var n=u(v,d);return l=p-n[0]*h,c=g+n[1]*h,i()}function i(){return f&&(f.valid=!1,f=null),t}var u,o,a,l,c,f,s=ee(function(n,t){return n=u(n,t),[n[0]*h+l,c-n[1]*h]}),h=150,p=480,g=250,v=0,d=0,y=0,M=0,x=0,b=Fa,_=m,w=null,S=null;return t.stream=function(n){return f&&(f.valid=!1),f=le(b(o,s(_(n)))),f.valid=!0,f},t.clipAngle=function(n){return arguments.length?(b=null==n?(w=n,Fa):It((w=+n)*Yo),i()):w},t.clipExtent=function(n){return arguments.length?(S=n,_=n?Zt(n[0][0],n[0][1],n[1][0],n[1][1]):m,i()):S},t.scale=function(n){return arguments.length?(h=+n,r()):h},t.translate=function(n){return arguments.length?(p=+n[0],g=+n[1],r()):[p,g]},t.center=function(n){return arguments.length?(v=n[0]%360*Yo,d=n[1]%360*Yo,r()):[v*Zo,d*Zo]},t.rotate=function(n){return arguments.length?(y=n[0]%360*Yo,M=n[1]%360*Yo,x=n.length>2?n[2]%360*Yo:0,r()):[y*Zo,M*Zo,x*Zo]},ao.rebind(t,s,"precision"),function(){return u=n.apply(this,arguments),t.invert=u.invert&&e,r()}}function le(n){return ue(n,function(t,e){n.point(t*Yo,e*Yo)})}function ce(n,t){return[n,t]}function fe(n,t){return[n>Fo?n-Ho:-Fo>n?n+Ho:n,t]}function se(n,t,e){return n?t||e?Ct(pe(n),ge(t,e)):pe(n):t||e?ge(t,e):fe}function he(n){return function(t,e){return t+=n,[t>Fo?t-Ho:-Fo>t?t+Ho:t,e]}}function pe(n){var t=he(n);return t.invert=he(-n),t}function ge(n,t){function e(n,t){var e=Math.cos(t),a=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),f=c*r+a*i;return[Math.atan2(l*u-f*o,a*r-c*i),tn(f*u+l*o)]}var r=Math.cos(n),i=Math.sin(n),u=Math.cos(t),o=Math.sin(t);return e.invert=function(n,t){var e=Math.cos(t),a=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),f=c*u-l*o;return[Math.atan2(l*u+c*o,a*r+f*i),tn(f*r-a*i)]},e}function ve(n,t){var e=Math.cos(n),r=Math.sin(n);return function(i,u,o,a){var l=o*t;null!=i?(i=de(e,i),u=de(e,u),(o>0?u>i:i>u)&&(i+=o*Ho)):(i=n+o*Ho,u=n-.5*l);for(var c,f=i;o>0?f>u:u>f;f-=l)a.point((c=_t([e,-r*Math.cos(f),-r*Math.sin(f)]))[0],c[1])}}function de(n,t){var e=dt(t);e[0]-=n,bt(e);var r=nn(-e[1]);return((-e[2]<0?-r:r)+2*Math.PI-Uo)%(2*Math.PI)}function ye(n,t,e){var r=ao.range(n,t-Uo,e).concat(t);return function(n){return r.map(function(t){return[n,t]})}}function me(n,t,e){var r=ao.range(n,t-Uo,e).concat(t);return function(n){return r.map(function(t){return[t,n]})}}function Me(n){return n.source}function xe(n){return n.target}function be(n,t,e,r){var i=Math.cos(t),u=Math.sin(t),o=Math.cos(r),a=Math.sin(r),l=i*Math.cos(n),c=i*Math.sin(n),f=o*Math.cos(e),s=o*Math.sin(e),h=2*Math.asin(Math.sqrt(on(r-t)+i*o*on(e-n))),p=1/Math.sin(h),g=h?function(n){var t=Math.sin(n*=h)*p,e=Math.sin(h-n)*p,r=e*l+t*f,i=e*c+t*s,o=e*u+t*a;return[Math.atan2(i,r)*Zo,Math.atan2(o,Math.sqrt(r*r+i*i))*Zo]}:function(){return[n*Zo,t*Zo]};return g.distance=h,g}function _e(){function n(n,i){var u=Math.sin(i*=Yo),o=Math.cos(i),a=xo((n*=Yo)-t),l=Math.cos(a);Ja+=Math.atan2(Math.sqrt((a=o*Math.sin(a))*a+(a=r*u-e*o*l)*a),e*u+r*o*l),t=n,e=u,r=o}var t,e,r;Ga.point=function(i,u){t=i*Yo,e=Math.sin(u*=Yo),r=Math.cos(u),Ga.point=n},Ga.lineEnd=function(){Ga.point=Ga.lineEnd=b}}function we(n,t){function e(t,e){var r=Math.cos(t),i=Math.cos(e),u=n(r*i);return[u*i*Math.sin(t),u*Math.sin(e)]}return e.invert=function(n,e){var r=Math.sqrt(n*n+e*e),i=t(r),u=Math.sin(i),o=Math.cos(i);return[Math.atan2(n*u,r*o),Math.asin(r&&e*u/r)]},e}function Se(n,t){function e(n,t){o>0?-Io+Uo>t&&(t=-Io+Uo):t>Io-Uo&&(t=Io-Uo);var e=o/Math.pow(i(t),u);return[e*Math.sin(u*n),o-e*Math.cos(u*n)]}var r=Math.cos(n),i=function(n){return Math.tan(Fo/4+n/2)},u=n===t?Math.sin(n):Math.log(r/Math.cos(t))/Math.log(i(t)/i(n)),o=r*Math.pow(i(n),u)/u;return u?(e.invert=function(n,t){var e=o-t,r=K(u)*Math.sqrt(n*n+e*e);return[Math.atan2(n,e)/u,2*Math.atan(Math.pow(o/r,1/u))-Io]},e):Ne}function ke(n,t){function e(n,t){var e=u-t;return[e*Math.sin(i*n),u-e*Math.cos(i*n)]}var r=Math.cos(n),i=n===t?Math.sin(n):(r-Math.cos(t))/(t-n),u=r/i+n;return xo(i)i;i++){for(;r>1&&Q(n[e[r-2]],n[e[r-1]],n[i])<=0;)--r;e[r++]=i}return e.slice(0,r)}function qe(n,t){return n[0]-t[0]||n[1]-t[1]}function Te(n,t,e){return(e[0]-t[0])*(n[1]-t[1])<(e[1]-t[1])*(n[0]-t[0])}function Re(n,t,e,r){var i=n[0],u=e[0],o=t[0]-i,a=r[0]-u,l=n[1],c=e[1],f=t[1]-l,s=r[1]-c,h=(a*(l-c)-s*(i-u))/(s*o-a*f);return[i+h*o,l+h*f]}function De(n){var t=n[0],e=n[n.length-1];return!(t[0]-e[0]||t[1]-e[1])}function Pe(){rr(this),this.edge=this.site=this.circle=null}function Ue(n){var t=cl.pop()||new Pe;return t.site=n,t}function je(n){Be(n),ol.remove(n),cl.push(n),rr(n)}function Fe(n){var t=n.circle,e=t.x,r=t.cy,i={x:e,y:r},u=n.P,o=n.N,a=[n];je(n);for(var l=u;l.circle&&xo(e-l.circle.x)f;++f)c=a[f],l=a[f-1],nr(c.edge,l.site,c.site,i);l=a[0],c=a[s-1],c.edge=Ke(l.site,c.site,null,i),$e(l),$e(c)}function He(n){for(var t,e,r,i,u=n.x,o=n.y,a=ol._;a;)if(r=Oe(a,o)-u,r>Uo)a=a.L;else{if(i=u-Ie(a,o),!(i>Uo)){r>-Uo?(t=a.P,e=a):i>-Uo?(t=a,e=a.N):t=e=a;break}if(!a.R){t=a;break}a=a.R}var l=Ue(n);if(ol.insert(t,l),t||e){if(t===e)return Be(t),e=Ue(t.site),ol.insert(l,e),l.edge=e.edge=Ke(t.site,l.site),$e(t),void $e(e);if(!e)return void(l.edge=Ke(t.site,l.site));Be(t),Be(e);var c=t.site,f=c.x,s=c.y,h=n.x-f,p=n.y-s,g=e.site,v=g.x-f,d=g.y-s,y=2*(h*d-p*v),m=h*h+p*p,M=v*v+d*d,x={x:(d*m-p*M)/y+f,y:(h*M-v*m)/y+s};nr(e.edge,c,g,x),l.edge=Ke(c,n,null,x),e.edge=Ke(n,g,null,x),$e(t),$e(e)}}function Oe(n,t){var e=n.site,r=e.x,i=e.y,u=i-t;if(!u)return r;var o=n.P;if(!o)return-(1/0);e=o.site;var a=e.x,l=e.y,c=l-t;if(!c)return a;var f=a-r,s=1/u-1/c,h=f/c;return s?(-h+Math.sqrt(h*h-2*s*(f*f/(-2*c)-l+c/2+i-u/2)))/s+r:(r+a)/2}function Ie(n,t){var e=n.N;if(e)return Oe(e,t);var r=n.site;return r.y===t?r.x:1/0}function Ye(n){this.site=n,this.edges=[]}function Ze(n){for(var t,e,r,i,u,o,a,l,c,f,s=n[0][0],h=n[1][0],p=n[0][1],g=n[1][1],v=ul,d=v.length;d--;)if(u=v[d],u&&u.prepare())for(a=u.edges,l=a.length,o=0;l>o;)f=a[o].end(),r=f.x,i=f.y,c=a[++o%l].start(),t=c.x,e=c.y,(xo(r-t)>Uo||xo(i-e)>Uo)&&(a.splice(o,0,new tr(Qe(u.site,f,xo(r-s)Uo?{x:s,y:xo(t-s)Uo?{x:xo(e-g)Uo?{x:h,y:xo(t-h)Uo?{x:xo(e-p)=-jo)){var p=l*l+c*c,g=f*f+s*s,v=(s*p-c*g)/h,d=(l*g-f*p)/h,s=d+a,y=fl.pop()||new Xe;y.arc=n,y.site=i,y.x=v+o,y.y=s+Math.sqrt(v*v+d*d),y.cy=s,n.circle=y;for(var m=null,M=ll._;M;)if(y.yd||d>=a)return;if(h>g){if(u){if(u.y>=c)return}else u={x:d,y:l};e={x:d,y:c}}else{if(u){if(u.yr||r>1)if(h>g){if(u){if(u.y>=c)return}else u={x:(l-i)/r,y:l};e={x:(c-i)/r,y:c}}else{if(u){if(u.yp){if(u){if(u.x>=a)return}else u={x:o,y:r*o+i};e={x:a,y:r*a+i}}else{if(u){if(u.xu||s>o||r>h||i>p)){if(g=n.point){var g,v=t-n.x,d=e-n.y,y=v*v+d*d;if(l>y){var m=Math.sqrt(l=y);r=t-m,i=e-m,u=t+m,o=e+m,a=g}}for(var M=n.nodes,x=.5*(f+h),b=.5*(s+p),_=t>=x,w=e>=b,S=w<<1|_,k=S+4;k>S;++S)if(n=M[3&S])switch(3&S){case 0:c(n,f,s,x,b);break;case 1:c(n,x,s,h,b);break;case 2:c(n,f,b,x,p);break;case 3:c(n,x,b,h,p)}}}(n,r,i,u,o),a}function vr(n,t){n=ao.rgb(n),t=ao.rgb(t);var e=n.r,r=n.g,i=n.b,u=t.r-e,o=t.g-r,a=t.b-i;return function(n){return"#"+bn(Math.round(e+u*n))+bn(Math.round(r+o*n))+bn(Math.round(i+a*n))}}function dr(n,t){var e,r={},i={};for(e in n)e in t?r[e]=Mr(n[e],t[e]):i[e]=n[e];for(e in t)e in n||(i[e]=t[e]);return function(n){for(e in r)i[e]=r[e](n);return i}}function yr(n,t){return n=+n,t=+t,function(e){return n*(1-e)+t*e}}function mr(n,t){var e,r,i,u=hl.lastIndex=pl.lastIndex=0,o=-1,a=[],l=[];for(n+="",t+="";(e=hl.exec(n))&&(r=pl.exec(t));)(i=r.index)>u&&(i=t.slice(u,i),a[o]?a[o]+=i:a[++o]=i),(e=e[0])===(r=r[0])?a[o]?a[o]+=r:a[++o]=r:(a[++o]=null,l.push({i:o,x:yr(e,r)})),u=pl.lastIndex;return ur;++r)a[(e=l[r]).i]=e.x(n);return a.join("")})}function Mr(n,t){for(var e,r=ao.interpolators.length;--r>=0&&!(e=ao.interpolators[r](n,t)););return e}function xr(n,t){var e,r=[],i=[],u=n.length,o=t.length,a=Math.min(n.length,t.length);for(e=0;a>e;++e)r.push(Mr(n[e],t[e]));for(;u>e;++e)i[e]=n[e];for(;o>e;++e)i[e]=t[e];return function(n){for(e=0;a>e;++e)i[e]=r[e](n);return i}}function br(n){return function(t){return 0>=t?0:t>=1?1:n(t)}}function _r(n){return function(t){return 1-n(1-t)}}function wr(n){return function(t){return.5*(.5>t?n(2*t):2-n(2-2*t))}}function Sr(n){return n*n}function kr(n){return n*n*n}function Nr(n){if(0>=n)return 0;if(n>=1)return 1;var t=n*n,e=t*n;return 4*(.5>n?e:3*(n-t)+e-.75)}function Er(n){return function(t){return Math.pow(t,n)}}function Ar(n){return 1-Math.cos(n*Io)}function Cr(n){return Math.pow(2,10*(n-1))}function zr(n){return 1-Math.sqrt(1-n*n)}function Lr(n,t){var e;return arguments.length<2&&(t=.45),arguments.length?e=t/Ho*Math.asin(1/n):(n=1,e=t/4),function(r){return 1+n*Math.pow(2,-10*r)*Math.sin((r-e)*Ho/t)}}function qr(n){return n||(n=1.70158),function(t){return t*t*((n+1)*t-n)}}function Tr(n){return 1/2.75>n?7.5625*n*n:2/2.75>n?7.5625*(n-=1.5/2.75)*n+.75:2.5/2.75>n?7.5625*(n-=2.25/2.75)*n+.9375:7.5625*(n-=2.625/2.75)*n+.984375}function Rr(n,t){n=ao.hcl(n),t=ao.hcl(t);var e=n.h,r=n.c,i=n.l,u=t.h-e,o=t.c-r,a=t.l-i;return isNaN(o)&&(o=0,r=isNaN(r)?t.c:r),isNaN(u)?(u=0,e=isNaN(e)?t.h:e):u>180?u-=360:-180>u&&(u+=360),function(n){return sn(e+u*n,r+o*n,i+a*n)+""}}function Dr(n,t){n=ao.hsl(n),t=ao.hsl(t);var e=n.h,r=n.s,i=n.l,u=t.h-e,o=t.s-r,a=t.l-i;return isNaN(o)&&(o=0,r=isNaN(r)?t.s:r),isNaN(u)?(u=0,e=isNaN(e)?t.h:e):u>180?u-=360:-180>u&&(u+=360),function(n){return cn(e+u*n,r+o*n,i+a*n)+""}}function Pr(n,t){n=ao.lab(n),t=ao.lab(t);var e=n.l,r=n.a,i=n.b,u=t.l-e,o=t.a-r,a=t.b-i;return function(n){return pn(e+u*n,r+o*n,i+a*n)+""}}function Ur(n,t){return t-=n,function(e){return Math.round(n+t*e)}}function jr(n){var t=[n.a,n.b],e=[n.c,n.d],r=Hr(t),i=Fr(t,e),u=Hr(Or(e,t,-i))||0;t[0]*e[1]180?t+=360:t-n>180&&(n+=360),r.push({i:e.push(Ir(e)+"rotate(",null,")")-2,x:yr(n,t)})):t&&e.push(Ir(e)+"rotate("+t+")")}function Vr(n,t,e,r){n!==t?r.push({i:e.push(Ir(e)+"skewX(",null,")")-2,x:yr(n,t)}):t&&e.push(Ir(e)+"skewX("+t+")")}function Xr(n,t,e,r){if(n[0]!==t[0]||n[1]!==t[1]){var i=e.push(Ir(e)+"scale(",null,",",null,")");r.push({i:i-4,x:yr(n[0],t[0])},{i:i-2,x:yr(n[1],t[1])})}else 1===t[0]&&1===t[1]||e.push(Ir(e)+"scale("+t+")")}function $r(n,t){var e=[],r=[];return n=ao.transform(n),t=ao.transform(t),Yr(n.translate,t.translate,e,r),Zr(n.rotate,t.rotate,e,r),Vr(n.skew,t.skew,e,r),Xr(n.scale,t.scale,e,r),n=t=null,function(n){for(var t,i=-1,u=r.length;++i=0;)e.push(i[r])}function oi(n,t){for(var e=[n],r=[];null!=(n=e.pop());)if(r.push(n),(u=n.children)&&(i=u.length))for(var i,u,o=-1;++oe;++e)(t=n[e][1])>i&&(r=e,i=t);return r}function yi(n){return n.reduce(mi,0)}function mi(n,t){return n+t[1]}function Mi(n,t){return xi(n,Math.ceil(Math.log(t.length)/Math.LN2+1))}function xi(n,t){for(var e=-1,r=+n[0],i=(n[1]-r)/t,u=[];++e<=t;)u[e]=i*e+r;return u}function bi(n){return[ao.min(n),ao.max(n)]}function _i(n,t){return n.value-t.value}function wi(n,t){var e=n._pack_next;n._pack_next=t,t._pack_prev=n,t._pack_next=e,e._pack_prev=t}function Si(n,t){n._pack_next=t,t._pack_prev=n}function ki(n,t){var e=t.x-n.x,r=t.y-n.y,i=n.r+t.r;return.999*i*i>e*e+r*r}function Ni(n){function t(n){f=Math.min(n.x-n.r,f),s=Math.max(n.x+n.r,s),h=Math.min(n.y-n.r,h),p=Math.max(n.y+n.r,p)}if((e=n.children)&&(c=e.length)){var e,r,i,u,o,a,l,c,f=1/0,s=-(1/0),h=1/0,p=-(1/0);if(e.forEach(Ei),r=e[0],r.x=-r.r,r.y=0,t(r),c>1&&(i=e[1],i.x=i.r,i.y=0,t(i),c>2))for(u=e[2],zi(r,i,u),t(u),wi(r,u),r._pack_prev=u,wi(u,i),i=r._pack_next,o=3;c>o;o++){zi(r,i,u=e[o]);var g=0,v=1,d=1;for(a=i._pack_next;a!==i;a=a._pack_next,v++)if(ki(a,u)){g=1;break}if(1==g)for(l=r._pack_prev;l!==a._pack_prev&&!ki(l,u);l=l._pack_prev,d++);g?(d>v||v==d&&i.ro;o++)u=e[o],u.x-=y,u.y-=m,M=Math.max(M,u.r+Math.sqrt(u.x*u.x+u.y*u.y));n.r=M,e.forEach(Ai)}}function Ei(n){n._pack_next=n._pack_prev=n}function Ai(n){delete n._pack_next,delete n._pack_prev}function Ci(n,t,e,r){var i=n.children;if(n.x=t+=r*n.x,n.y=e+=r*n.y,n.r*=r,i)for(var u=-1,o=i.length;++u=0;)t=i[u],t.z+=e,t.m+=e,e+=t.s+(r+=t.c)}function Pi(n,t,e){return n.a.parent===t.parent?n.a:e}function Ui(n){return 1+ao.max(n,function(n){return n.y})}function ji(n){return n.reduce(function(n,t){return n+t.x},0)/n.length}function Fi(n){var t=n.children;return t&&t.length?Fi(t[0]):n}function Hi(n){var t,e=n.children;return e&&(t=e.length)?Hi(e[t-1]):n}function Oi(n){return{x:n.x,y:n.y,dx:n.dx,dy:n.dy}}function Ii(n,t){var e=n.x+t[3],r=n.y+t[0],i=n.dx-t[1]-t[3],u=n.dy-t[0]-t[2];return 0>i&&(e+=i/2,i=0),0>u&&(r+=u/2,u=0),{x:e,y:r,dx:i,dy:u}}function Yi(n){var t=n[0],e=n[n.length-1];return e>t?[t,e]:[e,t]}function Zi(n){return n.rangeExtent?n.rangeExtent():Yi(n.range())}function Vi(n,t,e,r){var i=e(n[0],n[1]),u=r(t[0],t[1]);return function(n){return u(i(n))}}function Xi(n,t){var e,r=0,i=n.length-1,u=n[r],o=n[i];return u>o&&(e=r,r=i,i=e,e=u,u=o,o=e),n[r]=t.floor(u),n[i]=t.ceil(o),n}function $i(n){return n?{floor:function(t){return Math.floor(t/n)*n},ceil:function(t){return Math.ceil(t/n)*n}}:Sl}function Bi(n,t,e,r){var i=[],u=[],o=0,a=Math.min(n.length,t.length)-1;for(n[a]2?Bi:Vi,l=r?Wr:Br;return o=i(n,t,l,e),a=i(t,n,l,Mr),u}function u(n){return o(n)}var o,a;return u.invert=function(n){return a(n)},u.domain=function(t){return arguments.length?(n=t.map(Number),i()):n},u.range=function(n){return arguments.length?(t=n,i()):t},u.rangeRound=function(n){return u.range(n).interpolate(Ur)},u.clamp=function(n){return arguments.length?(r=n,i()):r},u.interpolate=function(n){return arguments.length?(e=n,i()):e},u.ticks=function(t){return Qi(n,t)},u.tickFormat=function(t,e){return nu(n,t,e)},u.nice=function(t){return Gi(n,t),i()},u.copy=function(){return Wi(n,t,e,r)},i()}function Ji(n,t){return ao.rebind(n,t,"range","rangeRound","interpolate","clamp")}function Gi(n,t){return Xi(n,$i(Ki(n,t)[2])),Xi(n,$i(Ki(n,t)[2])),n}function Ki(n,t){null==t&&(t=10);var e=Yi(n),r=e[1]-e[0],i=Math.pow(10,Math.floor(Math.log(r/t)/Math.LN10)),u=t/r*i;return.15>=u?i*=10:.35>=u?i*=5:.75>=u&&(i*=2),e[0]=Math.ceil(e[0]/i)*i,e[1]=Math.floor(e[1]/i)*i+.5*i,e[2]=i,e}function Qi(n,t){return ao.range.apply(ao,Ki(n,t))}function nu(n,t,e){var r=Ki(n,t);if(e){var i=ha.exec(e);if(i.shift(),"s"===i[8]){var u=ao.formatPrefix(Math.max(xo(r[0]),xo(r[1])));return i[7]||(i[7]="."+tu(u.scale(r[2]))),i[8]="f",e=ao.format(i.join("")),function(n){return e(u.scale(n))+u.symbol}}i[7]||(i[7]="."+eu(i[8],r)),e=i.join("")}else e=",."+tu(r[2])+"f";return ao.format(e)}function tu(n){return-Math.floor(Math.log(n)/Math.LN10+.01)}function eu(n,t){var e=tu(t[2]);return n in kl?Math.abs(e-tu(Math.max(xo(t[0]),xo(t[1]))))+ +("e"!==n):e-2*("%"===n)}function ru(n,t,e,r){function i(n){return(e?Math.log(0>n?0:n):-Math.log(n>0?0:-n))/Math.log(t)}function u(n){return e?Math.pow(t,n):-Math.pow(t,-n)}function o(t){return n(i(t))}return o.invert=function(t){return u(n.invert(t))},o.domain=function(t){return arguments.length?(e=t[0]>=0,n.domain((r=t.map(Number)).map(i)),o):r},o.base=function(e){return arguments.length?(t=+e,n.domain(r.map(i)),o):t},o.nice=function(){var t=Xi(r.map(i),e?Math:El);return n.domain(t),r=t.map(u),o},o.ticks=function(){var n=Yi(r),o=[],a=n[0],l=n[1],c=Math.floor(i(a)),f=Math.ceil(i(l)),s=t%1?2:t;if(isFinite(f-c)){if(e){for(;f>c;c++)for(var h=1;s>h;h++)o.push(u(c)*h);o.push(u(c))}else for(o.push(u(c));c++0;h--)o.push(u(c)*h);for(c=0;o[c]l;f--);o=o.slice(c,f)}return o},o.tickFormat=function(n,e){if(!arguments.length)return Nl;arguments.length<2?e=Nl:"function"!=typeof e&&(e=ao.format(e));var r=Math.max(1,t*n/o.ticks().length);return function(n){var o=n/u(Math.round(i(n)));return t-.5>o*t&&(o*=t),r>=o?e(n):""}},o.copy=function(){return ru(n.copy(),t,e,r)},Ji(o,n)}function iu(n,t,e){function r(t){return n(i(t))}var i=uu(t),u=uu(1/t);return r.invert=function(t){return u(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain((e=t.map(Number)).map(i)),r):e},r.ticks=function(n){return Qi(e,n)},r.tickFormat=function(n,t){return nu(e,n,t)},r.nice=function(n){return r.domain(Gi(e,n))},r.exponent=function(o){return arguments.length?(i=uu(t=o),u=uu(1/t),n.domain(e.map(i)),r):t},r.copy=function(){return iu(n.copy(),t,e)},Ji(r,n)}function uu(n){return function(t){return 0>t?-Math.pow(-t,n):Math.pow(t,n)}}function ou(n,t){function e(e){return u[((i.get(e)||("range"===t.t?i.set(e,n.push(e)):NaN))-1)%u.length]}function r(t,e){return ao.range(n.length).map(function(n){return t+e*n})}var i,u,o;return e.domain=function(r){if(!arguments.length)return n;n=[],i=new c;for(var u,o=-1,a=r.length;++oe?[NaN,NaN]:[e>0?a[e-1]:n[0],et?NaN:t/u+n,[t,t+1/u]},r.copy=function(){return lu(n,t,e)},i()}function cu(n,t){function e(e){return e>=e?t[ao.bisect(n,e)]:void 0}return e.domain=function(t){return arguments.length?(n=t,e):n},e.range=function(n){return arguments.length?(t=n,e):t},e.invertExtent=function(e){return e=t.indexOf(e),[n[e-1],n[e]]},e.copy=function(){return cu(n,t)},e}function fu(n){function t(n){return+n}return t.invert=t,t.domain=t.range=function(e){return arguments.length?(n=e.map(t),t):n},t.ticks=function(t){return Qi(n,t)},t.tickFormat=function(t,e){return nu(n,t,e)},t.copy=function(){return fu(n)},t}function su(){return 0}function hu(n){return n.innerRadius}function pu(n){return n.outerRadius}function gu(n){return n.startAngle}function vu(n){return n.endAngle}function du(n){return n&&n.padAngle}function yu(n,t,e,r){return(n-e)*t-(t-r)*n>0?0:1}function mu(n,t,e,r,i){var u=n[0]-t[0],o=n[1]-t[1],a=(i?r:-r)/Math.sqrt(u*u+o*o),l=a*o,c=-a*u,f=n[0]+l,s=n[1]+c,h=t[0]+l,p=t[1]+c,g=(f+h)/2,v=(s+p)/2,d=h-f,y=p-s,m=d*d+y*y,M=e-r,x=f*p-h*s,b=(0>y?-1:1)*Math.sqrt(Math.max(0,M*M*m-x*x)),_=(x*y-d*b)/m,w=(-x*d-y*b)/m,S=(x*y+d*b)/m,k=(-x*d+y*b)/m,N=_-g,E=w-v,A=S-g,C=k-v;return N*N+E*E>A*A+C*C&&(_=S,w=k),[[_-l,w-c],[_*e/M,w*e/M]]}function Mu(n){function t(t){function o(){c.push("M",u(n(f),a))}for(var l,c=[],f=[],s=-1,h=t.length,p=En(e),g=En(r);++s1?n.join("L"):n+"Z"}function bu(n){return n.join("L")+"Z"}function _u(n){for(var t=0,e=n.length,r=n[0],i=[r[0],",",r[1]];++t1&&i.push("H",r[0]),i.join("")}function wu(n){for(var t=0,e=n.length,r=n[0],i=[r[0],",",r[1]];++t1){a=t[1],u=n[l],l++,r+="C"+(i[0]+o[0])+","+(i[1]+o[1])+","+(u[0]-a[0])+","+(u[1]-a[1])+","+u[0]+","+u[1];for(var c=2;c9&&(i=3*t/Math.sqrt(i),o[a]=i*e,o[a+1]=i*r));for(a=-1;++a<=l;)i=(n[Math.min(l,a+1)][0]-n[Math.max(0,a-1)][0])/(6*(1+o[a]*o[a])),u.push([i||0,o[a]*i||0]);return u}function Fu(n){return n.length<3?xu(n):n[0]+Au(n,ju(n))}function Hu(n){for(var t,e,r,i=-1,u=n.length;++i=t?o(n-t):void(f.c=o)}function o(e){var i=g.active,u=g[i];u&&(u.timer.c=null,u.timer.t=NaN,--g.count,delete g[i],u.event&&u.event.interrupt.call(n,n.__data__,u.index));for(var o in g)if(r>+o){var c=g[o];c.timer.c=null,c.timer.t=NaN,--g.count,delete g[o]}f.c=a,qn(function(){return f.c&&a(e||1)&&(f.c=null,f.t=NaN),1},0,l),g.active=r,v.event&&v.event.start.call(n,n.__data__,t),p=[],v.tween.forEach(function(e,r){(r=r.call(n,n.__data__,t))&&p.push(r)}),h=v.ease,s=v.duration}function a(i){for(var u=i/s,o=h(u),a=p.length;a>0;)p[--a].call(n,o);return u>=1?(v.event&&v.event.end.call(n,n.__data__,t),--g.count?delete g[r]:delete n[e],1):void 0}var l,f,s,h,p,g=n[e]||(n[e]={active:0,count:0}),v=g[r];v||(l=i.time,f=qn(u,0,l),v=g[r]={tween:new c,time:l,timer:f,delay:i.delay,duration:i.duration,ease:i.ease,index:t},i=null,++g.count)}function no(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate("+(isFinite(r)?r:e(n))+",0)"})}function to(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate(0,"+(isFinite(r)?r:e(n))+")"})}function eo(n){return n.toISOString()}function ro(n,t,e){function r(t){return n(t)}function i(n,e){var r=n[1]-n[0],i=r/e,u=ao.bisect(Kl,i);return u==Kl.length?[t.year,Ki(n.map(function(n){return n/31536e6}),e)[2]]:u?t[i/Kl[u-1]1?{floor:function(t){for(;e(t=n.floor(t));)t=io(t-1);return t},ceil:function(t){for(;e(t=n.ceil(t));)t=io(+t+1);return t}}:n))},r.ticks=function(n,t){var e=Yi(r.domain()),u=null==n?i(e,10):"number"==typeof n?i(e,n):!n.range&&[{range:n},t];return u&&(n=u[0],t=u[1]),n.range(e[0],io(+e[1]+1),1>t?1:t)},r.tickFormat=function(){return e},r.copy=function(){return ro(n.copy(),t,e)},Ji(r,n)}function io(n){return new Date(n)}function uo(n){return JSON.parse(n.responseText)}function oo(n){var t=fo.createRange();return t.selectNode(fo.body),t.createContextualFragment(n.responseText)}var ao={version:"3.5.17"},lo=[].slice,co=function(n){return lo.call(n)},fo=this.document;if(fo)try{co(fo.documentElement.childNodes)[0].nodeType}catch(so){co=function(n){for(var t=n.length,e=new Array(t);t--;)e[t]=n[t];return e}}if(Date.now||(Date.now=function(){return+new Date}),fo)try{fo.createElement("DIV").style.setProperty("opacity",0,"")}catch(ho){var po=this.Element.prototype,go=po.setAttribute,vo=po.setAttributeNS,yo=this.CSSStyleDeclaration.prototype,mo=yo.setProperty;po.setAttribute=function(n,t){go.call(this,n,t+"")},po.setAttributeNS=function(n,t,e){vo.call(this,n,t,e+"")},yo.setProperty=function(n,t,e){mo.call(this,n,t+"",e)}}ao.ascending=e,ao.descending=function(n,t){return n>t?-1:t>n?1:t>=n?0:NaN},ao.min=function(n,t){var e,r,i=-1,u=n.length;if(1===arguments.length){for(;++i=r){e=r;break}for(;++ir&&(e=r)}else{for(;++i=r){e=r;break}for(;++ir&&(e=r)}return e},ao.max=function(n,t){var e,r,i=-1,u=n.length;if(1===arguments.length){for(;++i=r){e=r;break}for(;++ie&&(e=r)}else{for(;++i=r){e=r;break}for(;++ie&&(e=r)}return e},ao.extent=function(n,t){var e,r,i,u=-1,o=n.length;if(1===arguments.length){for(;++u=r){e=i=r;break}for(;++ur&&(e=r),r>i&&(i=r))}else{for(;++u=r){e=i=r;break}for(;++ur&&(e=r),r>i&&(i=r))}return[e,i]},ao.sum=function(n,t){var e,r=0,u=n.length,o=-1;if(1===arguments.length)for(;++o1?l/(f-1):void 0},ao.deviation=function(){var n=ao.variance.apply(this,arguments);return n?Math.sqrt(n):n};var Mo=u(e);ao.bisectLeft=Mo.left,ao.bisect=ao.bisectRight=Mo.right,ao.bisector=function(n){return u(1===n.length?function(t,r){return e(n(t),r)}:n)},ao.shuffle=function(n,t,e){(u=arguments.length)<3&&(e=n.length,2>u&&(t=0));for(var r,i,u=e-t;u;)i=Math.random()*u--|0,r=n[u+t],n[u+t]=n[i+t],n[i+t]=r;return n},ao.permute=function(n,t){for(var e=t.length,r=new Array(e);e--;)r[e]=n[t[e]];return r},ao.pairs=function(n){for(var t,e=0,r=n.length-1,i=n[0],u=new Array(0>r?0:r);r>e;)u[e]=[t=i,i=n[++e]];return u},ao.transpose=function(n){if(!(i=n.length))return[];for(var t=-1,e=ao.min(n,o),r=new Array(e);++t=0;)for(r=n[i],t=r.length;--t>=0;)e[--o]=r[t];return e};var xo=Math.abs;ao.range=function(n,t,e){if(arguments.length<3&&(e=1,arguments.length<2&&(t=n,n=0)),(t-n)/e===1/0)throw new Error("infinite range");var r,i=[],u=a(xo(e)),o=-1;if(n*=u,t*=u,e*=u,0>e)for(;(r=n+e*++o)>t;)i.push(r/u);else for(;(r=n+e*++o)=u.length)return r?r.call(i,o):e?o.sort(e):o;for(var l,f,s,h,p=-1,g=o.length,v=u[a++],d=new c;++p=u.length)return n;var r=[],i=o[e++];return n.forEach(function(n,i){r.push({key:n,values:t(i,e)})}),i?r.sort(function(n,t){return i(n.key,t.key)}):r}var e,r,i={},u=[],o=[];return i.map=function(t,e){return n(e,t,0)},i.entries=function(e){return t(n(ao.map,e,0),0)},i.key=function(n){return u.push(n),i},i.sortKeys=function(n){return o[u.length-1]=n,i},i.sortValues=function(n){return e=n,i},i.rollup=function(n){return r=n,i},i},ao.set=function(n){var t=new y;if(n)for(var e=0,r=n.length;r>e;++e)t.add(n[e]);return t},l(y,{has:h,add:function(n){return this._[f(n+="")]=!0,n},remove:p,values:g,size:v,empty:d,forEach:function(n){for(var t in this._)n.call(this,s(t))}}),ao.behavior={},ao.rebind=function(n,t){for(var e,r=1,i=arguments.length;++r=0&&(r=n.slice(e+1),n=n.slice(0,e)),n)return arguments.length<2?this[n].on(r):this[n].on(r,t);if(2===arguments.length){if(null==t)for(n in this)this.hasOwnProperty(n)&&this[n].on(r,null);return this}},ao.event=null,ao.requote=function(n){return n.replace(So,"\\$&")};var So=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g,ko={}.__proto__?function(n,t){n.__proto__=t}:function(n,t){for(var e in t)n[e]=t[e]},No=function(n,t){return t.querySelector(n)},Eo=function(n,t){return t.querySelectorAll(n)},Ao=function(n,t){var e=n.matches||n[x(n,"matchesSelector")];return(Ao=function(n,t){return e.call(n,t)})(n,t)};"function"==typeof Sizzle&&(No=function(n,t){return Sizzle(n,t)[0]||null},Eo=Sizzle,Ao=Sizzle.matchesSelector),ao.selection=function(){return ao.select(fo.documentElement)};var Co=ao.selection.prototype=[];Co.select=function(n){var t,e,r,i,u=[];n=A(n);for(var o=-1,a=this.length;++o=0&&"xmlns"!==(e=n.slice(0,t))&&(n=n.slice(t+1)),Lo.hasOwnProperty(e)?{space:Lo[e],local:n}:n}},Co.attr=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node();return n=ao.ns.qualify(n),n.local?e.getAttributeNS(n.space,n.local):e.getAttribute(n)}for(t in n)this.each(z(t,n[t]));return this}return this.each(z(n,t))},Co.classed=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node(),r=(n=T(n)).length,i=-1;if(t=e.classList){for(;++ii){if("string"!=typeof n){2>i&&(e="");for(r in n)this.each(P(r,n[r],e));return this}if(2>i){var u=this.node();return t(u).getComputedStyle(u,null).getPropertyValue(n)}r=""}return this.each(P(n,e,r))},Co.property=function(n,t){if(arguments.length<2){if("string"==typeof n)return this.node()[n];for(t in n)this.each(U(t,n[t]));return this}return this.each(U(n,t))},Co.text=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.textContent=null==t?"":t}:null==n?function(){this.textContent=""}:function(){this.textContent=n}):this.node().textContent},Co.html=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.innerHTML=null==t?"":t}:null==n?function(){this.innerHTML=""}:function(){this.innerHTML=n}):this.node().innerHTML},Co.append=function(n){return n=j(n),this.select(function(){return this.appendChild(n.apply(this,arguments))})},Co.insert=function(n,t){return n=j(n),t=A(t),this.select(function(){return this.insertBefore(n.apply(this,arguments),t.apply(this,arguments)||null)})},Co.remove=function(){return this.each(F)},Co.data=function(n,t){function e(n,e){var r,i,u,o=n.length,s=e.length,h=Math.min(o,s),p=new Array(s),g=new Array(s),v=new Array(o);if(t){var d,y=new c,m=new Array(o);for(r=-1;++rr;++r)g[r]=H(e[r]);for(;o>r;++r)v[r]=n[r]}g.update=p,g.parentNode=p.parentNode=v.parentNode=n.parentNode,a.push(g),l.push(p),f.push(v)}var r,i,u=-1,o=this.length;if(!arguments.length){for(n=new Array(o=(r=this[0]).length);++uu;u++){i.push(t=[]),t.parentNode=(e=this[u]).parentNode;for(var a=0,l=e.length;l>a;a++)(r=e[a])&&n.call(r,r.__data__,a,u)&&t.push(r)}return E(i)},Co.order=function(){for(var n=-1,t=this.length;++n=0;)(e=r[i])&&(u&&u!==e.nextSibling&&u.parentNode.insertBefore(e,u),u=e);return this},Co.sort=function(n){n=I.apply(this,arguments);for(var t=-1,e=this.length;++tn;n++)for(var e=this[n],r=0,i=e.length;i>r;r++){var u=e[r];if(u)return u}return null},Co.size=function(){var n=0;return Y(this,function(){++n}),n};var qo=[];ao.selection.enter=Z,ao.selection.enter.prototype=qo,qo.append=Co.append,qo.empty=Co.empty,qo.node=Co.node,qo.call=Co.call,qo.size=Co.size,qo.select=function(n){for(var t,e,r,i,u,o=[],a=-1,l=this.length;++ar){if("string"!=typeof n){2>r&&(t=!1);for(e in n)this.each(X(e,n[e],t));return this}if(2>r)return(r=this.node()["__on"+n])&&r._;e=!1}return this.each(X(n,t,e))};var To=ao.map({mouseenter:"mouseover",mouseleave:"mouseout"});fo&&To.forEach(function(n){"on"+n in fo&&To.remove(n)});var Ro,Do=0;ao.mouse=function(n){return J(n,k())};var Po=this.navigator&&/WebKit/.test(this.navigator.userAgent)?-1:0;ao.touch=function(n,t,e){if(arguments.length<3&&(e=t,t=k().changedTouches),t)for(var r,i=0,u=t.length;u>i;++i)if((r=t[i]).identifier===e)return J(n,r)},ao.behavior.drag=function(){function n(){this.on("mousedown.drag",u).on("touchstart.drag",o)}function e(n,t,e,u,o){return function(){function a(){var n,e,r=t(h,v);r&&(n=r[0]-M[0],e=r[1]-M[1],g|=n|e,M=r,p({type:"drag",x:r[0]+c[0],y:r[1]+c[1],dx:n,dy:e}))}function l(){t(h,v)&&(y.on(u+d,null).on(o+d,null),m(g),p({type:"dragend"}))}var c,f=this,s=ao.event.target.correspondingElement||ao.event.target,h=f.parentNode,p=r.of(f,arguments),g=0,v=n(),d=".drag"+(null==v?"":"-"+v),y=ao.select(e(s)).on(u+d,a).on(o+d,l),m=W(s),M=t(h,v);i?(c=i.apply(f,arguments),c=[c.x-M[0],c.y-M[1]]):c=[0,0],p({type:"dragstart"})}}var r=N(n,"drag","dragstart","dragend"),i=null,u=e(b,ao.mouse,t,"mousemove","mouseup"),o=e(G,ao.touch,m,"touchmove","touchend");return n.origin=function(t){return arguments.length?(i=t,n):i},ao.rebind(n,r,"on")},ao.touches=function(n,t){return arguments.length<2&&(t=k().touches),t?co(t).map(function(t){var e=J(n,t);return e.identifier=t.identifier,e}):[]};var Uo=1e-6,jo=Uo*Uo,Fo=Math.PI,Ho=2*Fo,Oo=Ho-Uo,Io=Fo/2,Yo=Fo/180,Zo=180/Fo,Vo=Math.SQRT2,Xo=2,$o=4;ao.interpolateZoom=function(n,t){var e,r,i=n[0],u=n[1],o=n[2],a=t[0],l=t[1],c=t[2],f=a-i,s=l-u,h=f*f+s*s;if(jo>h)r=Math.log(c/o)/Vo,e=function(n){return[i+n*f,u+n*s,o*Math.exp(Vo*n*r)]};else{var p=Math.sqrt(h),g=(c*c-o*o+$o*h)/(2*o*Xo*p),v=(c*c-o*o-$o*h)/(2*c*Xo*p),d=Math.log(Math.sqrt(g*g+1)-g),y=Math.log(Math.sqrt(v*v+1)-v);r=(y-d)/Vo,e=function(n){var t=n*r,e=rn(d),a=o/(Xo*p)*(e*un(Vo*t+d)-en(d));return[i+a*f,u+a*s,o*e/rn(Vo*t+d)]}}return e.duration=1e3*r,e},ao.behavior.zoom=function(){function n(n){n.on(L,s).on(Wo+".zoom",p).on("dblclick.zoom",g).on(R,h)}function e(n){return[(n[0]-k.x)/k.k,(n[1]-k.y)/k.k]}function r(n){return[n[0]*k.k+k.x,n[1]*k.k+k.y]}function i(n){k.k=Math.max(A[0],Math.min(A[1],n))}function u(n,t){t=r(t),k.x+=n[0]-t[0],k.y+=n[1]-t[1]}function o(t,e,r,o){t.__chart__={x:k.x,y:k.y,k:k.k},i(Math.pow(2,o)),u(d=e,r),t=ao.select(t),C>0&&(t=t.transition().duration(C)),t.call(n.event)}function a(){b&&b.domain(x.range().map(function(n){return(n-k.x)/k.k}).map(x.invert)),w&&w.domain(_.range().map(function(n){return(n-k.y)/k.k}).map(_.invert))}function l(n){z++||n({type:"zoomstart"})}function c(n){a(),n({type:"zoom",scale:k.k,translate:[k.x,k.y]})}function f(n){--z||(n({type:"zoomend"}),d=null)}function s(){function n(){a=1,u(ao.mouse(i),h),c(o)}function r(){s.on(q,null).on(T,null),p(a),f(o)}var i=this,o=D.of(i,arguments),a=0,s=ao.select(t(i)).on(q,n).on(T,r),h=e(ao.mouse(i)),p=W(i);Il.call(i),l(o)}function h(){function n(){var n=ao.touches(g);return p=k.k,n.forEach(function(n){n.identifier in d&&(d[n.identifier]=e(n))}),n}function t(){var t=ao.event.target;ao.select(t).on(x,r).on(b,a),_.push(t);for(var e=ao.event.changedTouches,i=0,u=e.length;u>i;++i)d[e[i].identifier]=null;var l=n(),c=Date.now();if(1===l.length){if(500>c-M){var f=l[0];o(g,f,d[f.identifier],Math.floor(Math.log(k.k)/Math.LN2)+1),S()}M=c}else if(l.length>1){var f=l[0],s=l[1],h=f[0]-s[0],p=f[1]-s[1];y=h*h+p*p}}function r(){var n,t,e,r,o=ao.touches(g);Il.call(g);for(var a=0,l=o.length;l>a;++a,r=null)if(e=o[a],r=d[e.identifier]){if(t)break;n=e,t=r}if(r){var f=(f=e[0]-n[0])*f+(f=e[1]-n[1])*f,s=y&&Math.sqrt(f/y);n=[(n[0]+e[0])/2,(n[1]+e[1])/2],t=[(t[0]+r[0])/2,(t[1]+r[1])/2],i(s*p)}M=null,u(n,t),c(v)}function a(){if(ao.event.touches.length){for(var t=ao.event.changedTouches,e=0,r=t.length;r>e;++e)delete d[t[e].identifier];for(var i in d)return void n()}ao.selectAll(_).on(m,null),w.on(L,s).on(R,h),N(),f(v)}var p,g=this,v=D.of(g,arguments),d={},y=0,m=".zoom-"+ao.event.changedTouches[0].identifier,x="touchmove"+m,b="touchend"+m,_=[],w=ao.select(g),N=W(g);t(),l(v),w.on(L,null).on(R,t)}function p(){var n=D.of(this,arguments);m?clearTimeout(m):(Il.call(this),v=e(d=y||ao.mouse(this)),l(n)),m=setTimeout(function(){m=null,f(n)},50),S(),i(Math.pow(2,.002*Bo())*k.k),u(d,v),c(n)}function g(){var n=ao.mouse(this),t=Math.log(k.k)/Math.LN2;o(this,n,e(n),ao.event.shiftKey?Math.ceil(t)-1:Math.floor(t)+1)}var v,d,y,m,M,x,b,_,w,k={x:0,y:0,k:1},E=[960,500],A=Jo,C=250,z=0,L="mousedown.zoom",q="mousemove.zoom",T="mouseup.zoom",R="touchstart.zoom",D=N(n,"zoomstart","zoom","zoomend");return Wo||(Wo="onwheel"in fo?(Bo=function(){return-ao.event.deltaY*(ao.event.deltaMode?120:1)},"wheel"):"onmousewheel"in fo?(Bo=function(){return ao.event.wheelDelta},"mousewheel"):(Bo=function(){return-ao.event.detail},"MozMousePixelScroll")),n.event=function(n){n.each(function(){var n=D.of(this,arguments),t=k;Hl?ao.select(this).transition().each("start.zoom",function(){k=this.__chart__||{x:0,y:0,k:1},l(n)}).tween("zoom:zoom",function(){var e=E[0],r=E[1],i=d?d[0]:e/2,u=d?d[1]:r/2,o=ao.interpolateZoom([(i-k.x)/k.k,(u-k.y)/k.k,e/k.k],[(i-t.x)/t.k,(u-t.y)/t.k,e/t.k]);return function(t){var r=o(t),a=e/r[2];this.__chart__=k={x:i-r[0]*a,y:u-r[1]*a,k:a},c(n)}}).each("interrupt.zoom",function(){f(n)}).each("end.zoom",function(){f(n)}):(this.__chart__=k,l(n),c(n),f(n))})},n.translate=function(t){return arguments.length?(k={x:+t[0],y:+t[1],k:k.k},a(),n):[k.x,k.y]},n.scale=function(t){return arguments.length?(k={x:k.x,y:k.y,k:null},i(+t),a(),n):k.k},n.scaleExtent=function(t){return arguments.length?(A=null==t?Jo:[+t[0],+t[1]],n):A},n.center=function(t){return arguments.length?(y=t&&[+t[0],+t[1]],n):y},n.size=function(t){return arguments.length?(E=t&&[+t[0],+t[1]],n):E},n.duration=function(t){return arguments.length?(C=+t,n):C},n.x=function(t){return arguments.length?(b=t,x=t.copy(),k={x:0,y:0,k:1},n):b},n.y=function(t){return arguments.length?(w=t,_=t.copy(),k={x:0,y:0,k:1},n):w},ao.rebind(n,D,"on")};var Bo,Wo,Jo=[0,1/0];ao.color=an,an.prototype.toString=function(){return this.rgb()+""},ao.hsl=ln;var Go=ln.prototype=new an;Go.brighter=function(n){return n=Math.pow(.7,arguments.length?n:1),new ln(this.h,this.s,this.l/n)},Go.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new ln(this.h,this.s,n*this.l)},Go.rgb=function(){return cn(this.h,this.s,this.l)},ao.hcl=fn;var Ko=fn.prototype=new an;Ko.brighter=function(n){return new fn(this.h,this.c,Math.min(100,this.l+Qo*(arguments.length?n:1)))},Ko.darker=function(n){return new fn(this.h,this.c,Math.max(0,this.l-Qo*(arguments.length?n:1)))},Ko.rgb=function(){return sn(this.h,this.c,this.l).rgb()},ao.lab=hn;var Qo=18,na=.95047,ta=1,ea=1.08883,ra=hn.prototype=new an;ra.brighter=function(n){return new hn(Math.min(100,this.l+Qo*(arguments.length?n:1)),this.a,this.b)},ra.darker=function(n){return new hn(Math.max(0,this.l-Qo*(arguments.length?n:1)),this.a,this.b)},ra.rgb=function(){return pn(this.l,this.a,this.b)},ao.rgb=mn;var ia=mn.prototype=new an;ia.brighter=function(n){n=Math.pow(.7,arguments.length?n:1);var t=this.r,e=this.g,r=this.b,i=30;return t||e||r?(t&&i>t&&(t=i),e&&i>e&&(e=i),r&&i>r&&(r=i),new mn(Math.min(255,t/n),Math.min(255,e/n),Math.min(255,r/n))):new mn(i,i,i)},ia.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new mn(n*this.r,n*this.g,n*this.b)},ia.hsl=function(){return wn(this.r,this.g,this.b)},ia.toString=function(){return"#"+bn(this.r)+bn(this.g)+bn(this.b)};var ua=ao.map({aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074});ua.forEach(function(n,t){ua.set(n,Mn(t))}),ao.functor=En,ao.xhr=An(m),ao.dsv=function(n,t){function e(n,e,u){arguments.length<3&&(u=e,e=null);var o=Cn(n,t,null==e?r:i(e),u);return o.row=function(n){return arguments.length?o.response(null==(e=n)?r:i(n)):e},o}function r(n){return e.parse(n.responseText)}function i(n){return function(t){return e.parse(t.responseText,n)}}function u(t){return t.map(o).join(n)}function o(n){return a.test(n)?'"'+n.replace(/\"/g,'""')+'"':n}var a=new RegExp('["'+n+"\n]"),l=n.charCodeAt(0);return e.parse=function(n,t){var r;return e.parseRows(n,function(n,e){if(r)return r(n,e-1);var i=new Function("d","return {"+n.map(function(n,t){return JSON.stringify(n)+": d["+t+"]"}).join(",")+"}");r=t?function(n,e){return t(i(n),e)}:i})},e.parseRows=function(n,t){function e(){if(f>=c)return o;if(i)return i=!1,u;var t=f;if(34===n.charCodeAt(t)){for(var e=t;e++f;){var r=n.charCodeAt(f++),a=1;if(10===r)i=!0;else if(13===r)i=!0,10===n.charCodeAt(f)&&(++f,++a);else if(r!==l)continue;return n.slice(t,f-a)}return n.slice(t)}for(var r,i,u={},o={},a=[],c=n.length,f=0,s=0;(r=e())!==o;){for(var h=[];r!==u&&r!==o;)h.push(r),r=e();t&&null==(h=t(h,s++))||a.push(h)}return a},e.format=function(t){if(Array.isArray(t[0]))return e.formatRows(t);var r=new y,i=[];return t.forEach(function(n){for(var t in n)r.has(t)||i.push(r.add(t))}),[i.map(o).join(n)].concat(t.map(function(t){return i.map(function(n){return o(t[n])}).join(n)})).join("\n")},e.formatRows=function(n){return n.map(u).join("\n")},e},ao.csv=ao.dsv(",","text/csv"),ao.tsv=ao.dsv(" ","text/tab-separated-values");var oa,aa,la,ca,fa=this[x(this,"requestAnimationFrame")]||function(n){setTimeout(n,17)};ao.timer=function(){qn.apply(this,arguments)},ao.timer.flush=function(){Rn(),Dn()},ao.round=function(n,t){return t?Math.round(n*(t=Math.pow(10,t)))/t:Math.round(n)};var sa=["y","z","a","f","p","n","\xb5","m","","k","M","G","T","P","E","Z","Y"].map(Un);ao.formatPrefix=function(n,t){var e=0;return(n=+n)&&(0>n&&(n*=-1),t&&(n=ao.round(n,Pn(n,t))),e=1+Math.floor(1e-12+Math.log(n)/Math.LN10),e=Math.max(-24,Math.min(24,3*Math.floor((e-1)/3)))),sa[8+e/3]};var ha=/(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i,pa=ao.map({b:function(n){return n.toString(2)},c:function(n){return String.fromCharCode(n)},o:function(n){return n.toString(8)},x:function(n){return n.toString(16)},X:function(n){return n.toString(16).toUpperCase()},g:function(n,t){return n.toPrecision(t)},e:function(n,t){return n.toExponential(t)},f:function(n,t){return n.toFixed(t)},r:function(n,t){return(n=ao.round(n,Pn(n,t))).toFixed(Math.max(0,Math.min(20,Pn(n*(1+1e-15),t))))}}),ga=ao.time={},va=Date;Hn.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){da.setUTCDate.apply(this._,arguments)},setDay:function(){da.setUTCDay.apply(this._,arguments)},setFullYear:function(){da.setUTCFullYear.apply(this._,arguments)},setHours:function(){da.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){da.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){da.setUTCMinutes.apply(this._,arguments)},setMonth:function(){da.setUTCMonth.apply(this._,arguments)},setSeconds:function(){da.setUTCSeconds.apply(this._,arguments)},setTime:function(){da.setTime.apply(this._,arguments)}};var da=Date.prototype;ga.year=On(function(n){return n=ga.day(n),n.setMonth(0,1),n},function(n,t){n.setFullYear(n.getFullYear()+t)},function(n){return n.getFullYear()}),ga.years=ga.year.range,ga.years.utc=ga.year.utc.range,ga.day=On(function(n){var t=new va(2e3,0);return t.setFullYear(n.getFullYear(),n.getMonth(),n.getDate()),t},function(n,t){n.setDate(n.getDate()+t)},function(n){return n.getDate()-1}),ga.days=ga.day.range,ga.days.utc=ga.day.utc.range,ga.dayOfYear=function(n){var t=ga.year(n);return Math.floor((n-t-6e4*(n.getTimezoneOffset()-t.getTimezoneOffset()))/864e5)},["sunday","monday","tuesday","wednesday","thursday","friday","saturday"].forEach(function(n,t){t=7-t;var e=ga[n]=On(function(n){return(n=ga.day(n)).setDate(n.getDate()-(n.getDay()+t)%7),n},function(n,t){n.setDate(n.getDate()+7*Math.floor(t))},function(n){var e=ga.year(n).getDay();return Math.floor((ga.dayOfYear(n)+(e+t)%7)/7)-(e!==t)});ga[n+"s"]=e.range,ga[n+"s"].utc=e.utc.range,ga[n+"OfYear"]=function(n){var e=ga.year(n).getDay();return Math.floor((ga.dayOfYear(n)+(e+t)%7)/7)}}),ga.week=ga.sunday,ga.weeks=ga.sunday.range,ga.weeks.utc=ga.sunday.utc.range,ga.weekOfYear=ga.sundayOfYear;var ya={"-":"",_:" ",0:"0"},ma=/^\s*\d+/,Ma=/^%/;ao.locale=function(n){return{numberFormat:jn(n),timeFormat:Yn(n)}};var xa=ao.locale({decimal:".",thousands:",",grouping:[3],currency:["$",""],dateTime:"%a %b %e %X %Y",date:"%m/%d/%Y",time:"%H:%M:%S",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"], +shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});ao.format=xa.numberFormat,ao.geo={},ft.prototype={s:0,t:0,add:function(n){st(n,this.t,ba),st(ba.s,this.s,this),this.s?this.t+=ba.t:this.s=ba.t},reset:function(){this.s=this.t=0},valueOf:function(){return this.s}};var ba=new ft;ao.geo.stream=function(n,t){n&&_a.hasOwnProperty(n.type)?_a[n.type](n,t):ht(n,t)};var _a={Feature:function(n,t){ht(n.geometry,t)},FeatureCollection:function(n,t){for(var e=n.features,r=-1,i=e.length;++rn?4*Fo+n:n,Na.lineStart=Na.lineEnd=Na.point=b}};ao.geo.bounds=function(){function n(n,t){M.push(x=[f=n,h=n]),s>t&&(s=t),t>p&&(p=t)}function t(t,e){var r=dt([t*Yo,e*Yo]);if(y){var i=mt(y,r),u=[i[1],-i[0],0],o=mt(u,i);bt(o),o=_t(o);var l=t-g,c=l>0?1:-1,v=o[0]*Zo*c,d=xo(l)>180;if(d^(v>c*g&&c*t>v)){var m=o[1]*Zo;m>p&&(p=m)}else if(v=(v+360)%360-180,d^(v>c*g&&c*t>v)){var m=-o[1]*Zo;s>m&&(s=m)}else s>e&&(s=e),e>p&&(p=e);d?g>t?a(f,t)>a(f,h)&&(h=t):a(t,h)>a(f,h)&&(f=t):h>=f?(f>t&&(f=t),t>h&&(h=t)):t>g?a(f,t)>a(f,h)&&(h=t):a(t,h)>a(f,h)&&(f=t)}else n(t,e);y=r,g=t}function e(){b.point=t}function r(){x[0]=f,x[1]=h,b.point=n,y=null}function i(n,e){if(y){var r=n-g;m+=xo(r)>180?r+(r>0?360:-360):r}else v=n,d=e;Na.point(n,e),t(n,e)}function u(){Na.lineStart()}function o(){i(v,d),Na.lineEnd(),xo(m)>Uo&&(f=-(h=180)),x[0]=f,x[1]=h,y=null}function a(n,t){return(t-=n)<0?t+360:t}function l(n,t){return n[0]-t[0]}function c(n,t){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:nka?(f=-(h=180),s=-(p=90)):m>Uo?p=90:-Uo>m&&(s=-90),x[0]=f,x[1]=h}};return function(n){p=h=-(f=s=1/0),M=[],ao.geo.stream(n,b);var t=M.length;if(t){M.sort(l);for(var e,r=1,i=M[0],u=[i];t>r;++r)e=M[r],c(e[0],i)||c(e[1],i)?(a(i[0],e[1])>a(i[0],i[1])&&(i[1]=e[1]),a(e[0],i[1])>a(i[0],i[1])&&(i[0]=e[0])):u.push(i=e);for(var o,e,g=-(1/0),t=u.length-1,r=0,i=u[t];t>=r;i=e,++r)e=u[r],(o=a(i[1],e[0]))>g&&(g=o,f=e[0],h=i[1])}return M=x=null,f===1/0||s===1/0?[[NaN,NaN],[NaN,NaN]]:[[f,s],[h,p]]}}(),ao.geo.centroid=function(n){Ea=Aa=Ca=za=La=qa=Ta=Ra=Da=Pa=Ua=0,ao.geo.stream(n,ja);var t=Da,e=Pa,r=Ua,i=t*t+e*e+r*r;return jo>i&&(t=qa,e=Ta,r=Ra,Uo>Aa&&(t=Ca,e=za,r=La),i=t*t+e*e+r*r,jo>i)?[NaN,NaN]:[Math.atan2(e,t)*Zo,tn(r/Math.sqrt(i))*Zo]};var Ea,Aa,Ca,za,La,qa,Ta,Ra,Da,Pa,Ua,ja={sphere:b,point:St,lineStart:Nt,lineEnd:Et,polygonStart:function(){ja.lineStart=At},polygonEnd:function(){ja.lineStart=Nt}},Fa=Rt(zt,jt,Ht,[-Fo,-Fo/2]),Ha=1e9;ao.geo.clipExtent=function(){var n,t,e,r,i,u,o={stream:function(n){return i&&(i.valid=!1),i=u(n),i.valid=!0,i},extent:function(a){return arguments.length?(u=Zt(n=+a[0][0],t=+a[0][1],e=+a[1][0],r=+a[1][1]),i&&(i.valid=!1,i=null),o):[[n,t],[e,r]]}};return o.extent([[0,0],[960,500]])},(ao.geo.conicEqualArea=function(){return Vt(Xt)}).raw=Xt,ao.geo.albers=function(){return ao.geo.conicEqualArea().rotate([96,0]).center([-.6,38.7]).parallels([29.5,45.5]).scale(1070)},ao.geo.albersUsa=function(){function n(n){var u=n[0],o=n[1];return t=null,e(u,o),t||(r(u,o),t)||i(u,o),t}var t,e,r,i,u=ao.geo.albers(),o=ao.geo.conicEqualArea().rotate([154,0]).center([-2,58.5]).parallels([55,65]),a=ao.geo.conicEqualArea().rotate([157,0]).center([-3,19.9]).parallels([8,18]),l={point:function(n,e){t=[n,e]}};return n.invert=function(n){var t=u.scale(),e=u.translate(),r=(n[0]-e[0])/t,i=(n[1]-e[1])/t;return(i>=.12&&.234>i&&r>=-.425&&-.214>r?o:i>=.166&&.234>i&&r>=-.214&&-.115>r?a:u).invert(n)},n.stream=function(n){var t=u.stream(n),e=o.stream(n),r=a.stream(n);return{point:function(n,i){t.point(n,i),e.point(n,i),r.point(n,i)},sphere:function(){t.sphere(),e.sphere(),r.sphere()},lineStart:function(){t.lineStart(),e.lineStart(),r.lineStart()},lineEnd:function(){t.lineEnd(),e.lineEnd(),r.lineEnd()},polygonStart:function(){t.polygonStart(),e.polygonStart(),r.polygonStart()},polygonEnd:function(){t.polygonEnd(),e.polygonEnd(),r.polygonEnd()}}},n.precision=function(t){return arguments.length?(u.precision(t),o.precision(t),a.precision(t),n):u.precision()},n.scale=function(t){return arguments.length?(u.scale(t),o.scale(.35*t),a.scale(t),n.translate(u.translate())):u.scale()},n.translate=function(t){if(!arguments.length)return u.translate();var c=u.scale(),f=+t[0],s=+t[1];return e=u.translate(t).clipExtent([[f-.455*c,s-.238*c],[f+.455*c,s+.238*c]]).stream(l).point,r=o.translate([f-.307*c,s+.201*c]).clipExtent([[f-.425*c+Uo,s+.12*c+Uo],[f-.214*c-Uo,s+.234*c-Uo]]).stream(l).point,i=a.translate([f-.205*c,s+.212*c]).clipExtent([[f-.214*c+Uo,s+.166*c+Uo],[f-.115*c-Uo,s+.234*c-Uo]]).stream(l).point,n},n.scale(1070)};var Oa,Ia,Ya,Za,Va,Xa,$a={point:b,lineStart:b,lineEnd:b,polygonStart:function(){Ia=0,$a.lineStart=$t},polygonEnd:function(){$a.lineStart=$a.lineEnd=$a.point=b,Oa+=xo(Ia/2)}},Ba={point:Bt,lineStart:b,lineEnd:b,polygonStart:b,polygonEnd:b},Wa={point:Gt,lineStart:Kt,lineEnd:Qt,polygonStart:function(){Wa.lineStart=ne},polygonEnd:function(){Wa.point=Gt,Wa.lineStart=Kt,Wa.lineEnd=Qt}};ao.geo.path=function(){function n(n){return n&&("function"==typeof a&&u.pointRadius(+a.apply(this,arguments)),o&&o.valid||(o=i(u)),ao.geo.stream(n,o)),u.result()}function t(){return o=null,n}var e,r,i,u,o,a=4.5;return n.area=function(n){return Oa=0,ao.geo.stream(n,i($a)),Oa},n.centroid=function(n){return Ca=za=La=qa=Ta=Ra=Da=Pa=Ua=0,ao.geo.stream(n,i(Wa)),Ua?[Da/Ua,Pa/Ua]:Ra?[qa/Ra,Ta/Ra]:La?[Ca/La,za/La]:[NaN,NaN]},n.bounds=function(n){return Va=Xa=-(Ya=Za=1/0),ao.geo.stream(n,i(Ba)),[[Ya,Za],[Va,Xa]]},n.projection=function(n){return arguments.length?(i=(e=n)?n.stream||re(n):m,t()):e},n.context=function(n){return arguments.length?(u=null==(r=n)?new Wt:new te(n),"function"!=typeof a&&u.pointRadius(a),t()):r},n.pointRadius=function(t){return arguments.length?(a="function"==typeof t?t:(u.pointRadius(+t),+t),n):a},n.projection(ao.geo.albersUsa()).context(null)},ao.geo.transform=function(n){return{stream:function(t){var e=new ie(t);for(var r in n)e[r]=n[r];return e}}},ie.prototype={point:function(n,t){this.stream.point(n,t)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}},ao.geo.projection=oe,ao.geo.projectionMutator=ae,(ao.geo.equirectangular=function(){return oe(ce)}).raw=ce.invert=ce,ao.geo.rotation=function(n){function t(t){return t=n(t[0]*Yo,t[1]*Yo),t[0]*=Zo,t[1]*=Zo,t}return n=se(n[0]%360*Yo,n[1]*Yo,n.length>2?n[2]*Yo:0),t.invert=function(t){return t=n.invert(t[0]*Yo,t[1]*Yo),t[0]*=Zo,t[1]*=Zo,t},t},fe.invert=ce,ao.geo.circle=function(){function n(){var n="function"==typeof r?r.apply(this,arguments):r,t=se(-n[0]*Yo,-n[1]*Yo,0).invert,i=[];return e(null,null,1,{point:function(n,e){i.push(n=t(n,e)),n[0]*=Zo,n[1]*=Zo}}),{type:"Polygon",coordinates:[i]}}var t,e,r=[0,0],i=6;return n.origin=function(t){return arguments.length?(r=t,n):r},n.angle=function(r){return arguments.length?(e=ve((t=+r)*Yo,i*Yo),n):t},n.precision=function(r){return arguments.length?(e=ve(t*Yo,(i=+r)*Yo),n):i},n.angle(90)},ao.geo.distance=function(n,t){var e,r=(t[0]-n[0])*Yo,i=n[1]*Yo,u=t[1]*Yo,o=Math.sin(r),a=Math.cos(r),l=Math.sin(i),c=Math.cos(i),f=Math.sin(u),s=Math.cos(u);return Math.atan2(Math.sqrt((e=s*o)*e+(e=c*f-l*s*a)*e),l*f+c*s*a)},ao.geo.graticule=function(){function n(){return{type:"MultiLineString",coordinates:t()}}function t(){return ao.range(Math.ceil(u/d)*d,i,d).map(h).concat(ao.range(Math.ceil(c/y)*y,l,y).map(p)).concat(ao.range(Math.ceil(r/g)*g,e,g).filter(function(n){return xo(n%d)>Uo}).map(f)).concat(ao.range(Math.ceil(a/v)*v,o,v).filter(function(n){return xo(n%y)>Uo}).map(s))}var e,r,i,u,o,a,l,c,f,s,h,p,g=10,v=g,d=90,y=360,m=2.5;return n.lines=function(){return t().map(function(n){return{type:"LineString",coordinates:n}})},n.outline=function(){return{type:"Polygon",coordinates:[h(u).concat(p(l).slice(1),h(i).reverse().slice(1),p(c).reverse().slice(1))]}},n.extent=function(t){return arguments.length?n.majorExtent(t).minorExtent(t):n.minorExtent()},n.majorExtent=function(t){return arguments.length?(u=+t[0][0],i=+t[1][0],c=+t[0][1],l=+t[1][1],u>i&&(t=u,u=i,i=t),c>l&&(t=c,c=l,l=t),n.precision(m)):[[u,c],[i,l]]},n.minorExtent=function(t){return arguments.length?(r=+t[0][0],e=+t[1][0],a=+t[0][1],o=+t[1][1],r>e&&(t=r,r=e,e=t),a>o&&(t=a,a=o,o=t),n.precision(m)):[[r,a],[e,o]]},n.step=function(t){return arguments.length?n.majorStep(t).minorStep(t):n.minorStep()},n.majorStep=function(t){return arguments.length?(d=+t[0],y=+t[1],n):[d,y]},n.minorStep=function(t){return arguments.length?(g=+t[0],v=+t[1],n):[g,v]},n.precision=function(t){return arguments.length?(m=+t,f=ye(a,o,90),s=me(r,e,m),h=ye(c,l,90),p=me(u,i,m),n):m},n.majorExtent([[-180,-90+Uo],[180,90-Uo]]).minorExtent([[-180,-80-Uo],[180,80+Uo]])},ao.geo.greatArc=function(){function n(){return{type:"LineString",coordinates:[t||r.apply(this,arguments),e||i.apply(this,arguments)]}}var t,e,r=Me,i=xe;return n.distance=function(){return ao.geo.distance(t||r.apply(this,arguments),e||i.apply(this,arguments))},n.source=function(e){return arguments.length?(r=e,t="function"==typeof e?null:e,n):r},n.target=function(t){return arguments.length?(i=t,e="function"==typeof t?null:t,n):i},n.precision=function(){return arguments.length?n:0},n},ao.geo.interpolate=function(n,t){return be(n[0]*Yo,n[1]*Yo,t[0]*Yo,t[1]*Yo)},ao.geo.length=function(n){return Ja=0,ao.geo.stream(n,Ga),Ja};var Ja,Ga={sphere:b,point:b,lineStart:_e,lineEnd:b,polygonStart:b,polygonEnd:b},Ka=we(function(n){return Math.sqrt(2/(1+n))},function(n){return 2*Math.asin(n/2)});(ao.geo.azimuthalEqualArea=function(){return oe(Ka)}).raw=Ka;var Qa=we(function(n){var t=Math.acos(n);return t&&t/Math.sin(t)},m);(ao.geo.azimuthalEquidistant=function(){return oe(Qa)}).raw=Qa,(ao.geo.conicConformal=function(){return Vt(Se)}).raw=Se,(ao.geo.conicEquidistant=function(){return Vt(ke)}).raw=ke;var nl=we(function(n){return 1/n},Math.atan);(ao.geo.gnomonic=function(){return oe(nl)}).raw=nl,Ne.invert=function(n,t){return[n,2*Math.atan(Math.exp(t))-Io]},(ao.geo.mercator=function(){return Ee(Ne)}).raw=Ne;var tl=we(function(){return 1},Math.asin);(ao.geo.orthographic=function(){return oe(tl)}).raw=tl;var el=we(function(n){return 1/(1+n)},function(n){return 2*Math.atan(n)});(ao.geo.stereographic=function(){return oe(el)}).raw=el,Ae.invert=function(n,t){return[-t,2*Math.atan(Math.exp(n))-Io]},(ao.geo.transverseMercator=function(){var n=Ee(Ae),t=n.center,e=n.rotate;return n.center=function(n){return n?t([-n[1],n[0]]):(n=t(),[n[1],-n[0]])},n.rotate=function(n){return n?e([n[0],n[1],n.length>2?n[2]+90:90]):(n=e(),[n[0],n[1],n[2]-90])},e([0,0,90])}).raw=Ae,ao.geom={},ao.geom.hull=function(n){function t(n){if(n.length<3)return[];var t,i=En(e),u=En(r),o=n.length,a=[],l=[];for(t=0;o>t;t++)a.push([+i.call(this,n[t],t),+u.call(this,n[t],t),t]);for(a.sort(qe),t=0;o>t;t++)l.push([a[t][0],-a[t][1]]);var c=Le(a),f=Le(l),s=f[0]===c[0],h=f[f.length-1]===c[c.length-1],p=[];for(t=c.length-1;t>=0;--t)p.push(n[a[c[t]][2]]);for(t=+s;t=r&&c.x<=u&&c.y>=i&&c.y<=o?[[r,o],[u,o],[u,i],[r,i]]:[];f.point=n[a]}),t}function e(n){return n.map(function(n,t){return{x:Math.round(u(n,t)/Uo)*Uo,y:Math.round(o(n,t)/Uo)*Uo,i:t}})}var r=Ce,i=ze,u=r,o=i,a=sl;return n?t(n):(t.links=function(n){return ar(e(n)).edges.filter(function(n){return n.l&&n.r}).map(function(t){return{source:n[t.l.i],target:n[t.r.i]}})},t.triangles=function(n){var t=[];return ar(e(n)).cells.forEach(function(e,r){for(var i,u,o=e.site,a=e.edges.sort(Ve),l=-1,c=a.length,f=a[c-1].edge,s=f.l===o?f.r:f.l;++l=c,h=r>=f,p=h<<1|s;n.leaf=!1,n=n.nodes[p]||(n.nodes[p]=hr()),s?i=c:a=c,h?o=f:l=f,u(n,t,e,r,i,o,a,l)}var f,s,h,p,g,v,d,y,m,M=En(a),x=En(l);if(null!=t)v=t,d=e,y=r,m=i;else if(y=m=-(v=d=1/0),s=[],h=[],g=n.length,o)for(p=0;g>p;++p)f=n[p],f.xy&&(y=f.x),f.y>m&&(m=f.y),s.push(f.x),h.push(f.y);else for(p=0;g>p;++p){var b=+M(f=n[p],p),_=+x(f,p);v>b&&(v=b),d>_&&(d=_),b>y&&(y=b),_>m&&(m=_),s.push(b),h.push(_)}var w=y-v,S=m-d;w>S?m=d+w:y=v+S;var k=hr();if(k.add=function(n){u(k,n,+M(n,++p),+x(n,p),v,d,y,m)},k.visit=function(n){pr(n,k,v,d,y,m)},k.find=function(n){return gr(k,n[0],n[1],v,d,y,m)},p=-1,null==t){for(;++p=0?n.slice(0,t):n,r=t>=0?n.slice(t+1):"in";return e=vl.get(e)||gl,r=dl.get(r)||m,br(r(e.apply(null,lo.call(arguments,1))))},ao.interpolateHcl=Rr,ao.interpolateHsl=Dr,ao.interpolateLab=Pr,ao.interpolateRound=Ur,ao.transform=function(n){var t=fo.createElementNS(ao.ns.prefix.svg,"g");return(ao.transform=function(n){if(null!=n){t.setAttribute("transform",n);var e=t.transform.baseVal.consolidate()}return new jr(e?e.matrix:yl)})(n)},jr.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var yl={a:1,b:0,c:0,d:1,e:0,f:0};ao.interpolateTransform=$r,ao.layout={},ao.layout.bundle=function(){return function(n){for(var t=[],e=-1,r=n.length;++ea*a/y){if(v>l){var c=t.charge/l;n.px-=u*c,n.py-=o*c}return!0}if(t.point&&l&&v>l){var c=t.pointCharge/l;n.px-=u*c,n.py-=o*c}}return!t.charge}}function t(n){n.px=ao.event.x,n.py=ao.event.y,l.resume()}var e,r,i,u,o,a,l={},c=ao.dispatch("start","tick","end"),f=[1,1],s=.9,h=ml,p=Ml,g=-30,v=xl,d=.1,y=.64,M=[],x=[];return l.tick=function(){if((i*=.99)<.005)return e=null,c.end({type:"end",alpha:i=0}),!0;var t,r,l,h,p,v,y,m,b,_=M.length,w=x.length;for(r=0;w>r;++r)l=x[r],h=l.source,p=l.target,m=p.x-h.x,b=p.y-h.y,(v=m*m+b*b)&&(v=i*o[r]*((v=Math.sqrt(v))-u[r])/v,m*=v,b*=v,p.x-=m*(y=h.weight+p.weight?h.weight/(h.weight+p.weight):.5),p.y-=b*y,h.x+=m*(y=1-y),h.y+=b*y);if((y=i*d)&&(m=f[0]/2,b=f[1]/2,r=-1,y))for(;++r<_;)l=M[r],l.x+=(m-l.x)*y,l.y+=(b-l.y)*y;if(g)for(ri(t=ao.geom.quadtree(M),i,a),r=-1;++r<_;)(l=M[r]).fixed||t.visit(n(l));for(r=-1;++r<_;)l=M[r],l.fixed?(l.x=l.px,l.y=l.py):(l.x-=(l.px-(l.px=l.x))*s,l.y-=(l.py-(l.py=l.y))*s);c.tick({type:"tick",alpha:i})},l.nodes=function(n){return arguments.length?(M=n,l):M},l.links=function(n){return arguments.length?(x=n,l):x},l.size=function(n){return arguments.length?(f=n,l):f},l.linkDistance=function(n){return arguments.length?(h="function"==typeof n?n:+n,l):h},l.distance=l.linkDistance,l.linkStrength=function(n){return arguments.length?(p="function"==typeof n?n:+n,l):p},l.friction=function(n){return arguments.length?(s=+n,l):s},l.charge=function(n){return arguments.length?(g="function"==typeof n?n:+n,l):g},l.chargeDistance=function(n){return arguments.length?(v=n*n,l):Math.sqrt(v)},l.gravity=function(n){return arguments.length?(d=+n,l):d},l.theta=function(n){return arguments.length?(y=n*n,l):Math.sqrt(y)},l.alpha=function(n){return arguments.length?(n=+n,i?n>0?i=n:(e.c=null,e.t=NaN,e=null,c.end({type:"end",alpha:i=0})):n>0&&(c.start({type:"start",alpha:i=n}),e=qn(l.tick)),l):i},l.start=function(){function n(n,r){if(!e){for(e=new Array(i),l=0;i>l;++l)e[l]=[];for(l=0;c>l;++l){var u=x[l];e[u.source.index].push(u.target),e[u.target.index].push(u.source)}}for(var o,a=e[t],l=-1,f=a.length;++lt;++t)(r=M[t]).index=t,r.weight=0;for(t=0;c>t;++t)r=x[t],"number"==typeof r.source&&(r.source=M[r.source]),"number"==typeof r.target&&(r.target=M[r.target]),++r.source.weight,++r.target.weight;for(t=0;i>t;++t)r=M[t],isNaN(r.x)&&(r.x=n("x",s)),isNaN(r.y)&&(r.y=n("y",v)),isNaN(r.px)&&(r.px=r.x),isNaN(r.py)&&(r.py=r.y);if(u=[],"function"==typeof h)for(t=0;c>t;++t)u[t]=+h.call(this,x[t],t);else for(t=0;c>t;++t)u[t]=h;if(o=[],"function"==typeof p)for(t=0;c>t;++t)o[t]=+p.call(this,x[t],t);else for(t=0;c>t;++t)o[t]=p;if(a=[],"function"==typeof g)for(t=0;i>t;++t)a[t]=+g.call(this,M[t],t);else for(t=0;i>t;++t)a[t]=g;return l.resume()},l.resume=function(){return l.alpha(.1)},l.stop=function(){return l.alpha(0)},l.drag=function(){return r||(r=ao.behavior.drag().origin(m).on("dragstart.force",Qr).on("drag.force",t).on("dragend.force",ni)),arguments.length?void this.on("mouseover.force",ti).on("mouseout.force",ei).call(r):r},ao.rebind(l,c,"on")};var ml=20,Ml=1,xl=1/0;ao.layout.hierarchy=function(){function n(i){var u,o=[i],a=[];for(i.depth=0;null!=(u=o.pop());)if(a.push(u),(c=e.call(n,u,u.depth))&&(l=c.length)){for(var l,c,f;--l>=0;)o.push(f=c[l]),f.parent=u,f.depth=u.depth+1;r&&(u.value=0),u.children=c}else r&&(u.value=+r.call(n,u,u.depth)||0),delete u.children;return oi(i,function(n){var e,i;t&&(e=n.children)&&e.sort(t),r&&(i=n.parent)&&(i.value+=n.value)}),a}var t=ci,e=ai,r=li;return n.sort=function(e){return arguments.length?(t=e,n):t},n.children=function(t){return arguments.length?(e=t,n):e},n.value=function(t){return arguments.length?(r=t,n):r},n.revalue=function(t){return r&&(ui(t,function(n){n.children&&(n.value=0)}),oi(t,function(t){var e;t.children||(t.value=+r.call(n,t,t.depth)||0),(e=t.parent)&&(e.value+=t.value)})),t},n},ao.layout.partition=function(){function n(t,e,r,i){var u=t.children;if(t.x=e,t.y=t.depth*i,t.dx=r,t.dy=i,u&&(o=u.length)){var o,a,l,c=-1;for(r=t.value?r/t.value:0;++cs?-1:1),g=ao.sum(c),v=g?(s-l*p)/g:0,d=ao.range(l),y=[];return null!=e&&d.sort(e===bl?function(n,t){return c[t]-c[n]}:function(n,t){return e(o[n],o[t])}),d.forEach(function(n){y[n]={data:o[n],value:a=c[n],startAngle:f,endAngle:f+=a*v+p,padAngle:h}}),y}var t=Number,e=bl,r=0,i=Ho,u=0;return n.value=function(e){return arguments.length?(t=e,n):t},n.sort=function(t){return arguments.length?(e=t,n):e},n.startAngle=function(t){return arguments.length?(r=t,n):r},n.endAngle=function(t){return arguments.length?(i=t,n):i},n.padAngle=function(t){return arguments.length?(u=t,n):u},n};var bl={};ao.layout.stack=function(){function n(a,l){if(!(h=a.length))return a;var c=a.map(function(e,r){return t.call(n,e,r)}),f=c.map(function(t){return t.map(function(t,e){return[u.call(n,t,e),o.call(n,t,e)]})}),s=e.call(n,f,l);c=ao.permute(c,s),f=ao.permute(f,s);var h,p,g,v,d=r.call(n,f,l),y=c[0].length;for(g=0;y>g;++g)for(i.call(n,c[0][g],v=d[g],f[0][g][1]),p=1;h>p;++p)i.call(n,c[p][g],v+=f[p-1][g][1],f[p][g][1]);return a}var t=m,e=gi,r=vi,i=pi,u=si,o=hi;return n.values=function(e){return arguments.length?(t=e,n):t},n.order=function(t){return arguments.length?(e="function"==typeof t?t:_l.get(t)||gi,n):e},n.offset=function(t){return arguments.length?(r="function"==typeof t?t:wl.get(t)||vi,n):r},n.x=function(t){return arguments.length?(u=t,n):u},n.y=function(t){return arguments.length?(o=t,n):o},n.out=function(t){return arguments.length?(i=t,n):i},n};var _l=ao.map({"inside-out":function(n){var t,e,r=n.length,i=n.map(di),u=n.map(yi),o=ao.range(r).sort(function(n,t){return i[n]-i[t]}),a=0,l=0,c=[],f=[];for(t=0;r>t;++t)e=o[t],l>a?(a+=u[e],c.push(e)):(l+=u[e],f.push(e));return f.reverse().concat(c)},reverse:function(n){return ao.range(n.length).reverse()},"default":gi}),wl=ao.map({silhouette:function(n){var t,e,r,i=n.length,u=n[0].length,o=[],a=0,l=[];for(e=0;u>e;++e){for(t=0,r=0;i>t;t++)r+=n[t][e][1];r>a&&(a=r),o.push(r)}for(e=0;u>e;++e)l[e]=(a-o[e])/2;return l},wiggle:function(n){var t,e,r,i,u,o,a,l,c,f=n.length,s=n[0],h=s.length,p=[];for(p[0]=l=c=0,e=1;h>e;++e){for(t=0,i=0;f>t;++t)i+=n[t][e][1];for(t=0,u=0,a=s[e][0]-s[e-1][0];f>t;++t){for(r=0,o=(n[t][e][1]-n[t][e-1][1])/(2*a);t>r;++r)o+=(n[r][e][1]-n[r][e-1][1])/a;u+=o*n[t][e][1]}p[e]=l-=i?u/i*a:0,c>l&&(c=l)}for(e=0;h>e;++e)p[e]-=c;return p},expand:function(n){var t,e,r,i=n.length,u=n[0].length,o=1/i,a=[];for(e=0;u>e;++e){for(t=0,r=0;i>t;t++)r+=n[t][e][1];if(r)for(t=0;i>t;t++)n[t][e][1]/=r;else for(t=0;i>t;t++)n[t][e][1]=o}for(e=0;u>e;++e)a[e]=0;return a},zero:vi});ao.layout.histogram=function(){function n(n,u){for(var o,a,l=[],c=n.map(e,this),f=r.call(this,c,u),s=i.call(this,f,c,u),u=-1,h=c.length,p=s.length-1,g=t?1:1/h;++u0)for(u=-1;++u=f[0]&&a<=f[1]&&(o=l[ao.bisect(s,a,1,p)-1],o.y+=g,o.push(n[u]));return l}var t=!0,e=Number,r=bi,i=Mi;return n.value=function(t){return arguments.length?(e=t,n):e},n.range=function(t){return arguments.length?(r=En(t),n):r},n.bins=function(t){return arguments.length?(i="number"==typeof t?function(n){return xi(n,t)}:En(t),n):i},n.frequency=function(e){return arguments.length?(t=!!e,n):t},n},ao.layout.pack=function(){function n(n,u){var o=e.call(this,n,u),a=o[0],l=i[0],c=i[1],f=null==t?Math.sqrt:"function"==typeof t?t:function(){return t};if(a.x=a.y=0,oi(a,function(n){n.r=+f(n.value)}),oi(a,Ni),r){var s=r*(t?1:Math.max(2*a.r/l,2*a.r/c))/2;oi(a,function(n){n.r+=s}),oi(a,Ni),oi(a,function(n){n.r-=s})}return Ci(a,l/2,c/2,t?1:1/Math.max(2*a.r/l,2*a.r/c)),o}var t,e=ao.layout.hierarchy().sort(_i),r=0,i=[1,1];return n.size=function(t){return arguments.length?(i=t,n):i},n.radius=function(e){return arguments.length?(t=null==e||"function"==typeof e?e:+e,n):t},n.padding=function(t){return arguments.length?(r=+t,n):r},ii(n,e)},ao.layout.tree=function(){function n(n,i){var f=o.call(this,n,i),s=f[0],h=t(s);if(oi(h,e),h.parent.m=-h.z,ui(h,r),c)ui(s,u);else{var p=s,g=s,v=s;ui(s,function(n){n.xg.x&&(g=n),n.depth>v.depth&&(v=n)});var d=a(p,g)/2-p.x,y=l[0]/(g.x+a(g,p)/2+d),m=l[1]/(v.depth||1);ui(s,function(n){n.x=(n.x+d)*y,n.y=n.depth*m})}return f}function t(n){for(var t,e={A:null,children:[n]},r=[e];null!=(t=r.pop());)for(var i,u=t.children,o=0,a=u.length;a>o;++o)r.push((u[o]=i={_:u[o],parent:t,children:(i=u[o].children)&&i.slice()||[],A:null,a:null,z:0,m:0,c:0,s:0,t:null,i:o}).a=i);return e.children[0]}function e(n){var t=n.children,e=n.parent.children,r=n.i?e[n.i-1]:null;if(t.length){Di(n);var u=(t[0].z+t[t.length-1].z)/2;r?(n.z=r.z+a(n._,r._),n.m=n.z-u):n.z=u}else r&&(n.z=r.z+a(n._,r._));n.parent.A=i(n,r,n.parent.A||e[0])}function r(n){n._.x=n.z+n.parent.m,n.m+=n.parent.m}function i(n,t,e){if(t){for(var r,i=n,u=n,o=t,l=i.parent.children[0],c=i.m,f=u.m,s=o.m,h=l.m;o=Ti(o),i=qi(i),o&&i;)l=qi(l),u=Ti(u),u.a=n,r=o.z+s-i.z-c+a(o._,i._),r>0&&(Ri(Pi(o,n,e),n,r),c+=r,f+=r),s+=o.m,c+=i.m,h+=l.m,f+=u.m;o&&!Ti(u)&&(u.t=o,u.m+=s-f),i&&!qi(l)&&(l.t=i,l.m+=c-h,e=n)}return e}function u(n){n.x*=l[0],n.y=n.depth*l[1]}var o=ao.layout.hierarchy().sort(null).value(null),a=Li,l=[1,1],c=null;return n.separation=function(t){return arguments.length?(a=t,n):a},n.size=function(t){return arguments.length?(c=null==(l=t)?u:null,n):c?null:l},n.nodeSize=function(t){return arguments.length?(c=null==(l=t)?null:u,n):c?l:null},ii(n,o)},ao.layout.cluster=function(){function n(n,u){var o,a=t.call(this,n,u),l=a[0],c=0;oi(l,function(n){var t=n.children;t&&t.length?(n.x=ji(t),n.y=Ui(t)):(n.x=o?c+=e(n,o):0,n.y=0,o=n)});var f=Fi(l),s=Hi(l),h=f.x-e(f,s)/2,p=s.x+e(s,f)/2;return oi(l,i?function(n){n.x=(n.x-l.x)*r[0],n.y=(l.y-n.y)*r[1]}:function(n){n.x=(n.x-h)/(p-h)*r[0],n.y=(1-(l.y?n.y/l.y:1))*r[1]}),a}var t=ao.layout.hierarchy().sort(null).value(null),e=Li,r=[1,1],i=!1;return n.separation=function(t){return arguments.length?(e=t,n):e},n.size=function(t){return arguments.length?(i=null==(r=t),n):i?null:r},n.nodeSize=function(t){return arguments.length?(i=null!=(r=t),n):i?r:null},ii(n,t)},ao.layout.treemap=function(){function n(n,t){for(var e,r,i=-1,u=n.length;++it?0:t),e.area=isNaN(r)||0>=r?0:r}function t(e){var u=e.children;if(u&&u.length){var o,a,l,c=s(e),f=[],h=u.slice(),g=1/0,v="slice"===p?c.dx:"dice"===p?c.dy:"slice-dice"===p?1&e.depth?c.dy:c.dx:Math.min(c.dx,c.dy);for(n(h,c.dx*c.dy/e.value),f.area=0;(l=h.length)>0;)f.push(o=h[l-1]),f.area+=o.area,"squarify"!==p||(a=r(f,v))<=g?(h.pop(),g=a):(f.area-=f.pop().area,i(f,v,c,!1),v=Math.min(c.dx,c.dy),f.length=f.area=0,g=1/0);f.length&&(i(f,v,c,!0),f.length=f.area=0),u.forEach(t)}}function e(t){var r=t.children;if(r&&r.length){var u,o=s(t),a=r.slice(),l=[];for(n(a,o.dx*o.dy/t.value),l.area=0;u=a.pop();)l.push(u),l.area+=u.area,null!=u.z&&(i(l,u.z?o.dx:o.dy,o,!a.length),l.length=l.area=0);r.forEach(e)}}function r(n,t){for(var e,r=n.area,i=0,u=1/0,o=-1,a=n.length;++oe&&(u=e),e>i&&(i=e));return r*=r,t*=t,r?Math.max(t*i*g/r,r/(t*u*g)):1/0}function i(n,t,e,r){var i,u=-1,o=n.length,a=e.x,c=e.y,f=t?l(n.area/t):0; +if(t==e.dx){for((r||f>e.dy)&&(f=e.dy);++ue.dx)&&(f=e.dx);++ue&&(t=1),1>e&&(n=0),function(){var e,r,i;do e=2*Math.random()-1,r=2*Math.random()-1,i=e*e+r*r;while(!i||i>1);return n+t*e*Math.sqrt(-2*Math.log(i)/i)}},logNormal:function(){var n=ao.random.normal.apply(ao,arguments);return function(){return Math.exp(n())}},bates:function(n){var t=ao.random.irwinHall(n);return function(){return t()/n}},irwinHall:function(n){return function(){for(var t=0,e=0;n>e;e++)t+=Math.random();return t}}},ao.scale={};var Sl={floor:m,ceil:m};ao.scale.linear=function(){return Wi([0,1],[0,1],Mr,!1)};var kl={s:1,g:1,p:1,r:1,e:1};ao.scale.log=function(){return ru(ao.scale.linear().domain([0,1]),10,!0,[1,10])};var Nl=ao.format(".0e"),El={floor:function(n){return-Math.ceil(-n)},ceil:function(n){return-Math.floor(-n)}};ao.scale.pow=function(){return iu(ao.scale.linear(),1,[0,1])},ao.scale.sqrt=function(){return ao.scale.pow().exponent(.5)},ao.scale.ordinal=function(){return ou([],{t:"range",a:[[]]})},ao.scale.category10=function(){return ao.scale.ordinal().range(Al)},ao.scale.category20=function(){return ao.scale.ordinal().range(Cl)},ao.scale.category20b=function(){return ao.scale.ordinal().range(zl)},ao.scale.category20c=function(){return ao.scale.ordinal().range(Ll)};var Al=[2062260,16744206,2924588,14034728,9725885,9197131,14907330,8355711,12369186,1556175].map(xn),Cl=[2062260,11454440,16744206,16759672,2924588,10018698,14034728,16750742,9725885,12955861,9197131,12885140,14907330,16234194,8355711,13092807,12369186,14408589,1556175,10410725].map(xn),zl=[3750777,5395619,7040719,10264286,6519097,9216594,11915115,13556636,9202993,12426809,15186514,15190932,8666169,11356490,14049643,15177372,8077683,10834324,13528509,14589654].map(xn),Ll=[3244733,7057110,10406625,13032431,15095053,16616764,16625259,16634018,3253076,7652470,10607003,13101504,7695281,10394312,12369372,14342891,6513507,9868950,12434877,14277081].map(xn);ao.scale.quantile=function(){return au([],[])},ao.scale.quantize=function(){return lu(0,1,[0,1])},ao.scale.threshold=function(){return cu([.5],[0,1])},ao.scale.identity=function(){return fu([0,1])},ao.svg={},ao.svg.arc=function(){function n(){var n=Math.max(0,+e.apply(this,arguments)),c=Math.max(0,+r.apply(this,arguments)),f=o.apply(this,arguments)-Io,s=a.apply(this,arguments)-Io,h=Math.abs(s-f),p=f>s?0:1;if(n>c&&(g=c,c=n,n=g),h>=Oo)return t(c,p)+(n?t(n,1-p):"")+"Z";var g,v,d,y,m,M,x,b,_,w,S,k,N=0,E=0,A=[];if((y=(+l.apply(this,arguments)||0)/2)&&(d=u===ql?Math.sqrt(n*n+c*c):+u.apply(this,arguments),p||(E*=-1),c&&(E=tn(d/c*Math.sin(y))),n&&(N=tn(d/n*Math.sin(y)))),c){m=c*Math.cos(f+E),M=c*Math.sin(f+E),x=c*Math.cos(s-E),b=c*Math.sin(s-E);var C=Math.abs(s-f-2*E)<=Fo?0:1;if(E&&yu(m,M,x,b)===p^C){var z=(f+s)/2;m=c*Math.cos(z),M=c*Math.sin(z),x=b=null}}else m=M=0;if(n){_=n*Math.cos(s-N),w=n*Math.sin(s-N),S=n*Math.cos(f+N),k=n*Math.sin(f+N);var L=Math.abs(f-s+2*N)<=Fo?0:1;if(N&&yu(_,w,S,k)===1-p^L){var q=(f+s)/2;_=n*Math.cos(q),w=n*Math.sin(q),S=k=null}}else _=w=0;if(h>Uo&&(g=Math.min(Math.abs(c-n)/2,+i.apply(this,arguments)))>.001){v=c>n^p?0:1;var T=g,R=g;if(Fo>h){var D=null==S?[_,w]:null==x?[m,M]:Re([m,M],[S,k],[x,b],[_,w]),P=m-D[0],U=M-D[1],j=x-D[0],F=b-D[1],H=1/Math.sin(Math.acos((P*j+U*F)/(Math.sqrt(P*P+U*U)*Math.sqrt(j*j+F*F)))/2),O=Math.sqrt(D[0]*D[0]+D[1]*D[1]);R=Math.min(g,(n-O)/(H-1)),T=Math.min(g,(c-O)/(H+1))}if(null!=x){var I=mu(null==S?[_,w]:[S,k],[m,M],c,T,p),Y=mu([x,b],[_,w],c,T,p);g===T?A.push("M",I[0],"A",T,",",T," 0 0,",v," ",I[1],"A",c,",",c," 0 ",1-p^yu(I[1][0],I[1][1],Y[1][0],Y[1][1]),",",p," ",Y[1],"A",T,",",T," 0 0,",v," ",Y[0]):A.push("M",I[0],"A",T,",",T," 0 1,",v," ",Y[0])}else A.push("M",m,",",M);if(null!=S){var Z=mu([m,M],[S,k],n,-R,p),V=mu([_,w],null==x?[m,M]:[x,b],n,-R,p);g===R?A.push("L",V[0],"A",R,",",R," 0 0,",v," ",V[1],"A",n,",",n," 0 ",p^yu(V[1][0],V[1][1],Z[1][0],Z[1][1]),",",1-p," ",Z[1],"A",R,",",R," 0 0,",v," ",Z[0]):A.push("L",V[0],"A",R,",",R," 0 0,",v," ",Z[0])}else A.push("L",_,",",w)}else A.push("M",m,",",M),null!=x&&A.push("A",c,",",c," 0 ",C,",",p," ",x,",",b),A.push("L",_,",",w),null!=S&&A.push("A",n,",",n," 0 ",L,",",1-p," ",S,",",k);return A.push("Z"),A.join("")}function t(n,t){return"M0,"+n+"A"+n+","+n+" 0 1,"+t+" 0,"+-n+"A"+n+","+n+" 0 1,"+t+" 0,"+n}var e=hu,r=pu,i=su,u=ql,o=gu,a=vu,l=du;return n.innerRadius=function(t){return arguments.length?(e=En(t),n):e},n.outerRadius=function(t){return arguments.length?(r=En(t),n):r},n.cornerRadius=function(t){return arguments.length?(i=En(t),n):i},n.padRadius=function(t){return arguments.length?(u=t==ql?ql:En(t),n):u},n.startAngle=function(t){return arguments.length?(o=En(t),n):o},n.endAngle=function(t){return arguments.length?(a=En(t),n):a},n.padAngle=function(t){return arguments.length?(l=En(t),n):l},n.centroid=function(){var n=(+e.apply(this,arguments)+ +r.apply(this,arguments))/2,t=(+o.apply(this,arguments)+ +a.apply(this,arguments))/2-Io;return[Math.cos(t)*n,Math.sin(t)*n]},n};var ql="auto";ao.svg.line=function(){return Mu(m)};var Tl=ao.map({linear:xu,"linear-closed":bu,step:_u,"step-before":wu,"step-after":Su,basis:zu,"basis-open":Lu,"basis-closed":qu,bundle:Tu,cardinal:Eu,"cardinal-open":ku,"cardinal-closed":Nu,monotone:Fu});Tl.forEach(function(n,t){t.key=n,t.closed=/-closed$/.test(n)});var Rl=[0,2/3,1/3,0],Dl=[0,1/3,2/3,0],Pl=[0,1/6,2/3,1/6];ao.svg.line.radial=function(){var n=Mu(Hu);return n.radius=n.x,delete n.x,n.angle=n.y,delete n.y,n},wu.reverse=Su,Su.reverse=wu,ao.svg.area=function(){return Ou(m)},ao.svg.area.radial=function(){var n=Ou(Hu);return n.radius=n.x,delete n.x,n.innerRadius=n.x0,delete n.x0,n.outerRadius=n.x1,delete n.x1,n.angle=n.y,delete n.y,n.startAngle=n.y0,delete n.y0,n.endAngle=n.y1,delete n.y1,n},ao.svg.chord=function(){function n(n,a){var l=t(this,u,n,a),c=t(this,o,n,a);return"M"+l.p0+r(l.r,l.p1,l.a1-l.a0)+(e(l,c)?i(l.r,l.p1,l.r,l.p0):i(l.r,l.p1,c.r,c.p0)+r(c.r,c.p1,c.a1-c.a0)+i(c.r,c.p1,l.r,l.p0))+"Z"}function t(n,t,e,r){var i=t.call(n,e,r),u=a.call(n,i,r),o=l.call(n,i,r)-Io,f=c.call(n,i,r)-Io;return{r:u,a0:o,a1:f,p0:[u*Math.cos(o),u*Math.sin(o)],p1:[u*Math.cos(f),u*Math.sin(f)]}}function e(n,t){return n.a0==t.a0&&n.a1==t.a1}function r(n,t,e){return"A"+n+","+n+" 0 "+ +(e>Fo)+",1 "+t}function i(n,t,e,r){return"Q 0,0 "+r}var u=Me,o=xe,a=Iu,l=gu,c=vu;return n.radius=function(t){return arguments.length?(a=En(t),n):a},n.source=function(t){return arguments.length?(u=En(t),n):u},n.target=function(t){return arguments.length?(o=En(t),n):o},n.startAngle=function(t){return arguments.length?(l=En(t),n):l},n.endAngle=function(t){return arguments.length?(c=En(t),n):c},n},ao.svg.diagonal=function(){function n(n,i){var u=t.call(this,n,i),o=e.call(this,n,i),a=(u.y+o.y)/2,l=[u,{x:u.x,y:a},{x:o.x,y:a},o];return l=l.map(r),"M"+l[0]+"C"+l[1]+" "+l[2]+" "+l[3]}var t=Me,e=xe,r=Yu;return n.source=function(e){return arguments.length?(t=En(e),n):t},n.target=function(t){return arguments.length?(e=En(t),n):e},n.projection=function(t){return arguments.length?(r=t,n):r},n},ao.svg.diagonal.radial=function(){var n=ao.svg.diagonal(),t=Yu,e=n.projection;return n.projection=function(n){return arguments.length?e(Zu(t=n)):t},n},ao.svg.symbol=function(){function n(n,r){return(Ul.get(t.call(this,n,r))||$u)(e.call(this,n,r))}var t=Xu,e=Vu;return n.type=function(e){return arguments.length?(t=En(e),n):t},n.size=function(t){return arguments.length?(e=En(t),n):e},n};var Ul=ao.map({circle:$u,cross:function(n){var t=Math.sqrt(n/5)/2;return"M"+-3*t+","+-t+"H"+-t+"V"+-3*t+"H"+t+"V"+-t+"H"+3*t+"V"+t+"H"+t+"V"+3*t+"H"+-t+"V"+t+"H"+-3*t+"Z"},diamond:function(n){var t=Math.sqrt(n/(2*Fl)),e=t*Fl;return"M0,"+-t+"L"+e+",0 0,"+t+" "+-e+",0Z"},square:function(n){var t=Math.sqrt(n)/2;return"M"+-t+","+-t+"L"+t+","+-t+" "+t+","+t+" "+-t+","+t+"Z"},"triangle-down":function(n){var t=Math.sqrt(n/jl),e=t*jl/2;return"M0,"+e+"L"+t+","+-e+" "+-t+","+-e+"Z"},"triangle-up":function(n){var t=Math.sqrt(n/jl),e=t*jl/2;return"M0,"+-e+"L"+t+","+e+" "+-t+","+e+"Z"}});ao.svg.symbolTypes=Ul.keys();var jl=Math.sqrt(3),Fl=Math.tan(30*Yo);Co.transition=function(n){for(var t,e,r=Hl||++Zl,i=Ku(n),u=[],o=Ol||{time:Date.now(),ease:Nr,delay:0,duration:250},a=-1,l=this.length;++au;u++){i.push(t=[]);for(var e=this[u],a=0,l=e.length;l>a;a++)(r=e[a])&&n.call(r,r.__data__,a,u)&&t.push(r)}return Wu(i,this.namespace,this.id)},Yl.tween=function(n,t){var e=this.id,r=this.namespace;return arguments.length<2?this.node()[r][e].tween.get(n):Y(this,null==t?function(t){t[r][e].tween.remove(n)}:function(i){i[r][e].tween.set(n,t)})},Yl.attr=function(n,t){function e(){this.removeAttribute(a)}function r(){this.removeAttributeNS(a.space,a.local)}function i(n){return null==n?e:(n+="",function(){var t,e=this.getAttribute(a);return e!==n&&(t=o(e,n),function(n){this.setAttribute(a,t(n))})})}function u(n){return null==n?r:(n+="",function(){var t,e=this.getAttributeNS(a.space,a.local);return e!==n&&(t=o(e,n),function(n){this.setAttributeNS(a.space,a.local,t(n))})})}if(arguments.length<2){for(t in n)this.attr(t,n[t]);return this}var o="transform"==n?$r:Mr,a=ao.ns.qualify(n);return Ju(this,"attr."+n,t,a.local?u:i)},Yl.attrTween=function(n,t){function e(n,e){var r=t.call(this,n,e,this.getAttribute(i));return r&&function(n){this.setAttribute(i,r(n))}}function r(n,e){var r=t.call(this,n,e,this.getAttributeNS(i.space,i.local));return r&&function(n){this.setAttributeNS(i.space,i.local,r(n))}}var i=ao.ns.qualify(n);return this.tween("attr."+n,i.local?r:e)},Yl.style=function(n,e,r){function i(){this.style.removeProperty(n)}function u(e){return null==e?i:(e+="",function(){var i,u=t(this).getComputedStyle(this,null).getPropertyValue(n);return u!==e&&(i=Mr(u,e),function(t){this.style.setProperty(n,i(t),r)})})}var o=arguments.length;if(3>o){if("string"!=typeof n){2>o&&(e="");for(r in n)this.style(r,n[r],e);return this}r=""}return Ju(this,"style."+n,e,u)},Yl.styleTween=function(n,e,r){function i(i,u){var o=e.call(this,i,u,t(this).getComputedStyle(this,null).getPropertyValue(n));return o&&function(t){this.style.setProperty(n,o(t),r)}}return arguments.length<3&&(r=""),this.tween("style."+n,i)},Yl.text=function(n){return Ju(this,"text",n,Gu)},Yl.remove=function(){var n=this.namespace;return this.each("end.transition",function(){var t;this[n].count<2&&(t=this.parentNode)&&t.removeChild(this)})},Yl.ease=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].ease:("function"!=typeof n&&(n=ao.ease.apply(ao,arguments)),Y(this,function(r){r[e][t].ease=n}))},Yl.delay=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].delay:Y(this,"function"==typeof n?function(r,i,u){r[e][t].delay=+n.call(r,r.__data__,i,u)}:(n=+n,function(r){r[e][t].delay=n}))},Yl.duration=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].duration:Y(this,"function"==typeof n?function(r,i,u){r[e][t].duration=Math.max(1,n.call(r,r.__data__,i,u))}:(n=Math.max(1,n),function(r){r[e][t].duration=n}))},Yl.each=function(n,t){var e=this.id,r=this.namespace;if(arguments.length<2){var i=Ol,u=Hl;try{Hl=e,Y(this,function(t,i,u){Ol=t[r][e],n.call(t,t.__data__,i,u)})}finally{Ol=i,Hl=u}}else Y(this,function(i){var u=i[r][e];(u.event||(u.event=ao.dispatch("start","end","interrupt"))).on(n,t)});return this},Yl.transition=function(){for(var n,t,e,r,i=this.id,u=++Zl,o=this.namespace,a=[],l=0,c=this.length;c>l;l++){a.push(n=[]);for(var t=this[l],f=0,s=t.length;s>f;f++)(e=t[f])&&(r=e[o][i],Qu(e,f,o,u,{time:r.time,ease:r.ease,delay:r.delay+r.duration,duration:r.duration})),n.push(e)}return Wu(a,o,u)},ao.svg.axis=function(){function n(n){n.each(function(){var n,c=ao.select(this),f=this.__chart__||e,s=this.__chart__=e.copy(),h=null==l?s.ticks?s.ticks.apply(s,a):s.domain():l,p=null==t?s.tickFormat?s.tickFormat.apply(s,a):m:t,g=c.selectAll(".tick").data(h,s),v=g.enter().insert("g",".domain").attr("class","tick").style("opacity",Uo),d=ao.transition(g.exit()).style("opacity",Uo).remove(),y=ao.transition(g.order()).style("opacity",1),M=Math.max(i,0)+o,x=Zi(s),b=c.selectAll(".domain").data([0]),_=(b.enter().append("path").attr("class","domain"),ao.transition(b));v.append("line"),v.append("text");var w,S,k,N,E=v.select("line"),A=y.select("line"),C=g.select("text").text(p),z=v.select("text"),L=y.select("text"),q="top"===r||"left"===r?-1:1;if("bottom"===r||"top"===r?(n=no,w="x",k="y",S="x2",N="y2",C.attr("dy",0>q?"0em":".71em").style("text-anchor","middle"),_.attr("d","M"+x[0]+","+q*u+"V0H"+x[1]+"V"+q*u)):(n=to,w="y",k="x",S="y2",N="x2",C.attr("dy",".32em").style("text-anchor",0>q?"end":"start"),_.attr("d","M"+q*u+","+x[0]+"H0V"+x[1]+"H"+q*u)),E.attr(N,q*i),z.attr(k,q*M),A.attr(S,0).attr(N,q*i),L.attr(w,0).attr(k,q*M),s.rangeBand){var T=s,R=T.rangeBand()/2;f=s=function(n){return T(n)+R}}else f.rangeBand?f=s:d.call(n,s,f);v.call(n,f,s),y.call(n,s,s)})}var t,e=ao.scale.linear(),r=Vl,i=6,u=6,o=3,a=[10],l=null;return n.scale=function(t){return arguments.length?(e=t,n):e},n.orient=function(t){return arguments.length?(r=t in Xl?t+"":Vl,n):r},n.ticks=function(){return arguments.length?(a=co(arguments),n):a},n.tickValues=function(t){return arguments.length?(l=t,n):l},n.tickFormat=function(e){return arguments.length?(t=e,n):t},n.tickSize=function(t){var e=arguments.length;return e?(i=+t,u=+arguments[e-1],n):i},n.innerTickSize=function(t){return arguments.length?(i=+t,n):i},n.outerTickSize=function(t){return arguments.length?(u=+t,n):u},n.tickPadding=function(t){return arguments.length?(o=+t,n):o},n.tickSubdivide=function(){return arguments.length&&n},n};var Vl="bottom",Xl={top:1,right:1,bottom:1,left:1};ao.svg.brush=function(){function n(t){t.each(function(){var t=ao.select(this).style("pointer-events","all").style("-webkit-tap-highlight-color","rgba(0,0,0,0)").on("mousedown.brush",u).on("touchstart.brush",u),o=t.selectAll(".background").data([0]);o.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),t.selectAll(".extent").data([0]).enter().append("rect").attr("class","extent").style("cursor","move");var a=t.selectAll(".resize").data(v,m);a.exit().remove(),a.enter().append("g").attr("class",function(n){return"resize "+n}).style("cursor",function(n){return $l[n]}).append("rect").attr("x",function(n){return/[ew]$/.test(n)?-3:null}).attr("y",function(n){return/^[ns]/.test(n)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),a.style("display",n.empty()?"none":null);var l,s=ao.transition(t),h=ao.transition(o);c&&(l=Zi(c),h.attr("x",l[0]).attr("width",l[1]-l[0]),r(s)),f&&(l=Zi(f),h.attr("y",l[0]).attr("height",l[1]-l[0]),i(s)),e(s)})}function e(n){n.selectAll(".resize").attr("transform",function(n){return"translate("+s[+/e$/.test(n)]+","+h[+/^s/.test(n)]+")"})}function r(n){n.select(".extent").attr("x",s[0]),n.selectAll(".extent,.n>rect,.s>rect").attr("width",s[1]-s[0])}function i(n){n.select(".extent").attr("y",h[0]),n.selectAll(".extent,.e>rect,.w>rect").attr("height",h[1]-h[0])}function u(){function u(){32==ao.event.keyCode&&(C||(M=null,L[0]-=s[1],L[1]-=h[1],C=2),S())}function v(){32==ao.event.keyCode&&2==C&&(L[0]+=s[1],L[1]+=h[1],C=0,S())}function d(){var n=ao.mouse(b),t=!1;x&&(n[0]+=x[0],n[1]+=x[1]),C||(ao.event.altKey?(M||(M=[(s[0]+s[1])/2,(h[0]+h[1])/2]),L[0]=s[+(n[0]f?(i=r,r=f):i=f),v[0]!=r||v[1]!=i?(e?a=null:o=null,v[0]=r,v[1]=i,!0):void 0}function m(){d(),k.style("pointer-events","all").selectAll(".resize").style("display",n.empty()?"none":null),ao.select("body").style("cursor",null),q.on("mousemove.brush",null).on("mouseup.brush",null).on("touchmove.brush",null).on("touchend.brush",null).on("keydown.brush",null).on("keyup.brush",null),z(),w({type:"brushend"})}var M,x,b=this,_=ao.select(ao.event.target),w=l.of(b,arguments),k=ao.select(b),N=_.datum(),E=!/^(n|s)$/.test(N)&&c,A=!/^(e|w)$/.test(N)&&f,C=_.classed("extent"),z=W(b),L=ao.mouse(b),q=ao.select(t(b)).on("keydown.brush",u).on("keyup.brush",v);if(ao.event.changedTouches?q.on("touchmove.brush",d).on("touchend.brush",m):q.on("mousemove.brush",d).on("mouseup.brush",m),k.interrupt().selectAll("*").interrupt(),C)L[0]=s[0]-L[0],L[1]=h[0]-L[1];else if(N){var T=+/w$/.test(N),R=+/^n/.test(N);x=[s[1-T]-L[0],h[1-R]-L[1]],L[0]=s[T],L[1]=h[R]}else ao.event.altKey&&(M=L.slice());k.style("pointer-events","none").selectAll(".resize").style("display",null),ao.select("body").style("cursor",_.style("cursor")),w({type:"brushstart"}),d()}var o,a,l=N(n,"brushstart","brush","brushend"),c=null,f=null,s=[0,0],h=[0,0],p=!0,g=!0,v=Bl[0];return n.event=function(n){n.each(function(){var n=l.of(this,arguments),t={x:s,y:h,i:o,j:a},e=this.__chart__||t;this.__chart__=t,Hl?ao.select(this).transition().each("start.brush",function(){o=e.i,a=e.j,s=e.x,h=e.y,n({type:"brushstart"})}).tween("brush:brush",function(){var e=xr(s,t.x),r=xr(h,t.y);return o=a=null,function(i){s=t.x=e(i),h=t.y=r(i),n({type:"brush",mode:"resize"})}}).each("end.brush",function(){o=t.i,a=t.j,n({type:"brush",mode:"resize"}),n({type:"brushend"})}):(n({type:"brushstart"}),n({type:"brush",mode:"resize"}),n({type:"brushend"}))})},n.x=function(t){return arguments.length?(c=t,v=Bl[!c<<1|!f],n):c},n.y=function(t){return arguments.length?(f=t,v=Bl[!c<<1|!f],n):f},n.clamp=function(t){return arguments.length?(c&&f?(p=!!t[0],g=!!t[1]):c?p=!!t:f&&(g=!!t),n):c&&f?[p,g]:c?p:f?g:null},n.extent=function(t){var e,r,i,u,l;return arguments.length?(c&&(e=t[0],r=t[1],f&&(e=e[0],r=r[0]),o=[e,r],c.invert&&(e=c(e),r=c(r)),e>r&&(l=e,e=r,r=l),e==s[0]&&r==s[1]||(s=[e,r])),f&&(i=t[0],u=t[1],c&&(i=i[1],u=u[1]),a=[i,u],f.invert&&(i=f(i),u=f(u)),i>u&&(l=i,i=u,u=l),i==h[0]&&u==h[1]||(h=[i,u])),n):(c&&(o?(e=o[0],r=o[1]):(e=s[0],r=s[1],c.invert&&(e=c.invert(e),r=c.invert(r)),e>r&&(l=e,e=r,r=l))),f&&(a?(i=a[0],u=a[1]):(i=h[0],u=h[1],f.invert&&(i=f.invert(i),u=f.invert(u)),i>u&&(l=i,i=u,u=l))),c&&f?[[e,i],[r,u]]:c?[e,r]:f&&[i,u])},n.clear=function(){return n.empty()||(s=[0,0],h=[0,0],o=a=null),n},n.empty=function(){return!!c&&s[0]==s[1]||!!f&&h[0]==h[1]},ao.rebind(n,l,"on")};var $l={n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},Bl=[["n","e","s","w","nw","ne","se","sw"],["e","w"],["n","s"],[]],Wl=ga.format=xa.timeFormat,Jl=Wl.utc,Gl=Jl("%Y-%m-%dT%H:%M:%S.%LZ");Wl.iso=Date.prototype.toISOString&&+new Date("2000-01-01T00:00:00.000Z")?eo:Gl,eo.parse=function(n){var t=new Date(n);return isNaN(t)?null:t},eo.toString=Gl.toString,ga.second=On(function(n){return new va(1e3*Math.floor(n/1e3))},function(n,t){n.setTime(n.getTime()+1e3*Math.floor(t))},function(n){return n.getSeconds()}),ga.seconds=ga.second.range,ga.seconds.utc=ga.second.utc.range,ga.minute=On(function(n){return new va(6e4*Math.floor(n/6e4))},function(n,t){n.setTime(n.getTime()+6e4*Math.floor(t))},function(n){return n.getMinutes()}),ga.minutes=ga.minute.range,ga.minutes.utc=ga.minute.utc.range,ga.hour=On(function(n){var t=n.getTimezoneOffset()/60;return new va(36e5*(Math.floor(n/36e5-t)+t))},function(n,t){n.setTime(n.getTime()+36e5*Math.floor(t))},function(n){return n.getHours()}),ga.hours=ga.hour.range,ga.hours.utc=ga.hour.utc.range,ga.month=On(function(n){return n=ga.day(n),n.setDate(1),n},function(n,t){n.setMonth(n.getMonth()+t)},function(n){return n.getMonth()}),ga.months=ga.month.range,ga.months.utc=ga.month.utc.range;var Kl=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],Ql=[[ga.second,1],[ga.second,5],[ga.second,15],[ga.second,30],[ga.minute,1],[ga.minute,5],[ga.minute,15],[ga.minute,30],[ga.hour,1],[ga.hour,3],[ga.hour,6],[ga.hour,12],[ga.day,1],[ga.day,2],[ga.week,1],[ga.month,1],[ga.month,3],[ga.year,1]],nc=Wl.multi([[".%L",function(n){return n.getMilliseconds()}],[":%S",function(n){return n.getSeconds()}],["%I:%M",function(n){return n.getMinutes()}],["%I %p",function(n){return n.getHours()}],["%a %d",function(n){return n.getDay()&&1!=n.getDate()}],["%b %d",function(n){return 1!=n.getDate()}],["%B",function(n){return n.getMonth()}],["%Y",zt]]),tc={range:function(n,t,e){return ao.range(Math.ceil(n/e)*e,+t,e).map(io)},floor:m,ceil:m};Ql.year=ga.year,ga.scale=function(){return ro(ao.scale.linear(),Ql,nc)};var ec=Ql.map(function(n){return[n[0].utc,n[1]]}),rc=Jl.multi([[".%L",function(n){return n.getUTCMilliseconds()}],[":%S",function(n){return n.getUTCSeconds()}],["%I:%M",function(n){return n.getUTCMinutes()}],["%I %p",function(n){return n.getUTCHours()}],["%a %d",function(n){return n.getUTCDay()&&1!=n.getUTCDate()}],["%b %d",function(n){return 1!=n.getUTCDate()}],["%B",function(n){return n.getUTCMonth()}],["%Y",zt]]);ec.year=ga.year.utc,ga.scale.utc=function(){return ro(ao.scale.linear(),ec,rc)},ao.text=An(function(n){return n.responseText}),ao.json=function(n,t){return Cn(n,"application/json",uo,t)},ao.html=function(n,t){return Cn(n,"text/html",oo,t)},ao.xml=An(function(n){return n.responseXML}),"function"==typeof define&&define.amd?(this.d3=ao,define(ao)):"object"==typeof module&&module.exports?module.exports=ao:this.d3=ao}(); \ No newline at end of file diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/file.js b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/file.js new file mode 100644 index 00000000..29cacd4d --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/file.js @@ -0,0 +1,62 @@ + $(function() { + var $window = $(window) + , $top_link = $('#toplink') + , $body = $('body, html') + , offset = $('#code').offset().top + , hidePopover = function ($target) { + $target.data('popover-hover', false); + + setTimeout(function () { + if (!$target.data('popover-hover')) { + $target.popover('hide'); + } + }, 300); + }; + + $top_link.hide().click(function(event) { + event.preventDefault(); + $body.animate({scrollTop:0}, 800); + }); + + $window.scroll(function() { + if($window.scrollTop() > offset) { + $top_link.fadeIn(); + } else { + $top_link.fadeOut(); + } + }).scroll(); + + $('.popin') + .popover({trigger: 'manual'}) + .on({ + 'mouseenter.popover': function () { + var $target = $(this); + var $container = $target.children().first(); + + $target.data('popover-hover', true); + + // popover already displayed + if ($target.next('.popover').length) { + return; + } + + // show the popover + $container.popover('show'); + + // register mouse events on the popover + $target.next('.popover:not(.popover-initialized)') + .on({ + 'mouseenter': function () { + $target.data('popover-hover', true); + }, + 'mouseleave': function () { + hidePopover($container); + } + }) + .addClass('popover-initialized'); + }, + 'mouseleave.popover': function () { + hidePopover($(this).children().first()); + } + }); + }); diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/jquery.min.js b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/jquery.min.js new file mode 100644 index 00000000..2c69bc90 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/jquery.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.6.1 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,y=n.hasOwnProperty,a=y.toString,l=a.call(Object),v={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.1",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&v(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!y||!y.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ve(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ye(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ve(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],y=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&y.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||y.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||y.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||y.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||y.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||y.push(".#.+[+~]"),e.querySelectorAll("\\\f"),y.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&y.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&y.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&y.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),y.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),y=y.length&&new RegExp(y.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),v=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&v(p,e)?-1:t==C||t.ownerDocument==p&&v(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!y||!y.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),v.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",v.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",v.option=!!ce.lastChild;var ge={thead:[1,"","
      "],col:[2,"","
      "],tr:[2,"","
      "],td:[3,"","
      "],_default:[0,"",""]};function ye(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),v.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="
      ",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(v.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return B(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=_e(v.pixelPosition,function(e,t){if(t)return t=Be(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return B(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0f&&(e=a.render.queue[f]);f++)d=e.generate(),typeof e.callback==typeof Function&&e.callback(d);a.render.queue.splice(0,f),a.render.queue.length?setTimeout(c):(a.dispatch.render_end(),a.render.active=!1)};setTimeout(c)},a.render.active=!1,a.render.queue=[],a.addGraph=function(b){typeof arguments[0]==typeof Function&&(b={generate:arguments[0],callback:arguments[1]}),a.render.queue.push(b),a.render.active||a.render()},"undefined"!=typeof module&&"undefined"!=typeof exports&&(module.exports=a),"undefined"!=typeof window&&(window.nv=a),a.dom.write=function(a){return void 0!==window.fastdom?fastdom.write(a):a()},a.dom.read=function(a){return void 0!==window.fastdom?fastdom.read(a):a()},a.interactiveGuideline=function(){"use strict";function b(l){l.each(function(l){function m(){var a=d3.mouse(this),d=a[0],e=a[1],i=!0,j=!1;if(k&&(d=d3.event.offsetX,e=d3.event.offsetY,"svg"!==d3.event.target.tagName&&(i=!1),d3.event.target.className.baseVal.match("nv-legend")&&(j=!0)),i&&(d-=f.left,e-=f.top),0>d||0>e||d>o||e>p||d3.event.relatedTarget&&void 0===d3.event.relatedTarget.ownerSVGElement||j){if(k&&d3.event.relatedTarget&&void 0===d3.event.relatedTarget.ownerSVGElement&&(void 0===d3.event.relatedTarget.className||d3.event.relatedTarget.className.match(c.nvPointerEventsClass)))return;return h.elementMouseout({mouseX:d,mouseY:e}),b.renderGuideLine(null),void c.hidden(!0)}c.hidden(!1);var l=g.invert(d);h.elementMousemove({mouseX:d,mouseY:e,pointXValue:l}),"dblclick"===d3.event.type&&h.elementDblclick({mouseX:d,mouseY:e,pointXValue:l}),"click"===d3.event.type&&h.elementClick({mouseX:d,mouseY:e,pointXValue:l})}var n=d3.select(this),o=d||960,p=e||400,q=n.selectAll("g.nv-wrap.nv-interactiveLineLayer").data([l]),r=q.enter().append("g").attr("class"," nv-wrap nv-interactiveLineLayer");r.append("g").attr("class","nv-interactiveGuideLine"),j&&(j.on("touchmove",m).on("mousemove",m,!0).on("mouseout",m,!0).on("dblclick",m).on("click",m),b.guideLine=null,b.renderGuideLine=function(c){i&&(b.guideLine&&b.guideLine.attr("x1")===c||a.dom.write(function(){var b=q.select(".nv-interactiveGuideLine").selectAll("line").data(null!=c?[a.utils.NaNtoZero(c)]:[],String);b.enter().append("line").attr("class","nv-guideline").attr("x1",function(a){return a}).attr("x2",function(a){return a}).attr("y1",p).attr("y2",0),b.exit().remove()}))})})}var c=a.models.tooltip();c.duration(0).hideDelay(0)._isInteractiveLayer(!0).hidden(!1);var d=null,e=null,f={left:0,top:0},g=d3.scale.linear(),h=d3.dispatch("elementMousemove","elementMouseout","elementClick","elementDblclick"),i=!0,j=null,k="ActiveXObject"in window;return b.dispatch=h,b.tooltip=c,b.margin=function(a){return arguments.length?(f.top="undefined"!=typeof a.top?a.top:f.top,f.left="undefined"!=typeof a.left?a.left:f.left,b):f},b.width=function(a){return arguments.length?(d=a,b):d},b.height=function(a){return arguments.length?(e=a,b):e},b.xScale=function(a){return arguments.length?(g=a,b):g},b.showGuideLine=function(a){return arguments.length?(i=a,b):i},b.svgContainer=function(a){return arguments.length?(j=a,b):j},b},a.interactiveBisect=function(a,b,c){"use strict";if(!(a instanceof Array))return null;var d;d="function"!=typeof c?function(a){return a.x}:c;var e=function(a,b){return d(a)-b},f=d3.bisector(e).left,g=d3.max([0,f(a,b)-1]),h=d(a[g]);if("undefined"==typeof h&&(h=g),h===b)return g;var i=d3.min([g+1,a.length-1]),j=d(a[i]);return"undefined"==typeof j&&(j=i),Math.abs(j-b)>=Math.abs(h-b)?g:i},a.nearestValueIndex=function(a,b,c){"use strict";var d=1/0,e=null;return a.forEach(function(a,f){var g=Math.abs(b-a);null!=a&&d>=g&&c>g&&(d=g,e=f)}),e},function(){"use strict";a.models.tooltip=function(){function b(){if(k){var a=d3.select(k);"svg"!==a.node().tagName&&(a=a.select("svg"));var b=a.node()?a.attr("viewBox"):null;if(b){b=b.split(" ");var c=parseInt(a.style("width"),10)/b[2];p.left=p.left*c,p.top=p.top*c}}}function c(){if(!n){var a;a=k?k:document.body,n=d3.select(a).append("div").attr("class","nvtooltip "+(j?j:"xy-tooltip")).attr("id",v),n.style("top",0).style("left",0),n.style("opacity",0),n.selectAll("div, table, td, tr").classed(w,!0),n.classed(w,!0),o=n.node()}}function d(){if(r&&B(e)){b();var f=p.left,g=null!==i?i:p.top;return a.dom.write(function(){c();var b=A(e);b&&(o.innerHTML=b),k&&u?a.dom.read(function(){var a=k.getElementsByTagName("svg")[0],b={left:0,top:0};if(a){var c=a.getBoundingClientRect(),d=k.getBoundingClientRect(),e=c.top;if(0>e){var i=k.getBoundingClientRect();e=Math.abs(e)>i.height?0:e}b.top=Math.abs(e-d.top),b.left=Math.abs(c.left-d.left)}f+=k.offsetLeft+b.left-2*k.scrollLeft,g+=k.offsetTop+b.top-2*k.scrollTop,h&&h>0&&(g=Math.floor(g/h)*h),C([f,g])}):C([f,g])}),d}}var e=null,f="w",g=25,h=0,i=null,j=null,k=null,l=!0,m=400,n=null,o=null,p={left:null,top:null},q={left:0,top:0},r=!0,s=100,t=!0,u=!1,v="nvtooltip-"+Math.floor(1e5*Math.random()),w="nv-pointer-events-none",x=function(a){return a},y=function(a){return a},z=function(a){return a},A=function(a){if(null===a)return"";var b=d3.select(document.createElement("table"));if(t){var c=b.selectAll("thead").data([a]).enter().append("thead");c.append("tr").append("td").attr("colspan",3).append("strong").classed("x-value",!0).html(y(a.value))}var d=b.selectAll("tbody").data([a]).enter().append("tbody"),e=d.selectAll("tr").data(function(a){return a.series}).enter().append("tr").classed("highlight",function(a){return a.highlight});e.append("td").classed("legend-color-guide",!0).append("div").style("background-color",function(a){return a.color}),e.append("td").classed("key",!0).html(function(a,b){return z(a.key,b)}),e.append("td").classed("value",!0).html(function(a,b){return x(a.value,b)}),e.selectAll("td").each(function(a){if(a.highlight){var b=d3.scale.linear().domain([0,1]).range(["#fff",a.color]),c=.6;d3.select(this).style("border-bottom-color",b(c)).style("border-top-color",b(c))}});var f=b.node().outerHTML;return void 0!==a.footer&&(f+=""),f},B=function(a){if(a&&a.series){if(a.series instanceof Array)return!!a.series.length;if(a.series instanceof Object)return a.series=[a.series],!0}return!1},C=function(b){o&&a.dom.read(function(){var c,d,e=parseInt(o.offsetHeight,10),h=parseInt(o.offsetWidth,10),i=a.utils.windowSize().width,j=a.utils.windowSize().height,k=window.pageYOffset,p=window.pageXOffset;j=window.innerWidth>=document.body.scrollWidth?j:j-16,i=window.innerHeight>=document.body.scrollHeight?i:i-16;var r,t,u=function(a){var b=d;do isNaN(a.offsetTop)||(b+=a.offsetTop),a=a.offsetParent;while(a);return b},v=function(a){var b=c;do isNaN(a.offsetLeft)||(b+=a.offsetLeft),a=a.offsetParent;while(a);return b};switch(f){case"e":c=b[0]-h-g,d=b[1]-e/2,r=v(o),t=u(o),p>r&&(c=b[0]+g>p?b[0]+g:p-r+c),k>t&&(d=k-t+d),t+e>k+j&&(d=k+j-t+d-e);break;case"w":c=b[0]+g,d=b[1]-e/2,r=v(o),t=u(o),r+h>i&&(c=b[0]-h-g),k>t&&(d=k+5),t+e>k+j&&(d=k+j-t+d-e);break;case"n":c=b[0]-h/2-5,d=b[1]+g,r=v(o),t=u(o),p>r&&(c=p+5),r+h>i&&(c=c-h/2+5),t+e>k+j&&(d=k+j-t+d-e);break;case"s":c=b[0]-h/2,d=b[1]-e-g,r=v(o),t=u(o),p>r&&(c=p+5),r+h>i&&(c=c-h/2+5),k>t&&(d=k);break;case"none":c=b[0],d=b[1]-g,r=v(o),t=u(o)}c-=q.left,d-=q.top;var w=o.getBoundingClientRect(),k=window.pageYOffset||document.documentElement.scrollTop,p=window.pageXOffset||document.documentElement.scrollLeft,x="translate("+(w.left+p)+"px, "+(w.top+k)+"px)",y="translate("+c+"px, "+d+"px)",z=d3.interpolateString(x,y),A=n.style("opacity")<.1;l?n.transition().delay(m).duration(0).style("opacity",0):n.interrupt().transition().duration(A?0:s).styleTween("transform",function(){return z},"important").style("-webkit-transform",y).style("opacity",1)})};return d.nvPointerEventsClass=w,d.options=a.utils.optionsFunc.bind(d),d._options=Object.create({},{duration:{get:function(){return s},set:function(a){s=a}},gravity:{get:function(){return f},set:function(a){f=a}},distance:{get:function(){return g},set:function(a){g=a}},snapDistance:{get:function(){return h},set:function(a){h=a}},classes:{get:function(){return j},set:function(a){j=a}},chartContainer:{get:function(){return k},set:function(a){k=a}},fixedTop:{get:function(){return i},set:function(a){i=a}},enabled:{get:function(){return r},set:function(a){r=a}},hideDelay:{get:function(){return m},set:function(a){m=a}},contentGenerator:{get:function(){return A},set:function(a){A=a}},valueFormatter:{get:function(){return x},set:function(a){x=a}},headerFormatter:{get:function(){return y},set:function(a){y=a}},keyFormatter:{get:function(){return z},set:function(a){z=a}},headerEnabled:{get:function(){return t},set:function(a){t=a}},_isInteractiveLayer:{get:function(){return u},set:function(a){u=!!a}},position:{get:function(){return p},set:function(a){p.left=void 0!==a.left?a.left:p.left,p.top=void 0!==a.top?a.top:p.top}},offset:{get:function(){return q},set:function(a){q.left=void 0!==a.left?a.left:q.left,q.top=void 0!==a.top?a.top:q.top}},hidden:{get:function(){return l},set:function(a){l!=a&&(l=!!a,d())}},data:{get:function(){return e},set:function(a){a.point&&(a.value=a.point.x,a.series=a.series||{},a.series.value=a.point.y,a.series.color=a.point.color||a.series.color),e=a}},tooltipElem:{get:function(){return o},set:function(){}},id:{get:function(){return v},set:function(){}}}),a.utils.initOptions(d),d}}(),a.utils.windowSize=function(){var a={width:640,height:480};return window.innerWidth&&window.innerHeight?(a.width=window.innerWidth,a.height=window.innerHeight,a):"CSS1Compat"==document.compatMode&&document.documentElement&&document.documentElement.offsetWidth?(a.width=document.documentElement.offsetWidth,a.height=document.documentElement.offsetHeight,a):document.body&&document.body.offsetWidth?(a.width=document.body.offsetWidth,a.height=document.body.offsetHeight,a):a},a.utils.windowResize=function(b){return window.addEventListener?window.addEventListener("resize",b):a.log("ERROR: Failed to bind to window.resize with: ",b),{callback:b,clear:function(){window.removeEventListener("resize",b)}}},a.utils.getColor=function(b){if(void 0===b)return a.utils.defaultColor();if(Array.isArray(b)){var c=d3.scale.ordinal().range(b);return function(a,b){var d=void 0===b?a:b;return a.color||c(d)}}return b},a.utils.defaultColor=function(){return a.utils.getColor(d3.scale.category20().range())},a.utils.customTheme=function(a,b,c){b=b||function(a){return a.key},c=c||d3.scale.category20().range();var d=c.length;return function(e){var f=b(e);return"function"==typeof a[f]?a[f]():void 0!==a[f]?a[f]:(d||(d=c.length),d-=1,c[d])}},a.utils.pjax=function(b,c){var d=function(d){d3.html(d,function(d){var e=d3.select(c).node();e.parentNode.replaceChild(d3.select(d).select(c).node(),e),a.utils.pjax(b,c)})};d3.selectAll(b).on("click",function(){history.pushState(this.href,this.textContent,this.href),d(this.href),d3.event.preventDefault()}),d3.select(window).on("popstate",function(){d3.event.state&&d(d3.event.state)})},a.utils.calcApproxTextWidth=function(a){if("function"==typeof a.style&&"function"==typeof a.text){var b=parseInt(a.style("font-size").replace("px",""),10),c=a.text().length;return c*b*.5}return 0},a.utils.NaNtoZero=function(a){return"number"!=typeof a||isNaN(a)||null===a||1/0===a||a===-1/0?0:a},d3.selection.prototype.watchTransition=function(a){var b=[this].concat([].slice.call(arguments,1));return a.transition.apply(a,b)},a.utils.renderWatch=function(b,c){if(!(this instanceof a.utils.renderWatch))return new a.utils.renderWatch(b,c);var d=void 0!==c?c:250,e=[],f=this;this.models=function(a){return a=[].slice.call(arguments,0),a.forEach(function(a){a.__rendered=!1,function(a){a.dispatch.on("renderEnd",function(){a.__rendered=!0,f.renderEnd("model")})}(a),e.indexOf(a)<0&&e.push(a)}),this},this.reset=function(a){void 0!==a&&(d=a),e=[]},this.transition=function(a,b,c){if(b=arguments.length>1?[].slice.call(arguments,1):[],c=b.length>1?b.pop():void 0!==d?d:250,a.__rendered=!1,e.indexOf(a)<0&&e.push(a),0===c)return a.__rendered=!0,a.delay=function(){return this},a.duration=function(){return this},a;a.__rendered=0===a.length?!0:a.every(function(a){return!a.length})?!0:!1;var g=0;return a.transition().duration(c).each(function(){++g}).each("end",function(){0===--g&&(a.__rendered=!0,f.renderEnd.apply(this,b))})},this.renderEnd=function(){e.every(function(a){return a.__rendered})&&(e.forEach(function(a){a.__rendered=!1}),b.renderEnd.apply(this,arguments))}},a.utils.deepExtend=function(b){var c=arguments.length>1?[].slice.call(arguments,1):[];c.forEach(function(c){for(var d in c){var e=b[d]instanceof Array,f="object"==typeof b[d],g="object"==typeof c[d];f&&!e&&g?a.utils.deepExtend(b[d],c[d]):b[d]=c[d]}})},a.utils.state=function(){if(!(this instanceof a.utils.state))return new a.utils.state;var b={},c=function(){},d=function(){return{}},e=null,f=null;this.dispatch=d3.dispatch("change","set"),this.dispatch.on("set",function(a){c(a,!0)}),this.getter=function(a){return d=a,this},this.setter=function(a,b){return b||(b=function(){}),c=function(c,d){a(c),d&&b()},this},this.init=function(b){e=e||{},a.utils.deepExtend(e,b)};var g=function(){var a=d();if(JSON.stringify(a)===JSON.stringify(b))return!1;for(var c in a)void 0===b[c]&&(b[c]={}),b[c]=a[c],f=!0;return!0};this.update=function(){e&&(c(e,!1),e=null),g.call(this)&&this.dispatch.change(b)}},a.utils.optionsFunc=function(a){return a&&d3.map(a).forEach(function(a,b){"function"==typeof this[a]&&this[a](b)}.bind(this)),this},a.utils.calcTicksX=function(b,c){var d=1,e=0;for(e;ed?f:d}return a.log("Requested number of ticks: ",b),a.log("Calculated max values to be: ",d),b=b>d?b=d-1:b,b=1>b?1:b,b=Math.floor(b),a.log("Calculating tick count as: ",b),b},a.utils.calcTicksY=function(b,c){return a.utils.calcTicksX(b,c)},a.utils.initOption=function(a,b){a._calls&&a._calls[b]?a[b]=a._calls[b]:(a[b]=function(c){return arguments.length?(a._overrides[b]=!0,a._options[b]=c,a):a._options[b]},a["_"+b]=function(c){return arguments.length?(a._overrides[b]||(a._options[b]=c),a):a._options[b]})},a.utils.initOptions=function(b){b._overrides=b._overrides||{};var c=Object.getOwnPropertyNames(b._options||{}),d=Object.getOwnPropertyNames(b._calls||{});c=c.concat(d);for(var e in c)a.utils.initOption(b,c[e])},a.utils.inheritOptionsD3=function(a,b,c){a._d3options=c.concat(a._d3options||[]),c.unshift(b),c.unshift(a),d3.rebind.apply(this,c)},a.utils.arrayUnique=function(a){return a.sort().filter(function(b,c){return!c||b!=a[c-1]})},a.utils.symbolMap=d3.map(),a.utils.symbol=function(){function b(b,e){var f=c.call(this,b,e),g=d.call(this,b,e);return-1!==d3.svg.symbolTypes.indexOf(f)?d3.svg.symbol().type(f).size(g)():a.utils.symbolMap.get(f)(g)}var c,d=64;return b.type=function(a){return arguments.length?(c=d3.functor(a),b):c},b.size=function(a){return arguments.length?(d=d3.functor(a),b):d},b},a.utils.inheritOptions=function(b,c){var d=Object.getOwnPropertyNames(c._options||{}),e=Object.getOwnPropertyNames(c._calls||{}),f=c._inherited||[],g=c._d3options||[],h=d.concat(e).concat(f).concat(g);h.unshift(c),h.unshift(b),d3.rebind.apply(this,h),b._inherited=a.utils.arrayUnique(d.concat(e).concat(f).concat(d).concat(b._inherited||[])),b._d3options=a.utils.arrayUnique(g.concat(b._d3options||[]))},a.utils.initSVG=function(a){a.classed({"nvd3-svg":!0})},a.utils.sanitizeHeight=function(a,b){return a||parseInt(b.style("height"),10)||400},a.utils.sanitizeWidth=function(a,b){return a||parseInt(b.style("width"),10)||960},a.utils.availableHeight=function(b,c,d){return a.utils.sanitizeHeight(b,c)-d.top-d.bottom},a.utils.availableWidth=function(b,c,d){return a.utils.sanitizeWidth(b,c)-d.left-d.right},a.utils.noData=function(b,c){var d=b.options(),e=d.margin(),f=d.noData(),g=null==f?["No Data Available."]:[f],h=a.utils.availableHeight(d.height(),c,e),i=a.utils.availableWidth(d.width(),c,e),j=e.left+i/2,k=e.top+h/2;c.selectAll("g").remove();var l=c.selectAll(".nv-noData").data(g);l.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),l.attr("x",j).attr("y",k).text(function(a){return a})},a.models.axis=function(){"use strict";function b(g){return s.reset(),g.each(function(b){var g=d3.select(this);a.utils.initSVG(g);var p=g.selectAll("g.nv-wrap.nv-axis").data([b]),q=p.enter().append("g").attr("class","nvd3 nv-wrap nv-axis"),t=(q.append("g"),p.select("g"));null!==n?c.ticks(n):("top"==c.orient()||"bottom"==c.orient())&&c.ticks(Math.abs(d.range()[1]-d.range()[0])/100),t.watchTransition(s,"axis").call(c),r=r||c.scale();var u=c.tickFormat();null==u&&(u=r.tickFormat());var v=t.selectAll("text.nv-axislabel").data([h||null]);v.exit().remove();var w,x,y;switch(c.orient()){case"top":v.enter().append("text").attr("class","nv-axislabel"),y=d.range().length<2?0:2===d.range().length?d.range()[1]:d.range()[d.range().length-1]+(d.range()[1]-d.range()[0]),v.attr("text-anchor","middle").attr("y",0).attr("x",y/2),i&&(x=p.selectAll("g.nv-axisMaxMin").data(d.domain()),x.enter().append("g").attr("class",function(a,b){return["nv-axisMaxMin","nv-axisMaxMin-x",0==b?"nv-axisMin-x":"nv-axisMax-x"].join(" ")}).append("text"),x.exit().remove(),x.attr("transform",function(b){return"translate("+a.utils.NaNtoZero(d(b))+",0)"}).select("text").attr("dy","-0.5em").attr("y",-c.tickPadding()).attr("text-anchor","middle").text(function(a){var b=u(a);return(""+b).match("NaN")?"":b}),x.watchTransition(s,"min-max top").attr("transform",function(b,c){return"translate("+a.utils.NaNtoZero(d.range()[c])+",0)"}));break;case"bottom":w=o+36;var z=30,A=0,B=t.selectAll("g").select("text"),C="";if(j%360){B.each(function(){var a=this.getBoundingClientRect(),b=a.width;A=a.height,b>z&&(z=b)}),C="rotate("+j+" 0,"+(A/2+c.tickPadding())+")";var D=Math.abs(Math.sin(j*Math.PI/180));w=(D?D*z:z)+30,B.attr("transform",C).style("text-anchor",j%360>0?"start":"end")}v.enter().append("text").attr("class","nv-axislabel"),y=d.range().length<2?0:2===d.range().length?d.range()[1]:d.range()[d.range().length-1]+(d.range()[1]-d.range()[0]),v.attr("text-anchor","middle").attr("y",w).attr("x",y/2),i&&(x=p.selectAll("g.nv-axisMaxMin").data([d.domain()[0],d.domain()[d.domain().length-1]]),x.enter().append("g").attr("class",function(a,b){return["nv-axisMaxMin","nv-axisMaxMin-x",0==b?"nv-axisMin-x":"nv-axisMax-x"].join(" ")}).append("text"),x.exit().remove(),x.attr("transform",function(b){return"translate("+a.utils.NaNtoZero(d(b)+(m?d.rangeBand()/2:0))+",0)"}).select("text").attr("dy",".71em").attr("y",c.tickPadding()).attr("transform",C).style("text-anchor",j?j%360>0?"start":"end":"middle").text(function(a){var b=u(a);return(""+b).match("NaN")?"":b}),x.watchTransition(s,"min-max bottom").attr("transform",function(b){return"translate("+a.utils.NaNtoZero(d(b)+(m?d.rangeBand()/2:0))+",0)"})),l&&B.attr("transform",function(a,b){return"translate(0,"+(b%2==0?"0":"12")+")"});break;case"right":v.enter().append("text").attr("class","nv-axislabel"),v.style("text-anchor",k?"middle":"begin").attr("transform",k?"rotate(90)":"").attr("y",k?-Math.max(e.right,f)+12:-10).attr("x",k?d3.max(d.range())/2:c.tickPadding()),i&&(x=p.selectAll("g.nv-axisMaxMin").data(d.domain()),x.enter().append("g").attr("class",function(a,b){return["nv-axisMaxMin","nv-axisMaxMin-y",0==b?"nv-axisMin-y":"nv-axisMax-y"].join(" ")}).append("text").style("opacity",0),x.exit().remove(),x.attr("transform",function(b){return"translate(0,"+a.utils.NaNtoZero(d(b))+")"}).select("text").attr("dy",".32em").attr("y",0).attr("x",c.tickPadding()).style("text-anchor","start").text(function(a){var b=u(a);return(""+b).match("NaN")?"":b}),x.watchTransition(s,"min-max right").attr("transform",function(b,c){return"translate(0,"+a.utils.NaNtoZero(d.range()[c])+")"}).select("text").style("opacity",1));break;case"left":v.enter().append("text").attr("class","nv-axislabel"),v.style("text-anchor",k?"middle":"end").attr("transform",k?"rotate(-90)":"").attr("y",k?-Math.max(e.left,f)+25-(o||0):-10).attr("x",k?-d3.max(d.range())/2:-c.tickPadding()),i&&(x=p.selectAll("g.nv-axisMaxMin").data(d.domain()),x.enter().append("g").attr("class",function(a,b){return["nv-axisMaxMin","nv-axisMaxMin-y",0==b?"nv-axisMin-y":"nv-axisMax-y"].join(" ")}).append("text").style("opacity",0),x.exit().remove(),x.attr("transform",function(b){return"translate(0,"+a.utils.NaNtoZero(r(b))+")"}).select("text").attr("dy",".32em").attr("y",0).attr("x",-c.tickPadding()).attr("text-anchor","end").text(function(a){var b=u(a);return(""+b).match("NaN")?"":b}),x.watchTransition(s,"min-max right").attr("transform",function(b,c){return"translate(0,"+a.utils.NaNtoZero(d.range()[c])+")"}).select("text").style("opacity",1))}if(v.text(function(a){return a}),!i||"left"!==c.orient()&&"right"!==c.orient()||(t.selectAll("g").each(function(a){d3.select(this).select("text").attr("opacity",1),(d(a)d.range()[0]-10)&&((a>1e-10||-1e-10>a)&&d3.select(this).attr("opacity",0),d3.select(this).select("text").attr("opacity",0))}),d.domain()[0]==d.domain()[1]&&0==d.domain()[0]&&p.selectAll("g.nv-axisMaxMin").style("opacity",function(a,b){return b?0:1})),i&&("top"===c.orient()||"bottom"===c.orient())){var E=[];p.selectAll("g.nv-axisMaxMin").each(function(a,b){try{E.push(b?d(a)-this.getBoundingClientRect().width-4:d(a)+this.getBoundingClientRect().width+4)}catch(c){E.push(b?d(a)-4:d(a)+4)}}),t.selectAll("g").each(function(a){(d(a)E[1])&&(a>1e-10||-1e-10>a?d3.select(this).remove():d3.select(this).select("text").remove())})}t.selectAll(".tick").filter(function(a){return!parseFloat(Math.round(1e5*a)/1e6)&&void 0!==a}).classed("zero",!0),r=d.copy()}),s.renderEnd("axis immediate"),b}var c=d3.svg.axis(),d=d3.scale.linear(),e={top:0,right:0,bottom:0,left:0},f=75,g=60,h=null,i=!0,j=0,k=!0,l=!1,m=!1,n=null,o=0,p=250,q=d3.dispatch("renderEnd");c.scale(d).orient("bottom").tickFormat(function(a){return a});var r,s=a.utils.renderWatch(q,p);return b.axis=c,b.dispatch=q,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{axisLabelDistance:{get:function(){return o},set:function(a){o=a}},staggerLabels:{get:function(){return l},set:function(a){l=a}},rotateLabels:{get:function(){return j},set:function(a){j=a}},rotateYLabel:{get:function(){return k},set:function(a){k=a}},showMaxMin:{get:function(){return i},set:function(a){i=a}},axisLabel:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return g},set:function(a){g=a}},ticks:{get:function(){return n},set:function(a){n=a}},width:{get:function(){return f},set:function(a){f=a}},margin:{get:function(){return e},set:function(a){e.top=void 0!==a.top?a.top:e.top,e.right=void 0!==a.right?a.right:e.right,e.bottom=void 0!==a.bottom?a.bottom:e.bottom,e.left=void 0!==a.left?a.left:e.left}},duration:{get:function(){return p},set:function(a){p=a,s.reset(p)}},scale:{get:function(){return d},set:function(e){d=e,c.scale(d),m="function"==typeof d.rangeBands,a.utils.inheritOptionsD3(b,d,["domain","range","rangeBand","rangeBands"])}}}),a.utils.initOptions(b),a.utils.inheritOptionsD3(b,c,["orient","tickValues","tickSubdivide","tickSize","tickPadding","tickFormat"]),a.utils.inheritOptionsD3(b,d,["domain","range","rangeBand","rangeBands"]),b},a.models.boxPlot=function(){"use strict";function b(l){return v.reset(),l.each(function(b){var l=j-i.left-i.right,p=k-i.top-i.bottom;r=d3.select(this),a.utils.initSVG(r),m.domain(c||b.map(function(a,b){return o(a,b)})).rangeBands(e||[0,l],.1);var w=[];if(!d){var x=d3.min(b.map(function(a){var b=[];return b.push(a.values.Q1),a.values.hasOwnProperty("whisker_low")&&null!==a.values.whisker_low&&b.push(a.values.whisker_low),a.values.hasOwnProperty("outliers")&&null!==a.values.outliers&&(b=b.concat(a.values.outliers)),d3.min(b)})),y=d3.max(b.map(function(a){var b=[];return b.push(a.values.Q3),a.values.hasOwnProperty("whisker_high")&&null!==a.values.whisker_high&&b.push(a.values.whisker_high),a.values.hasOwnProperty("outliers")&&null!==a.values.outliers&&(b=b.concat(a.values.outliers)),d3.max(b)}));w=[x,y]}n.domain(d||w),n.range(f||[p,0]),g=g||m,h=h||n.copy().range([n(0),n(0)]);{var z=r.selectAll("g.nv-wrap").data([b]);z.enter().append("g").attr("class","nvd3 nv-wrap")}z.attr("transform","translate("+i.left+","+i.top+")");var A=z.selectAll(".nv-boxplot").data(function(a){return a}),B=A.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6);A.attr("class","nv-boxplot").attr("transform",function(a,b){return"translate("+(m(o(a,b))+.05*m.rangeBand())+", 0)"}).classed("hover",function(a){return a.hover}),A.watchTransition(v,"nv-boxplot: boxplots").style("stroke-opacity",1).style("fill-opacity",.75).delay(function(a,c){return c*t/b.length}).attr("transform",function(a,b){return"translate("+(m(o(a,b))+.05*m.rangeBand())+", 0)"}),A.exit().remove(),B.each(function(a,b){var c=d3.select(this);["low","high"].forEach(function(d){a.values.hasOwnProperty("whisker_"+d)&&null!==a.values["whisker_"+d]&&(c.append("line").style("stroke",a.color?a.color:q(a,b)).attr("class","nv-boxplot-whisker nv-boxplot-"+d),c.append("line").style("stroke",a.color?a.color:q(a,b)).attr("class","nv-boxplot-tick nv-boxplot-"+d))})});var C=A.selectAll(".nv-boxplot-outlier").data(function(a){return a.values.hasOwnProperty("outliers")&&null!==a.values.outliers?a.values.outliers:[]});C.enter().append("circle").style("fill",function(a,b,c){return q(a,c)}).style("stroke",function(a,b,c){return q(a,c)}).on("mouseover",function(a,b,c){d3.select(this).classed("hover",!0),s.elementMouseover({series:{key:a,color:q(a,c)},e:d3.event})}).on("mouseout",function(a,b,c){d3.select(this).classed("hover",!1),s.elementMouseout({series:{key:a,color:q(a,c)},e:d3.event})}).on("mousemove",function(){s.elementMousemove({e:d3.event})}),C.attr("class","nv-boxplot-outlier"),C.watchTransition(v,"nv-boxplot: nv-boxplot-outlier").attr("cx",.45*m.rangeBand()).attr("cy",function(a){return n(a)}).attr("r","3"),C.exit().remove();var D=function(){return null===u?.9*m.rangeBand():Math.min(75,.9*m.rangeBand())},E=function(){return.45*m.rangeBand()-D()/2},F=function(){return.45*m.rangeBand()+D()/2};["low","high"].forEach(function(a){var b="low"===a?"Q1":"Q3";A.select("line.nv-boxplot-whisker.nv-boxplot-"+a).watchTransition(v,"nv-boxplot: boxplots").attr("x1",.45*m.rangeBand()).attr("y1",function(b){return n(b.values["whisker_"+a])}).attr("x2",.45*m.rangeBand()).attr("y2",function(a){return n(a.values[b])}),A.select("line.nv-boxplot-tick.nv-boxplot-"+a).watchTransition(v,"nv-boxplot: boxplots").attr("x1",E).attr("y1",function(b){return n(b.values["whisker_"+a])}).attr("x2",F).attr("y2",function(b){return n(b.values["whisker_"+a])})}),["low","high"].forEach(function(a){B.selectAll(".nv-boxplot-"+a).on("mouseover",function(b,c,d){d3.select(this).classed("hover",!0),s.elementMouseover({series:{key:b.values["whisker_"+a],color:q(b,d)},e:d3.event})}).on("mouseout",function(b,c,d){d3.select(this).classed("hover",!1),s.elementMouseout({series:{key:b.values["whisker_"+a],color:q(b,d)},e:d3.event})}).on("mousemove",function(){s.elementMousemove({e:d3.event})})}),B.append("rect").attr("class","nv-boxplot-box").on("mouseover",function(a,b){d3.select(this).classed("hover",!0),s.elementMouseover({key:a.label,value:a.label,series:[{key:"Q3",value:a.values.Q3,color:a.color||q(a,b)},{key:"Q2",value:a.values.Q2,color:a.color||q(a,b)},{key:"Q1",value:a.values.Q1,color:a.color||q(a,b)}],data:a,index:b,e:d3.event})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),s.elementMouseout({key:a.label,value:a.label,series:[{key:"Q3",value:a.values.Q3,color:a.color||q(a,b)},{key:"Q2",value:a.values.Q2,color:a.color||q(a,b)},{key:"Q1",value:a.values.Q1,color:a.color||q(a,b)}],data:a,index:b,e:d3.event})}).on("mousemove",function(){s.elementMousemove({e:d3.event})}),A.select("rect.nv-boxplot-box").watchTransition(v,"nv-boxplot: boxes").attr("y",function(a){return n(a.values.Q3)}).attr("width",D).attr("x",E).attr("height",function(a){return Math.abs(n(a.values.Q3)-n(a.values.Q1))||1}).style("fill",function(a,b){return a.color||q(a,b)}).style("stroke",function(a,b){return a.color||q(a,b)}),B.append("line").attr("class","nv-boxplot-median"),A.select("line.nv-boxplot-median").watchTransition(v,"nv-boxplot: boxplots line").attr("x1",E).attr("y1",function(a){return n(a.values.Q2)}).attr("x2",F).attr("y2",function(a){return n(a.values.Q2)}),g=m.copy(),h=n.copy()}),v.renderEnd("nv-boxplot immediate"),b}var c,d,e,f,g,h,i={top:0,right:0,bottom:0,left:0},j=960,k=500,l=Math.floor(1e4*Math.random()),m=d3.scale.ordinal(),n=d3.scale.linear(),o=function(a){return a.x},p=function(a){return a.y},q=a.utils.defaultColor(),r=null,s=d3.dispatch("elementMouseover","elementMouseout","elementMousemove","renderEnd"),t=250,u=null,v=a.utils.renderWatch(s,t);return b.dispatch=s,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return j},set:function(a){j=a}},height:{get:function(){return k},set:function(a){k=a}},maxBoxWidth:{get:function(){return u},set:function(a){u=a}},x:{get:function(){return o},set:function(a){o=a}},y:{get:function(){return p},set:function(a){p=a}},xScale:{get:function(){return m},set:function(a){m=a}},yScale:{get:function(){return n},set:function(a){n=a}},xDomain:{get:function(){return c},set:function(a){c=a}},yDomain:{get:function(){return d},set:function(a){d=a}},xRange:{get:function(){return e},set:function(a){e=a}},yRange:{get:function(){return f},set:function(a){f=a}},id:{get:function(){return l},set:function(a){l=a}},margin:{get:function(){return i},set:function(a){i.top=void 0!==a.top?a.top:i.top,i.right=void 0!==a.right?a.right:i.right,i.bottom=void 0!==a.bottom?a.bottom:i.bottom,i.left=void 0!==a.left?a.left:i.left}},color:{get:function(){return q},set:function(b){q=a.utils.getColor(b)}},duration:{get:function(){return t},set:function(a){t=a,v.reset(t)}}}),a.utils.initOptions(b),b},a.models.boxPlotChart=function(){"use strict";function b(k){return t.reset(),t.models(e),l&&t.models(f),m&&t.models(g),k.each(function(k){var p=d3.select(this);a.utils.initSVG(p);var t=(i||parseInt(p.style("width"))||960)-h.left-h.right,u=(j||parseInt(p.style("height"))||400)-h.top-h.bottom;if(b.update=function(){r.beforeUpdate(),p.transition().duration(s).call(b)},b.container=this,!(k&&k.length&&k.filter(function(a){return a.values.hasOwnProperty("Q1")&&a.values.hasOwnProperty("Q2")&&a.values.hasOwnProperty("Q3")}).length)){var v=p.selectAll(".nv-noData").data([q]);return v.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),v.attr("x",h.left+t/2).attr("y",h.top+u/2).text(function(a){return a}),b}p.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale().clamp(!0);var w=p.selectAll("g.nv-wrap.nv-boxPlotWithAxes").data([k]),x=w.enter().append("g").attr("class","nvd3 nv-wrap nv-boxPlotWithAxes").append("g"),y=x.append("defs"),z=w.select("g"); +x.append("g").attr("class","nv-x nv-axis"),x.append("g").attr("class","nv-y nv-axis").append("g").attr("class","nv-zeroLine").append("line"),x.append("g").attr("class","nv-barsWrap"),z.attr("transform","translate("+h.left+","+h.top+")"),n&&z.select(".nv-y.nv-axis").attr("transform","translate("+t+",0)"),e.width(t).height(u);var A=z.select(".nv-barsWrap").datum(k.filter(function(a){return!a.disabled}));if(A.transition().call(e),y.append("clipPath").attr("id","nv-x-label-clip-"+e.id()).append("rect"),z.select("#nv-x-label-clip-"+e.id()+" rect").attr("width",c.rangeBand()*(o?2:1)).attr("height",16).attr("x",-c.rangeBand()/(o?1:2)),l){f.scale(c).ticks(a.utils.calcTicksX(t/100,k)).tickSize(-u,0),z.select(".nv-x.nv-axis").attr("transform","translate(0,"+d.range()[0]+")"),z.select(".nv-x.nv-axis").call(f);var B=z.select(".nv-x.nv-axis").selectAll("g");o&&B.selectAll("text").attr("transform",function(a,b,c){return"translate(0,"+(c%2==0?"5":"17")+")"})}m&&(g.scale(d).ticks(Math.floor(u/36)).tickSize(-t,0),z.select(".nv-y.nv-axis").call(g)),z.select(".nv-zeroLine line").attr("x1",0).attr("x2",t).attr("y1",d(0)).attr("y2",d(0))}),t.renderEnd("nv-boxplot chart immediate"),b}var c,d,e=a.models.boxPlot(),f=a.models.axis(),g=a.models.axis(),h={top:15,right:10,bottom:50,left:60},i=null,j=null,k=a.utils.getColor(),l=!0,m=!0,n=!1,o=!1,p=a.models.tooltip(),q="No Data Available.",r=d3.dispatch("tooltipShow","tooltipHide","beforeUpdate","renderEnd"),s=250;f.orient("bottom").showMaxMin(!1).tickFormat(function(a){return a}),g.orient(n?"right":"left").tickFormat(d3.format(",.1f")),p.duration(0);var t=a.utils.renderWatch(r,s);return e.dispatch.on("elementMouseover.tooltip",function(a){p.data(a).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(a){p.data(a).hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(){p.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=r,b.boxplot=e,b.xAxis=f,b.yAxis=g,b.tooltip=p,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return i},set:function(a){i=a}},height:{get:function(){return j},set:function(a){j=a}},staggerLabels:{get:function(){return o},set:function(a){o=a}},showXAxis:{get:function(){return l},set:function(a){l=a}},showYAxis:{get:function(){return m},set:function(a){m=a}},tooltips:{get:function(){return tooltips},set:function(a){tooltips=a}},tooltipContent:{get:function(){return p},set:function(a){p=a}},noData:{get:function(){return q},set:function(a){q=a}},margin:{get:function(){return h},set:function(a){h.top=void 0!==a.top?a.top:h.top,h.right=void 0!==a.right?a.right:h.right,h.bottom=void 0!==a.bottom?a.bottom:h.bottom,h.left=void 0!==a.left?a.left:h.left}},duration:{get:function(){return s},set:function(a){s=a,t.reset(s),e.duration(s),f.duration(s),g.duration(s)}},color:{get:function(){return k},set:function(b){k=a.utils.getColor(b),e.color(k)}},rightAlignYAxis:{get:function(){return n},set:function(a){n=a,g.orient(a?"right":"left")}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.bullet=function(){"use strict";function b(d){return d.each(function(b,d){var p=m-c.left-c.right,s=n-c.top-c.bottom;o=d3.select(this),a.utils.initSVG(o);{var t=f.call(this,b,d).slice().sort(d3.descending),u=g.call(this,b,d).slice().sort(d3.descending),v=h.call(this,b,d).slice().sort(d3.descending),w=i.call(this,b,d).slice(),x=j.call(this,b,d).slice(),y=k.call(this,b,d).slice(),z=d3.scale.linear().domain(d3.extent(d3.merge([l,t]))).range(e?[p,0]:[0,p]);this.__chart__||d3.scale.linear().domain([0,1/0]).range(z.range())}this.__chart__=z;var A=d3.min(t),B=d3.max(t),C=t[1],D=o.selectAll("g.nv-wrap.nv-bullet").data([b]),E=D.enter().append("g").attr("class","nvd3 nv-wrap nv-bullet"),F=E.append("g"),G=D.select("g");F.append("rect").attr("class","nv-range nv-rangeMax"),F.append("rect").attr("class","nv-range nv-rangeAvg"),F.append("rect").attr("class","nv-range nv-rangeMin"),F.append("rect").attr("class","nv-measure"),D.attr("transform","translate("+c.left+","+c.top+")");var H=function(a){return Math.abs(z(a)-z(0))},I=function(a){return z(0>a?a:0)};G.select("rect.nv-rangeMax").attr("height",s).attr("width",H(B>0?B:A)).attr("x",I(B>0?B:A)).datum(B>0?B:A),G.select("rect.nv-rangeAvg").attr("height",s).attr("width",H(C)).attr("x",I(C)).datum(C),G.select("rect.nv-rangeMin").attr("height",s).attr("width",H(B)).attr("x",I(B)).attr("width",H(B>0?A:B)).attr("x",I(B>0?A:B)).datum(B>0?A:B),G.select("rect.nv-measure").style("fill",q).attr("height",s/3).attr("y",s/3).attr("width",0>v?z(0)-z(v[0]):z(v[0])-z(0)).attr("x",I(v)).on("mouseover",function(){r.elementMouseover({value:v[0],label:y[0]||"Current",color:d3.select(this).style("fill")})}).on("mousemove",function(){r.elementMousemove({value:v[0],label:y[0]||"Current",color:d3.select(this).style("fill")})}).on("mouseout",function(){r.elementMouseout({value:v[0],label:y[0]||"Current",color:d3.select(this).style("fill")})});var J=s/6,K=u.map(function(a,b){return{value:a,label:x[b]}});F.selectAll("path.nv-markerTriangle").data(K).enter().append("path").attr("class","nv-markerTriangle").attr("transform",function(a){return"translate("+z(a.value)+","+s/2+")"}).attr("d","M0,"+J+"L"+J+","+-J+" "+-J+","+-J+"Z").on("mouseover",function(a){r.elementMouseover({value:a.value,label:a.label||"Previous",color:d3.select(this).style("fill"),pos:[z(a.value),s/2]})}).on("mousemove",function(a){r.elementMousemove({value:a.value,label:a.label||"Previous",color:d3.select(this).style("fill")})}).on("mouseout",function(a){r.elementMouseout({value:a.value,label:a.label||"Previous",color:d3.select(this).style("fill")})}),D.selectAll(".nv-range").on("mouseover",function(a,b){var c=w[b]||(b?1==b?"Mean":"Minimum":"Maximum");r.elementMouseover({value:a,label:c,color:d3.select(this).style("fill")})}).on("mousemove",function(){r.elementMousemove({value:v[0],label:y[0]||"Previous",color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){var c=w[b]||(b?1==b?"Mean":"Minimum":"Maximum");r.elementMouseout({value:a,label:c,color:d3.select(this).style("fill")})})}),b}var c={top:0,right:0,bottom:0,left:0},d="left",e=!1,f=function(a){return a.ranges},g=function(a){return a.markers?a.markers:[0]},h=function(a){return a.measures},i=function(a){return a.rangeLabels?a.rangeLabels:[]},j=function(a){return a.markerLabels?a.markerLabels:[]},k=function(a){return a.measureLabels?a.measureLabels:[]},l=[0],m=380,n=30,o=null,p=null,q=a.utils.getColor(["#1f77b4"]),r=d3.dispatch("elementMouseover","elementMouseout","elementMousemove");return b.dispatch=r,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{ranges:{get:function(){return f},set:function(a){f=a}},markers:{get:function(){return g},set:function(a){g=a}},measures:{get:function(){return h},set:function(a){h=a}},forceX:{get:function(){return l},set:function(a){l=a}},width:{get:function(){return m},set:function(a){m=a}},height:{get:function(){return n},set:function(a){n=a}},tickFormat:{get:function(){return p},set:function(a){p=a}},margin:{get:function(){return c},set:function(a){c.top=void 0!==a.top?a.top:c.top,c.right=void 0!==a.right?a.right:c.right,c.bottom=void 0!==a.bottom?a.bottom:c.bottom,c.left=void 0!==a.left?a.left:c.left}},orient:{get:function(){return d},set:function(a){d=a,e="right"==d||"bottom"==d}},color:{get:function(){return q},set:function(b){q=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.bulletChart=function(){"use strict";function b(d){return d.each(function(e,o){var p=d3.select(this);a.utils.initSVG(p);var q=a.utils.availableWidth(k,p,g),r=l-g.top-g.bottom;if(b.update=function(){b(d)},b.container=this,!e||!h.call(this,e,o))return a.utils.noData(b,p),b;p.selectAll(".nv-noData").remove();var s=h.call(this,e,o).slice().sort(d3.descending),t=i.call(this,e,o).slice().sort(d3.descending),u=j.call(this,e,o).slice().sort(d3.descending),v=p.selectAll("g.nv-wrap.nv-bulletChart").data([e]),w=v.enter().append("g").attr("class","nvd3 nv-wrap nv-bulletChart"),x=w.append("g"),y=v.select("g");x.append("g").attr("class","nv-bulletWrap"),x.append("g").attr("class","nv-titles"),v.attr("transform","translate("+g.left+","+g.top+")");var z=d3.scale.linear().domain([0,Math.max(s[0],t[0],u[0])]).range(f?[q,0]:[0,q]),A=this.__chart__||d3.scale.linear().domain([0,1/0]).range(z.range());this.__chart__=z;var B=x.select(".nv-titles").append("g").attr("text-anchor","end").attr("transform","translate(-6,"+(l-g.top-g.bottom)/2+")");B.append("text").attr("class","nv-title").text(function(a){return a.title}),B.append("text").attr("class","nv-subtitle").attr("dy","1em").text(function(a){return a.subtitle}),c.width(q).height(r);var C=y.select(".nv-bulletWrap");d3.transition(C).call(c);var D=m||z.tickFormat(q/100),E=y.selectAll("g.nv-tick").data(z.ticks(n?n:q/50),function(a){return this.textContent||D(a)}),F=E.enter().append("g").attr("class","nv-tick").attr("transform",function(a){return"translate("+A(a)+",0)"}).style("opacity",1e-6);F.append("line").attr("y1",r).attr("y2",7*r/6),F.append("text").attr("text-anchor","middle").attr("dy","1em").attr("y",7*r/6).text(D);var G=d3.transition(E).attr("transform",function(a){return"translate("+z(a)+",0)"}).style("opacity",1);G.select("line").attr("y1",r).attr("y2",7*r/6),G.select("text").attr("y",7*r/6),d3.transition(E.exit()).attr("transform",function(a){return"translate("+z(a)+",0)"}).style("opacity",1e-6).remove()}),d3.timer.flush(),b}var c=a.models.bullet(),d=a.models.tooltip(),e="left",f=!1,g={top:5,right:40,bottom:20,left:120},h=function(a){return a.ranges},i=function(a){return a.markers?a.markers:[0]},j=function(a){return a.measures},k=null,l=55,m=null,n=null,o=null,p=d3.dispatch("tooltipShow","tooltipHide");return d.duration(0).headerEnabled(!1),c.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:a.label,value:a.value,color:a.color},d.data(a).hidden(!1)}),c.dispatch.on("elementMouseout.tooltip",function(){d.hidden(!0)}),c.dispatch.on("elementMousemove.tooltip",function(){d.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.bullet=c,b.dispatch=p,b.tooltip=d,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{ranges:{get:function(){return h},set:function(a){h=a}},markers:{get:function(){return i},set:function(a){i=a}},measures:{get:function(){return j},set:function(a){j=a}},width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},tickFormat:{get:function(){return m},set:function(a){m=a}},ticks:{get:function(){return n},set:function(a){n=a}},noData:{get:function(){return o},set:function(a){o=a}},tooltips:{get:function(){return d.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),d.enabled(!!b)}},tooltipContent:{get:function(){return d.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),d.contentGenerator(b)}},margin:{get:function(){return g},set:function(a){g.top=void 0!==a.top?a.top:g.top,g.right=void 0!==a.right?a.right:g.right,g.bottom=void 0!==a.bottom?a.bottom:g.bottom,g.left=void 0!==a.left?a.left:g.left}},orient:{get:function(){return e},set:function(a){e=a,f="right"==e||"bottom"==e}}}),a.utils.inheritOptions(b,c),a.utils.initOptions(b),b},a.models.candlestickBar=function(){"use strict";function b(x){return x.each(function(b){c=d3.select(this);var x=a.utils.availableWidth(i,c,h),y=a.utils.availableHeight(j,c,h);a.utils.initSVG(c);var A=x/b[0].values.length*.45;l.domain(d||d3.extent(b[0].values.map(n).concat(t))),l.range(v?f||[.5*x/b[0].values.length,x*(b[0].values.length-.5)/b[0].values.length]:f||[5+A/2,x-A/2-5]),m.domain(e||[d3.min(b[0].values.map(s).concat(u)),d3.max(b[0].values.map(r).concat(u))]).range(g||[y,0]),l.domain()[0]===l.domain()[1]&&l.domain(l.domain()[0]?[l.domain()[0]-.01*l.domain()[0],l.domain()[1]+.01*l.domain()[1]]:[-1,1]),m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]+.01*m.domain()[0],m.domain()[1]-.01*m.domain()[1]]:[-1,1]);var B=d3.select(this).selectAll("g.nv-wrap.nv-candlestickBar").data([b[0].values]),C=B.enter().append("g").attr("class","nvd3 nv-wrap nv-candlestickBar"),D=C.append("defs"),E=C.append("g"),F=B.select("g");E.append("g").attr("class","nv-ticks"),B.attr("transform","translate("+h.left+","+h.top+")"),c.on("click",function(a,b){z.chartClick({data:a,index:b,pos:d3.event,id:k})}),D.append("clipPath").attr("id","nv-chart-clip-path-"+k).append("rect"),B.select("#nv-chart-clip-path-"+k+" rect").attr("width",x).attr("height",y),F.attr("clip-path",w?"url(#nv-chart-clip-path-"+k+")":"");var G=B.select(".nv-ticks").selectAll(".nv-tick").data(function(a){return a});G.exit().remove();{var H=G.enter().append("g").attr("class",function(a,b,c){return(p(a,b)>q(a,b)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+c+"-"+b});H.append("line").attr("class","nv-candlestick-lines").attr("transform",function(a,b){return"translate("+l(n(a,b))+",0)"}).attr("x1",0).attr("y1",function(a,b){return m(r(a,b))}).attr("x2",0).attr("y2",function(a,b){return m(s(a,b))}),H.append("rect").attr("class","nv-candlestick-rects nv-bars").attr("transform",function(a,b){return"translate("+(l(n(a,b))-A/2)+","+(m(o(a,b))-(p(a,b)>q(a,b)?m(q(a,b))-m(p(a,b)):0))+")"}).attr("x",0).attr("y",0).attr("width",A).attr("height",function(a,b){var c=p(a,b),d=q(a,b);return c>d?m(d)-m(c):m(c)-m(d)})}c.selectAll(".nv-candlestick-lines").transition().attr("transform",function(a,b){return"translate("+l(n(a,b))+",0)"}).attr("x1",0).attr("y1",function(a,b){return m(r(a,b))}).attr("x2",0).attr("y2",function(a,b){return m(s(a,b))}),c.selectAll(".nv-candlestick-rects").transition().attr("transform",function(a,b){return"translate("+(l(n(a,b))-A/2)+","+(m(o(a,b))-(p(a,b)>q(a,b)?m(q(a,b))-m(p(a,b)):0))+")"}).attr("x",0).attr("y",0).attr("width",A).attr("height",function(a,b){var c=p(a,b),d=q(a,b);return c>d?m(d)-m(c):m(c)-m(d)})}),b}var c,d,e,f,g,h={top:0,right:0,bottom:0,left:0},i=null,j=null,k=Math.floor(1e4*Math.random()),l=d3.scale.linear(),m=d3.scale.linear(),n=function(a){return a.x},o=function(a){return a.y},p=function(a){return a.open},q=function(a){return a.close},r=function(a){return a.high},s=function(a){return a.low},t=[],u=[],v=!1,w=!0,x=a.utils.defaultColor(),y=!1,z=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd","chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove");return b.highlightPoint=function(a,d){b.clearHighlights(),c.select(".nv-candlestickBar .nv-tick-0-"+a).classed("hover",d)},b.clearHighlights=function(){c.select(".nv-candlestickBar .nv-tick.hover").classed("hover",!1)},b.dispatch=z,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return i},set:function(a){i=a}},height:{get:function(){return j},set:function(a){j=a}},xScale:{get:function(){return l},set:function(a){l=a}},yScale:{get:function(){return m},set:function(a){m=a}},xDomain:{get:function(){return d},set:function(a){d=a}},yDomain:{get:function(){return e},set:function(a){e=a}},xRange:{get:function(){return f},set:function(a){f=a}},yRange:{get:function(){return g},set:function(a){g=a}},forceX:{get:function(){return t},set:function(a){t=a}},forceY:{get:function(){return u},set:function(a){u=a}},padData:{get:function(){return v},set:function(a){v=a}},clipEdge:{get:function(){return w},set:function(a){w=a}},id:{get:function(){return k},set:function(a){k=a}},interactive:{get:function(){return y},set:function(a){y=a}},x:{get:function(){return n},set:function(a){n=a}},y:{get:function(){return o},set:function(a){o=a}},open:{get:function(){return p()},set:function(a){p=a}},close:{get:function(){return q()},set:function(a){q=a}},high:{get:function(){return r},set:function(a){r=a}},low:{get:function(){return s},set:function(a){s=a}},margin:{get:function(){return h},set:function(a){h.top=void 0!=a.top?a.top:h.top,h.right=void 0!=a.right?a.right:h.right,h.bottom=void 0!=a.bottom?a.bottom:h.bottom,h.left=void 0!=a.left?a.left:h.left}},color:{get:function(){return x},set:function(b){x=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.cumulativeLineChart=function(){"use strict";function b(l){return H.reset(),H.models(f),r&&H.models(g),s&&H.models(h),l.each(function(l){function A(){d3.select(b.container).style("cursor","ew-resize")}function E(){G.x=d3.event.x,G.i=Math.round(F.invert(G.x)),K()}function H(){d3.select(b.container).style("cursor","auto"),y.index=G.i,C.stateChange(y)}function K(){bb.data([G]);var a=b.duration();b.duration(0),b.update(),b.duration(a)}var L=d3.select(this);a.utils.initSVG(L),L.classed("nv-chart-"+x,!0);var M=this,N=a.utils.availableWidth(o,L,m),O=a.utils.availableHeight(p,L,m);if(b.update=function(){0===D?L.call(b):L.transition().duration(D).call(b)},b.container=this,y.setter(J(l),b.update).getter(I(l)).update(),y.disabled=l.map(function(a){return!!a.disabled}),!z){var P;z={};for(P in y)z[P]=y[P]instanceof Array?y[P].slice(0):y[P]}var Q=d3.behavior.drag().on("dragstart",A).on("drag",E).on("dragend",H);if(!(l&&l.length&&l.filter(function(a){return a.values.length}).length))return a.utils.noData(b,L),b;if(L.selectAll(".nv-noData").remove(),d=f.xScale(),e=f.yScale(),w)f.yDomain(null);else{var R=l.filter(function(a){return!a.disabled}).map(function(a){var b=d3.extent(a.values,f.y());return b[0]<-.95&&(b[0]=-.95),[(b[0]-b[1])/(1+b[1]),(b[1]-b[0])/(1+b[0])]}),S=[d3.min(R,function(a){return a[0]}),d3.max(R,function(a){return a[1]})];f.yDomain(S)}F.domain([0,l[0].values.length-1]).range([0,N]).clamp(!0);var l=c(G.i,l),T=v?"none":"all",U=L.selectAll("g.nv-wrap.nv-cumulativeLine").data([l]),V=U.enter().append("g").attr("class","nvd3 nv-wrap nv-cumulativeLine").append("g"),W=U.select("g");if(V.append("g").attr("class","nv-interactive"),V.append("g").attr("class","nv-x nv-axis").style("pointer-events","none"),V.append("g").attr("class","nv-y nv-axis"),V.append("g").attr("class","nv-background"),V.append("g").attr("class","nv-linesWrap").style("pointer-events",T),V.append("g").attr("class","nv-avgLinesWrap").style("pointer-events","none"),V.append("g").attr("class","nv-legendWrap"),V.append("g").attr("class","nv-controlsWrap"),q&&(i.width(N),W.select(".nv-legendWrap").datum(l).call(i),m.top!=i.height()&&(m.top=i.height(),O=a.utils.availableHeight(p,L,m)),W.select(".nv-legendWrap").attr("transform","translate(0,"+-m.top+")")),u){var X=[{key:"Re-scale y-axis",disabled:!w}];j.width(140).color(["#444","#444","#444"]).rightAlign(!1).margin({top:5,right:0,bottom:5,left:20}),W.select(".nv-controlsWrap").datum(X).attr("transform","translate(0,"+-m.top+")").call(j)}U.attr("transform","translate("+m.left+","+m.top+")"),t&&W.select(".nv-y.nv-axis").attr("transform","translate("+N+",0)");var Y=l.filter(function(a){return a.tempDisabled});U.select(".tempDisabled").remove(),Y.length&&U.append("text").attr("class","tempDisabled").attr("x",N/2).attr("y","-.71em").style("text-anchor","end").text(Y.map(function(a){return a.key}).join(", ")+" values cannot be calculated for this time period."),v&&(k.width(N).height(O).margin({left:m.left,top:m.top}).svgContainer(L).xScale(d),U.select(".nv-interactive").call(k)),V.select(".nv-background").append("rect"),W.select(".nv-background rect").attr("width",N).attr("height",O),f.y(function(a){return a.display.y}).width(N).height(O).color(l.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!l[b].disabled&&!l[b].tempDisabled}));var Z=W.select(".nv-linesWrap").datum(l.filter(function(a){return!a.disabled&&!a.tempDisabled}));Z.call(f),l.forEach(function(a,b){a.seriesIndex=b});var $=l.filter(function(a){return!a.disabled&&!!B(a)}),_=W.select(".nv-avgLinesWrap").selectAll("line").data($,function(a){return a.key}),ab=function(a){var b=e(B(a));return 0>b?0:b>O?O:b};_.enter().append("line").style("stroke-width",2).style("stroke-dasharray","10,10").style("stroke",function(a){return f.color()(a,a.seriesIndex)}).attr("x1",0).attr("x2",N).attr("y1",ab).attr("y2",ab),_.style("stroke-opacity",function(a){var b=e(B(a));return 0>b||b>O?0:1}).attr("x1",0).attr("x2",N).attr("y1",ab).attr("y2",ab),_.exit().remove();var bb=Z.selectAll(".nv-indexLine").data([G]);bb.enter().append("rect").attr("class","nv-indexLine").attr("width",3).attr("x",-2).attr("fill","red").attr("fill-opacity",.5).style("pointer-events","all").call(Q),bb.attr("transform",function(a){return"translate("+F(a.i)+",0)"}).attr("height",O),r&&(g.scale(d)._ticks(a.utils.calcTicksX(N/70,l)).tickSize(-O,0),W.select(".nv-x.nv-axis").attr("transform","translate(0,"+e.range()[0]+")"),W.select(".nv-x.nv-axis").call(g)),s&&(h.scale(e)._ticks(a.utils.calcTicksY(O/36,l)).tickSize(-N,0),W.select(".nv-y.nv-axis").call(h)),W.select(".nv-background rect").on("click",function(){G.x=d3.mouse(this)[0],G.i=Math.round(F.invert(G.x)),y.index=G.i,C.stateChange(y),K()}),f.dispatch.on("elementClick",function(a){G.i=a.pointIndex,G.x=F(G.i),y.index=G.i,C.stateChange(y),K()}),j.dispatch.on("legendClick",function(a){a.disabled=!a.disabled,w=!a.disabled,y.rescaleY=w,C.stateChange(y),b.update()}),i.dispatch.on("stateChange",function(a){for(var c in a)y[c]=a[c];C.stateChange(y),b.update()}),k.dispatch.on("elementMousemove",function(c){f.clearHighlights();var d,e,i,j=[];if(l.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(g,h){e=a.interactiveBisect(g.values,c.pointXValue,b.x()),f.highlightPoint(h,e,!0);var k=g.values[e];"undefined"!=typeof k&&("undefined"==typeof d&&(d=k),"undefined"==typeof i&&(i=b.xScale()(b.x()(k,e))),j.push({key:g.key,value:b.y()(k,e),color:n(g,g.seriesIndex)}))}),j.length>2){var o=b.yScale().invert(c.mouseY),p=Math.abs(b.yScale().domain()[0]-b.yScale().domain()[1]),q=.03*p,r=a.nearestValueIndex(j.map(function(a){return a.value}),o,q);null!==r&&(j[r].highlight=!0)}var s=g.tickFormat()(b.x()(d,e),e);k.tooltip.position({left:i+m.left,top:c.mouseY+m.top}).chartContainer(M.parentNode).valueFormatter(function(a){return h.tickFormat()(a)}).data({value:s,series:j})(),k.renderGuideLine(i)}),k.dispatch.on("elementMouseout",function(){f.clearHighlights()}),C.on("changeState",function(a){"undefined"!=typeof a.disabled&&(l.forEach(function(b,c){b.disabled=a.disabled[c]}),y.disabled=a.disabled),"undefined"!=typeof a.index&&(G.i=a.index,G.x=F(G.i),y.index=a.index,bb.data([G])),"undefined"!=typeof a.rescaleY&&(w=a.rescaleY),b.update()})}),H.renderEnd("cumulativeLineChart immediate"),b}function c(a,b){return K||(K=f.y()),b.map(function(b){if(!b.values)return b;var c=b.values[a];if(null==c)return b;var d=K(c,a);return-.95>d&&!E?(b.tempDisabled=!0,b):(b.tempDisabled=!1,b.values=b.values.map(function(a,b){return a.display={y:(K(a,b)-d)/(1+d)},a}),b)})}var d,e,f=a.models.line(),g=a.models.axis(),h=a.models.axis(),i=a.models.legend(),j=a.models.legend(),k=a.interactiveGuideline(),l=a.models.tooltip(),m={top:30,right:30,bottom:50,left:60},n=a.utils.defaultColor(),o=null,p=null,q=!0,r=!0,s=!0,t=!1,u=!0,v=!1,w=!0,x=f.id(),y=a.utils.state(),z=null,A=null,B=function(a){return a.average},C=d3.dispatch("stateChange","changeState","renderEnd"),D=250,E=!1;y.index=0,y.rescaleY=w,g.orient("bottom").tickPadding(7),h.orient(t?"right":"left"),l.valueFormatter(function(a,b){return h.tickFormat()(a,b)}).headerFormatter(function(a,b){return g.tickFormat()(a,b)}),j.updateState(!1);var F=d3.scale.linear(),G={i:0,x:0},H=a.utils.renderWatch(C,D),I=function(a){return function(){return{active:a.map(function(a){return!a.disabled}),index:G.i,rescaleY:w}}},J=function(a){return function(b){void 0!==b.index&&(G.i=b.index),void 0!==b.rescaleY&&(w=b.rescaleY),void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};f.dispatch.on("elementMouseover.tooltip",function(a){var c={x:b.x()(a.point),y:b.y()(a.point),color:a.point.color};a.point=c,l.data(a).position(a.pos).hidden(!1)}),f.dispatch.on("elementMouseout.tooltip",function(){l.hidden(!0)});var K=null;return b.dispatch=C,b.lines=f,b.legend=i,b.controls=j,b.xAxis=g,b.yAxis=h,b.interactiveLayer=k,b.state=y,b.tooltip=l,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return o},set:function(a){o=a}},height:{get:function(){return p},set:function(a){p=a}},rescaleY:{get:function(){return w},set:function(a){w=a}},showControls:{get:function(){return u},set:function(a){u=a}},showLegend:{get:function(){return q},set:function(a){q=a}},average:{get:function(){return B},set:function(a){B=a}},defaultState:{get:function(){return z},set:function(a){z=a}},noData:{get:function(){return A},set:function(a){A=a}},showXAxis:{get:function(){return r},set:function(a){r=a}},showYAxis:{get:function(){return s},set:function(a){s=a}},noErrorCheck:{get:function(){return E},set:function(a){E=a}},tooltips:{get:function(){return l.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),l.enabled(!!b)}},tooltipContent:{get:function(){return l.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),l.contentGenerator(b)}},margin:{get:function(){return m},set:function(a){m.top=void 0!==a.top?a.top:m.top,m.right=void 0!==a.right?a.right:m.right,m.bottom=void 0!==a.bottom?a.bottom:m.bottom,m.left=void 0!==a.left?a.left:m.left}},color:{get:function(){return n},set:function(b){n=a.utils.getColor(b),i.color(n)}},useInteractiveGuideline:{get:function(){return v},set:function(a){v=a,a===!0&&(b.interactive(!1),b.useVoronoi(!1))}},rightAlignYAxis:{get:function(){return t},set:function(a){t=a,h.orient(a?"right":"left")}},duration:{get:function(){return D},set:function(a){D=a,f.duration(D),g.duration(D),h.duration(D),H.reset(D)}}}),a.utils.inheritOptions(b,f),a.utils.initOptions(b),b},a.models.discreteBar=function(){"use strict";function b(m){return y.reset(),m.each(function(b){var m=k-j.left-j.right,x=l-j.top-j.bottom;c=d3.select(this),a.utils.initSVG(c),b.forEach(function(a,b){a.values.forEach(function(a){a.series=b})});var z=d&&e?[]:b.map(function(a){return a.values.map(function(a,b){return{x:p(a,b),y:q(a,b),y0:a.y0}})});n.domain(d||d3.merge(z).map(function(a){return a.x})).rangeBands(f||[0,m],.1),o.domain(e||d3.extent(d3.merge(z).map(function(a){return a.y}).concat(r))),o.range(t?g||[x-(o.domain()[0]<0?12:0),o.domain()[1]>0?12:0]:g||[x,0]),h=h||n,i=i||o.copy().range([o(0),o(0)]);{var A=c.selectAll("g.nv-wrap.nv-discretebar").data([b]),B=A.enter().append("g").attr("class","nvd3 nv-wrap nv-discretebar"),C=B.append("g");A.select("g")}C.append("g").attr("class","nv-groups"),A.attr("transform","translate("+j.left+","+j.top+")");var D=A.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a){return a.key});D.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),D.exit().watchTransition(y,"discreteBar: exit groups").style("stroke-opacity",1e-6).style("fill-opacity",1e-6).remove(),D.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}),D.watchTransition(y,"discreteBar: groups").style("stroke-opacity",1).style("fill-opacity",.75);var E=D.selectAll("g.nv-bar").data(function(a){return a.values});E.exit().remove();var F=E.enter().append("g").attr("transform",function(a,b){return"translate("+(n(p(a,b))+.05*n.rangeBand())+", "+o(0)+")"}).on("mouseover",function(a,b){d3.select(this).classed("hover",!0),v.elementMouseover({data:a,index:b,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),v.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")})}).on("mousemove",function(a,b){v.elementMousemove({data:a,index:b,color:d3.select(this).style("fill")})}).on("click",function(a,b){v.elementClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}).on("dblclick",function(a,b){v.elementDblClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()});F.append("rect").attr("height",0).attr("width",.9*n.rangeBand()/b.length),t?(F.append("text").attr("text-anchor","middle"),E.select("text").text(function(a,b){return u(q(a,b))}).watchTransition(y,"discreteBar: bars text").attr("x",.9*n.rangeBand()/2).attr("y",function(a,b){return q(a,b)<0?o(q(a,b))-o(0)+12:-4})):E.selectAll("text").remove(),E.attr("class",function(a,b){return q(a,b)<0?"nv-bar negative":"nv-bar positive"}).style("fill",function(a,b){return a.color||s(a,b)}).style("stroke",function(a,b){return a.color||s(a,b)}).select("rect").attr("class",w).watchTransition(y,"discreteBar: bars rect").attr("width",.9*n.rangeBand()/b.length),E.watchTransition(y,"discreteBar: bars").attr("transform",function(a,b){var c=n(p(a,b))+.05*n.rangeBand(),d=q(a,b)<0?o(0):o(0)-o(q(a,b))<1?o(0)-1:o(q(a,b));return"translate("+c+", "+d+")"}).select("rect").attr("height",function(a,b){return Math.max(Math.abs(o(q(a,b))-o(e&&e[0]||0))||1)}),h=n.copy(),i=o.copy()}),y.renderEnd("discreteBar immediate"),b}var c,d,e,f,g,h,i,j={top:0,right:0,bottom:0,left:0},k=960,l=500,m=Math.floor(1e4*Math.random()),n=d3.scale.ordinal(),o=d3.scale.linear(),p=function(a){return a.x},q=function(a){return a.y},r=[0],s=a.utils.defaultColor(),t=!1,u=d3.format(",.2f"),v=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),w="discreteBar",x=250,y=a.utils.renderWatch(v,x);return b.dispatch=v,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},forceY:{get:function(){return r},set:function(a){r=a}},showValues:{get:function(){return t},set:function(a){t=a}},x:{get:function(){return p},set:function(a){p=a}},y:{get:function(){return q},set:function(a){q=a}},xScale:{get:function(){return n},set:function(a){n=a}},yScale:{get:function(){return o},set:function(a){o=a}},xDomain:{get:function(){return d},set:function(a){d=a}},yDomain:{get:function(){return e},set:function(a){e=a}},xRange:{get:function(){return f},set:function(a){f=a}},yRange:{get:function(){return g},set:function(a){g=a}},valueFormat:{get:function(){return u},set:function(a){u=a}},id:{get:function(){return m},set:function(a){m=a}},rectClass:{get:function(){return w},set:function(a){w=a}},margin:{get:function(){return j},set:function(a){j.top=void 0!==a.top?a.top:j.top,j.right=void 0!==a.right?a.right:j.right,j.bottom=void 0!==a.bottom?a.bottom:j.bottom,j.left=void 0!==a.left?a.left:j.left}},color:{get:function(){return s},set:function(b){s=a.utils.getColor(b)}},duration:{get:function(){return x},set:function(a){x=a,y.reset(x)}}}),a.utils.initOptions(b),b},a.models.discreteBarChart=function(){"use strict";function b(h){return t.reset(),t.models(e),m&&t.models(f),n&&t.models(g),h.each(function(h){var l=d3.select(this);a.utils.initSVG(l);var q=a.utils.availableWidth(j,l,i),t=a.utils.availableHeight(k,l,i);if(b.update=function(){r.beforeUpdate(),l.transition().duration(s).call(b)},b.container=this,!(h&&h.length&&h.filter(function(a){return a.values.length}).length))return a.utils.noData(b,l),b;l.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale().clamp(!0);var u=l.selectAll("g.nv-wrap.nv-discreteBarWithAxes").data([h]),v=u.enter().append("g").attr("class","nvd3 nv-wrap nv-discreteBarWithAxes").append("g"),w=v.append("defs"),x=u.select("g");v.append("g").attr("class","nv-x nv-axis"),v.append("g").attr("class","nv-y nv-axis").append("g").attr("class","nv-zeroLine").append("line"),v.append("g").attr("class","nv-barsWrap"),x.attr("transform","translate("+i.left+","+i.top+")"),o&&x.select(".nv-y.nv-axis").attr("transform","translate("+q+",0)"),e.width(q).height(t);var y=x.select(".nv-barsWrap").datum(h.filter(function(a){return!a.disabled}));if(y.transition().call(e),w.append("clipPath").attr("id","nv-x-label-clip-"+e.id()).append("rect"),x.select("#nv-x-label-clip-"+e.id()+" rect").attr("width",c.rangeBand()*(p?2:1)).attr("height",16).attr("x",-c.rangeBand()/(p?1:2)),m){f.scale(c)._ticks(a.utils.calcTicksX(q/100,h)).tickSize(-t,0),x.select(".nv-x.nv-axis").attr("transform","translate(0,"+(d.range()[0]+(e.showValues()&&d.domain()[0]<0?16:0))+")"),x.select(".nv-x.nv-axis").call(f); +var z=x.select(".nv-x.nv-axis").selectAll("g");p&&z.selectAll("text").attr("transform",function(a,b,c){return"translate(0,"+(c%2==0?"5":"17")+")"})}n&&(g.scale(d)._ticks(a.utils.calcTicksY(t/36,h)).tickSize(-q,0),x.select(".nv-y.nv-axis").call(g)),x.select(".nv-zeroLine line").attr("x1",0).attr("x2",q).attr("y1",d(0)).attr("y2",d(0))}),t.renderEnd("discreteBar chart immediate"),b}var c,d,e=a.models.discreteBar(),f=a.models.axis(),g=a.models.axis(),h=a.models.tooltip(),i={top:15,right:10,bottom:50,left:60},j=null,k=null,l=a.utils.getColor(),m=!0,n=!0,o=!1,p=!1,q=null,r=d3.dispatch("beforeUpdate","renderEnd"),s=250;f.orient("bottom").showMaxMin(!1).tickFormat(function(a){return a}),g.orient(o?"right":"left").tickFormat(d3.format(",.1f")),h.duration(0).headerEnabled(!1).valueFormatter(function(a,b){return g.tickFormat()(a,b)}).keyFormatter(function(a,b){return f.tickFormat()(a,b)});var t=a.utils.renderWatch(r,s);return e.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:b.x()(a.data),value:b.y()(a.data),color:a.color},h.data(a).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){h.hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(){h.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=r,b.discretebar=e,b.xAxis=f,b.yAxis=g,b.tooltip=h,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return j},set:function(a){j=a}},height:{get:function(){return k},set:function(a){k=a}},staggerLabels:{get:function(){return p},set:function(a){p=a}},showXAxis:{get:function(){return m},set:function(a){m=a}},showYAxis:{get:function(){return n},set:function(a){n=a}},noData:{get:function(){return q},set:function(a){q=a}},tooltips:{get:function(){return h.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),h.enabled(!!b)}},tooltipContent:{get:function(){return h.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),h.contentGenerator(b)}},margin:{get:function(){return i},set:function(a){i.top=void 0!==a.top?a.top:i.top,i.right=void 0!==a.right?a.right:i.right,i.bottom=void 0!==a.bottom?a.bottom:i.bottom,i.left=void 0!==a.left?a.left:i.left}},duration:{get:function(){return s},set:function(a){s=a,t.reset(s),e.duration(s),f.duration(s),g.duration(s)}},color:{get:function(){return l},set:function(b){l=a.utils.getColor(b),e.color(l)}},rightAlignYAxis:{get:function(){return o},set:function(a){o=a,g.orient(a?"right":"left")}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.distribution=function(){"use strict";function b(k){return m.reset(),k.each(function(b){var k=(e-("x"===g?d.left+d.right:d.top+d.bottom),"x"==g?"y":"x"),l=d3.select(this);a.utils.initSVG(l),c=c||j;var n=l.selectAll("g.nv-distribution").data([b]),o=n.enter().append("g").attr("class","nvd3 nv-distribution"),p=(o.append("g"),n.select("g"));n.attr("transform","translate("+d.left+","+d.top+")");var q=p.selectAll("g.nv-dist").data(function(a){return a},function(a){return a.key});q.enter().append("g"),q.attr("class",function(a,b){return"nv-dist nv-series-"+b}).style("stroke",function(a,b){return i(a,b)});var r=q.selectAll("line.nv-dist"+g).data(function(a){return a.values});r.enter().append("line").attr(g+"1",function(a,b){return c(h(a,b))}).attr(g+"2",function(a,b){return c(h(a,b))}),m.transition(q.exit().selectAll("line.nv-dist"+g),"dist exit").attr(g+"1",function(a,b){return j(h(a,b))}).attr(g+"2",function(a,b){return j(h(a,b))}).style("stroke-opacity",0).remove(),r.attr("class",function(a,b){return"nv-dist"+g+" nv-dist"+g+"-"+b}).attr(k+"1",0).attr(k+"2",f),m.transition(r,"dist").attr(g+"1",function(a,b){return j(h(a,b))}).attr(g+"2",function(a,b){return j(h(a,b))}),c=j.copy()}),m.renderEnd("distribution immediate"),b}var c,d={top:0,right:0,bottom:0,left:0},e=400,f=8,g="x",h=function(a){return a[g]},i=a.utils.defaultColor(),j=d3.scale.linear(),k=250,l=d3.dispatch("renderEnd"),m=a.utils.renderWatch(l,k);return b.options=a.utils.optionsFunc.bind(b),b.dispatch=l,b.margin=function(a){return arguments.length?(d.top="undefined"!=typeof a.top?a.top:d.top,d.right="undefined"!=typeof a.right?a.right:d.right,d.bottom="undefined"!=typeof a.bottom?a.bottom:d.bottom,d.left="undefined"!=typeof a.left?a.left:d.left,b):d},b.width=function(a){return arguments.length?(e=a,b):e},b.axis=function(a){return arguments.length?(g=a,b):g},b.size=function(a){return arguments.length?(f=a,b):f},b.getData=function(a){return arguments.length?(h=d3.functor(a),b):h},b.scale=function(a){return arguments.length?(j=a,b):j},b.color=function(c){return arguments.length?(i=a.utils.getColor(c),b):i},b.duration=function(a){return arguments.length?(k=a,m.reset(k),b):k},b},a.models.furiousLegend=function(){"use strict";function b(p){function q(a,b){return"furious"!=o?"#000":m?a.disengaged?g(a,b):"#fff":m?void 0:a.disabled?g(a,b):"#fff"}function r(a,b){return m&&"furious"==o?a.disengaged?"#fff":g(a,b):a.disabled?"#fff":g(a,b)}return p.each(function(b){var p=d-c.left-c.right,s=d3.select(this);a.utils.initSVG(s);var t=s.selectAll("g.nv-legend").data([b]),u=(t.enter().append("g").attr("class","nvd3 nv-legend").append("g"),t.select("g"));t.attr("transform","translate("+c.left+","+c.top+")");var v,w=u.selectAll(".nv-series").data(function(a){return"furious"!=o?a:a.filter(function(a){return m?!0:!a.disengaged})}),x=w.enter().append("g").attr("class","nv-series");if("classic"==o)x.append("circle").style("stroke-width",2).attr("class","nv-legend-symbol").attr("r",5),v=w.select("circle");else if("furious"==o){x.append("rect").style("stroke-width",2).attr("class","nv-legend-symbol").attr("rx",3).attr("ry",3),v=w.select("rect"),x.append("g").attr("class","nv-check-box").property("innerHTML",'').attr("transform","translate(-10,-8)scale(0.5)");var y=w.select(".nv-check-box");y.each(function(a,b){d3.select(this).selectAll("path").attr("stroke",q(a,b))})}x.append("text").attr("text-anchor","start").attr("class","nv-legend-text").attr("dy",".32em").attr("dx","8");var z=w.select("text.nv-legend-text");w.on("mouseover",function(a,b){n.legendMouseover(a,b)}).on("mouseout",function(a,b){n.legendMouseout(a,b)}).on("click",function(a,b){n.legendClick(a,b);var c=w.data();if(k){if("classic"==o)l?(c.forEach(function(a){a.disabled=!0}),a.disabled=!1):(a.disabled=!a.disabled,c.every(function(a){return a.disabled})&&c.forEach(function(a){a.disabled=!1}));else if("furious"==o)if(m)a.disengaged=!a.disengaged,a.userDisabled=void 0==a.userDisabled?!!a.disabled:a.userDisabled,a.disabled=a.disengaged||a.userDisabled;else if(!m){a.disabled=!a.disabled,a.userDisabled=a.disabled;var d=c.filter(function(a){return!a.disengaged});d.every(function(a){return a.userDisabled})&&c.forEach(function(a){a.disabled=a.userDisabled=!1})}n.stateChange({disabled:c.map(function(a){return!!a.disabled}),disengaged:c.map(function(a){return!!a.disengaged})})}}).on("dblclick",function(a,b){if(("furious"!=o||!m)&&(n.legendDblclick(a,b),k)){var c=w.data();c.forEach(function(a){a.disabled=!0,"furious"==o&&(a.userDisabled=a.disabled)}),a.disabled=!1,"furious"==o&&(a.userDisabled=a.disabled),n.stateChange({disabled:c.map(function(a){return!!a.disabled})})}}),w.classed("nv-disabled",function(a){return a.userDisabled}),w.exit().remove(),z.attr("fill",q).text(f);var A;switch(o){case"furious":A=23;break;case"classic":A=20}if(h){var B=[];w.each(function(){var b,c=d3.select(this).select("text");try{if(b=c.node().getComputedTextLength(),0>=b)throw Error()}catch(d){b=a.utils.calcApproxTextWidth(c)}B.push(b+i)});for(var C=0,D=0,E=[];p>D&&Cp&&C>1;){E=[],C--;for(var F=0;F(E[F%C]||0)&&(E[F%C]=B[F]);D=E.reduce(function(a,b){return a+b})}for(var G=[],H=0,I=0;C>H;H++)G[H]=I,I+=E[H];w.attr("transform",function(a,b){return"translate("+G[b%C]+","+(5+Math.floor(b/C)*A)+")"}),j?u.attr("transform","translate("+(d-c.right-D)+","+c.top+")"):u.attr("transform","translate(0,"+c.top+")"),e=c.top+c.bottom+Math.ceil(B.length/C)*A}else{var J,K=5,L=5,M=0;w.attr("transform",function(){var a=d3.select(this).select("text").node().getComputedTextLength()+i;return J=L,dM&&(M=L),"translate("+J+","+K+")"}),u.attr("transform","translate("+(d-c.right-M)+","+c.top+")"),e=c.top+c.bottom+K+15}"furious"==o&&v.attr("width",function(a,b){return z[0][b].getComputedTextLength()+27}).attr("height",18).attr("y",-9).attr("x",-15),v.style("fill",r).style("stroke",function(a,b){return a.color||g(a,b)})}),b}var c={top:5,right:0,bottom:5,left:0},d=400,e=20,f=function(a){return a.key},g=a.utils.getColor(),h=!0,i=28,j=!0,k=!0,l=!1,m=!1,n=d3.dispatch("legendClick","legendDblclick","legendMouseover","legendMouseout","stateChange"),o="classic";return b.dispatch=n,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return d},set:function(a){d=a}},height:{get:function(){return e},set:function(a){e=a}},key:{get:function(){return f},set:function(a){f=a}},align:{get:function(){return h},set:function(a){h=a}},rightAlign:{get:function(){return j},set:function(a){j=a}},padding:{get:function(){return i},set:function(a){i=a}},updateState:{get:function(){return k},set:function(a){k=a}},radioButtonMode:{get:function(){return l},set:function(a){l=a}},expanded:{get:function(){return m},set:function(a){m=a}},vers:{get:function(){return o},set:function(a){o=a}},margin:{get:function(){return c},set:function(a){c.top=void 0!==a.top?a.top:c.top,c.right=void 0!==a.right?a.right:c.right,c.bottom=void 0!==a.bottom?a.bottom:c.bottom,c.left=void 0!==a.left?a.left:c.left}},color:{get:function(){return g},set:function(b){g=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.historicalBar=function(){"use strict";function b(x){return x.each(function(b){w.reset(),k=d3.select(this);var x=a.utils.availableWidth(h,k,g),y=a.utils.availableHeight(i,k,g);a.utils.initSVG(k),l.domain(c||d3.extent(b[0].values.map(n).concat(p))),l.range(r?e||[.5*x/b[0].values.length,x*(b[0].values.length-.5)/b[0].values.length]:e||[0,x]),m.domain(d||d3.extent(b[0].values.map(o).concat(q))).range(f||[y,0]),l.domain()[0]===l.domain()[1]&&l.domain(l.domain()[0]?[l.domain()[0]-.01*l.domain()[0],l.domain()[1]+.01*l.domain()[1]]:[-1,1]),m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]+.01*m.domain()[0],m.domain()[1]-.01*m.domain()[1]]:[-1,1]);var z=k.selectAll("g.nv-wrap.nv-historicalBar-"+j).data([b[0].values]),A=z.enter().append("g").attr("class","nvd3 nv-wrap nv-historicalBar-"+j),B=A.append("defs"),C=A.append("g"),D=z.select("g");C.append("g").attr("class","nv-bars"),z.attr("transform","translate("+g.left+","+g.top+")"),k.on("click",function(a,b){u.chartClick({data:a,index:b,pos:d3.event,id:j})}),B.append("clipPath").attr("id","nv-chart-clip-path-"+j).append("rect"),z.select("#nv-chart-clip-path-"+j+" rect").attr("width",x).attr("height",y),D.attr("clip-path",s?"url(#nv-chart-clip-path-"+j+")":"");var E=z.select(".nv-bars").selectAll(".nv-bar").data(function(a){return a},function(a,b){return n(a,b)});E.exit().remove(),E.enter().append("rect").attr("x",0).attr("y",function(b,c){return a.utils.NaNtoZero(m(Math.max(0,o(b,c))))}).attr("height",function(b,c){return a.utils.NaNtoZero(Math.abs(m(o(b,c))-m(0)))}).attr("transform",function(a,c){return"translate("+(l(n(a,c))-x/b[0].values.length*.45)+",0)"}).on("mouseover",function(a,b){v&&(d3.select(this).classed("hover",!0),u.elementMouseover({data:a,index:b,color:d3.select(this).style("fill")}))}).on("mouseout",function(a,b){v&&(d3.select(this).classed("hover",!1),u.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")}))}).on("mousemove",function(a,b){v&&u.elementMousemove({data:a,index:b,color:d3.select(this).style("fill")})}).on("click",function(a,b){v&&(u.elementClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation())}).on("dblclick",function(a,b){v&&(u.elementDblClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation())}),E.attr("fill",function(a,b){return t(a,b)}).attr("class",function(a,b,c){return(o(a,b)<0?"nv-bar negative":"nv-bar positive")+" nv-bar-"+c+"-"+b}).watchTransition(w,"bars").attr("transform",function(a,c){return"translate("+(l(n(a,c))-x/b[0].values.length*.45)+",0)"}).attr("width",x/b[0].values.length*.9),E.watchTransition(w,"bars").attr("y",function(b,c){var d=o(b,c)<0?m(0):m(0)-m(o(b,c))<1?m(0)-1:m(o(b,c));return a.utils.NaNtoZero(d)}).attr("height",function(b,c){return a.utils.NaNtoZero(Math.max(Math.abs(m(o(b,c))-m(0)),1))})}),w.renderEnd("historicalBar immediate"),b}var c,d,e,f,g={top:0,right:0,bottom:0,left:0},h=null,i=null,j=Math.floor(1e4*Math.random()),k=null,l=d3.scale.linear(),m=d3.scale.linear(),n=function(a){return a.x},o=function(a){return a.y},p=[],q=[0],r=!1,s=!0,t=a.utils.defaultColor(),u=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),v=!0,w=a.utils.renderWatch(u,0);return b.highlightPoint=function(a,b){k.select(".nv-bars .nv-bar-0-"+a).classed("hover",b)},b.clearHighlights=function(){k.select(".nv-bars .nv-bar.hover").classed("hover",!1)},b.dispatch=u,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return i},set:function(a){i=a}},forceX:{get:function(){return p},set:function(a){p=a}},forceY:{get:function(){return q},set:function(a){q=a}},padData:{get:function(){return r},set:function(a){r=a}},x:{get:function(){return n},set:function(a){n=a}},y:{get:function(){return o},set:function(a){o=a}},xScale:{get:function(){return l},set:function(a){l=a}},yScale:{get:function(){return m},set:function(a){m=a}},xDomain:{get:function(){return c},set:function(a){c=a}},yDomain:{get:function(){return d},set:function(a){d=a}},xRange:{get:function(){return e},set:function(a){e=a}},yRange:{get:function(){return f},set:function(a){f=a}},clipEdge:{get:function(){return s},set:function(a){s=a}},id:{get:function(){return j},set:function(a){j=a}},interactive:{get:function(){return v},set:function(a){v=a}},margin:{get:function(){return g},set:function(a){g.top=void 0!==a.top?a.top:g.top,g.right=void 0!==a.right?a.right:g.right,g.bottom=void 0!==a.bottom?a.bottom:g.bottom,g.left=void 0!==a.left?a.left:g.left}},color:{get:function(){return t},set:function(b){t=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.historicalBarChart=function(b){"use strict";function c(b){return b.each(function(k){z.reset(),z.models(f),q&&z.models(g),r&&z.models(h);var w=d3.select(this),A=this;a.utils.initSVG(w);var B=a.utils.availableWidth(n,w,l),C=a.utils.availableHeight(o,w,l);if(c.update=function(){w.transition().duration(y).call(c)},c.container=this,u.disabled=k.map(function(a){return!!a.disabled}),!v){var D;v={};for(D in u)v[D]=u[D]instanceof Array?u[D].slice(0):u[D]}if(!(k&&k.length&&k.filter(function(a){return a.values.length}).length))return a.utils.noData(c,w),c;w.selectAll(".nv-noData").remove(),d=f.xScale(),e=f.yScale();var E=w.selectAll("g.nv-wrap.nv-historicalBarChart").data([k]),F=E.enter().append("g").attr("class","nvd3 nv-wrap nv-historicalBarChart").append("g"),G=E.select("g");F.append("g").attr("class","nv-x nv-axis"),F.append("g").attr("class","nv-y nv-axis"),F.append("g").attr("class","nv-barsWrap"),F.append("g").attr("class","nv-legendWrap"),F.append("g").attr("class","nv-interactive"),p&&(i.width(B),G.select(".nv-legendWrap").datum(k).call(i),l.top!=i.height()&&(l.top=i.height(),C=a.utils.availableHeight(o,w,l)),E.select(".nv-legendWrap").attr("transform","translate(0,"+-l.top+")")),E.attr("transform","translate("+l.left+","+l.top+")"),s&&G.select(".nv-y.nv-axis").attr("transform","translate("+B+",0)"),t&&(j.width(B).height(C).margin({left:l.left,top:l.top}).svgContainer(w).xScale(d),E.select(".nv-interactive").call(j)),f.width(B).height(C).color(k.map(function(a,b){return a.color||m(a,b)}).filter(function(a,b){return!k[b].disabled}));var H=G.select(".nv-barsWrap").datum(k.filter(function(a){return!a.disabled}));H.transition().call(f),q&&(g.scale(d)._ticks(a.utils.calcTicksX(B/100,k)).tickSize(-C,0),G.select(".nv-x.nv-axis").attr("transform","translate(0,"+e.range()[0]+")"),G.select(".nv-x.nv-axis").transition().call(g)),r&&(h.scale(e)._ticks(a.utils.calcTicksY(C/36,k)).tickSize(-B,0),G.select(".nv-y.nv-axis").transition().call(h)),j.dispatch.on("elementMousemove",function(b){f.clearHighlights();var d,e,i,n=[];k.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(g){e=a.interactiveBisect(g.values,b.pointXValue,c.x()),f.highlightPoint(e,!0);var h=g.values[e];void 0!==h&&(void 0===d&&(d=h),void 0===i&&(i=c.xScale()(c.x()(h,e))),n.push({key:g.key,value:c.y()(h,e),color:m(g,g.seriesIndex),data:g.values[e]}))});var o=g.tickFormat()(c.x()(d,e));j.tooltip.position({left:i+l.left,top:b.mouseY+l.top}).chartContainer(A.parentNode).valueFormatter(function(a){return h.tickFormat()(a)}).data({value:o,index:e,series:n})(),j.renderGuideLine(i)}),j.dispatch.on("elementMouseout",function(){x.tooltipHide(),f.clearHighlights()}),i.dispatch.on("legendClick",function(a){a.disabled=!a.disabled,k.filter(function(a){return!a.disabled}).length||k.map(function(a){return a.disabled=!1,E.selectAll(".nv-series").classed("disabled",!1),a}),u.disabled=k.map(function(a){return!!a.disabled}),x.stateChange(u),b.transition().call(c)}),i.dispatch.on("legendDblclick",function(a){k.forEach(function(a){a.disabled=!0}),a.disabled=!1,u.disabled=k.map(function(a){return!!a.disabled}),x.stateChange(u),c.update()}),x.on("changeState",function(a){"undefined"!=typeof a.disabled&&(k.forEach(function(b,c){b.disabled=a.disabled[c]}),u.disabled=a.disabled),c.update()})}),z.renderEnd("historicalBarChart immediate"),c}var d,e,f=b||a.models.historicalBar(),g=a.models.axis(),h=a.models.axis(),i=a.models.legend(),j=a.interactiveGuideline(),k=a.models.tooltip(),l={top:30,right:90,bottom:50,left:90},m=a.utils.defaultColor(),n=null,o=null,p=!1,q=!0,r=!0,s=!1,t=!1,u={},v=null,w=null,x=d3.dispatch("tooltipHide","stateChange","changeState","renderEnd"),y=250;g.orient("bottom").tickPadding(7),h.orient(s?"right":"left"),k.duration(0).headerEnabled(!1).valueFormatter(function(a,b){return h.tickFormat()(a,b)}).headerFormatter(function(a,b){return g.tickFormat()(a,b)});var z=a.utils.renderWatch(x,0);return f.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:c.x()(a.data),value:c.y()(a.data),color:a.color},k.data(a).hidden(!1)}),f.dispatch.on("elementMouseout.tooltip",function(){k.hidden(!0)}),f.dispatch.on("elementMousemove.tooltip",function(){k.position({top:d3.event.pageY,left:d3.event.pageX})()}),c.dispatch=x,c.bars=f,c.legend=i,c.xAxis=g,c.yAxis=h,c.interactiveLayer=j,c.tooltip=k,c.options=a.utils.optionsFunc.bind(c),c._options=Object.create({},{width:{get:function(){return n},set:function(a){n=a}},height:{get:function(){return o},set:function(a){o=a}},showLegend:{get:function(){return p},set:function(a){p=a}},showXAxis:{get:function(){return q},set:function(a){q=a}},showYAxis:{get:function(){return r},set:function(a){r=a}},defaultState:{get:function(){return v},set:function(a){v=a}},noData:{get:function(){return w},set:function(a){w=a}},tooltips:{get:function(){return k.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),k.enabled(!!b)}},tooltipContent:{get:function(){return k.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),k.contentGenerator(b)}},margin:{get:function(){return l},set:function(a){l.top=void 0!==a.top?a.top:l.top,l.right=void 0!==a.right?a.right:l.right,l.bottom=void 0!==a.bottom?a.bottom:l.bottom,l.left=void 0!==a.left?a.left:l.left}},color:{get:function(){return m},set:function(b){m=a.utils.getColor(b),i.color(m),f.color(m)}},duration:{get:function(){return y},set:function(a){y=a,z.reset(y),h.duration(y),g.duration(y)}},rightAlignYAxis:{get:function(){return s},set:function(a){s=a,h.orient(a?"right":"left")}},useInteractiveGuideline:{get:function(){return t},set:function(a){t=a,a===!0&&c.interactive(!1)}}}),a.utils.inheritOptions(c,f),a.utils.initOptions(c),c},a.models.ohlcBarChart=function(){var b=a.models.historicalBarChart(a.models.ohlcBar());return b.useInteractiveGuideline(!0),b.interactiveLayer.tooltip.contentGenerator(function(a){var c=a.series[0].data,d=c.open'+a.value+"
      open:"+b.yAxis.tickFormat()(c.open)+"
      close:"+b.yAxis.tickFormat()(c.close)+"
      high"+b.yAxis.tickFormat()(c.high)+"
      low:"+b.yAxis.tickFormat()(c.low)+"
      "}),b},a.models.candlestickBarChart=function(){var b=a.models.historicalBarChart(a.models.candlestickBar());return b.useInteractiveGuideline(!0),b.interactiveLayer.tooltip.contentGenerator(function(a){var c=a.series[0].data,d=c.open'+a.value+"
      open:"+b.yAxis.tickFormat()(c.open)+"
      close:"+b.yAxis.tickFormat()(c.close)+"
      high"+b.yAxis.tickFormat()(c.high)+"
      low:"+b.yAxis.tickFormat()(c.low)+"
      "}),b},a.models.legend=function(){"use strict";function b(p){function q(a,b){return"furious"!=o?"#000":m?a.disengaged?"#000":"#fff":m?void 0:(a.color||(a.color=g(a,b)),a.disabled?a.color:"#fff")}function r(a,b){return m&&"furious"==o&&a.disengaged?"#eee":a.color||g(a,b)}function s(a){return m&&"furious"==o?1:a.disabled?0:1}return p.each(function(b){var g=d-c.left-c.right,p=d3.select(this);a.utils.initSVG(p);var t=p.selectAll("g.nv-legend").data([b]),u=t.enter().append("g").attr("class","nvd3 nv-legend").append("g"),v=t.select("g");t.attr("transform","translate("+c.left+","+c.top+")");var w,x,y=v.selectAll(".nv-series").data(function(a){return"furious"!=o?a:a.filter(function(a){return m?!0:!a.disengaged})}),z=y.enter().append("g").attr("class","nv-series");switch(o){case"furious":x=23;break;case"classic":x=20}if("classic"==o)z.append("circle").style("stroke-width",2).attr("class","nv-legend-symbol").attr("r",5),w=y.select("circle");else if("furious"==o){z.append("rect").style("stroke-width",2).attr("class","nv-legend-symbol").attr("rx",3).attr("ry",3),w=y.select(".nv-legend-symbol"),z.append("g").attr("class","nv-check-box").property("innerHTML",'').attr("transform","translate(-10,-8)scale(0.5)");var A=y.select(".nv-check-box");A.each(function(a,b){d3.select(this).selectAll("path").attr("stroke",q(a,b))})}z.append("text").attr("text-anchor","start").attr("class","nv-legend-text").attr("dy",".32em").attr("dx","8");var B=y.select("text.nv-legend-text");y.on("mouseover",function(a,b){n.legendMouseover(a,b)}).on("mouseout",function(a,b){n.legendMouseout(a,b)}).on("click",function(a,b){n.legendClick(a,b);var c=y.data();if(k){if("classic"==o)l?(c.forEach(function(a){a.disabled=!0}),a.disabled=!1):(a.disabled=!a.disabled,c.every(function(a){return a.disabled})&&c.forEach(function(a){a.disabled=!1}));else if("furious"==o)if(m)a.disengaged=!a.disengaged,a.userDisabled=void 0==a.userDisabled?!!a.disabled:a.userDisabled,a.disabled=a.disengaged||a.userDisabled;else if(!m){a.disabled=!a.disabled,a.userDisabled=a.disabled;var d=c.filter(function(a){return!a.disengaged});d.every(function(a){return a.userDisabled})&&c.forEach(function(a){a.disabled=a.userDisabled=!1})}n.stateChange({disabled:c.map(function(a){return!!a.disabled}),disengaged:c.map(function(a){return!!a.disengaged})})}}).on("dblclick",function(a,b){if(("furious"!=o||!m)&&(n.legendDblclick(a,b),k)){var c=y.data();c.forEach(function(a){a.disabled=!0,"furious"==o&&(a.userDisabled=a.disabled)}),a.disabled=!1,"furious"==o&&(a.userDisabled=a.disabled),n.stateChange({disabled:c.map(function(a){return!!a.disabled})})}}),y.classed("nv-disabled",function(a){return a.userDisabled}),y.exit().remove(),B.attr("fill",q).text(f);var C=0;if(h){var D=[];y.each(function(){var b,c=d3.select(this).select("text");try{if(b=c.node().getComputedTextLength(),0>=b)throw Error()}catch(d){b=a.utils.calcApproxTextWidth(c)}D.push(b+i)});var E=0,F=[];for(C=0;g>C&&Eg&&E>1;){F=[],E--;for(var G=0;G(F[G%E]||0)&&(F[G%E]=D[G]);C=F.reduce(function(a,b){return a+b})}for(var H=[],I=0,J=0;E>I;I++)H[I]=J,J+=F[I];y.attr("transform",function(a,b){return"translate("+H[b%E]+","+(5+Math.floor(b/E)*x)+")"}),j?v.attr("transform","translate("+(d-c.right-C)+","+c.top+")"):v.attr("transform","translate(0,"+c.top+")"),e=c.top+c.bottom+Math.ceil(D.length/E)*x}else{var K,L=5,M=5,N=0;y.attr("transform",function(){var a=d3.select(this).select("text").node().getComputedTextLength()+i;return K=M,dN&&(N=M),K+N>C&&(C=K+N),"translate("+K+","+L+")"}),v.attr("transform","translate("+(d-c.right-N)+","+c.top+")"),e=c.top+c.bottom+L+15}if("furious"==o){w.attr("width",function(a,b){return B[0][b].getComputedTextLength()+27}).attr("height",18).attr("y",-9).attr("x",-15),u.insert("rect",":first-child").attr("class","nv-legend-bg").attr("fill","#eee").attr("opacity",0);var O=v.select(".nv-legend-bg");O.transition().duration(300).attr("x",-x).attr("width",C+x-12).attr("height",e+10).attr("y",-c.top-10).attr("opacity",m?1:0)}w.style("fill",r).style("fill-opacity",s).style("stroke",r)}),b}var c={top:5,right:0,bottom:5,left:0},d=400,e=20,f=function(a){return a.key},g=a.utils.getColor(),h=!0,i=32,j=!0,k=!0,l=!1,m=!1,n=d3.dispatch("legendClick","legendDblclick","legendMouseover","legendMouseout","stateChange"),o="classic";return b.dispatch=n,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return d},set:function(a){d=a}},height:{get:function(){return e},set:function(a){e=a}},key:{get:function(){return f},set:function(a){f=a}},align:{get:function(){return h},set:function(a){h=a}},rightAlign:{get:function(){return j},set:function(a){j=a}},padding:{get:function(){return i},set:function(a){i=a}},updateState:{get:function(){return k},set:function(a){k=a}},radioButtonMode:{get:function(){return l},set:function(a){l=a}},expanded:{get:function(){return m},set:function(a){m=a}},vers:{get:function(){return o},set:function(a){o=a}},margin:{get:function(){return c},set:function(a){c.top=void 0!==a.top?a.top:c.top,c.right=void 0!==a.right?a.right:c.right,c.bottom=void 0!==a.bottom?a.bottom:c.bottom,c.left=void 0!==a.left?a.left:c.left}},color:{get:function(){return g},set:function(b){g=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.line=function(){"use strict";function b(r){return v.reset(),v.models(e),r.each(function(b){i=d3.select(this);var r=a.utils.availableWidth(g,i,f),s=a.utils.availableHeight(h,i,f);a.utils.initSVG(i),c=e.xScale(),d=e.yScale(),t=t||c,u=u||d;var w=i.selectAll("g.nv-wrap.nv-line").data([b]),x=w.enter().append("g").attr("class","nvd3 nv-wrap nv-line"),y=x.append("defs"),z=x.append("g"),A=w.select("g");z.append("g").attr("class","nv-groups"),z.append("g").attr("class","nv-scatterWrap"),w.attr("transform","translate("+f.left+","+f.top+")"),e.width(r).height(s);var B=w.select(".nv-scatterWrap");B.call(e),y.append("clipPath").attr("id","nv-edge-clip-"+e.id()).append("rect"),w.select("#nv-edge-clip-"+e.id()+" rect").attr("width",r).attr("height",s>0?s:0),A.attr("clip-path",p?"url(#nv-edge-clip-"+e.id()+")":""),B.attr("clip-path",p?"url(#nv-edge-clip-"+e.id()+")":"");var C=w.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a){return a.key});C.enter().append("g").style("stroke-opacity",1e-6).style("stroke-width",function(a){return a.strokeWidth||j}).style("fill-opacity",1e-6),C.exit().remove(),C.attr("class",function(a,b){return(a.classed||"")+" nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}).style("fill",function(a,b){return k(a,b)}).style("stroke",function(a,b){return k(a,b)}),C.watchTransition(v,"line: groups").style("stroke-opacity",1).style("fill-opacity",function(a){return a.fillOpacity||.5});var D=C.selectAll("path.nv-area").data(function(a){return o(a)?[a]:[]});D.enter().append("path").attr("class","nv-area").attr("d",function(b){return d3.svg.area().interpolate(q).defined(n).x(function(b,c){return a.utils.NaNtoZero(t(l(b,c)))}).y0(function(b,c){return a.utils.NaNtoZero(u(m(b,c)))}).y1(function(){return u(d.domain()[0]<=0?d.domain()[1]>=0?0:d.domain()[1]:d.domain()[0])}).apply(this,[b.values])}),C.exit().selectAll("path.nv-area").remove(),D.watchTransition(v,"line: areaPaths").attr("d",function(b){return d3.svg.area().interpolate(q).defined(n).x(function(b,d){return a.utils.NaNtoZero(c(l(b,d)))}).y0(function(b,c){return a.utils.NaNtoZero(d(m(b,c)))}).y1(function(){return d(d.domain()[0]<=0?d.domain()[1]>=0?0:d.domain()[1]:d.domain()[0])}).apply(this,[b.values])});var E=C.selectAll("path.nv-line").data(function(a){return[a.values]});E.enter().append("path").attr("class","nv-line").attr("d",d3.svg.line().interpolate(q).defined(n).x(function(b,c){return a.utils.NaNtoZero(t(l(b,c)))}).y(function(b,c){return a.utils.NaNtoZero(u(m(b,c)))})),E.watchTransition(v,"line: linePaths").attr("d",d3.svg.line().interpolate(q).defined(n).x(function(b,d){return a.utils.NaNtoZero(c(l(b,d)))}).y(function(b,c){return a.utils.NaNtoZero(d(m(b,c)))})),t=c.copy(),u=d.copy()}),v.renderEnd("line immediate"),b}var c,d,e=a.models.scatter(),f={top:0,right:0,bottom:0,left:0},g=960,h=500,i=null,j=1.5,k=a.utils.defaultColor(),l=function(a){return a.x},m=function(a){return a.y},n=function(a,b){return!isNaN(m(a,b))&&null!==m(a,b)},o=function(a){return a.area},p=!1,q="linear",r=250,s=d3.dispatch("elementClick","elementMouseover","elementMouseout","renderEnd");e.pointSize(16).pointDomain([16,256]);var t,u,v=a.utils.renderWatch(s,r);return b.dispatch=s,b.scatter=e,e.dispatch.on("elementClick",function(){s.elementClick.apply(this,arguments)}),e.dispatch.on("elementMouseover",function(){s.elementMouseover.apply(this,arguments)}),e.dispatch.on("elementMouseout",function(){s.elementMouseout.apply(this,arguments)}),b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return g},set:function(a){g=a}},height:{get:function(){return h},set:function(a){h=a}},defined:{get:function(){return n},set:function(a){n=a}},interpolate:{get:function(){return q},set:function(a){q=a}},clipEdge:{get:function(){return p},set:function(a){p=a}},margin:{get:function(){return f},set:function(a){f.top=void 0!==a.top?a.top:f.top,f.right=void 0!==a.right?a.right:f.right,f.bottom=void 0!==a.bottom?a.bottom:f.bottom,f.left=void 0!==a.left?a.left:f.left}},duration:{get:function(){return r},set:function(a){r=a,v.reset(r),e.duration(r)}},isArea:{get:function(){return o},set:function(a){o=d3.functor(a)}},x:{get:function(){return l},set:function(a){l=a,e.x(a)}},y:{get:function(){return m},set:function(a){m=a,e.y(a)}},color:{get:function(){return k},set:function(b){k=a.utils.getColor(b),e.color(k)}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.lineChart=function(){"use strict";function b(j){return y.reset(),y.models(e),p&&y.models(f),q&&y.models(g),j.each(function(j){var v=d3.select(this),y=this;a.utils.initSVG(v);var B=a.utils.availableWidth(m,v,k),C=a.utils.availableHeight(n,v,k);if(b.update=function(){0===x?v.call(b):v.transition().duration(x).call(b)},b.container=this,t.setter(A(j),b.update).getter(z(j)).update(),t.disabled=j.map(function(a){return!!a.disabled}),!u){var D;u={};for(D in t)u[D]=t[D]instanceof Array?t[D].slice(0):t[D] +}if(!(j&&j.length&&j.filter(function(a){return a.values.length}).length))return a.utils.noData(b,v),b;v.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale();var E=v.selectAll("g.nv-wrap.nv-lineChart").data([j]),F=E.enter().append("g").attr("class","nvd3 nv-wrap nv-lineChart").append("g"),G=E.select("g");F.append("rect").style("opacity",0),F.append("g").attr("class","nv-x nv-axis"),F.append("g").attr("class","nv-y nv-axis"),F.append("g").attr("class","nv-linesWrap"),F.append("g").attr("class","nv-legendWrap"),F.append("g").attr("class","nv-interactive"),G.select("rect").attr("width",B).attr("height",C>0?C:0),o&&(h.width(B),G.select(".nv-legendWrap").datum(j).call(h),k.top!=h.height()&&(k.top=h.height(),C=a.utils.availableHeight(n,v,k)),E.select(".nv-legendWrap").attr("transform","translate(0,"+-k.top+")")),E.attr("transform","translate("+k.left+","+k.top+")"),r&&G.select(".nv-y.nv-axis").attr("transform","translate("+B+",0)"),s&&(i.width(B).height(C).margin({left:k.left,top:k.top}).svgContainer(v).xScale(c),E.select(".nv-interactive").call(i)),e.width(B).height(C).color(j.map(function(a,b){return a.color||l(a,b)}).filter(function(a,b){return!j[b].disabled}));var H=G.select(".nv-linesWrap").datum(j.filter(function(a){return!a.disabled}));H.call(e),p&&(f.scale(c)._ticks(a.utils.calcTicksX(B/100,j)).tickSize(-C,0),G.select(".nv-x.nv-axis").attr("transform","translate(0,"+d.range()[0]+")"),G.select(".nv-x.nv-axis").call(f)),q&&(g.scale(d)._ticks(a.utils.calcTicksY(C/36,j)).tickSize(-B,0),G.select(".nv-y.nv-axis").call(g)),h.dispatch.on("stateChange",function(a){for(var c in a)t[c]=a[c];w.stateChange(t),b.update()}),i.dispatch.on("elementMousemove",function(c){e.clearHighlights();var d,h,m,n=[];if(j.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(f,g){h=a.interactiveBisect(f.values,c.pointXValue,b.x());var i=f.values[h],j=b.y()(i,h);null!=j&&e.highlightPoint(g,h,!0),void 0!==i&&(void 0===d&&(d=i),void 0===m&&(m=b.xScale()(b.x()(i,h))),n.push({key:f.key,value:j,color:l(f,f.seriesIndex)}))}),n.length>2){var o=b.yScale().invert(c.mouseY),p=Math.abs(b.yScale().domain()[0]-b.yScale().domain()[1]),q=.03*p,r=a.nearestValueIndex(n.map(function(a){return a.value}),o,q);null!==r&&(n[r].highlight=!0)}var s=f.tickFormat()(b.x()(d,h));i.tooltip.position({left:c.mouseX+k.left,top:c.mouseY+k.top}).chartContainer(y.parentNode).valueFormatter(function(a){return null==a?"N/A":g.tickFormat()(a)}).data({value:s,index:h,series:n})(),i.renderGuideLine(m)}),i.dispatch.on("elementClick",function(c){var d,f=[];j.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(e){var g=a.interactiveBisect(e.values,c.pointXValue,b.x()),h=e.values[g];if("undefined"!=typeof h){"undefined"==typeof d&&(d=b.xScale()(b.x()(h,g)));var i=b.yScale()(b.y()(h,g));f.push({point:h,pointIndex:g,pos:[d,i],seriesIndex:e.seriesIndex,series:e})}}),e.dispatch.elementClick(f)}),i.dispatch.on("elementMouseout",function(){e.clearHighlights()}),w.on("changeState",function(a){"undefined"!=typeof a.disabled&&j.length===a.disabled.length&&(j.forEach(function(b,c){b.disabled=a.disabled[c]}),t.disabled=a.disabled),b.update()})}),y.renderEnd("lineChart immediate"),b}var c,d,e=a.models.line(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend(),i=a.interactiveGuideline(),j=a.models.tooltip(),k={top:30,right:20,bottom:50,left:60},l=a.utils.defaultColor(),m=null,n=null,o=!0,p=!0,q=!0,r=!1,s=!1,t=a.utils.state(),u=null,v=null,w=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd"),x=250;f.orient("bottom").tickPadding(7),g.orient(r?"right":"left"),j.valueFormatter(function(a,b){return g.tickFormat()(a,b)}).headerFormatter(function(a,b){return f.tickFormat()(a,b)});var y=a.utils.renderWatch(w,x),z=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},A=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return e.dispatch.on("elementMouseover.tooltip",function(a){j.data(a).position(a.pos).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){j.hidden(!0)}),b.dispatch=w,b.lines=e,b.legend=h,b.xAxis=f,b.yAxis=g,b.interactiveLayer=i,b.tooltip=j,b.dispatch=w,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return m},set:function(a){m=a}},height:{get:function(){return n},set:function(a){n=a}},showLegend:{get:function(){return o},set:function(a){o=a}},showXAxis:{get:function(){return p},set:function(a){p=a}},showYAxis:{get:function(){return q},set:function(a){q=a}},defaultState:{get:function(){return u},set:function(a){u=a}},noData:{get:function(){return v},set:function(a){v=a}},tooltips:{get:function(){return j.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),j.enabled(!!b)}},tooltipContent:{get:function(){return j.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),j.contentGenerator(b)}},margin:{get:function(){return k},set:function(a){k.top=void 0!==a.top?a.top:k.top,k.right=void 0!==a.right?a.right:k.right,k.bottom=void 0!==a.bottom?a.bottom:k.bottom,k.left=void 0!==a.left?a.left:k.left}},duration:{get:function(){return x},set:function(a){x=a,y.reset(x),e.duration(x),f.duration(x),g.duration(x)}},color:{get:function(){return l},set:function(b){l=a.utils.getColor(b),h.color(l),e.color(l)}},rightAlignYAxis:{get:function(){return r},set:function(a){r=a,g.orient(r?"right":"left")}},useInteractiveGuideline:{get:function(){return s},set:function(a){s=a,s&&(e.interactive(!1),e.useVoronoi(!1))}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.linePlusBarChart=function(){"use strict";function b(v){return v.each(function(v){function J(a){var b=+("e"==a),c=b?1:-1,d=X/3;return"M"+.5*c+","+d+"A6,6 0 0 "+b+" "+6.5*c+","+(d+6)+"V"+(2*d-6)+"A6,6 0 0 "+b+" "+.5*c+","+2*d+"ZM"+2.5*c+","+(d+8)+"V"+(2*d-8)+"M"+4.5*c+","+(d+8)+"V"+(2*d-8)}function S(){u.empty()||u.extent(I),kb.data([u.empty()?e.domain():I]).each(function(a){var b=e(a[0])-e.range()[0],c=e.range()[1]-e(a[1]);d3.select(this).select(".left").attr("width",0>b?0:b),d3.select(this).select(".right").attr("x",e(a[1])).attr("width",0>c?0:c)})}function T(){I=u.empty()?null:u.extent(),c=u.empty()?e.domain():u.extent(),K.brush({extent:c,brush:u}),S(),l.width(V).height(W).color(v.map(function(a,b){return a.color||C(a,b)}).filter(function(a,b){return!v[b].disabled&&v[b].bar})),j.width(V).height(W).color(v.map(function(a,b){return a.color||C(a,b)}).filter(function(a,b){return!v[b].disabled&&!v[b].bar}));var b=db.select(".nv-focus .nv-barsWrap").datum(Z.length?Z.map(function(a){return{key:a.key,values:a.values.filter(function(a,b){return l.x()(a,b)>=c[0]&&l.x()(a,b)<=c[1]})}}):[{values:[]}]),h=db.select(".nv-focus .nv-linesWrap").datum($[0].disabled?[{values:[]}]:$.map(function(a){return{area:a.area,fillOpacity:a.fillOpacity,key:a.key,values:a.values.filter(function(a,b){return j.x()(a,b)>=c[0]&&j.x()(a,b)<=c[1]})}}));d=Z.length?l.xScale():j.xScale(),n.scale(d)._ticks(a.utils.calcTicksX(V/100,v)).tickSize(-W,0),n.domain([Math.ceil(c[0]),Math.floor(c[1])]),db.select(".nv-x.nv-axis").transition().duration(L).call(n),b.transition().duration(L).call(l),h.transition().duration(L).call(j),db.select(".nv-focus .nv-x.nv-axis").attr("transform","translate(0,"+f.range()[0]+")"),p.scale(f)._ticks(a.utils.calcTicksY(W/36,v)).tickSize(-V,0),q.scale(g)._ticks(a.utils.calcTicksY(W/36,v)).tickSize(Z.length?0:-V,0),db.select(".nv-focus .nv-y1.nv-axis").style("opacity",Z.length?1:0),db.select(".nv-focus .nv-y2.nv-axis").style("opacity",$.length&&!$[0].disabled?1:0).attr("transform","translate("+d.range()[1]+",0)"),db.select(".nv-focus .nv-y1.nv-axis").transition().duration(L).call(p),db.select(".nv-focus .nv-y2.nv-axis").transition().duration(L).call(q)}var U=d3.select(this);a.utils.initSVG(U);var V=a.utils.availableWidth(y,U,w),W=a.utils.availableHeight(z,U,w)-(E?H:0),X=H-x.top-x.bottom;if(b.update=function(){U.transition().duration(L).call(b)},b.container=this,M.setter(R(v),b.update).getter(Q(v)).update(),M.disabled=v.map(function(a){return!!a.disabled}),!N){var Y;N={};for(Y in M)N[Y]=M[Y]instanceof Array?M[Y].slice(0):M[Y]}if(!(v&&v.length&&v.filter(function(a){return a.values.length}).length))return a.utils.noData(b,U),b;U.selectAll(".nv-noData").remove();var Z=v.filter(function(a){return!a.disabled&&a.bar}),$=v.filter(function(a){return!a.bar});d=l.xScale(),e=o.scale(),f=l.yScale(),g=j.yScale(),h=m.yScale(),i=k.yScale();var _=v.filter(function(a){return!a.disabled&&a.bar}).map(function(a){return a.values.map(function(a,b){return{x:A(a,b),y:B(a,b)}})}),ab=v.filter(function(a){return!a.disabled&&!a.bar}).map(function(a){return a.values.map(function(a,b){return{x:A(a,b),y:B(a,b)}})});d.range([0,V]),e.domain(d3.extent(d3.merge(_.concat(ab)),function(a){return a.x})).range([0,V]);var bb=U.selectAll("g.nv-wrap.nv-linePlusBar").data([v]),cb=bb.enter().append("g").attr("class","nvd3 nv-wrap nv-linePlusBar").append("g"),db=bb.select("g");cb.append("g").attr("class","nv-legendWrap");var eb=cb.append("g").attr("class","nv-focus");eb.append("g").attr("class","nv-x nv-axis"),eb.append("g").attr("class","nv-y1 nv-axis"),eb.append("g").attr("class","nv-y2 nv-axis"),eb.append("g").attr("class","nv-barsWrap"),eb.append("g").attr("class","nv-linesWrap");var fb=cb.append("g").attr("class","nv-context");if(fb.append("g").attr("class","nv-x nv-axis"),fb.append("g").attr("class","nv-y1 nv-axis"),fb.append("g").attr("class","nv-y2 nv-axis"),fb.append("g").attr("class","nv-barsWrap"),fb.append("g").attr("class","nv-linesWrap"),fb.append("g").attr("class","nv-brushBackground"),fb.append("g").attr("class","nv-x nv-brush"),D){var gb=t.align()?V/2:V,hb=t.align()?gb:0;t.width(gb),db.select(".nv-legendWrap").datum(v.map(function(a){return a.originalKey=void 0===a.originalKey?a.key:a.originalKey,a.key=a.originalKey+(a.bar?O:P),a})).call(t),w.top!=t.height()&&(w.top=t.height(),W=a.utils.availableHeight(z,U,w)-H),db.select(".nv-legendWrap").attr("transform","translate("+hb+","+-w.top+")")}bb.attr("transform","translate("+w.left+","+w.top+")"),db.select(".nv-context").style("display",E?"initial":"none"),m.width(V).height(X).color(v.map(function(a,b){return a.color||C(a,b)}).filter(function(a,b){return!v[b].disabled&&v[b].bar})),k.width(V).height(X).color(v.map(function(a,b){return a.color||C(a,b)}).filter(function(a,b){return!v[b].disabled&&!v[b].bar}));var ib=db.select(".nv-context .nv-barsWrap").datum(Z.length?Z:[{values:[]}]),jb=db.select(".nv-context .nv-linesWrap").datum($[0].disabled?[{values:[]}]:$);db.select(".nv-context").attr("transform","translate(0,"+(W+w.bottom+x.top)+")"),ib.transition().call(m),jb.transition().call(k),G&&(o._ticks(a.utils.calcTicksX(V/100,v)).tickSize(-X,0),db.select(".nv-context .nv-x.nv-axis").attr("transform","translate(0,"+h.range()[0]+")"),db.select(".nv-context .nv-x.nv-axis").transition().call(o)),F&&(r.scale(h)._ticks(X/36).tickSize(-V,0),s.scale(i)._ticks(X/36).tickSize(Z.length?0:-V,0),db.select(".nv-context .nv-y3.nv-axis").style("opacity",Z.length?1:0).attr("transform","translate(0,"+e.range()[0]+")"),db.select(".nv-context .nv-y2.nv-axis").style("opacity",$.length?1:0).attr("transform","translate("+e.range()[1]+",0)"),db.select(".nv-context .nv-y1.nv-axis").transition().call(r),db.select(".nv-context .nv-y2.nv-axis").transition().call(s)),u.x(e).on("brush",T),I&&u.extent(I);var kb=db.select(".nv-brushBackground").selectAll("g").data([I||u.extent()]),lb=kb.enter().append("g");lb.append("rect").attr("class","left").attr("x",0).attr("y",0).attr("height",X),lb.append("rect").attr("class","right").attr("x",0).attr("y",0).attr("height",X);var mb=db.select(".nv-x.nv-brush").call(u);mb.selectAll("rect").attr("height",X),mb.selectAll(".resize").append("path").attr("d",J),t.dispatch.on("stateChange",function(a){for(var c in a)M[c]=a[c];K.stateChange(M),b.update()}),K.on("changeState",function(a){"undefined"!=typeof a.disabled&&(v.forEach(function(b,c){b.disabled=a.disabled[c]}),M.disabled=a.disabled),b.update()}),T()}),b}var c,d,e,f,g,h,i,j=a.models.line(),k=a.models.line(),l=a.models.historicalBar(),m=a.models.historicalBar(),n=a.models.axis(),o=a.models.axis(),p=a.models.axis(),q=a.models.axis(),r=a.models.axis(),s=a.models.axis(),t=a.models.legend(),u=d3.svg.brush(),v=a.models.tooltip(),w={top:30,right:30,bottom:30,left:60},x={top:0,right:30,bottom:20,left:60},y=null,z=null,A=function(a){return a.x},B=function(a){return a.y},C=a.utils.defaultColor(),D=!0,E=!0,F=!1,G=!0,H=50,I=null,J=null,K=d3.dispatch("brush","stateChange","changeState"),L=0,M=a.utils.state(),N=null,O=" (left axis)",P=" (right axis)";j.clipEdge(!0),k.interactive(!1),n.orient("bottom").tickPadding(5),p.orient("left"),q.orient("right"),o.orient("bottom").tickPadding(5),r.orient("left"),s.orient("right"),v.headerEnabled(!0).headerFormatter(function(a,b){return n.tickFormat()(a,b)});var Q=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},R=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return j.dispatch.on("elementMouseover.tooltip",function(a){v.duration(100).valueFormatter(function(a,b){return q.tickFormat()(a,b)}).data(a).position(a.pos).hidden(!1)}),j.dispatch.on("elementMouseout.tooltip",function(){v.hidden(!0)}),l.dispatch.on("elementMouseover.tooltip",function(a){a.value=b.x()(a.data),a.series={value:b.y()(a.data),color:a.color},v.duration(0).valueFormatter(function(a,b){return p.tickFormat()(a,b)}).data(a).hidden(!1)}),l.dispatch.on("elementMouseout.tooltip",function(){v.hidden(!0)}),l.dispatch.on("elementMousemove.tooltip",function(){v.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=K,b.legend=t,b.lines=j,b.lines2=k,b.bars=l,b.bars2=m,b.xAxis=n,b.x2Axis=o,b.y1Axis=p,b.y2Axis=q,b.y3Axis=r,b.y4Axis=s,b.tooltip=v,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return y},set:function(a){y=a}},height:{get:function(){return z},set:function(a){z=a}},showLegend:{get:function(){return D},set:function(a){D=a}},brushExtent:{get:function(){return I},set:function(a){I=a}},noData:{get:function(){return J},set:function(a){J=a}},focusEnable:{get:function(){return E},set:function(a){E=a}},focusHeight:{get:function(){return H},set:function(a){H=a}},focusShowAxisX:{get:function(){return G},set:function(a){G=a}},focusShowAxisY:{get:function(){return F},set:function(a){F=a}},legendLeftAxisHint:{get:function(){return O},set:function(a){O=a}},legendRightAxisHint:{get:function(){return P},set:function(a){P=a}},tooltips:{get:function(){return v.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),v.enabled(!!b)}},tooltipContent:{get:function(){return v.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),v.contentGenerator(b)}},margin:{get:function(){return w},set:function(a){w.top=void 0!==a.top?a.top:w.top,w.right=void 0!==a.right?a.right:w.right,w.bottom=void 0!==a.bottom?a.bottom:w.bottom,w.left=void 0!==a.left?a.left:w.left}},duration:{get:function(){return L},set:function(a){L=a}},color:{get:function(){return C},set:function(b){C=a.utils.getColor(b),t.color(C)}},x:{get:function(){return A},set:function(a){A=a,j.x(a),k.x(a),l.x(a),m.x(a)}},y:{get:function(){return B},set:function(a){B=a,j.y(a),k.y(a),l.y(a),m.y(a)}}}),a.utils.inheritOptions(b,j),a.utils.initOptions(b),b},a.models.lineWithFocusChart=function(){"use strict";function b(o){return o.each(function(o){function z(a){var b=+("e"==a),c=b?1:-1,d=M/3;return"M"+.5*c+","+d+"A6,6 0 0 "+b+" "+6.5*c+","+(d+6)+"V"+(2*d-6)+"A6,6 0 0 "+b+" "+.5*c+","+2*d+"ZM"+2.5*c+","+(d+8)+"V"+(2*d-8)+"M"+4.5*c+","+(d+8)+"V"+(2*d-8)}function G(){n.empty()||n.extent(y),U.data([n.empty()?e.domain():y]).each(function(a){var b=e(a[0])-c.range()[0],d=K-e(a[1]);d3.select(this).select(".left").attr("width",0>b?0:b),d3.select(this).select(".right").attr("x",e(a[1])).attr("width",0>d?0:d)})}function H(){y=n.empty()?null:n.extent();var a=n.empty()?e.domain():n.extent();if(!(Math.abs(a[0]-a[1])<=1)){A.brush({extent:a,brush:n}),G();var b=Q.select(".nv-focus .nv-linesWrap").datum(o.filter(function(a){return!a.disabled}).map(function(b){return{key:b.key,area:b.area,values:b.values.filter(function(b,c){return g.x()(b,c)>=a[0]&&g.x()(b,c)<=a[1]})}}));b.transition().duration(B).call(g),Q.select(".nv-focus .nv-x.nv-axis").transition().duration(B).call(i),Q.select(".nv-focus .nv-y.nv-axis").transition().duration(B).call(j)}}var I=d3.select(this),J=this;a.utils.initSVG(I);var K=a.utils.availableWidth(t,I,q),L=a.utils.availableHeight(u,I,q)-v,M=v-r.top-r.bottom;if(b.update=function(){I.transition().duration(B).call(b)},b.container=this,C.setter(F(o),b.update).getter(E(o)).update(),C.disabled=o.map(function(a){return!!a.disabled}),!D){var N;D={};for(N in C)D[N]=C[N]instanceof Array?C[N].slice(0):C[N]}if(!(o&&o.length&&o.filter(function(a){return a.values.length}).length))return a.utils.noData(b,I),b;I.selectAll(".nv-noData").remove(),c=g.xScale(),d=g.yScale(),e=h.xScale(),f=h.yScale();var O=I.selectAll("g.nv-wrap.nv-lineWithFocusChart").data([o]),P=O.enter().append("g").attr("class","nvd3 nv-wrap nv-lineWithFocusChart").append("g"),Q=O.select("g");P.append("g").attr("class","nv-legendWrap");var R=P.append("g").attr("class","nv-focus");R.append("g").attr("class","nv-x nv-axis"),R.append("g").attr("class","nv-y nv-axis"),R.append("g").attr("class","nv-linesWrap"),R.append("g").attr("class","nv-interactive");var S=P.append("g").attr("class","nv-context");S.append("g").attr("class","nv-x nv-axis"),S.append("g").attr("class","nv-y nv-axis"),S.append("g").attr("class","nv-linesWrap"),S.append("g").attr("class","nv-brushBackground"),S.append("g").attr("class","nv-x nv-brush"),x&&(m.width(K),Q.select(".nv-legendWrap").datum(o).call(m),q.top!=m.height()&&(q.top=m.height(),L=a.utils.availableHeight(u,I,q)-v),Q.select(".nv-legendWrap").attr("transform","translate(0,"+-q.top+")")),O.attr("transform","translate("+q.left+","+q.top+")"),w&&(p.width(K).height(L).margin({left:q.left,top:q.top}).svgContainer(I).xScale(c),O.select(".nv-interactive").call(p)),g.width(K).height(L).color(o.map(function(a,b){return a.color||s(a,b)}).filter(function(a,b){return!o[b].disabled})),h.defined(g.defined()).width(K).height(M).color(o.map(function(a,b){return a.color||s(a,b)}).filter(function(a,b){return!o[b].disabled})),Q.select(".nv-context").attr("transform","translate(0,"+(L+q.bottom+r.top)+")");var T=Q.select(".nv-context .nv-linesWrap").datum(o.filter(function(a){return!a.disabled}));d3.transition(T).call(h),i.scale(c)._ticks(a.utils.calcTicksX(K/100,o)).tickSize(-L,0),j.scale(d)._ticks(a.utils.calcTicksY(L/36,o)).tickSize(-K,0),Q.select(".nv-focus .nv-x.nv-axis").attr("transform","translate(0,"+L+")"),n.x(e).on("brush",function(){H()}),y&&n.extent(y);var U=Q.select(".nv-brushBackground").selectAll("g").data([y||n.extent()]),V=U.enter().append("g");V.append("rect").attr("class","left").attr("x",0).attr("y",0).attr("height",M),V.append("rect").attr("class","right").attr("x",0).attr("y",0).attr("height",M);var W=Q.select(".nv-x.nv-brush").call(n);W.selectAll("rect").attr("height",M),W.selectAll(".resize").append("path").attr("d",z),H(),k.scale(e)._ticks(a.utils.calcTicksX(K/100,o)).tickSize(-M,0),Q.select(".nv-context .nv-x.nv-axis").attr("transform","translate(0,"+f.range()[0]+")"),d3.transition(Q.select(".nv-context .nv-x.nv-axis")).call(k),l.scale(f)._ticks(a.utils.calcTicksY(M/36,o)).tickSize(-K,0),d3.transition(Q.select(".nv-context .nv-y.nv-axis")).call(l),Q.select(".nv-context .nv-x.nv-axis").attr("transform","translate(0,"+f.range()[0]+")"),m.dispatch.on("stateChange",function(a){for(var c in a)C[c]=a[c];A.stateChange(C),b.update()}),p.dispatch.on("elementMousemove",function(c){g.clearHighlights();var d,f,h,k=[];if(o.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(i,j){var l=n.empty()?e.domain():n.extent(),m=i.values.filter(function(a,b){return g.x()(a,b)>=l[0]&&g.x()(a,b)<=l[1]});f=a.interactiveBisect(m,c.pointXValue,g.x());var o=m[f],p=b.y()(o,f);null!=p&&g.highlightPoint(j,f,!0),void 0!==o&&(void 0===d&&(d=o),void 0===h&&(h=b.xScale()(b.x()(o,f))),k.push({key:i.key,value:b.y()(o,f),color:s(i,i.seriesIndex)}))}),k.length>2){var l=b.yScale().invert(c.mouseY),m=Math.abs(b.yScale().domain()[0]-b.yScale().domain()[1]),r=.03*m,t=a.nearestValueIndex(k.map(function(a){return a.value}),l,r);null!==t&&(k[t].highlight=!0)}var u=i.tickFormat()(b.x()(d,f));p.tooltip.position({left:c.mouseX+q.left,top:c.mouseY+q.top}).chartContainer(J.parentNode).valueFormatter(function(a){return null==a?"N/A":j.tickFormat()(a)}).data({value:u,index:f,series:k})(),p.renderGuideLine(h)}),p.dispatch.on("elementMouseout",function(){g.clearHighlights()}),A.on("changeState",function(a){"undefined"!=typeof a.disabled&&o.forEach(function(b,c){b.disabled=a.disabled[c]}),b.update()})}),b}var c,d,e,f,g=a.models.line(),h=a.models.line(),i=a.models.axis(),j=a.models.axis(),k=a.models.axis(),l=a.models.axis(),m=a.models.legend(),n=d3.svg.brush(),o=a.models.tooltip(),p=a.interactiveGuideline(),q={top:30,right:30,bottom:30,left:60},r={top:0,right:30,bottom:20,left:60},s=a.utils.defaultColor(),t=null,u=null,v=50,w=!1,x=!0,y=null,z=null,A=d3.dispatch("brush","stateChange","changeState"),B=250,C=a.utils.state(),D=null;g.clipEdge(!0).duration(0),h.interactive(!1),i.orient("bottom").tickPadding(5),j.orient("left"),k.orient("bottom").tickPadding(5),l.orient("left"),o.valueFormatter(function(a,b){return j.tickFormat()(a,b)}).headerFormatter(function(a,b){return i.tickFormat()(a,b)});var E=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},F=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return g.dispatch.on("elementMouseover.tooltip",function(a){o.data(a).position(a.pos).hidden(!1)}),g.dispatch.on("elementMouseout.tooltip",function(){o.hidden(!0)}),b.dispatch=A,b.legend=m,b.lines=g,b.lines2=h,b.xAxis=i,b.yAxis=j,b.x2Axis=k,b.y2Axis=l,b.interactiveLayer=p,b.tooltip=o,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return t},set:function(a){t=a}},height:{get:function(){return u},set:function(a){u=a}},focusHeight:{get:function(){return v},set:function(a){v=a}},showLegend:{get:function(){return x},set:function(a){x=a}},brushExtent:{get:function(){return y},set:function(a){y=a}},defaultState:{get:function(){return D},set:function(a){D=a}},noData:{get:function(){return z},set:function(a){z=a}},tooltips:{get:function(){return o.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),o.enabled(!!b)}},tooltipContent:{get:function(){return o.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),o.contentGenerator(b)}},margin:{get:function(){return q},set:function(a){q.top=void 0!==a.top?a.top:q.top,q.right=void 0!==a.right?a.right:q.right,q.bottom=void 0!==a.bottom?a.bottom:q.bottom,q.left=void 0!==a.left?a.left:q.left}},color:{get:function(){return s},set:function(b){s=a.utils.getColor(b),m.color(s)}},interpolate:{get:function(){return g.interpolate()},set:function(a){g.interpolate(a),h.interpolate(a)}},xTickFormat:{get:function(){return i.tickFormat()},set:function(a){i.tickFormat(a),k.tickFormat(a)}},yTickFormat:{get:function(){return j.tickFormat()},set:function(a){j.tickFormat(a),l.tickFormat(a)}},duration:{get:function(){return B},set:function(a){B=a,j.duration(B),l.duration(B),i.duration(B),k.duration(B)}},x:{get:function(){return g.x()},set:function(a){g.x(a),h.x(a)}},y:{get:function(){return g.y()},set:function(a){g.y(a),h.y(a)}},useInteractiveGuideline:{get:function(){return w},set:function(a){w=a,w&&(g.interactive(!1),g.useVoronoi(!1))}}}),a.utils.inheritOptions(b,g),a.utils.initOptions(b),b},a.models.multiBar=function(){"use strict";function b(E){return C.reset(),E.each(function(b){var E=k-j.left-j.right,F=l-j.top-j.bottom;p=d3.select(this),a.utils.initSVG(p);var G=0;if(x&&b.length&&(x=[{values:b[0].values.map(function(a){return{x:a.x,y:0,series:a.series,size:.01}})}]),u){var H=d3.layout.stack().offset(v).values(function(a){return a.values}).y(r)(!b.length&&x?x:b);H.forEach(function(a,c){a.nonStackable?(b[c].nonStackableSeries=G++,H[c]=b[c]):c>0&&H[c-1].nonStackable&&H[c].values.map(function(a,b){a.y0-=H[c-1].values[b].y,a.y1=a.y0+a.y})}),b=H}b.forEach(function(a,b){a.values.forEach(function(c){c.series=b,c.key=a.key})}),u&&b[0].values.map(function(a,c){var d=0,e=0;b.map(function(a,f){if(!b[f].nonStackable){var g=a.values[c];g.size=Math.abs(g.y),g.y<0?(g.y1=e,e-=g.size):(g.y1=g.size+d,d+=g.size)}})});var I=d&&e?[]:b.map(function(a,b){return a.values.map(function(a,c){return{x:q(a,c),y:r(a,c),y0:a.y0,y1:a.y1,idx:b}})});m.domain(d||d3.merge(I).map(function(a){return a.x})).rangeBands(f||[0,E],A),n.domain(e||d3.extent(d3.merge(I).map(function(a){var c=a.y;return u&&!b[a.idx].nonStackable&&(c=a.y>0?a.y1:a.y1+a.y),c}).concat(s))).range(g||[F,0]),m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]-.01*m.domain()[0],m.domain()[1]+.01*m.domain()[1]]:[-1,1]),n.domain()[0]===n.domain()[1]&&n.domain(n.domain()[0]?[n.domain()[0]+.01*n.domain()[0],n.domain()[1]-.01*n.domain()[1]]:[-1,1]),h=h||m,i=i||n;var J=p.selectAll("g.nv-wrap.nv-multibar").data([b]),K=J.enter().append("g").attr("class","nvd3 nv-wrap nv-multibar"),L=K.append("defs"),M=K.append("g"),N=J.select("g");M.append("g").attr("class","nv-groups"),J.attr("transform","translate("+j.left+","+j.top+")"),L.append("clipPath").attr("id","nv-edge-clip-"+o).append("rect"),J.select("#nv-edge-clip-"+o+" rect").attr("width",E).attr("height",F),N.attr("clip-path",t?"url(#nv-edge-clip-"+o+")":"");var O=J.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a,b){return b});O.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6);var P=C.transition(O.exit().selectAll("rect.nv-bar"),"multibarExit",Math.min(100,z)).attr("y",function(a){var c=i(0)||0;return u&&b[a.series]&&!b[a.series].nonStackable&&(c=i(a.y0)),c}).attr("height",0).remove();P.delay&&P.delay(function(a,b){var c=b*(z/(D+1))-b;return c}),O.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}).style("fill",function(a,b){return w(a,b)}).style("stroke",function(a,b){return w(a,b)}),O.style("stroke-opacity",1).style("fill-opacity",.75);var Q=O.selectAll("rect.nv-bar").data(function(a){return x&&!b.length?x.values:a.values});Q.exit().remove();Q.enter().append("rect").attr("class",function(a,b){return r(a,b)<0?"nv-bar negative":"nv-bar positive"}).attr("x",function(a,c,d){return u&&!b[d].nonStackable?0:d*m.rangeBand()/b.length}).attr("y",function(a,c,d){return i(u&&!b[d].nonStackable?a.y0:0)||0}).attr("height",0).attr("width",function(a,c,d){return m.rangeBand()/(u&&!b[d].nonStackable?1:b.length)}).attr("transform",function(a,b){return"translate("+m(q(a,b))+",0)"});Q.style("fill",function(a,b,c){return w(a,c,b)}).style("stroke",function(a,b,c){return w(a,c,b)}).on("mouseover",function(a,b){d3.select(this).classed("hover",!0),B.elementMouseover({data:a,index:b,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),B.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")})}).on("mousemove",function(a,b){B.elementMousemove({data:a,index:b,color:d3.select(this).style("fill")})}).on("click",function(a,b){B.elementClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}).on("dblclick",function(a,b){B.elementDblClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}),Q.attr("class",function(a,b){return r(a,b)<0?"nv-bar negative":"nv-bar positive"}).attr("transform",function(a,b){return"translate("+m(q(a,b))+",0)"}),y&&(c||(c=b.map(function(){return!0})),Q.style("fill",function(a,b,d){return d3.rgb(y(a,b)).darker(c.map(function(a,b){return b}).filter(function(a,b){return!c[b]})[d]).toString()}).style("stroke",function(a,b,d){return d3.rgb(y(a,b)).darker(c.map(function(a,b){return b}).filter(function(a,b){return!c[b]})[d]).toString()}));var R=Q.watchTransition(C,"multibar",Math.min(250,z)).delay(function(a,c){return c*z/b[0].values.length});u?R.attr("y",function(a,c,d){var e=0;return e=b[d].nonStackable?r(a,c)<0?n(0):n(0)-n(r(a,c))<-1?n(0)-1:n(r(a,c))||0:n(a.y1)}).attr("height",function(a,c,d){return b[d].nonStackable?Math.max(Math.abs(n(r(a,c))-n(0)),1)||0:Math.max(Math.abs(n(a.y+a.y0)-n(a.y0)),1)}).attr("x",function(a,c,d){var e=0;return b[d].nonStackable&&(e=a.series*m.rangeBand()/b.length,b.length!==G&&(e=b[d].nonStackableSeries*m.rangeBand()/(2*G))),e}).attr("width",function(a,c,d){if(b[d].nonStackable){var e=m.rangeBand()/G;return b.length!==G&&(e=m.rangeBand()/(2*G)),e}return m.rangeBand()}):R.attr("x",function(a){return a.series*m.rangeBand()/b.length}).attr("width",m.rangeBand()/b.length).attr("y",function(a,b){return r(a,b)<0?n(0):n(0)-n(r(a,b))<1?n(0)-1:n(r(a,b))||0}).attr("height",function(a,b){return Math.max(Math.abs(n(r(a,b))-n(0)),1)||0}),h=m.copy(),i=n.copy(),b[0]&&b[0].values&&(D=b[0].values.length)}),C.renderEnd("multibar immediate"),b}var c,d,e,f,g,h,i,j={top:0,right:0,bottom:0,left:0},k=960,l=500,m=d3.scale.ordinal(),n=d3.scale.linear(),o=Math.floor(1e4*Math.random()),p=null,q=function(a){return a.x},r=function(a){return a.y},s=[0],t=!0,u=!1,v="zero",w=a.utils.defaultColor(),x=!1,y=null,z=500,A=.1,B=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),C=a.utils.renderWatch(B,z),D=0;return b.dispatch=B,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},x:{get:function(){return q},set:function(a){q=a}},y:{get:function(){return r},set:function(a){r=a}},xScale:{get:function(){return m},set:function(a){m=a}},yScale:{get:function(){return n},set:function(a){n=a}},xDomain:{get:function(){return d},set:function(a){d=a}},yDomain:{get:function(){return e},set:function(a){e=a}},xRange:{get:function(){return f},set:function(a){f=a}},yRange:{get:function(){return g},set:function(a){g=a}},forceY:{get:function(){return s},set:function(a){s=a}},stacked:{get:function(){return u},set:function(a){u=a}},stackOffset:{get:function(){return v},set:function(a){v=a}},clipEdge:{get:function(){return t},set:function(a){t=a}},disabled:{get:function(){return c},set:function(a){c=a}},id:{get:function(){return o},set:function(a){o=a}},hideable:{get:function(){return x},set:function(a){x=a}},groupSpacing:{get:function(){return A},set:function(a){A=a}},margin:{get:function(){return j},set:function(a){j.top=void 0!==a.top?a.top:j.top,j.right=void 0!==a.right?a.right:j.right,j.bottom=void 0!==a.bottom?a.bottom:j.bottom,j.left=void 0!==a.left?a.left:j.left}},duration:{get:function(){return z},set:function(a){z=a,C.reset(z)}},color:{get:function(){return w},set:function(b){w=a.utils.getColor(b)}},barColor:{get:function(){return y},set:function(b){y=b?a.utils.getColor(b):null}}}),a.utils.initOptions(b),b},a.models.multiBarChart=function(){"use strict";function b(j){return D.reset(),D.models(e),r&&D.models(f),s&&D.models(g),j.each(function(j){var z=d3.select(this);a.utils.initSVG(z);var D=a.utils.availableWidth(l,z,k),H=a.utils.availableHeight(m,z,k);if(b.update=function(){0===C?z.call(b):z.transition().duration(C).call(b)},b.container=this,x.setter(G(j),b.update).getter(F(j)).update(),x.disabled=j.map(function(a){return!!a.disabled}),!y){var I;y={};for(I in x)y[I]=x[I]instanceof Array?x[I].slice(0):x[I]}if(!(j&&j.length&&j.filter(function(a){return a.values.length}).length))return a.utils.noData(b,z),b;z.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale(); +var J=z.selectAll("g.nv-wrap.nv-multiBarWithLegend").data([j]),K=J.enter().append("g").attr("class","nvd3 nv-wrap nv-multiBarWithLegend").append("g"),L=J.select("g");if(K.append("g").attr("class","nv-x nv-axis"),K.append("g").attr("class","nv-y nv-axis"),K.append("g").attr("class","nv-barsWrap"),K.append("g").attr("class","nv-legendWrap"),K.append("g").attr("class","nv-controlsWrap"),q&&(h.width(D-B()),L.select(".nv-legendWrap").datum(j).call(h),k.top!=h.height()&&(k.top=h.height(),H=a.utils.availableHeight(m,z,k)),L.select(".nv-legendWrap").attr("transform","translate("+B()+","+-k.top+")")),o){var M=[{key:p.grouped||"Grouped",disabled:e.stacked()},{key:p.stacked||"Stacked",disabled:!e.stacked()}];i.width(B()).color(["#444","#444","#444"]),L.select(".nv-controlsWrap").datum(M).attr("transform","translate(0,"+-k.top+")").call(i)}J.attr("transform","translate("+k.left+","+k.top+")"),t&&L.select(".nv-y.nv-axis").attr("transform","translate("+D+",0)"),e.disabled(j.map(function(a){return a.disabled})).width(D).height(H).color(j.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!j[b].disabled}));var N=L.select(".nv-barsWrap").datum(j.filter(function(a){return!a.disabled}));if(N.call(e),r){f.scale(c)._ticks(a.utils.calcTicksX(D/100,j)).tickSize(-H,0),L.select(".nv-x.nv-axis").attr("transform","translate(0,"+d.range()[0]+")"),L.select(".nv-x.nv-axis").call(f);var O=L.select(".nv-x.nv-axis > g").selectAll("g");if(O.selectAll("line, text").style("opacity",1),v){var P=function(a,b){return"translate("+a+","+b+")"},Q=5,R=17;O.selectAll("text").attr("transform",function(a,b,c){return P(0,c%2==0?Q:R)});var S=d3.selectAll(".nv-x.nv-axis .nv-wrap g g text")[0].length;L.selectAll(".nv-x.nv-axis .nv-axisMaxMin text").attr("transform",function(a,b){return P(0,0===b||S%2!==0?R:Q)})}u&&O.filter(function(a,b){return b%Math.ceil(j[0].values.length/(D/100))!==0}).selectAll("text, line").style("opacity",0),w&&O.selectAll(".tick text").attr("transform","rotate("+w+" 0,0)").style("text-anchor",w>0?"start":"end"),L.select(".nv-x.nv-axis").selectAll("g.nv-axisMaxMin text").style("opacity",1)}s&&(g.scale(d)._ticks(a.utils.calcTicksY(H/36,j)).tickSize(-D,0),L.select(".nv-y.nv-axis").call(g)),h.dispatch.on("stateChange",function(a){for(var c in a)x[c]=a[c];A.stateChange(x),b.update()}),i.dispatch.on("legendClick",function(a){if(a.disabled){switch(M=M.map(function(a){return a.disabled=!0,a}),a.disabled=!1,a.key){case"Grouped":case p.grouped:e.stacked(!1);break;case"Stacked":case p.stacked:e.stacked(!0)}x.stacked=e.stacked(),A.stateChange(x),b.update()}}),A.on("changeState",function(a){"undefined"!=typeof a.disabled&&(j.forEach(function(b,c){b.disabled=a.disabled[c]}),x.disabled=a.disabled),"undefined"!=typeof a.stacked&&(e.stacked(a.stacked),x.stacked=a.stacked,E=a.stacked),b.update()})}),D.renderEnd("multibarchart immediate"),b}var c,d,e=a.models.multiBar(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend(),i=a.models.legend(),j=a.models.tooltip(),k={top:30,right:20,bottom:50,left:60},l=null,m=null,n=a.utils.defaultColor(),o=!0,p={},q=!0,r=!0,s=!0,t=!1,u=!0,v=!1,w=0,x=a.utils.state(),y=null,z=null,A=d3.dispatch("stateChange","changeState","renderEnd"),B=function(){return o?180:0},C=250;x.stacked=!1,e.stacked(!1),f.orient("bottom").tickPadding(7).showMaxMin(!1).tickFormat(function(a){return a}),g.orient(t?"right":"left").tickFormat(d3.format(",.1f")),j.duration(0).valueFormatter(function(a,b){return g.tickFormat()(a,b)}).headerFormatter(function(a,b){return f.tickFormat()(a,b)}),i.updateState(!1);var D=a.utils.renderWatch(A),E=!1,F=function(a){return function(){return{active:a.map(function(a){return!a.disabled}),stacked:E}}},G=function(a){return function(b){void 0!==b.stacked&&(E=b.stacked),void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return e.dispatch.on("elementMouseover.tooltip",function(a){a.value=b.x()(a.data),a.series={key:a.data.key,value:b.y()(a.data),color:a.color},j.data(a).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){j.hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(){j.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=A,b.multibar=e,b.legend=h,b.controls=i,b.xAxis=f,b.yAxis=g,b.state=x,b.tooltip=j,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return l},set:function(a){l=a}},height:{get:function(){return m},set:function(a){m=a}},showLegend:{get:function(){return q},set:function(a){q=a}},showControls:{get:function(){return o},set:function(a){o=a}},controlLabels:{get:function(){return p},set:function(a){p=a}},showXAxis:{get:function(){return r},set:function(a){r=a}},showYAxis:{get:function(){return s},set:function(a){s=a}},defaultState:{get:function(){return y},set:function(a){y=a}},noData:{get:function(){return z},set:function(a){z=a}},reduceXTicks:{get:function(){return u},set:function(a){u=a}},rotateLabels:{get:function(){return w},set:function(a){w=a}},staggerLabels:{get:function(){return v},set:function(a){v=a}},tooltips:{get:function(){return j.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),j.enabled(!!b)}},tooltipContent:{get:function(){return j.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),j.contentGenerator(b)}},margin:{get:function(){return k},set:function(a){k.top=void 0!==a.top?a.top:k.top,k.right=void 0!==a.right?a.right:k.right,k.bottom=void 0!==a.bottom?a.bottom:k.bottom,k.left=void 0!==a.left?a.left:k.left}},duration:{get:function(){return C},set:function(a){C=a,e.duration(C),f.duration(C),g.duration(C),D.reset(C)}},color:{get:function(){return n},set:function(b){n=a.utils.getColor(b),h.color(n)}},rightAlignYAxis:{get:function(){return t},set:function(a){t=a,g.orient(t?"right":"left")}},barColor:{get:function(){return e.barColor},set:function(a){e.barColor(a),h.color(function(a,b){return d3.rgb("#ccc").darker(1.5*b).toString()})}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.multiBarHorizontal=function(){"use strict";function b(m){return E.reset(),m.each(function(b){var m=k-j.left-j.right,C=l-j.top-j.bottom;n=d3.select(this),a.utils.initSVG(n),w&&(b=d3.layout.stack().offset("zero").values(function(a){return a.values}).y(r)(b)),b.forEach(function(a,b){a.values.forEach(function(c){c.series=b,c.key=a.key})}),w&&b[0].values.map(function(a,c){var d=0,e=0;b.map(function(a){var b=a.values[c];b.size=Math.abs(b.y),b.y<0?(b.y1=e-b.size,e-=b.size):(b.y1=d,d+=b.size)})});var F=d&&e?[]:b.map(function(a){return a.values.map(function(a,b){return{x:q(a,b),y:r(a,b),y0:a.y0,y1:a.y1}})});o.domain(d||d3.merge(F).map(function(a){return a.x})).rangeBands(f||[0,C],A),p.domain(e||d3.extent(d3.merge(F).map(function(a){return w?a.y>0?a.y1+a.y:a.y1:a.y}).concat(t))),p.range(x&&!w?g||[p.domain()[0]<0?z:0,m-(p.domain()[1]>0?z:0)]:g||[0,m]),h=h||o,i=i||d3.scale.linear().domain(p.domain()).range([p(0),p(0)]);{var G=d3.select(this).selectAll("g.nv-wrap.nv-multibarHorizontal").data([b]),H=G.enter().append("g").attr("class","nvd3 nv-wrap nv-multibarHorizontal"),I=(H.append("defs"),H.append("g"));G.select("g")}I.append("g").attr("class","nv-groups"),G.attr("transform","translate("+j.left+","+j.top+")");var J=G.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a,b){return b});J.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),J.exit().watchTransition(E,"multibarhorizontal: exit groups").style("stroke-opacity",1e-6).style("fill-opacity",1e-6).remove(),J.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}).style("fill",function(a,b){return u(a,b)}).style("stroke",function(a,b){return u(a,b)}),J.watchTransition(E,"multibarhorizontal: groups").style("stroke-opacity",1).style("fill-opacity",.75);var K=J.selectAll("g.nv-bar").data(function(a){return a.values});K.exit().remove();var L=K.enter().append("g").attr("transform",function(a,c,d){return"translate("+i(w?a.y0:0)+","+(w?0:d*o.rangeBand()/b.length+o(q(a,c)))+")"});L.append("rect").attr("width",0).attr("height",o.rangeBand()/(w?1:b.length)),K.on("mouseover",function(a,b){d3.select(this).classed("hover",!0),D.elementMouseover({data:a,index:b,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),D.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){D.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")})}).on("mousemove",function(a,b){D.elementMousemove({data:a,index:b,color:d3.select(this).style("fill")})}).on("click",function(a,b){D.elementClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}).on("dblclick",function(a,b){D.elementDblClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}),s(b[0],0)&&(L.append("polyline"),K.select("polyline").attr("fill","none").attr("points",function(a,c){var d=s(a,c),e=.8*o.rangeBand()/(2*(w?1:b.length));d=d.length?d:[-Math.abs(d),Math.abs(d)],d=d.map(function(a){return p(a)-p(0)});var f=[[d[0],-e],[d[0],e],[d[0],0],[d[1],0],[d[1],-e],[d[1],e]];return f.map(function(a){return a.join(",")}).join(" ")}).attr("transform",function(a,c){var d=o.rangeBand()/(2*(w?1:b.length));return"translate("+(r(a,c)<0?0:p(r(a,c))-p(0))+", "+d+")"})),L.append("text"),x&&!w?(K.select("text").attr("text-anchor",function(a,b){return r(a,b)<0?"end":"start"}).attr("y",o.rangeBand()/(2*b.length)).attr("dy",".32em").text(function(a,b){var c=B(r(a,b)),d=s(a,b);return void 0===d?c:d.length?c+"+"+B(Math.abs(d[1]))+"-"+B(Math.abs(d[0])):c+"±"+B(Math.abs(d))}),K.watchTransition(E,"multibarhorizontal: bars").select("text").attr("x",function(a,b){return r(a,b)<0?-4:p(r(a,b))-p(0)+4})):K.selectAll("text").text(""),y&&!w?(L.append("text").classed("nv-bar-label",!0),K.select("text.nv-bar-label").attr("text-anchor",function(a,b){return r(a,b)<0?"start":"end"}).attr("y",o.rangeBand()/(2*b.length)).attr("dy",".32em").text(function(a,b){return q(a,b)}),K.watchTransition(E,"multibarhorizontal: bars").select("text.nv-bar-label").attr("x",function(a,b){return r(a,b)<0?p(0)-p(r(a,b))+4:-4})):K.selectAll("text.nv-bar-label").text(""),K.attr("class",function(a,b){return r(a,b)<0?"nv-bar negative":"nv-bar positive"}),v&&(c||(c=b.map(function(){return!0})),K.style("fill",function(a,b,d){return d3.rgb(v(a,b)).darker(c.map(function(a,b){return b}).filter(function(a,b){return!c[b]})[d]).toString()}).style("stroke",function(a,b,d){return d3.rgb(v(a,b)).darker(c.map(function(a,b){return b}).filter(function(a,b){return!c[b]})[d]).toString()})),w?K.watchTransition(E,"multibarhorizontal: bars").attr("transform",function(a,b){return"translate("+p(a.y1)+","+o(q(a,b))+")"}).select("rect").attr("width",function(a,b){return Math.abs(p(r(a,b)+a.y0)-p(a.y0))}).attr("height",o.rangeBand()):K.watchTransition(E,"multibarhorizontal: bars").attr("transform",function(a,c){return"translate("+p(r(a,c)<0?r(a,c):0)+","+(a.series*o.rangeBand()/b.length+o(q(a,c)))+")"}).select("rect").attr("height",o.rangeBand()/b.length).attr("width",function(a,b){return Math.max(Math.abs(p(r(a,b))-p(0)),1)}),h=o.copy(),i=p.copy()}),E.renderEnd("multibarHorizontal immediate"),b}var c,d,e,f,g,h,i,j={top:0,right:0,bottom:0,left:0},k=960,l=500,m=Math.floor(1e4*Math.random()),n=null,o=d3.scale.ordinal(),p=d3.scale.linear(),q=function(a){return a.x},r=function(a){return a.y},s=function(a){return a.yErr},t=[0],u=a.utils.defaultColor(),v=null,w=!1,x=!1,y=!1,z=60,A=.1,B=d3.format(",.2f"),C=250,D=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),E=a.utils.renderWatch(D,C);return b.dispatch=D,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},x:{get:function(){return q},set:function(a){q=a}},y:{get:function(){return r},set:function(a){r=a}},yErr:{get:function(){return s},set:function(a){s=a}},xScale:{get:function(){return o},set:function(a){o=a}},yScale:{get:function(){return p},set:function(a){p=a}},xDomain:{get:function(){return d},set:function(a){d=a}},yDomain:{get:function(){return e},set:function(a){e=a}},xRange:{get:function(){return f},set:function(a){f=a}},yRange:{get:function(){return g},set:function(a){g=a}},forceY:{get:function(){return t},set:function(a){t=a}},stacked:{get:function(){return w},set:function(a){w=a}},showValues:{get:function(){return x},set:function(a){x=a}},disabled:{get:function(){return c},set:function(a){c=a}},id:{get:function(){return m},set:function(a){m=a}},valueFormat:{get:function(){return B},set:function(a){B=a}},valuePadding:{get:function(){return z},set:function(a){z=a}},groupSpacing:{get:function(){return A},set:function(a){A=a}},margin:{get:function(){return j},set:function(a){j.top=void 0!==a.top?a.top:j.top,j.right=void 0!==a.right?a.right:j.right,j.bottom=void 0!==a.bottom?a.bottom:j.bottom,j.left=void 0!==a.left?a.left:j.left}},duration:{get:function(){return C},set:function(a){C=a,E.reset(C)}},color:{get:function(){return u},set:function(b){u=a.utils.getColor(b)}},barColor:{get:function(){return v},set:function(b){v=b?a.utils.getColor(b):null}}}),a.utils.initOptions(b),b},a.models.multiBarHorizontalChart=function(){"use strict";function b(j){return C.reset(),C.models(e),r&&C.models(f),s&&C.models(g),j.each(function(j){var w=d3.select(this);a.utils.initSVG(w);var C=a.utils.availableWidth(l,w,k),D=a.utils.availableHeight(m,w,k);if(b.update=function(){w.transition().duration(z).call(b)},b.container=this,t=e.stacked(),u.setter(B(j),b.update).getter(A(j)).update(),u.disabled=j.map(function(a){return!!a.disabled}),!v){var E;v={};for(E in u)v[E]=u[E]instanceof Array?u[E].slice(0):u[E]}if(!(j&&j.length&&j.filter(function(a){return a.values.length}).length))return a.utils.noData(b,w),b;w.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale();var F=w.selectAll("g.nv-wrap.nv-multiBarHorizontalChart").data([j]),G=F.enter().append("g").attr("class","nvd3 nv-wrap nv-multiBarHorizontalChart").append("g"),H=F.select("g");if(G.append("g").attr("class","nv-x nv-axis"),G.append("g").attr("class","nv-y nv-axis").append("g").attr("class","nv-zeroLine").append("line"),G.append("g").attr("class","nv-barsWrap"),G.append("g").attr("class","nv-legendWrap"),G.append("g").attr("class","nv-controlsWrap"),q&&(h.width(C-y()),H.select(".nv-legendWrap").datum(j).call(h),k.top!=h.height()&&(k.top=h.height(),D=a.utils.availableHeight(m,w,k)),H.select(".nv-legendWrap").attr("transform","translate("+y()+","+-k.top+")")),o){var I=[{key:p.grouped||"Grouped",disabled:e.stacked()},{key:p.stacked||"Stacked",disabled:!e.stacked()}];i.width(y()).color(["#444","#444","#444"]),H.select(".nv-controlsWrap").datum(I).attr("transform","translate(0,"+-k.top+")").call(i)}F.attr("transform","translate("+k.left+","+k.top+")"),e.disabled(j.map(function(a){return a.disabled})).width(C).height(D).color(j.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!j[b].disabled}));var J=H.select(".nv-barsWrap").datum(j.filter(function(a){return!a.disabled}));if(J.transition().call(e),r){f.scale(c)._ticks(a.utils.calcTicksY(D/24,j)).tickSize(-C,0),H.select(".nv-x.nv-axis").call(f);var K=H.select(".nv-x.nv-axis").selectAll("g");K.selectAll("line, text")}s&&(g.scale(d)._ticks(a.utils.calcTicksX(C/100,j)).tickSize(-D,0),H.select(".nv-y.nv-axis").attr("transform","translate(0,"+D+")"),H.select(".nv-y.nv-axis").call(g)),H.select(".nv-zeroLine line").attr("x1",d(0)).attr("x2",d(0)).attr("y1",0).attr("y2",-D),h.dispatch.on("stateChange",function(a){for(var c in a)u[c]=a[c];x.stateChange(u),b.update()}),i.dispatch.on("legendClick",function(a){if(a.disabled){switch(I=I.map(function(a){return a.disabled=!0,a}),a.disabled=!1,a.key){case"Grouped":e.stacked(!1);break;case"Stacked":e.stacked(!0)}u.stacked=e.stacked(),x.stateChange(u),t=e.stacked(),b.update()}}),x.on("changeState",function(a){"undefined"!=typeof a.disabled&&(j.forEach(function(b,c){b.disabled=a.disabled[c]}),u.disabled=a.disabled),"undefined"!=typeof a.stacked&&(e.stacked(a.stacked),u.stacked=a.stacked,t=a.stacked),b.update()})}),C.renderEnd("multibar horizontal chart immediate"),b}var c,d,e=a.models.multiBarHorizontal(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend().height(30),i=a.models.legend().height(30),j=a.models.tooltip(),k={top:30,right:20,bottom:50,left:60},l=null,m=null,n=a.utils.defaultColor(),o=!0,p={},q=!0,r=!0,s=!0,t=!1,u=a.utils.state(),v=null,w=null,x=d3.dispatch("stateChange","changeState","renderEnd"),y=function(){return o?180:0},z=250;u.stacked=!1,e.stacked(t),f.orient("left").tickPadding(5).showMaxMin(!1).tickFormat(function(a){return a}),g.orient("bottom").tickFormat(d3.format(",.1f")),j.duration(0).valueFormatter(function(a,b){return g.tickFormat()(a,b)}).headerFormatter(function(a,b){return f.tickFormat()(a,b)}),i.updateState(!1);var A=function(a){return function(){return{active:a.map(function(a){return!a.disabled}),stacked:t}}},B=function(a){return function(b){void 0!==b.stacked&&(t=b.stacked),void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}},C=a.utils.renderWatch(x,z);return e.dispatch.on("elementMouseover.tooltip",function(a){a.value=b.x()(a.data),a.series={key:a.data.key,value:b.y()(a.data),color:a.color},j.data(a).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){j.hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(){j.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=x,b.multibar=e,b.legend=h,b.controls=i,b.xAxis=f,b.yAxis=g,b.state=u,b.tooltip=j,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return l},set:function(a){l=a}},height:{get:function(){return m},set:function(a){m=a}},showLegend:{get:function(){return q},set:function(a){q=a}},showControls:{get:function(){return o},set:function(a){o=a}},controlLabels:{get:function(){return p},set:function(a){p=a}},showXAxis:{get:function(){return r},set:function(a){r=a}},showYAxis:{get:function(){return s},set:function(a){s=a}},defaultState:{get:function(){return v},set:function(a){v=a}},noData:{get:function(){return w},set:function(a){w=a}},tooltips:{get:function(){return j.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),j.enabled(!!b)}},tooltipContent:{get:function(){return j.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),j.contentGenerator(b)}},margin:{get:function(){return k},set:function(a){k.top=void 0!==a.top?a.top:k.top,k.right=void 0!==a.right?a.right:k.right,k.bottom=void 0!==a.bottom?a.bottom:k.bottom,k.left=void 0!==a.left?a.left:k.left}},duration:{get:function(){return z},set:function(a){z=a,C.reset(z),e.duration(z),f.duration(z),g.duration(z)}},color:{get:function(){return n},set:function(b){n=a.utils.getColor(b),h.color(n)}},barColor:{get:function(){return e.barColor},set:function(a){e.barColor(a),h.color(function(a,b){return d3.rgb("#ccc").darker(1.5*b).toString()})}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.multiChart=function(){"use strict";function b(j){return j.each(function(j){function k(a){var b=2===j[a.seriesIndex].yAxis?z:y;a.value=a.point.x,a.series={value:a.point.y,color:a.point.color},B.duration(100).valueFormatter(function(a,c){return b.tickFormat()(a,c)}).data(a).position(a.pos).hidden(!1)}function l(a){var b=2===j[a.seriesIndex].yAxis?z:y;a.point.x=v.x()(a.point),a.point.y=v.y()(a.point),B.duration(100).valueFormatter(function(a,c){return b.tickFormat()(a,c)}).data(a).position(a.pos).hidden(!1)}function n(a){var b=2===j[a.data.series].yAxis?z:y;a.value=t.x()(a.data),a.series={value:t.y()(a.data),color:a.color},B.duration(0).valueFormatter(function(a,c){return b.tickFormat()(a,c)}).data(a).hidden(!1)}var C=d3.select(this);a.utils.initSVG(C),b.update=function(){C.transition().call(b)},b.container=this;var D=a.utils.availableWidth(g,C,e),E=a.utils.availableHeight(h,C,e),F=j.filter(function(a){return"line"==a.type&&1==a.yAxis}),G=j.filter(function(a){return"line"==a.type&&2==a.yAxis}),H=j.filter(function(a){return"bar"==a.type&&1==a.yAxis}),I=j.filter(function(a){return"bar"==a.type&&2==a.yAxis}),J=j.filter(function(a){return"area"==a.type&&1==a.yAxis}),K=j.filter(function(a){return"area"==a.type&&2==a.yAxis});if(!(j&&j.length&&j.filter(function(a){return a.values.length}).length))return a.utils.noData(b,C),b;C.selectAll(".nv-noData").remove();var L=j.filter(function(a){return!a.disabled&&1==a.yAxis}).map(function(a){return a.values.map(function(a){return{x:a.x,y:a.y}})}),M=j.filter(function(a){return!a.disabled&&2==a.yAxis}).map(function(a){return a.values.map(function(a){return{x:a.x,y:a.y}})});o.domain(d3.extent(d3.merge(L.concat(M)),function(a){return a.x})).range([0,D]);var N=C.selectAll("g.wrap.multiChart").data([j]),O=N.enter().append("g").attr("class","wrap nvd3 multiChart").append("g");O.append("g").attr("class","nv-x nv-axis"),O.append("g").attr("class","nv-y1 nv-axis"),O.append("g").attr("class","nv-y2 nv-axis"),O.append("g").attr("class","lines1Wrap"),O.append("g").attr("class","lines2Wrap"),O.append("g").attr("class","bars1Wrap"),O.append("g").attr("class","bars2Wrap"),O.append("g").attr("class","stack1Wrap"),O.append("g").attr("class","stack2Wrap"),O.append("g").attr("class","legendWrap");var P=N.select("g"),Q=j.map(function(a,b){return j[b].color||f(a,b)});if(i){var R=A.align()?D/2:D,S=A.align()?R:0;A.width(R),A.color(Q),P.select(".legendWrap").datum(j.map(function(a){return a.originalKey=void 0===a.originalKey?a.key:a.originalKey,a.key=a.originalKey+(1==a.yAxis?"":" (right axis)"),a})).call(A),e.top!=A.height()&&(e.top=A.height(),E=a.utils.availableHeight(h,C,e)),P.select(".legendWrap").attr("transform","translate("+S+","+-e.top+")")}r.width(D).height(E).interpolate(m).color(Q.filter(function(a,b){return!j[b].disabled&&1==j[b].yAxis&&"line"==j[b].type})),s.width(D).height(E).interpolate(m).color(Q.filter(function(a,b){return!j[b].disabled&&2==j[b].yAxis&&"line"==j[b].type})),t.width(D).height(E).color(Q.filter(function(a,b){return!j[b].disabled&&1==j[b].yAxis&&"bar"==j[b].type})),u.width(D).height(E).color(Q.filter(function(a,b){return!j[b].disabled&&2==j[b].yAxis&&"bar"==j[b].type})),v.width(D).height(E).color(Q.filter(function(a,b){return!j[b].disabled&&1==j[b].yAxis&&"area"==j[b].type})),w.width(D).height(E).color(Q.filter(function(a,b){return!j[b].disabled&&2==j[b].yAxis&&"area"==j[b].type})),P.attr("transform","translate("+e.left+","+e.top+")");var T=P.select(".lines1Wrap").datum(F.filter(function(a){return!a.disabled})),U=P.select(".bars1Wrap").datum(H.filter(function(a){return!a.disabled})),V=P.select(".stack1Wrap").datum(J.filter(function(a){return!a.disabled})),W=P.select(".lines2Wrap").datum(G.filter(function(a){return!a.disabled})),X=P.select(".bars2Wrap").datum(I.filter(function(a){return!a.disabled})),Y=P.select(".stack2Wrap").datum(K.filter(function(a){return!a.disabled})),Z=J.length?J.map(function(a){return a.values}).reduce(function(a,b){return a.map(function(a,c){return{x:a.x,y:a.y+b[c].y}})}).concat([{x:0,y:0}]):[],$=K.length?K.map(function(a){return a.values}).reduce(function(a,b){return a.map(function(a,c){return{x:a.x,y:a.y+b[c].y}})}).concat([{x:0,y:0}]):[];p.domain(c||d3.extent(d3.merge(L).concat(Z),function(a){return a.y})).range([0,E]),q.domain(d||d3.extent(d3.merge(M).concat($),function(a){return a.y})).range([0,E]),r.yDomain(p.domain()),t.yDomain(p.domain()),v.yDomain(p.domain()),s.yDomain(q.domain()),u.yDomain(q.domain()),w.yDomain(q.domain()),J.length&&d3.transition(V).call(v),K.length&&d3.transition(Y).call(w),H.length&&d3.transition(U).call(t),I.length&&d3.transition(X).call(u),F.length&&d3.transition(T).call(r),G.length&&d3.transition(W).call(s),x._ticks(a.utils.calcTicksX(D/100,j)).tickSize(-E,0),P.select(".nv-x.nv-axis").attr("transform","translate(0,"+E+")"),d3.transition(P.select(".nv-x.nv-axis")).call(x),y._ticks(a.utils.calcTicksY(E/36,j)).tickSize(-D,0),d3.transition(P.select(".nv-y1.nv-axis")).call(y),z._ticks(a.utils.calcTicksY(E/36,j)).tickSize(-D,0),d3.transition(P.select(".nv-y2.nv-axis")).call(z),P.select(".nv-y1.nv-axis").classed("nv-disabled",L.length?!1:!0).attr("transform","translate("+o.range()[0]+",0)"),P.select(".nv-y2.nv-axis").classed("nv-disabled",M.length?!1:!0).attr("transform","translate("+o.range()[1]+",0)"),A.dispatch.on("stateChange",function(){b.update()}),r.dispatch.on("elementMouseover.tooltip",k),s.dispatch.on("elementMouseover.tooltip",k),r.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),s.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),v.dispatch.on("elementMouseover.tooltip",l),w.dispatch.on("elementMouseover.tooltip",l),v.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),w.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),t.dispatch.on("elementMouseover.tooltip",n),u.dispatch.on("elementMouseover.tooltip",n),t.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),u.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),t.dispatch.on("elementMousemove.tooltip",function(){B.position({top:d3.event.pageY,left:d3.event.pageX})()}),u.dispatch.on("elementMousemove.tooltip",function(){B.position({top:d3.event.pageY,left:d3.event.pageX})()})}),b}var c,d,e={top:30,right:20,bottom:50,left:60},f=a.utils.defaultColor(),g=null,h=null,i=!0,j=null,k=function(a){return a.x},l=function(a){return a.y},m="monotone",n=!0,o=d3.scale.linear(),p=d3.scale.linear(),q=d3.scale.linear(),r=a.models.line().yScale(p),s=a.models.line().yScale(q),t=a.models.multiBar().stacked(!1).yScale(p),u=a.models.multiBar().stacked(!1).yScale(q),v=a.models.stackedArea().yScale(p),w=a.models.stackedArea().yScale(q),x=a.models.axis().scale(o).orient("bottom").tickPadding(5),y=a.models.axis().scale(p).orient("left"),z=a.models.axis().scale(q).orient("right"),A=a.models.legend().height(30),B=a.models.tooltip(),C=d3.dispatch();return b.dispatch=C,b.lines1=r,b.lines2=s,b.bars1=t,b.bars2=u,b.stack1=v,b.stack2=w,b.xAxis=x,b.yAxis1=y,b.yAxis2=z,b.tooltip=B,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return g},set:function(a){g=a}},height:{get:function(){return h},set:function(a){h=a}},showLegend:{get:function(){return i},set:function(a){i=a}},yDomain1:{get:function(){return c},set:function(a){c=a}},yDomain2:{get:function(){return d},set:function(a){d=a}},noData:{get:function(){return j},set:function(a){j=a}},interpolate:{get:function(){return m},set:function(a){m=a}},tooltips:{get:function(){return B.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),B.enabled(!!b)}},tooltipContent:{get:function(){return B.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),B.contentGenerator(b)}},margin:{get:function(){return e},set:function(a){e.top=void 0!==a.top?a.top:e.top,e.right=void 0!==a.right?a.right:e.right,e.bottom=void 0!==a.bottom?a.bottom:e.bottom,e.left=void 0!==a.left?a.left:e.left}},color:{get:function(){return f},set:function(b){f=a.utils.getColor(b)}},x:{get:function(){return k},set:function(a){k=a,r.x(a),s.x(a),t.x(a),u.x(a),v.x(a),w.x(a)}},y:{get:function(){return l},set:function(a){l=a,r.y(a),s.y(a),v.y(a),w.y(a),t.y(a),u.y(a)}},useVoronoi:{get:function(){return n},set:function(a){n=a,r.useVoronoi(a),s.useVoronoi(a),v.useVoronoi(a),w.useVoronoi(a)}}}),a.utils.initOptions(b),b},a.models.ohlcBar=function(){"use strict";function b(y){return y.each(function(b){k=d3.select(this);var y=a.utils.availableWidth(h,k,g),A=a.utils.availableHeight(i,k,g);a.utils.initSVG(k);var B=y/b[0].values.length*.9;l.domain(c||d3.extent(b[0].values.map(n).concat(t))),l.range(v?e||[.5*y/b[0].values.length,y*(b[0].values.length-.5)/b[0].values.length]:e||[5+B/2,y-B/2-5]),m.domain(d||[d3.min(b[0].values.map(s).concat(u)),d3.max(b[0].values.map(r).concat(u))]).range(f||[A,0]),l.domain()[0]===l.domain()[1]&&l.domain(l.domain()[0]?[l.domain()[0]-.01*l.domain()[0],l.domain()[1]+.01*l.domain()[1]]:[-1,1]),m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]+.01*m.domain()[0],m.domain()[1]-.01*m.domain()[1]]:[-1,1]);var C=d3.select(this).selectAll("g.nv-wrap.nv-ohlcBar").data([b[0].values]),D=C.enter().append("g").attr("class","nvd3 nv-wrap nv-ohlcBar"),E=D.append("defs"),F=D.append("g"),G=C.select("g");F.append("g").attr("class","nv-ticks"),C.attr("transform","translate("+g.left+","+g.top+")"),k.on("click",function(a,b){z.chartClick({data:a,index:b,pos:d3.event,id:j})}),E.append("clipPath").attr("id","nv-chart-clip-path-"+j).append("rect"),C.select("#nv-chart-clip-path-"+j+" rect").attr("width",y).attr("height",A),G.attr("clip-path",w?"url(#nv-chart-clip-path-"+j+")":"");var H=C.select(".nv-ticks").selectAll(".nv-tick").data(function(a){return a});H.exit().remove(),H.enter().append("path").attr("class",function(a,b,c){return(p(a,b)>q(a,b)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+c+"-"+b}).attr("d",function(a,b){return"m0,0l0,"+(m(p(a,b))-m(r(a,b)))+"l"+-B/2+",0l"+B/2+",0l0,"+(m(s(a,b))-m(p(a,b)))+"l0,"+(m(q(a,b))-m(s(a,b)))+"l"+B/2+",0l"+-B/2+",0z"}).attr("transform",function(a,b){return"translate("+l(n(a,b))+","+m(r(a,b))+")"}).attr("fill",function(){return x[0]}).attr("stroke",function(){return x[0]}).attr("x",0).attr("y",function(a,b){return m(Math.max(0,o(a,b)))}).attr("height",function(a,b){return Math.abs(m(o(a,b))-m(0))}),H.attr("class",function(a,b,c){return(p(a,b)>q(a,b)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+c+"-"+b}),d3.transition(H).attr("transform",function(a,b){return"translate("+l(n(a,b))+","+m(r(a,b))+")"}).attr("d",function(a,c){var d=y/b[0].values.length*.9;return"m0,0l0,"+(m(p(a,c))-m(r(a,c)))+"l"+-d/2+",0l"+d/2+",0l0,"+(m(s(a,c))-m(p(a,c)))+"l0,"+(m(q(a,c))-m(s(a,c)))+"l"+d/2+",0l"+-d/2+",0z"})}),b}var c,d,e,f,g={top:0,right:0,bottom:0,left:0},h=null,i=null,j=Math.floor(1e4*Math.random()),k=null,l=d3.scale.linear(),m=d3.scale.linear(),n=function(a){return a.x},o=function(a){return a.y},p=function(a){return a.open},q=function(a){return a.close},r=function(a){return a.high},s=function(a){return a.low},t=[],u=[],v=!1,w=!0,x=a.utils.defaultColor(),y=!1,z=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd","chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove");return b.highlightPoint=function(a,c){b.clearHighlights(),k.select(".nv-ohlcBar .nv-tick-0-"+a).classed("hover",c)},b.clearHighlights=function(){k.select(".nv-ohlcBar .nv-tick.hover").classed("hover",!1)},b.dispatch=z,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return i},set:function(a){i=a}},xScale:{get:function(){return l},set:function(a){l=a}},yScale:{get:function(){return m},set:function(a){m=a}},xDomain:{get:function(){return c},set:function(a){c=a}},yDomain:{get:function(){return d},set:function(a){d=a}},xRange:{get:function(){return e},set:function(a){e=a}},yRange:{get:function(){return f},set:function(a){f=a}},forceX:{get:function(){return t},set:function(a){t=a}},forceY:{get:function(){return u},set:function(a){u=a}},padData:{get:function(){return v},set:function(a){v=a}},clipEdge:{get:function(){return w},set:function(a){w=a}},id:{get:function(){return j},set:function(a){j=a}},interactive:{get:function(){return y},set:function(a){y=a}},x:{get:function(){return n},set:function(a){n=a}},y:{get:function(){return o},set:function(a){o=a}},open:{get:function(){return p()},set:function(a){p=a}},close:{get:function(){return q()},set:function(a){q=a}},high:{get:function(){return r},set:function(a){r=a}},low:{get:function(){return s},set:function(a){s=a}},margin:{get:function(){return g},set:function(a){g.top=void 0!=a.top?a.top:g.top,g.right=void 0!=a.right?a.right:g.right,g.bottom=void 0!=a.bottom?a.bottom:g.bottom,g.left=void 0!=a.left?a.left:g.left +}},color:{get:function(){return x},set:function(b){x=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.parallelCoordinates=function(){"use strict";function b(p){return p.each(function(b){function p(a){return F(h.map(function(b){if(isNaN(a[b])||isNaN(parseFloat(a[b]))){var c=g[b].domain(),d=g[b].range(),e=c[0]-(c[1]-c[0])/9;if(J.indexOf(b)<0){var h=d3.scale.linear().domain([e,c[1]]).range([x-12,d[1]]);g[b].brush.y(h),J.push(b)}return[f(b),g[b](e)]}return J.length>0?(D.style("display","inline"),E.style("display","inline")):(D.style("display","none"),E.style("display","none")),[f(b),g[b](a[b])]}))}function q(){var a=h.filter(function(a){return!g[a].brush.empty()}),b=a.map(function(a){return g[a].brush.extent()});k=[],a.forEach(function(a,c){k[c]={dimension:a,extent:b[c]}}),l=[],M.style("display",function(c){var d=a.every(function(a,d){return isNaN(c[a])&&b[d][0]==g[a].brush.y().domain()[0]?!0:b[d][0]<=c[a]&&c[a]<=b[d][1]});return d&&l.push(c),d?null:"none"}),o.brush({filters:k,active:l})}function r(a){m[a]=this.parentNode.__origin__=f(a),L.attr("visibility","hidden")}function s(a){m[a]=Math.min(w,Math.max(0,this.parentNode.__origin__+=d3.event.x)),M.attr("d",p),h.sort(function(a,b){return u(a)-u(b)}),f.domain(h),N.attr("transform",function(a){return"translate("+u(a)+")"})}function t(a){delete this.parentNode.__origin__,delete m[a],d3.select(this.parentNode).attr("transform","translate("+f(a)+")"),M.attr("d",p),L.attr("d",p).attr("visibility",null)}function u(a){var b=m[a];return null==b?f(a):b}var v=d3.select(this),w=a.utils.availableWidth(d,v,c),x=a.utils.availableHeight(e,v,c);a.utils.initSVG(v),l=b,f.rangePoints([0,w],1).domain(h);var y={};h.forEach(function(a){var c=d3.extent(b,function(b){return+b[a]});return y[a]=!1,void 0===c[0]&&(y[a]=!0,c[0]=0,c[1]=0),c[0]===c[1]&&(c[0]=c[0]-1,c[1]=c[1]+1),g[a]=d3.scale.linear().domain(c).range([.9*(x-12),0]),g[a].brush=d3.svg.brush().y(g[a]).on("brush",q),"name"!=a});var z=v.selectAll("g.nv-wrap.nv-parallelCoordinates").data([b]),A=z.enter().append("g").attr("class","nvd3 nv-wrap nv-parallelCoordinates"),B=A.append("g"),C=z.select("g");B.append("g").attr("class","nv-parallelCoordinates background"),B.append("g").attr("class","nv-parallelCoordinates foreground"),B.append("g").attr("class","nv-parallelCoordinates missingValuesline"),z.attr("transform","translate("+c.left+","+c.top+")");var D,E,F=d3.svg.line().interpolate("cardinal").tension(n),G=d3.svg.axis().orient("left"),H=d3.behavior.drag().on("dragstart",r).on("drag",s).on("dragend",t),I=f.range()[1]-f.range()[0],J=[],K=[0+I/2,x-12,w-I/2,x-12];D=z.select(".missingValuesline").selectAll("line").data([K]),D.enter().append("line"),D.exit().remove(),D.attr("x1",function(a){return a[0]}).attr("y1",function(a){return a[1]}).attr("x2",function(a){return a[2]}).attr("y2",function(a){return a[3]}),E=z.select(".missingValuesline").selectAll("text").data(["undefined values"]),E.append("text").data(["undefined values"]),E.enter().append("text"),E.exit().remove(),E.attr("y",x).attr("x",w-92-I/2).text(function(a){return a});var L=z.select(".background").selectAll("path").data(b);L.enter().append("path"),L.exit().remove(),L.attr("d",p);var M=z.select(".foreground").selectAll("path").data(b);M.enter().append("path"),M.exit().remove(),M.attr("d",p).attr("stroke",j),M.on("mouseover",function(a,b){d3.select(this).classed("hover",!0),o.elementMouseover({label:a.name,data:a.data,index:b,pos:[d3.mouse(this.parentNode)[0],d3.mouse(this.parentNode)[1]]})}),M.on("mouseout",function(a,b){d3.select(this).classed("hover",!1),o.elementMouseout({label:a.name,data:a.data,index:b})});var N=C.selectAll(".dimension").data(h),O=N.enter().append("g").attr("class","nv-parallelCoordinates dimension");O.append("g").attr("class","nv-parallelCoordinates nv-axis"),O.append("g").attr("class","nv-parallelCoordinates-brush"),O.append("text").attr("class","nv-parallelCoordinates nv-label"),N.attr("transform",function(a){return"translate("+f(a)+",0)"}),N.exit().remove(),N.select(".nv-label").style("cursor","move").attr("dy","-1em").attr("text-anchor","middle").text(String).on("mouseover",function(a){o.elementMouseover({dim:a,pos:[d3.mouse(this.parentNode.parentNode)[0],d3.mouse(this.parentNode.parentNode)[1]]})}).on("mouseout",function(a){o.elementMouseout({dim:a})}).call(H),N.select(".nv-axis").each(function(a,b){d3.select(this).call(G.scale(g[a]).tickFormat(d3.format(i[b])))}),N.select(".nv-parallelCoordinates-brush").each(function(a){d3.select(this).call(g[a].brush)}).selectAll("rect").attr("x",-8).attr("width",16)}),b}var c={top:30,right:0,bottom:10,left:0},d=null,e=null,f=d3.scale.ordinal(),g={},h=[],i=[],j=a.utils.defaultColor(),k=[],l=[],m=[],n=1,o=d3.dispatch("brush","elementMouseover","elementMouseout");return b.dispatch=o,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return d},set:function(a){d=a}},height:{get:function(){return e},set:function(a){e=a}},dimensionNames:{get:function(){return h},set:function(a){h=a}},dimensionFormats:{get:function(){return i},set:function(a){i=a}},lineTension:{get:function(){return n},set:function(a){n=a}},dimensions:{get:function(){return h},set:function(b){a.deprecated("dimensions","use dimensionNames instead"),h=b}},margin:{get:function(){return c},set:function(a){c.top=void 0!==a.top?a.top:c.top,c.right=void 0!==a.right?a.right:c.right,c.bottom=void 0!==a.bottom?a.bottom:c.bottom,c.left=void 0!==a.left?a.left:c.left}},color:{get:function(){return j},set:function(b){j=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.pie=function(){"use strict";function b(E){return D.reset(),E.each(function(b){function E(a,b){a.endAngle=isNaN(a.endAngle)?0:a.endAngle,a.startAngle=isNaN(a.startAngle)?0:a.startAngle,p||(a.innerRadius=0);var c=d3.interpolate(this._current,a);return this._current=c(0),function(a){return B[b](c(a))}}var F=d-c.left-c.right,G=e-c.top-c.bottom,H=Math.min(F,G)/2,I=[],J=[];if(i=d3.select(this),0===z.length)for(var K=H-H/5,L=y*H,M=0;Mc)return"";if("function"==typeof n)d=n(a,b,{key:f(a.data),value:g(a.data),percent:k(c)});else switch(n){case"key":d=f(a.data);break;case"value":d=k(g(a.data));break;case"percent":d=d3.format("%")(c)}return d})}}),D.renderEnd("pie immediate"),b}var c={top:0,right:0,bottom:0,left:0},d=500,e=500,f=function(a){return a.x},g=function(a){return a.y},h=Math.floor(1e4*Math.random()),i=null,j=a.utils.defaultColor(),k=d3.format(",.2f"),l=!0,m=!1,n="key",o=.02,p=!1,q=!1,r=!0,s=0,t=!1,u=!1,v=!1,w=!1,x=0,y=.5,z=[],A=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),B=[],C=[],D=a.utils.renderWatch(A);return b.dispatch=A,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{arcsRadius:{get:function(){return z},set:function(a){z=a}},width:{get:function(){return d},set:function(a){d=a}},height:{get:function(){return e},set:function(a){e=a}},showLabels:{get:function(){return l},set:function(a){l=a}},title:{get:function(){return q},set:function(a){q=a}},titleOffset:{get:function(){return s},set:function(a){s=a}},labelThreshold:{get:function(){return o},set:function(a){o=a}},valueFormat:{get:function(){return k},set:function(a){k=a}},x:{get:function(){return f},set:function(a){f=a}},id:{get:function(){return h},set:function(a){h=a}},endAngle:{get:function(){return w},set:function(a){w=a}},startAngle:{get:function(){return u},set:function(a){u=a}},padAngle:{get:function(){return v},set:function(a){v=a}},cornerRadius:{get:function(){return x},set:function(a){x=a}},donutRatio:{get:function(){return y},set:function(a){y=a}},labelsOutside:{get:function(){return m},set:function(a){m=a}},labelSunbeamLayout:{get:function(){return t},set:function(a){t=a}},donut:{get:function(){return p},set:function(a){p=a}},growOnHover:{get:function(){return r},set:function(a){r=a}},pieLabelsOutside:{get:function(){return m},set:function(b){m=b,a.deprecated("pieLabelsOutside","use labelsOutside instead")}},donutLabelsOutside:{get:function(){return m},set:function(b){m=b,a.deprecated("donutLabelsOutside","use labelsOutside instead")}},labelFormat:{get:function(){return k},set:function(b){k=b,a.deprecated("labelFormat","use valueFormat instead")}},margin:{get:function(){return c},set:function(a){c.top="undefined"!=typeof a.top?a.top:c.top,c.right="undefined"!=typeof a.right?a.right:c.right,c.bottom="undefined"!=typeof a.bottom?a.bottom:c.bottom,c.left="undefined"!=typeof a.left?a.left:c.left}},y:{get:function(){return g},set:function(a){g=d3.functor(a)}},color:{get:function(){return j},set:function(b){j=a.utils.getColor(b)}},labelType:{get:function(){return n},set:function(a){n=a||"key"}}}),a.utils.initOptions(b),b},a.models.pieChart=function(){"use strict";function b(e){return q.reset(),q.models(c),e.each(function(e){var k=d3.select(this);a.utils.initSVG(k);var n=a.utils.availableWidth(g,k,f),o=a.utils.availableHeight(h,k,f);if(b.update=function(){k.transition().call(b)},b.container=this,l.setter(s(e),b.update).getter(r(e)).update(),l.disabled=e.map(function(a){return!!a.disabled}),!m){var q;m={};for(q in l)m[q]=l[q]instanceof Array?l[q].slice(0):l[q]}if(!e||!e.length)return a.utils.noData(b,k),b;k.selectAll(".nv-noData").remove();var t=k.selectAll("g.nv-wrap.nv-pieChart").data([e]),u=t.enter().append("g").attr("class","nvd3 nv-wrap nv-pieChart").append("g"),v=t.select("g");if(u.append("g").attr("class","nv-pieWrap"),u.append("g").attr("class","nv-legendWrap"),i)if("top"===j)d.width(n).key(c.x()),t.select(".nv-legendWrap").datum(e).call(d),f.top!=d.height()&&(f.top=d.height(),o=a.utils.availableHeight(h,k,f)),t.select(".nv-legendWrap").attr("transform","translate(0,"+-f.top+")");else if("right"===j){var w=a.models.legend().width();w>n/2&&(w=n/2),d.height(o).key(c.x()),d.width(w),n-=d.width(),t.select(".nv-legendWrap").datum(e).call(d).attr("transform","translate("+n+",0)")}t.attr("transform","translate("+f.left+","+f.top+")"),c.width(n).height(o);var x=v.select(".nv-pieWrap").datum([e]);d3.transition(x).call(c),d.dispatch.on("stateChange",function(a){for(var c in a)l[c]=a[c];p.stateChange(l),b.update()}),p.on("changeState",function(a){"undefined"!=typeof a.disabled&&(e.forEach(function(b,c){b.disabled=a.disabled[c]}),l.disabled=a.disabled),b.update()})}),q.renderEnd("pieChart immediate"),b}var c=a.models.pie(),d=a.models.legend(),e=a.models.tooltip(),f={top:30,right:20,bottom:20,left:20},g=null,h=null,i=!0,j="top",k=a.utils.defaultColor(),l=a.utils.state(),m=null,n=null,o=250,p=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd");e.headerEnabled(!1).duration(0).valueFormatter(function(a,b){return c.valueFormat()(a,b)});var q=a.utils.renderWatch(p),r=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},s=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return c.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:b.x()(a.data),value:b.y()(a.data),color:a.color},e.data(a).hidden(!1)}),c.dispatch.on("elementMouseout.tooltip",function(){e.hidden(!0)}),c.dispatch.on("elementMousemove.tooltip",function(){e.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.legend=d,b.dispatch=p,b.pie=c,b.tooltip=e,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{noData:{get:function(){return n},set:function(a){n=a}},showLegend:{get:function(){return i},set:function(a){i=a}},legendPosition:{get:function(){return j},set:function(a){j=a}},defaultState:{get:function(){return m},set:function(a){m=a}},tooltips:{get:function(){return e.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),e.enabled(!!b)}},tooltipContent:{get:function(){return e.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),e.contentGenerator(b)}},color:{get:function(){return k},set:function(a){k=a,d.color(k),c.color(k)}},duration:{get:function(){return o},set:function(a){o=a,q.reset(o)}},margin:{get:function(){return f},set:function(a){f.top=void 0!==a.top?a.top:f.top,f.right=void 0!==a.right?a.right:f.right,f.bottom=void 0!==a.bottom?a.bottom:f.bottom,f.left=void 0!==a.left?a.left:f.left}}}),a.utils.inheritOptions(b,c),a.utils.initOptions(b),b},a.models.scatter=function(){"use strict";function b(N){return P.reset(),N.each(function(b){function N(){if(O=!1,!w)return!1;if(M===!0){var a=d3.merge(b.map(function(a,b){return a.values.map(function(a,c){var d=p(a,c),e=q(a,c);return[m(d)+1e-4*Math.random(),n(e)+1e-4*Math.random(),b,c,a]}).filter(function(a,b){return x(a[4],b)})}));if(0==a.length)return!1;a.length<3&&(a.push([m.range()[0]-20,n.range()[0]-20,null,null]),a.push([m.range()[1]+20,n.range()[1]+20,null,null]),a.push([m.range()[0]-20,n.range()[0]+20,null,null]),a.push([m.range()[1]+20,n.range()[1]-20,null,null]));var c=d3.geom.polygon([[-10,-10],[-10,i+10],[h+10,i+10],[h+10,-10]]),d=d3.geom.voronoi(a).map(function(b,d){return{data:c.clip(b),series:a[d][2],point:a[d][3]}});U.select(".nv-point-paths").selectAll("path").remove();var e=U.select(".nv-point-paths").selectAll("path").data(d),f=e.enter().append("svg:path").attr("d",function(a){return a&&a.data&&0!==a.data.length?"M"+a.data.join(",")+"Z":"M 0 0"}).attr("id",function(a,b){return"nv-path-"+b}).attr("clip-path",function(a,b){return"url(#nv-clip-"+b+")"});C&&f.style("fill",d3.rgb(230,230,230)).style("fill-opacity",.4).style("stroke-opacity",1).style("stroke",d3.rgb(200,200,200)),B&&(U.select(".nv-point-clips").selectAll("clipPath").remove(),U.select(".nv-point-clips").selectAll("clipPath").data(a).enter().append("svg:clipPath").attr("id",function(a,b){return"nv-clip-"+b}).append("svg:circle").attr("cx",function(a){return a[0]}).attr("cy",function(a){return a[1]}).attr("r",D));var k=function(a,c){if(O)return 0;var d=b[a.series];if(void 0!==d){var e=d.values[a.point];e.color=j(d,a.series),e.x=p(e),e.y=q(e);var f=l.node().getBoundingClientRect(),h=window.pageYOffset||document.documentElement.scrollTop,i=window.pageXOffset||document.documentElement.scrollLeft,k={left:m(p(e,a.point))+f.left+i+g.left+10,top:n(q(e,a.point))+f.top+h+g.top+10};c({point:e,series:d,pos:k,seriesIndex:a.series,pointIndex:a.point})}};e.on("click",function(a){k(a,L.elementClick)}).on("dblclick",function(a){k(a,L.elementDblClick)}).on("mouseover",function(a){k(a,L.elementMouseover)}).on("mouseout",function(a){k(a,L.elementMouseout)})}else U.select(".nv-groups").selectAll(".nv-group").selectAll(".nv-point").on("click",function(a,c){if(O||!b[a.series])return 0;var d=b[a.series],e=d.values[c];L.elementClick({point:e,series:d,pos:[m(p(e,c))+g.left,n(q(e,c))+g.top],seriesIndex:a.series,pointIndex:c})}).on("dblclick",function(a,c){if(O||!b[a.series])return 0;var d=b[a.series],e=d.values[c];L.elementDblClick({point:e,series:d,pos:[m(p(e,c))+g.left,n(q(e,c))+g.top],seriesIndex:a.series,pointIndex:c})}).on("mouseover",function(a,c){if(O||!b[a.series])return 0;var d=b[a.series],e=d.values[c];L.elementMouseover({point:e,series:d,pos:[m(p(e,c))+g.left,n(q(e,c))+g.top],seriesIndex:a.series,pointIndex:c,color:j(a,c)})}).on("mouseout",function(a,c){if(O||!b[a.series])return 0;var d=b[a.series],e=d.values[c];L.elementMouseout({point:e,series:d,seriesIndex:a.series,pointIndex:c,color:j(a,c)})})}l=d3.select(this);var R=a.utils.availableWidth(h,l,g),S=a.utils.availableHeight(i,l,g);a.utils.initSVG(l),b.forEach(function(a,b){a.values.forEach(function(a){a.series=b})});var T=E&&F&&I?[]:d3.merge(b.map(function(a){return a.values.map(function(a,b){return{x:p(a,b),y:q(a,b),size:r(a,b)}})}));m.domain(E||d3.extent(T.map(function(a){return a.x}).concat(t))),m.range(y&&b[0]?G||[(R*z+R)/(2*b[0].values.length),R-R*(1+z)/(2*b[0].values.length)]:G||[0,R]),n.domain(F||d3.extent(T.map(function(a){return a.y}).concat(u))).range(H||[S,0]),o.domain(I||d3.extent(T.map(function(a){return a.size}).concat(v))).range(J||Q),K=m.domain()[0]===m.domain()[1]||n.domain()[0]===n.domain()[1],m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]-.01*m.domain()[0],m.domain()[1]+.01*m.domain()[1]]:[-1,1]),n.domain()[0]===n.domain()[1]&&n.domain(n.domain()[0]?[n.domain()[0]-.01*n.domain()[0],n.domain()[1]+.01*n.domain()[1]]:[-1,1]),isNaN(m.domain()[0])&&m.domain([-1,1]),isNaN(n.domain()[0])&&n.domain([-1,1]),c=c||m,d=d||n,e=e||o;var U=l.selectAll("g.nv-wrap.nv-scatter").data([b]),V=U.enter().append("g").attr("class","nvd3 nv-wrap nv-scatter nv-chart-"+k),W=V.append("defs"),X=V.append("g"),Y=U.select("g");U.classed("nv-single-point",K),X.append("g").attr("class","nv-groups"),X.append("g").attr("class","nv-point-paths"),V.append("g").attr("class","nv-point-clips"),U.attr("transform","translate("+g.left+","+g.top+")"),W.append("clipPath").attr("id","nv-edge-clip-"+k).append("rect"),U.select("#nv-edge-clip-"+k+" rect").attr("width",R).attr("height",S>0?S:0),Y.attr("clip-path",A?"url(#nv-edge-clip-"+k+")":""),O=!0;var Z=U.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a){return a.key});Z.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),Z.exit().remove(),Z.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}),Z.watchTransition(P,"scatter: groups").style("fill",function(a,b){return j(a,b)}).style("stroke",function(a,b){return j(a,b)}).style("stroke-opacity",1).style("fill-opacity",.5);var $=Z.selectAll("path.nv-point").data(function(a){return a.values.map(function(a,b){return[a,b]}).filter(function(a,b){return x(a[0],b)})});$.enter().append("path").style("fill",function(a){return a.color}).style("stroke",function(a){return a.color}).attr("transform",function(a){return"translate("+c(p(a[0],a[1]))+","+d(q(a[0],a[1]))+")"}).attr("d",a.utils.symbol().type(function(a){return s(a[0])}).size(function(a){return o(r(a[0],a[1]))})),$.exit().remove(),Z.exit().selectAll("path.nv-point").watchTransition(P,"scatter exit").attr("transform",function(a){return"translate("+m(p(a[0],a[1]))+","+n(q(a[0],a[1]))+")"}).remove(),$.each(function(a){d3.select(this).classed("nv-point",!0).classed("nv-point-"+a[1],!0).classed("nv-noninteractive",!w).classed("hover",!1)}),$.watchTransition(P,"scatter points").attr("transform",function(a){return"translate("+m(p(a[0],a[1]))+","+n(q(a[0],a[1]))+")"}).attr("d",a.utils.symbol().type(function(a){return s(a[0])}).size(function(a){return o(r(a[0],a[1]))})),clearTimeout(f),f=setTimeout(N,300),c=m.copy(),d=n.copy(),e=o.copy()}),P.renderEnd("scatter immediate"),b}var c,d,e,f,g={top:0,right:0,bottom:0,left:0},h=null,i=null,j=a.utils.defaultColor(),k=Math.floor(1e5*Math.random()),l=null,m=d3.scale.linear(),n=d3.scale.linear(),o=d3.scale.linear(),p=function(a){return a.x},q=function(a){return a.y},r=function(a){return a.size||1},s=function(a){return a.shape||"circle"},t=[],u=[],v=[],w=!0,x=function(a){return!a.notActive},y=!1,z=.1,A=!1,B=!0,C=!1,D=function(){return 25},E=null,F=null,G=null,H=null,I=null,J=null,K=!1,L=d3.dispatch("elementClick","elementDblClick","elementMouseover","elementMouseout","renderEnd"),M=!0,N=250,O=!1,P=a.utils.renderWatch(L,N),Q=[16,256];return b.dispatch=L,b.options=a.utils.optionsFunc.bind(b),b._calls=new function(){this.clearHighlights=function(){return a.dom.write(function(){l.selectAll(".nv-point.hover").classed("hover",!1)}),null},this.highlightPoint=function(b,c,d){a.dom.write(function(){l.select(" .nv-series-"+b+" .nv-point-"+c).classed("hover",d)})}},L.on("elementMouseover.point",function(a){w&&b._calls.highlightPoint(a.seriesIndex,a.pointIndex,!0)}),L.on("elementMouseout.point",function(a){w&&b._calls.highlightPoint(a.seriesIndex,a.pointIndex,!1)}),b._options=Object.create({},{width:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return i},set:function(a){i=a}},xScale:{get:function(){return m},set:function(a){m=a}},yScale:{get:function(){return n},set:function(a){n=a}},pointScale:{get:function(){return o},set:function(a){o=a}},xDomain:{get:function(){return E},set:function(a){E=a}},yDomain:{get:function(){return F},set:function(a){F=a}},pointDomain:{get:function(){return I},set:function(a){I=a}},xRange:{get:function(){return G},set:function(a){G=a}},yRange:{get:function(){return H},set:function(a){H=a}},pointRange:{get:function(){return J},set:function(a){J=a}},forceX:{get:function(){return t},set:function(a){t=a}},forceY:{get:function(){return u},set:function(a){u=a}},forcePoint:{get:function(){return v},set:function(a){v=a}},interactive:{get:function(){return w},set:function(a){w=a}},pointActive:{get:function(){return x},set:function(a){x=a}},padDataOuter:{get:function(){return z},set:function(a){z=a}},padData:{get:function(){return y},set:function(a){y=a}},clipEdge:{get:function(){return A},set:function(a){A=a}},clipVoronoi:{get:function(){return B},set:function(a){B=a}},clipRadius:{get:function(){return D},set:function(a){D=a}},showVoronoi:{get:function(){return C},set:function(a){C=a}},id:{get:function(){return k},set:function(a){k=a}},x:{get:function(){return p},set:function(a){p=d3.functor(a)}},y:{get:function(){return q},set:function(a){q=d3.functor(a)}},pointSize:{get:function(){return r},set:function(a){r=d3.functor(a)}},pointShape:{get:function(){return s},set:function(a){s=d3.functor(a)}},margin:{get:function(){return g},set:function(a){g.top=void 0!==a.top?a.top:g.top,g.right=void 0!==a.right?a.right:g.right,g.bottom=void 0!==a.bottom?a.bottom:g.bottom,g.left=void 0!==a.left?a.left:g.left}},duration:{get:function(){return N},set:function(a){N=a,P.reset(N)}},color:{get:function(){return j},set:function(b){j=a.utils.getColor(b)}},useVoronoi:{get:function(){return M},set:function(a){M=a,M===!1&&(B=!1)}}}),a.utils.initOptions(b),b},a.models.scatterChart=function(){"use strict";function b(z){return D.reset(),D.models(c),t&&D.models(d),u&&D.models(e),q&&D.models(g),r&&D.models(h),z.each(function(z){m=d3.select(this),a.utils.initSVG(m);var G=a.utils.availableWidth(k,m,j),H=a.utils.availableHeight(l,m,j);if(b.update=function(){0===A?m.call(b):m.transition().duration(A).call(b)},b.container=this,w.setter(F(z),b.update).getter(E(z)).update(),w.disabled=z.map(function(a){return!!a.disabled}),!x){var I;x={};for(I in w)x[I]=w[I]instanceof Array?w[I].slice(0):w[I]}if(!(z&&z.length&&z.filter(function(a){return a.values.length}).length))return a.utils.noData(b,m),D.renderEnd("scatter immediate"),b;m.selectAll(".nv-noData").remove(),o=c.xScale(),p=c.yScale();var J=m.selectAll("g.nv-wrap.nv-scatterChart").data([z]),K=J.enter().append("g").attr("class","nvd3 nv-wrap nv-scatterChart nv-chart-"+c.id()),L=K.append("g"),M=J.select("g");if(L.append("rect").attr("class","nvd3 nv-background").style("pointer-events","none"),L.append("g").attr("class","nv-x nv-axis"),L.append("g").attr("class","nv-y nv-axis"),L.append("g").attr("class","nv-scatterWrap"),L.append("g").attr("class","nv-regressionLinesWrap"),L.append("g").attr("class","nv-distWrap"),L.append("g").attr("class","nv-legendWrap"),v&&M.select(".nv-y.nv-axis").attr("transform","translate("+G+",0)"),s){var N=G;f.width(N),J.select(".nv-legendWrap").datum(z).call(f),j.top!=f.height()&&(j.top=f.height(),H=a.utils.availableHeight(l,m,j)),J.select(".nv-legendWrap").attr("transform","translate(0,"+-j.top+")")}J.attr("transform","translate("+j.left+","+j.top+")"),c.width(G).height(H).color(z.map(function(a,b){return a.color=a.color||n(a,b),a.color}).filter(function(a,b){return!z[b].disabled})),J.select(".nv-scatterWrap").datum(z.filter(function(a){return!a.disabled})).call(c),J.select(".nv-regressionLinesWrap").attr("clip-path","url(#nv-edge-clip-"+c.id()+")");var O=J.select(".nv-regressionLinesWrap").selectAll(".nv-regLines").data(function(a){return a});O.enter().append("g").attr("class","nv-regLines");var P=O.selectAll(".nv-regLine").data(function(a){return[a]});P.enter().append("line").attr("class","nv-regLine").style("stroke-opacity",0),P.filter(function(a){return a.intercept&&a.slope}).watchTransition(D,"scatterPlusLineChart: regline").attr("x1",o.range()[0]).attr("x2",o.range()[1]).attr("y1",function(a){return p(o.domain()[0]*a.slope+a.intercept)}).attr("y2",function(a){return p(o.domain()[1]*a.slope+a.intercept)}).style("stroke",function(a,b,c){return n(a,c)}).style("stroke-opacity",function(a){return a.disabled||"undefined"==typeof a.slope||"undefined"==typeof a.intercept?0:1}),t&&(d.scale(o)._ticks(a.utils.calcTicksX(G/100,z)).tickSize(-H,0),M.select(".nv-x.nv-axis").attr("transform","translate(0,"+p.range()[0]+")").call(d)),u&&(e.scale(p)._ticks(a.utils.calcTicksY(H/36,z)).tickSize(-G,0),M.select(".nv-y.nv-axis").call(e)),q&&(g.getData(c.x()).scale(o).width(G).color(z.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!z[b].disabled})),L.select(".nv-distWrap").append("g").attr("class","nv-distributionX"),M.select(".nv-distributionX").attr("transform","translate(0,"+p.range()[0]+")").datum(z.filter(function(a){return!a.disabled})).call(g)),r&&(h.getData(c.y()).scale(p).width(H).color(z.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!z[b].disabled})),L.select(".nv-distWrap").append("g").attr("class","nv-distributionY"),M.select(".nv-distributionY").attr("transform","translate("+(v?G:-h.size())+",0)").datum(z.filter(function(a){return!a.disabled})).call(h)),f.dispatch.on("stateChange",function(a){for(var c in a)w[c]=a[c];y.stateChange(w),b.update()}),y.on("changeState",function(a){"undefined"!=typeof a.disabled&&(z.forEach(function(b,c){b.disabled=a.disabled[c]}),w.disabled=a.disabled),b.update()}),c.dispatch.on("elementMouseout.tooltip",function(a){i.hidden(!0),m.select(".nv-chart-"+c.id()+" .nv-series-"+a.seriesIndex+" .nv-distx-"+a.pointIndex).attr("y1",0),m.select(".nv-chart-"+c.id()+" .nv-series-"+a.seriesIndex+" .nv-disty-"+a.pointIndex).attr("x2",h.size())}),c.dispatch.on("elementMouseover.tooltip",function(a){m.select(".nv-series-"+a.seriesIndex+" .nv-distx-"+a.pointIndex).attr("y1",a.pos.top-H-j.top),m.select(".nv-series-"+a.seriesIndex+" .nv-disty-"+a.pointIndex).attr("x2",a.pos.left+g.size()-j.left),i.position(a.pos).data(a).hidden(!1)}),B=o.copy(),C=p.copy()}),D.renderEnd("scatter with line immediate"),b}var c=a.models.scatter(),d=a.models.axis(),e=a.models.axis(),f=a.models.legend(),g=a.models.distribution(),h=a.models.distribution(),i=a.models.tooltip(),j={top:30,right:20,bottom:50,left:75},k=null,l=null,m=null,n=a.utils.defaultColor(),o=c.xScale(),p=c.yScale(),q=!1,r=!1,s=!0,t=!0,u=!0,v=!1,w=a.utils.state(),x=null,y=d3.dispatch("stateChange","changeState","renderEnd"),z=null,A=250;c.xScale(o).yScale(p),d.orient("bottom").tickPadding(10),e.orient(v?"right":"left").tickPadding(10),g.axis("x"),h.axis("y"),i.headerFormatter(function(a,b){return d.tickFormat()(a,b)}).valueFormatter(function(a,b){return e.tickFormat()(a,b)});var B,C,D=a.utils.renderWatch(y,A),E=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},F=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return b.dispatch=y,b.scatter=c,b.legend=f,b.xAxis=d,b.yAxis=e,b.distX=g,b.distY=h,b.tooltip=i,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},container:{get:function(){return m},set:function(a){m=a}},showDistX:{get:function(){return q},set:function(a){q=a}},showDistY:{get:function(){return r},set:function(a){r=a}},showLegend:{get:function(){return s},set:function(a){s=a}},showXAxis:{get:function(){return t},set:function(a){t=a}},showYAxis:{get:function(){return u},set:function(a){u=a}},defaultState:{get:function(){return x},set:function(a){x=a}},noData:{get:function(){return z},set:function(a){z=a}},duration:{get:function(){return A},set:function(a){A=a}},tooltips:{get:function(){return i.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),i.enabled(!!b) +}},tooltipContent:{get:function(){return i.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),i.contentGenerator(b)}},tooltipXContent:{get:function(){return i.contentGenerator()},set:function(){a.deprecated("tooltipContent","This option is removed, put values into main tooltip.")}},tooltipYContent:{get:function(){return i.contentGenerator()},set:function(){a.deprecated("tooltipContent","This option is removed, put values into main tooltip.")}},margin:{get:function(){return j},set:function(a){j.top=void 0!==a.top?a.top:j.top,j.right=void 0!==a.right?a.right:j.right,j.bottom=void 0!==a.bottom?a.bottom:j.bottom,j.left=void 0!==a.left?a.left:j.left}},rightAlignYAxis:{get:function(){return v},set:function(a){v=a,e.orient(a?"right":"left")}},color:{get:function(){return n},set:function(b){n=a.utils.getColor(b),f.color(n),g.color(n),h.color(n)}}}),a.utils.inheritOptions(b,c),a.utils.initOptions(b),b},a.models.sparkline=function(){"use strict";function b(k){return k.each(function(b){var k=h-g.left-g.right,q=i-g.top-g.bottom;j=d3.select(this),a.utils.initSVG(j),l.domain(c||d3.extent(b,n)).range(e||[0,k]),m.domain(d||d3.extent(b,o)).range(f||[q,0]);{var r=j.selectAll("g.nv-wrap.nv-sparkline").data([b]),s=r.enter().append("g").attr("class","nvd3 nv-wrap nv-sparkline");s.append("g"),r.select("g")}r.attr("transform","translate("+g.left+","+g.top+")");var t=r.selectAll("path").data(function(a){return[a]});t.enter().append("path"),t.exit().remove(),t.style("stroke",function(a,b){return a.color||p(a,b)}).attr("d",d3.svg.line().x(function(a,b){return l(n(a,b))}).y(function(a,b){return m(o(a,b))}));var u=r.selectAll("circle.nv-point").data(function(a){function b(b){if(-1!=b){var c=a[b];return c.pointIndex=b,c}return null}var c=a.map(function(a,b){return o(a,b)}),d=b(c.lastIndexOf(m.domain()[1])),e=b(c.indexOf(m.domain()[0])),f=b(c.length-1);return[e,d,f].filter(function(a){return null!=a})});u.enter().append("circle"),u.exit().remove(),u.attr("cx",function(a){return l(n(a,a.pointIndex))}).attr("cy",function(a){return m(o(a,a.pointIndex))}).attr("r",2).attr("class",function(a){return n(a,a.pointIndex)==l.domain()[1]?"nv-point nv-currentValue":o(a,a.pointIndex)==m.domain()[0]?"nv-point nv-minValue":"nv-point nv-maxValue"})}),b}var c,d,e,f,g={top:2,right:0,bottom:2,left:0},h=400,i=32,j=null,k=!0,l=d3.scale.linear(),m=d3.scale.linear(),n=function(a){return a.x},o=function(a){return a.y},p=a.utils.getColor(["#000"]);return b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return i},set:function(a){i=a}},xDomain:{get:function(){return c},set:function(a){c=a}},yDomain:{get:function(){return d},set:function(a){d=a}},xRange:{get:function(){return e},set:function(a){e=a}},yRange:{get:function(){return f},set:function(a){f=a}},xScale:{get:function(){return l},set:function(a){l=a}},yScale:{get:function(){return m},set:function(a){m=a}},animate:{get:function(){return k},set:function(a){k=a}},x:{get:function(){return n},set:function(a){n=d3.functor(a)}},y:{get:function(){return o},set:function(a){o=d3.functor(a)}},margin:{get:function(){return g},set:function(a){g.top=void 0!==a.top?a.top:g.top,g.right=void 0!==a.right?a.right:g.right,g.bottom=void 0!==a.bottom?a.bottom:g.bottom,g.left=void 0!==a.left?a.left:g.left}},color:{get:function(){return p},set:function(b){p=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.sparklinePlus=function(){"use strict";function b(p){return p.each(function(p){function q(){if(!j){var a=z.selectAll(".nv-hoverValue").data(i),b=a.enter().append("g").attr("class","nv-hoverValue").style("stroke-opacity",0).style("fill-opacity",0);a.exit().transition().duration(250).style("stroke-opacity",0).style("fill-opacity",0).remove(),a.attr("transform",function(a){return"translate("+c(e.x()(p[a],a))+",0)"}).transition().duration(250).style("stroke-opacity",1).style("fill-opacity",1),i.length&&(b.append("line").attr("x1",0).attr("y1",-f.top).attr("x2",0).attr("y2",u),b.append("text").attr("class","nv-xValue").attr("x",-6).attr("y",-f.top).attr("text-anchor","end").attr("dy",".9em"),z.select(".nv-hoverValue .nv-xValue").text(k(e.x()(p[i[0]],i[0]))),b.append("text").attr("class","nv-yValue").attr("x",6).attr("y",-f.top).attr("text-anchor","start").attr("dy",".9em"),z.select(".nv-hoverValue .nv-yValue").text(l(e.y()(p[i[0]],i[0]))))}}function r(){function a(a,b){for(var c=Math.abs(e.x()(a[0],0)-b),d=0,f=0;fc;++c){for(b=0,d=0;bb;b++)a[b][c][1]/=d;else for(b=0;e>b;b++)a[b][c][1]=0}for(c=0;f>c;++c)g[c]=0;return g}}),u.renderEnd("stackedArea immediate"),b}var c,d,e={top:0,right:0,bottom:0,left:0},f=960,g=500,h=a.utils.defaultColor(),i=Math.floor(1e5*Math.random()),j=null,k=function(a){return a.x},l=function(a){return a.y},m="stack",n="zero",o="default",p="linear",q=!1,r=a.models.scatter(),s=250,t=d3.dispatch("areaClick","areaMouseover","areaMouseout","renderEnd","elementClick","elementMouseover","elementMouseout");r.pointSize(2.2).pointDomain([2.2,2.2]);var u=a.utils.renderWatch(t,s);return b.dispatch=t,b.scatter=r,r.dispatch.on("elementClick",function(){t.elementClick.apply(this,arguments)}),r.dispatch.on("elementMouseover",function(){t.elementMouseover.apply(this,arguments)}),r.dispatch.on("elementMouseout",function(){t.elementMouseout.apply(this,arguments)}),b.interpolate=function(a){return arguments.length?(p=a,b):p},b.duration=function(a){return arguments.length?(s=a,u.reset(s),r.duration(s),b):s},b.dispatch=t,b.scatter=r,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return f},set:function(a){f=a}},height:{get:function(){return g},set:function(a){g=a}},clipEdge:{get:function(){return q},set:function(a){q=a}},offset:{get:function(){return n},set:function(a){n=a}},order:{get:function(){return o},set:function(a){o=a}},interpolate:{get:function(){return p},set:function(a){p=a}},x:{get:function(){return k},set:function(a){k=d3.functor(a)}},y:{get:function(){return l},set:function(a){l=d3.functor(a)}},margin:{get:function(){return e},set:function(a){e.top=void 0!==a.top?a.top:e.top,e.right=void 0!==a.right?a.right:e.right,e.bottom=void 0!==a.bottom?a.bottom:e.bottom,e.left=void 0!==a.left?a.left:e.left}},color:{get:function(){return h},set:function(b){h=a.utils.getColor(b)}},style:{get:function(){return m},set:function(a){switch(m=a){case"stack":b.offset("zero"),b.order("default");break;case"stream":b.offset("wiggle"),b.order("inside-out");break;case"stream-center":b.offset("silhouette"),b.order("inside-out");break;case"expand":b.offset("expand"),b.order("default");break;case"stack_percent":b.offset(b.d3_stackedOffset_stackPercent),b.order("default")}}},duration:{get:function(){return s},set:function(a){s=a,u.reset(s),r.duration(s)}}}),a.utils.inheritOptions(b,r),a.utils.initOptions(b),b},a.models.stackedAreaChart=function(){"use strict";function b(k){return F.reset(),F.models(e),r&&F.models(f),s&&F.models(g),k.each(function(k){var x=d3.select(this),F=this;a.utils.initSVG(x);var K=a.utils.availableWidth(m,x,l),L=a.utils.availableHeight(n,x,l);if(b.update=function(){x.transition().duration(C).call(b)},b.container=this,v.setter(I(k),b.update).getter(H(k)).update(),v.disabled=k.map(function(a){return!!a.disabled}),!w){var M;w={};for(M in v)w[M]=v[M]instanceof Array?v[M].slice(0):v[M]}if(!(k&&k.length&&k.filter(function(a){return a.values.length}).length))return a.utils.noData(b,x),b;x.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale();var N=x.selectAll("g.nv-wrap.nv-stackedAreaChart").data([k]),O=N.enter().append("g").attr("class","nvd3 nv-wrap nv-stackedAreaChart").append("g"),P=N.select("g");if(O.append("rect").style("opacity",0),O.append("g").attr("class","nv-x nv-axis"),O.append("g").attr("class","nv-y nv-axis"),O.append("g").attr("class","nv-stackedWrap"),O.append("g").attr("class","nv-legendWrap"),O.append("g").attr("class","nv-controlsWrap"),O.append("g").attr("class","nv-interactive"),P.select("rect").attr("width",K).attr("height",L),q){var Q=p?K-z:K;h.width(Q),P.select(".nv-legendWrap").datum(k).call(h),l.top!=h.height()&&(l.top=h.height(),L=a.utils.availableHeight(n,x,l)),P.select(".nv-legendWrap").attr("transform","translate("+(K-Q)+","+-l.top+")")}if(p){var R=[{key:B.stacked||"Stacked",metaKey:"Stacked",disabled:"stack"!=e.style(),style:"stack"},{key:B.stream||"Stream",metaKey:"Stream",disabled:"stream"!=e.style(),style:"stream"},{key:B.expanded||"Expanded",metaKey:"Expanded",disabled:"expand"!=e.style(),style:"expand"},{key:B.stack_percent||"Stack %",metaKey:"Stack_Percent",disabled:"stack_percent"!=e.style(),style:"stack_percent"}];z=A.length/3*260,R=R.filter(function(a){return-1!==A.indexOf(a.metaKey)}),i.width(z).color(["#444","#444","#444"]),P.select(".nv-controlsWrap").datum(R).call(i),l.top!=Math.max(i.height(),h.height())&&(l.top=Math.max(i.height(),h.height()),L=a.utils.availableHeight(n,x,l)),P.select(".nv-controlsWrap").attr("transform","translate(0,"+-l.top+")")}N.attr("transform","translate("+l.left+","+l.top+")"),t&&P.select(".nv-y.nv-axis").attr("transform","translate("+K+",0)"),u&&(j.width(K).height(L).margin({left:l.left,top:l.top}).svgContainer(x).xScale(c),N.select(".nv-interactive").call(j)),e.width(K).height(L);var S=P.select(".nv-stackedWrap").datum(k);if(S.transition().call(e),r&&(f.scale(c)._ticks(a.utils.calcTicksX(K/100,k)).tickSize(-L,0),P.select(".nv-x.nv-axis").attr("transform","translate(0,"+L+")"),P.select(".nv-x.nv-axis").transition().duration(0).call(f)),s){var T;if(T="wiggle"===e.offset()?0:a.utils.calcTicksY(L/36,k),g.scale(d)._ticks(T).tickSize(-K,0),"expand"===e.style()||"stack_percent"===e.style()){var U=g.tickFormat();D&&U===J||(D=U),g.tickFormat(J)}else D&&(g.tickFormat(D),D=null);P.select(".nv-y.nv-axis").transition().duration(0).call(g)}e.dispatch.on("areaClick.toggle",function(a){k.forEach(1===k.filter(function(a){return!a.disabled}).length?function(a){a.disabled=!1}:function(b,c){b.disabled=c!=a.seriesIndex}),v.disabled=k.map(function(a){return!!a.disabled}),y.stateChange(v),b.update()}),h.dispatch.on("stateChange",function(a){for(var c in a)v[c]=a[c];y.stateChange(v),b.update()}),i.dispatch.on("legendClick",function(a){a.disabled&&(R=R.map(function(a){return a.disabled=!0,a}),a.disabled=!1,e.style(a.style),v.style=e.style(),y.stateChange(v),b.update())}),j.dispatch.on("elementMousemove",function(c){e.clearHighlights();var d,g,h,i=[];if(k.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(f,j){g=a.interactiveBisect(f.values,c.pointXValue,b.x());var k=f.values[g],l=b.y()(k,g);if(null!=l&&e.highlightPoint(j,g,!0),"undefined"!=typeof k){"undefined"==typeof d&&(d=k),"undefined"==typeof h&&(h=b.xScale()(b.x()(k,g)));var m="expand"==e.style()?k.display.y:b.y()(k,g);i.push({key:f.key,value:m,color:o(f,f.seriesIndex),stackedValue:k.display})}}),i.reverse(),i.length>2){var m=b.yScale().invert(c.mouseY),n=null;i.forEach(function(a,b){m=Math.abs(m);var c=Math.abs(a.stackedValue.y0),d=Math.abs(a.stackedValue.y);return m>=c&&d+c>=m?void(n=b):void 0}),null!=n&&(i[n].highlight=!0)}var p=f.tickFormat()(b.x()(d,g)),q=j.tooltip.valueFormatter();"expand"===e.style()||"stack_percent"===e.style()?(E||(E=q),q=d3.format(".1%")):E&&(q=E,E=null),j.tooltip.position({left:h+l.left,top:c.mouseY+l.top}).chartContainer(F.parentNode).valueFormatter(q).data({value:p,series:i})(),j.renderGuideLine(h)}),j.dispatch.on("elementMouseout",function(){e.clearHighlights()}),y.on("changeState",function(a){"undefined"!=typeof a.disabled&&k.length===a.disabled.length&&(k.forEach(function(b,c){b.disabled=a.disabled[c]}),v.disabled=a.disabled),"undefined"!=typeof a.style&&(e.style(a.style),G=a.style),b.update()})}),F.renderEnd("stacked Area chart immediate"),b}var c,d,e=a.models.stackedArea(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend(),i=a.models.legend(),j=a.interactiveGuideline(),k=a.models.tooltip(),l={top:30,right:25,bottom:50,left:60},m=null,n=null,o=a.utils.defaultColor(),p=!0,q=!0,r=!0,s=!0,t=!1,u=!1,v=a.utils.state(),w=null,x=null,y=d3.dispatch("stateChange","changeState","renderEnd"),z=250,A=["Stacked","Stream","Expanded"],B={},C=250;v.style=e.style(),f.orient("bottom").tickPadding(7),g.orient(t?"right":"left"),k.headerFormatter(function(a,b){return f.tickFormat()(a,b)}).valueFormatter(function(a,b){return g.tickFormat()(a,b)}),j.tooltip.headerFormatter(function(a,b){return f.tickFormat()(a,b)}).valueFormatter(function(a,b){return g.tickFormat()(a,b)});var D=null,E=null;i.updateState(!1);var F=a.utils.renderWatch(y),G=e.style(),H=function(a){return function(){return{active:a.map(function(a){return!a.disabled}),style:e.style()}}},I=function(a){return function(b){void 0!==b.style&&(G=b.style),void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}},J=d3.format("%");return e.dispatch.on("elementMouseover.tooltip",function(a){a.point.x=e.x()(a.point),a.point.y=e.y()(a.point),k.data(a).position(a.pos).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){k.hidden(!0)}),b.dispatch=y,b.stacked=e,b.legend=h,b.controls=i,b.xAxis=f,b.yAxis=g,b.interactiveLayer=j,b.tooltip=k,b.dispatch=y,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return m},set:function(a){m=a}},height:{get:function(){return n},set:function(a){n=a}},showLegend:{get:function(){return q},set:function(a){q=a}},showXAxis:{get:function(){return r},set:function(a){r=a}},showYAxis:{get:function(){return s},set:function(a){s=a}},defaultState:{get:function(){return w},set:function(a){w=a}},noData:{get:function(){return x},set:function(a){x=a}},showControls:{get:function(){return p},set:function(a){p=a}},controlLabels:{get:function(){return B},set:function(a){B=a}},controlOptions:{get:function(){return A},set:function(a){A=a}},tooltips:{get:function(){return k.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),k.enabled(!!b)}},tooltipContent:{get:function(){return k.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),k.contentGenerator(b)}},margin:{get:function(){return l},set:function(a){l.top=void 0!==a.top?a.top:l.top,l.right=void 0!==a.right?a.right:l.right,l.bottom=void 0!==a.bottom?a.bottom:l.bottom,l.left=void 0!==a.left?a.left:l.left}},duration:{get:function(){return C},set:function(a){C=a,F.reset(C),e.duration(C),f.duration(C),g.duration(C)}},color:{get:function(){return o},set:function(b){o=a.utils.getColor(b),h.color(o),e.color(o)}},rightAlignYAxis:{get:function(){return t},set:function(a){t=a,g.orient(t?"right":"left")}},useInteractiveGuideline:{get:function(){return u},set:function(a){u=!!a,b.interactive(!a),b.useVoronoi(!a),e.scatter.interactive(!a)}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.sunburst=function(){"use strict";function b(u){return t.reset(),u.each(function(b){function t(a){a.x0=a.x,a.dx0=a.dx}function u(a){var b=d3.interpolate(p.domain(),[a.x,a.x+a.dx]),c=d3.interpolate(q.domain(),[a.y,1]),d=d3.interpolate(q.range(),[a.y?20:0,y]);return function(a,e){return e?function(){return s(a)}:function(e){return p.domain(b(e)),q.domain(c(e)).range(d(e)),s(a)}}}l=d3.select(this);var v,w=a.utils.availableWidth(g,l,f),x=a.utils.availableHeight(h,l,f),y=Math.min(w,x)/2;a.utils.initSVG(l);var z=l.selectAll(".nv-wrap.nv-sunburst").data(b),A=z.enter().append("g").attr("class","nvd3 nv-wrap nv-sunburst nv-chart-"+k),B=A.selectAll("nv-sunburst");z.attr("transform","translate("+w/2+","+x/2+")"),l.on("click",function(a,b){o.chartClick({data:a,index:b,pos:d3.event,id:k})}),q.range([0,y]),c=c||b,e=b[0],r.value(j[i]||j.count),v=B.data(r.nodes).enter().append("path").attr("d",s).style("fill",function(a){return m((a.children?a:a.parent).name)}).style("stroke","#FFF").on("click",function(a){d!==c&&c!==a&&(d=c),c=a,v.transition().duration(n).attrTween("d",u(a))}).each(t).on("dblclick",function(a){d.parent==a&&v.transition().duration(n).attrTween("d",u(e))}).each(t).on("mouseover",function(a){d3.select(this).classed("hover",!0).style("opacity",.8),o.elementMouseover({data:a,color:d3.select(this).style("fill")})}).on("mouseout",function(a){d3.select(this).classed("hover",!1).style("opacity",1),o.elementMouseout({data:a})}).on("mousemove",function(a){o.elementMousemove({data:a})})}),t.renderEnd("sunburst immediate"),b}var c,d,e,f={top:0,right:0,bottom:0,left:0},g=null,h=null,i="count",j={count:function(){return 1},size:function(a){return a.size}},k=Math.floor(1e4*Math.random()),l=null,m=a.utils.defaultColor(),n=500,o=d3.dispatch("chartClick","elementClick","elementDblClick","elementMousemove","elementMouseover","elementMouseout","renderEnd"),p=d3.scale.linear().range([0,2*Math.PI]),q=d3.scale.sqrt(),r=d3.layout.partition().sort(null).value(function(){return 1}),s=d3.svg.arc().startAngle(function(a){return Math.max(0,Math.min(2*Math.PI,p(a.x)))}).endAngle(function(a){return Math.max(0,Math.min(2*Math.PI,p(a.x+a.dx)))}).innerRadius(function(a){return Math.max(0,q(a.y))}).outerRadius(function(a){return Math.max(0,q(a.y+a.dy))}),t=a.utils.renderWatch(o);return b.dispatch=o,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return g},set:function(a){g=a}},height:{get:function(){return h},set:function(a){h=a}},mode:{get:function(){return i},set:function(a){i=a}},id:{get:function(){return k},set:function(a){k=a}},duration:{get:function(){return n},set:function(a){n=a}},margin:{get:function(){return f},set:function(a){f.top=void 0!=a.top?a.top:f.top,f.right=void 0!=a.right?a.right:f.right,f.bottom=void 0!=a.bottom?a.bottom:f.bottom,f.left=void 0!=a.left?a.left:f.left}},color:{get:function(){return m},set:function(b){m=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.sunburstChart=function(){"use strict";function b(d){return m.reset(),m.models(c),d.each(function(d){var h=d3.select(this);a.utils.initSVG(h);var i=a.utils.availableWidth(f,h,e),j=a.utils.availableHeight(g,h,e);if(b.update=function(){0===k?h.call(b):h.transition().duration(k).call(b)},b.container=this,!d||!d.length)return a.utils.noData(b,h),b;h.selectAll(".nv-noData").remove();var l=h.selectAll("g.nv-wrap.nv-sunburstChart").data(d),m=l.enter().append("g").attr("class","nvd3 nv-wrap nv-sunburstChart").append("g"),n=l.select("g");m.append("g").attr("class","nv-sunburstWrap"),l.attr("transform","translate("+e.left+","+e.top+")"),c.width(i).height(j);var o=n.select(".nv-sunburstWrap").datum(d);d3.transition(o).call(c)}),m.renderEnd("sunburstChart immediate"),b}var c=a.models.sunburst(),d=a.models.tooltip(),e={top:30,right:20,bottom:20,left:20},f=null,g=null,h=a.utils.defaultColor(),i=(Math.round(1e5*Math.random()),null),j=null,k=250,l=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd"),m=a.utils.renderWatch(l);return d.headerEnabled(!1).duration(0).valueFormatter(function(a){return a}),c.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:a.data.name,value:a.data.size,color:a.color},d.data(a).hidden(!1)}),c.dispatch.on("elementMouseout.tooltip",function(){d.hidden(!0)}),c.dispatch.on("elementMousemove.tooltip",function(){d.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=l,b.sunburst=c,b.tooltip=d,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{noData:{get:function(){return j},set:function(a){j=a}},defaultState:{get:function(){return i},set:function(a){i=a}},color:{get:function(){return h},set:function(a){h=a,c.color(h)}},duration:{get:function(){return k},set:function(a){k=a,m.reset(k),c.duration(k)}},margin:{get:function(){return e},set:function(a){e.top=void 0!==a.top?a.top:e.top,e.right=void 0!==a.right?a.right:e.right,e.bottom=void 0!==a.bottom?a.bottom:e.bottom,e.left=void 0!==a.left?a.left:e.left}}}),a.utils.inheritOptions(b,c),a.utils.initOptions(b),b},a.version="1.8.1"}(); \ No newline at end of file diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/popper.min.js b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/popper.min.js new file mode 100644 index 00000000..bb1aaae3 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/popper.min.js @@ -0,0 +1,5 @@ +/* + Copyright (C) Federico Zivolo 2020 + Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT). + */(function(e,t){'object'==typeof exports&&'undefined'!=typeof module?module.exports=t():'function'==typeof define&&define.amd?define(t):e.Popper=t()})(this,function(){'use strict';function e(e){return e&&'[object Function]'==={}.toString.call(e)}function t(e,t){if(1!==e.nodeType)return[];var o=e.ownerDocument.defaultView,n=o.getComputedStyle(e,null);return t?n[t]:n}function o(e){return'HTML'===e.nodeName?e:e.parentNode||e.host}function n(e){if(!e)return document.body;switch(e.nodeName){case'HTML':case'BODY':return e.ownerDocument.body;case'#document':return e.body;}var i=t(e),r=i.overflow,p=i.overflowX,s=i.overflowY;return /(auto|scroll|overlay)/.test(r+s+p)?e:n(o(e))}function i(e){return e&&e.referenceNode?e.referenceNode:e}function r(e){return 11===e?re:10===e?pe:re||pe}function p(e){if(!e)return document.documentElement;for(var o=r(10)?document.body:null,n=e.offsetParent||null;n===o&&e.nextElementSibling;)n=(e=e.nextElementSibling).offsetParent;var i=n&&n.nodeName;return i&&'BODY'!==i&&'HTML'!==i?-1!==['TH','TD','TABLE'].indexOf(n.nodeName)&&'static'===t(n,'position')?p(n):n:e?e.ownerDocument.documentElement:document.documentElement}function s(e){var t=e.nodeName;return'BODY'!==t&&('HTML'===t||p(e.firstElementChild)===e)}function d(e){return null===e.parentNode?e:d(e.parentNode)}function a(e,t){if(!e||!e.nodeType||!t||!t.nodeType)return document.documentElement;var o=e.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_FOLLOWING,n=o?e:t,i=o?t:e,r=document.createRange();r.setStart(n,0),r.setEnd(i,0);var l=r.commonAncestorContainer;if(e!==l&&t!==l||n.contains(i))return s(l)?l:p(l);var f=d(e);return f.host?a(f.host,t):a(e,d(t).host)}function l(e){var t=1=o.clientWidth&&n>=o.clientHeight}),l=0a[e]&&!t.escapeWithReference&&(n=Q(f[o],a[e]-('right'===e?f.width:f.height))),ae({},o,n)}};return l.forEach(function(e){var t=-1===['left','top'].indexOf(e)?'secondary':'primary';f=le({},f,m[t](e))}),e.offsets.popper=f,e},priority:['left','right','top','bottom'],padding:5,boundariesElement:'scrollParent'},keepTogether:{order:400,enabled:!0,fn:function(e){var t=e.offsets,o=t.popper,n=t.reference,i=e.placement.split('-')[0],r=Z,p=-1!==['top','bottom'].indexOf(i),s=p?'right':'bottom',d=p?'left':'top',a=p?'width':'height';return o[s]r(n[s])&&(e.offsets.popper[d]=r(n[s])),e}},arrow:{order:500,enabled:!0,fn:function(e,o){var n;if(!K(e.instance.modifiers,'arrow','keepTogether'))return e;var i=o.element;if('string'==typeof i){if(i=e.instance.popper.querySelector(i),!i)return e;}else if(!e.instance.popper.contains(i))return console.warn('WARNING: `arrow.element` must be child of its popper element!'),e;var r=e.placement.split('-')[0],p=e.offsets,s=p.popper,d=p.reference,a=-1!==['left','right'].indexOf(r),l=a?'height':'width',f=a?'Top':'Left',m=f.toLowerCase(),h=a?'left':'top',c=a?'bottom':'right',u=S(i)[l];d[c]-us[c]&&(e.offsets.popper[m]+=d[m]+u-s[c]),e.offsets.popper=g(e.offsets.popper);var b=d[m]+d[l]/2-u/2,w=t(e.instance.popper),y=parseFloat(w['margin'+f]),E=parseFloat(w['border'+f+'Width']),v=b-e.offsets.popper[m]-y-E;return v=ee(Q(s[l]-u,v),0),e.arrowElement=i,e.offsets.arrow=(n={},ae(n,m,$(v)),ae(n,h,''),n),e},element:'[x-arrow]'},flip:{order:600,enabled:!0,fn:function(e,t){if(W(e.instance.modifiers,'inner'))return e;if(e.flipped&&e.placement===e.originalPlacement)return e;var o=v(e.instance.popper,e.instance.reference,t.padding,t.boundariesElement,e.positionFixed),n=e.placement.split('-')[0],i=T(n),r=e.placement.split('-')[1]||'',p=[];switch(t.behavior){case ce.FLIP:p=[n,i];break;case ce.CLOCKWISE:p=G(n);break;case ce.COUNTERCLOCKWISE:p=G(n,!0);break;default:p=t.behavior;}return p.forEach(function(s,d){if(n!==s||p.length===d+1)return e;n=e.placement.split('-')[0],i=T(n);var a=e.offsets.popper,l=e.offsets.reference,f=Z,m='left'===n&&f(a.right)>f(l.left)||'right'===n&&f(a.left)f(l.top)||'bottom'===n&&f(a.top)f(o.right),g=f(a.top)f(o.bottom),b='left'===n&&h||'right'===n&&c||'top'===n&&g||'bottom'===n&&u,w=-1!==['top','bottom'].indexOf(n),y=!!t.flipVariations&&(w&&'start'===r&&h||w&&'end'===r&&c||!w&&'start'===r&&g||!w&&'end'===r&&u),E=!!t.flipVariationsByContent&&(w&&'start'===r&&c||w&&'end'===r&&h||!w&&'start'===r&&u||!w&&'end'===r&&g),v=y||E;(m||b||v)&&(e.flipped=!0,(m||b)&&(n=p[d+1]),v&&(r=z(r)),e.placement=n+(r?'-'+r:''),e.offsets.popper=le({},e.offsets.popper,C(e.instance.popper,e.offsets.reference,e.placement)),e=P(e.instance.modifiers,e,'flip'))}),e},behavior:'flip',padding:5,boundariesElement:'viewport',flipVariations:!1,flipVariationsByContent:!1},inner:{order:700,enabled:!1,fn:function(e){var t=e.placement,o=t.split('-')[0],n=e.offsets,i=n.popper,r=n.reference,p=-1!==['left','right'].indexOf(o),s=-1===['top','left'].indexOf(o);return i[p?'left':'top']=r[o]-(s?i[p?'width':'height']:0),e.placement=T(t),e.offsets.popper=g(i),e}},hide:{order:800,enabled:!0,fn:function(e){if(!K(e.instance.modifiers,'hide','preventOverflow'))return e;var t=e.offsets.reference,o=D(e.instance.modifiers,function(e){return'preventOverflow'===e.name}).boundaries;if(t.bottomo.right||t.top>o.bottom||t.rightwindow.devicePixelRatio||!fe),c='bottom'===o?'top':'bottom',g='right'===n?'left':'right',b=B('transform');if(d='bottom'==c?'HTML'===l.nodeName?-l.clientHeight+h.bottom:-f.height+h.bottom:h.top,s='right'==g?'HTML'===l.nodeName?-l.clientWidth+h.right:-f.width+h.right:h.left,a&&b)m[b]='translate3d('+s+'px, '+d+'px, 0)',m[c]=0,m[g]=0,m.willChange='transform';else{var w='bottom'==c?-1:1,y='right'==g?-1:1;m[c]=d*w,m[g]=s*y,m.willChange=c+', '+g}var E={"x-placement":e.placement};return e.attributes=le({},E,e.attributes),e.styles=le({},m,e.styles),e.arrowStyles=le({},e.offsets.arrow,e.arrowStyles),e},gpuAcceleration:!0,x:'bottom',y:'right'},applyStyle:{order:900,enabled:!0,fn:function(e){return V(e.instance.popper,e.styles),j(e.instance.popper,e.attributes),e.arrowElement&&Object.keys(e.arrowStyles).length&&V(e.arrowElement,e.arrowStyles),e},onLoad:function(e,t,o,n,i){var r=L(i,t,e,o.positionFixed),p=O(o.placement,r,t,e,o.modifiers.flip.boundariesElement,o.modifiers.flip.padding);return t.setAttribute('x-placement',p),V(t,{position:o.positionFixed?'fixed':'absolute'}),o},gpuAcceleration:void 0}}},ge}); +//# sourceMappingURL=popper.min.js.map diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/line.html.dist b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/line.html.dist new file mode 100644 index 00000000..89810d15 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/line.html.dist @@ -0,0 +1 @@ + {{lineNumber}}{{lineContent}} diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/lines.html.dist b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/lines.html.dist new file mode 100644 index 00000000..add40e4e --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/lines.html.dist @@ -0,0 +1,5 @@ + + +{{lines}} + +
      diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/method_item.html.dist b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/method_item.html.dist new file mode 100644 index 00000000..2311d456 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/method_item.html.dist @@ -0,0 +1,12 @@ + + {{name}} + {{lines_bar}} +
      {{lines_executed_percent}}
      +
      {{lines_number}}
      + {{methods_bar}} +
      {{methods_tested_percent}}
      +
      {{methods_number}}
      + {{crap}} + + + diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/method_item_branch.html.dist b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/method_item_branch.html.dist new file mode 100644 index 00000000..36d6cb74 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/method_item_branch.html.dist @@ -0,0 +1,18 @@ + + {{name}} + {{lines_bar}} +
      {{lines_executed_percent}}
      +
      {{lines_number}}
      + {{branches_bar}} +
      {{branches_executed_percent}}
      +
      {{branches_number}}
      + {{paths_bar}} +
      {{paths_executed_percent}}
      +
      {{paths_number}}
      + {{methods_bar}} +
      {{methods_tested_percent}}
      +
      {{methods_number}}
      + {{crap}} + + + diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/paths.html.dist b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/paths.html.dist new file mode 100644 index 00000000..d14b8ad9 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/paths.html.dist @@ -0,0 +1,9 @@ +
      +

      Paths

      +

      + Below are the source code lines that represent each code path as identified by Xdebug. Please note a path is not + necessarily coterminous with a line, a line may contain multiple paths and therefore show up more than once. + Please also be aware that some paths may include implicit rather than explicit branches, e.g. an if statement + always has an else as part of its logical flow even if you didn't write one. +

      +{{paths}} diff --git a/vendor/phpunit/php-code-coverage/src/Report/PHP.php b/vendor/phpunit/php-code-coverage/src/Report/PHP.php new file mode 100644 index 00000000..1f8186d0 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/PHP.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Report; + +use function dirname; +use function file_put_contents; +use function serialize; +use function strpos; +use SebastianBergmann\CodeCoverage\CodeCoverage; +use SebastianBergmann\CodeCoverage\Driver\WriteOperationFailedException; +use SebastianBergmann\CodeCoverage\Util\Filesystem; + +final class PHP +{ + public function process(CodeCoverage $coverage, ?string $target = null): string + { + $coverage->clearCache(); + + $buffer = " + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Report; + +use const PHP_EOL; +use function array_map; +use function date; +use function ksort; +use function max; +use function sprintf; +use function str_pad; +use function strlen; +use SebastianBergmann\CodeCoverage\CodeCoverage; +use SebastianBergmann\CodeCoverage\Node\File; +use SebastianBergmann\CodeCoverage\Util\Percentage; + +final class Text +{ + /** + * @var string + */ + private const COLOR_GREEN = "\x1b[30;42m"; + + /** + * @var string + */ + private const COLOR_YELLOW = "\x1b[30;43m"; + + /** + * @var string + */ + private const COLOR_RED = "\x1b[37;41m"; + + /** + * @var string + */ + private const COLOR_HEADER = "\x1b[1;37;40m"; + + /** + * @var string + */ + private const COLOR_RESET = "\x1b[0m"; + + /** + * @var string + */ + private const COLOR_EOL = "\x1b[2K"; + + /** + * @var int + */ + private $lowUpperBound; + + /** + * @var int + */ + private $highLowerBound; + + /** + * @var bool + */ + private $showUncoveredFiles; + + /** + * @var bool + */ + private $showOnlySummary; + + public function __construct(int $lowUpperBound = 50, int $highLowerBound = 90, bool $showUncoveredFiles = false, bool $showOnlySummary = false) + { + $this->lowUpperBound = $lowUpperBound; + $this->highLowerBound = $highLowerBound; + $this->showUncoveredFiles = $showUncoveredFiles; + $this->showOnlySummary = $showOnlySummary; + } + + public function process(CodeCoverage $coverage, bool $showColors = false): string + { + $hasBranchCoverage = !empty($coverage->getData(true)->functionCoverage()); + + $output = PHP_EOL . PHP_EOL; + $report = $coverage->getReport(); + + $colors = [ + 'header' => '', + 'classes' => '', + 'methods' => '', + 'lines' => '', + 'branches' => '', + 'paths' => '', + 'reset' => '', + 'eol' => '', + ]; + + if ($showColors) { + $colors['classes'] = $this->coverageColor( + $report->numberOfTestedClassesAndTraits(), + $report->numberOfClassesAndTraits() + ); + + $colors['methods'] = $this->coverageColor( + $report->numberOfTestedMethods(), + $report->numberOfMethods() + ); + + $colors['lines'] = $this->coverageColor( + $report->numberOfExecutedLines(), + $report->numberOfExecutableLines() + ); + + $colors['branches'] = $this->coverageColor( + $report->numberOfExecutedBranches(), + $report->numberOfExecutableBranches() + ); + + $colors['paths'] = $this->coverageColor( + $report->numberOfExecutedPaths(), + $report->numberOfExecutablePaths() + ); + + $colors['reset'] = self::COLOR_RESET; + $colors['header'] = self::COLOR_HEADER; + $colors['eol'] = self::COLOR_EOL; + } + + $classes = sprintf( + ' Classes: %6s (%d/%d)', + Percentage::fromFractionAndTotal( + $report->numberOfTestedClassesAndTraits(), + $report->numberOfClassesAndTraits() + )->asString(), + $report->numberOfTestedClassesAndTraits(), + $report->numberOfClassesAndTraits() + ); + + $methods = sprintf( + ' Methods: %6s (%d/%d)', + Percentage::fromFractionAndTotal( + $report->numberOfTestedMethods(), + $report->numberOfMethods(), + )->asString(), + $report->numberOfTestedMethods(), + $report->numberOfMethods() + ); + + $paths = ''; + $branches = ''; + + if ($hasBranchCoverage) { + $paths = sprintf( + ' Paths: %6s (%d/%d)', + Percentage::fromFractionAndTotal( + $report->numberOfExecutedPaths(), + $report->numberOfExecutablePaths(), + )->asString(), + $report->numberOfExecutedPaths(), + $report->numberOfExecutablePaths() + ); + + $branches = sprintf( + ' Branches: %6s (%d/%d)', + Percentage::fromFractionAndTotal( + $report->numberOfExecutedBranches(), + $report->numberOfExecutableBranches(), + )->asString(), + $report->numberOfExecutedBranches(), + $report->numberOfExecutableBranches() + ); + } + + $lines = sprintf( + ' Lines: %6s (%d/%d)', + Percentage::fromFractionAndTotal( + $report->numberOfExecutedLines(), + $report->numberOfExecutableLines(), + )->asString(), + $report->numberOfExecutedLines(), + $report->numberOfExecutableLines() + ); + + $padding = max(array_map('strlen', [$classes, $methods, $lines])); + + if ($this->showOnlySummary) { + $title = 'Code Coverage Report Summary:'; + $padding = max($padding, strlen($title)); + + $output .= $this->format($colors['header'], $padding, $title); + } else { + $date = date(' Y-m-d H:i:s'); + $title = 'Code Coverage Report:'; + + $output .= $this->format($colors['header'], $padding, $title); + $output .= $this->format($colors['header'], $padding, $date); + $output .= $this->format($colors['header'], $padding, ''); + $output .= $this->format($colors['header'], $padding, ' Summary:'); + } + + $output .= $this->format($colors['classes'], $padding, $classes); + $output .= $this->format($colors['methods'], $padding, $methods); + + if ($hasBranchCoverage) { + $output .= $this->format($colors['paths'], $padding, $paths); + $output .= $this->format($colors['branches'], $padding, $branches); + } + $output .= $this->format($colors['lines'], $padding, $lines); + + if ($this->showOnlySummary) { + return $output . PHP_EOL; + } + + $classCoverage = []; + + foreach ($report as $item) { + if (!$item instanceof File) { + continue; + } + + $classes = $item->classesAndTraits(); + + foreach ($classes as $className => $class) { + $classExecutableLines = 0; + $classExecutedLines = 0; + $classExecutableBranches = 0; + $classExecutedBranches = 0; + $classExecutablePaths = 0; + $classExecutedPaths = 0; + $coveredMethods = 0; + $classMethods = 0; + + foreach ($class['methods'] as $method) { + if ($method['executableLines'] == 0) { + continue; + } + + $classMethods++; + $classExecutableLines += $method['executableLines']; + $classExecutedLines += $method['executedLines']; + $classExecutableBranches += $method['executableBranches']; + $classExecutedBranches += $method['executedBranches']; + $classExecutablePaths += $method['executablePaths']; + $classExecutedPaths += $method['executedPaths']; + + if ($method['coverage'] == 100) { + $coveredMethods++; + } + } + + $classCoverage[$className] = [ + 'namespace' => $class['namespace'], + 'className' => $className, + 'methodsCovered' => $coveredMethods, + 'methodCount' => $classMethods, + 'statementsCovered' => $classExecutedLines, + 'statementCount' => $classExecutableLines, + 'branchesCovered' => $classExecutedBranches, + 'branchesCount' => $classExecutableBranches, + 'pathsCovered' => $classExecutedPaths, + 'pathsCount' => $classExecutablePaths, + ]; + } + } + + ksort($classCoverage); + + $methodColor = ''; + $pathsColor = ''; + $branchesColor = ''; + $linesColor = ''; + $resetColor = ''; + + foreach ($classCoverage as $fullQualifiedPath => $classInfo) { + if ($this->showUncoveredFiles || $classInfo['statementsCovered'] != 0) { + if ($showColors) { + $methodColor = $this->coverageColor($classInfo['methodsCovered'], $classInfo['methodCount']); + $pathsColor = $this->coverageColor($classInfo['pathsCovered'], $classInfo['pathsCount']); + $branchesColor = $this->coverageColor($classInfo['branchesCovered'], $classInfo['branchesCount']); + $linesColor = $this->coverageColor($classInfo['statementsCovered'], $classInfo['statementCount']); + $resetColor = $colors['reset']; + } + + $output .= PHP_EOL . $fullQualifiedPath . PHP_EOL + . ' ' . $methodColor . 'Methods: ' . $this->printCoverageCounts($classInfo['methodsCovered'], $classInfo['methodCount'], 2) . $resetColor . ' '; + + if ($hasBranchCoverage) { + $output .= ' ' . $pathsColor . 'Paths: ' . $this->printCoverageCounts($classInfo['pathsCovered'], $classInfo['pathsCount'], 3) . $resetColor . ' ' + . ' ' . $branchesColor . 'Branches: ' . $this->printCoverageCounts($classInfo['branchesCovered'], $classInfo['branchesCount'], 3) . $resetColor . ' '; + } + $output .= ' ' . $linesColor . 'Lines: ' . $this->printCoverageCounts($classInfo['statementsCovered'], $classInfo['statementCount'], 3) . $resetColor; + } + } + + return $output . PHP_EOL; + } + + private function coverageColor(int $numberOfCoveredElements, int $totalNumberOfElements): string + { + $coverage = Percentage::fromFractionAndTotal( + $numberOfCoveredElements, + $totalNumberOfElements + ); + + if ($coverage->asFloat() >= $this->highLowerBound) { + return self::COLOR_GREEN; + } + + if ($coverage->asFloat() > $this->lowUpperBound) { + return self::COLOR_YELLOW; + } + + return self::COLOR_RED; + } + + private function printCoverageCounts(int $numberOfCoveredElements, int $totalNumberOfElements, int $precision): string + { + $format = '%' . $precision . 's'; + + return Percentage::fromFractionAndTotal( + $numberOfCoveredElements, + $totalNumberOfElements + )->asFixedWidthString() . + ' (' . sprintf($format, $numberOfCoveredElements) . '/' . + sprintf($format, $totalNumberOfElements) . ')'; + } + + /** + * @param false|string $string + */ + private function format(string $color, int $padding, $string): string + { + $reset = $color ? self::COLOR_RESET : ''; + + return $color . str_pad((string) $string, $padding) . $reset . PHP_EOL; + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Report/Xml/BuildInformation.php b/vendor/phpunit/php-code-coverage/src/Report/Xml/BuildInformation.php new file mode 100644 index 00000000..ebdbae61 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Xml/BuildInformation.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Report\Xml; + +use function constant; +use function phpversion; +use DateTimeImmutable; +use DOMElement; +use SebastianBergmann\Environment\Runtime; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class BuildInformation +{ + /** + * @var DOMElement + */ + private $contextNode; + + public function __construct(DOMElement $contextNode) + { + $this->contextNode = $contextNode; + } + + public function setRuntimeInformation(Runtime $runtime): void + { + $runtimeNode = $this->nodeByName('runtime'); + + $runtimeNode->setAttribute('name', $runtime->getName()); + $runtimeNode->setAttribute('version', $runtime->getVersion()); + $runtimeNode->setAttribute('url', $runtime->getVendorUrl()); + + $driverNode = $this->nodeByName('driver'); + + if ($runtime->hasPHPDBGCodeCoverage()) { + $driverNode->setAttribute('name', 'phpdbg'); + $driverNode->setAttribute('version', constant('PHPDBG_VERSION')); + } + + if ($runtime->hasXdebug()) { + $driverNode->setAttribute('name', 'xdebug'); + $driverNode->setAttribute('version', phpversion('xdebug')); + } + + if ($runtime->hasPCOV()) { + $driverNode->setAttribute('name', 'pcov'); + $driverNode->setAttribute('version', phpversion('pcov')); + } + } + + public function setBuildTime(DateTimeImmutable $date): void + { + $this->contextNode->setAttribute('time', $date->format('D M j G:i:s T Y')); + } + + public function setGeneratorVersions(string $phpUnitVersion, string $coverageVersion): void + { + $this->contextNode->setAttribute('phpunit', $phpUnitVersion); + $this->contextNode->setAttribute('coverage', $coverageVersion); + } + + private function nodeByName(string $name): DOMElement + { + $node = $this->contextNode->getElementsByTagNameNS( + 'https://schema.phpunit.de/coverage/1.0', + $name + )->item(0); + + if (!$node) { + $node = $this->contextNode->appendChild( + $this->contextNode->ownerDocument->createElementNS( + 'https://schema.phpunit.de/coverage/1.0', + $name + ) + ); + } + + return $node; + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Report/Xml/Coverage.php b/vendor/phpunit/php-code-coverage/src/Report/Xml/Coverage.php new file mode 100644 index 00000000..b556d820 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Xml/Coverage.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Report\Xml; + +use DOMElement; +use SebastianBergmann\CodeCoverage\ReportAlreadyFinalizedException; +use XMLWriter; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class Coverage +{ + /** + * @var XMLWriter + */ + private $writer; + + /** + * @var DOMElement + */ + private $contextNode; + + /** + * @var bool + */ + private $finalized = false; + + public function __construct(DOMElement $context, string $line) + { + $this->contextNode = $context; + + $this->writer = new XMLWriter; + $this->writer->openMemory(); + $this->writer->startElementNS(null, $context->nodeName, 'https://schema.phpunit.de/coverage/1.0'); + $this->writer->writeAttribute('nr', $line); + } + + /** + * @throws ReportAlreadyFinalizedException + */ + public function addTest(string $test): void + { + if ($this->finalized) { + throw new ReportAlreadyFinalizedException; + } + + $this->writer->startElement('covered'); + $this->writer->writeAttribute('by', $test); + $this->writer->endElement(); + } + + public function finalize(): void + { + $this->writer->endElement(); + + $fragment = $this->contextNode->ownerDocument->createDocumentFragment(); + $fragment->appendXML($this->writer->outputMemory()); + + $this->contextNode->parentNode->replaceChild( + $fragment, + $this->contextNode + ); + + $this->finalized = true; + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Report/Xml/Directory.php b/vendor/phpunit/php-code-coverage/src/Report/Xml/Directory.php new file mode 100644 index 00000000..b712953a --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Xml/Directory.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Report\Xml; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class Directory extends Node +{ +} diff --git a/vendor/phpunit/php-code-coverage/src/Report/Xml/Facade.php b/vendor/phpunit/php-code-coverage/src/Report/Xml/Facade.php new file mode 100644 index 00000000..3ecc7506 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Xml/Facade.php @@ -0,0 +1,315 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Report\Xml; + +use const DIRECTORY_SEPARATOR; +use const PHP_EOL; +use function count; +use function dirname; +use function file_get_contents; +use function file_put_contents; +use function is_array; +use function is_dir; +use function is_file; +use function is_writable; +use function libxml_clear_errors; +use function libxml_get_errors; +use function libxml_use_internal_errors; +use function sprintf; +use function strlen; +use function substr; +use DateTimeImmutable; +use DOMDocument; +use SebastianBergmann\CodeCoverage\CodeCoverage; +use SebastianBergmann\CodeCoverage\Driver\PathExistsButIsNotDirectoryException; +use SebastianBergmann\CodeCoverage\Driver\WriteOperationFailedException; +use SebastianBergmann\CodeCoverage\Node\AbstractNode; +use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode; +use SebastianBergmann\CodeCoverage\Node\File as FileNode; +use SebastianBergmann\CodeCoverage\Util\Filesystem as DirectoryUtil; +use SebastianBergmann\CodeCoverage\Version; +use SebastianBergmann\CodeCoverage\XmlException; +use SebastianBergmann\Environment\Runtime; + +final class Facade +{ + /** + * @var string + */ + private $target; + + /** + * @var Project + */ + private $project; + + /** + * @var string + */ + private $phpUnitVersion; + + public function __construct(string $version) + { + $this->phpUnitVersion = $version; + } + + /** + * @throws XmlException + */ + public function process(CodeCoverage $coverage, string $target): void + { + if (substr($target, -1, 1) !== DIRECTORY_SEPARATOR) { + $target .= DIRECTORY_SEPARATOR; + } + + $this->target = $target; + $this->initTargetDirectory($target); + + $report = $coverage->getReport(); + + $this->project = new Project( + $coverage->getReport()->name() + ); + + $this->setBuildInformation(); + $this->processTests($coverage->getTests()); + $this->processDirectory($report, $this->project); + + $this->saveDocument($this->project->asDom(), 'index'); + } + + private function setBuildInformation(): void + { + $buildNode = $this->project->buildInformation(); + $buildNode->setRuntimeInformation(new Runtime); + $buildNode->setBuildTime(new DateTimeImmutable); + $buildNode->setGeneratorVersions($this->phpUnitVersion, Version::id()); + } + + /** + * @throws PathExistsButIsNotDirectoryException + * @throws WriteOperationFailedException + */ + private function initTargetDirectory(string $directory): void + { + if (is_file($directory)) { + if (!is_dir($directory)) { + throw new PathExistsButIsNotDirectoryException($directory); + } + + if (!is_writable($directory)) { + throw new WriteOperationFailedException($directory); + } + } + + DirectoryUtil::createDirectory($directory); + } + + /** + * @throws XmlException + */ + private function processDirectory(DirectoryNode $directory, Node $context): void + { + $directoryName = $directory->name(); + + if ($this->project->projectSourceDirectory() === $directoryName) { + $directoryName = '/'; + } + + $directoryObject = $context->addDirectory($directoryName); + + $this->setTotals($directory, $directoryObject->totals()); + + foreach ($directory->directories() as $node) { + $this->processDirectory($node, $directoryObject); + } + + foreach ($directory->files() as $node) { + $this->processFile($node, $directoryObject); + } + } + + /** + * @throws XmlException + */ + private function processFile(FileNode $file, Directory $context): void + { + $fileObject = $context->addFile( + $file->name(), + $file->id() . '.xml' + ); + + $this->setTotals($file, $fileObject->totals()); + + $path = substr( + $file->pathAsString(), + strlen($this->project->projectSourceDirectory()) + ); + + $fileReport = new Report($path); + + $this->setTotals($file, $fileReport->totals()); + + foreach ($file->classesAndTraits() as $unit) { + $this->processUnit($unit, $fileReport); + } + + foreach ($file->functions() as $function) { + $this->processFunction($function, $fileReport); + } + + foreach ($file->lineCoverageData() as $line => $tests) { + if (!is_array($tests) || count($tests) === 0) { + continue; + } + + $coverage = $fileReport->lineCoverage((string) $line); + + foreach ($tests as $test) { + $coverage->addTest($test); + } + + $coverage->finalize(); + } + + $fileReport->source()->setSourceCode( + file_get_contents($file->pathAsString()) + ); + + $this->saveDocument($fileReport->asDom(), $file->id()); + } + + private function processUnit(array $unit, Report $report): void + { + if (isset($unit['className'])) { + $unitObject = $report->classObject($unit['className']); + } else { + $unitObject = $report->traitObject($unit['traitName']); + } + + $unitObject->setLines( + $unit['startLine'], + $unit['executableLines'], + $unit['executedLines'] + ); + + $unitObject->setCrap((float) $unit['crap']); + $unitObject->setNamespace($unit['namespace']); + + foreach ($unit['methods'] as $method) { + $methodObject = $unitObject->addMethod($method['methodName']); + $methodObject->setSignature($method['signature']); + $methodObject->setLines((string) $method['startLine'], (string) $method['endLine']); + $methodObject->setCrap($method['crap']); + $methodObject->setTotals( + (string) $method['executableLines'], + (string) $method['executedLines'], + (string) $method['coverage'] + ); + } + } + + private function processFunction(array $function, Report $report): void + { + $functionObject = $report->functionObject($function['functionName']); + + $functionObject->setSignature($function['signature']); + $functionObject->setLines((string) $function['startLine']); + $functionObject->setCrap($function['crap']); + $functionObject->setTotals((string) $function['executableLines'], (string) $function['executedLines'], (string) $function['coverage']); + } + + private function processTests(array $tests): void + { + $testsObject = $this->project->tests(); + + foreach ($tests as $test => $result) { + $testsObject->addTest($test, $result); + } + } + + private function setTotals(AbstractNode $node, Totals $totals): void + { + $loc = $node->linesOfCode(); + + $totals->setNumLines( + $loc['linesOfCode'], + $loc['commentLinesOfCode'], + $loc['nonCommentLinesOfCode'], + $node->numberOfExecutableLines(), + $node->numberOfExecutedLines() + ); + + $totals->setNumClasses( + $node->numberOfClasses(), + $node->numberOfTestedClasses() + ); + + $totals->setNumTraits( + $node->numberOfTraits(), + $node->numberOfTestedTraits() + ); + + $totals->setNumMethods( + $node->numberOfMethods(), + $node->numberOfTestedMethods() + ); + + $totals->setNumFunctions( + $node->numberOfFunctions(), + $node->numberOfTestedFunctions() + ); + } + + private function targetDirectory(): string + { + return $this->target; + } + + /** + * @throws XmlException + */ + private function saveDocument(DOMDocument $document, string $name): void + { + $filename = sprintf('%s/%s.xml', $this->targetDirectory(), $name); + + $document->formatOutput = true; + $document->preserveWhiteSpace = false; + $this->initTargetDirectory(dirname($filename)); + + file_put_contents($filename, $this->documentAsString($document)); + } + + /** + * @throws XmlException + * + * @see https://bugs.php.net/bug.php?id=79191 + */ + private function documentAsString(DOMDocument $document): string + { + $xmlErrorHandling = libxml_use_internal_errors(true); + $xml = $document->saveXML(); + + if ($xml === false) { + $message = 'Unable to generate the XML'; + + foreach (libxml_get_errors() as $error) { + $message .= PHP_EOL . $error->message; + } + + throw new XmlException($message); + } + + libxml_clear_errors(); + libxml_use_internal_errors($xmlErrorHandling); + + return $xml; + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Report/Xml/File.php b/vendor/phpunit/php-code-coverage/src/Report/Xml/File.php new file mode 100644 index 00000000..245c5cee --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Xml/File.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Report\Xml; + +use DOMDocument; +use DOMElement; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +class File +{ + /** + * @var DOMDocument + */ + private $dom; + + /** + * @var DOMElement + */ + private $contextNode; + + public function __construct(DOMElement $context) + { + $this->dom = $context->ownerDocument; + $this->contextNode = $context; + } + + public function totals(): Totals + { + $totalsContainer = $this->contextNode->firstChild; + + if (!$totalsContainer) { + $totalsContainer = $this->contextNode->appendChild( + $this->dom->createElementNS( + 'https://schema.phpunit.de/coverage/1.0', + 'totals' + ) + ); + } + + return new Totals($totalsContainer); + } + + public function lineCoverage(string $line): Coverage + { + $coverage = $this->contextNode->getElementsByTagNameNS( + 'https://schema.phpunit.de/coverage/1.0', + 'coverage' + )->item(0); + + if (!$coverage) { + $coverage = $this->contextNode->appendChild( + $this->dom->createElementNS( + 'https://schema.phpunit.de/coverage/1.0', + 'coverage' + ) + ); + } + + $lineNode = $coverage->appendChild( + $this->dom->createElementNS( + 'https://schema.phpunit.de/coverage/1.0', + 'line' + ) + ); + + return new Coverage($lineNode, $line); + } + + protected function contextNode(): DOMElement + { + return $this->contextNode; + } + + protected function dom(): DOMDocument + { + return $this->dom; + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Report/Xml/Method.php b/vendor/phpunit/php-code-coverage/src/Report/Xml/Method.php new file mode 100644 index 00000000..7e300999 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Xml/Method.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Report\Xml; + +use DOMElement; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class Method +{ + /** + * @var DOMElement + */ + private $contextNode; + + public function __construct(DOMElement $context, string $name) + { + $this->contextNode = $context; + + $this->setName($name); + } + + public function setSignature(string $signature): void + { + $this->contextNode->setAttribute('signature', $signature); + } + + public function setLines(string $start, ?string $end = null): void + { + $this->contextNode->setAttribute('start', $start); + + if ($end !== null) { + $this->contextNode->setAttribute('end', $end); + } + } + + public function setTotals(string $executable, string $executed, string $coverage): void + { + $this->contextNode->setAttribute('executable', $executable); + $this->contextNode->setAttribute('executed', $executed); + $this->contextNode->setAttribute('coverage', $coverage); + } + + public function setCrap(string $crap): void + { + $this->contextNode->setAttribute('crap', $crap); + } + + private function setName(string $name): void + { + $this->contextNode->setAttribute('name', $name); + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Report/Xml/Node.php b/vendor/phpunit/php-code-coverage/src/Report/Xml/Node.php new file mode 100644 index 00000000..15992309 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Xml/Node.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Report\Xml; + +use DOMDocument; +use DOMElement; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +abstract class Node +{ + /** + * @var DOMDocument + */ + private $dom; + + /** + * @var DOMElement + */ + private $contextNode; + + public function __construct(DOMElement $context) + { + $this->setContextNode($context); + } + + public function dom(): DOMDocument + { + return $this->dom; + } + + public function totals(): Totals + { + $totalsContainer = $this->contextNode()->firstChild; + + if (!$totalsContainer) { + $totalsContainer = $this->contextNode()->appendChild( + $this->dom->createElementNS( + 'https://schema.phpunit.de/coverage/1.0', + 'totals' + ) + ); + } + + return new Totals($totalsContainer); + } + + public function addDirectory(string $name): Directory + { + $dirNode = $this->dom()->createElementNS( + 'https://schema.phpunit.de/coverage/1.0', + 'directory' + ); + + $dirNode->setAttribute('name', $name); + $this->contextNode()->appendChild($dirNode); + + return new Directory($dirNode); + } + + public function addFile(string $name, string $href): File + { + $fileNode = $this->dom()->createElementNS( + 'https://schema.phpunit.de/coverage/1.0', + 'file' + ); + + $fileNode->setAttribute('name', $name); + $fileNode->setAttribute('href', $href); + $this->contextNode()->appendChild($fileNode); + + return new File($fileNode); + } + + protected function setContextNode(DOMElement $context): void + { + $this->dom = $context->ownerDocument; + $this->contextNode = $context; + } + + protected function contextNode(): DOMElement + { + return $this->contextNode; + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Report/Xml/Project.php b/vendor/phpunit/php-code-coverage/src/Report/Xml/Project.php new file mode 100644 index 00000000..b566bce0 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Xml/Project.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Report\Xml; + +use DOMDocument; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class Project extends Node +{ + public function __construct(string $directory) + { + $this->init(); + $this->setProjectSourceDirectory($directory); + } + + public function projectSourceDirectory(): string + { + return $this->contextNode()->getAttribute('source'); + } + + public function buildInformation(): BuildInformation + { + $buildNode = $this->dom()->getElementsByTagNameNS( + 'https://schema.phpunit.de/coverage/1.0', + 'build' + )->item(0); + + if (!$buildNode) { + $buildNode = $this->dom()->documentElement->appendChild( + $this->dom()->createElementNS( + 'https://schema.phpunit.de/coverage/1.0', + 'build' + ) + ); + } + + return new BuildInformation($buildNode); + } + + public function tests(): Tests + { + $testsNode = $this->contextNode()->getElementsByTagNameNS( + 'https://schema.phpunit.de/coverage/1.0', + 'tests' + )->item(0); + + if (!$testsNode) { + $testsNode = $this->contextNode()->appendChild( + $this->dom()->createElementNS( + 'https://schema.phpunit.de/coverage/1.0', + 'tests' + ) + ); + } + + return new Tests($testsNode); + } + + public function asDom(): DOMDocument + { + return $this->dom(); + } + + private function init(): void + { + $dom = new DOMDocument; + $dom->loadXML(''); + + $this->setContextNode( + $dom->getElementsByTagNameNS( + 'https://schema.phpunit.de/coverage/1.0', + 'project' + )->item(0) + ); + } + + private function setProjectSourceDirectory(string $name): void + { + $this->contextNode()->setAttribute('source', $name); + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Report/Xml/Report.php b/vendor/phpunit/php-code-coverage/src/Report/Xml/Report.php new file mode 100644 index 00000000..7f2badae --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Xml/Report.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Report\Xml; + +use function basename; +use function dirname; +use DOMDocument; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class Report extends File +{ + public function __construct(string $name) + { + $dom = new DOMDocument; + $dom->loadXML(''); + + $contextNode = $dom->getElementsByTagNameNS( + 'https://schema.phpunit.de/coverage/1.0', + 'file' + )->item(0); + + parent::__construct($contextNode); + + $this->setName($name); + } + + public function asDom(): DOMDocument + { + return $this->dom(); + } + + public function functionObject($name): Method + { + $node = $this->contextNode()->appendChild( + $this->dom()->createElementNS( + 'https://schema.phpunit.de/coverage/1.0', + 'function' + ) + ); + + return new Method($node, $name); + } + + public function classObject($name): Unit + { + return $this->unitObject('class', $name); + } + + public function traitObject($name): Unit + { + return $this->unitObject('trait', $name); + } + + public function source(): Source + { + $source = $this->contextNode()->getElementsByTagNameNS( + 'https://schema.phpunit.de/coverage/1.0', + 'source' + )->item(0); + + if (!$source) { + $source = $this->contextNode()->appendChild( + $this->dom()->createElementNS( + 'https://schema.phpunit.de/coverage/1.0', + 'source' + ) + ); + } + + return new Source($source); + } + + private function setName(string $name): void + { + $this->contextNode()->setAttribute('name', basename($name)); + $this->contextNode()->setAttribute('path', dirname($name)); + } + + private function unitObject(string $tagName, $name): Unit + { + $node = $this->contextNode()->appendChild( + $this->dom()->createElementNS( + 'https://schema.phpunit.de/coverage/1.0', + $tagName + ) + ); + + return new Unit($node, $name); + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Report/Xml/Source.php b/vendor/phpunit/php-code-coverage/src/Report/Xml/Source.php new file mode 100644 index 00000000..2b67ce1d --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Xml/Source.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Report\Xml; + +use DOMElement; +use TheSeer\Tokenizer\NamespaceUri; +use TheSeer\Tokenizer\Tokenizer; +use TheSeer\Tokenizer\XMLSerializer; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class Source +{ + /** @var DOMElement */ + private $context; + + public function __construct(DOMElement $context) + { + $this->context = $context; + } + + public function setSourceCode(string $source): void + { + $context = $this->context; + + $tokens = (new Tokenizer)->parse($source); + $srcDom = (new XMLSerializer(new NamespaceUri($context->namespaceURI)))->toDom($tokens); + + $context->parentNode->replaceChild( + $context->ownerDocument->importNode($srcDom->documentElement, true), + $context + ); + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Report/Xml/Tests.php b/vendor/phpunit/php-code-coverage/src/Report/Xml/Tests.php new file mode 100644 index 00000000..c6da4145 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Xml/Tests.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Report\Xml; + +use DOMElement; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class Tests +{ + private $contextNode; + private $codeMap = [ + -1 => 'UNKNOWN', // PHPUnit_Runner_BaseTestRunner::STATUS_UNKNOWN + 0 => 'PASSED', // PHPUnit_Runner_BaseTestRunner::STATUS_PASSED + 1 => 'SKIPPED', // PHPUnit_Runner_BaseTestRunner::STATUS_SKIPPED + 2 => 'INCOMPLETE', // PHPUnit_Runner_BaseTestRunner::STATUS_INCOMPLETE + 3 => 'FAILURE', // PHPUnit_Runner_BaseTestRunner::STATUS_FAILURE + 4 => 'ERROR', // PHPUnit_Runner_BaseTestRunner::STATUS_ERROR + 5 => 'RISKY', // PHPUnit_Runner_BaseTestRunner::STATUS_RISKY + 6 => 'WARNING', // PHPUnit_Runner_BaseTestRunner::STATUS_WARNING + ]; + + public function __construct(DOMElement $context) + { + $this->contextNode = $context; + } + + public function addTest(string $test, array $result): void + { + $node = $this->contextNode->appendChild( + $this->contextNode->ownerDocument->createElementNS( + 'https://schema.phpunit.de/coverage/1.0', + 'test' + ) + ); + + $node->setAttribute('name', $test); + $node->setAttribute('size', $result['size']); + $node->setAttribute('result', (string) $result['status']); + $node->setAttribute('status', $this->codeMap[(int) $result['status']]); + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Report/Xml/Totals.php b/vendor/phpunit/php-code-coverage/src/Report/Xml/Totals.php new file mode 100644 index 00000000..37081318 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Xml/Totals.php @@ -0,0 +1,146 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Report\Xml; + +use function sprintf; +use DOMElement; +use DOMNode; +use SebastianBergmann\CodeCoverage\Util\Percentage; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class Totals +{ + /** + * @var DOMNode + */ + private $container; + + /** + * @var DOMElement + */ + private $linesNode; + + /** + * @var DOMElement + */ + private $methodsNode; + + /** + * @var DOMElement + */ + private $functionsNode; + + /** + * @var DOMElement + */ + private $classesNode; + + /** + * @var DOMElement + */ + private $traitsNode; + + public function __construct(DOMElement $container) + { + $this->container = $container; + $dom = $container->ownerDocument; + + $this->linesNode = $dom->createElementNS( + 'https://schema.phpunit.de/coverage/1.0', + 'lines' + ); + + $this->methodsNode = $dom->createElementNS( + 'https://schema.phpunit.de/coverage/1.0', + 'methods' + ); + + $this->functionsNode = $dom->createElementNS( + 'https://schema.phpunit.de/coverage/1.0', + 'functions' + ); + + $this->classesNode = $dom->createElementNS( + 'https://schema.phpunit.de/coverage/1.0', + 'classes' + ); + + $this->traitsNode = $dom->createElementNS( + 'https://schema.phpunit.de/coverage/1.0', + 'traits' + ); + + $container->appendChild($this->linesNode); + $container->appendChild($this->methodsNode); + $container->appendChild($this->functionsNode); + $container->appendChild($this->classesNode); + $container->appendChild($this->traitsNode); + } + + public function container(): DOMNode + { + return $this->container; + } + + public function setNumLines(int $loc, int $cloc, int $ncloc, int $executable, int $executed): void + { + $this->linesNode->setAttribute('total', (string) $loc); + $this->linesNode->setAttribute('comments', (string) $cloc); + $this->linesNode->setAttribute('code', (string) $ncloc); + $this->linesNode->setAttribute('executable', (string) $executable); + $this->linesNode->setAttribute('executed', (string) $executed); + $this->linesNode->setAttribute( + 'percent', + $executable === 0 ? '0' : sprintf('%01.2F', Percentage::fromFractionAndTotal($executed, $executable)->asFloat()) + ); + } + + public function setNumClasses(int $count, int $tested): void + { + $this->classesNode->setAttribute('count', (string) $count); + $this->classesNode->setAttribute('tested', (string) $tested); + $this->classesNode->setAttribute( + 'percent', + $count === 0 ? '0' : sprintf('%01.2F', Percentage::fromFractionAndTotal($tested, $count)->asFloat()) + ); + } + + public function setNumTraits(int $count, int $tested): void + { + $this->traitsNode->setAttribute('count', (string) $count); + $this->traitsNode->setAttribute('tested', (string) $tested); + $this->traitsNode->setAttribute( + 'percent', + $count === 0 ? '0' : sprintf('%01.2F', Percentage::fromFractionAndTotal($tested, $count)->asFloat()) + ); + } + + public function setNumMethods(int $count, int $tested): void + { + $this->methodsNode->setAttribute('count', (string) $count); + $this->methodsNode->setAttribute('tested', (string) $tested); + $this->methodsNode->setAttribute( + 'percent', + $count === 0 ? '0' : sprintf('%01.2F', Percentage::fromFractionAndTotal($tested, $count)->asFloat()) + ); + } + + public function setNumFunctions(int $count, int $tested): void + { + $this->functionsNode->setAttribute('count', (string) $count); + $this->functionsNode->setAttribute('tested', (string) $tested); + $this->functionsNode->setAttribute( + 'percent', + $count === 0 ? '0' : sprintf('%01.2F', Percentage::fromFractionAndTotal($tested, $count)->asFloat()) + ); + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Report/Xml/Unit.php b/vendor/phpunit/php-code-coverage/src/Report/Xml/Unit.php new file mode 100644 index 00000000..d84dc481 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Xml/Unit.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Report\Xml; + +use DOMElement; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class Unit +{ + /** + * @var DOMElement + */ + private $contextNode; + + public function __construct(DOMElement $context, string $name) + { + $this->contextNode = $context; + + $this->setName($name); + } + + public function setLines(int $start, int $executable, int $executed): void + { + $this->contextNode->setAttribute('start', (string) $start); + $this->contextNode->setAttribute('executable', (string) $executable); + $this->contextNode->setAttribute('executed', (string) $executed); + } + + public function setCrap(float $crap): void + { + $this->contextNode->setAttribute('crap', (string) $crap); + } + + public function setNamespace(string $namespace): void + { + $node = $this->contextNode->getElementsByTagNameNS( + 'https://schema.phpunit.de/coverage/1.0', + 'namespace' + )->item(0); + + if (!$node) { + $node = $this->contextNode->appendChild( + $this->contextNode->ownerDocument->createElementNS( + 'https://schema.phpunit.de/coverage/1.0', + 'namespace' + ) + ); + } + + $node->setAttribute('name', $namespace); + } + + public function addMethod(string $name): Method + { + $node = $this->contextNode->appendChild( + $this->contextNode->ownerDocument->createElementNS( + 'https://schema.phpunit.de/coverage/1.0', + 'method' + ) + ); + + return new Method($node, $name); + } + + private function setName(string $name): void + { + $this->contextNode->setAttribute('name', $name); + } +} diff --git a/vendor/phpunit/php-code-coverage/src/StaticAnalysis/CacheWarmer.php b/vendor/phpunit/php-code-coverage/src/StaticAnalysis/CacheWarmer.php new file mode 100644 index 00000000..6f8a0449 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/StaticAnalysis/CacheWarmer.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\StaticAnalysis; + +use SebastianBergmann\CodeCoverage\Filter; + +final class CacheWarmer +{ + public function warmCache(string $cacheDirectory, bool $useAnnotationsForIgnoringCode, bool $ignoreDeprecatedCode, Filter $filter): void + { + $analyser = new CachingFileAnalyser( + $cacheDirectory, + new ParsingFileAnalyser( + $useAnnotationsForIgnoringCode, + $ignoreDeprecatedCode + ), + $useAnnotationsForIgnoringCode, + $ignoreDeprecatedCode, + ); + + foreach ($filter->files() as $file) { + $analyser->process($file); + } + } +} diff --git a/vendor/phpunit/php-code-coverage/src/StaticAnalysis/CachingFileAnalyser.php b/vendor/phpunit/php-code-coverage/src/StaticAnalysis/CachingFileAnalyser.php new file mode 100644 index 00000000..63e6e22b --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/StaticAnalysis/CachingFileAnalyser.php @@ -0,0 +1,209 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\StaticAnalysis; + +use function file_get_contents; +use function file_put_contents; +use function implode; +use function is_file; +use function md5; +use function serialize; +use function unserialize; +use SebastianBergmann\CodeCoverage\Util\Filesystem; +use SebastianBergmann\FileIterator\Facade as FileIteratorFacade; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class CachingFileAnalyser implements FileAnalyser +{ + /** + * @var ?string + */ + private static $cacheVersion; + + /** + * @var string + */ + private $directory; + + /** + * @var FileAnalyser + */ + private $analyser; + + /** + * @var bool + */ + private $useAnnotationsForIgnoringCode; + + /** + * @var bool + */ + private $ignoreDeprecatedCode; + + /** + * @var array + */ + private $cache = []; + + public function __construct(string $directory, FileAnalyser $analyser, bool $useAnnotationsForIgnoringCode, bool $ignoreDeprecatedCode) + { + Filesystem::createDirectory($directory); + + $this->analyser = $analyser; + $this->directory = $directory; + $this->useAnnotationsForIgnoringCode = $useAnnotationsForIgnoringCode; + $this->ignoreDeprecatedCode = $ignoreDeprecatedCode; + } + + public function classesIn(string $filename): array + { + if (!isset($this->cache[$filename])) { + $this->process($filename); + } + + return $this->cache[$filename]['classesIn']; + } + + public function traitsIn(string $filename): array + { + if (!isset($this->cache[$filename])) { + $this->process($filename); + } + + return $this->cache[$filename]['traitsIn']; + } + + public function functionsIn(string $filename): array + { + if (!isset($this->cache[$filename])) { + $this->process($filename); + } + + return $this->cache[$filename]['functionsIn']; + } + + /** + * @psalm-return array{linesOfCode: int, commentLinesOfCode: int, nonCommentLinesOfCode: int} + */ + public function linesOfCodeFor(string $filename): array + { + if (!isset($this->cache[$filename])) { + $this->process($filename); + } + + return $this->cache[$filename]['linesOfCodeFor']; + } + + public function executableLinesIn(string $filename): array + { + if (!isset($this->cache[$filename])) { + $this->process($filename); + } + + return $this->cache[$filename]['executableLinesIn']; + } + + public function ignoredLinesFor(string $filename): array + { + if (!isset($this->cache[$filename])) { + $this->process($filename); + } + + return $this->cache[$filename]['ignoredLinesFor']; + } + + public function process(string $filename): void + { + $cache = $this->read($filename); + + if ($cache !== false) { + $this->cache[$filename] = $cache; + + return; + } + + $this->cache[$filename] = [ + 'classesIn' => $this->analyser->classesIn($filename), + 'traitsIn' => $this->analyser->traitsIn($filename), + 'functionsIn' => $this->analyser->functionsIn($filename), + 'linesOfCodeFor' => $this->analyser->linesOfCodeFor($filename), + 'ignoredLinesFor' => $this->analyser->ignoredLinesFor($filename), + 'executableLinesIn' => $this->analyser->executableLinesIn($filename), + ]; + + $this->write($filename, $this->cache[$filename]); + } + + /** + * @return mixed + */ + private function read(string $filename) + { + $cacheFile = $this->cacheFile($filename); + + if (!is_file($cacheFile)) { + return false; + } + + return unserialize( + file_get_contents($cacheFile), + ['allowed_classes' => false] + ); + } + + /** + * @param mixed $data + */ + private function write(string $filename, $data): void + { + file_put_contents( + $this->cacheFile($filename), + serialize($data) + ); + } + + private function cacheFile(string $filename): string + { + $cacheKey = md5( + implode( + "\0", + [ + $filename, + file_get_contents($filename), + self::cacheVersion(), + $this->useAnnotationsForIgnoringCode, + $this->ignoreDeprecatedCode, + ] + ) + ); + + return $this->directory . DIRECTORY_SEPARATOR . $cacheKey; + } + + private static function cacheVersion(): string + { + if (self::$cacheVersion !== null) { + return self::$cacheVersion; + } + + $buffer = []; + + foreach ((new FileIteratorFacade)->getFilesAsArray(__DIR__, '.php') as $file) { + $buffer[] = $file; + $buffer[] = file_get_contents($file); + } + + self::$cacheVersion = md5(implode("\0", $buffer)); + + return self::$cacheVersion; + } +} diff --git a/vendor/phpunit/php-code-coverage/src/StaticAnalysis/CodeUnitFindingVisitor.php b/vendor/phpunit/php-code-coverage/src/StaticAnalysis/CodeUnitFindingVisitor.php new file mode 100644 index 00000000..cb85cd61 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/StaticAnalysis/CodeUnitFindingVisitor.php @@ -0,0 +1,345 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\StaticAnalysis; + +use function assert; +use function implode; +use function rtrim; +use function trim; +use PhpParser\Node; +use PhpParser\Node\ComplexType; +use PhpParser\Node\Identifier; +use PhpParser\Node\IntersectionType; +use PhpParser\Node\Name; +use PhpParser\Node\NullableType; +use PhpParser\Node\Stmt\Class_; +use PhpParser\Node\Stmt\ClassMethod; +use PhpParser\Node\Stmt\Enum_; +use PhpParser\Node\Stmt\Function_; +use PhpParser\Node\Stmt\Interface_; +use PhpParser\Node\Stmt\Trait_; +use PhpParser\Node\UnionType; +use PhpParser\NodeAbstract; +use PhpParser\NodeTraverser; +use PhpParser\NodeVisitorAbstract; +use SebastianBergmann\Complexity\CyclomaticComplexityCalculatingVisitor; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class CodeUnitFindingVisitor extends NodeVisitorAbstract +{ + /** + * @psalm-var array}> + */ + private $classes = []; + + /** + * @psalm-var array}> + */ + private $traits = []; + + /** + * @psalm-var array + */ + private $functions = []; + + public function enterNode(Node $node): void + { + if ($node instanceof Class_) { + if ($node->isAnonymous()) { + return; + } + + $this->processClass($node); + } + + if ($node instanceof Trait_) { + $this->processTrait($node); + } + + if (!$node instanceof ClassMethod && !$node instanceof Function_) { + return; + } + + if ($node instanceof ClassMethod) { + $parentNode = $node->getAttribute('parent'); + + if ($parentNode instanceof Class_ && $parentNode->isAnonymous()) { + return; + } + + $this->processMethod($node); + + return; + } + + $this->processFunction($node); + } + + /** + * @psalm-return array}> + */ + public function classes(): array + { + return $this->classes; + } + + /** + * @psalm-return array}> + */ + public function traits(): array + { + return $this->traits; + } + + /** + * @psalm-return array + */ + public function functions(): array + { + return $this->functions; + } + + /** + * @psalm-param ClassMethod|Function_ $node + */ + private function cyclomaticComplexity(Node $node): int + { + assert($node instanceof ClassMethod || $node instanceof Function_); + + $nodes = $node->getStmts(); + + if ($nodes === null) { + return 0; + } + + $traverser = new NodeTraverser; + + $cyclomaticComplexityCalculatingVisitor = new CyclomaticComplexityCalculatingVisitor; + + $traverser->addVisitor($cyclomaticComplexityCalculatingVisitor); + + /* @noinspection UnusedFunctionResultInspection */ + $traverser->traverse($nodes); + + return $cyclomaticComplexityCalculatingVisitor->cyclomaticComplexity(); + } + + /** + * @psalm-param ClassMethod|Function_ $node + */ + private function signature(Node $node): string + { + assert($node instanceof ClassMethod || $node instanceof Function_); + + $signature = ($node->returnsByRef() ? '&' : '') . $node->name->toString() . '('; + $parameters = []; + + foreach ($node->getParams() as $parameter) { + assert(isset($parameter->var->name)); + + $parameterAsString = ''; + + if ($parameter->type !== null) { + $parameterAsString = $this->type($parameter->type) . ' '; + } + + $parameterAsString .= '$' . $parameter->var->name; + + /* @todo Handle default values */ + + $parameters[] = $parameterAsString; + } + + $signature .= implode(', ', $parameters) . ')'; + + $returnType = $node->getReturnType(); + + if ($returnType !== null) { + $signature .= ': ' . $this->type($returnType); + } + + return $signature; + } + + /** + * @psalm-param Identifier|Name|ComplexType $type + */ + private function type(Node $type): string + { + assert($type instanceof Identifier || $type instanceof Name || $type instanceof ComplexType); + + if ($type instanceof NullableType) { + return '?' . $type->type; + } + + if ($type instanceof UnionType) { + return $this->unionTypeAsString($type); + } + + if ($type instanceof IntersectionType) { + return $this->intersectionTypeAsString($type); + } + + return $type->toString(); + } + + private function visibility(ClassMethod $node): string + { + if ($node->isPrivate()) { + return 'private'; + } + + if ($node->isProtected()) { + return 'protected'; + } + + return 'public'; + } + + private function processClass(Class_ $node): void + { + $name = $node->name->toString(); + $namespacedName = $node->namespacedName->toString(); + + $this->classes[$namespacedName] = [ + 'name' => $name, + 'namespacedName' => $namespacedName, + 'namespace' => $this->namespace($namespacedName, $name), + 'startLine' => $node->getStartLine(), + 'endLine' => $node->getEndLine(), + 'methods' => [], + ]; + } + + private function processTrait(Trait_ $node): void + { + $name = $node->name->toString(); + $namespacedName = $node->namespacedName->toString(); + + $this->traits[$namespacedName] = [ + 'name' => $name, + 'namespacedName' => $namespacedName, + 'namespace' => $this->namespace($namespacedName, $name), + 'startLine' => $node->getStartLine(), + 'endLine' => $node->getEndLine(), + 'methods' => [], + ]; + } + + private function processMethod(ClassMethod $node): void + { + $parentNode = $node->getAttribute('parent'); + + if ($parentNode instanceof Interface_) { + return; + } + + assert($parentNode instanceof Class_ || $parentNode instanceof Trait_ || $parentNode instanceof Enum_); + assert(isset($parentNode->name)); + assert(isset($parentNode->namespacedName)); + assert($parentNode->namespacedName instanceof Name); + + $parentName = $parentNode->name->toString(); + $parentNamespacedName = $parentNode->namespacedName->toString(); + + if ($parentNode instanceof Class_) { + $storage = &$this->classes; + } else { + $storage = &$this->traits; + } + + if (!isset($storage[$parentNamespacedName])) { + $storage[$parentNamespacedName] = [ + 'name' => $parentName, + 'namespacedName' => $parentNamespacedName, + 'namespace' => $this->namespace($parentNamespacedName, $parentName), + 'startLine' => $parentNode->getStartLine(), + 'endLine' => $parentNode->getEndLine(), + 'methods' => [], + ]; + } + + $storage[$parentNamespacedName]['methods'][$node->name->toString()] = [ + 'methodName' => $node->name->toString(), + 'signature' => $this->signature($node), + 'visibility' => $this->visibility($node), + 'startLine' => $node->getStartLine(), + 'endLine' => $node->getEndLine(), + 'ccn' => $this->cyclomaticComplexity($node), + ]; + } + + private function processFunction(Function_ $node): void + { + assert(isset($node->name)); + assert(isset($node->namespacedName)); + assert($node->namespacedName instanceof Name); + + $name = $node->name->toString(); + $namespacedName = $node->namespacedName->toString(); + + $this->functions[$namespacedName] = [ + 'name' => $name, + 'namespacedName' => $namespacedName, + 'namespace' => $this->namespace($namespacedName, $name), + 'signature' => $this->signature($node), + 'startLine' => $node->getStartLine(), + 'endLine' => $node->getEndLine(), + 'ccn' => $this->cyclomaticComplexity($node), + ]; + } + + private function namespace(string $namespacedName, string $name): string + { + return trim(rtrim($namespacedName, $name), '\\'); + } + + private function unionTypeAsString(UnionType $node): string + { + $types = []; + + foreach ($node->types as $type) { + if ($type instanceof IntersectionType) { + $types[] = '(' . $this->intersectionTypeAsString($type) . ')'; + + continue; + } + + $types[] = $this->typeAsString($type); + } + + return implode('|', $types); + } + + private function intersectionTypeAsString(IntersectionType $node): string + { + $types = []; + + foreach ($node->types as $type) { + $types[] = $this->typeAsString($type); + } + + return implode('&', $types); + } + + /** + * @psalm-param Identifier|Name $node $node + */ + private function typeAsString(NodeAbstract $node): string + { + if ($node instanceof Name) { + return $node->toCodeString(); + } + + return $node->toString(); + } +} diff --git a/vendor/phpunit/php-code-coverage/src/StaticAnalysis/ExecutableLinesFindingVisitor.php b/vendor/phpunit/php-code-coverage/src/StaticAnalysis/ExecutableLinesFindingVisitor.php new file mode 100644 index 00000000..38e64c0b --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/StaticAnalysis/ExecutableLinesFindingVisitor.php @@ -0,0 +1,390 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\StaticAnalysis; + +use function array_diff_key; +use function assert; +use function count; +use function current; +use function end; +use function explode; +use function max; +use function preg_match; +use function preg_quote; +use function range; +use function reset; +use function sprintf; +use PhpParser\Node; +use PhpParser\NodeVisitorAbstract; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class ExecutableLinesFindingVisitor extends NodeVisitorAbstract +{ + /** + * @var int + */ + private $nextBranch = 0; + + /** + * @var string + */ + private $source; + + /** + * @var array + */ + private $executableLinesGroupedByBranch = []; + + /** + * @var array + */ + private $unsets = []; + + /** + * @var array + */ + private $commentsToCheckForUnset = []; + + public function __construct(string $source) + { + $this->source = $source; + } + + public function enterNode(Node $node): void + { + foreach ($node->getComments() as $comment) { + $commentLine = $comment->getStartLine(); + + if (!isset($this->executableLinesGroupedByBranch[$commentLine])) { + continue; + } + + foreach (explode("\n", $comment->getText()) as $text) { + $this->commentsToCheckForUnset[$commentLine] = $text; + $commentLine++; + } + } + + if ($node instanceof Node\Scalar\String_ || + $node instanceof Node\Scalar\EncapsedStringPart) { + $startLine = $node->getStartLine() + 1; + $endLine = $node->getEndLine() - 1; + + if ($startLine <= $endLine) { + foreach (range($startLine, $endLine) as $line) { + unset($this->executableLinesGroupedByBranch[$line]); + } + } + + return; + } + + if ($node instanceof Node\Stmt\Interface_) { + foreach (range($node->getStartLine(), $node->getEndLine()) as $line) { + $this->unsets[$line] = true; + } + + return; + } + + if ($node instanceof Node\Stmt\Declare_ || + $node instanceof Node\Stmt\DeclareDeclare || + $node instanceof Node\Stmt\Else_ || + $node instanceof Node\Stmt\EnumCase || + $node instanceof Node\Stmt\Finally_ || + $node instanceof Node\Stmt\GroupUse || + $node instanceof Node\Stmt\Label || + $node instanceof Node\Stmt\Namespace_ || + $node instanceof Node\Stmt\Nop || + $node instanceof Node\Stmt\Switch_ || + $node instanceof Node\Stmt\TryCatch || + $node instanceof Node\Stmt\Use_ || + $node instanceof Node\Stmt\UseUse || + $node instanceof Node\Expr\ConstFetch || + $node instanceof Node\Expr\Match_ || + $node instanceof Node\Expr\Variable || + $node instanceof Node\Expr\Throw_ || + $node instanceof Node\ComplexType || + $node instanceof Node\Const_ || + $node instanceof Node\Identifier || + $node instanceof Node\Name || + $node instanceof Node\Param || + $node instanceof Node\Scalar) { + return; + } + + /* + * nikic/php-parser ^4.18 represents throw statements + * as Stmt\Throw_ objects + */ + if ($node instanceof Node\Stmt\Throw_) { + $this->setLineBranch($node->expr->getEndLine(), $node->expr->getEndLine(), ++$this->nextBranch); + + return; + } + + /* + * nikic/php-parser ^5 represents throw statements + * as Stmt\Expression objects that contain an + * Expr\Throw_ object + */ + if ($node instanceof Node\Stmt\Expression && $node->expr instanceof Node\Expr\Throw_) { + $this->setLineBranch($node->expr->expr->getEndLine(), $node->expr->expr->getEndLine(), ++$this->nextBranch); + + return; + } + + if ($node instanceof Node\Stmt\Enum_ || + $node instanceof Node\Stmt\Function_ || + $node instanceof Node\Stmt\Class_ || + $node instanceof Node\Stmt\ClassMethod || + $node instanceof Node\Expr\Closure || + $node instanceof Node\Stmt\Trait_) { + $isConcreteClassLike = $node instanceof Node\Stmt\Enum_ || $node instanceof Node\Stmt\Class_ || $node instanceof Node\Stmt\Trait_; + + if (null !== $node->stmts) { + foreach ($node->stmts as $stmt) { + if ($stmt instanceof Node\Stmt\Nop) { + continue; + } + + foreach (range($stmt->getStartLine(), $stmt->getEndLine()) as $line) { + unset($this->executableLinesGroupedByBranch[$line]); + + if ( + $isConcreteClassLike && + !$stmt instanceof Node\Stmt\ClassMethod + ) { + $this->unsets[$line] = true; + } + } + } + } + + if ($isConcreteClassLike) { + return; + } + + $hasEmptyBody = [] === $node->stmts || + null === $node->stmts || + ( + 1 === count($node->stmts) && + $node->stmts[0] instanceof Node\Stmt\Nop + ); + + if ($hasEmptyBody) { + if ($node->getEndLine() === $node->getStartLine()) { + return; + } + + $this->setLineBranch($node->getEndLine(), $node->getEndLine(), ++$this->nextBranch); + + return; + } + + return; + } + + if ($node instanceof Node\Expr\ArrowFunction) { + $startLine = max( + $node->getStartLine() + 1, + $node->expr->getStartLine() + ); + + $endLine = $node->expr->getEndLine(); + + if ($endLine < $startLine) { + return; + } + + $this->setLineBranch($startLine, $endLine, ++$this->nextBranch); + + return; + } + + if ($node instanceof Node\Expr\Ternary) { + if (null !== $node->if && + $node->getStartLine() !== $node->if->getEndLine()) { + $this->setLineBranch($node->if->getStartLine(), $node->if->getEndLine(), ++$this->nextBranch); + } + + if ($node->getStartLine() !== $node->else->getEndLine()) { + $this->setLineBranch($node->else->getStartLine(), $node->else->getEndLine(), ++$this->nextBranch); + } + + return; + } + + if ($node instanceof Node\Expr\BinaryOp\Coalesce) { + if ($node->getStartLine() !== $node->getEndLine()) { + $this->setLineBranch($node->getEndLine(), $node->getEndLine(), ++$this->nextBranch); + } + + return; + } + + if ($node instanceof Node\Stmt\If_ || + $node instanceof Node\Stmt\ElseIf_ || + $node instanceof Node\Stmt\Case_) { + if (null === $node->cond) { + return; + } + + $this->setLineBranch( + $node->cond->getStartLine(), + $node->cond->getStartLine(), + ++$this->nextBranch + ); + + return; + } + + if ($node instanceof Node\Stmt\For_) { + $startLine = null; + $endLine = null; + + if ([] !== $node->init) { + $startLine = $node->init[0]->getStartLine(); + + end($node->init); + + $endLine = current($node->init)->getEndLine(); + + reset($node->init); + } + + if ([] !== $node->cond) { + if (null === $startLine) { + $startLine = $node->cond[0]->getStartLine(); + } + + end($node->cond); + + $endLine = current($node->cond)->getEndLine(); + + reset($node->cond); + } + + if ([] !== $node->loop) { + if (null === $startLine) { + $startLine = $node->loop[0]->getStartLine(); + } + + end($node->loop); + + $endLine = current($node->loop)->getEndLine(); + + reset($node->loop); + } + + if (null === $startLine || null === $endLine) { + return; + } + + $this->setLineBranch( + $startLine, + $endLine, + ++$this->nextBranch + ); + + return; + } + + if ($node instanceof Node\Stmt\Foreach_) { + $this->setLineBranch( + $node->expr->getStartLine(), + $node->valueVar->getEndLine(), + ++$this->nextBranch + ); + + return; + } + + if ($node instanceof Node\Stmt\While_ || + $node instanceof Node\Stmt\Do_) { + $this->setLineBranch( + $node->cond->getStartLine(), + $node->cond->getEndLine(), + ++$this->nextBranch + ); + + return; + } + + if ($node instanceof Node\Stmt\Catch_) { + assert([] !== $node->types); + $startLine = $node->types[0]->getStartLine(); + end($node->types); + $endLine = current($node->types)->getEndLine(); + + $this->setLineBranch( + $startLine, + $endLine, + ++$this->nextBranch + ); + + return; + } + + if ($node instanceof Node\Expr\CallLike) { + if (isset($this->executableLinesGroupedByBranch[$node->getStartLine()])) { + $branch = $this->executableLinesGroupedByBranch[$node->getStartLine()]; + } else { + $branch = ++$this->nextBranch; + } + + $this->setLineBranch($node->getStartLine(), $node->getEndLine(), $branch); + + return; + } + + if (isset($this->executableLinesGroupedByBranch[$node->getStartLine()])) { + return; + } + + $this->setLineBranch($node->getStartLine(), $node->getEndLine(), ++$this->nextBranch); + } + + public function afterTraverse(array $nodes): void + { + $lines = explode("\n", $this->source); + + foreach ($lines as $lineNumber => $line) { + $lineNumber++; + + if (1 === preg_match('/^\s*$/', $line) || + ( + isset($this->commentsToCheckForUnset[$lineNumber]) && + 1 === preg_match(sprintf('/^\s*%s\s*$/', preg_quote($this->commentsToCheckForUnset[$lineNumber], '/')), $line) + )) { + unset($this->executableLinesGroupedByBranch[$lineNumber]); + } + } + + $this->executableLinesGroupedByBranch = array_diff_key( + $this->executableLinesGroupedByBranch, + $this->unsets + ); + } + + public function executableLinesGroupedByBranch(): array + { + return $this->executableLinesGroupedByBranch; + } + + private function setLineBranch(int $start, int $end, int $branch): void + { + foreach (range($start, $end) as $line) { + $this->executableLinesGroupedByBranch[$line] = $branch; + } + } +} diff --git a/vendor/phpunit/php-code-coverage/src/StaticAnalysis/FileAnalyser.php b/vendor/phpunit/php-code-coverage/src/StaticAnalysis/FileAnalyser.php new file mode 100644 index 00000000..3dbcf68f --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/StaticAnalysis/FileAnalyser.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\StaticAnalysis; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +interface FileAnalyser +{ + public function classesIn(string $filename): array; + + public function traitsIn(string $filename): array; + + public function functionsIn(string $filename): array; + + /** + * @psalm-return array{linesOfCode: int, commentLinesOfCode: int, nonCommentLinesOfCode: int} + */ + public function linesOfCodeFor(string $filename): array; + + public function executableLinesIn(string $filename): array; + + public function ignoredLinesFor(string $filename): array; +} diff --git a/vendor/phpunit/php-code-coverage/src/StaticAnalysis/IgnoredLinesFindingVisitor.php b/vendor/phpunit/php-code-coverage/src/StaticAnalysis/IgnoredLinesFindingVisitor.php new file mode 100644 index 00000000..3c0b2373 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/StaticAnalysis/IgnoredLinesFindingVisitor.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\StaticAnalysis; + +use function array_merge; +use function assert; +use function range; +use function strpos; +use PhpParser\Node; +use PhpParser\Node\Attribute; +use PhpParser\Node\Stmt\Class_; +use PhpParser\Node\Stmt\ClassMethod; +use PhpParser\Node\Stmt\Function_; +use PhpParser\Node\Stmt\Interface_; +use PhpParser\Node\Stmt\Trait_; +use PhpParser\NodeVisitorAbstract; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class IgnoredLinesFindingVisitor extends NodeVisitorAbstract +{ + /** + * @psalm-var list + */ + private $ignoredLines = []; + + /** + * @var bool + */ + private $useAnnotationsForIgnoringCode; + + /** + * @var bool + */ + private $ignoreDeprecated; + + public function __construct(bool $useAnnotationsForIgnoringCode, bool $ignoreDeprecated) + { + $this->useAnnotationsForIgnoringCode = $useAnnotationsForIgnoringCode; + $this->ignoreDeprecated = $ignoreDeprecated; + } + + public function enterNode(Node $node): void + { + if (!$node instanceof Class_ && + !$node instanceof Trait_ && + !$node instanceof Interface_ && + !$node instanceof ClassMethod && + !$node instanceof Function_ && + !$node instanceof Attribute) { + return; + } + + if ($node instanceof Class_ && $node->isAnonymous()) { + return; + } + + if ($node instanceof Class_ || + $node instanceof Trait_ || + $node instanceof Interface_ || + $node instanceof Attribute) { + $this->ignoredLines[] = $node->getStartLine(); + + assert($node->name !== null); + + // Workaround for https://github.com/nikic/PHP-Parser/issues/886 + $this->ignoredLines[] = $node->name->getStartLine(); + } + + if (!$this->useAnnotationsForIgnoringCode) { + return; + } + + if ($node instanceof Interface_) { + return; + } + + $this->processDocComment($node); + } + + /** + * @psalm-return list + */ + public function ignoredLines(): array + { + return $this->ignoredLines; + } + + private function processDocComment(Node $node): void + { + $docComment = $node->getDocComment(); + + if ($docComment === null) { + return; + } + + if (strpos($docComment->getText(), '@codeCoverageIgnore') !== false) { + $this->ignoredLines = array_merge( + $this->ignoredLines, + range($node->getStartLine(), $node->getEndLine()) + ); + } + + if ($this->ignoreDeprecated && strpos($docComment->getText(), '@deprecated') !== false) { + $this->ignoredLines = array_merge( + $this->ignoredLines, + range($node->getStartLine(), $node->getEndLine()) + ); + } + } +} diff --git a/vendor/phpunit/php-code-coverage/src/StaticAnalysis/ParsingFileAnalyser.php b/vendor/phpunit/php-code-coverage/src/StaticAnalysis/ParsingFileAnalyser.php new file mode 100644 index 00000000..923b1f50 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/StaticAnalysis/ParsingFileAnalyser.php @@ -0,0 +1,249 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\StaticAnalysis; + +use function array_merge; +use function array_unique; +use function assert; +use function file_get_contents; +use function is_array; +use function max; +use function range; +use function sort; +use function sprintf; +use function substr_count; +use function token_get_all; +use function trim; +use PhpParser\Error; +use PhpParser\NodeTraverser; +use PhpParser\NodeVisitor\NameResolver; +use PhpParser\NodeVisitor\ParentConnectingVisitor; +use PhpParser\ParserFactory; +use SebastianBergmann\CodeCoverage\ParserException; +use SebastianBergmann\LinesOfCode\LineCountingVisitor; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class ParsingFileAnalyser implements FileAnalyser +{ + /** + * @var array + */ + private $classes = []; + + /** + * @var array + */ + private $traits = []; + + /** + * @var array + */ + private $functions = []; + + /** + * @var array + */ + private $linesOfCode = []; + + /** + * @var array + */ + private $ignoredLines = []; + + /** + * @var array + */ + private $executableLines = []; + + /** + * @var bool + */ + private $useAnnotationsForIgnoringCode; + + /** + * @var bool + */ + private $ignoreDeprecatedCode; + + public function __construct(bool $useAnnotationsForIgnoringCode, bool $ignoreDeprecatedCode) + { + $this->useAnnotationsForIgnoringCode = $useAnnotationsForIgnoringCode; + $this->ignoreDeprecatedCode = $ignoreDeprecatedCode; + } + + public function classesIn(string $filename): array + { + $this->analyse($filename); + + return $this->classes[$filename]; + } + + public function traitsIn(string $filename): array + { + $this->analyse($filename); + + return $this->traits[$filename]; + } + + public function functionsIn(string $filename): array + { + $this->analyse($filename); + + return $this->functions[$filename]; + } + + /** + * @psalm-return array{linesOfCode: int, commentLinesOfCode: int, nonCommentLinesOfCode: int} + */ + public function linesOfCodeFor(string $filename): array + { + $this->analyse($filename); + + return $this->linesOfCode[$filename]; + } + + public function executableLinesIn(string $filename): array + { + $this->analyse($filename); + + return $this->executableLines[$filename]; + } + + public function ignoredLinesFor(string $filename): array + { + $this->analyse($filename); + + return $this->ignoredLines[$filename]; + } + + /** + * @throws ParserException + */ + private function analyse(string $filename): void + { + if (isset($this->classes[$filename])) { + return; + } + + $source = file_get_contents($filename); + $linesOfCode = max(substr_count($source, "\n") + 1, substr_count($source, "\r") + 1); + + if ($linesOfCode === 0 && !empty($source)) { + $linesOfCode = 1; + } + + $parser = (new ParserFactory)->createForHostVersion(); + + try { + $nodes = $parser->parse($source); + + assert($nodes !== null); + + $traverser = new NodeTraverser; + $codeUnitFindingVisitor = new CodeUnitFindingVisitor; + $lineCountingVisitor = new LineCountingVisitor($linesOfCode); + $ignoredLinesFindingVisitor = new IgnoredLinesFindingVisitor($this->useAnnotationsForIgnoringCode, $this->ignoreDeprecatedCode); + $executableLinesFindingVisitor = new ExecutableLinesFindingVisitor($source); + + $traverser->addVisitor(new NameResolver); + $traverser->addVisitor(new ParentConnectingVisitor); + $traverser->addVisitor($codeUnitFindingVisitor); + $traverser->addVisitor($lineCountingVisitor); + $traverser->addVisitor($ignoredLinesFindingVisitor); + $traverser->addVisitor($executableLinesFindingVisitor); + + /* @noinspection UnusedFunctionResultInspection */ + $traverser->traverse($nodes); + // @codeCoverageIgnoreStart + } catch (Error $error) { + throw new ParserException( + sprintf( + 'Cannot parse %s: %s', + $filename, + $error->getMessage() + ), + $error->getCode(), + $error + ); + } + // @codeCoverageIgnoreEnd + + $this->classes[$filename] = $codeUnitFindingVisitor->classes(); + $this->traits[$filename] = $codeUnitFindingVisitor->traits(); + $this->functions[$filename] = $codeUnitFindingVisitor->functions(); + $this->executableLines[$filename] = $executableLinesFindingVisitor->executableLinesGroupedByBranch(); + $this->ignoredLines[$filename] = []; + + $this->findLinesIgnoredByLineBasedAnnotations($filename, $source, $this->useAnnotationsForIgnoringCode); + + $this->ignoredLines[$filename] = array_unique( + array_merge( + $this->ignoredLines[$filename], + $ignoredLinesFindingVisitor->ignoredLines() + ) + ); + + sort($this->ignoredLines[$filename]); + + $result = $lineCountingVisitor->result(); + + $this->linesOfCode[$filename] = [ + 'linesOfCode' => $result->linesOfCode(), + 'commentLinesOfCode' => $result->commentLinesOfCode(), + 'nonCommentLinesOfCode' => $result->nonCommentLinesOfCode(), + ]; + } + + private function findLinesIgnoredByLineBasedAnnotations(string $filename, string $source, bool $useAnnotationsForIgnoringCode): void + { + if (!$useAnnotationsForIgnoringCode) { + return; + } + + $start = false; + + foreach (token_get_all($source) as $token) { + if (!is_array($token) || + !(T_COMMENT === $token[0] || T_DOC_COMMENT === $token[0])) { + continue; + } + + $comment = trim($token[1]); + + if ($comment === '// @codeCoverageIgnore' || + $comment === '//@codeCoverageIgnore') { + $this->ignoredLines[$filename][] = $token[2]; + + continue; + } + + if ($comment === '// @codeCoverageIgnoreStart' || + $comment === '//@codeCoverageIgnoreStart') { + $start = $token[2]; + + continue; + } + + if ($comment === '// @codeCoverageIgnoreEnd' || + $comment === '//@codeCoverageIgnoreEnd') { + if (false === $start) { + $start = $token[2]; + } + + $this->ignoredLines[$filename] = array_merge( + $this->ignoredLines[$filename], + range($start, $token[2]) + ); + } + } + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Util/Filesystem.php b/vendor/phpunit/php-code-coverage/src/Util/Filesystem.php new file mode 100644 index 00000000..ff0e16ae --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Util/Filesystem.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Util; + +use function is_dir; +use function mkdir; +use function sprintf; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class Filesystem +{ + /** + * @throws DirectoryCouldNotBeCreatedException + */ + public static function createDirectory(string $directory): void + { + $success = !(!is_dir($directory) && !@mkdir($directory, 0777, true) && !is_dir($directory)); + + if (!$success) { + throw new DirectoryCouldNotBeCreatedException( + sprintf( + 'Directory "%s" could not be created', + $directory + ) + ); + } + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Util/Percentage.php b/vendor/phpunit/php-code-coverage/src/Util/Percentage.php new file mode 100644 index 00000000..0f7a3fec --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Util/Percentage.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Util; + +use function sprintf; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class Percentage +{ + /** + * @var float + */ + private $fraction; + + /** + * @var float + */ + private $total; + + public static function fromFractionAndTotal(float $fraction, float $total): self + { + return new self($fraction, $total); + } + + private function __construct(float $fraction, float $total) + { + $this->fraction = $fraction; + $this->total = $total; + } + + public function asFloat(): float + { + if ($this->total > 0) { + return ($this->fraction / $this->total) * 100; + } + + return 100.0; + } + + public function asString(): string + { + if ($this->total > 0) { + return sprintf('%01.2F%%', $this->asFloat()); + } + + return ''; + } + + public function asFixedWidthString(): string + { + if ($this->total > 0) { + return sprintf('%6.2F%%', $this->asFloat()); + } + + return ''; + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Version.php b/vendor/phpunit/php-code-coverage/src/Version.php new file mode 100644 index 00000000..1753a97b --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Version.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage; + +use function dirname; +use SebastianBergmann\Version as VersionId; + +final class Version +{ + /** + * @var string + */ + private static $version; + + public static function id(): string + { + if (self::$version === null) { + self::$version = (new VersionId('9.2.31', dirname(__DIR__)))->getVersion(); + } + + return self::$version; + } +} diff --git a/vendor/phpunit/php-file-iterator/.psalm/baseline.xml b/vendor/phpunit/php-file-iterator/.psalm/baseline.xml new file mode 100644 index 00000000..8b6cdc24 --- /dev/null +++ b/vendor/phpunit/php-file-iterator/.psalm/baseline.xml @@ -0,0 +1,8 @@ + + + + + current + + + diff --git a/vendor/phpunit/php-file-iterator/.psalm/config.xml b/vendor/phpunit/php-file-iterator/.psalm/config.xml new file mode 100644 index 00000000..2a4b16f2 --- /dev/null +++ b/vendor/phpunit/php-file-iterator/.psalm/config.xml @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/vendor/phpunit/php-file-iterator/ChangeLog.md b/vendor/phpunit/php-file-iterator/ChangeLog.md new file mode 100644 index 00000000..44833762 --- /dev/null +++ b/vendor/phpunit/php-file-iterator/ChangeLog.md @@ -0,0 +1,144 @@ +# Change Log + +All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). + +## [3.0.6] - 2021-12-02 + +### Changed + +* [#73](https://github.com/sebastianbergmann/php-file-iterator/pull/73): Micro performance improvements on parsing paths + +## [3.0.5] - 2020-09-28 + +### Changed + +* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3` + +## [3.0.4] - 2020-07-11 + +### Fixed + +* [#67](https://github.com/sebastianbergmann/php-file-iterator/issues/67): `TypeError` in `SebastianBergmann\FileIterator\Iterator::accept()` + +## [3.0.3] - 2020-06-26 + +### Added + +* This component is now supported on PHP 8 + +## [3.0.2] - 2020-06-15 + +### Changed + +* Tests etc. are now ignored for archive exports + +## [3.0.1] - 2020-04-18 + +### Fixed + +* [#64](https://github.com/sebastianbergmann/php-file-iterator/issues/64): Release tarball contains Composer PHAR + +## [3.0.0] - 2020-02-07 + +### Removed + +* This component is no longer supported on PHP 7.1 and PHP 7.2 + +## [2.0.5] - 2021-12-02 + +### Changed + +* [#73](https://github.com/sebastianbergmann/php-file-iterator/pull/73): Micro performance improvements on parsing paths + +### Fixed + +* [#74](https://github.com/sebastianbergmann/php-file-iterator/pull/74): Document return type of `SebastianBergmann\FileIterator\Iterator::accept()` so that Symfony's `DebugClassLoader` does not trigger a deprecation warning + +## [2.0.4] - 2021-07-19 + +### Changed + +* Added `ReturnTypeWillChange` attribute to `SebastianBergmann\FileIterator\Iterator::accept()` because the return type of `\FilterIterator::accept()` will change in PHP 8.1 + +## [2.0.3] - 2020-11-30 + +### Changed + +* Changed PHP version constraint in `composer.json` from `^7.1` to `>=7.1` + +## [2.0.2] - 2018-09-13 + +### Fixed + +* [#48](https://github.com/sebastianbergmann/php-file-iterator/issues/48): Excluding an array that contains false ends up excluding the current working directory + +## [2.0.1] - 2018-06-11 + +### Fixed + +* [#46](https://github.com/sebastianbergmann/php-file-iterator/issues/46): Regression with hidden parent directory + +## [2.0.0] - 2018-05-28 + +### Fixed + +* [#30](https://github.com/sebastianbergmann/php-file-iterator/issues/30): Exclude is not considered if it is a parent of the base path + +### Changed + +* This component now uses namespaces + +### Removed + +* This component is no longer supported on PHP 5.3, PHP 5.4, PHP 5.5, PHP 5.6, and PHP 7.0 + +## [1.4.5] - 2017-11-27 + +### Fixed + +* [#37](https://github.com/sebastianbergmann/php-file-iterator/issues/37): Regression caused by fix for [#30](https://github.com/sebastianbergmann/php-file-iterator/issues/30) + +## [1.4.4] - 2017-11-27 + +### Fixed + +* [#30](https://github.com/sebastianbergmann/php-file-iterator/issues/30): Exclude is not considered if it is a parent of the base path + +## [1.4.3] - 2017-11-25 + +### Fixed + +* [#34](https://github.com/sebastianbergmann/php-file-iterator/issues/34): Factory should use canonical directory names + +## [1.4.2] - 2016-11-26 + +No changes + +## [1.4.1] - 2015-07-26 + +No changes + +## 1.4.0 - 2015-04-02 + +### Added + +* [#23](https://github.com/sebastianbergmann/php-file-iterator/pull/23): Added support for wildcards (glob) in exclude + +[3.0.6]: https://github.com/sebastianbergmann/php-file-iterator/compare/3.0.5...3.0.6 +[3.0.5]: https://github.com/sebastianbergmann/php-file-iterator/compare/3.0.4...3.0.5 +[3.0.4]: https://github.com/sebastianbergmann/php-file-iterator/compare/3.0.3...3.0.4 +[3.0.3]: https://github.com/sebastianbergmann/php-file-iterator/compare/3.0.2...3.0.3 +[3.0.2]: https://github.com/sebastianbergmann/php-file-iterator/compare/3.0.1...3.0.2 +[3.0.1]: https://github.com/sebastianbergmann/php-file-iterator/compare/3.0.0...3.0.1 +[3.0.0]: https://github.com/sebastianbergmann/php-file-iterator/compare/2.0.5...3.0.0 +[2.0.5]: https://github.com/sebastianbergmann/php-file-iterator/compare/2.0.4...2.0.5 +[2.0.4]: https://github.com/sebastianbergmann/php-file-iterator/compare/2.0.3...2.0.4 +[2.0.3]: https://github.com/sebastianbergmann/php-file-iterator/compare/2.0.2...2.0.3 +[2.0.2]: https://github.com/sebastianbergmann/php-file-iterator/compare/2.0.1...2.0.2 +[2.0.1]: https://github.com/sebastianbergmann/php-file-iterator/compare/2.0.0...2.0.1 +[2.0.0]: https://github.com/sebastianbergmann/php-file-iterator/compare/1.4.5...2.0.0 +[1.4.5]: https://github.com/sebastianbergmann/php-file-iterator/compare/1.4.4...1.4.5 +[1.4.4]: https://github.com/sebastianbergmann/php-file-iterator/compare/1.4.3...1.4.4 +[1.4.3]: https://github.com/sebastianbergmann/php-file-iterator/compare/1.4.2...1.4.3 +[1.4.2]: https://github.com/sebastianbergmann/php-file-iterator/compare/1.4.1...1.4.2 +[1.4.1]: https://github.com/sebastianbergmann/php-file-iterator/compare/1.4.0...1.4.1 diff --git a/vendor/phpunit/php-file-iterator/LICENSE b/vendor/phpunit/php-file-iterator/LICENSE new file mode 100644 index 00000000..51db9163 --- /dev/null +++ b/vendor/phpunit/php-file-iterator/LICENSE @@ -0,0 +1,33 @@ +php-file-iterator + +Copyright (c) 2009-2021, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/phpunit/php-file-iterator/README.md b/vendor/phpunit/php-file-iterator/README.md new file mode 100644 index 00000000..3cbfdaae --- /dev/null +++ b/vendor/phpunit/php-file-iterator/README.md @@ -0,0 +1,14 @@ +[![Build Status](https://travis-ci.org/sebastianbergmann/php-file-iterator.svg?branch=master)](https://travis-ci.org/sebastianbergmann/php-file-iterator) + +# php-file-iterator + +## Installation + +You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): + + composer require phpunit/php-file-iterator + +If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency: + + composer require --dev phpunit/php-file-iterator + diff --git a/vendor/phpunit/php-file-iterator/composer.json b/vendor/phpunit/php-file-iterator/composer.json new file mode 100644 index 00000000..f1b95b3f --- /dev/null +++ b/vendor/phpunit/php-file-iterator/composer.json @@ -0,0 +1,45 @@ +{ + "name": "phpunit/php-file-iterator", + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "type": "library", + "keywords": [ + "iterator", + "filesystem" + ], + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues" + }, + "config": { + "platform": { + "php": "7.3.0" + }, + "optimize-autoloader": true, + "sort-packages": true + }, + "prefer-stable": true, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + } +} diff --git a/vendor/phpunit/php-file-iterator/src/Facade.php b/vendor/phpunit/php-file-iterator/src/Facade.php new file mode 100644 index 00000000..87b6588d --- /dev/null +++ b/vendor/phpunit/php-file-iterator/src/Facade.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\FileIterator; + +use const DIRECTORY_SEPARATOR; +use function array_unique; +use function count; +use function dirname; +use function explode; +use function is_file; +use function is_string; +use function realpath; +use function sort; + +class Facade +{ + /** + * @param array|string $paths + * @param array|string $suffixes + * @param array|string $prefixes + */ + public function getFilesAsArray($paths, $suffixes = '', $prefixes = '', array $exclude = [], bool $commonPath = false): array + { + if (is_string($paths)) { + $paths = [$paths]; + } + + $iterator = (new Factory)->getFileIterator($paths, $suffixes, $prefixes, $exclude); + + $files = []; + + foreach ($iterator as $file) { + $file = $file->getRealPath(); + + if ($file) { + $files[] = $file; + } + } + + foreach ($paths as $path) { + if (is_file($path)) { + $files[] = realpath($path); + } + } + + $files = array_unique($files); + sort($files); + + if ($commonPath) { + return [ + 'commonPath' => $this->getCommonPath($files), + 'files' => $files, + ]; + } + + return $files; + } + + protected function getCommonPath(array $files): string + { + $count = count($files); + + if ($count === 0) { + return ''; + } + + if ($count === 1) { + return dirname($files[0]) . DIRECTORY_SEPARATOR; + } + + $_files = []; + + foreach ($files as $file) { + $_files[] = $_fileParts = explode(DIRECTORY_SEPARATOR, $file); + + if (empty($_fileParts[0])) { + $_fileParts[0] = DIRECTORY_SEPARATOR; + } + } + + $common = ''; + $done = false; + $j = 0; + $count--; + + while (!$done) { + for ($i = 0; $i < $count; $i++) { + if ($_files[$i][$j] != $_files[$i + 1][$j]) { + $done = true; + + break; + } + } + + if (!$done) { + $common .= $_files[0][$j]; + + if ($j > 0) { + $common .= DIRECTORY_SEPARATOR; + } + } + + $j++; + } + + return DIRECTORY_SEPARATOR . $common; + } +} diff --git a/vendor/phpunit/php-file-iterator/src/Factory.php b/vendor/phpunit/php-file-iterator/src/Factory.php new file mode 100644 index 00000000..08f8de99 --- /dev/null +++ b/vendor/phpunit/php-file-iterator/src/Factory.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\FileIterator; + +use const GLOB_ONLYDIR; +use function array_filter; +use function array_map; +use function array_merge; +use function glob; +use function is_dir; +use function is_string; +use function realpath; +use AppendIterator; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; + +class Factory +{ + /** + * @param array|string $paths + * @param array|string $suffixes + * @param array|string $prefixes + */ + public function getFileIterator($paths, $suffixes = '', $prefixes = '', array $exclude = []): AppendIterator + { + if (is_string($paths)) { + $paths = [$paths]; + } + + $paths = $this->getPathsAfterResolvingWildcards($paths); + $exclude = $this->getPathsAfterResolvingWildcards($exclude); + + if (is_string($prefixes)) { + if ($prefixes !== '') { + $prefixes = [$prefixes]; + } else { + $prefixes = []; + } + } + + if (is_string($suffixes)) { + if ($suffixes !== '') { + $suffixes = [$suffixes]; + } else { + $suffixes = []; + } + } + + $iterator = new AppendIterator; + + foreach ($paths as $path) { + if (is_dir($path)) { + $iterator->append( + new Iterator( + $path, + new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::FOLLOW_SYMLINKS | RecursiveDirectoryIterator::SKIP_DOTS) + ), + $suffixes, + $prefixes, + $exclude + ) + ); + } + } + + return $iterator; + } + + protected function getPathsAfterResolvingWildcards(array $paths): array + { + $_paths = [[]]; + + foreach ($paths as $path) { + if ($locals = glob($path, GLOB_ONLYDIR)) { + $_paths[] = array_map('\realpath', $locals); + } else { + $_paths[] = [realpath($path)]; + } + } + + return array_filter(array_merge(...$_paths)); + } +} diff --git a/vendor/phpunit/php-file-iterator/src/Iterator.php b/vendor/phpunit/php-file-iterator/src/Iterator.php new file mode 100644 index 00000000..7eb82ad6 --- /dev/null +++ b/vendor/phpunit/php-file-iterator/src/Iterator.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\FileIterator; + +use function array_filter; +use function array_map; +use function preg_match; +use function realpath; +use function str_replace; +use function strlen; +use function strpos; +use function substr; +use FilterIterator; + +class Iterator extends FilterIterator +{ + public const PREFIX = 0; + + public const SUFFIX = 1; + + /** + * @var string + */ + private $basePath; + + /** + * @var array + */ + private $suffixes = []; + + /** + * @var array + */ + private $prefixes = []; + + /** + * @var array + */ + private $exclude = []; + + public function __construct(string $basePath, \Iterator $iterator, array $suffixes = [], array $prefixes = [], array $exclude = []) + { + $this->basePath = realpath($basePath); + $this->prefixes = $prefixes; + $this->suffixes = $suffixes; + $this->exclude = array_filter(array_map('realpath', $exclude)); + + parent::__construct($iterator); + } + + public function accept(): bool + { + $current = $this->getInnerIterator()->current(); + $filename = $current->getFilename(); + $realPath = $current->getRealPath(); + + if ($realPath === false) { + return false; + } + + return $this->acceptPath($realPath) && + $this->acceptPrefix($filename) && + $this->acceptSuffix($filename); + } + + private function acceptPath(string $path): bool + { + // Filter files in hidden directories by checking path that is relative to the base path. + if (preg_match('=/\.[^/]*/=', str_replace($this->basePath, '', $path))) { + return false; + } + + foreach ($this->exclude as $exclude) { + if (strpos($path, $exclude) === 0) { + return false; + } + } + + return true; + } + + private function acceptPrefix(string $filename): bool + { + return $this->acceptSubString($filename, $this->prefixes, self::PREFIX); + } + + private function acceptSuffix(string $filename): bool + { + return $this->acceptSubString($filename, $this->suffixes, self::SUFFIX); + } + + private function acceptSubString(string $filename, array $subStrings, int $type): bool + { + if (empty($subStrings)) { + return true; + } + + $matched = false; + + foreach ($subStrings as $string) { + if (($type === self::PREFIX && strpos($filename, $string) === 0) || + ($type === self::SUFFIX && + substr($filename, -1 * strlen($string)) === $string)) { + $matched = true; + + break; + } + } + + return $matched; + } +} diff --git a/vendor/phpunit/php-invoker/ChangeLog.md b/vendor/phpunit/php-invoker/ChangeLog.md new file mode 100644 index 00000000..15cff1f1 --- /dev/null +++ b/vendor/phpunit/php-invoker/ChangeLog.md @@ -0,0 +1,48 @@ +# ChangeLog + +All notable changes are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles. + +## [3.1.1] - 2020-09-28 + +### Changed + +* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3` + +## [3.1.0] - 2020-08-06 + +### Changed + +* [#14](https://github.com/sebastianbergmann/php-invoker/pull/14): Clear alarm in `finally` block + +## [3.0.2] - 2020-06-26 + +### Added + +* This component is now supported on PHP 8 + +## [3.0.1] - 2020-06-15 + +### Changed + +* Tests etc. are now ignored for archive exports + +## [3.0.0] - 2020-02-07 + +### Added + +* Added `canInvokeWithTimeout()` method to check requirements for the functionality provided by this component to work + +### Changed + +* Moved `"ext-pcntl": "*"` requirement from `require` to `suggest` so that this component can be installed even if `ext/pcntl` is not available +* `invoke()` now raises an exception when the requirements for the functionality provided by this component to work are not met + +### Removed + +* This component is no longer supported on PHP 7.1 and PHP 7.2 + +[3.1.1]: https://github.com/sebastianbergmann/php-invoker/compare/3.1.0...3.1.1 +[3.1.0]: https://github.com/sebastianbergmann/php-invoker/compare/3.0.2...3.1.0 +[3.0.2]: https://github.com/sebastianbergmann/php-invoker/compare/3.0.1...3.0.2 +[3.0.1]: https://github.com/sebastianbergmann/php-invoker/compare/3.0.0...3.0.1 +[3.0.0]: https://github.com/sebastianbergmann/php-invoker/compare/2.0.0...3.0.0 diff --git a/vendor/phpunit/php-invoker/LICENSE b/vendor/phpunit/php-invoker/LICENSE new file mode 100644 index 00000000..4620776e --- /dev/null +++ b/vendor/phpunit/php-invoker/LICENSE @@ -0,0 +1,33 @@ +php-invoker + +Copyright (c) 2011-2020, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/phpunit/php-invoker/README.md b/vendor/phpunit/php-invoker/README.md new file mode 100644 index 00000000..ace07e5f --- /dev/null +++ b/vendor/phpunit/php-invoker/README.md @@ -0,0 +1,18 @@ +# phpunit/php-invoker + +[![CI Status](https://github.com/sebastianbergmann/php-invoker/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/php-invoker/actions) +[![Type Coverage](https://shepherd.dev/github/sebastianbergmann/php-invoker/coverage.svg)](https://shepherd.dev/github/sebastianbergmann/php-invoker) + +## Installation + +You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): + +``` +composer require phpunit/php-invoker +``` + +If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency: + +``` +composer require --dev phpunit/php-invoker +``` diff --git a/vendor/phpunit/php-invoker/composer.json b/vendor/phpunit/php-invoker/composer.json new file mode 100644 index 00000000..6c007cd8 --- /dev/null +++ b/vendor/phpunit/php-invoker/composer.json @@ -0,0 +1,54 @@ +{ + "name": "phpunit/php-invoker", + "description": "Invoke callables with a timeout", + "type": "library", + "keywords": [ + "process" + ], + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues" + }, + "prefer-stable": true, + "config": { + "platform": { + "php": "7.3.0" + }, + "optimize-autoloader": true, + "sort-packages": true + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "autoload-dev": { + "classmap": [ + "tests/_fixture/" + ] + }, + "suggest": { + "ext-pcntl": "*" + }, + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + } +} + diff --git a/vendor/phpunit/php-invoker/src/Invoker.php b/vendor/phpunit/php-invoker/src/Invoker.php new file mode 100644 index 00000000..656f4180 --- /dev/null +++ b/vendor/phpunit/php-invoker/src/Invoker.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Invoker; + +use const SIGALRM; +use function call_user_func_array; +use function function_exists; +use function pcntl_alarm; +use function pcntl_async_signals; +use function pcntl_signal; +use function sprintf; +use Throwable; + +final class Invoker +{ + /** + * @var int + */ + private $timeout; + + /** + * @throws Throwable + */ + public function invoke(callable $callable, array $arguments, int $timeout) + { + if (!$this->canInvokeWithTimeout()) { + throw new ProcessControlExtensionNotLoadedException( + 'The pcntl (process control) extension for PHP is required' + ); + } + + pcntl_signal( + SIGALRM, + function (): void { + throw new TimeoutException( + sprintf( + 'Execution aborted after %d second%s', + $this->timeout, + $this->timeout === 1 ? '' : 's' + ) + ); + }, + true + ); + + $this->timeout = $timeout; + + pcntl_async_signals(true); + pcntl_alarm($timeout); + + try { + return call_user_func_array($callable, $arguments); + } finally { + pcntl_alarm(0); + } + } + + public function canInvokeWithTimeout(): bool + { + return function_exists('pcntl_signal') && function_exists('pcntl_async_signals') && function_exists('pcntl_alarm'); + } +} diff --git a/vendor/phpunit/php-invoker/src/exceptions/Exception.php b/vendor/phpunit/php-invoker/src/exceptions/Exception.php new file mode 100644 index 00000000..6ecbf5dd --- /dev/null +++ b/vendor/phpunit/php-invoker/src/exceptions/Exception.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Invoker; + +use Throwable; + +interface Exception extends Throwable +{ +} diff --git a/vendor/phpunit/php-invoker/src/exceptions/ProcessControlExtensionNotLoadedException.php b/vendor/phpunit/php-invoker/src/exceptions/ProcessControlExtensionNotLoadedException.php new file mode 100644 index 00000000..ef42fd19 --- /dev/null +++ b/vendor/phpunit/php-invoker/src/exceptions/ProcessControlExtensionNotLoadedException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Invoker; + +use RuntimeException; + +final class ProcessControlExtensionNotLoadedException extends RuntimeException implements Exception +{ +} diff --git a/vendor/phpunit/php-invoker/src/exceptions/TimeoutException.php b/vendor/phpunit/php-invoker/src/exceptions/TimeoutException.php new file mode 100644 index 00000000..2f7631c0 --- /dev/null +++ b/vendor/phpunit/php-invoker/src/exceptions/TimeoutException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Invoker; + +use RuntimeException; + +final class TimeoutException extends RuntimeException implements Exception +{ +} diff --git a/vendor/phpunit/php-text-template/.psalm/baseline.xml b/vendor/phpunit/php-text-template/.psalm/baseline.xml new file mode 100644 index 00000000..77e688e0 --- /dev/null +++ b/vendor/phpunit/php-text-template/.psalm/baseline.xml @@ -0,0 +1,2 @@ + + diff --git a/vendor/phpunit/php-text-template/.psalm/config.xml b/vendor/phpunit/php-text-template/.psalm/config.xml new file mode 100644 index 00000000..2a4b16f2 --- /dev/null +++ b/vendor/phpunit/php-text-template/.psalm/config.xml @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/vendor/phpunit/php-text-template/ChangeLog.md b/vendor/phpunit/php-text-template/ChangeLog.md new file mode 100644 index 00000000..32a48a7a --- /dev/null +++ b/vendor/phpunit/php-text-template/ChangeLog.md @@ -0,0 +1,43 @@ +# ChangeLog + +All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. + +## [2.0.4] - 2020-10-26 + +### Fixed + +* `SebastianBergmann\Template\Exception` now correctly extends `\Throwable` + +## [2.0.3] - 2020-09-28 + +### Changed + +* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3` + +## [2.0.2] - 2020-06-26 + +### Added + +* This component is now supported on PHP 8 + +## [2.0.1] - 2020-06-15 + +### Changed + +* Tests etc. are now ignored for archive exports + +## [2.0.0] - 2020-02-07 + +### Changed + +* The `Text_Template` class was renamed to `SebastianBergmann\Template\Template` + +### Removed + +* Removed support for PHP 5.3, PHP 5.4, PHP 5.5, PHP 5.6, PHP 7.0, PHP 7.1, and PHP 7.2 + +[2.0.4]: https://github.com/sebastianbergmann/php-text-template/compare/2.0.3...2.0.4 +[2.0.3]: https://github.com/sebastianbergmann/php-text-template/compare/2.0.2...2.0.3 +[2.0.2]: https://github.com/sebastianbergmann/php-text-template/compare/2.0.1...2.0.2 +[2.0.1]: https://github.com/sebastianbergmann/php-text-template/compare/2.0.0...2.0.1 +[2.0.0]: https://github.com/sebastianbergmann/php-text-template/compare/1.2.1...2.0.0 diff --git a/vendor/phpunit/php-text-template/LICENSE b/vendor/phpunit/php-text-template/LICENSE new file mode 100644 index 00000000..6db5566c --- /dev/null +++ b/vendor/phpunit/php-text-template/LICENSE @@ -0,0 +1,33 @@ +phpunit/php-text-template + +Copyright (c) 2009-2020, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/phpunit/php-text-template/README.md b/vendor/phpunit/php-text-template/README.md new file mode 100644 index 00000000..b2865935 --- /dev/null +++ b/vendor/phpunit/php-text-template/README.md @@ -0,0 +1,12 @@ +# Text_Template + +## Installation + +You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): + + composer require phpunit/php-text-template + +If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency: + + composer require --dev phpunit/php-text-template + diff --git a/vendor/phpunit/php-text-template/composer.json b/vendor/phpunit/php-text-template/composer.json new file mode 100644 index 00000000..a51b34b9 --- /dev/null +++ b/vendor/phpunit/php-text-template/composer.json @@ -0,0 +1,43 @@ +{ + "name": "phpunit/php-text-template", + "description": "Simple template engine.", + "type": "library", + "keywords": [ + "template" + ], + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues" + }, + "config": { + "platform": { + "php": "7.3.0" + }, + "optimize-autoloader": true, + "sort-packages": true + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + } +} diff --git a/vendor/phpunit/php-text-template/src/Template.php b/vendor/phpunit/php-text-template/src/Template.php new file mode 100644 index 00000000..25e29ea9 --- /dev/null +++ b/vendor/phpunit/php-text-template/src/Template.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Template; + +use function array_merge; +use function file_exists; +use function file_get_contents; +use function file_put_contents; +use function sprintf; +use function str_replace; + +final class Template +{ + /** + * @var string + */ + private $template = ''; + + /** + * @var string + */ + private $openDelimiter; + + /** + * @var string + */ + private $closeDelimiter; + + /** + * @var array + */ + private $values = []; + + /** + * @throws InvalidArgumentException + */ + public function __construct(string $file = '', string $openDelimiter = '{', string $closeDelimiter = '}') + { + $this->setFile($file); + + $this->openDelimiter = $openDelimiter; + $this->closeDelimiter = $closeDelimiter; + } + + /** + * @throws InvalidArgumentException + */ + public function setFile(string $file): void + { + $distFile = $file . '.dist'; + + if (file_exists($file)) { + $this->template = file_get_contents($file); + } elseif (file_exists($distFile)) { + $this->template = file_get_contents($distFile); + } else { + throw new InvalidArgumentException( + sprintf( + 'Failed to load template "%s"', + $file + ) + ); + } + } + + public function setVar(array $values, bool $merge = true): void + { + if (!$merge || empty($this->values)) { + $this->values = $values; + } else { + $this->values = array_merge($this->values, $values); + } + } + + public function render(): string + { + $keys = []; + + foreach ($this->values as $key => $value) { + $keys[] = $this->openDelimiter . $key . $this->closeDelimiter; + } + + return str_replace($keys, $this->values, $this->template); + } + + /** + * @codeCoverageIgnore + */ + public function renderTo(string $target): void + { + if (!file_put_contents($target, $this->render())) { + throw new RuntimeException( + sprintf( + 'Writing rendered result to "%s" failed', + $target + ) + ); + } + } +} diff --git a/vendor/phpunit/php-text-template/src/exceptions/Exception.php b/vendor/phpunit/php-text-template/src/exceptions/Exception.php new file mode 100644 index 00000000..d7dc5cbe --- /dev/null +++ b/vendor/phpunit/php-text-template/src/exceptions/Exception.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Template; + +use Throwable; + +interface Exception extends Throwable +{ +} diff --git a/vendor/phpunit/php-text-template/src/exceptions/InvalidArgumentException.php b/vendor/phpunit/php-text-template/src/exceptions/InvalidArgumentException.php new file mode 100644 index 00000000..10e1cd11 --- /dev/null +++ b/vendor/phpunit/php-text-template/src/exceptions/InvalidArgumentException.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Template; + +final class InvalidArgumentException extends \InvalidArgumentException implements Exception +{ +} diff --git a/vendor/phpunit/php-text-template/src/exceptions/RuntimeException.php b/vendor/phpunit/php-text-template/src/exceptions/RuntimeException.php new file mode 100644 index 00000000..131498e6 --- /dev/null +++ b/vendor/phpunit/php-text-template/src/exceptions/RuntimeException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Template; + +use InvalidArgumentException; + +final class RuntimeException extends InvalidArgumentException implements Exception +{ +} diff --git a/vendor/phpunit/php-timer/.psalm/baseline.xml b/vendor/phpunit/php-timer/.psalm/baseline.xml new file mode 100644 index 00000000..77e688e0 --- /dev/null +++ b/vendor/phpunit/php-timer/.psalm/baseline.xml @@ -0,0 +1,2 @@ + + diff --git a/vendor/phpunit/php-timer/.psalm/config.xml b/vendor/phpunit/php-timer/.psalm/config.xml new file mode 100644 index 00000000..15abef05 --- /dev/null +++ b/vendor/phpunit/php-timer/.psalm/config.xml @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/vendor/phpunit/php-timer/ChangeLog.md b/vendor/phpunit/php-timer/ChangeLog.md new file mode 100644 index 00000000..34ef7d1d --- /dev/null +++ b/vendor/phpunit/php-timer/ChangeLog.md @@ -0,0 +1,138 @@ +# ChangeLog + +All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. + +## [5.0.3] - 2020-10-26 + +### Fixed + +* `SebastianBergmann\Timer\Exception` now correctly extends `\Throwable` + +## [5.0.2] - 2020-09-28 + +### Changed + +* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3` + +## [5.0.1] - 2020-06-26 + +### Added + +* This component is now supported on PHP 8 + +## [5.0.0] - 2020-06-07 + +### Changed + +* Parameter type for `SebastianBergmann\Timer\Duration::fromMicroseconds()` was changed from `int` to `float` +* Parameter type for `SebastianBergmann\Timer\Duration::fromNanoseconds()` was changed from `int` to `float` +* Return type for `SebastianBergmann\Timer\Duration::asNanoseconds()` was changed from `int` to `float` + +### Fixed + +* [#31](https://github.com/sebastianbergmann/php-timer/issues/31): Type Error on 32-bit systems (where `hrtime()` returns `float` instead of `int`) + +## [4.0.0] - 2020-06-01 + +### Added + +* Introduced `Duration` value object for encapsulating a duration with nanosecond granularity +* Introduced `ResourceUsageFormatter` object for formatting resource usage with option to explicitly pass a duration (instead of looking at the unreliable `$_SERVER['REQUEST_TIME_FLOAT']` variable) + +### Changed + +* The methods of `Timer` are no longer static +* `Timer::stop()` now returns a `Duration` value object + +### Removed + +* Functionality that is now implemented in `Duration` and `ResourceUsageFormatter` has been removed from `Timer` + +## [3.1.4] - 2020-04-20 + +### Changed + +* `Timer::timeSinceStartOfRequest()` no longer tries `$_SERVER['REQUEST_TIME']` when `$_SERVER['REQUEST_TIME_FLOAT']` is not available (`$_SERVER['REQUEST_TIME_FLOAT']` was added in PHP 5.4 and this library requires PHP 7.3) +* Improved exception messages when `$_SERVER['REQUEST_TIME_FLOAT']` is not set or is not of type `float` + +### Changed + +## [3.1.3] - 2020-04-20 + +### Changed + +* `Timer::timeSinceStartOfRequest()` now raises an exception if `$_SERVER['REQUEST_TIME_FLOAT']` does not contain a `float` (or `$_SERVER['REQUEST_TIME']` does not contain an `int`) + +## [3.1.2] - 2020-04-17 + +### Changed + +* Improved the fix for [#30](https://github.com/sebastianbergmann/php-timer/issues/30) and restored usage of `hrtime()` + +## [3.1.1] - 2020-04-17 + +### Fixed + +* [#30](https://github.com/sebastianbergmann/php-timer/issues/30): Resolution of time returned by `Timer::stop()` is different than before (this reverts using `hrtime()` instead of `microtime()`) + +## [3.1.0] - 2020-04-17 + +### Added + +* `Timer::secondsToShortTimeString()` as alternative to `Timer::secondsToTimeString()` + +### Changed + +* `Timer::start()` and `Timer::stop()` now use `hrtime()` (high resolution monotonic timer) instead of `microtime()` +* `Timer::timeSinceStartOfRequest()` now uses `Timer::secondsToShortTimeString()` for time formatting +* Improved formatting of `Timer::secondsToTimeString()` result + +## [3.0.0] - 2020-02-07 + +### Removed + +* This component is no longer supported on PHP 7.1 and PHP 7.2 + +## [2.1.2] - 2019-06-07 + +### Fixed + +* [#21](https://github.com/sebastianbergmann/php-timer/pull/21): Formatting of memory consumption does not work on 32bit systems + +## [2.1.1] - 2019-02-20 + +### Changed + +* Improved formatting of memory consumption for `resourceUsage()` + +## [2.1.0] - 2019-02-20 + +### Changed + +* Improved formatting of memory consumption for `resourceUsage()` + +## [2.0.0] - 2018-02-01 + +### Changed + +* This component now uses namespaces + +### Removed + +* This component is no longer supported on PHP 5.3, PHP 5.4, PHP 5.5, PHP 5.6, and PHP 7.0 + +[5.0.3]: https://github.com/sebastianbergmann/php-timer/compare/5.0.2...5.0.3 +[5.0.2]: https://github.com/sebastianbergmann/php-timer/compare/5.0.1...5.0.2 +[5.0.1]: https://github.com/sebastianbergmann/php-timer/compare/5.0.0...5.0.1 +[5.0.0]: https://github.com/sebastianbergmann/php-timer/compare/4.0.0...5.0.0 +[4.0.0]: https://github.com/sebastianbergmann/php-timer/compare/3.1.4...4.0.0 +[3.1.4]: https://github.com/sebastianbergmann/php-timer/compare/3.1.3...3.1.4 +[3.1.3]: https://github.com/sebastianbergmann/php-timer/compare/3.1.2...3.1.3 +[3.1.2]: https://github.com/sebastianbergmann/php-timer/compare/3.1.1...3.1.2 +[3.1.1]: https://github.com/sebastianbergmann/php-timer/compare/3.1.0...3.1.1 +[3.1.0]: https://github.com/sebastianbergmann/php-timer/compare/3.0.0...3.1.0 +[3.0.0]: https://github.com/sebastianbergmann/php-timer/compare/2.1.2...3.0.0 +[2.1.2]: https://github.com/sebastianbergmann/php-timer/compare/2.1.1...2.1.2 +[2.1.1]: https://github.com/sebastianbergmann/php-timer/compare/2.1.0...2.1.1 +[2.1.0]: https://github.com/sebastianbergmann/php-timer/compare/2.0.0...2.1.0 +[2.0.0]: https://github.com/sebastianbergmann/php-timer/compare/1.0.9...2.0.0 diff --git a/vendor/phpunit/php-timer/LICENSE b/vendor/phpunit/php-timer/LICENSE new file mode 100644 index 00000000..4193d8ae --- /dev/null +++ b/vendor/phpunit/php-timer/LICENSE @@ -0,0 +1,33 @@ +phpunit/php-timer + +Copyright (c) 2010-2020, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/phpunit/php-timer/README.md b/vendor/phpunit/php-timer/README.md new file mode 100644 index 00000000..a7d1e70d --- /dev/null +++ b/vendor/phpunit/php-timer/README.md @@ -0,0 +1,104 @@ +# phpunit/php-timer + +[![CI Status](https://github.com/sebastianbergmann/php-timer/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/php-timer/actions) +[![Type Coverage](https://shepherd.dev/github/sebastianbergmann/php-timer/coverage.svg)](https://shepherd.dev/github/sebastianbergmann/php-timer) + +Utility class for timing things, factored out of PHPUnit into a stand-alone component. + +## Installation + +You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): + +``` +composer require phpunit/php-timer +``` + +If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency: + +``` +composer require --dev phpunit/php-timer +``` + +## Usage + +### Basic Timing + +```php +require __DIR__ . '/vendor/autoload.php'; + +use SebastianBergmann\Timer\Timer; + +$timer = new Timer; + +$timer->start(); + +foreach (\range(0, 100000) as $i) { + // ... +} + +$duration = $timer->stop(); + +var_dump(get_class($duration)); +var_dump($duration->asString()); +var_dump($duration->asSeconds()); +var_dump($duration->asMilliseconds()); +var_dump($duration->asMicroseconds()); +var_dump($duration->asNanoseconds()); +``` + +The code above yields the output below: + +``` +string(32) "SebastianBergmann\Timer\Duration" +string(9) "00:00.002" +float(0.002851062) +float(2.851062) +float(2851.062) +int(2851062) +``` + +### Resource Consumption + +#### Explicit duration + +```php +require __DIR__ . '/vendor/autoload.php'; + +use SebastianBergmann\Timer\ResourceUsageFormatter; +use SebastianBergmann\Timer\Timer; + +$timer = new Timer; +$timer->start(); + +foreach (\range(0, 100000) as $i) { + // ... +} + +print (new ResourceUsageFormatter)->resourceUsage($timer->stop()); +``` + +The code above yields the output below: + +``` +Time: 00:00.002, Memory: 6.00 MB +``` + +#### Duration since PHP Startup (using unreliable `$_SERVER['REQUEST_TIME_FLOAT']`) + +```php +require __DIR__ . '/vendor/autoload.php'; + +use SebastianBergmann\Timer\ResourceUsageFormatter; + +foreach (\range(0, 100000) as $i) { + // ... +} + +print (new ResourceUsageFormatter)->resourceUsageSinceStartOfRequest(); +``` + +The code above yields the output below: + +``` +Time: 00:00.002, Memory: 6.00 MB +``` diff --git a/vendor/phpunit/php-timer/composer.json b/vendor/phpunit/php-timer/composer.json new file mode 100644 index 00000000..001701c2 --- /dev/null +++ b/vendor/phpunit/php-timer/composer.json @@ -0,0 +1,45 @@ +{ + "name": "phpunit/php-timer", + "description": "Utility class for timing", + "type": "library", + "keywords": [ + "timer" + ], + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues" + }, + "prefer-stable": true, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "config": { + "platform": { + "php": "7.3.0" + }, + "optimize-autoloader": true, + "sort-packages": true + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + } +} + diff --git a/vendor/phpunit/php-timer/src/Duration.php b/vendor/phpunit/php-timer/src/Duration.php new file mode 100644 index 00000000..e52bf018 --- /dev/null +++ b/vendor/phpunit/php-timer/src/Duration.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Timer; + +use function floor; +use function sprintf; + +/** + * @psalm-immutable + */ +final class Duration +{ + /** + * @var float + */ + private $nanoseconds; + + /** + * @var int + */ + private $hours; + + /** + * @var int + */ + private $minutes; + + /** + * @var int + */ + private $seconds; + + /** + * @var int + */ + private $milliseconds; + + public static function fromMicroseconds(float $microseconds): self + { + return new self($microseconds * 1000); + } + + public static function fromNanoseconds(float $nanoseconds): self + { + return new self($nanoseconds); + } + + private function __construct(float $nanoseconds) + { + $this->nanoseconds = $nanoseconds; + $timeInMilliseconds = $nanoseconds / 1000000; + $hours = floor($timeInMilliseconds / 60 / 60 / 1000); + $hoursInMilliseconds = $hours * 60 * 60 * 1000; + $minutes = floor($timeInMilliseconds / 60 / 1000) % 60; + $minutesInMilliseconds = $minutes * 60 * 1000; + $seconds = floor(($timeInMilliseconds - $hoursInMilliseconds - $minutesInMilliseconds) / 1000); + $secondsInMilliseconds = $seconds * 1000; + $milliseconds = $timeInMilliseconds - $hoursInMilliseconds - $minutesInMilliseconds - $secondsInMilliseconds; + $this->hours = (int) $hours; + $this->minutes = $minutes; + $this->seconds = (int) $seconds; + $this->milliseconds = (int) $milliseconds; + } + + public function asNanoseconds(): float + { + return $this->nanoseconds; + } + + public function asMicroseconds(): float + { + return $this->nanoseconds / 1000; + } + + public function asMilliseconds(): float + { + return $this->nanoseconds / 1000000; + } + + public function asSeconds(): float + { + return $this->nanoseconds / 1000000000; + } + + public function asString(): string + { + $result = ''; + + if ($this->hours > 0) { + $result = sprintf('%02d', $this->hours) . ':'; + } + + $result .= sprintf('%02d', $this->minutes) . ':'; + $result .= sprintf('%02d', $this->seconds); + + if ($this->milliseconds > 0) { + $result .= '.' . sprintf('%03d', $this->milliseconds); + } + + return $result; + } +} diff --git a/vendor/phpunit/php-timer/src/ResourceUsageFormatter.php b/vendor/phpunit/php-timer/src/ResourceUsageFormatter.php new file mode 100644 index 00000000..ad792627 --- /dev/null +++ b/vendor/phpunit/php-timer/src/ResourceUsageFormatter.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Timer; + +use function is_float; +use function memory_get_peak_usage; +use function microtime; +use function sprintf; + +final class ResourceUsageFormatter +{ + /** + * @psalm-var array + */ + private const SIZES = [ + 'GB' => 1073741824, + 'MB' => 1048576, + 'KB' => 1024, + ]; + + public function resourceUsage(Duration $duration): string + { + return sprintf( + 'Time: %s, Memory: %s', + $duration->asString(), + $this->bytesToString(memory_get_peak_usage(true)) + ); + } + + /** + * @throws TimeSinceStartOfRequestNotAvailableException + */ + public function resourceUsageSinceStartOfRequest(): string + { + if (!isset($_SERVER['REQUEST_TIME_FLOAT'])) { + throw new TimeSinceStartOfRequestNotAvailableException( + 'Cannot determine time at which the request started because $_SERVER[\'REQUEST_TIME_FLOAT\'] is not available' + ); + } + + if (!is_float($_SERVER['REQUEST_TIME_FLOAT'])) { + throw new TimeSinceStartOfRequestNotAvailableException( + 'Cannot determine time at which the request started because $_SERVER[\'REQUEST_TIME_FLOAT\'] is not of type float' + ); + } + + return $this->resourceUsage( + Duration::fromMicroseconds( + (1000000 * (microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'])) + ) + ); + } + + private function bytesToString(int $bytes): string + { + foreach (self::SIZES as $unit => $value) { + if ($bytes >= $value) { + return sprintf('%.2f %s', $bytes >= 1024 ? $bytes / $value : $bytes, $unit); + } + } + + // @codeCoverageIgnoreStart + return $bytes . ' byte' . ($bytes !== 1 ? 's' : ''); + // @codeCoverageIgnoreEnd + } +} diff --git a/vendor/phpunit/php-timer/src/Timer.php b/vendor/phpunit/php-timer/src/Timer.php new file mode 100644 index 00000000..0917109b --- /dev/null +++ b/vendor/phpunit/php-timer/src/Timer.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Timer; + +use function array_pop; +use function hrtime; + +final class Timer +{ + /** + * @psalm-var list + */ + private $startTimes = []; + + public function start(): void + { + $this->startTimes[] = (float) hrtime(true); + } + + /** + * @throws NoActiveTimerException + */ + public function stop(): Duration + { + if (empty($this->startTimes)) { + throw new NoActiveTimerException( + 'Timer::start() has to be called before Timer::stop()' + ); + } + + return Duration::fromNanoseconds((float) hrtime(true) - array_pop($this->startTimes)); + } +} diff --git a/vendor/phpunit/php-timer/src/exceptions/Exception.php b/vendor/phpunit/php-timer/src/exceptions/Exception.php new file mode 100644 index 00000000..996da086 --- /dev/null +++ b/vendor/phpunit/php-timer/src/exceptions/Exception.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Timer; + +use Throwable; + +interface Exception extends Throwable +{ +} diff --git a/vendor/phpunit/php-timer/src/exceptions/NoActiveTimerException.php b/vendor/phpunit/php-timer/src/exceptions/NoActiveTimerException.php new file mode 100644 index 00000000..40fe45e8 --- /dev/null +++ b/vendor/phpunit/php-timer/src/exceptions/NoActiveTimerException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Timer; + +use LogicException; + +final class NoActiveTimerException extends LogicException implements Exception +{ +} diff --git a/vendor/phpunit/php-timer/src/exceptions/TimeSinceStartOfRequestNotAvailableException.php b/vendor/phpunit/php-timer/src/exceptions/TimeSinceStartOfRequestNotAvailableException.php new file mode 100644 index 00000000..a2d94ce8 --- /dev/null +++ b/vendor/phpunit/php-timer/src/exceptions/TimeSinceStartOfRequestNotAvailableException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Timer; + +use RuntimeException; + +final class TimeSinceStartOfRequestNotAvailableException extends RuntimeException implements Exception +{ +} diff --git a/vendor/phpunit/phpunit/.phpstorm.meta.php b/vendor/phpunit/phpunit/.phpstorm.meta.php new file mode 100644 index 00000000..b69ff789 --- /dev/null +++ b/vendor/phpunit/phpunit/.phpstorm.meta.php @@ -0,0 +1,33 @@ +"$0"]) + ); + + override( + \PHPUnit\Framework\TestCase::createStub(0), + map([""=>"$0"]) + ); + + override( + \PHPUnit\Framework\TestCase::createConfiguredMock(0), + map([""=>"$0"]) + ); + + override( + \PHPUnit\Framework\TestCase::createPartialMock(0), + map([""=>"$0"]) + ); + + override( + \PHPUnit\Framework\TestCase::createTestProxy(0), + map([""=>"$0"]) + ); + + override( + \PHPUnit\Framework\TestCase::getMockForAbstractClass(0), + map([""=>"$0"]) + ); +} diff --git a/vendor/phpunit/phpunit/ChangeLog-9.6.md b/vendor/phpunit/phpunit/ChangeLog-9.6.md new file mode 100644 index 00000000..9689bfbb --- /dev/null +++ b/vendor/phpunit/phpunit/ChangeLog-9.6.md @@ -0,0 +1,141 @@ +# Changes in PHPUnit 9.6 + +All notable changes of the PHPUnit 9.6 release series are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles. + +## [9.6.17] - 2024-02-23 + +### Fixed + +* [#5712](https://github.com/sebastianbergmann/phpunit/issues/5712): Update dependencies for PHAR distribution of PHPUnit 9.6 + +## [9.6.16] - 2024-01-19 + +### Fixed + +* [#5516](https://github.com/sebastianbergmann/phpunit/issues/5516): Assertions that use the `LogicalNot` constraint (`assertNotEquals()`, `assertStringNotContainsString()`, ...) can generate confusing failure messages +* [#5666](https://github.com/sebastianbergmann/phpunit/issues/5666): `--no-extensions` CLI option does not work +* [#5673](https://github.com/sebastianbergmann/phpunit/issues/5673): Confusing error message when migration of a configuration is requested that does not need to be migrated + +## [9.6.15] - 2023-12-01 + +### Fixed + +* [#5596](https://github.com/sebastianbergmann/phpunit/issues/5596): `PHPUnit\Framework\TestCase` has `@internal` annotation in PHAR + +## [9.6.14] - 2023-12-01 + +### Added + +* [#5577](https://github.com/sebastianbergmann/phpunit/issues/5577): `--composer-lock` CLI option for PHAR binary that displays the `composer.lock` used to build the PHAR + +## [9.6.13] - 2023-09-19 + +### Changed + +* The child processes used for process isolation now use temporary files to communicate their result to the parent process + +## [9.6.12] - 2023-09-12 + +### Changed + +* [#5508](https://github.com/sebastianbergmann/phpunit/pull/5508): Generate code coverage report in PHP format as first in list to avoid serializing cache data + +## [9.6.11] - 2023-08-19 + +### Added + +* [#5478](https://github.com/sebastianbergmann/phpunit/pull/5478): `assertObjectHasProperty()` and `assertObjectNotHasProperty()` + +## [9.6.10] - 2023-07-10 + +### Changed + +* [#5419](https://github.com/sebastianbergmann/phpunit/pull/5419): Allow empty `` element in XML configuration + +## [9.6.9] - 2023-06-11 + +### Fixed + +* [#5405](https://github.com/sebastianbergmann/phpunit/issues/5405): XML configuration migration does not migrate `whitelist/file` elements +* Always use `X.Y.Z` version number (and not just `X.Y`) of PHPUnit's version when checking whether a PHAR-distributed extension is compatible + +## [9.6.8] - 2023-05-11 + +### Fixed + +* [#5345](https://github.com/sebastianbergmann/phpunit/issues/5345): No stack trace shown for previous exceptions during bootstrap + +## [9.6.7] - 2023-04-14 + +### Fixed + +* Tests that have `@doesNotPerformAssertions` do not contribute to code coverage + +## [9.6.6] - 2023-03-27 + +### Fixed + +* [#5270](https://github.com/sebastianbergmann/phpunit/issues/5270): `GlobalState::getIniSettingsAsString()` generates code that triggers warnings + +## [9.6.5] - 2023-03-09 + +### Changed + +* Backported the HTML and CSS improvements made to the `--testdox-html` from PHPUnit 10 + +### Fixed + +* [#5205](https://github.com/sebastianbergmann/phpunit/issues/5205): Wrong default value for optional parameter of `PHPUnit\Util\Test::parseTestMethodAnnotations()` causes `ReflectionException` + +## [9.6.4] - 2023-02-27 + +### Fixed + +* [#5186](https://github.com/sebastianbergmann/phpunit/issues/5186): SBOM does not validate + +## [9.6.3] - 2023-02-04 + +### Fixed + +* [#5164](https://github.com/sebastianbergmann/phpunit/issues/5164): `markTestSkipped()` not handled correctly when called in "before first test" method + +## [9.6.2] - 2023-02-04 + +### Fixed + +* [#4618](https://github.com/sebastianbergmann/phpunit/issues/4618): Support for generators in `assertCount()` etc. is not marked as deprecated in PHPUnit 9.6 + +## [9.6.1] - 2023-02-03 + +### Fixed + +* [#5073](https://github.com/sebastianbergmann/phpunit/issues/5073): `--no-extensions` CLI option only prevents extension PHARs from being loaded +* [#5160](https://github.com/sebastianbergmann/phpunit/issues/5160): Deprecate `assertClassHasAttribute()`, `assertClassNotHasAttribute()`, `assertClassHasStaticAttribute()`, `assertClassNotHasStaticAttribute()`, `assertObjectHasAttribute()`, `assertObjectNotHasAttribute()`, `classHasAttribute()`, `classHasStaticAttribute()`, and `objectHasAttribute()` + +## [9.6.0] - 2023-02-03 + +### Changed + +* [#5062](https://github.com/sebastianbergmann/phpunit/issues/5062): Deprecate `expectDeprecation()`, `expectDeprecationMessage()`, `expectDeprecationMessageMatches()`, `expectError()`, `expectErrorMessage()`, `expectErrorMessageMatches()`, `expectNotice()`, `expectNoticeMessage()`, `expectNoticeMessageMatches()`, `expectWarning()`, `expectWarningMessage()`, and `expectWarningMessageMatches()` +* [#5063](https://github.com/sebastianbergmann/phpunit/issues/5063): Deprecate `withConsecutive()` +* [#5064](https://github.com/sebastianbergmann/phpunit/issues/5064): Deprecate `PHPUnit\Framework\TestCase::getMockClass()` +* [#5132](https://github.com/sebastianbergmann/phpunit/issues/5132): Deprecate `Test` suffix for abstract test case classes + +[9.6.17]: https://github.com/sebastianbergmann/phpunit/compare/9.6.16...9.6.17 +[9.6.16]: https://github.com/sebastianbergmann/phpunit/compare/9.6.15...9.6.16 +[9.6.15]: https://github.com/sebastianbergmann/phpunit/compare/9.6.14...9.6.15 +[9.6.14]: https://github.com/sebastianbergmann/phpunit/compare/9.6.13...9.6.14 +[9.6.13]: https://github.com/sebastianbergmann/phpunit/compare/9.6.12...9.6.13 +[9.6.12]: https://github.com/sebastianbergmann/phpunit/compare/9.6.11...9.6.12 +[9.6.11]: https://github.com/sebastianbergmann/phpunit/compare/9.6.10...9.6.11 +[9.6.10]: https://github.com/sebastianbergmann/phpunit/compare/9.6.9...9.6.10 +[9.6.9]: https://github.com/sebastianbergmann/phpunit/compare/9.6.8...9.6.9 +[9.6.8]: https://github.com/sebastianbergmann/phpunit/compare/9.6.7...9.6.8 +[9.6.7]: https://github.com/sebastianbergmann/phpunit/compare/9.6.6...9.6.7 +[9.6.6]: https://github.com/sebastianbergmann/phpunit/compare/9.6.5...9.6.6 +[9.6.5]: https://github.com/sebastianbergmann/phpunit/compare/9.6.4...9.6.5 +[9.6.4]: https://github.com/sebastianbergmann/phpunit/compare/9.6.3...9.6.4 +[9.6.3]: https://github.com/sebastianbergmann/phpunit/compare/9.6.2...9.6.3 +[9.6.2]: https://github.com/sebastianbergmann/phpunit/compare/9.6.1...9.6.2 +[9.6.1]: https://github.com/sebastianbergmann/phpunit/compare/9.6.0...9.6.1 +[9.6.0]: https://github.com/sebastianbergmann/phpunit/compare/9.5.28...9.6.0 diff --git a/vendor/phpunit/phpunit/DEPRECATIONS.md b/vendor/phpunit/phpunit/DEPRECATIONS.md new file mode 100644 index 00000000..b8349592 --- /dev/null +++ b/vendor/phpunit/phpunit/DEPRECATIONS.md @@ -0,0 +1,89 @@ +# Deprecations + +## Soft Deprecations + +This functionality is currently [soft-deprecated](https://phpunit.de/backward-compatibility.html#soft-deprecation): + +### Writing Tests + +#### Test Double API + +| Issue | Description | Since | Replacement | +|-------------------------------------------------------------------|-----------------------------------|-------|-------------| +| [#3687](https://github.com/sebastianbergmann/phpunit/issues/3687) | `MockBuilder::setMethods()` | 8.3.0 | | +| [#3687](https://github.com/sebastianbergmann/phpunit/issues/3687) | `MockBuilder::setMethodsExcept()` | 9.6.0 | | + +## Hard Deprecations + +This functionality is currently [hard-deprecated](https://phpunit.de/backward-compatibility.html#hard-deprecation): + +### Writing Tests + +#### Assertions, Constraints, and Expectations + +| Issue | Description | Since | Replacement | +|-------------------------------------------------------------------|------------------------------------------------|-------|---------------------------------------------------| +| [#4062](https://github.com/sebastianbergmann/phpunit/issues/4062) | `TestCase::assertNotIsReadable()` | 9.1.0 | `TestCase::assertIsNotReadable()` | +| [#4065](https://github.com/sebastianbergmann/phpunit/issues/4065) | `TestCase::assertNotIsWritable()` | 9.1.0 | `TestCase::assertIsNotWritable()` | +| [#4068](https://github.com/sebastianbergmann/phpunit/issues/4068) | `TestCase::assertDirectoryNotExists()` | 9.1.0 | `TestCase::assertDirectoryDoesNotExist()` | +| [#4071](https://github.com/sebastianbergmann/phpunit/issues/4071) | `TestCase::assertDirectoryNotIsReadable()` | 9.1.0 | `TestCase::assertDirectoryIsNotReadable()` | +| [#4074](https://github.com/sebastianbergmann/phpunit/issues/4074) | `TestCase::assertDirectoryNotIsWritable()` | 9.1.0 | `TestCase::assertDirectoryIsNotWritable()` | +| [#4077](https://github.com/sebastianbergmann/phpunit/issues/4077) | `TestCase::assertFileNotExists()` | 9.1.0 | `TestCase::assertFileDoesNotExist()` | +| [#4080](https://github.com/sebastianbergmann/phpunit/issues/4080) | `TestCase::assertFileNotIsReadable()` | 9.1.0 | `TestCase::assertFileIsNotReadable()` | +| [#4083](https://github.com/sebastianbergmann/phpunit/issues/4083) | `TestCase::assertFileNotIsWritable()` | 9.1.0 | `TestCase::assertFileIsNotWritable()` | +| [#4086](https://github.com/sebastianbergmann/phpunit/issues/4086) | `TestCase::assertRegExp()` | 9.1.0 | `TestCase::assertMatchesRegularExpression()` | +| [#4089](https://github.com/sebastianbergmann/phpunit/issues/4089) | `TestCase::assertNotRegExp()` | 9.1.0 | `TestCase::assertDoesNotMatchRegularExpression()` | +| [#4091](https://github.com/sebastianbergmann/phpunit/issues/4091) | `TestCase::assertEqualXMLStructure()` | 9.1.0 | | +| [#5062](https://github.com/sebastianbergmann/phpunit/issues/5062) | `TestCase::expectDeprecation()` | 9.6.0 | | +| [#5062](https://github.com/sebastianbergmann/phpunit/issues/5062) | `TestCase::expectDeprecationMessage()` | 9.6.0 | | +| [#5062](https://github.com/sebastianbergmann/phpunit/issues/5062) | `TestCase::expectDeprecationMessageMatches()` | 9.6.0 | | +| [#5062](https://github.com/sebastianbergmann/phpunit/issues/5062) | `TestCase::expectError()` | 9.6.0 | | +| [#5062](https://github.com/sebastianbergmann/phpunit/issues/5062) | `TestCase::expectErrorMessage()` | 9.6.0 | | +| [#5062](https://github.com/sebastianbergmann/phpunit/issues/5062) | `TestCase::expectErrorMessageMatches()` | 9.6.0 | | +| [#5062](https://github.com/sebastianbergmann/phpunit/issues/5062) | `TestCase::expectNotice()` | 9.6.0 | | +| [#5062](https://github.com/sebastianbergmann/phpunit/issues/5062) | `TestCase::expectNoticeMessage()` | 9.6.0 | | +| [#5062](https://github.com/sebastianbergmann/phpunit/issues/5062) | `TestCase::expectNoticeMessageMatches()` | 9.6.0 | | +| [#5062](https://github.com/sebastianbergmann/phpunit/issues/5062) | `TestCase::expectWarning()` | 9.6.0 | | +| [#5062](https://github.com/sebastianbergmann/phpunit/issues/5062) | `TestCase::expectWarningMessage()` | 9.6.0 | | +| [#5062](https://github.com/sebastianbergmann/phpunit/issues/5062) | `TestCase::expectWarningMessageMatches()` | 9.6.0 | | +| [#4601](https://github.com/sebastianbergmann/phpunit/issues/4601) | `TestCase::assertClassHasAttribute()` | 9.6.1 | | +| [#4601](https://github.com/sebastianbergmann/phpunit/issues/4601) | `TestCase::assertClassNotHasAttribute()` | 9.6.1 | | +| [#4601](https://github.com/sebastianbergmann/phpunit/issues/4601) | `TestCase::assertClassHasStaticAttribute()` | 9.6.1 | | +| [#4601](https://github.com/sebastianbergmann/phpunit/issues/4601) | `TestCase::assertClassNotHasStaticAttribute()` | 9.6.1 | | +| [#4601](https://github.com/sebastianbergmann/phpunit/issues/4601) | `TestCase::assertObjectHasAttribute()` | 9.6.1 | `TestCase::assertObjectHasProperty()` | +| [#4601](https://github.com/sebastianbergmann/phpunit/issues/4601) | `TestCase::assertObjectNotHasAttribute()` | 9.6.1 | `TestCase::assertObjectNotHasProperty()` | +| [#4601](https://github.com/sebastianbergmann/phpunit/issues/4601) | `TestCase::classHasAttribute()` | 9.6.1 | | +| [#4601](https://github.com/sebastianbergmann/phpunit/issues/4601) | `TestCase::classHasStaticAttribute()` | 9.6.1 | | +| [#4601](https://github.com/sebastianbergmann/phpunit/issues/4601) | `TestCase::objectHasAttribute()` | 9.6.1 | | +| [#4601](https://github.com/sebastianbergmann/phpunit/issues/4601) | `ClassHasAttribute` | 9.6.1 | | +| [#4601](https://github.com/sebastianbergmann/phpunit/issues/4601) | `ClassHasStaticAttribute` | 9.6.1 | | +| [#4601](https://github.com/sebastianbergmann/phpunit/issues/4601) | `ObjectHasAttribute` | 9.6.1 | `ObjectHasProperty` | + +#### Test Double API + +| Issue | Description | Since | Replacement | +|-------------------------------------------------------------------|---------------------------------------|-------|-------------------------------------------------------------------------| +| [#4141](https://github.com/sebastianbergmann/phpunit/issues/4141) | `TestCase::prophesize()` | 9.1.0 | [phpspec/prophecy-phpunit](https://github.com/phpspec/prophecy-phpunit) | +| [#4297](https://github.com/sebastianbergmann/phpunit/issues/4297) | `TestCase::at()` | 9.3.0 | | +| [#4297](https://github.com/sebastianbergmann/phpunit/issues/4297) | `InvokedAtIndex` | 9.3.0 | | +| [#5063](https://github.com/sebastianbergmann/phpunit/issues/5063) | `InvocationMocker::withConsecutive()` | 9.6.0 | | +| [#5063](https://github.com/sebastianbergmann/phpunit/issues/5063) | `ConsecutiveParameters` | 9.6.0 | | +| [#5064](https://github.com/sebastianbergmann/phpunit/issues/5064) | `TestCase::getMockClass()` | 9.6.0 | | + +#### Miscellaneous + +| Issue | Description | Since | Replacement | +|-------------------------------------------------------------------|----------------------------------------------|-------|------------------------------------------------| +| [#5132](https://github.com/sebastianbergmann/phpunit/issues/5132) | `Test` suffix for abstract test case classes | | | +| | `TestCase::$backupGlobalsBlacklist` | 9.3.0 | `TestCase::$backupGlobalsExcludeList` | +| | `TestCase::$backupStaticAttributesBlacklist` | 9.3.0 | `TestCase::$backupStaticAttributesExcludeList` | + +### Extending PHPUnit + +| Issue | Description | Since | Replacement | +|-------------------------------------------------------------------|--------------------------------------|-------|-------------------------------------------------------------| +| [#4676](https://github.com/sebastianbergmann/phpunit/issues/4676) | `TestListener` | 8.0.0 | [Event System](https://docs.phpunit.de/en/10.3/events.html) | +| [#4039](https://github.com/sebastianbergmann/phpunit/issues/4039) | `Command::handleLoader()` | 9.1.0 | | +| [#4039](https://github.com/sebastianbergmann/phpunit/issues/4039) | `TestSuiteLoader` | 9.1.0 | | +| [#4039](https://github.com/sebastianbergmann/phpunit/issues/4039) | `StandardTestSuiteLoader` | 9.1.0 | | +| [#4676](https://github.com/sebastianbergmann/phpunit/issues/4676) | `TestListenerDefaultImplementation` | 8.2.4 | [Event System](https://docs.phpunit.de/en/10.3/events.html) | diff --git a/vendor/phpunit/phpunit/LICENSE b/vendor/phpunit/phpunit/LICENSE new file mode 100644 index 00000000..bdb57ec6 --- /dev/null +++ b/vendor/phpunit/phpunit/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2001-2024, Sebastian Bergmann +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/phpunit/phpunit/README.md b/vendor/phpunit/phpunit/README.md new file mode 100644 index 00000000..9fae13df --- /dev/null +++ b/vendor/phpunit/phpunit/README.md @@ -0,0 +1,36 @@ +# PHPUnit + +[![Latest Stable Version](https://poser.pugx.org/phpunit/phpunit/v/stable.png)](https://packagist.org/packages/phpunit/phpunit) +[![CI Status](https://github.com/sebastianbergmann/phpunit/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/phpunit/actions) +[![Type Coverage](https://shepherd.dev/github/sebastianbergmann/phpunit/coverage.svg)](https://shepherd.dev/github/sebastianbergmann/phpunit) +[![codecov](https://codecov.io/gh/sebastianbergmann/phpunit/branch/9.6/graph/badge.svg)](https://codecov.io/gh/sebastianbergmann/phpunit) + +PHPUnit is a programmer-oriented testing framework for PHP. It is an instance of the xUnit architecture for unit testing frameworks. + +## Installation + +We distribute a [PHP Archive (PHAR)](https://php.net/phar) that has all required (as well as some optional) dependencies of PHPUnit bundled in a single file: + +```bash +$ wget https://phar.phpunit.de/phpunit-X.Y.phar + +$ php phpunit-X.Y.phar --version +``` + +Please replace `X.Y` with the version of PHPUnit you are interested in. + +Alternatively, you may use [Composer](https://getcomposer.org/) to download and install PHPUnit as well as its dependencies. Please refer to the "[Getting Started](https://phpunit.de/getting-started-with-phpunit.html)" guide for details on how to install PHPUnit. + +## Contribute + +Please refer to [CONTRIBUTING.md](https://github.com/sebastianbergmann/phpunit/blob/main/.github/CONTRIBUTING.md) for information on how to contribute to PHPUnit and its related projects. + +## List of Contributors + +Thanks to everyone who has contributed to PHPUnit! You can find a detailed list of contributors on every PHPUnit related package on GitHub. This list shows only the major components: + +* [PHPUnit](https://github.com/sebastianbergmann/phpunit/graphs/contributors) +* [php-code-coverage](https://github.com/sebastianbergmann/php-code-coverage/graphs/contributors) + +A very special thanks to everyone who has contributed to the [documentation](https://github.com/sebastianbergmann/phpunit-documentation-english/graphs/contributors). + diff --git a/vendor/phpunit/phpunit/SECURITY.md b/vendor/phpunit/phpunit/SECURITY.md new file mode 100644 index 00000000..5f55c419 --- /dev/null +++ b/vendor/phpunit/phpunit/SECURITY.md @@ -0,0 +1,33 @@ +# Security Policy + +If you believe you have found a security vulnerability in PHPUnit, please report it to us through coordinated disclosure. + +**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** + +Instead, please email `sebastian@phpunit.de`. + +Please include as much of the information listed below as you can to help us better understand and resolve the issue: + +* The type of issue +* Full paths of source file(s) related to the manifestation of the issue +* The location of the affected source code (tag/branch/commit or direct URL) +* Any special configuration required to reproduce the issue +* Step-by-step instructions to reproduce the issue +* Proof-of-concept or exploit code (if possible) +* Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +## Web Context + +PHPUnit is a framework for writing as well as a command-line tool for running tests. Writing and running tests is a development-time activity. There is no reason why PHPUnit should be installed on a webserver and/or in a production environment. + +**If you upload PHPUnit to a webserver then your deployment process is broken. On a more general note, if your `vendor` directory is publicly accessible on your webserver then your deployment process is also broken.** + +Please note that if you upload PHPUnit to a webserver "bad things" may happen. [You have been warned.](https://thephp.cc/articles/phpunit-a-security-risk?ref=phpunit) + +PHPUnit is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using PHPUnit in an HTTP or web context or with untrusted input data is performed. PHPUnit might also contain functionality that intentionally exposes internal application data for debugging purposes. + +If PHPUnit is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context. + +Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes. diff --git a/vendor/phpunit/phpunit/composer.json b/vendor/phpunit/phpunit/composer.json new file mode 100644 index 00000000..42e16c6f --- /dev/null +++ b/vendor/phpunit/phpunit/composer.json @@ -0,0 +1,90 @@ +{ + "name": "phpunit/phpunit", + "description": "The PHP Unit Testing framework.", + "type": "library", + "keywords": [ + "phpunit", + "xunit", + "testing" + ], + "homepage": "https://phpunit.de/", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy" + }, + "prefer-stable": true, + "require": { + "php": ">=7.3", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "doctrine/instantiator": "^1.3.1 || ^2", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "phpunit/php-code-coverage": "^9.2.28", + "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.3", + "phpunit/php-timer": "^5.0.2", + "sebastian/cli-parser": "^1.0.1", + "sebastian/code-unit": "^1.0.6", + "sebastian/comparator": "^4.0.8", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.5", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^3.2", + "sebastian/version": "^3.0.2" + }, + "config": { + "platform": { + "php": "7.3.0" + }, + "optimize-autoloader": true, + "sort-packages": true + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "bin": [ + "phpunit" + ], + "autoload": { + "classmap": [ + "src/" + ], + "files": [ + "src/Framework/Assert/Functions.php" + ] + }, + "autoload-dev": { + "classmap": [ + "tests/" + ], + "files": [ + "tests/_files/CoverageNamespacedFunctionTest.php", + "tests/_files/CoveredFunction.php", + "tests/_files/NamespaceCoveredFunction.php" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "9.6-dev" + } + } +} diff --git a/vendor/phpunit/phpunit/composer.lock b/vendor/phpunit/phpunit/composer.lock new file mode 100644 index 00000000..6965da45 --- /dev/null +++ b/vendor/phpunit/phpunit/composer.lock @@ -0,0 +1,1659 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "a33593542389289bb642826edd1be24d", + "packages": [ + { + "name": "doctrine/instantiator", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^11", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.30 || ^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.5.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2022-12-30T00:15:36+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.11.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3,<3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2023-03-08T13:26:56+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.18.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bcbb2179f97633e98bbbc87044ee2611c7d7999", + "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.0" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.18.0" + }, + "time": "2023-12-10T21:03:43+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.3" + }, + "time": "2021-07-20T11:28:43+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.30", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca2bd87d2f9215904682a9cb9bb37dda98e76089", + "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0.3", + "sebastian/version": "^3.0.1", + "theseer/tokenizer": "^1.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.30" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T06:47:57+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-12-02T12:48:52+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:08:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:08:54+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:30:19+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-09-14T12:41:17+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T06:19:30+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-05-07T05:35:17+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:03:51+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-09-14T06:03:37+00:00" + }, + { + "name": "sebastian/global-state", + "version": "5.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bde739e7565280bda77be70044ac1047bc007e34" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34", + "reference": "bde739e7565280bda77be70044ac1047bc007e34", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-02T09:26:13+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T06:20:34+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:12:34+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:14:26+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:07:39+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:45:17+00:00" + }, + { + "name": "sebastian/type", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:13:03+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.2" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2023-11-20T00:12:19+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": ">=7.3", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*" + }, + "platform-dev": [], + "platform-overrides": { + "php": "7.3.0" + }, + "plugin-api-version": "2.6.0" +} diff --git a/vendor/phpunit/phpunit/phpunit b/vendor/phpunit/phpunit/phpunit new file mode 100755 index 00000000..b9f5cf29 --- /dev/null +++ b/vendor/phpunit/phpunit/phpunit @@ -0,0 +1,107 @@ +#!/usr/bin/env php + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (!version_compare(PHP_VERSION, PHP_VERSION, '=')) { + fwrite( + STDERR, + sprintf( + '%s declares an invalid value for PHP_VERSION.' . PHP_EOL . + 'This breaks fundamental functionality such as version_compare().' . PHP_EOL . + 'Please use a different PHP interpreter.' . PHP_EOL, + + PHP_BINARY + ) + ); + + die(1); +} + +if (version_compare('7.3.0', PHP_VERSION, '>')) { + fwrite( + STDERR, + sprintf( + 'This version of PHPUnit requires PHP >= 7.3.' . PHP_EOL . + 'You are using PHP %s (%s).' . PHP_EOL, + PHP_VERSION, + PHP_BINARY + ) + ); + + die(1); +} + +$requiredExtensions = ['dom', 'json', 'libxml', 'mbstring', 'tokenizer', 'xml', 'xmlwriter']; + +$unavailableExtensions = array_filter( + $requiredExtensions, + static function ($extension) { + return !extension_loaded($extension); + } +); + +if ([] !== $unavailableExtensions) { + fwrite( + STDERR, + sprintf( + 'PHPUnit requires the "%s" extensions, but the "%s" %s not available.' . PHP_EOL, + implode('", "', $requiredExtensions), + implode('", "', $unavailableExtensions), + count($unavailableExtensions) === 1 ? 'extension is' : 'extensions are' + ) + ); + + die(1); +} + +unset($requiredExtensions, $unavailableExtensions); + +if (!ini_get('date.timezone')) { + ini_set('date.timezone', 'UTC'); +} + +if (isset($GLOBALS['_composer_autoload_path'])) { + define('PHPUNIT_COMPOSER_INSTALL', $GLOBALS['_composer_autoload_path']); + + unset($GLOBALS['_composer_autoload_path']); +} else { + foreach (array(__DIR__ . '/../../autoload.php', __DIR__ . '/../vendor/autoload.php', __DIR__ . '/vendor/autoload.php') as $file) { + if (file_exists($file)) { + define('PHPUNIT_COMPOSER_INSTALL', $file); + + break; + } + } + + unset($file); +} + +if (!defined('PHPUNIT_COMPOSER_INSTALL')) { + fwrite( + STDERR, + 'You need to set up the project dependencies using Composer:' . PHP_EOL . PHP_EOL . + ' composer install' . PHP_EOL . PHP_EOL . + 'You can learn all about Composer on https://getcomposer.org/.' . PHP_EOL + ); + + die(1); +} + +$options = getopt('', array('prepend:')); + +if (isset($options['prepend'])) { + require $options['prepend']; +} + +unset($options); + +require PHPUNIT_COMPOSER_INSTALL; + +PHPUnit\TextUI\Command::main(); diff --git a/vendor/phpunit/phpunit/phpunit.xsd b/vendor/phpunit/phpunit/phpunit.xsd new file mode 100644 index 00000000..619434ef --- /dev/null +++ b/vendor/phpunit/phpunit/phpunit.xsd @@ -0,0 +1,330 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 9.6 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/phpunit/phpunit/schema/8.5.xsd b/vendor/phpunit/phpunit/schema/8.5.xsd new file mode 100644 index 00000000..75e22289 --- /dev/null +++ b/vendor/phpunit/phpunit/schema/8.5.xsd @@ -0,0 +1,319 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 8.5 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/phpunit/phpunit/schema/9.0.xsd b/vendor/phpunit/phpunit/schema/9.0.xsd new file mode 100644 index 00000000..6db04c09 --- /dev/null +++ b/vendor/phpunit/phpunit/schema/9.0.xsd @@ -0,0 +1,315 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 9.0 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/phpunit/phpunit/schema/9.1.xsd b/vendor/phpunit/phpunit/schema/9.1.xsd new file mode 100644 index 00000000..b10d30b4 --- /dev/null +++ b/vendor/phpunit/phpunit/schema/9.1.xsd @@ -0,0 +1,317 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 9.0 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/phpunit/phpunit/schema/9.2.xsd b/vendor/phpunit/phpunit/schema/9.2.xsd new file mode 100644 index 00000000..d770e8b0 --- /dev/null +++ b/vendor/phpunit/phpunit/schema/9.2.xsd @@ -0,0 +1,317 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 9.2 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/phpunit/phpunit/schema/9.3.xsd b/vendor/phpunit/phpunit/schema/9.3.xsd new file mode 100644 index 00000000..638f663a --- /dev/null +++ b/vendor/phpunit/phpunit/schema/9.3.xsd @@ -0,0 +1,327 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 9.3 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/phpunit/phpunit/schema/9.4.xsd b/vendor/phpunit/phpunit/schema/9.4.xsd new file mode 100644 index 00000000..75a91e83 --- /dev/null +++ b/vendor/phpunit/phpunit/schema/9.4.xsd @@ -0,0 +1,328 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 9.4 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/phpunit/phpunit/schema/9.5.xsd b/vendor/phpunit/phpunit/schema/9.5.xsd new file mode 100644 index 00000000..eabefac3 --- /dev/null +++ b/vendor/phpunit/phpunit/schema/9.5.xsd @@ -0,0 +1,330 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 9.5 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/phpunit/phpunit/src/Exception.php b/vendor/phpunit/phpunit/src/Exception.php new file mode 100644 index 00000000..4e7c3335 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Exception.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit; + +use Throwable; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface Exception extends Throwable +{ +} diff --git a/vendor/phpunit/phpunit/src/Framework/Assert.php b/vendor/phpunit/phpunit/src/Framework/Assert.php new file mode 100644 index 00000000..ea8f17d2 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Assert.php @@ -0,0 +1,2963 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use const DEBUG_BACKTRACE_IGNORE_ARGS; +use const PHP_EOL; +use function array_shift; +use function array_unshift; +use function assert; +use function class_exists; +use function count; +use function debug_backtrace; +use function explode; +use function file_get_contents; +use function func_get_args; +use function implode; +use function interface_exists; +use function is_array; +use function is_bool; +use function is_int; +use function is_iterable; +use function is_object; +use function is_string; +use function preg_match; +use function preg_split; +use function sprintf; +use function strpos; +use ArrayAccess; +use Countable; +use DOMAttr; +use DOMDocument; +use DOMElement; +use Generator; +use PHPUnit\Framework\Constraint\ArrayHasKey; +use PHPUnit\Framework\Constraint\Callback; +use PHPUnit\Framework\Constraint\ClassHasAttribute; +use PHPUnit\Framework\Constraint\ClassHasStaticAttribute; +use PHPUnit\Framework\Constraint\Constraint; +use PHPUnit\Framework\Constraint\Count; +use PHPUnit\Framework\Constraint\DirectoryExists; +use PHPUnit\Framework\Constraint\FileExists; +use PHPUnit\Framework\Constraint\GreaterThan; +use PHPUnit\Framework\Constraint\IsAnything; +use PHPUnit\Framework\Constraint\IsEmpty; +use PHPUnit\Framework\Constraint\IsEqual; +use PHPUnit\Framework\Constraint\IsEqualCanonicalizing; +use PHPUnit\Framework\Constraint\IsEqualIgnoringCase; +use PHPUnit\Framework\Constraint\IsEqualWithDelta; +use PHPUnit\Framework\Constraint\IsFalse; +use PHPUnit\Framework\Constraint\IsFinite; +use PHPUnit\Framework\Constraint\IsIdentical; +use PHPUnit\Framework\Constraint\IsInfinite; +use PHPUnit\Framework\Constraint\IsInstanceOf; +use PHPUnit\Framework\Constraint\IsJson; +use PHPUnit\Framework\Constraint\IsNan; +use PHPUnit\Framework\Constraint\IsNull; +use PHPUnit\Framework\Constraint\IsReadable; +use PHPUnit\Framework\Constraint\IsTrue; +use PHPUnit\Framework\Constraint\IsType; +use PHPUnit\Framework\Constraint\IsWritable; +use PHPUnit\Framework\Constraint\JsonMatches; +use PHPUnit\Framework\Constraint\LessThan; +use PHPUnit\Framework\Constraint\LogicalAnd; +use PHPUnit\Framework\Constraint\LogicalNot; +use PHPUnit\Framework\Constraint\LogicalOr; +use PHPUnit\Framework\Constraint\LogicalXor; +use PHPUnit\Framework\Constraint\ObjectEquals; +use PHPUnit\Framework\Constraint\ObjectHasAttribute; +use PHPUnit\Framework\Constraint\ObjectHasProperty; +use PHPUnit\Framework\Constraint\RegularExpression; +use PHPUnit\Framework\Constraint\SameSize; +use PHPUnit\Framework\Constraint\StringContains; +use PHPUnit\Framework\Constraint\StringEndsWith; +use PHPUnit\Framework\Constraint\StringMatchesFormatDescription; +use PHPUnit\Framework\Constraint\StringStartsWith; +use PHPUnit\Framework\Constraint\TraversableContainsEqual; +use PHPUnit\Framework\Constraint\TraversableContainsIdentical; +use PHPUnit\Framework\Constraint\TraversableContainsOnly; +use PHPUnit\Util\Type; +use PHPUnit\Util\Xml; +use PHPUnit\Util\Xml\Loader as XmlLoader; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +abstract class Assert +{ + /** + * @var int + */ + private static $count = 0; + + /** + * Asserts that an array has a specified key. + * + * @param int|string $key + * @param array|ArrayAccess $array + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * @throws ExpectationFailedException + */ + public static function assertArrayHasKey($key, $array, string $message = ''): void + { + if (!(is_int($key) || is_string($key))) { + throw InvalidArgumentException::create( + 1, + 'integer or string', + ); + } + + if (!(is_array($array) || $array instanceof ArrayAccess)) { + throw InvalidArgumentException::create( + 2, + 'array or ArrayAccess', + ); + } + + $constraint = new ArrayHasKey($key); + + static::assertThat($array, $constraint, $message); + } + + /** + * Asserts that an array does not have a specified key. + * + * @param int|string $key + * @param array|ArrayAccess $array + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * @throws ExpectationFailedException + */ + public static function assertArrayNotHasKey($key, $array, string $message = ''): void + { + if (!(is_int($key) || is_string($key))) { + throw InvalidArgumentException::create( + 1, + 'integer or string', + ); + } + + if (!(is_array($array) || $array instanceof ArrayAccess)) { + throw InvalidArgumentException::create( + 2, + 'array or ArrayAccess', + ); + } + + $constraint = new LogicalNot( + new ArrayHasKey($key), + ); + + static::assertThat($array, $constraint, $message); + } + + /** + * Asserts that a haystack contains a needle. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * @throws ExpectationFailedException + */ + public static function assertContains($needle, iterable $haystack, string $message = ''): void + { + $constraint = new TraversableContainsIdentical($needle); + + static::assertThat($haystack, $constraint, $message); + } + + public static function assertContainsEquals($needle, iterable $haystack, string $message = ''): void + { + $constraint = new TraversableContainsEqual($needle); + + static::assertThat($haystack, $constraint, $message); + } + + /** + * Asserts that a haystack does not contain a needle. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * @throws ExpectationFailedException + */ + public static function assertNotContains($needle, iterable $haystack, string $message = ''): void + { + $constraint = new LogicalNot( + new TraversableContainsIdentical($needle), + ); + + static::assertThat($haystack, $constraint, $message); + } + + public static function assertNotContainsEquals($needle, iterable $haystack, string $message = ''): void + { + $constraint = new LogicalNot(new TraversableContainsEqual($needle)); + + static::assertThat($haystack, $constraint, $message); + } + + /** + * Asserts that a haystack contains only values of a given type. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertContainsOnly(string $type, iterable $haystack, ?bool $isNativeType = null, string $message = ''): void + { + if ($isNativeType === null) { + $isNativeType = Type::isType($type); + } + + static::assertThat( + $haystack, + new TraversableContainsOnly( + $type, + $isNativeType, + ), + $message, + ); + } + + /** + * Asserts that a haystack contains only instances of a given class name. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertContainsOnlyInstancesOf(string $className, iterable $haystack, string $message = ''): void + { + static::assertThat( + $haystack, + new TraversableContainsOnly( + $className, + false, + ), + $message, + ); + } + + /** + * Asserts that a haystack does not contain only values of a given type. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertNotContainsOnly(string $type, iterable $haystack, ?bool $isNativeType = null, string $message = ''): void + { + if ($isNativeType === null) { + $isNativeType = Type::isType($type); + } + + static::assertThat( + $haystack, + new LogicalNot( + new TraversableContainsOnly( + $type, + $isNativeType, + ), + ), + $message, + ); + } + + /** + * Asserts the number of elements of an array, Countable or Traversable. + * + * @param Countable|iterable $haystack + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * @throws ExpectationFailedException + */ + public static function assertCount(int $expectedCount, $haystack, string $message = ''): void + { + if ($haystack instanceof Generator) { + self::createWarning('Passing an argument of type Generator for the $haystack parameter is deprecated. Support for this will be removed in PHPUnit 10.'); + } + + if (!$haystack instanceof Countable && !is_iterable($haystack)) { + throw InvalidArgumentException::create(2, 'countable or iterable'); + } + + static::assertThat( + $haystack, + new Count($expectedCount), + $message, + ); + } + + /** + * Asserts the number of elements of an array, Countable or Traversable. + * + * @param Countable|iterable $haystack + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * @throws ExpectationFailedException + */ + public static function assertNotCount(int $expectedCount, $haystack, string $message = ''): void + { + if ($haystack instanceof Generator) { + self::createWarning('Passing an argument of type Generator for the $haystack parameter is deprecated. Support for this will be removed in PHPUnit 10.'); + } + + if (!$haystack instanceof Countable && !is_iterable($haystack)) { + throw InvalidArgumentException::create(2, 'countable or iterable'); + } + + $constraint = new LogicalNot( + new Count($expectedCount), + ); + + static::assertThat($haystack, $constraint, $message); + } + + /** + * Asserts that two variables are equal. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertEquals($expected, $actual, string $message = ''): void + { + $constraint = new IsEqual($expected); + + static::assertThat($actual, $constraint, $message); + } + + /** + * Asserts that two variables are equal (canonicalizing). + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertEqualsCanonicalizing($expected, $actual, string $message = ''): void + { + $constraint = new IsEqualCanonicalizing($expected); + + static::assertThat($actual, $constraint, $message); + } + + /** + * Asserts that two variables are equal (ignoring case). + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertEqualsIgnoringCase($expected, $actual, string $message = ''): void + { + $constraint = new IsEqualIgnoringCase($expected); + + static::assertThat($actual, $constraint, $message); + } + + /** + * Asserts that two variables are equal (with delta). + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertEqualsWithDelta($expected, $actual, float $delta, string $message = ''): void + { + $constraint = new IsEqualWithDelta( + $expected, + $delta, + ); + + static::assertThat($actual, $constraint, $message); + } + + /** + * Asserts that two variables are not equal. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertNotEquals($expected, $actual, string $message = ''): void + { + $constraint = new LogicalNot( + new IsEqual($expected), + ); + + static::assertThat($actual, $constraint, $message); + } + + /** + * Asserts that two variables are not equal (canonicalizing). + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertNotEqualsCanonicalizing($expected, $actual, string $message = ''): void + { + $constraint = new LogicalNot( + new IsEqualCanonicalizing($expected), + ); + + static::assertThat($actual, $constraint, $message); + } + + /** + * Asserts that two variables are not equal (ignoring case). + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertNotEqualsIgnoringCase($expected, $actual, string $message = ''): void + { + $constraint = new LogicalNot( + new IsEqualIgnoringCase($expected), + ); + + static::assertThat($actual, $constraint, $message); + } + + /** + * Asserts that two variables are not equal (with delta). + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertNotEqualsWithDelta($expected, $actual, float $delta, string $message = ''): void + { + $constraint = new LogicalNot( + new IsEqualWithDelta( + $expected, + $delta, + ), + ); + + static::assertThat($actual, $constraint, $message); + } + + /** + * @throws ExpectationFailedException + */ + public static function assertObjectEquals(object $expected, object $actual, string $method = 'equals', string $message = ''): void + { + static::assertThat( + $actual, + static::objectEquals($expected, $method), + $message, + ); + } + + /** + * Asserts that a variable is empty. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert empty $actual + */ + public static function assertEmpty($actual, string $message = ''): void + { + if ($actual instanceof Generator) { + self::createWarning('Passing an argument of type Generator for the $actual parameter is deprecated. Support for this will be removed in PHPUnit 10.'); + } + + static::assertThat($actual, static::isEmpty(), $message); + } + + /** + * Asserts that a variable is not empty. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert !empty $actual + */ + public static function assertNotEmpty($actual, string $message = ''): void + { + if ($actual instanceof Generator) { + self::createWarning('Passing an argument of type Generator for the $actual parameter is deprecated. Support for this will be removed in PHPUnit 10.'); + } + + static::assertThat($actual, static::logicalNot(static::isEmpty()), $message); + } + + /** + * Asserts that a value is greater than another value. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertGreaterThan($expected, $actual, string $message = ''): void + { + static::assertThat($actual, static::greaterThan($expected), $message); + } + + /** + * Asserts that a value is greater than or equal to another value. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertGreaterThanOrEqual($expected, $actual, string $message = ''): void + { + static::assertThat( + $actual, + static::greaterThanOrEqual($expected), + $message, + ); + } + + /** + * Asserts that a value is smaller than another value. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertLessThan($expected, $actual, string $message = ''): void + { + static::assertThat($actual, static::lessThan($expected), $message); + } + + /** + * Asserts that a value is smaller than or equal to another value. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertLessThanOrEqual($expected, $actual, string $message = ''): void + { + static::assertThat($actual, static::lessThanOrEqual($expected), $message); + } + + /** + * Asserts that the contents of one file is equal to the contents of another + * file. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertFileEquals(string $expected, string $actual, string $message = ''): void + { + static::assertFileExists($expected, $message); + static::assertFileExists($actual, $message); + + $constraint = new IsEqual(file_get_contents($expected)); + + static::assertThat(file_get_contents($actual), $constraint, $message); + } + + /** + * Asserts that the contents of one file is equal to the contents of another + * file (canonicalizing). + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertFileEqualsCanonicalizing(string $expected, string $actual, string $message = ''): void + { + static::assertFileExists($expected, $message); + static::assertFileExists($actual, $message); + + $constraint = new IsEqualCanonicalizing( + file_get_contents($expected), + ); + + static::assertThat(file_get_contents($actual), $constraint, $message); + } + + /** + * Asserts that the contents of one file is equal to the contents of another + * file (ignoring case). + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertFileEqualsIgnoringCase(string $expected, string $actual, string $message = ''): void + { + static::assertFileExists($expected, $message); + static::assertFileExists($actual, $message); + + $constraint = new IsEqualIgnoringCase(file_get_contents($expected)); + + static::assertThat(file_get_contents($actual), $constraint, $message); + } + + /** + * Asserts that the contents of one file is not equal to the contents of + * another file. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertFileNotEquals(string $expected, string $actual, string $message = ''): void + { + static::assertFileExists($expected, $message); + static::assertFileExists($actual, $message); + + $constraint = new LogicalNot( + new IsEqual(file_get_contents($expected)), + ); + + static::assertThat(file_get_contents($actual), $constraint, $message); + } + + /** + * Asserts that the contents of one file is not equal to the contents of another + * file (canonicalizing). + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertFileNotEqualsCanonicalizing(string $expected, string $actual, string $message = ''): void + { + static::assertFileExists($expected, $message); + static::assertFileExists($actual, $message); + + $constraint = new LogicalNot( + new IsEqualCanonicalizing(file_get_contents($expected)), + ); + + static::assertThat(file_get_contents($actual), $constraint, $message); + } + + /** + * Asserts that the contents of one file is not equal to the contents of another + * file (ignoring case). + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertFileNotEqualsIgnoringCase(string $expected, string $actual, string $message = ''): void + { + static::assertFileExists($expected, $message); + static::assertFileExists($actual, $message); + + $constraint = new LogicalNot( + new IsEqualIgnoringCase(file_get_contents($expected)), + ); + + static::assertThat(file_get_contents($actual), $constraint, $message); + } + + /** + * Asserts that the contents of a string is equal + * to the contents of a file. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertStringEqualsFile(string $expectedFile, string $actualString, string $message = ''): void + { + static::assertFileExists($expectedFile, $message); + + $constraint = new IsEqual(file_get_contents($expectedFile)); + + static::assertThat($actualString, $constraint, $message); + } + + /** + * Asserts that the contents of a string is equal + * to the contents of a file (canonicalizing). + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertStringEqualsFileCanonicalizing(string $expectedFile, string $actualString, string $message = ''): void + { + static::assertFileExists($expectedFile, $message); + + $constraint = new IsEqualCanonicalizing(file_get_contents($expectedFile)); + + static::assertThat($actualString, $constraint, $message); + } + + /** + * Asserts that the contents of a string is equal + * to the contents of a file (ignoring case). + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertStringEqualsFileIgnoringCase(string $expectedFile, string $actualString, string $message = ''): void + { + static::assertFileExists($expectedFile, $message); + + $constraint = new IsEqualIgnoringCase(file_get_contents($expectedFile)); + + static::assertThat($actualString, $constraint, $message); + } + + /** + * Asserts that the contents of a string is not equal + * to the contents of a file. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertStringNotEqualsFile(string $expectedFile, string $actualString, string $message = ''): void + { + static::assertFileExists($expectedFile, $message); + + $constraint = new LogicalNot( + new IsEqual(file_get_contents($expectedFile)), + ); + + static::assertThat($actualString, $constraint, $message); + } + + /** + * Asserts that the contents of a string is not equal + * to the contents of a file (canonicalizing). + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertStringNotEqualsFileCanonicalizing(string $expectedFile, string $actualString, string $message = ''): void + { + static::assertFileExists($expectedFile, $message); + + $constraint = new LogicalNot( + new IsEqualCanonicalizing(file_get_contents($expectedFile)), + ); + + static::assertThat($actualString, $constraint, $message); + } + + /** + * Asserts that the contents of a string is not equal + * to the contents of a file (ignoring case). + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertStringNotEqualsFileIgnoringCase(string $expectedFile, string $actualString, string $message = ''): void + { + static::assertFileExists($expectedFile, $message); + + $constraint = new LogicalNot( + new IsEqualIgnoringCase(file_get_contents($expectedFile)), + ); + + static::assertThat($actualString, $constraint, $message); + } + + /** + * Asserts that a file/dir is readable. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertIsReadable(string $filename, string $message = ''): void + { + static::assertThat($filename, new IsReadable, $message); + } + + /** + * Asserts that a file/dir exists and is not readable. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertIsNotReadable(string $filename, string $message = ''): void + { + static::assertThat($filename, new LogicalNot(new IsReadable), $message); + } + + /** + * Asserts that a file/dir exists and is not readable. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @codeCoverageIgnore + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4062 + */ + public static function assertNotIsReadable(string $filename, string $message = ''): void + { + self::createWarning('assertNotIsReadable() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertIsNotReadable() instead.'); + + static::assertThat($filename, new LogicalNot(new IsReadable), $message); + } + + /** + * Asserts that a file/dir exists and is writable. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertIsWritable(string $filename, string $message = ''): void + { + static::assertThat($filename, new IsWritable, $message); + } + + /** + * Asserts that a file/dir exists and is not writable. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertIsNotWritable(string $filename, string $message = ''): void + { + static::assertThat($filename, new LogicalNot(new IsWritable), $message); + } + + /** + * Asserts that a file/dir exists and is not writable. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @codeCoverageIgnore + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4065 + */ + public static function assertNotIsWritable(string $filename, string $message = ''): void + { + self::createWarning('assertNotIsWritable() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertIsNotWritable() instead.'); + + static::assertThat($filename, new LogicalNot(new IsWritable), $message); + } + + /** + * Asserts that a directory exists. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertDirectoryExists(string $directory, string $message = ''): void + { + static::assertThat($directory, new DirectoryExists, $message); + } + + /** + * Asserts that a directory does not exist. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertDirectoryDoesNotExist(string $directory, string $message = ''): void + { + static::assertThat($directory, new LogicalNot(new DirectoryExists), $message); + } + + /** + * Asserts that a directory does not exist. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @codeCoverageIgnore + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4068 + */ + public static function assertDirectoryNotExists(string $directory, string $message = ''): void + { + self::createWarning('assertDirectoryNotExists() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertDirectoryDoesNotExist() instead.'); + + static::assertThat($directory, new LogicalNot(new DirectoryExists), $message); + } + + /** + * Asserts that a directory exists and is readable. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertDirectoryIsReadable(string $directory, string $message = ''): void + { + self::assertDirectoryExists($directory, $message); + self::assertIsReadable($directory, $message); + } + + /** + * Asserts that a directory exists and is not readable. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertDirectoryIsNotReadable(string $directory, string $message = ''): void + { + self::assertDirectoryExists($directory, $message); + self::assertIsNotReadable($directory, $message); + } + + /** + * Asserts that a directory exists and is not readable. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @codeCoverageIgnore + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4071 + */ + public static function assertDirectoryNotIsReadable(string $directory, string $message = ''): void + { + self::createWarning('assertDirectoryNotIsReadable() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertDirectoryIsNotReadable() instead.'); + + self::assertDirectoryExists($directory, $message); + self::assertIsNotReadable($directory, $message); + } + + /** + * Asserts that a directory exists and is writable. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertDirectoryIsWritable(string $directory, string $message = ''): void + { + self::assertDirectoryExists($directory, $message); + self::assertIsWritable($directory, $message); + } + + /** + * Asserts that a directory exists and is not writable. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertDirectoryIsNotWritable(string $directory, string $message = ''): void + { + self::assertDirectoryExists($directory, $message); + self::assertIsNotWritable($directory, $message); + } + + /** + * Asserts that a directory exists and is not writable. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @codeCoverageIgnore + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4074 + */ + public static function assertDirectoryNotIsWritable(string $directory, string $message = ''): void + { + self::createWarning('assertDirectoryNotIsWritable() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertDirectoryIsNotWritable() instead.'); + + self::assertDirectoryExists($directory, $message); + self::assertIsNotWritable($directory, $message); + } + + /** + * Asserts that a file exists. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertFileExists(string $filename, string $message = ''): void + { + static::assertThat($filename, new FileExists, $message); + } + + /** + * Asserts that a file does not exist. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertFileDoesNotExist(string $filename, string $message = ''): void + { + static::assertThat($filename, new LogicalNot(new FileExists), $message); + } + + /** + * Asserts that a file does not exist. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @codeCoverageIgnore + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4077 + */ + public static function assertFileNotExists(string $filename, string $message = ''): void + { + self::createWarning('assertFileNotExists() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertFileDoesNotExist() instead.'); + + static::assertThat($filename, new LogicalNot(new FileExists), $message); + } + + /** + * Asserts that a file exists and is readable. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertFileIsReadable(string $file, string $message = ''): void + { + self::assertFileExists($file, $message); + self::assertIsReadable($file, $message); + } + + /** + * Asserts that a file exists and is not readable. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertFileIsNotReadable(string $file, string $message = ''): void + { + self::assertFileExists($file, $message); + self::assertIsNotReadable($file, $message); + } + + /** + * Asserts that a file exists and is not readable. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @codeCoverageIgnore + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4080 + */ + public static function assertFileNotIsReadable(string $file, string $message = ''): void + { + self::createWarning('assertFileNotIsReadable() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertFileIsNotReadable() instead.'); + + self::assertFileExists($file, $message); + self::assertIsNotReadable($file, $message); + } + + /** + * Asserts that a file exists and is writable. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertFileIsWritable(string $file, string $message = ''): void + { + self::assertFileExists($file, $message); + self::assertIsWritable($file, $message); + } + + /** + * Asserts that a file exists and is not writable. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertFileIsNotWritable(string $file, string $message = ''): void + { + self::assertFileExists($file, $message); + self::assertIsNotWritable($file, $message); + } + + /** + * Asserts that a file exists and is not writable. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @codeCoverageIgnore + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4083 + */ + public static function assertFileNotIsWritable(string $file, string $message = ''): void + { + self::createWarning('assertFileNotIsWritable() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertFileIsNotWritable() instead.'); + + self::assertFileExists($file, $message); + self::assertIsNotWritable($file, $message); + } + + /** + * Asserts that a condition is true. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert true $condition + */ + public static function assertTrue($condition, string $message = ''): void + { + static::assertThat($condition, static::isTrue(), $message); + } + + /** + * Asserts that a condition is not true. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert !true $condition + */ + public static function assertNotTrue($condition, string $message = ''): void + { + static::assertThat($condition, static::logicalNot(static::isTrue()), $message); + } + + /** + * Asserts that a condition is false. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert false $condition + */ + public static function assertFalse($condition, string $message = ''): void + { + static::assertThat($condition, static::isFalse(), $message); + } + + /** + * Asserts that a condition is not false. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert !false $condition + */ + public static function assertNotFalse($condition, string $message = ''): void + { + static::assertThat($condition, static::logicalNot(static::isFalse()), $message); + } + + /** + * Asserts that a variable is null. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert null $actual + */ + public static function assertNull($actual, string $message = ''): void + { + static::assertThat($actual, static::isNull(), $message); + } + + /** + * Asserts that a variable is not null. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert !null $actual + */ + public static function assertNotNull($actual, string $message = ''): void + { + static::assertThat($actual, static::logicalNot(static::isNull()), $message); + } + + /** + * Asserts that a variable is finite. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertFinite($actual, string $message = ''): void + { + static::assertThat($actual, static::isFinite(), $message); + } + + /** + * Asserts that a variable is infinite. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertInfinite($actual, string $message = ''): void + { + static::assertThat($actual, static::isInfinite(), $message); + } + + /** + * Asserts that a variable is nan. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertNan($actual, string $message = ''): void + { + static::assertThat($actual, static::isNan(), $message); + } + + /** + * Asserts that a class has a specified attribute. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * @throws ExpectationFailedException + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4601 + */ + public static function assertClassHasAttribute(string $attributeName, string $className, string $message = ''): void + { + self::createWarning('assertClassHasAttribute() is deprecated and will be removed in PHPUnit 10.'); + + if (!self::isValidClassAttributeName($attributeName)) { + throw InvalidArgumentException::create(1, 'valid attribute name'); + } + + if (!class_exists($className)) { + throw InvalidArgumentException::create(2, 'class name'); + } + + static::assertThat($className, new ClassHasAttribute($attributeName), $message); + } + + /** + * Asserts that a class does not have a specified attribute. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * @throws ExpectationFailedException + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4601 + */ + public static function assertClassNotHasAttribute(string $attributeName, string $className, string $message = ''): void + { + self::createWarning('assertClassNotHasAttribute() is deprecated and will be removed in PHPUnit 10.'); + + if (!self::isValidClassAttributeName($attributeName)) { + throw InvalidArgumentException::create(1, 'valid attribute name'); + } + + if (!class_exists($className)) { + throw InvalidArgumentException::create(2, 'class name'); + } + + static::assertThat( + $className, + new LogicalNot( + new ClassHasAttribute($attributeName), + ), + $message, + ); + } + + /** + * Asserts that a class has a specified static attribute. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * @throws ExpectationFailedException + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4601 + */ + public static function assertClassHasStaticAttribute(string $attributeName, string $className, string $message = ''): void + { + self::createWarning('assertClassHasStaticAttribute() is deprecated and will be removed in PHPUnit 10.'); + + if (!self::isValidClassAttributeName($attributeName)) { + throw InvalidArgumentException::create(1, 'valid attribute name'); + } + + if (!class_exists($className)) { + throw InvalidArgumentException::create(2, 'class name'); + } + + static::assertThat( + $className, + new ClassHasStaticAttribute($attributeName), + $message, + ); + } + + /** + * Asserts that a class does not have a specified static attribute. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * @throws ExpectationFailedException + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4601 + */ + public static function assertClassNotHasStaticAttribute(string $attributeName, string $className, string $message = ''): void + { + self::createWarning('assertClassNotHasStaticAttribute() is deprecated and will be removed in PHPUnit 10.'); + + if (!self::isValidClassAttributeName($attributeName)) { + throw InvalidArgumentException::create(1, 'valid attribute name'); + } + + if (!class_exists($className)) { + throw InvalidArgumentException::create(2, 'class name'); + } + + static::assertThat( + $className, + new LogicalNot( + new ClassHasStaticAttribute($attributeName), + ), + $message, + ); + } + + /** + * Asserts that an object has a specified attribute. + * + * @param object $object + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * @throws ExpectationFailedException + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4601 + */ + public static function assertObjectHasAttribute(string $attributeName, $object, string $message = ''): void + { + self::createWarning('assertObjectHasAttribute() is deprecated and will be removed in PHPUnit 10. Refactor your test to use assertObjectHasProperty() instead.'); + + if (!self::isValidObjectAttributeName($attributeName)) { + throw InvalidArgumentException::create(1, 'valid attribute name'); + } + + if (!is_object($object)) { + throw InvalidArgumentException::create(2, 'object'); + } + + static::assertThat( + $object, + new ObjectHasAttribute($attributeName), + $message, + ); + } + + /** + * Asserts that an object does not have a specified attribute. + * + * @param object $object + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * @throws ExpectationFailedException + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4601 + */ + public static function assertObjectNotHasAttribute(string $attributeName, $object, string $message = ''): void + { + self::createWarning('assertObjectNotHasAttribute() is deprecated and will be removed in PHPUnit 10. Refactor your test to use assertObjectNotHasProperty() instead.'); + + if (!self::isValidObjectAttributeName($attributeName)) { + throw InvalidArgumentException::create(1, 'valid attribute name'); + } + + if (!is_object($object)) { + throw InvalidArgumentException::create(2, 'object'); + } + + static::assertThat( + $object, + new LogicalNot( + new ObjectHasAttribute($attributeName), + ), + $message, + ); + } + + /** + * Asserts that an object has a specified property. + * + * @throws ExpectationFailedException + */ + final public static function assertObjectHasProperty(string $propertyName, object $object, string $message = ''): void + { + static::assertThat( + $object, + new ObjectHasProperty($propertyName), + $message, + ); + } + + /** + * Asserts that an object does not have a specified property. + * + * @throws ExpectationFailedException + */ + final public static function assertObjectNotHasProperty(string $propertyName, object $object, string $message = ''): void + { + static::assertThat( + $object, + new LogicalNot( + new ObjectHasProperty($propertyName), + ), + $message, + ); + } + + /** + * Asserts that two variables have the same type and value. + * Used on objects, it asserts that two variables reference + * the same object. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-template ExpectedType + * + * @psalm-param ExpectedType $expected + * + * @psalm-assert =ExpectedType $actual + */ + public static function assertSame($expected, $actual, string $message = ''): void + { + static::assertThat( + $actual, + new IsIdentical($expected), + $message, + ); + } + + /** + * Asserts that two variables do not have the same type and value. + * Used on objects, it asserts that two variables do not reference + * the same object. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertNotSame($expected, $actual, string $message = ''): void + { + if (is_bool($expected) && is_bool($actual)) { + static::assertNotEquals($expected, $actual, $message); + } + + static::assertThat( + $actual, + new LogicalNot( + new IsIdentical($expected), + ), + $message, + ); + } + + /** + * Asserts that a variable is of a given type. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * @throws ExpectationFailedException + * + * @psalm-template ExpectedType of object + * + * @psalm-param class-string $expected + * + * @psalm-assert =ExpectedType $actual + */ + public static function assertInstanceOf(string $expected, $actual, string $message = ''): void + { + if (!class_exists($expected) && !interface_exists($expected)) { + throw InvalidArgumentException::create(1, 'class or interface name'); + } + + static::assertThat( + $actual, + new IsInstanceOf($expected), + $message, + ); + } + + /** + * Asserts that a variable is not of a given type. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * @throws ExpectationFailedException + * + * @psalm-template ExpectedType of object + * + * @psalm-param class-string $expected + * + * @psalm-assert !ExpectedType $actual + */ + public static function assertNotInstanceOf(string $expected, $actual, string $message = ''): void + { + if (!class_exists($expected) && !interface_exists($expected)) { + throw InvalidArgumentException::create(1, 'class or interface name'); + } + + static::assertThat( + $actual, + new LogicalNot( + new IsInstanceOf($expected), + ), + $message, + ); + } + + /** + * Asserts that a variable is of type array. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert array $actual + */ + public static function assertIsArray($actual, string $message = ''): void + { + static::assertThat( + $actual, + new IsType(IsType::TYPE_ARRAY), + $message, + ); + } + + /** + * Asserts that a variable is of type bool. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert bool $actual + */ + public static function assertIsBool($actual, string $message = ''): void + { + static::assertThat( + $actual, + new IsType(IsType::TYPE_BOOL), + $message, + ); + } + + /** + * Asserts that a variable is of type float. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert float $actual + */ + public static function assertIsFloat($actual, string $message = ''): void + { + static::assertThat( + $actual, + new IsType(IsType::TYPE_FLOAT), + $message, + ); + } + + /** + * Asserts that a variable is of type int. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert int $actual + */ + public static function assertIsInt($actual, string $message = ''): void + { + static::assertThat( + $actual, + new IsType(IsType::TYPE_INT), + $message, + ); + } + + /** + * Asserts that a variable is of type numeric. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert numeric $actual + */ + public static function assertIsNumeric($actual, string $message = ''): void + { + static::assertThat( + $actual, + new IsType(IsType::TYPE_NUMERIC), + $message, + ); + } + + /** + * Asserts that a variable is of type object. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert object $actual + */ + public static function assertIsObject($actual, string $message = ''): void + { + static::assertThat( + $actual, + new IsType(IsType::TYPE_OBJECT), + $message, + ); + } + + /** + * Asserts that a variable is of type resource. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert resource $actual + */ + public static function assertIsResource($actual, string $message = ''): void + { + static::assertThat( + $actual, + new IsType(IsType::TYPE_RESOURCE), + $message, + ); + } + + /** + * Asserts that a variable is of type resource and is closed. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert resource $actual + */ + public static function assertIsClosedResource($actual, string $message = ''): void + { + static::assertThat( + $actual, + new IsType(IsType::TYPE_CLOSED_RESOURCE), + $message, + ); + } + + /** + * Asserts that a variable is of type string. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert string $actual + */ + public static function assertIsString($actual, string $message = ''): void + { + static::assertThat( + $actual, + new IsType(IsType::TYPE_STRING), + $message, + ); + } + + /** + * Asserts that a variable is of type scalar. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert scalar $actual + */ + public static function assertIsScalar($actual, string $message = ''): void + { + static::assertThat( + $actual, + new IsType(IsType::TYPE_SCALAR), + $message, + ); + } + + /** + * Asserts that a variable is of type callable. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert callable $actual + */ + public static function assertIsCallable($actual, string $message = ''): void + { + static::assertThat( + $actual, + new IsType(IsType::TYPE_CALLABLE), + $message, + ); + } + + /** + * Asserts that a variable is of type iterable. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert iterable $actual + */ + public static function assertIsIterable($actual, string $message = ''): void + { + static::assertThat( + $actual, + new IsType(IsType::TYPE_ITERABLE), + $message, + ); + } + + /** + * Asserts that a variable is not of type array. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert !array $actual + */ + public static function assertIsNotArray($actual, string $message = ''): void + { + static::assertThat( + $actual, + new LogicalNot(new IsType(IsType::TYPE_ARRAY)), + $message, + ); + } + + /** + * Asserts that a variable is not of type bool. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert !bool $actual + */ + public static function assertIsNotBool($actual, string $message = ''): void + { + static::assertThat( + $actual, + new LogicalNot(new IsType(IsType::TYPE_BOOL)), + $message, + ); + } + + /** + * Asserts that a variable is not of type float. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert !float $actual + */ + public static function assertIsNotFloat($actual, string $message = ''): void + { + static::assertThat( + $actual, + new LogicalNot(new IsType(IsType::TYPE_FLOAT)), + $message, + ); + } + + /** + * Asserts that a variable is not of type int. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert !int $actual + */ + public static function assertIsNotInt($actual, string $message = ''): void + { + static::assertThat( + $actual, + new LogicalNot(new IsType(IsType::TYPE_INT)), + $message, + ); + } + + /** + * Asserts that a variable is not of type numeric. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert !numeric $actual + */ + public static function assertIsNotNumeric($actual, string $message = ''): void + { + static::assertThat( + $actual, + new LogicalNot(new IsType(IsType::TYPE_NUMERIC)), + $message, + ); + } + + /** + * Asserts that a variable is not of type object. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert !object $actual + */ + public static function assertIsNotObject($actual, string $message = ''): void + { + static::assertThat( + $actual, + new LogicalNot(new IsType(IsType::TYPE_OBJECT)), + $message, + ); + } + + /** + * Asserts that a variable is not of type resource. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert !resource $actual + */ + public static function assertIsNotResource($actual, string $message = ''): void + { + static::assertThat( + $actual, + new LogicalNot(new IsType(IsType::TYPE_RESOURCE)), + $message, + ); + } + + /** + * Asserts that a variable is not of type resource. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert !resource $actual + */ + public static function assertIsNotClosedResource($actual, string $message = ''): void + { + static::assertThat( + $actual, + new LogicalNot(new IsType(IsType::TYPE_CLOSED_RESOURCE)), + $message, + ); + } + + /** + * Asserts that a variable is not of type string. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert !string $actual + */ + public static function assertIsNotString($actual, string $message = ''): void + { + static::assertThat( + $actual, + new LogicalNot(new IsType(IsType::TYPE_STRING)), + $message, + ); + } + + /** + * Asserts that a variable is not of type scalar. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert !scalar $actual + */ + public static function assertIsNotScalar($actual, string $message = ''): void + { + static::assertThat( + $actual, + new LogicalNot(new IsType(IsType::TYPE_SCALAR)), + $message, + ); + } + + /** + * Asserts that a variable is not of type callable. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert !callable $actual + */ + public static function assertIsNotCallable($actual, string $message = ''): void + { + static::assertThat( + $actual, + new LogicalNot(new IsType(IsType::TYPE_CALLABLE)), + $message, + ); + } + + /** + * Asserts that a variable is not of type iterable. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-assert !iterable $actual + */ + public static function assertIsNotIterable($actual, string $message = ''): void + { + static::assertThat( + $actual, + new LogicalNot(new IsType(IsType::TYPE_ITERABLE)), + $message, + ); + } + + /** + * Asserts that a string matches a given regular expression. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertMatchesRegularExpression(string $pattern, string $string, string $message = ''): void + { + static::assertThat($string, new RegularExpression($pattern), $message); + } + + /** + * Asserts that a string matches a given regular expression. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @codeCoverageIgnore + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4086 + */ + public static function assertRegExp(string $pattern, string $string, string $message = ''): void + { + self::createWarning('assertRegExp() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertMatchesRegularExpression() instead.'); + + static::assertThat($string, new RegularExpression($pattern), $message); + } + + /** + * Asserts that a string does not match a given regular expression. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertDoesNotMatchRegularExpression(string $pattern, string $string, string $message = ''): void + { + static::assertThat( + $string, + new LogicalNot( + new RegularExpression($pattern), + ), + $message, + ); + } + + /** + * Asserts that a string does not match a given regular expression. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @codeCoverageIgnore + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4089 + */ + public static function assertNotRegExp(string $pattern, string $string, string $message = ''): void + { + self::createWarning('assertNotRegExp() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertDoesNotMatchRegularExpression() instead.'); + + static::assertThat( + $string, + new LogicalNot( + new RegularExpression($pattern), + ), + $message, + ); + } + + /** + * Assert that the size of two arrays (or `Countable` or `Traversable` objects) + * is the same. + * + * @param Countable|iterable $expected + * @param Countable|iterable $actual + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * @throws ExpectationFailedException + */ + public static function assertSameSize($expected, $actual, string $message = ''): void + { + if ($expected instanceof Generator) { + self::createWarning('Passing an argument of type Generator for the $expected parameter is deprecated. Support for this will be removed in PHPUnit 10.'); + } + + if ($actual instanceof Generator) { + self::createWarning('Passing an argument of type Generator for the $actual parameter is deprecated. Support for this will be removed in PHPUnit 10.'); + } + + if (!$expected instanceof Countable && !is_iterable($expected)) { + throw InvalidArgumentException::create(1, 'countable or iterable'); + } + + if (!$actual instanceof Countable && !is_iterable($actual)) { + throw InvalidArgumentException::create(2, 'countable or iterable'); + } + + static::assertThat( + $actual, + new SameSize($expected), + $message, + ); + } + + /** + * Assert that the size of two arrays (or `Countable` or `Traversable` objects) + * is not the same. + * + * @param Countable|iterable $expected + * @param Countable|iterable $actual + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * @throws ExpectationFailedException + */ + public static function assertNotSameSize($expected, $actual, string $message = ''): void + { + if ($expected instanceof Generator) { + self::createWarning('Passing an argument of type Generator for the $expected parameter is deprecated. Support for this will be removed in PHPUnit 10.'); + } + + if ($actual instanceof Generator) { + self::createWarning('Passing an argument of type Generator for the $actual parameter is deprecated. Support for this will be removed in PHPUnit 10.'); + } + + if (!$expected instanceof Countable && !is_iterable($expected)) { + throw InvalidArgumentException::create(1, 'countable or iterable'); + } + + if (!$actual instanceof Countable && !is_iterable($actual)) { + throw InvalidArgumentException::create(2, 'countable or iterable'); + } + + static::assertThat( + $actual, + new LogicalNot( + new SameSize($expected), + ), + $message, + ); + } + + /** + * Asserts that a string matches a given format string. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertStringMatchesFormat(string $format, string $string, string $message = ''): void + { + static::assertThat($string, new StringMatchesFormatDescription($format), $message); + } + + /** + * Asserts that a string does not match a given format string. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertStringNotMatchesFormat(string $format, string $string, string $message = ''): void + { + static::assertThat( + $string, + new LogicalNot( + new StringMatchesFormatDescription($format), + ), + $message, + ); + } + + /** + * Asserts that a string matches a given format file. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertStringMatchesFormatFile(string $formatFile, string $string, string $message = ''): void + { + static::assertFileExists($formatFile, $message); + + static::assertThat( + $string, + new StringMatchesFormatDescription( + file_get_contents($formatFile), + ), + $message, + ); + } + + /** + * Asserts that a string does not match a given format string. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertStringNotMatchesFormatFile(string $formatFile, string $string, string $message = ''): void + { + static::assertFileExists($formatFile, $message); + + static::assertThat( + $string, + new LogicalNot( + new StringMatchesFormatDescription( + file_get_contents($formatFile), + ), + ), + $message, + ); + } + + /** + * Asserts that a string starts with a given prefix. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertStringStartsWith(string $prefix, string $string, string $message = ''): void + { + static::assertThat($string, new StringStartsWith($prefix), $message); + } + + /** + * Asserts that a string starts not with a given prefix. + * + * @param string $prefix + * @param string $string + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertStringStartsNotWith($prefix, $string, string $message = ''): void + { + static::assertThat( + $string, + new LogicalNot( + new StringStartsWith($prefix), + ), + $message, + ); + } + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertStringContainsString(string $needle, string $haystack, string $message = ''): void + { + $constraint = new StringContains($needle, false); + + static::assertThat($haystack, $constraint, $message); + } + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertStringContainsStringIgnoringCase(string $needle, string $haystack, string $message = ''): void + { + $constraint = new StringContains($needle, true); + + static::assertThat($haystack, $constraint, $message); + } + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertStringNotContainsString(string $needle, string $haystack, string $message = ''): void + { + $constraint = new LogicalNot(new StringContains($needle)); + + static::assertThat($haystack, $constraint, $message); + } + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertStringNotContainsStringIgnoringCase(string $needle, string $haystack, string $message = ''): void + { + $constraint = new LogicalNot(new StringContains($needle, true)); + + static::assertThat($haystack, $constraint, $message); + } + + /** + * Asserts that a string ends with a given suffix. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertStringEndsWith(string $suffix, string $string, string $message = ''): void + { + static::assertThat($string, new StringEndsWith($suffix), $message); + } + + /** + * Asserts that a string ends not with a given suffix. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertStringEndsNotWith(string $suffix, string $string, string $message = ''): void + { + static::assertThat( + $string, + new LogicalNot( + new StringEndsWith($suffix), + ), + $message, + ); + } + + /** + * Asserts that two XML files are equal. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * @throws ExpectationFailedException + */ + public static function assertXmlFileEqualsXmlFile(string $expectedFile, string $actualFile, string $message = ''): void + { + $expected = (new XmlLoader)->loadFile($expectedFile); + $actual = (new XmlLoader)->loadFile($actualFile); + + static::assertEquals($expected, $actual, $message); + } + + /** + * Asserts that two XML files are not equal. + * + * @throws \PHPUnit\Util\Exception + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertXmlFileNotEqualsXmlFile(string $expectedFile, string $actualFile, string $message = ''): void + { + $expected = (new XmlLoader)->loadFile($expectedFile); + $actual = (new XmlLoader)->loadFile($actualFile); + + static::assertNotEquals($expected, $actual, $message); + } + + /** + * Asserts that two XML documents are equal. + * + * @param DOMDocument|string $actualXml + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * @throws Xml\Exception + */ + public static function assertXmlStringEqualsXmlFile(string $expectedFile, $actualXml, string $message = ''): void + { + if (!is_string($actualXml)) { + self::createWarning('Passing an argument of type DOMDocument for the $actualXml parameter is deprecated. Support for this will be removed in PHPUnit 10.'); + + $actual = $actualXml; + } else { + $actual = (new XmlLoader)->load($actualXml); + } + + $expected = (new XmlLoader)->loadFile($expectedFile); + + static::assertEquals($expected, $actual, $message); + } + + /** + * Asserts that two XML documents are not equal. + * + * @param DOMDocument|string $actualXml + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * @throws Xml\Exception + */ + public static function assertXmlStringNotEqualsXmlFile(string $expectedFile, $actualXml, string $message = ''): void + { + if (!is_string($actualXml)) { + self::createWarning('Passing an argument of type DOMDocument for the $actualXml parameter is deprecated. Support for this will be removed in PHPUnit 10.'); + + $actual = $actualXml; + } else { + $actual = (new XmlLoader)->load($actualXml); + } + + $expected = (new XmlLoader)->loadFile($expectedFile); + + static::assertNotEquals($expected, $actual, $message); + } + + /** + * Asserts that two XML documents are equal. + * + * @param DOMDocument|string $expectedXml + * @param DOMDocument|string $actualXml + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * @throws Xml\Exception + */ + public static function assertXmlStringEqualsXmlString($expectedXml, $actualXml, string $message = ''): void + { + if (!is_string($expectedXml)) { + self::createWarning('Passing an argument of type DOMDocument for the $expectedXml parameter is deprecated. Support for this will be removed in PHPUnit 10.'); + + $expected = $expectedXml; + } else { + $expected = (new XmlLoader)->load($expectedXml); + } + + if (!is_string($actualXml)) { + self::createWarning('Passing an argument of type DOMDocument for the $actualXml parameter is deprecated. Support for this will be removed in PHPUnit 10.'); + + $actual = $actualXml; + } else { + $actual = (new XmlLoader)->load($actualXml); + } + + static::assertEquals($expected, $actual, $message); + } + + /** + * Asserts that two XML documents are not equal. + * + * @param DOMDocument|string $expectedXml + * @param DOMDocument|string $actualXml + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * @throws Xml\Exception + */ + public static function assertXmlStringNotEqualsXmlString($expectedXml, $actualXml, string $message = ''): void + { + if (!is_string($expectedXml)) { + self::createWarning('Passing an argument of type DOMDocument for the $expectedXml parameter is deprecated. Support for this will be removed in PHPUnit 10.'); + + $expected = $expectedXml; + } else { + $expected = (new XmlLoader)->load($expectedXml); + } + + if (!is_string($actualXml)) { + self::createWarning('Passing an argument of type DOMDocument for the $actualXml parameter is deprecated. Support for this will be removed in PHPUnit 10.'); + + $actual = $actualXml; + } else { + $actual = (new XmlLoader)->load($actualXml); + } + + static::assertNotEquals($expected, $actual, $message); + } + + /** + * Asserts that a hierarchy of DOMElements matches. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws AssertionFailedError + * @throws ExpectationFailedException + * + * @codeCoverageIgnore + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4091 + */ + public static function assertEqualXMLStructure(DOMElement $expectedElement, DOMElement $actualElement, bool $checkAttributes = false, string $message = ''): void + { + self::createWarning('assertEqualXMLStructure() is deprecated and will be removed in PHPUnit 10.'); + + $expectedElement = Xml::import($expectedElement); + $actualElement = Xml::import($actualElement); + + static::assertSame( + $expectedElement->tagName, + $actualElement->tagName, + $message, + ); + + if ($checkAttributes) { + static::assertSame( + $expectedElement->attributes->length, + $actualElement->attributes->length, + sprintf( + '%s%sNumber of attributes on node "%s" does not match', + $message, + !empty($message) ? "\n" : '', + $expectedElement->tagName, + ), + ); + + for ($i = 0; $i < $expectedElement->attributes->length; $i++) { + $expectedAttribute = $expectedElement->attributes->item($i); + $actualAttribute = $actualElement->attributes->getNamedItem($expectedAttribute->name); + + assert($expectedAttribute instanceof DOMAttr); + + if (!$actualAttribute) { + static::fail( + sprintf( + '%s%sCould not find attribute "%s" on node "%s"', + $message, + !empty($message) ? "\n" : '', + $expectedAttribute->name, + $expectedElement->tagName, + ), + ); + } + } + } + + Xml::removeCharacterDataNodes($expectedElement); + Xml::removeCharacterDataNodes($actualElement); + + static::assertSame( + $expectedElement->childNodes->length, + $actualElement->childNodes->length, + sprintf( + '%s%sNumber of child nodes of "%s" differs', + $message, + !empty($message) ? "\n" : '', + $expectedElement->tagName, + ), + ); + + for ($i = 0; $i < $expectedElement->childNodes->length; $i++) { + static::assertEqualXMLStructure( + $expectedElement->childNodes->item($i), + $actualElement->childNodes->item($i), + $checkAttributes, + $message, + ); + } + } + + /** + * Evaluates a PHPUnit\Framework\Constraint matcher object. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertThat($value, Constraint $constraint, string $message = ''): void + { + self::$count += count($constraint); + + $constraint->evaluate($value, $message); + } + + /** + * Asserts that a string is a valid JSON string. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertJson(string $actualJson, string $message = ''): void + { + static::assertThat($actualJson, static::isJson(), $message); + } + + /** + * Asserts that two given JSON encoded objects or arrays are equal. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertJsonStringEqualsJsonString(string $expectedJson, string $actualJson, string $message = ''): void + { + static::assertJson($expectedJson, $message); + static::assertJson($actualJson, $message); + + static::assertThat($actualJson, new JsonMatches($expectedJson), $message); + } + + /** + * Asserts that two given JSON encoded objects or arrays are not equal. + * + * @param string $expectedJson + * @param string $actualJson + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertJsonStringNotEqualsJsonString($expectedJson, $actualJson, string $message = ''): void + { + static::assertJson($expectedJson, $message); + static::assertJson($actualJson, $message); + + static::assertThat( + $actualJson, + new LogicalNot( + new JsonMatches($expectedJson), + ), + $message, + ); + } + + /** + * Asserts that the generated JSON encoded object and the content of the given file are equal. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertJsonStringEqualsJsonFile(string $expectedFile, string $actualJson, string $message = ''): void + { + static::assertFileExists($expectedFile, $message); + $expectedJson = file_get_contents($expectedFile); + + static::assertJson($expectedJson, $message); + static::assertJson($actualJson, $message); + + static::assertThat($actualJson, new JsonMatches($expectedJson), $message); + } + + /** + * Asserts that the generated JSON encoded object and the content of the given file are not equal. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertJsonStringNotEqualsJsonFile(string $expectedFile, string $actualJson, string $message = ''): void + { + static::assertFileExists($expectedFile, $message); + $expectedJson = file_get_contents($expectedFile); + + static::assertJson($expectedJson, $message); + static::assertJson($actualJson, $message); + + static::assertThat( + $actualJson, + new LogicalNot( + new JsonMatches($expectedJson), + ), + $message, + ); + } + + /** + * Asserts that two JSON files are equal. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertJsonFileEqualsJsonFile(string $expectedFile, string $actualFile, string $message = ''): void + { + static::assertFileExists($expectedFile, $message); + static::assertFileExists($actualFile, $message); + + $actualJson = file_get_contents($actualFile); + $expectedJson = file_get_contents($expectedFile); + + static::assertJson($expectedJson, $message); + static::assertJson($actualJson, $message); + + $constraintExpected = new JsonMatches( + $expectedJson, + ); + + $constraintActual = new JsonMatches($actualJson); + + static::assertThat($expectedJson, $constraintActual, $message); + static::assertThat($actualJson, $constraintExpected, $message); + } + + /** + * Asserts that two JSON files are not equal. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public static function assertJsonFileNotEqualsJsonFile(string $expectedFile, string $actualFile, string $message = ''): void + { + static::assertFileExists($expectedFile, $message); + static::assertFileExists($actualFile, $message); + + $actualJson = file_get_contents($actualFile); + $expectedJson = file_get_contents($expectedFile); + + static::assertJson($expectedJson, $message); + static::assertJson($actualJson, $message); + + $constraintExpected = new JsonMatches( + $expectedJson, + ); + + $constraintActual = new JsonMatches($actualJson); + + static::assertThat($expectedJson, new LogicalNot($constraintActual), $message); + static::assertThat($actualJson, new LogicalNot($constraintExpected), $message); + } + + /** + * @throws Exception + */ + public static function logicalAnd(): LogicalAnd + { + $constraints = func_get_args(); + + $constraint = new LogicalAnd; + $constraint->setConstraints($constraints); + + return $constraint; + } + + public static function logicalOr(): LogicalOr + { + $constraints = func_get_args(); + + $constraint = new LogicalOr; + $constraint->setConstraints($constraints); + + return $constraint; + } + + public static function logicalNot(Constraint $constraint): LogicalNot + { + return new LogicalNot($constraint); + } + + public static function logicalXor(): LogicalXor + { + $constraints = func_get_args(); + + $constraint = new LogicalXor; + $constraint->setConstraints($constraints); + + return $constraint; + } + + public static function anything(): IsAnything + { + return new IsAnything; + } + + public static function isTrue(): IsTrue + { + return new IsTrue; + } + + /** + * @psalm-template CallbackInput of mixed + * + * @psalm-param callable(CallbackInput $callback): bool $callback + * + * @psalm-return Callback + */ + public static function callback(callable $callback): Callback + { + return new Callback($callback); + } + + public static function isFalse(): IsFalse + { + return new IsFalse; + } + + public static function isJson(): IsJson + { + return new IsJson; + } + + public static function isNull(): IsNull + { + return new IsNull; + } + + public static function isFinite(): IsFinite + { + return new IsFinite; + } + + public static function isInfinite(): IsInfinite + { + return new IsInfinite; + } + + public static function isNan(): IsNan + { + return new IsNan; + } + + public static function containsEqual($value): TraversableContainsEqual + { + return new TraversableContainsEqual($value); + } + + public static function containsIdentical($value): TraversableContainsIdentical + { + return new TraversableContainsIdentical($value); + } + + public static function containsOnly(string $type): TraversableContainsOnly + { + return new TraversableContainsOnly($type); + } + + public static function containsOnlyInstancesOf(string $className): TraversableContainsOnly + { + return new TraversableContainsOnly($className, false); + } + + /** + * @param int|string $key + */ + public static function arrayHasKey($key): ArrayHasKey + { + return new ArrayHasKey($key); + } + + public static function equalTo($value): IsEqual + { + return new IsEqual($value, 0.0, false, false); + } + + public static function equalToCanonicalizing($value): IsEqualCanonicalizing + { + return new IsEqualCanonicalizing($value); + } + + public static function equalToIgnoringCase($value): IsEqualIgnoringCase + { + return new IsEqualIgnoringCase($value); + } + + public static function equalToWithDelta($value, float $delta): IsEqualWithDelta + { + return new IsEqualWithDelta($value, $delta); + } + + public static function isEmpty(): IsEmpty + { + return new IsEmpty; + } + + public static function isWritable(): IsWritable + { + return new IsWritable; + } + + public static function isReadable(): IsReadable + { + return new IsReadable; + } + + public static function directoryExists(): DirectoryExists + { + return new DirectoryExists; + } + + public static function fileExists(): FileExists + { + return new FileExists; + } + + public static function greaterThan($value): GreaterThan + { + return new GreaterThan($value); + } + + public static function greaterThanOrEqual($value): LogicalOr + { + return static::logicalOr( + new IsEqual($value), + new GreaterThan($value), + ); + } + + /** + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4601 + */ + public static function classHasAttribute(string $attributeName): ClassHasAttribute + { + self::createWarning('classHasAttribute() is deprecated and will be removed in PHPUnit 10.'); + + return new ClassHasAttribute($attributeName); + } + + /** + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4601 + */ + public static function classHasStaticAttribute(string $attributeName): ClassHasStaticAttribute + { + self::createWarning('classHasStaticAttribute() is deprecated and will be removed in PHPUnit 10.'); + + return new ClassHasStaticAttribute($attributeName); + } + + /** + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4601 + */ + public static function objectHasAttribute($attributeName): ObjectHasAttribute + { + self::createWarning('objectHasAttribute() is deprecated and will be removed in PHPUnit 10.'); + + return new ObjectHasAttribute($attributeName); + } + + public static function identicalTo($value): IsIdentical + { + return new IsIdentical($value); + } + + public static function isInstanceOf(string $className): IsInstanceOf + { + return new IsInstanceOf($className); + } + + public static function isType(string $type): IsType + { + return new IsType($type); + } + + public static function lessThan($value): LessThan + { + return new LessThan($value); + } + + public static function lessThanOrEqual($value): LogicalOr + { + return static::logicalOr( + new IsEqual($value), + new LessThan($value), + ); + } + + public static function matchesRegularExpression(string $pattern): RegularExpression + { + return new RegularExpression($pattern); + } + + public static function matches(string $string): StringMatchesFormatDescription + { + return new StringMatchesFormatDescription($string); + } + + public static function stringStartsWith($prefix): StringStartsWith + { + return new StringStartsWith($prefix); + } + + public static function stringContains(string $string, bool $case = true): StringContains + { + return new StringContains($string, $case); + } + + public static function stringEndsWith(string $suffix): StringEndsWith + { + return new StringEndsWith($suffix); + } + + public static function countOf(int $count): Count + { + return new Count($count); + } + + public static function objectEquals(object $object, string $method = 'equals'): ObjectEquals + { + return new ObjectEquals($object, $method); + } + + /** + * Fails a test with the given message. + * + * @throws AssertionFailedError + * + * @psalm-return never-return + */ + public static function fail(string $message = ''): void + { + self::$count++; + + throw new AssertionFailedError($message); + } + + /** + * Mark the test as incomplete. + * + * @throws IncompleteTestError + * + * @psalm-return never-return + */ + public static function markTestIncomplete(string $message = ''): void + { + throw new IncompleteTestError($message); + } + + /** + * Mark the test as skipped. + * + * @throws SkippedTestError + * @throws SyntheticSkippedError + * + * @psalm-return never-return + */ + public static function markTestSkipped(string $message = ''): void + { + if ($hint = self::detectLocationHint($message)) { + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + array_unshift($trace, $hint); + + throw new SyntheticSkippedError($hint['message'], 0, $hint['file'], (int) $hint['line'], $trace); + } + + throw new SkippedTestError($message); + } + + /** + * Return the current assertion count. + */ + public static function getCount(): int + { + return self::$count; + } + + /** + * Reset the assertion counter. + */ + public static function resetCount(): void + { + self::$count = 0; + } + + private static function detectLocationHint(string $message): ?array + { + $hint = null; + $lines = preg_split('/\r\n|\r|\n/', $message); + + while (strpos($lines[0], '__OFFSET') !== false) { + $offset = explode('=', array_shift($lines)); + + if ($offset[0] === '__OFFSET_FILE') { + $hint['file'] = $offset[1]; + } + + if ($offset[0] === '__OFFSET_LINE') { + $hint['line'] = $offset[1]; + } + } + + if ($hint) { + $hint['message'] = implode(PHP_EOL, $lines); + } + + return $hint; + } + + private static function isValidObjectAttributeName(string $attributeName): bool + { + return (bool) preg_match('/[^\x00-\x1f\x7f-\x9f]+/', $attributeName); + } + + private static function isValidClassAttributeName(string $attributeName): bool + { + return (bool) preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $attributeName); + } + + /** + * @codeCoverageIgnore + */ + private static function createWarning(string $warning): void + { + foreach (debug_backtrace() as $step) { + if (isset($step['object']) && $step['object'] instanceof TestCase) { + assert($step['object'] instanceof TestCase); + + $step['object']->addWarning($warning); + + break; + } + } + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Assert/Functions.php b/vendor/phpunit/phpunit/src/Framework/Assert/Functions.php new file mode 100644 index 00000000..2005cfde --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Assert/Functions.php @@ -0,0 +1,3078 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function func_get_args; +use function function_exists; +use ArrayAccess; +use Countable; +use DOMDocument; +use DOMElement; +use PHPUnit\Framework\Constraint\ArrayHasKey; +use PHPUnit\Framework\Constraint\Callback; +use PHPUnit\Framework\Constraint\ClassHasAttribute; +use PHPUnit\Framework\Constraint\ClassHasStaticAttribute; +use PHPUnit\Framework\Constraint\Constraint; +use PHPUnit\Framework\Constraint\Count; +use PHPUnit\Framework\Constraint\DirectoryExists; +use PHPUnit\Framework\Constraint\FileExists; +use PHPUnit\Framework\Constraint\GreaterThan; +use PHPUnit\Framework\Constraint\IsAnything; +use PHPUnit\Framework\Constraint\IsEmpty; +use PHPUnit\Framework\Constraint\IsEqual; +use PHPUnit\Framework\Constraint\IsEqualCanonicalizing; +use PHPUnit\Framework\Constraint\IsEqualIgnoringCase; +use PHPUnit\Framework\Constraint\IsEqualWithDelta; +use PHPUnit\Framework\Constraint\IsFalse; +use PHPUnit\Framework\Constraint\IsFinite; +use PHPUnit\Framework\Constraint\IsIdentical; +use PHPUnit\Framework\Constraint\IsInfinite; +use PHPUnit\Framework\Constraint\IsInstanceOf; +use PHPUnit\Framework\Constraint\IsJson; +use PHPUnit\Framework\Constraint\IsNan; +use PHPUnit\Framework\Constraint\IsNull; +use PHPUnit\Framework\Constraint\IsReadable; +use PHPUnit\Framework\Constraint\IsTrue; +use PHPUnit\Framework\Constraint\IsType; +use PHPUnit\Framework\Constraint\IsWritable; +use PHPUnit\Framework\Constraint\LessThan; +use PHPUnit\Framework\Constraint\LogicalAnd; +use PHPUnit\Framework\Constraint\LogicalNot; +use PHPUnit\Framework\Constraint\LogicalOr; +use PHPUnit\Framework\Constraint\LogicalXor; +use PHPUnit\Framework\Constraint\ObjectEquals; +use PHPUnit\Framework\Constraint\ObjectHasAttribute; +use PHPUnit\Framework\Constraint\RegularExpression; +use PHPUnit\Framework\Constraint\StringContains; +use PHPUnit\Framework\Constraint\StringEndsWith; +use PHPUnit\Framework\Constraint\StringMatchesFormatDescription; +use PHPUnit\Framework\Constraint\StringStartsWith; +use PHPUnit\Framework\Constraint\TraversableContainsEqual; +use PHPUnit\Framework\Constraint\TraversableContainsIdentical; +use PHPUnit\Framework\Constraint\TraversableContainsOnly; +use PHPUnit\Framework\MockObject\Rule\AnyInvokedCount as AnyInvokedCountMatcher; +use PHPUnit\Framework\MockObject\Rule\InvokedAtIndex as InvokedAtIndexMatcher; +use PHPUnit\Framework\MockObject\Rule\InvokedAtLeastCount as InvokedAtLeastCountMatcher; +use PHPUnit\Framework\MockObject\Rule\InvokedAtLeastOnce as InvokedAtLeastOnceMatcher; +use PHPUnit\Framework\MockObject\Rule\InvokedAtMostCount as InvokedAtMostCountMatcher; +use PHPUnit\Framework\MockObject\Rule\InvokedCount as InvokedCountMatcher; +use PHPUnit\Framework\MockObject\Stub\ConsecutiveCalls as ConsecutiveCallsStub; +use PHPUnit\Framework\MockObject\Stub\Exception as ExceptionStub; +use PHPUnit\Framework\MockObject\Stub\ReturnArgument as ReturnArgumentStub; +use PHPUnit\Framework\MockObject\Stub\ReturnCallback as ReturnCallbackStub; +use PHPUnit\Framework\MockObject\Stub\ReturnSelf as ReturnSelfStub; +use PHPUnit\Framework\MockObject\Stub\ReturnStub; +use PHPUnit\Framework\MockObject\Stub\ReturnValueMap as ReturnValueMapStub; +use Throwable; + +if (!function_exists('PHPUnit\Framework\assertArrayHasKey')) { + /** + * Asserts that an array has a specified key. + * + * @param int|string $key + * @param array|ArrayAccess $array + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertArrayHasKey + */ + function assertArrayHasKey($key, $array, string $message = ''): void + { + Assert::assertArrayHasKey(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertArrayNotHasKey')) { + /** + * Asserts that an array does not have a specified key. + * + * @param int|string $key + * @param array|ArrayAccess $array + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertArrayNotHasKey + */ + function assertArrayNotHasKey($key, $array, string $message = ''): void + { + Assert::assertArrayNotHasKey(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContains')) { + /** + * Asserts that a haystack contains a needle. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContains + */ + function assertContains($needle, iterable $haystack, string $message = ''): void + { + Assert::assertContains(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsEquals')) { + function assertContainsEquals($needle, iterable $haystack, string $message = ''): void + { + Assert::assertContainsEquals(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNotContains')) { + /** + * Asserts that a haystack does not contain a needle. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotContains + */ + function assertNotContains($needle, iterable $haystack, string $message = ''): void + { + Assert::assertNotContains(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNotContainsEquals')) { + function assertNotContainsEquals($needle, iterable $haystack, string $message = ''): void + { + Assert::assertNotContainsEquals(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsOnly')) { + /** + * Asserts that a haystack contains only values of a given type. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsOnly + */ + function assertContainsOnly(string $type, iterable $haystack, ?bool $isNativeType = null, string $message = ''): void + { + Assert::assertContainsOnly(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsOnlyInstancesOf')) { + /** + * Asserts that a haystack contains only instances of a given class name. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsOnlyInstancesOf + */ + function assertContainsOnlyInstancesOf(string $className, iterable $haystack, string $message = ''): void + { + Assert::assertContainsOnlyInstancesOf(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNotContainsOnly')) { + /** + * Asserts that a haystack does not contain only values of a given type. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotContainsOnly + */ + function assertNotContainsOnly(string $type, iterable $haystack, ?bool $isNativeType = null, string $message = ''): void + { + Assert::assertNotContainsOnly(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertCount')) { + /** + * Asserts the number of elements of an array, Countable or Traversable. + * + * @param Countable|iterable $haystack + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertCount + */ + function assertCount(int $expectedCount, $haystack, string $message = ''): void + { + Assert::assertCount(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNotCount')) { + /** + * Asserts the number of elements of an array, Countable or Traversable. + * + * @param Countable|iterable $haystack + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotCount + */ + function assertNotCount(int $expectedCount, $haystack, string $message = ''): void + { + Assert::assertNotCount(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertEquals')) { + /** + * Asserts that two variables are equal. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertEquals + */ + function assertEquals($expected, $actual, string $message = ''): void + { + Assert::assertEquals(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertEqualsCanonicalizing')) { + /** + * Asserts that two variables are equal (canonicalizing). + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertEqualsCanonicalizing + */ + function assertEqualsCanonicalizing($expected, $actual, string $message = ''): void + { + Assert::assertEqualsCanonicalizing(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertEqualsIgnoringCase')) { + /** + * Asserts that two variables are equal (ignoring case). + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertEqualsIgnoringCase + */ + function assertEqualsIgnoringCase($expected, $actual, string $message = ''): void + { + Assert::assertEqualsIgnoringCase(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertEqualsWithDelta')) { + /** + * Asserts that two variables are equal (with delta). + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertEqualsWithDelta + */ + function assertEqualsWithDelta($expected, $actual, float $delta, string $message = ''): void + { + Assert::assertEqualsWithDelta(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNotEquals')) { + /** + * Asserts that two variables are not equal. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotEquals + */ + function assertNotEquals($expected, $actual, string $message = ''): void + { + Assert::assertNotEquals(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNotEqualsCanonicalizing')) { + /** + * Asserts that two variables are not equal (canonicalizing). + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotEqualsCanonicalizing + */ + function assertNotEqualsCanonicalizing($expected, $actual, string $message = ''): void + { + Assert::assertNotEqualsCanonicalizing(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNotEqualsIgnoringCase')) { + /** + * Asserts that two variables are not equal (ignoring case). + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotEqualsIgnoringCase + */ + function assertNotEqualsIgnoringCase($expected, $actual, string $message = ''): void + { + Assert::assertNotEqualsIgnoringCase(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNotEqualsWithDelta')) { + /** + * Asserts that two variables are not equal (with delta). + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotEqualsWithDelta + */ + function assertNotEqualsWithDelta($expected, $actual, float $delta, string $message = ''): void + { + Assert::assertNotEqualsWithDelta(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertObjectEquals')) { + /** + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertObjectEquals + */ + function assertObjectEquals(object $expected, object $actual, string $method = 'equals', string $message = ''): void + { + Assert::assertObjectEquals(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertEmpty')) { + /** + * Asserts that a variable is empty. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert empty $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertEmpty + */ + function assertEmpty($actual, string $message = ''): void + { + Assert::assertEmpty(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNotEmpty')) { + /** + * Asserts that a variable is not empty. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert !empty $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotEmpty + */ + function assertNotEmpty($actual, string $message = ''): void + { + Assert::assertNotEmpty(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertGreaterThan')) { + /** + * Asserts that a value is greater than another value. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertGreaterThan + */ + function assertGreaterThan($expected, $actual, string $message = ''): void + { + Assert::assertGreaterThan(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertGreaterThanOrEqual')) { + /** + * Asserts that a value is greater than or equal to another value. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertGreaterThanOrEqual + */ + function assertGreaterThanOrEqual($expected, $actual, string $message = ''): void + { + Assert::assertGreaterThanOrEqual(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertLessThan')) { + /** + * Asserts that a value is smaller than another value. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertLessThan + */ + function assertLessThan($expected, $actual, string $message = ''): void + { + Assert::assertLessThan(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertLessThanOrEqual')) { + /** + * Asserts that a value is smaller than or equal to another value. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertLessThanOrEqual + */ + function assertLessThanOrEqual($expected, $actual, string $message = ''): void + { + Assert::assertLessThanOrEqual(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertFileEquals')) { + /** + * Asserts that the contents of one file is equal to the contents of another + * file. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileEquals + */ + function assertFileEquals(string $expected, string $actual, string $message = ''): void + { + Assert::assertFileEquals(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertFileEqualsCanonicalizing')) { + /** + * Asserts that the contents of one file is equal to the contents of another + * file (canonicalizing). + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileEqualsCanonicalizing + */ + function assertFileEqualsCanonicalizing(string $expected, string $actual, string $message = ''): void + { + Assert::assertFileEqualsCanonicalizing(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertFileEqualsIgnoringCase')) { + /** + * Asserts that the contents of one file is equal to the contents of another + * file (ignoring case). + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileEqualsIgnoringCase + */ + function assertFileEqualsIgnoringCase(string $expected, string $actual, string $message = ''): void + { + Assert::assertFileEqualsIgnoringCase(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertFileNotEquals')) { + /** + * Asserts that the contents of one file is not equal to the contents of + * another file. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileNotEquals + */ + function assertFileNotEquals(string $expected, string $actual, string $message = ''): void + { + Assert::assertFileNotEquals(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertFileNotEqualsCanonicalizing')) { + /** + * Asserts that the contents of one file is not equal to the contents of another + * file (canonicalizing). + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileNotEqualsCanonicalizing + */ + function assertFileNotEqualsCanonicalizing(string $expected, string $actual, string $message = ''): void + { + Assert::assertFileNotEqualsCanonicalizing(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertFileNotEqualsIgnoringCase')) { + /** + * Asserts that the contents of one file is not equal to the contents of another + * file (ignoring case). + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileNotEqualsIgnoringCase + */ + function assertFileNotEqualsIgnoringCase(string $expected, string $actual, string $message = ''): void + { + Assert::assertFileNotEqualsIgnoringCase(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringEqualsFile')) { + /** + * Asserts that the contents of a string is equal + * to the contents of a file. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringEqualsFile + */ + function assertStringEqualsFile(string $expectedFile, string $actualString, string $message = ''): void + { + Assert::assertStringEqualsFile(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringEqualsFileCanonicalizing')) { + /** + * Asserts that the contents of a string is equal + * to the contents of a file (canonicalizing). + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringEqualsFileCanonicalizing + */ + function assertStringEqualsFileCanonicalizing(string $expectedFile, string $actualString, string $message = ''): void + { + Assert::assertStringEqualsFileCanonicalizing(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringEqualsFileIgnoringCase')) { + /** + * Asserts that the contents of a string is equal + * to the contents of a file (ignoring case). + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringEqualsFileIgnoringCase + */ + function assertStringEqualsFileIgnoringCase(string $expectedFile, string $actualString, string $message = ''): void + { + Assert::assertStringEqualsFileIgnoringCase(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringNotEqualsFile')) { + /** + * Asserts that the contents of a string is not equal + * to the contents of a file. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringNotEqualsFile + */ + function assertStringNotEqualsFile(string $expectedFile, string $actualString, string $message = ''): void + { + Assert::assertStringNotEqualsFile(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringNotEqualsFileCanonicalizing')) { + /** + * Asserts that the contents of a string is not equal + * to the contents of a file (canonicalizing). + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringNotEqualsFileCanonicalizing + */ + function assertStringNotEqualsFileCanonicalizing(string $expectedFile, string $actualString, string $message = ''): void + { + Assert::assertStringNotEqualsFileCanonicalizing(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringNotEqualsFileIgnoringCase')) { + /** + * Asserts that the contents of a string is not equal + * to the contents of a file (ignoring case). + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringNotEqualsFileIgnoringCase + */ + function assertStringNotEqualsFileIgnoringCase(string $expectedFile, string $actualString, string $message = ''): void + { + Assert::assertStringNotEqualsFileIgnoringCase(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsReadable')) { + /** + * Asserts that a file/dir is readable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsReadable + */ + function assertIsReadable(string $filename, string $message = ''): void + { + Assert::assertIsReadable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsNotReadable')) { + /** + * Asserts that a file/dir exists and is not readable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotReadable + */ + function assertIsNotReadable(string $filename, string $message = ''): void + { + Assert::assertIsNotReadable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNotIsReadable')) { + /** + * Asserts that a file/dir exists and is not readable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @codeCoverageIgnore + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4062 + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotIsReadable + */ + function assertNotIsReadable(string $filename, string $message = ''): void + { + Assert::assertNotIsReadable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsWritable')) { + /** + * Asserts that a file/dir exists and is writable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsWritable + */ + function assertIsWritable(string $filename, string $message = ''): void + { + Assert::assertIsWritable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsNotWritable')) { + /** + * Asserts that a file/dir exists and is not writable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotWritable + */ + function assertIsNotWritable(string $filename, string $message = ''): void + { + Assert::assertIsNotWritable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNotIsWritable')) { + /** + * Asserts that a file/dir exists and is not writable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @codeCoverageIgnore + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4065 + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotIsWritable + */ + function assertNotIsWritable(string $filename, string $message = ''): void + { + Assert::assertNotIsWritable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertDirectoryExists')) { + /** + * Asserts that a directory exists. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertDirectoryExists + */ + function assertDirectoryExists(string $directory, string $message = ''): void + { + Assert::assertDirectoryExists(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertDirectoryDoesNotExist')) { + /** + * Asserts that a directory does not exist. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertDirectoryDoesNotExist + */ + function assertDirectoryDoesNotExist(string $directory, string $message = ''): void + { + Assert::assertDirectoryDoesNotExist(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertDirectoryNotExists')) { + /** + * Asserts that a directory does not exist. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @codeCoverageIgnore + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4068 + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertDirectoryNotExists + */ + function assertDirectoryNotExists(string $directory, string $message = ''): void + { + Assert::assertDirectoryNotExists(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertDirectoryIsReadable')) { + /** + * Asserts that a directory exists and is readable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertDirectoryIsReadable + */ + function assertDirectoryIsReadable(string $directory, string $message = ''): void + { + Assert::assertDirectoryIsReadable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertDirectoryIsNotReadable')) { + /** + * Asserts that a directory exists and is not readable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertDirectoryIsNotReadable + */ + function assertDirectoryIsNotReadable(string $directory, string $message = ''): void + { + Assert::assertDirectoryIsNotReadable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertDirectoryNotIsReadable')) { + /** + * Asserts that a directory exists and is not readable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @codeCoverageIgnore + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4071 + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertDirectoryNotIsReadable + */ + function assertDirectoryNotIsReadable(string $directory, string $message = ''): void + { + Assert::assertDirectoryNotIsReadable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertDirectoryIsWritable')) { + /** + * Asserts that a directory exists and is writable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertDirectoryIsWritable + */ + function assertDirectoryIsWritable(string $directory, string $message = ''): void + { + Assert::assertDirectoryIsWritable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertDirectoryIsNotWritable')) { + /** + * Asserts that a directory exists and is not writable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertDirectoryIsNotWritable + */ + function assertDirectoryIsNotWritable(string $directory, string $message = ''): void + { + Assert::assertDirectoryIsNotWritable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertDirectoryNotIsWritable')) { + /** + * Asserts that a directory exists and is not writable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @codeCoverageIgnore + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4074 + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertDirectoryNotIsWritable + */ + function assertDirectoryNotIsWritable(string $directory, string $message = ''): void + { + Assert::assertDirectoryNotIsWritable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertFileExists')) { + /** + * Asserts that a file exists. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileExists + */ + function assertFileExists(string $filename, string $message = ''): void + { + Assert::assertFileExists(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertFileDoesNotExist')) { + /** + * Asserts that a file does not exist. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileDoesNotExist + */ + function assertFileDoesNotExist(string $filename, string $message = ''): void + { + Assert::assertFileDoesNotExist(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertFileNotExists')) { + /** + * Asserts that a file does not exist. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @codeCoverageIgnore + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4077 + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileNotExists + */ + function assertFileNotExists(string $filename, string $message = ''): void + { + Assert::assertFileNotExists(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertFileIsReadable')) { + /** + * Asserts that a file exists and is readable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileIsReadable + */ + function assertFileIsReadable(string $file, string $message = ''): void + { + Assert::assertFileIsReadable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertFileIsNotReadable')) { + /** + * Asserts that a file exists and is not readable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileIsNotReadable + */ + function assertFileIsNotReadable(string $file, string $message = ''): void + { + Assert::assertFileIsNotReadable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertFileNotIsReadable')) { + /** + * Asserts that a file exists and is not readable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @codeCoverageIgnore + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4080 + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileNotIsReadable + */ + function assertFileNotIsReadable(string $file, string $message = ''): void + { + Assert::assertFileNotIsReadable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertFileIsWritable')) { + /** + * Asserts that a file exists and is writable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileIsWritable + */ + function assertFileIsWritable(string $file, string $message = ''): void + { + Assert::assertFileIsWritable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertFileIsNotWritable')) { + /** + * Asserts that a file exists and is not writable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileIsNotWritable + */ + function assertFileIsNotWritable(string $file, string $message = ''): void + { + Assert::assertFileIsNotWritable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertFileNotIsWritable')) { + /** + * Asserts that a file exists and is not writable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @codeCoverageIgnore + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4083 + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileNotIsWritable + */ + function assertFileNotIsWritable(string $file, string $message = ''): void + { + Assert::assertFileNotIsWritable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertTrue')) { + /** + * Asserts that a condition is true. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert true $condition + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertTrue + */ + function assertTrue($condition, string $message = ''): void + { + Assert::assertTrue(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNotTrue')) { + /** + * Asserts that a condition is not true. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert !true $condition + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotTrue + */ + function assertNotTrue($condition, string $message = ''): void + { + Assert::assertNotTrue(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertFalse')) { + /** + * Asserts that a condition is false. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert false $condition + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFalse + */ + function assertFalse($condition, string $message = ''): void + { + Assert::assertFalse(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNotFalse')) { + /** + * Asserts that a condition is not false. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert !false $condition + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotFalse + */ + function assertNotFalse($condition, string $message = ''): void + { + Assert::assertNotFalse(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNull')) { + /** + * Asserts that a variable is null. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert null $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNull + */ + function assertNull($actual, string $message = ''): void + { + Assert::assertNull(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNotNull')) { + /** + * Asserts that a variable is not null. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert !null $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotNull + */ + function assertNotNull($actual, string $message = ''): void + { + Assert::assertNotNull(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertFinite')) { + /** + * Asserts that a variable is finite. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFinite + */ + function assertFinite($actual, string $message = ''): void + { + Assert::assertFinite(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertInfinite')) { + /** + * Asserts that a variable is infinite. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertInfinite + */ + function assertInfinite($actual, string $message = ''): void + { + Assert::assertInfinite(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNan')) { + /** + * Asserts that a variable is nan. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNan + */ + function assertNan($actual, string $message = ''): void + { + Assert::assertNan(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertClassHasAttribute')) { + /** + * Asserts that a class has a specified attribute. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertClassHasAttribute + */ + function assertClassHasAttribute(string $attributeName, string $className, string $message = ''): void + { + Assert::assertClassHasAttribute(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertClassNotHasAttribute')) { + /** + * Asserts that a class does not have a specified attribute. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertClassNotHasAttribute + */ + function assertClassNotHasAttribute(string $attributeName, string $className, string $message = ''): void + { + Assert::assertClassNotHasAttribute(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertClassHasStaticAttribute')) { + /** + * Asserts that a class has a specified static attribute. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertClassHasStaticAttribute + */ + function assertClassHasStaticAttribute(string $attributeName, string $className, string $message = ''): void + { + Assert::assertClassHasStaticAttribute(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertClassNotHasStaticAttribute')) { + /** + * Asserts that a class does not have a specified static attribute. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertClassNotHasStaticAttribute + */ + function assertClassNotHasStaticAttribute(string $attributeName, string $className, string $message = ''): void + { + Assert::assertClassNotHasStaticAttribute(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertObjectHasAttribute')) { + /** + * Asserts that an object has a specified attribute. + * + * @param object $object + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertObjectHasAttribute + */ + function assertObjectHasAttribute(string $attributeName, $object, string $message = ''): void + { + Assert::assertObjectHasAttribute(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertObjectNotHasAttribute')) { + /** + * Asserts that an object does not have a specified attribute. + * + * @param object $object + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertObjectNotHasAttribute + */ + function assertObjectNotHasAttribute(string $attributeName, $object, string $message = ''): void + { + Assert::assertObjectNotHasAttribute(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertObjectHasProperty')) { + /** + * Asserts that an object has a specified property. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertObjectHasProperty + */ + function assertObjectHasProperty(string $attributeName, object $object, string $message = ''): void + { + Assert::assertObjectHasProperty(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertObjectNotHasProperty')) { + /** + * Asserts that an object does not have a specified property. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertObjectNotHasProperty + */ + function assertObjectNotHasProperty(string $attributeName, object $object, string $message = ''): void + { + Assert::assertObjectNotHasProperty(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertSame')) { + /** + * Asserts that two variables have the same type and value. + * Used on objects, it asserts that two variables reference + * the same object. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-template ExpectedType + * + * @psalm-param ExpectedType $expected + * + * @psalm-assert =ExpectedType $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertSame + */ + function assertSame($expected, $actual, string $message = ''): void + { + Assert::assertSame(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNotSame')) { + /** + * Asserts that two variables do not have the same type and value. + * Used on objects, it asserts that two variables do not reference + * the same object. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotSame + */ + function assertNotSame($expected, $actual, string $message = ''): void + { + Assert::assertNotSame(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertInstanceOf')) { + /** + * Asserts that a variable is of a given type. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * + * @psalm-template ExpectedType of object + * + * @psalm-param class-string $expected + * + * @psalm-assert =ExpectedType $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertInstanceOf + */ + function assertInstanceOf(string $expected, $actual, string $message = ''): void + { + Assert::assertInstanceOf(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNotInstanceOf')) { + /** + * Asserts that a variable is not of a given type. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * + * @psalm-template ExpectedType of object + * + * @psalm-param class-string $expected + * + * @psalm-assert !ExpectedType $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotInstanceOf + */ + function assertNotInstanceOf(string $expected, $actual, string $message = ''): void + { + Assert::assertNotInstanceOf(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsArray')) { + /** + * Asserts that a variable is of type array. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert array $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsArray + */ + function assertIsArray($actual, string $message = ''): void + { + Assert::assertIsArray(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsBool')) { + /** + * Asserts that a variable is of type bool. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert bool $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsBool + */ + function assertIsBool($actual, string $message = ''): void + { + Assert::assertIsBool(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsFloat')) { + /** + * Asserts that a variable is of type float. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert float $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsFloat + */ + function assertIsFloat($actual, string $message = ''): void + { + Assert::assertIsFloat(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsInt')) { + /** + * Asserts that a variable is of type int. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert int $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsInt + */ + function assertIsInt($actual, string $message = ''): void + { + Assert::assertIsInt(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsNumeric')) { + /** + * Asserts that a variable is of type numeric. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert numeric $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNumeric + */ + function assertIsNumeric($actual, string $message = ''): void + { + Assert::assertIsNumeric(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsObject')) { + /** + * Asserts that a variable is of type object. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert object $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsObject + */ + function assertIsObject($actual, string $message = ''): void + { + Assert::assertIsObject(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsResource')) { + /** + * Asserts that a variable is of type resource. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert resource $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsResource + */ + function assertIsResource($actual, string $message = ''): void + { + Assert::assertIsResource(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsClosedResource')) { + /** + * Asserts that a variable is of type resource and is closed. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert resource $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsClosedResource + */ + function assertIsClosedResource($actual, string $message = ''): void + { + Assert::assertIsClosedResource(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsString')) { + /** + * Asserts that a variable is of type string. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert string $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsString + */ + function assertIsString($actual, string $message = ''): void + { + Assert::assertIsString(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsScalar')) { + /** + * Asserts that a variable is of type scalar. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert scalar $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsScalar + */ + function assertIsScalar($actual, string $message = ''): void + { + Assert::assertIsScalar(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsCallable')) { + /** + * Asserts that a variable is of type callable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert callable $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsCallable + */ + function assertIsCallable($actual, string $message = ''): void + { + Assert::assertIsCallable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsIterable')) { + /** + * Asserts that a variable is of type iterable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert iterable $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsIterable + */ + function assertIsIterable($actual, string $message = ''): void + { + Assert::assertIsIterable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsNotArray')) { + /** + * Asserts that a variable is not of type array. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert !array $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotArray + */ + function assertIsNotArray($actual, string $message = ''): void + { + Assert::assertIsNotArray(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsNotBool')) { + /** + * Asserts that a variable is not of type bool. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert !bool $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotBool + */ + function assertIsNotBool($actual, string $message = ''): void + { + Assert::assertIsNotBool(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsNotFloat')) { + /** + * Asserts that a variable is not of type float. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert !float $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotFloat + */ + function assertIsNotFloat($actual, string $message = ''): void + { + Assert::assertIsNotFloat(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsNotInt')) { + /** + * Asserts that a variable is not of type int. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert !int $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotInt + */ + function assertIsNotInt($actual, string $message = ''): void + { + Assert::assertIsNotInt(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsNotNumeric')) { + /** + * Asserts that a variable is not of type numeric. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert !numeric $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotNumeric + */ + function assertIsNotNumeric($actual, string $message = ''): void + { + Assert::assertIsNotNumeric(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsNotObject')) { + /** + * Asserts that a variable is not of type object. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert !object $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotObject + */ + function assertIsNotObject($actual, string $message = ''): void + { + Assert::assertIsNotObject(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsNotResource')) { + /** + * Asserts that a variable is not of type resource. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert !resource $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotResource + */ + function assertIsNotResource($actual, string $message = ''): void + { + Assert::assertIsNotResource(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsNotClosedResource')) { + /** + * Asserts that a variable is not of type resource. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert !resource $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotClosedResource + */ + function assertIsNotClosedResource($actual, string $message = ''): void + { + Assert::assertIsNotClosedResource(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsNotString')) { + /** + * Asserts that a variable is not of type string. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert !string $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotString + */ + function assertIsNotString($actual, string $message = ''): void + { + Assert::assertIsNotString(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsNotScalar')) { + /** + * Asserts that a variable is not of type scalar. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert !scalar $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotScalar + */ + function assertIsNotScalar($actual, string $message = ''): void + { + Assert::assertIsNotScalar(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsNotCallable')) { + /** + * Asserts that a variable is not of type callable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert !callable $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotCallable + */ + function assertIsNotCallable($actual, string $message = ''): void + { + Assert::assertIsNotCallable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsNotIterable')) { + /** + * Asserts that a variable is not of type iterable. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @psalm-assert !iterable $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotIterable + */ + function assertIsNotIterable($actual, string $message = ''): void + { + Assert::assertIsNotIterable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertMatchesRegularExpression')) { + /** + * Asserts that a string matches a given regular expression. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertMatchesRegularExpression + */ + function assertMatchesRegularExpression(string $pattern, string $string, string $message = ''): void + { + Assert::assertMatchesRegularExpression(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertRegExp')) { + /** + * Asserts that a string matches a given regular expression. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @codeCoverageIgnore + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4086 + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertRegExp + */ + function assertRegExp(string $pattern, string $string, string $message = ''): void + { + Assert::assertRegExp(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertDoesNotMatchRegularExpression')) { + /** + * Asserts that a string does not match a given regular expression. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertDoesNotMatchRegularExpression + */ + function assertDoesNotMatchRegularExpression(string $pattern, string $string, string $message = ''): void + { + Assert::assertDoesNotMatchRegularExpression(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNotRegExp')) { + /** + * Asserts that a string does not match a given regular expression. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @codeCoverageIgnore + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4089 + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotRegExp + */ + function assertNotRegExp(string $pattern, string $string, string $message = ''): void + { + Assert::assertNotRegExp(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertSameSize')) { + /** + * Assert that the size of two arrays (or `Countable` or `Traversable` objects) + * is the same. + * + * @param Countable|iterable $expected + * @param Countable|iterable $actual + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertSameSize + */ + function assertSameSize($expected, $actual, string $message = ''): void + { + Assert::assertSameSize(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNotSameSize')) { + /** + * Assert that the size of two arrays (or `Countable` or `Traversable` objects) + * is not the same. + * + * @param Countable|iterable $expected + * @param Countable|iterable $actual + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotSameSize + */ + function assertNotSameSize($expected, $actual, string $message = ''): void + { + Assert::assertNotSameSize(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringMatchesFormat')) { + /** + * Asserts that a string matches a given format string. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringMatchesFormat + */ + function assertStringMatchesFormat(string $format, string $string, string $message = ''): void + { + Assert::assertStringMatchesFormat(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringNotMatchesFormat')) { + /** + * Asserts that a string does not match a given format string. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringNotMatchesFormat + */ + function assertStringNotMatchesFormat(string $format, string $string, string $message = ''): void + { + Assert::assertStringNotMatchesFormat(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringMatchesFormatFile')) { + /** + * Asserts that a string matches a given format file. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringMatchesFormatFile + */ + function assertStringMatchesFormatFile(string $formatFile, string $string, string $message = ''): void + { + Assert::assertStringMatchesFormatFile(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringNotMatchesFormatFile')) { + /** + * Asserts that a string does not match a given format string. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringNotMatchesFormatFile + */ + function assertStringNotMatchesFormatFile(string $formatFile, string $string, string $message = ''): void + { + Assert::assertStringNotMatchesFormatFile(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringStartsWith')) { + /** + * Asserts that a string starts with a given prefix. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringStartsWith + */ + function assertStringStartsWith(string $prefix, string $string, string $message = ''): void + { + Assert::assertStringStartsWith(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringStartsNotWith')) { + /** + * Asserts that a string starts not with a given prefix. + * + * @param string $prefix + * @param string $string + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringStartsNotWith + */ + function assertStringStartsNotWith($prefix, $string, string $message = ''): void + { + Assert::assertStringStartsNotWith(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringContainsString')) { + /** + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringContainsString + */ + function assertStringContainsString(string $needle, string $haystack, string $message = ''): void + { + Assert::assertStringContainsString(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringContainsStringIgnoringCase')) { + /** + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringContainsStringIgnoringCase + */ + function assertStringContainsStringIgnoringCase(string $needle, string $haystack, string $message = ''): void + { + Assert::assertStringContainsStringIgnoringCase(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringNotContainsString')) { + /** + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringNotContainsString + */ + function assertStringNotContainsString(string $needle, string $haystack, string $message = ''): void + { + Assert::assertStringNotContainsString(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringNotContainsStringIgnoringCase')) { + /** + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringNotContainsStringIgnoringCase + */ + function assertStringNotContainsStringIgnoringCase(string $needle, string $haystack, string $message = ''): void + { + Assert::assertStringNotContainsStringIgnoringCase(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringEndsWith')) { + /** + * Asserts that a string ends with a given suffix. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringEndsWith + */ + function assertStringEndsWith(string $suffix, string $string, string $message = ''): void + { + Assert::assertStringEndsWith(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringEndsNotWith')) { + /** + * Asserts that a string ends not with a given suffix. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringEndsNotWith + */ + function assertStringEndsNotWith(string $suffix, string $string, string $message = ''): void + { + Assert::assertStringEndsNotWith(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertXmlFileEqualsXmlFile')) { + /** + * Asserts that two XML files are equal. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertXmlFileEqualsXmlFile + */ + function assertXmlFileEqualsXmlFile(string $expectedFile, string $actualFile, string $message = ''): void + { + Assert::assertXmlFileEqualsXmlFile(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertXmlFileNotEqualsXmlFile')) { + /** + * Asserts that two XML files are not equal. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws \PHPUnit\Util\Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertXmlFileNotEqualsXmlFile + */ + function assertXmlFileNotEqualsXmlFile(string $expectedFile, string $actualFile, string $message = ''): void + { + Assert::assertXmlFileNotEqualsXmlFile(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertXmlStringEqualsXmlFile')) { + /** + * Asserts that two XML documents are equal. + * + * @param DOMDocument|string $actualXml + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws \PHPUnit\Util\Xml\Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertXmlStringEqualsXmlFile + */ + function assertXmlStringEqualsXmlFile(string $expectedFile, $actualXml, string $message = ''): void + { + Assert::assertXmlStringEqualsXmlFile(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertXmlStringNotEqualsXmlFile')) { + /** + * Asserts that two XML documents are not equal. + * + * @param DOMDocument|string $actualXml + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws \PHPUnit\Util\Xml\Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertXmlStringNotEqualsXmlFile + */ + function assertXmlStringNotEqualsXmlFile(string $expectedFile, $actualXml, string $message = ''): void + { + Assert::assertXmlStringNotEqualsXmlFile(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertXmlStringEqualsXmlString')) { + /** + * Asserts that two XML documents are equal. + * + * @param DOMDocument|string $expectedXml + * @param DOMDocument|string $actualXml + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws \PHPUnit\Util\Xml\Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertXmlStringEqualsXmlString + */ + function assertXmlStringEqualsXmlString($expectedXml, $actualXml, string $message = ''): void + { + Assert::assertXmlStringEqualsXmlString(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertXmlStringNotEqualsXmlString')) { + /** + * Asserts that two XML documents are not equal. + * + * @param DOMDocument|string $expectedXml + * @param DOMDocument|string $actualXml + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws \PHPUnit\Util\Xml\Exception + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertXmlStringNotEqualsXmlString + */ + function assertXmlStringNotEqualsXmlString($expectedXml, $actualXml, string $message = ''): void + { + Assert::assertXmlStringNotEqualsXmlString(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertEqualXMLStructure')) { + /** + * Asserts that a hierarchy of DOMElements matches. + * + * @throws AssertionFailedError + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @codeCoverageIgnore + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4091 + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertEqualXMLStructure + */ + function assertEqualXMLStructure(DOMElement $expectedElement, DOMElement $actualElement, bool $checkAttributes = false, string $message = ''): void + { + Assert::assertEqualXMLStructure(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertThat')) { + /** + * Evaluates a PHPUnit\Framework\Constraint matcher object. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertThat + */ + function assertThat($value, Constraint $constraint, string $message = ''): void + { + Assert::assertThat(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertJson')) { + /** + * Asserts that a string is a valid JSON string. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertJson + */ + function assertJson(string $actualJson, string $message = ''): void + { + Assert::assertJson(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertJsonStringEqualsJsonString')) { + /** + * Asserts that two given JSON encoded objects or arrays are equal. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertJsonStringEqualsJsonString + */ + function assertJsonStringEqualsJsonString(string $expectedJson, string $actualJson, string $message = ''): void + { + Assert::assertJsonStringEqualsJsonString(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertJsonStringNotEqualsJsonString')) { + /** + * Asserts that two given JSON encoded objects or arrays are not equal. + * + * @param string $expectedJson + * @param string $actualJson + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertJsonStringNotEqualsJsonString + */ + function assertJsonStringNotEqualsJsonString($expectedJson, $actualJson, string $message = ''): void + { + Assert::assertJsonStringNotEqualsJsonString(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertJsonStringEqualsJsonFile')) { + /** + * Asserts that the generated JSON encoded object and the content of the given file are equal. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertJsonStringEqualsJsonFile + */ + function assertJsonStringEqualsJsonFile(string $expectedFile, string $actualJson, string $message = ''): void + { + Assert::assertJsonStringEqualsJsonFile(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertJsonStringNotEqualsJsonFile')) { + /** + * Asserts that the generated JSON encoded object and the content of the given file are not equal. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertJsonStringNotEqualsJsonFile + */ + function assertJsonStringNotEqualsJsonFile(string $expectedFile, string $actualJson, string $message = ''): void + { + Assert::assertJsonStringNotEqualsJsonFile(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertJsonFileEqualsJsonFile')) { + /** + * Asserts that two JSON files are equal. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertJsonFileEqualsJsonFile + */ + function assertJsonFileEqualsJsonFile(string $expectedFile, string $actualFile, string $message = ''): void + { + Assert::assertJsonFileEqualsJsonFile(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertJsonFileNotEqualsJsonFile')) { + /** + * Asserts that two JSON files are not equal. + * + * @throws ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertJsonFileNotEqualsJsonFile + */ + function assertJsonFileNotEqualsJsonFile(string $expectedFile, string $actualFile, string $message = ''): void + { + Assert::assertJsonFileNotEqualsJsonFile(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\logicalAnd')) { + function logicalAnd(): LogicalAnd + { + return Assert::logicalAnd(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\logicalOr')) { + function logicalOr(): LogicalOr + { + return Assert::logicalOr(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\logicalNot')) { + function logicalNot(Constraint $constraint): LogicalNot + { + return Assert::logicalNot(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\logicalXor')) { + function logicalXor(): LogicalXor + { + return Assert::logicalXor(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\anything')) { + function anything(): IsAnything + { + return Assert::anything(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\isTrue')) { + function isTrue(): IsTrue + { + return Assert::isTrue(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\callback')) { + function callback(callable $callback): Callback + { + return Assert::callback(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\isFalse')) { + function isFalse(): IsFalse + { + return Assert::isFalse(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\isJson')) { + function isJson(): IsJson + { + return Assert::isJson(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\isNull')) { + function isNull(): IsNull + { + return Assert::isNull(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\isFinite')) { + function isFinite(): IsFinite + { + return Assert::isFinite(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\isInfinite')) { + function isInfinite(): IsInfinite + { + return Assert::isInfinite(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\isNan')) { + function isNan(): IsNan + { + return Assert::isNan(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\containsEqual')) { + function containsEqual($value): TraversableContainsEqual + { + return Assert::containsEqual(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\containsIdentical')) { + function containsIdentical($value): TraversableContainsIdentical + { + return Assert::containsIdentical(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\containsOnly')) { + function containsOnly(string $type): TraversableContainsOnly + { + return Assert::containsOnly(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\containsOnlyInstancesOf')) { + function containsOnlyInstancesOf(string $className): TraversableContainsOnly + { + return Assert::containsOnlyInstancesOf(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\arrayHasKey')) { + function arrayHasKey($key): ArrayHasKey + { + return Assert::arrayHasKey(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\equalTo')) { + function equalTo($value): IsEqual + { + return Assert::equalTo(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\equalToCanonicalizing')) { + function equalToCanonicalizing($value): IsEqualCanonicalizing + { + return Assert::equalToCanonicalizing(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\equalToIgnoringCase')) { + function equalToIgnoringCase($value): IsEqualIgnoringCase + { + return Assert::equalToIgnoringCase(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\equalToWithDelta')) { + function equalToWithDelta($value, float $delta): IsEqualWithDelta + { + return Assert::equalToWithDelta(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\isEmpty')) { + function isEmpty(): IsEmpty + { + return Assert::isEmpty(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\isWritable')) { + function isWritable(): IsWritable + { + return Assert::isWritable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\isReadable')) { + function isReadable(): IsReadable + { + return Assert::isReadable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\directoryExists')) { + function directoryExists(): DirectoryExists + { + return Assert::directoryExists(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\fileExists')) { + function fileExists(): FileExists + { + return Assert::fileExists(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\greaterThan')) { + function greaterThan($value): GreaterThan + { + return Assert::greaterThan(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\greaterThanOrEqual')) { + function greaterThanOrEqual($value): LogicalOr + { + return Assert::greaterThanOrEqual(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\classHasAttribute')) { + function classHasAttribute(string $attributeName): ClassHasAttribute + { + return Assert::classHasAttribute(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\classHasStaticAttribute')) { + function classHasStaticAttribute(string $attributeName): ClassHasStaticAttribute + { + return Assert::classHasStaticAttribute(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\objectHasAttribute')) { + function objectHasAttribute($attributeName): ObjectHasAttribute + { + return Assert::objectHasAttribute(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\identicalTo')) { + function identicalTo($value): IsIdentical + { + return Assert::identicalTo(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\isInstanceOf')) { + function isInstanceOf(string $className): IsInstanceOf + { + return Assert::isInstanceOf(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\isType')) { + function isType(string $type): IsType + { + return Assert::isType(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\lessThan')) { + function lessThan($value): LessThan + { + return Assert::lessThan(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\lessThanOrEqual')) { + function lessThanOrEqual($value): LogicalOr + { + return Assert::lessThanOrEqual(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\matchesRegularExpression')) { + function matchesRegularExpression(string $pattern): RegularExpression + { + return Assert::matchesRegularExpression(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\matches')) { + function matches(string $string): StringMatchesFormatDescription + { + return Assert::matches(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\stringStartsWith')) { + function stringStartsWith($prefix): StringStartsWith + { + return Assert::stringStartsWith(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\stringContains')) { + function stringContains(string $string, bool $case = true): StringContains + { + return Assert::stringContains(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\stringEndsWith')) { + function stringEndsWith(string $suffix): StringEndsWith + { + return Assert::stringEndsWith(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\countOf')) { + function countOf(int $count): Count + { + return Assert::countOf(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\objectEquals')) { + function objectEquals(object $object, string $method = 'equals'): ObjectEquals + { + return Assert::objectEquals(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\any')) { + /** + * Returns a matcher that matches when the method is executed + * zero or more times. + */ + function any(): AnyInvokedCountMatcher + { + return new AnyInvokedCountMatcher; + } +} + +if (!function_exists('PHPUnit\Framework\never')) { + /** + * Returns a matcher that matches when the method is never executed. + */ + function never(): InvokedCountMatcher + { + return new InvokedCountMatcher(0); + } +} + +if (!function_exists('PHPUnit\Framework\atLeast')) { + /** + * Returns a matcher that matches when the method is executed + * at least N times. + */ + function atLeast(int $requiredInvocations): InvokedAtLeastCountMatcher + { + return new InvokedAtLeastCountMatcher( + $requiredInvocations, + ); + } +} + +if (!function_exists('PHPUnit\Framework\atLeastOnce')) { + /** + * Returns a matcher that matches when the method is executed at least once. + */ + function atLeastOnce(): InvokedAtLeastOnceMatcher + { + return new InvokedAtLeastOnceMatcher; + } +} + +if (!function_exists('PHPUnit\Framework\once')) { + /** + * Returns a matcher that matches when the method is executed exactly once. + */ + function once(): InvokedCountMatcher + { + return new InvokedCountMatcher(1); + } +} + +if (!function_exists('PHPUnit\Framework\exactly')) { + /** + * Returns a matcher that matches when the method is executed + * exactly $count times. + */ + function exactly(int $count): InvokedCountMatcher + { + return new InvokedCountMatcher($count); + } +} + +if (!function_exists('PHPUnit\Framework\atMost')) { + /** + * Returns a matcher that matches when the method is executed + * at most N times. + */ + function atMost(int $allowedInvocations): InvokedAtMostCountMatcher + { + return new InvokedAtMostCountMatcher($allowedInvocations); + } +} + +if (!function_exists('PHPUnit\Framework\at')) { + /** + * Returns a matcher that matches when the method is executed + * at the given index. + */ + function at(int $index): InvokedAtIndexMatcher + { + return new InvokedAtIndexMatcher($index); + } +} + +if (!function_exists('PHPUnit\Framework\returnValue')) { + function returnValue($value): ReturnStub + { + return new ReturnStub($value); + } +} + +if (!function_exists('PHPUnit\Framework\returnValueMap')) { + function returnValueMap(array $valueMap): ReturnValueMapStub + { + return new ReturnValueMapStub($valueMap); + } +} + +if (!function_exists('PHPUnit\Framework\returnArgument')) { + function returnArgument(int $argumentIndex): ReturnArgumentStub + { + return new ReturnArgumentStub($argumentIndex); + } +} + +if (!function_exists('PHPUnit\Framework\returnCallback')) { + function returnCallback($callback): ReturnCallbackStub + { + return new ReturnCallbackStub($callback); + } +} + +if (!function_exists('PHPUnit\Framework\returnSelf')) { + /** + * Returns the current object. + * + * This method is useful when mocking a fluent interface. + */ + function returnSelf(): ReturnSelfStub + { + return new ReturnSelfStub; + } +} + +if (!function_exists('PHPUnit\Framework\throwException')) { + function throwException(Throwable $exception): ExceptionStub + { + return new ExceptionStub($exception); + } +} + +if (!function_exists('PHPUnit\Framework\onConsecutiveCalls')) { + function onConsecutiveCalls(): ConsecutiveCallsStub + { + $args = func_get_args(); + + return new ConsecutiveCallsStub($args); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Boolean/IsFalse.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Boolean/IsFalse.php new file mode 100644 index 00000000..212e2bcb --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Boolean/IsFalse.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class IsFalse extends Constraint +{ + /** + * Returns a string representation of the constraint. + */ + public function toString(): string + { + return 'is false'; + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other): bool + { + return $other === false; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Boolean/IsTrue.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Boolean/IsTrue.php new file mode 100644 index 00000000..e1d6b269 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Boolean/IsTrue.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class IsTrue extends Constraint +{ + /** + * Returns a string representation of the constraint. + */ + public function toString(): string + { + return 'is true'; + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other): bool + { + return $other === true; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Callback.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Callback.php new file mode 100644 index 00000000..b7cf95a1 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Callback.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +/** + * @psalm-template CallbackInput of mixed + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class Callback extends Constraint +{ + /** + * @var callable + * + * @psalm-var callable(CallbackInput $input): bool + */ + private $callback; + + /** @psalm-param callable(CallbackInput $input): bool $callback */ + public function __construct(callable $callback) + { + $this->callback = $callback; + } + + /** + * Returns a string representation of the constraint. + */ + public function toString(): string + { + return 'is accepted by specified callback'; + } + + /** + * Evaluates the constraint for parameter $value. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + * + * @psalm-param CallbackInput $other + */ + protected function matches($other): bool + { + return ($this->callback)($other); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Cardinality/Count.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Cardinality/Count.php new file mode 100644 index 00000000..ff04a698 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Cardinality/Count.php @@ -0,0 +1,142 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function count; +use function is_array; +use function iterator_count; +use function sprintf; +use Countable; +use EmptyIterator; +use Generator; +use Iterator; +use IteratorAggregate; +use PHPUnit\Framework\Exception; +use Traversable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +class Count extends Constraint +{ + /** + * @var int + */ + private $expectedCount; + + public function __construct(int $expected) + { + $this->expectedCount = $expected; + } + + public function toString(): string + { + return sprintf( + 'count matches %d', + $this->expectedCount, + ); + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @throws Exception + */ + protected function matches($other): bool + { + return $this->expectedCount === $this->getCountOf($other); + } + + /** + * @throws Exception + */ + protected function getCountOf($other): ?int + { + if ($other instanceof Countable || is_array($other)) { + return count($other); + } + + if ($other instanceof EmptyIterator) { + return 0; + } + + if ($other instanceof Traversable) { + while ($other instanceof IteratorAggregate) { + try { + $other = $other->getIterator(); + } catch (\Exception $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + } + + $iterator = $other; + + if ($iterator instanceof Generator) { + return $this->getCountOfGenerator($iterator); + } + + if (!$iterator instanceof Iterator) { + return iterator_count($iterator); + } + + $key = $iterator->key(); + $count = iterator_count($iterator); + + // Manually rewind $iterator to previous key, since iterator_count + // moves pointer. + if ($key !== null) { + $iterator->rewind(); + + while ($iterator->valid() && $key !== $iterator->key()) { + $iterator->next(); + } + } + + return $count; + } + + return null; + } + + /** + * Returns the total number of iterations from a generator. + * This will fully exhaust the generator. + */ + protected function getCountOfGenerator(Generator $generator): int + { + for ($count = 0; $generator->valid(); $generator->next()) { + $count++; + } + + return $count; + } + + /** + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object + */ + protected function failureDescription($other): string + { + return sprintf( + 'actual size %d matches expected size %d', + (int) $this->getCountOf($other), + $this->expectedCount, + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Cardinality/GreaterThan.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Cardinality/GreaterThan.php new file mode 100644 index 00000000..31df5020 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Cardinality/GreaterThan.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class GreaterThan extends Constraint +{ + /** + * @var float|int + */ + private $value; + + /** + * @param float|int $value + */ + public function __construct($value) + { + $this->value = $value; + } + + /** + * Returns a string representation of the constraint. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function toString(): string + { + return 'is greater than ' . $this->exporter()->export($this->value); + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other): bool + { + return $this->value < $other; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Cardinality/IsEmpty.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Cardinality/IsEmpty.php new file mode 100644 index 00000000..ee01e93d --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Cardinality/IsEmpty.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function count; +use function gettype; +use function sprintf; +use function strpos; +use Countable; +use EmptyIterator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class IsEmpty extends Constraint +{ + /** + * Returns a string representation of the constraint. + */ + public function toString(): string + { + return 'is empty'; + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other): bool + { + if ($other instanceof EmptyIterator) { + return true; + } + + if ($other instanceof Countable) { + return count($other) === 0; + } + + return empty($other); + } + + /** + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object + */ + protected function failureDescription($other): string + { + $type = gettype($other); + + return sprintf( + '%s %s %s', + strpos($type, 'a') === 0 || strpos($type, 'o') === 0 ? 'an' : 'a', + $type, + $this->toString(), + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Cardinality/LessThan.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Cardinality/LessThan.php new file mode 100644 index 00000000..c7884ba1 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Cardinality/LessThan.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class LessThan extends Constraint +{ + /** + * @var float|int + */ + private $value; + + /** + * @param float|int $value + */ + public function __construct($value) + { + $this->value = $value; + } + + /** + * Returns a string representation of the constraint. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function toString(): string + { + return 'is less than ' . $this->exporter()->export($this->value); + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other): bool + { + return $this->value > $other; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Cardinality/SameSize.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Cardinality/SameSize.php new file mode 100644 index 00000000..a5467942 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Cardinality/SameSize.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class SameSize extends Count +{ + public function __construct(iterable $expected) + { + parent::__construct((int) $this->getCountOf($expected)); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Constraint.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Constraint.php new file mode 100644 index 00000000..37548e56 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Constraint.php @@ -0,0 +1,269 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function sprintf; +use Countable; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\SelfDescribing; +use SebastianBergmann\Comparator\ComparisonFailure; +use SebastianBergmann\Exporter\Exporter; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +abstract class Constraint implements Countable, SelfDescribing +{ + /** + * @var ?Exporter + */ + private $exporter; + + /** + * Evaluates the constraint for parameter $other. + * + * If $returnResult is set to false (the default), an exception is thrown + * in case of a failure. null is returned otherwise. + * + * If $returnResult is true, the result of the evaluation is returned as + * a boolean value instead: true in case of success, false in case of a + * failure. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public function evaluate($other, string $description = '', bool $returnResult = false): ?bool + { + $success = false; + + if ($this->matches($other)) { + $success = true; + } + + if ($returnResult) { + return $success; + } + + if (!$success) { + $this->fail($other, $description); + } + + return null; + } + + /** + * Counts the number of constraint elements. + */ + public function count(): int + { + return 1; + } + + protected function exporter(): Exporter + { + if ($this->exporter === null) { + $this->exporter = new Exporter; + } + + return $this->exporter; + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * This method can be overridden to implement the evaluation algorithm. + * + * @param mixed $other value or object to evaluate + * + * @codeCoverageIgnore + */ + protected function matches($other): bool + { + return false; + } + + /** + * Throws an exception for the given compared value and test description. + * + * @param mixed $other evaluated value or object + * @param string $description Additional information about the test + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-return never-return + */ + protected function fail($other, $description, ComparisonFailure $comparisonFailure = null): void + { + $failureDescription = sprintf( + 'Failed asserting that %s.', + $this->failureDescription($other), + ); + + $additionalFailureDescription = $this->additionalFailureDescription($other); + + if ($additionalFailureDescription) { + $failureDescription .= "\n" . $additionalFailureDescription; + } + + if (!empty($description)) { + $failureDescription = $description . "\n" . $failureDescription; + } + + throw new ExpectationFailedException( + $failureDescription, + $comparisonFailure, + ); + } + + /** + * Return additional failure description where needed. + * + * The function can be overridden to provide additional failure + * information like a diff + * + * @param mixed $other evaluated value or object + */ + protected function additionalFailureDescription($other): string + { + return ''; + } + + /** + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * To provide additional failure information additionalFailureDescription + * can be used. + * + * @param mixed $other evaluated value or object + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + protected function failureDescription($other): string + { + return $this->exporter()->export($other) . ' ' . $this->toString(); + } + + /** + * Returns a custom string representation of the constraint object when it + * appears in context of an $operator expression. + * + * The purpose of this method is to provide meaningful descriptive string + * in context of operators such as LogicalNot. Native PHPUnit constraints + * are supported out of the box by LogicalNot, but externally developed + * ones had no way to provide correct strings in this context. + * + * The method shall return empty string, when it does not handle + * customization by itself. + * + * @param Operator $operator the $operator of the expression + * @param mixed $role role of $this constraint in the $operator expression + */ + protected function toStringInContext(Operator $operator, $role): string + { + return ''; + } + + /** + * Returns the description of the failure when this constraint appears in + * context of an $operator expression. + * + * The purpose of this method is to provide meaningful failure description + * in context of operators such as LogicalNot. Native PHPUnit constraints + * are supported out of the box by LogicalNot, but externally developed + * ones had no way to provide correct messages in this context. + * + * The method shall return empty string, when it does not handle + * customization by itself. + * + * @param Operator $operator the $operator of the expression + * @param mixed $role role of $this constraint in the $operator expression + * @param mixed $other evaluated value or object + */ + protected function failureDescriptionInContext(Operator $operator, $role, $other): string + { + $string = $this->toStringInContext($operator, $role); + + if ($string === '') { + return ''; + } + + return $this->exporter()->export($other) . ' ' . $string; + } + + /** + * Reduces the sub-expression starting at $this by skipping degenerate + * sub-expression and returns first descendant constraint that starts + * a non-reducible sub-expression. + * + * Returns $this for terminal constraints and for operators that start + * non-reducible sub-expression, or the nearest descendant of $this that + * starts a non-reducible sub-expression. + * + * A constraint expression may be modelled as a tree with non-terminal + * nodes (operators) and terminal nodes. For example: + * + * LogicalOr (operator, non-terminal) + * + LogicalAnd (operator, non-terminal) + * | + IsType('int') (terminal) + * | + GreaterThan(10) (terminal) + * + LogicalNot (operator, non-terminal) + * + IsType('array') (terminal) + * + * A degenerate sub-expression is a part of the tree, that effectively does + * not contribute to the evaluation of the expression it appears in. An example + * of degenerate sub-expression is a BinaryOperator constructed with single + * operand or nested BinaryOperators, each with single operand. An + * expression involving a degenerate sub-expression is equivalent to a + * reduced expression with the degenerate sub-expression removed, for example + * + * LogicalAnd (operator) + * + LogicalOr (degenerate operator) + * | + LogicalAnd (degenerate operator) + * | + IsType('int') (terminal) + * + GreaterThan(10) (terminal) + * + * is equivalent to + * + * LogicalAnd (operator) + * + IsType('int') (terminal) + * + GreaterThan(10) (terminal) + * + * because the subexpression + * + * + LogicalOr + * + LogicalAnd + * + - + * + * is degenerate. Calling reduce() on the LogicalOr object above, as well + * as on LogicalAnd, shall return the IsType('int') instance. + * + * Other specific reductions can be implemented, for example cascade of + * LogicalNot operators + * + * + LogicalNot + * + LogicalNot + * +LogicalNot + * + IsTrue + * + * can be reduced to + * + * LogicalNot + * + IsTrue + */ + protected function reduce(): self + { + return $this; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqual.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqual.php new file mode 100644 index 00000000..d209444b --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqual.php @@ -0,0 +1,136 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function is_string; +use function sprintf; +use function strpos; +use function trim; +use PHPUnit\Framework\ExpectationFailedException; +use SebastianBergmann\Comparator\ComparisonFailure; +use SebastianBergmann\Comparator\Factory as ComparatorFactory; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class IsEqual extends Constraint +{ + /** + * @var mixed + */ + private $value; + + /** + * @var float + */ + private $delta; + + /** + * @var bool + */ + private $canonicalize; + + /** + * @var bool + */ + private $ignoreCase; + + public function __construct($value, float $delta = 0.0, bool $canonicalize = false, bool $ignoreCase = false) + { + $this->value = $value; + $this->delta = $delta; + $this->canonicalize = $canonicalize; + $this->ignoreCase = $ignoreCase; + } + + /** + * Evaluates the constraint for parameter $other. + * + * If $returnResult is set to false (the default), an exception is thrown + * in case of a failure. null is returned otherwise. + * + * If $returnResult is true, the result of the evaluation is returned as + * a boolean value instead: true in case of success, false in case of a + * failure. + * + * @throws ExpectationFailedException + */ + public function evaluate($other, string $description = '', bool $returnResult = false): ?bool + { + // If $this->value and $other are identical, they are also equal. + // This is the most common path and will allow us to skip + // initialization of all the comparators. + if ($this->value === $other) { + return true; + } + + $comparatorFactory = ComparatorFactory::getInstance(); + + try { + $comparator = $comparatorFactory->getComparatorFor( + $this->value, + $other, + ); + + $comparator->assertEquals( + $this->value, + $other, + $this->delta, + $this->canonicalize, + $this->ignoreCase, + ); + } catch (ComparisonFailure $f) { + if ($returnResult) { + return false; + } + + throw new ExpectationFailedException( + trim($description . "\n" . $f->getMessage()), + $f, + ); + } + + return true; + } + + /** + * Returns a string representation of the constraint. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function toString(): string + { + $delta = ''; + + if (is_string($this->value)) { + if (strpos($this->value, "\n") !== false) { + return 'is equal to '; + } + + return sprintf( + "is equal to '%s'", + $this->value, + ); + } + + if ($this->delta != 0) { + $delta = sprintf( + ' with delta <%F>', + $this->delta, + ); + } + + return sprintf( + 'is equal to %s%s', + $this->exporter()->export($this->value), + $delta, + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqualCanonicalizing.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqualCanonicalizing.php new file mode 100644 index 00000000..b87dadad --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqualCanonicalizing.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function is_string; +use function sprintf; +use function strpos; +use function trim; +use PHPUnit\Framework\ExpectationFailedException; +use SebastianBergmann\Comparator\ComparisonFailure; +use SebastianBergmann\Comparator\Factory as ComparatorFactory; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class IsEqualCanonicalizing extends Constraint +{ + /** + * @var mixed + */ + private $value; + + public function __construct($value) + { + $this->value = $value; + } + + /** + * Evaluates the constraint for parameter $other. + * + * If $returnResult is set to false (the default), an exception is thrown + * in case of a failure. null is returned otherwise. + * + * If $returnResult is true, the result of the evaluation is returned as + * a boolean value instead: true in case of success, false in case of a + * failure. + * + * @throws ExpectationFailedException + */ + public function evaluate($other, string $description = '', bool $returnResult = false): ?bool + { + // If $this->value and $other are identical, they are also equal. + // This is the most common path and will allow us to skip + // initialization of all the comparators. + if ($this->value === $other) { + return true; + } + + $comparatorFactory = ComparatorFactory::getInstance(); + + try { + $comparator = $comparatorFactory->getComparatorFor( + $this->value, + $other, + ); + + $comparator->assertEquals( + $this->value, + $other, + 0.0, + true, + false, + ); + } catch (ComparisonFailure $f) { + if ($returnResult) { + return false; + } + + throw new ExpectationFailedException( + trim($description . "\n" . $f->getMessage()), + $f, + ); + } + + return true; + } + + /** + * Returns a string representation of the constraint. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function toString(): string + { + if (is_string($this->value)) { + if (strpos($this->value, "\n") !== false) { + return 'is equal to '; + } + + return sprintf( + "is equal to '%s'", + $this->value, + ); + } + + return sprintf( + 'is equal to %s', + $this->exporter()->export($this->value), + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqualIgnoringCase.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqualIgnoringCase.php new file mode 100644 index 00000000..3642da21 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqualIgnoringCase.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function is_string; +use function sprintf; +use function strpos; +use function trim; +use PHPUnit\Framework\ExpectationFailedException; +use SebastianBergmann\Comparator\ComparisonFailure; +use SebastianBergmann\Comparator\Factory as ComparatorFactory; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class IsEqualIgnoringCase extends Constraint +{ + /** + * @var mixed + */ + private $value; + + public function __construct($value) + { + $this->value = $value; + } + + /** + * Evaluates the constraint for parameter $other. + * + * If $returnResult is set to false (the default), an exception is thrown + * in case of a failure. null is returned otherwise. + * + * If $returnResult is true, the result of the evaluation is returned as + * a boolean value instead: true in case of success, false in case of a + * failure. + * + * @throws ExpectationFailedException + */ + public function evaluate($other, string $description = '', bool $returnResult = false): ?bool + { + // If $this->value and $other are identical, they are also equal. + // This is the most common path and will allow us to skip + // initialization of all the comparators. + if ($this->value === $other) { + return true; + } + + $comparatorFactory = ComparatorFactory::getInstance(); + + try { + $comparator = $comparatorFactory->getComparatorFor( + $this->value, + $other, + ); + + $comparator->assertEquals( + $this->value, + $other, + 0.0, + false, + true, + ); + } catch (ComparisonFailure $f) { + if ($returnResult) { + return false; + } + + throw new ExpectationFailedException( + trim($description . "\n" . $f->getMessage()), + $f, + ); + } + + return true; + } + + /** + * Returns a string representation of the constraint. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function toString(): string + { + if (is_string($this->value)) { + if (strpos($this->value, "\n") !== false) { + return 'is equal to '; + } + + return sprintf( + "is equal to '%s'", + $this->value, + ); + } + + return sprintf( + 'is equal to %s', + $this->exporter()->export($this->value), + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqualWithDelta.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqualWithDelta.php new file mode 100644 index 00000000..f7d8aced --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqualWithDelta.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function sprintf; +use function trim; +use PHPUnit\Framework\ExpectationFailedException; +use SebastianBergmann\Comparator\ComparisonFailure; +use SebastianBergmann\Comparator\Factory as ComparatorFactory; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class IsEqualWithDelta extends Constraint +{ + /** + * @var mixed + */ + private $value; + + /** + * @var float + */ + private $delta; + + public function __construct($value, float $delta) + { + $this->value = $value; + $this->delta = $delta; + } + + /** + * Evaluates the constraint for parameter $other. + * + * If $returnResult is set to false (the default), an exception is thrown + * in case of a failure. null is returned otherwise. + * + * If $returnResult is true, the result of the evaluation is returned as + * a boolean value instead: true in case of success, false in case of a + * failure. + * + * @throws ExpectationFailedException + */ + public function evaluate($other, string $description = '', bool $returnResult = false): ?bool + { + // If $this->value and $other are identical, they are also equal. + // This is the most common path and will allow us to skip + // initialization of all the comparators. + if ($this->value === $other) { + return true; + } + + $comparatorFactory = ComparatorFactory::getInstance(); + + try { + $comparator = $comparatorFactory->getComparatorFor( + $this->value, + $other, + ); + + $comparator->assertEquals( + $this->value, + $other, + $this->delta, + ); + } catch (ComparisonFailure $f) { + if ($returnResult) { + return false; + } + + throw new ExpectationFailedException( + trim($description . "\n" . $f->getMessage()), + $f, + ); + } + + return true; + } + + /** + * Returns a string representation of the constraint. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function toString(): string + { + return sprintf( + 'is equal to %s with delta <%F>', + $this->exporter()->export($this->value), + $this->delta, + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Exception/Exception.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Exception/Exception.php new file mode 100644 index 00000000..bbaab4af --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Exception/Exception.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function get_class; +use function sprintf; +use PHPUnit\Util\Filter; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class Exception extends Constraint +{ + /** + * @var string + */ + private $className; + + public function __construct(string $className) + { + $this->className = $className; + } + + /** + * Returns a string representation of the constraint. + */ + public function toString(): string + { + return sprintf( + 'exception of type "%s"', + $this->className, + ); + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other): bool + { + return $other instanceof $this->className; + } + + /** + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object + */ + protected function failureDescription($other): string + { + if ($other !== null) { + $message = ''; + + if ($other instanceof Throwable) { + $message = '. Message was: "' . $other->getMessage() . '" at' + . "\n" . Filter::getFilteredStacktrace($other); + } + + return sprintf( + 'exception of type "%s" matches expected exception "%s"%s', + get_class($other), + $this->className, + $message, + ); + } + + return sprintf( + 'exception of type "%s" is thrown', + $this->className, + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Exception/ExceptionCode.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Exception/ExceptionCode.php new file mode 100644 index 00000000..a6fad4c7 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Exception/ExceptionCode.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function sprintf; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class ExceptionCode extends Constraint +{ + /** + * @var int|string + */ + private $expectedCode; + + /** + * @param int|string $expected + */ + public function __construct($expected) + { + $this->expectedCode = $expected; + } + + public function toString(): string + { + return 'exception code is '; + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param Throwable $other + */ + protected function matches($other): bool + { + return (string) $other->getCode() === (string) $this->expectedCode; + } + + /** + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + protected function failureDescription($other): string + { + return sprintf( + '%s is equal to expected exception code %s', + $this->exporter()->export($other->getCode()), + $this->exporter()->export($this->expectedCode), + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Exception/ExceptionMessage.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Exception/ExceptionMessage.php new file mode 100644 index 00000000..5139e720 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Exception/ExceptionMessage.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function sprintf; +use function strpos; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class ExceptionMessage extends Constraint +{ + /** + * @var string + */ + private $expectedMessage; + + public function __construct(string $expected) + { + $this->expectedMessage = $expected; + } + + public function toString(): string + { + if ($this->expectedMessage === '') { + return 'exception message is empty'; + } + + return 'exception message contains '; + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param Throwable $other + */ + protected function matches($other): bool + { + if ($this->expectedMessage === '') { + return $other->getMessage() === ''; + } + + return strpos((string) $other->getMessage(), $this->expectedMessage) !== false; + } + + /** + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object + */ + protected function failureDescription($other): string + { + if ($this->expectedMessage === '') { + return sprintf( + "exception message is empty but is '%s'", + $other->getMessage(), + ); + } + + return sprintf( + "exception message '%s' contains '%s'", + $other->getMessage(), + $this->expectedMessage, + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Exception/ExceptionMessageRegularExpression.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Exception/ExceptionMessageRegularExpression.php new file mode 100644 index 00000000..bc737709 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Exception/ExceptionMessageRegularExpression.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function sprintf; +use Exception; +use PHPUnit\Util\RegularExpression as RegularExpressionUtil; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class ExceptionMessageRegularExpression extends Constraint +{ + /** + * @var string + */ + private $expectedMessageRegExp; + + public function __construct(string $expected) + { + $this->expectedMessageRegExp = $expected; + } + + public function toString(): string + { + return 'exception message matches '; + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param \PHPUnit\Framework\Exception $other + * + * @throws \PHPUnit\Framework\Exception + * @throws Exception + */ + protected function matches($other): bool + { + $match = RegularExpressionUtil::safeMatch($this->expectedMessageRegExp, $other->getMessage()); + + if ($match === false) { + throw new \PHPUnit\Framework\Exception( + "Invalid expected exception message regex given: '{$this->expectedMessageRegExp}'", + ); + } + + return $match === 1; + } + + /** + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object + */ + protected function failureDescription($other): string + { + return sprintf( + "exception message '%s' matches '%s'", + $other->getMessage(), + $this->expectedMessageRegExp, + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Filesystem/DirectoryExists.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Filesystem/DirectoryExists.php new file mode 100644 index 00000000..24268c7d --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Filesystem/DirectoryExists.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function is_dir; +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class DirectoryExists extends Constraint +{ + /** + * Returns a string representation of the constraint. + */ + public function toString(): string + { + return 'directory exists'; + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other): bool + { + return is_dir($other); + } + + /** + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object + */ + protected function failureDescription($other): string + { + return sprintf( + 'directory "%s" exists', + $other, + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Filesystem/FileExists.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Filesystem/FileExists.php new file mode 100644 index 00000000..6cae9502 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Filesystem/FileExists.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function file_exists; +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class FileExists extends Constraint +{ + /** + * Returns a string representation of the constraint. + */ + public function toString(): string + { + return 'file exists'; + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other): bool + { + return file_exists($other); + } + + /** + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object + */ + protected function failureDescription($other): string + { + return sprintf( + 'file "%s" exists', + $other, + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Filesystem/IsReadable.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Filesystem/IsReadable.php new file mode 100644 index 00000000..12436938 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Filesystem/IsReadable.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function is_readable; +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class IsReadable extends Constraint +{ + /** + * Returns a string representation of the constraint. + */ + public function toString(): string + { + return 'is readable'; + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other): bool + { + return is_readable($other); + } + + /** + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object + */ + protected function failureDescription($other): string + { + return sprintf( + '"%s" is readable', + $other, + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Filesystem/IsWritable.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Filesystem/IsWritable.php new file mode 100644 index 00000000..8da02076 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Filesystem/IsWritable.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function is_writable; +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class IsWritable extends Constraint +{ + /** + * Returns a string representation of the constraint. + */ + public function toString(): string + { + return 'is writable'; + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other): bool + { + return is_writable($other); + } + + /** + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object + */ + protected function failureDescription($other): string + { + return sprintf( + '"%s" is writable', + $other, + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/IsAnything.php b/vendor/phpunit/phpunit/src/Framework/Constraint/IsAnything.php new file mode 100644 index 00000000..db84a743 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/IsAnything.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use PHPUnit\Framework\ExpectationFailedException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class IsAnything extends Constraint +{ + /** + * Evaluates the constraint for parameter $other. + * + * If $returnResult is set to false (the default), an exception is thrown + * in case of a failure. null is returned otherwise. + * + * If $returnResult is true, the result of the evaluation is returned as + * a boolean value instead: true in case of success, false in case of a + * failure. + * + * @throws ExpectationFailedException + */ + public function evaluate($other, string $description = '', bool $returnResult = false): ?bool + { + return $returnResult ? true : null; + } + + /** + * Returns a string representation of the constraint. + */ + public function toString(): string + { + return 'is anything'; + } + + /** + * Counts the number of constraint elements. + */ + public function count(): int + { + return 0; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/IsIdentical.php b/vendor/phpunit/phpunit/src/Framework/Constraint/IsIdentical.php new file mode 100644 index 00000000..f36d44e7 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/IsIdentical.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function get_class; +use function is_array; +use function is_object; +use function is_string; +use function sprintf; +use PHPUnit\Framework\ExpectationFailedException; +use SebastianBergmann\Comparator\ComparisonFailure; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class IsIdentical extends Constraint +{ + /** + * @var mixed + */ + private $value; + + public function __construct($value) + { + $this->value = $value; + } + + /** + * Evaluates the constraint for parameter $other. + * + * If $returnResult is set to false (the default), an exception is thrown + * in case of a failure. null is returned otherwise. + * + * If $returnResult is true, the result of the evaluation is returned as + * a boolean value instead: true in case of success, false in case of a + * failure. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public function evaluate($other, string $description = '', bool $returnResult = false): ?bool + { + $success = $this->value === $other; + + if ($returnResult) { + return $success; + } + + if (!$success) { + $f = null; + + // if both values are strings, make sure a diff is generated + if (is_string($this->value) && is_string($other)) { + $f = new ComparisonFailure( + $this->value, + $other, + sprintf("'%s'", $this->value), + sprintf("'%s'", $other), + ); + } + + // if both values are array, make sure a diff is generated + if (is_array($this->value) && is_array($other)) { + $f = new ComparisonFailure( + $this->value, + $other, + $this->exporter()->export($this->value), + $this->exporter()->export($other), + ); + } + + $this->fail($other, $description, $f); + } + + return null; + } + + /** + * Returns a string representation of the constraint. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function toString(): string + { + if (is_object($this->value)) { + return 'is identical to an object of class "' . + get_class($this->value) . '"'; + } + + return 'is identical to ' . $this->exporter()->export($this->value); + } + + /** + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + protected function failureDescription($other): string + { + if (is_object($this->value) && is_object($other)) { + return 'two variables reference the same object'; + } + + if (is_string($this->value) && is_string($other)) { + return 'two strings are identical'; + } + + if (is_array($this->value) && is_array($other)) { + return 'two arrays are identical'; + } + + return parent::failureDescription($other); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/JsonMatches.php b/vendor/phpunit/phpunit/src/Framework/Constraint/JsonMatches.php new file mode 100644 index 00000000..54de688c --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/JsonMatches.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function json_decode; +use function sprintf; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Util\Json; +use SebastianBergmann\Comparator\ComparisonFailure; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class JsonMatches extends Constraint +{ + /** + * @var string + */ + private $value; + + public function __construct(string $value) + { + $this->value = $value; + } + + /** + * Returns a string representation of the object. + */ + public function toString(): string + { + return sprintf( + 'matches JSON string "%s"', + $this->value, + ); + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * This method can be overridden to implement the evaluation algorithm. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other): bool + { + [$error, $recodedOther] = Json::canonicalize($other); + + if ($error) { + return false; + } + + [$error, $recodedValue] = Json::canonicalize($this->value); + + if ($error) { + return false; + } + + return $recodedOther == $recodedValue; + } + + /** + * Throws an exception for the given compared value and test description. + * + * @param mixed $other evaluated value or object + * @param string $description Additional information about the test + * + * @throws \PHPUnit\Framework\Exception + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * + * @psalm-return never-return + */ + protected function fail($other, $description, ComparisonFailure $comparisonFailure = null): void + { + if ($comparisonFailure === null) { + [$error, $recodedOther] = Json::canonicalize($other); + + if ($error) { + parent::fail($other, $description); + } + + [$error, $recodedValue] = Json::canonicalize($this->value); + + if ($error) { + parent::fail($other, $description); + } + + $comparisonFailure = new ComparisonFailure( + json_decode($this->value), + json_decode($other), + Json::prettify($recodedValue), + Json::prettify($recodedOther), + false, + 'Failed asserting that two json values are equal.', + ); + } + + parent::fail($other, $description, $comparisonFailure); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/JsonMatchesErrorMessageProvider.php b/vendor/phpunit/phpunit/src/Framework/Constraint/JsonMatchesErrorMessageProvider.php new file mode 100644 index 00000000..4bf19e27 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/JsonMatchesErrorMessageProvider.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use const JSON_ERROR_CTRL_CHAR; +use const JSON_ERROR_DEPTH; +use const JSON_ERROR_NONE; +use const JSON_ERROR_STATE_MISMATCH; +use const JSON_ERROR_SYNTAX; +use const JSON_ERROR_UTF8; +use function strtolower; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class JsonMatchesErrorMessageProvider +{ + /** + * Translates JSON error to a human readable string. + */ + public static function determineJsonError(string $error, string $prefix = ''): ?string + { + switch ($error) { + case JSON_ERROR_NONE: + return null; + + case JSON_ERROR_DEPTH: + return $prefix . 'Maximum stack depth exceeded'; + + case JSON_ERROR_STATE_MISMATCH: + return $prefix . 'Underflow or the modes mismatch'; + + case JSON_ERROR_CTRL_CHAR: + return $prefix . 'Unexpected control character found'; + + case JSON_ERROR_SYNTAX: + return $prefix . 'Syntax error, malformed JSON'; + + case JSON_ERROR_UTF8: + return $prefix . 'Malformed UTF-8 characters, possibly incorrectly encoded'; + + default: + return $prefix . 'Unknown error'; + } + } + + /** + * Translates a given type to a human readable message prefix. + */ + public static function translateTypeToPrefix(string $type): string + { + switch (strtolower($type)) { + case 'expected': + $prefix = 'Expected value JSON decode error - '; + + break; + + case 'actual': + $prefix = 'Actual value JSON decode error - '; + + break; + + default: + $prefix = ''; + + break; + } + + return $prefix; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Math/IsFinite.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Math/IsFinite.php new file mode 100644 index 00000000..9a2f3286 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Math/IsFinite.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function is_finite; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class IsFinite extends Constraint +{ + /** + * Returns a string representation of the constraint. + */ + public function toString(): string + { + return 'is finite'; + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other): bool + { + return is_finite($other); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Math/IsInfinite.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Math/IsInfinite.php new file mode 100644 index 00000000..c718514c --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Math/IsInfinite.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function is_infinite; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class IsInfinite extends Constraint +{ + /** + * Returns a string representation of the constraint. + */ + public function toString(): string + { + return 'is infinite'; + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other): bool + { + return is_infinite($other); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Math/IsNan.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Math/IsNan.php new file mode 100644 index 00000000..0062c5b5 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Math/IsNan.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function is_nan; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class IsNan extends Constraint +{ + /** + * Returns a string representation of the constraint. + */ + public function toString(): string + { + return 'is nan'; + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other): bool + { + return is_nan($other); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Object/ClassHasAttribute.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Object/ClassHasAttribute.php new file mode 100644 index 00000000..40e1d614 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Object/ClassHasAttribute.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function get_class; +use function is_object; +use function sprintf; +use PHPUnit\Framework\Exception; +use ReflectionClass; +use ReflectionException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4601 + */ +class ClassHasAttribute extends Constraint +{ + /** + * @var string + */ + private $attributeName; + + public function __construct(string $attributeName) + { + $this->attributeName = $attributeName; + } + + /** + * Returns a string representation of the constraint. + */ + public function toString(): string + { + return sprintf( + 'has attribute "%s"', + $this->attributeName, + ); + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other): bool + { + try { + return (new ReflectionClass($other))->hasProperty($this->attributeName); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + } + + /** + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object + */ + protected function failureDescription($other): string + { + return sprintf( + '%sclass "%s" %s', + is_object($other) ? 'object of ' : '', + is_object($other) ? get_class($other) : $other, + $this->toString(), + ); + } + + protected function attributeName(): string + { + return $this->attributeName; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Object/ClassHasStaticAttribute.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Object/ClassHasStaticAttribute.php new file mode 100644 index 00000000..bd5eefe4 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Object/ClassHasStaticAttribute.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function sprintf; +use PHPUnit\Framework\Exception; +use ReflectionClass; +use ReflectionException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4601 + */ +final class ClassHasStaticAttribute extends ClassHasAttribute +{ + /** + * Returns a string representation of the constraint. + */ + public function toString(): string + { + return sprintf( + 'has static attribute "%s"', + $this->attributeName(), + ); + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other): bool + { + try { + $class = new ReflectionClass($other); + + if ($class->hasProperty($this->attributeName())) { + return $class->getProperty($this->attributeName())->isStatic(); + } + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + return false; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Object/ObjectEquals.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Object/ObjectEquals.php new file mode 100644 index 00000000..b837b4cd --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Object/ObjectEquals.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function get_class; +use function is_object; +use PHPUnit\Framework\ActualValueIsNotAnObjectException; +use PHPUnit\Framework\ComparisonMethodDoesNotAcceptParameterTypeException; +use PHPUnit\Framework\ComparisonMethodDoesNotDeclareBoolReturnTypeException; +use PHPUnit\Framework\ComparisonMethodDoesNotDeclareExactlyOneParameterException; +use PHPUnit\Framework\ComparisonMethodDoesNotDeclareParameterTypeException; +use PHPUnit\Framework\ComparisonMethodDoesNotExistException; +use ReflectionNamedType; +use ReflectionObject; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class ObjectEquals extends Constraint +{ + /** + * @var object + */ + private $expected; + + /** + * @var string + */ + private $method; + + public function __construct(object $object, string $method = 'equals') + { + $this->expected = $object; + $this->method = $method; + } + + public function toString(): string + { + return 'two objects are equal'; + } + + /** + * @throws ActualValueIsNotAnObjectException + * @throws ComparisonMethodDoesNotAcceptParameterTypeException + * @throws ComparisonMethodDoesNotDeclareBoolReturnTypeException + * @throws ComparisonMethodDoesNotDeclareExactlyOneParameterException + * @throws ComparisonMethodDoesNotDeclareParameterTypeException + * @throws ComparisonMethodDoesNotExistException + */ + protected function matches($other): bool + { + if (!is_object($other)) { + throw new ActualValueIsNotAnObjectException; + } + + $object = new ReflectionObject($other); + + if (!$object->hasMethod($this->method)) { + throw new ComparisonMethodDoesNotExistException( + get_class($other), + $this->method, + ); + } + + /** @noinspection PhpUnhandledExceptionInspection */ + $method = $object->getMethod($this->method); + + if (!$method->hasReturnType()) { + throw new ComparisonMethodDoesNotDeclareBoolReturnTypeException( + get_class($other), + $this->method, + ); + } + + $returnType = $method->getReturnType(); + + if (!$returnType instanceof ReflectionNamedType) { + throw new ComparisonMethodDoesNotDeclareBoolReturnTypeException( + get_class($other), + $this->method, + ); + } + + if ($returnType->allowsNull()) { + throw new ComparisonMethodDoesNotDeclareBoolReturnTypeException( + get_class($other), + $this->method, + ); + } + + if ($returnType->getName() !== 'bool') { + throw new ComparisonMethodDoesNotDeclareBoolReturnTypeException( + get_class($other), + $this->method, + ); + } + + if ($method->getNumberOfParameters() !== 1 || $method->getNumberOfRequiredParameters() !== 1) { + throw new ComparisonMethodDoesNotDeclareExactlyOneParameterException( + get_class($other), + $this->method, + ); + } + + $parameter = $method->getParameters()[0]; + + if (!$parameter->hasType()) { + throw new ComparisonMethodDoesNotDeclareParameterTypeException( + get_class($other), + $this->method, + ); + } + + $type = $parameter->getType(); + + if (!$type instanceof ReflectionNamedType) { + throw new ComparisonMethodDoesNotDeclareParameterTypeException( + get_class($other), + $this->method, + ); + } + + $typeName = $type->getName(); + + if ($typeName === 'self') { + $typeName = get_class($other); + } + + if (!$this->expected instanceof $typeName) { + throw new ComparisonMethodDoesNotAcceptParameterTypeException( + get_class($other), + $this->method, + get_class($this->expected), + ); + } + + return $other->{$this->method}($this->expected); + } + + protected function failureDescription($other): string + { + return $this->toString(); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Object/ObjectHasAttribute.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Object/ObjectHasAttribute.php new file mode 100644 index 00000000..602cb05d --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Object/ObjectHasAttribute.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use ReflectionObject; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4601 + */ +final class ObjectHasAttribute extends ClassHasAttribute +{ + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other): bool + { + return (new ReflectionObject($other))->hasProperty($this->attributeName()); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Object/ObjectHasProperty.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Object/ObjectHasProperty.php new file mode 100644 index 00000000..c41d21a1 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Object/ObjectHasProperty.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function get_class; +use function gettype; +use function is_object; +use function sprintf; +use ReflectionObject; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class ObjectHasProperty extends Constraint +{ + /** + * @var string + */ + private $propertyName; + + public function __construct(string $propertyName) + { + $this->propertyName = $propertyName; + } + + /** + * Returns a string representation of the constraint. + */ + public function toString(): string + { + return sprintf( + 'has property "%s"', + $this->propertyName, + ); + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other): bool + { + if (!is_object($other)) { + return false; + } + + return (new ReflectionObject($other))->hasProperty($this->propertyName); + } + + /** + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object + */ + protected function failureDescription($other): string + { + if (is_object($other)) { + return sprintf( + 'object of class "%s" %s', + get_class($other), + $this->toString(), + ); + } + + return sprintf( + '"%s" (%s) %s', + $other, + gettype($other), + $this->toString(), + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/BinaryOperator.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/BinaryOperator.php new file mode 100644 index 00000000..11c86b52 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/BinaryOperator.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function array_map; +use function array_values; +use function count; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +abstract class BinaryOperator extends Operator +{ + /** + * @var Constraint[] + */ + private $constraints = []; + + public static function fromConstraints(Constraint ...$constraints): self + { + $constraint = new static; + + $constraint->constraints = $constraints; + + return $constraint; + } + + /** + * @param mixed[] $constraints + */ + public function setConstraints(array $constraints): void + { + $this->constraints = array_map(function ($constraint): Constraint + { + return $this->checkConstraint($constraint); + }, array_values($constraints)); + } + + /** + * Returns the number of operands (constraints). + */ + final public function arity(): int + { + return count($this->constraints); + } + + /** + * Returns a string representation of the constraint. + */ + public function toString(): string + { + $reduced = $this->reduce(); + + if ($reduced !== $this) { + return $reduced->toString(); + } + + $text = ''; + + foreach ($this->constraints as $key => $constraint) { + $constraint = $constraint->reduce(); + + $text .= $this->constraintToString($constraint, $key); + } + + return $text; + } + + /** + * Counts the number of constraint elements. + */ + public function count(): int + { + $count = 0; + + foreach ($this->constraints as $constraint) { + $count += count($constraint); + } + + return $count; + } + + /** + * Returns the nested constraints. + */ + final protected function constraints(): array + { + return $this->constraints; + } + + /** + * Returns true if the $constraint needs to be wrapped with braces. + */ + final protected function constraintNeedsParentheses(Constraint $constraint): bool + { + return $this->arity() > 1 && parent::constraintNeedsParentheses($constraint); + } + + /** + * Reduces the sub-expression starting at $this by skipping degenerate + * sub-expression and returns first descendant constraint that starts + * a non-reducible sub-expression. + * + * See Constraint::reduce() for more. + */ + protected function reduce(): Constraint + { + if ($this->arity() === 1 && $this->constraints[0] instanceof Operator) { + return $this->constraints[0]->reduce(); + } + + return parent::reduce(); + } + + /** + * Returns string representation of given operand in context of this operator. + * + * @param Constraint $constraint operand constraint + * @param int $position position of $constraint in this expression + */ + private function constraintToString(Constraint $constraint, int $position): string + { + $prefix = ''; + + if ($position > 0) { + $prefix = (' ' . $this->operator() . ' '); + } + + if ($this->constraintNeedsParentheses($constraint)) { + return $prefix . '( ' . $constraint->toString() . ' )'; + } + + $string = $constraint->toStringInContext($this, $position); + + if ($string === '') { + $string = $constraint->toString(); + } + + return $prefix . $string; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/LogicalAnd.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/LogicalAnd.php new file mode 100644 index 00000000..a1af4dd3 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/LogicalAnd.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class LogicalAnd extends BinaryOperator +{ + /** + * Returns the name of this operator. + */ + public function operator(): string + { + return 'and'; + } + + /** + * Returns this operator's precedence. + * + * @see https://www.php.net/manual/en/language.operators.precedence.php + */ + public function precedence(): int + { + return 22; + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other): bool + { + foreach ($this->constraints() as $constraint) { + if (!$constraint->evaluate($other, '', true)) { + return false; + } + } + + return [] !== $this->constraints(); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/LogicalNot.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/LogicalNot.php new file mode 100644 index 00000000..586abc50 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/LogicalNot.php @@ -0,0 +1,143 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function array_map; +use function count; +use function preg_match; +use function preg_quote; +use function preg_replace; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class LogicalNot extends UnaryOperator +{ + public static function negate(string $string): string + { + $positives = [ + 'contains ', + 'exists', + 'has ', + 'is ', + 'are ', + 'matches ', + 'starts with ', + 'ends with ', + 'reference ', + 'not not ', + ]; + + $negatives = [ + 'does not contain ', + 'does not exist', + 'does not have ', + 'is not ', + 'are not ', + 'does not match ', + 'starts not with ', + 'ends not with ', + 'don\'t reference ', + 'not ', + ]; + + preg_match('/(\'[\w\W]*\')([\w\W]*)("[\w\W]*")/i', $string, $matches); + + if (count($matches) === 0) { + preg_match('/(\'[\w\W]*\')([\w\W]*)(\'[\w\W]*\')/i', $string, $matches); + } + + $positives = array_map( + static function (string $s) + { + return '/\\b' . preg_quote($s, '/') . '/'; + }, + $positives, + ); + + if (count($matches) > 0) { + $nonInput = $matches[2]; + + $negatedString = preg_replace( + '/' . preg_quote($nonInput, '/') . '/', + preg_replace( + $positives, + $negatives, + $nonInput, + ), + $string, + ); + } else { + $negatedString = preg_replace( + $positives, + $negatives, + $string, + ); + } + + return $negatedString; + } + + /** + * Returns the name of this operator. + */ + public function operator(): string + { + return 'not'; + } + + /** + * Returns this operator's precedence. + * + * @see https://www.php.net/manual/en/language.operators.precedence.php + */ + public function precedence(): int + { + return 5; + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other): bool + { + return !$this->constraint()->evaluate($other, '', true); + } + + /** + * Applies additional transformation to strings returned by toString() or + * failureDescription(). + */ + protected function transformString(string $string): string + { + return self::negate($string); + } + + /** + * Reduces the sub-expression starting at $this by skipping degenerate + * sub-expression and returns first descendant constraint that starts + * a non-reducible sub-expression. + * + * See Constraint::reduce() for more. + */ + protected function reduce(): Constraint + { + $constraint = $this->constraint(); + + if ($constraint instanceof self) { + return $constraint->constraint()->reduce(); + } + + return parent::reduce(); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/LogicalOr.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/LogicalOr.php new file mode 100644 index 00000000..2932de67 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/LogicalOr.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class LogicalOr extends BinaryOperator +{ + /** + * Returns the name of this operator. + */ + public function operator(): string + { + return 'or'; + } + + /** + * Returns this operator's precedence. + * + * @see https://www.php.net/manual/en/language.operators.precedence.php + */ + public function precedence(): int + { + return 24; + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + public function matches($other): bool + { + foreach ($this->constraints() as $constraint) { + if ($constraint->evaluate($other, '', true)) { + return true; + } + } + + return false; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/LogicalXor.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/LogicalXor.php new file mode 100644 index 00000000..ee1b1c29 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/LogicalXor.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function array_reduce; +use function array_shift; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class LogicalXor extends BinaryOperator +{ + /** + * Returns the name of this operator. + */ + public function operator(): string + { + return 'xor'; + } + + /** + * Returns this operator's precedence. + * + * @see https://www.php.net/manual/en/language.operators.precedence.php. + */ + public function precedence(): int + { + return 23; + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + public function matches($other): bool + { + $constraints = $this->constraints(); + + $initial = array_shift($constraints); + + if ($initial === null) { + return false; + } + + return array_reduce( + $constraints, + static function (bool $matches, Constraint $constraint) use ($other): bool + { + return $matches xor $constraint->evaluate($other, '', true); + }, + $initial->evaluate($other, '', true), + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/Operator.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/Operator.php new file mode 100644 index 00000000..3f51a0f4 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/Operator.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +abstract class Operator extends Constraint +{ + /** + * Returns the name of this operator. + */ + abstract public function operator(): string; + + /** + * Returns this operator's precedence. + * + * @see https://www.php.net/manual/en/language.operators.precedence.php + */ + abstract public function precedence(): int; + + /** + * Returns the number of operands. + */ + abstract public function arity(): int; + + /** + * Validates $constraint argument. + */ + protected function checkConstraint($constraint): Constraint + { + if (!$constraint instanceof Constraint) { + return new IsEqual($constraint); + } + + return $constraint; + } + + /** + * Returns true if the $constraint needs to be wrapped with braces. + */ + protected function constraintNeedsParentheses(Constraint $constraint): bool + { + return $constraint instanceof self && + $constraint->arity() > 1 && + $this->precedence() <= $constraint->precedence(); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/UnaryOperator.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/UnaryOperator.php new file mode 100644 index 00000000..0a7a5fa2 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/UnaryOperator.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function count; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +abstract class UnaryOperator extends Operator +{ + /** + * @var Constraint + */ + private $constraint; + + /** + * @param Constraint|mixed $constraint + */ + public function __construct($constraint) + { + $this->constraint = $this->checkConstraint($constraint); + } + + /** + * Returns the number of operands (constraints). + */ + public function arity(): int + { + return 1; + } + + /** + * Returns a string representation of the constraint. + */ + public function toString(): string + { + $reduced = $this->reduce(); + + if ($reduced !== $this) { + return $reduced->toString(); + } + + $constraint = $this->constraint->reduce(); + + if ($this->constraintNeedsParentheses($constraint)) { + return $this->operator() . '( ' . $constraint->toString() . ' )'; + } + + $string = $constraint->toStringInContext($this, 0); + + if ($string === '') { + return $this->transformString($constraint->toString()); + } + + return $string; + } + + /** + * Counts the number of constraint elements. + */ + public function count(): int + { + return count($this->constraint); + } + + /** + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + protected function failureDescription($other): string + { + $reduced = $this->reduce(); + + if ($reduced !== $this) { + return $reduced->failureDescription($other); + } + + $constraint = $this->constraint->reduce(); + + if ($this->constraintNeedsParentheses($constraint)) { + return $this->operator() . '( ' . $constraint->failureDescription($other) . ' )'; + } + + $string = $constraint->failureDescriptionInContext($this, 0, $other); + + if ($string === '') { + return $this->transformString($constraint->failureDescription($other)); + } + + return $string; + } + + /** + * Transforms string returned by the memeber constraint's toString() or + * failureDescription() such that it reflects constraint's participation in + * this expression. + * + * The method may be overwritten in a subclass to apply default + * transformation in case the operand constraint does not provide its own + * custom strings via toStringInContext() or failureDescriptionInContext(). + * + * @param string $string the string to be transformed + */ + protected function transformString(string $string): string + { + return $string; + } + + /** + * Provides access to $this->constraint for subclasses. + */ + final protected function constraint(): Constraint + { + return $this->constraint; + } + + /** + * Returns true if the $constraint needs to be wrapped with parentheses. + */ + protected function constraintNeedsParentheses(Constraint $constraint): bool + { + $constraint = $constraint->reduce(); + + return $constraint instanceof self || parent::constraintNeedsParentheses($constraint); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/String/IsJson.php b/vendor/phpunit/phpunit/src/Framework/Constraint/String/IsJson.php new file mode 100644 index 00000000..f90704f0 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/String/IsJson.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function json_decode; +use function json_last_error; +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class IsJson extends Constraint +{ + /** + * Returns a string representation of the constraint. + */ + public function toString(): string + { + return 'is valid JSON'; + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other): bool + { + if ($other === '') { + return false; + } + + json_decode($other); + + if (json_last_error()) { + return false; + } + + return true; + } + + /** + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + protected function failureDescription($other): string + { + if ($other === '') { + return 'an empty string is valid JSON'; + } + + json_decode($other); + $error = (string) JsonMatchesErrorMessageProvider::determineJsonError( + (string) json_last_error(), + ); + + return sprintf( + '%s is valid JSON (%s)', + $this->exporter()->shortenedExport($other), + $error, + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/String/RegularExpression.php b/vendor/phpunit/phpunit/src/Framework/Constraint/String/RegularExpression.php new file mode 100644 index 00000000..9ccfb9bd --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/String/RegularExpression.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function preg_match; +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +class RegularExpression extends Constraint +{ + /** + * @var string + */ + private $pattern; + + public function __construct(string $pattern) + { + $this->pattern = $pattern; + } + + /** + * Returns a string representation of the constraint. + */ + public function toString(): string + { + return sprintf( + 'matches PCRE pattern "%s"', + $this->pattern, + ); + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other): bool + { + return preg_match($this->pattern, $other) > 0; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/String/StringContains.php b/vendor/phpunit/phpunit/src/Framework/Constraint/String/StringContains.php new file mode 100644 index 00000000..5aa2c8e5 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/String/StringContains.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function mb_stripos; +use function mb_strtolower; +use function sprintf; +use function strpos; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class StringContains extends Constraint +{ + /** + * @var string + */ + private $string; + + /** + * @var bool + */ + private $ignoreCase; + + public function __construct(string $string, bool $ignoreCase = false) + { + $this->string = $string; + $this->ignoreCase = $ignoreCase; + } + + /** + * Returns a string representation of the constraint. + */ + public function toString(): string + { + if ($this->ignoreCase) { + $string = mb_strtolower($this->string, 'UTF-8'); + } else { + $string = $this->string; + } + + return sprintf( + 'contains "%s"', + $string, + ); + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other): bool + { + if ('' === $this->string) { + return true; + } + + if ($this->ignoreCase) { + /* + * We must use the multi byte safe version so we can accurately compare non latin upper characters with + * their lowercase equivalents. + */ + return mb_stripos($other, $this->string, 0, 'UTF-8') !== false; + } + + /* + * Use the non multi byte safe functions to see if the string is contained in $other. + * + * This function is very fast and we don't care about the character position in the string. + * + * Additionally, we want this method to be binary safe so we can check if some binary data is in other binary + * data. + */ + return strpos($other, $this->string) !== false; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/String/StringEndsWith.php b/vendor/phpunit/phpunit/src/Framework/Constraint/String/StringEndsWith.php new file mode 100644 index 00000000..bb4ce23b --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/String/StringEndsWith.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function strlen; +use function substr; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class StringEndsWith extends Constraint +{ + /** + * @var string + */ + private $suffix; + + public function __construct(string $suffix) + { + $this->suffix = $suffix; + } + + /** + * Returns a string representation of the constraint. + */ + public function toString(): string + { + return 'ends with "' . $this->suffix . '"'; + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other): bool + { + return substr($other, 0 - strlen($this->suffix)) === $this->suffix; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/String/StringMatchesFormatDescription.php b/vendor/phpunit/phpunit/src/Framework/Constraint/String/StringMatchesFormatDescription.php new file mode 100644 index 00000000..9c01ecb9 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/String/StringMatchesFormatDescription.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use const DIRECTORY_SEPARATOR; +use function explode; +use function implode; +use function preg_match; +use function preg_quote; +use function preg_replace; +use function strtr; +use SebastianBergmann\Diff\Differ; +use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class StringMatchesFormatDescription extends RegularExpression +{ + /** + * @var string + */ + private $string; + + public function __construct(string $string) + { + parent::__construct( + $this->createPatternFromFormat( + $this->convertNewlines($string), + ), + ); + + $this->string = $string; + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other): bool + { + return parent::matches( + $this->convertNewlines($other), + ); + } + + protected function failureDescription($other): string + { + return 'string matches format description'; + } + + protected function additionalFailureDescription($other): string + { + $from = explode("\n", $this->string); + $to = explode("\n", $this->convertNewlines($other)); + + foreach ($from as $index => $line) { + if (isset($to[$index]) && $line !== $to[$index]) { + $line = $this->createPatternFromFormat($line); + + if (preg_match($line, $to[$index]) > 0) { + $from[$index] = $to[$index]; + } + } + } + + $this->string = implode("\n", $from); + $other = implode("\n", $to); + + return (new Differ(new UnifiedDiffOutputBuilder("--- Expected\n+++ Actual\n")))->diff($this->string, $other); + } + + private function createPatternFromFormat(string $string): string + { + $string = strtr( + preg_quote($string, '/'), + [ + '%%' => '%', + '%e' => '\\' . DIRECTORY_SEPARATOR, + '%s' => '[^\r\n]+', + '%S' => '[^\r\n]*', + '%a' => '.+', + '%A' => '.*', + '%w' => '\s*', + '%i' => '[+-]?\d+', + '%d' => '\d+', + '%x' => '[0-9a-fA-F]+', + '%f' => '[+-]?\.?\d+\.?\d*(?:[Ee][+-]?\d+)?', + '%c' => '.', + ], + ); + + return '/^' . $string . '$/s'; + } + + private function convertNewlines(string $text): string + { + return preg_replace('/\r\n/', "\n", $text); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/String/StringStartsWith.php b/vendor/phpunit/phpunit/src/Framework/Constraint/String/StringStartsWith.php new file mode 100644 index 00000000..8683e272 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/String/StringStartsWith.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function strpos; +use PHPUnit\Framework\InvalidArgumentException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class StringStartsWith extends Constraint +{ + /** + * @var string + */ + private $prefix; + + public function __construct(string $prefix) + { + if ($prefix === '') { + throw InvalidArgumentException::create(1, 'non-empty string'); + } + + $this->prefix = $prefix; + } + + /** + * Returns a string representation of the constraint. + */ + public function toString(): string + { + return 'starts with "' . $this->prefix . '"'; + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other): bool + { + return strpos((string) $other, $this->prefix) === 0; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/ArrayHasKey.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/ArrayHasKey.php new file mode 100644 index 00000000..44cada3a --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/ArrayHasKey.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function array_key_exists; +use function is_array; +use ArrayAccess; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class ArrayHasKey extends Constraint +{ + /** + * @var int|string + */ + private $key; + + /** + * @param int|string $key + */ + public function __construct($key) + { + $this->key = $key; + } + + /** + * Returns a string representation of the constraint. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function toString(): string + { + return 'has the key ' . $this->exporter()->export($this->key); + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other): bool + { + if (is_array($other)) { + return array_key_exists($this->key, $other); + } + + if ($other instanceof ArrayAccess) { + return $other->offsetExists($this->key); + } + + return false; + } + + /** + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + protected function failureDescription($other): string + { + return 'an array ' . $this->toString(); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/TraversableContains.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/TraversableContains.php new file mode 100644 index 00000000..d0f61f46 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/TraversableContains.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function is_array; +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +abstract class TraversableContains extends Constraint +{ + /** + * @var mixed + */ + private $value; + + public function __construct($value) + { + $this->value = $value; + } + + /** + * Returns a string representation of the constraint. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function toString(): string + { + return 'contains ' . $this->exporter()->export($this->value); + } + + /** + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + protected function failureDescription($other): string + { + return sprintf( + '%s %s', + is_array($other) ? 'an array' : 'a traversable', + $this->toString(), + ); + } + + protected function value() + { + return $this->value; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/TraversableContainsEqual.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/TraversableContainsEqual.php new file mode 100644 index 00000000..c315e709 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/TraversableContainsEqual.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use SplObjectStorage; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class TraversableContainsEqual extends TraversableContains +{ + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other): bool + { + if ($other instanceof SplObjectStorage) { + return $other->contains($this->value()); + } + + foreach ($other as $element) { + /* @noinspection TypeUnsafeComparisonInspection */ + if ($this->value() == $element) { + return true; + } + } + + return false; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/TraversableContainsIdentical.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/TraversableContainsIdentical.php new file mode 100644 index 00000000..a3437dbc --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/TraversableContainsIdentical.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use SplObjectStorage; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class TraversableContainsIdentical extends TraversableContains +{ + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other): bool + { + if ($other instanceof SplObjectStorage) { + return $other->contains($this->value()); + } + + foreach ($other as $element) { + if ($this->value() === $element) { + return true; + } + } + + return false; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/TraversableContainsOnly.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/TraversableContainsOnly.php new file mode 100644 index 00000000..e5c68890 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/TraversableContainsOnly.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use PHPUnit\Framework\ExpectationFailedException; +use Traversable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class TraversableContainsOnly extends Constraint +{ + /** + * @var Constraint + */ + private $constraint; + + /** + * @var string + */ + private $type; + + /** + * @throws \PHPUnit\Framework\Exception + */ + public function __construct(string $type, bool $isNativeType = true) + { + if ($isNativeType) { + $this->constraint = new IsType($type); + } else { + $this->constraint = new IsInstanceOf( + $type, + ); + } + + $this->type = $type; + } + + /** + * Evaluates the constraint for parameter $other. + * + * If $returnResult is set to false (the default), an exception is thrown + * in case of a failure. null is returned otherwise. + * + * If $returnResult is true, the result of the evaluation is returned as + * a boolean value instead: true in case of success, false in case of a + * failure. + * + * @param mixed|Traversable $other + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public function evaluate($other, string $description = '', bool $returnResult = false): ?bool + { + $success = true; + + foreach ($other as $item) { + if (!$this->constraint->evaluate($item, '', true)) { + $success = false; + + break; + } + } + + if ($returnResult) { + return $success; + } + + if (!$success) { + $this->fail($other, $description); + } + + return null; + } + + /** + * Returns a string representation of the constraint. + */ + public function toString(): string + { + return 'contains only values of type "' . $this->type . '"'; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Type/IsInstanceOf.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Type/IsInstanceOf.php new file mode 100644 index 00000000..ef26813f --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Type/IsInstanceOf.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function sprintf; +use ReflectionClass; +use ReflectionException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class IsInstanceOf extends Constraint +{ + /** + * @var string + */ + private $className; + + public function __construct(string $className) + { + $this->className = $className; + } + + /** + * Returns a string representation of the constraint. + */ + public function toString(): string + { + return sprintf( + 'is instance of %s "%s"', + $this->getType(), + $this->className, + ); + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other): bool + { + return $other instanceof $this->className; + } + + /** + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + protected function failureDescription($other): string + { + return sprintf( + '%s is an instance of %s "%s"', + $this->exporter()->shortenedExport($other), + $this->getType(), + $this->className, + ); + } + + private function getType(): string + { + try { + $reflection = new ReflectionClass($this->className); + + if ($reflection->isInterface()) { + return 'interface'; + } + } catch (ReflectionException $e) { + } + + return 'class'; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Type/IsNull.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Type/IsNull.php new file mode 100644 index 00000000..b9fcdd7a --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Type/IsNull.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class IsNull extends Constraint +{ + /** + * Returns a string representation of the constraint. + */ + public function toString(): string + { + return 'is null'; + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other): bool + { + return $other === null; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Constraint/Type/IsType.php b/vendor/phpunit/phpunit/src/Framework/Constraint/Type/IsType.php new file mode 100644 index 00000000..6edcc2b9 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Constraint/Type/IsType.php @@ -0,0 +1,210 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function gettype; +use function is_array; +use function is_bool; +use function is_callable; +use function is_float; +use function is_int; +use function is_iterable; +use function is_numeric; +use function is_object; +use function is_scalar; +use function is_string; +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class IsType extends Constraint +{ + /** + * @var string + */ + public const TYPE_ARRAY = 'array'; + + /** + * @var string + */ + public const TYPE_BOOL = 'bool'; + + /** + * @var string + */ + public const TYPE_FLOAT = 'float'; + + /** + * @var string + */ + public const TYPE_INT = 'int'; + + /** + * @var string + */ + public const TYPE_NULL = 'null'; + + /** + * @var string + */ + public const TYPE_NUMERIC = 'numeric'; + + /** + * @var string + */ + public const TYPE_OBJECT = 'object'; + + /** + * @var string + */ + public const TYPE_RESOURCE = 'resource'; + + /** + * @var string + */ + public const TYPE_CLOSED_RESOURCE = 'resource (closed)'; + + /** + * @var string + */ + public const TYPE_STRING = 'string'; + + /** + * @var string + */ + public const TYPE_SCALAR = 'scalar'; + + /** + * @var string + */ + public const TYPE_CALLABLE = 'callable'; + + /** + * @var string + */ + public const TYPE_ITERABLE = 'iterable'; + + /** + * @var array + */ + private const KNOWN_TYPES = [ + 'array' => true, + 'boolean' => true, + 'bool' => true, + 'double' => true, + 'float' => true, + 'integer' => true, + 'int' => true, + 'null' => true, + 'numeric' => true, + 'object' => true, + 'real' => true, + 'resource' => true, + 'resource (closed)' => true, + 'string' => true, + 'scalar' => true, + 'callable' => true, + 'iterable' => true, + ]; + + /** + * @var string + */ + private $type; + + /** + * @throws \PHPUnit\Framework\Exception + */ + public function __construct(string $type) + { + if (!isset(self::KNOWN_TYPES[$type])) { + throw new \PHPUnit\Framework\Exception( + sprintf( + 'Type specified for PHPUnit\Framework\Constraint\IsType <%s> ' . + 'is not a valid type.', + $type, + ), + ); + } + + $this->type = $type; + } + + /** + * Returns a string representation of the constraint. + */ + public function toString(): string + { + return sprintf( + 'is of type "%s"', + $this->type, + ); + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other): bool + { + switch ($this->type) { + case 'numeric': + return is_numeric($other); + + case 'integer': + case 'int': + return is_int($other); + + case 'double': + case 'float': + case 'real': + return is_float($other); + + case 'string': + return is_string($other); + + case 'boolean': + case 'bool': + return is_bool($other); + + case 'null': + return null === $other; + + case 'array': + return is_array($other); + + case 'object': + return is_object($other); + + case 'resource': + $type = gettype($other); + + return $type === 'resource' || $type === 'resource (closed)'; + + case 'resource (closed)': + return gettype($other) === 'resource (closed)'; + + case 'scalar': + return is_scalar($other); + + case 'callable': + return is_callable($other); + + case 'iterable': + return is_iterable($other); + + default: + return false; + } + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/DataProviderTestSuite.php b/vendor/phpunit/phpunit/src/Framework/DataProviderTestSuite.php new file mode 100644 index 00000000..18b54999 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/DataProviderTestSuite.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function explode; +use PHPUnit\Util\Test as TestUtil; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class DataProviderTestSuite extends TestSuite +{ + /** + * @var list + */ + private $dependencies = []; + + /** + * @param list $dependencies + */ + public function setDependencies(array $dependencies): void + { + $this->dependencies = $dependencies; + + foreach ($this->tests as $test) { + if (!$test instanceof TestCase) { + // @codeCoverageIgnoreStart + continue; + // @codeCoverageIgnoreStart + } + $test->setDependencies($dependencies); + } + } + + /** + * @return list + */ + public function provides(): array + { + if ($this->providedTests === null) { + $this->providedTests = [new ExecutionOrderDependency($this->getName())]; + } + + return $this->providedTests; + } + + /** + * @return list + */ + public function requires(): array + { + // A DataProviderTestSuite does not have to traverse its child tests + // as these are inherited and cannot reference dataProvider rows directly + return $this->dependencies; + } + + /** + * Returns the size of the each test created using the data provider(s). + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function getSize(): int + { + [$className, $methodName] = explode('::', $this->getName()); + + return TestUtil::getSize($className, $methodName); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Error/Deprecated.php b/vendor/phpunit/phpunit/src/Framework/Error/Deprecated.php new file mode 100644 index 00000000..db62195f --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Error/Deprecated.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Error; + +/** + * @internal + */ +final class Deprecated extends Error +{ +} diff --git a/vendor/phpunit/phpunit/src/Framework/Error/Error.php b/vendor/phpunit/phpunit/src/Framework/Error/Error.php new file mode 100644 index 00000000..2990b360 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Error/Error.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Error; + +use PHPUnit\Framework\Exception; + +/** + * @internal + */ +class Error extends Exception +{ + public function __construct(string $message, int $code, string $file, int $line, \Exception $previous = null) + { + parent::__construct($message, $code, $previous); + + $this->file = $file; + $this->line = $line; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Error/Notice.php b/vendor/phpunit/phpunit/src/Framework/Error/Notice.php new file mode 100644 index 00000000..54e5e31e --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Error/Notice.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Error; + +/** + * @internal + */ +final class Notice extends Error +{ +} diff --git a/vendor/phpunit/phpunit/src/Framework/Error/Warning.php b/vendor/phpunit/phpunit/src/Framework/Error/Warning.php new file mode 100644 index 00000000..0c0c0064 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Error/Warning.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Error; + +/** + * @internal + */ +final class Warning extends Error +{ +} diff --git a/vendor/phpunit/phpunit/src/Framework/ErrorTestCase.php b/vendor/phpunit/phpunit/src/Framework/ErrorTestCase.php new file mode 100644 index 00000000..245f0336 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/ErrorTestCase.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ErrorTestCase extends TestCase +{ + /** + * @var ?bool + */ + protected $backupGlobals = false; + + /** + * @var ?bool + */ + protected $backupStaticAttributes = false; + + /** + * @var ?bool + */ + protected $runTestInSeparateProcess = false; + + /** + * @var string + */ + private $message; + + public function __construct(string $message = '') + { + $this->message = $message; + + parent::__construct('Error'); + } + + public function getMessage(): string + { + return $this->message; + } + + /** + * Returns a string representation of the test case. + */ + public function toString(): string + { + return 'Error'; + } + + /** + * @throws Exception + * + * @psalm-return never-return + */ + protected function runTest(): void + { + throw new Error($this->message); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Exception/ActualValueIsNotAnObjectException.php b/vendor/phpunit/phpunit/src/Framework/Exception/ActualValueIsNotAnObjectException.php new file mode 100644 index 00000000..4364788c --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Exception/ActualValueIsNotAnObjectException.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use const PHP_EOL; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ActualValueIsNotAnObjectException extends Exception +{ + public function __construct() + { + parent::__construct( + 'Actual value is not an object', + 0, + null, + ); + } + + public function __toString(): string + { + return $this->getMessage() . PHP_EOL; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Exception/AssertionFailedError.php b/vendor/phpunit/phpunit/src/Framework/Exception/AssertionFailedError.php new file mode 100644 index 00000000..0ba25286 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Exception/AssertionFailedError.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +class AssertionFailedError extends Exception implements SelfDescribing +{ + /** + * Wrapper for getMessage() which is declared as final. + */ + public function toString(): string + { + return $this->getMessage(); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Exception/CodeCoverageException.php b/vendor/phpunit/phpunit/src/Framework/Exception/CodeCoverageException.php new file mode 100644 index 00000000..36b07231 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Exception/CodeCoverageException.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +class CodeCoverageException extends Exception +{ +} diff --git a/vendor/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotAcceptParameterTypeException.php b/vendor/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotAcceptParameterTypeException.php new file mode 100644 index 00000000..0c2c1afe --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotAcceptParameterTypeException.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use const PHP_EOL; +use function sprintf; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ComparisonMethodDoesNotAcceptParameterTypeException extends Exception +{ + public function __construct(string $className, string $methodName, string $type) + { + parent::__construct( + sprintf( + '%s is not an accepted argument type for comparison method %s::%s().', + $type, + $className, + $methodName, + ), + 0, + null, + ); + } + + public function __toString(): string + { + return $this->getMessage() . PHP_EOL; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotDeclareBoolReturnTypeException.php b/vendor/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotDeclareBoolReturnTypeException.php new file mode 100644 index 00000000..4eb9a2fd --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotDeclareBoolReturnTypeException.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use const PHP_EOL; +use function sprintf; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ComparisonMethodDoesNotDeclareBoolReturnTypeException extends Exception +{ + public function __construct(string $className, string $methodName) + { + parent::__construct( + sprintf( + 'Comparison method %s::%s() does not declare bool return type.', + $className, + $methodName, + ), + 0, + null, + ); + } + + public function __toString(): string + { + return $this->getMessage() . PHP_EOL; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotDeclareExactlyOneParameterException.php b/vendor/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotDeclareExactlyOneParameterException.php new file mode 100644 index 00000000..e8cd9787 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotDeclareExactlyOneParameterException.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use const PHP_EOL; +use function sprintf; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ComparisonMethodDoesNotDeclareExactlyOneParameterException extends Exception +{ + public function __construct(string $className, string $methodName) + { + parent::__construct( + sprintf( + 'Comparison method %s::%s() does not declare exactly one parameter.', + $className, + $methodName, + ), + 0, + null, + ); + } + + public function __toString(): string + { + return $this->getMessage() . PHP_EOL; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotDeclareParameterTypeException.php b/vendor/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotDeclareParameterTypeException.php new file mode 100644 index 00000000..68616ba1 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotDeclareParameterTypeException.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use const PHP_EOL; +use function sprintf; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ComparisonMethodDoesNotDeclareParameterTypeException extends Exception +{ + public function __construct(string $className, string $methodName) + { + parent::__construct( + sprintf( + 'Parameter of comparison method %s::%s() does not have a declared type.', + $className, + $methodName, + ), + 0, + null, + ); + } + + public function __toString(): string + { + return $this->getMessage() . PHP_EOL; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotExistException.php b/vendor/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotExistException.php new file mode 100644 index 00000000..0f1adcbc --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotExistException.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use const PHP_EOL; +use function sprintf; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ComparisonMethodDoesNotExistException extends Exception +{ + public function __construct(string $className, string $methodName) + { + parent::__construct( + sprintf( + 'Comparison method %s::%s() does not exist.', + $className, + $methodName, + ), + 0, + null, + ); + } + + public function __toString(): string + { + return $this->getMessage() . PHP_EOL; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Exception/CoveredCodeNotExecutedException.php b/vendor/phpunit/phpunit/src/Framework/Exception/CoveredCodeNotExecutedException.php new file mode 100644 index 00000000..78f89bc3 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Exception/CoveredCodeNotExecutedException.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class CoveredCodeNotExecutedException extends RiskyTestError +{ +} diff --git a/vendor/phpunit/phpunit/src/Framework/Exception/Error.php b/vendor/phpunit/phpunit/src/Framework/Exception/Error.php new file mode 100644 index 00000000..d43e4218 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Exception/Error.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Error extends Exception implements SelfDescribing +{ + /** + * Wrapper for getMessage() which is declared as final. + */ + public function toString(): string + { + return $this->getMessage(); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Exception/Exception.php b/vendor/phpunit/phpunit/src/Framework/Exception/Exception.php new file mode 100644 index 00000000..0b21e6de --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Exception/Exception.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function array_keys; +use function get_object_vars; +use PHPUnit\Util\Filter; +use RuntimeException; +use Throwable; + +/** + * Base class for all PHPUnit Framework exceptions. + * + * Ensures that exceptions thrown during a test run do not leave stray + * references behind. + * + * Every Exception contains a stack trace. Each stack frame contains the 'args' + * of the called function. The function arguments can contain references to + * instantiated objects. The references prevent the objects from being + * destructed (until test results are eventually printed), so memory cannot be + * freed up. + * + * With enabled process isolation, test results are serialized in the child + * process and unserialized in the parent process. The stack trace of Exceptions + * may contain objects that cannot be serialized or unserialized (e.g., PDO + * connections). Unserializing user-space objects from the child process into + * the parent would break the intended encapsulation of process isolation. + * + * @see http://fabien.potencier.org/article/9/php-serialization-stack-traces-and-exceptions + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +class Exception extends RuntimeException implements \PHPUnit\Exception +{ + /** + * @var array + */ + protected $serializableTrace; + + public function __construct($message = '', $code = 0, Throwable $previous = null) + { + parent::__construct($message, $code, $previous); + + $this->serializableTrace = $this->getTrace(); + + foreach (array_keys($this->serializableTrace) as $key) { + unset($this->serializableTrace[$key]['args']); + } + } + + public function __toString(): string + { + $string = TestFailure::exceptionToString($this); + + if ($trace = Filter::getFilteredStacktrace($this)) { + $string .= "\n" . $trace; + } + + return $string; + } + + public function __sleep(): array + { + return array_keys(get_object_vars($this)); + } + + /** + * Returns the serializable trace (without 'args'). + */ + public function getSerializableTrace(): array + { + return $this->serializableTrace; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Exception/ExpectationFailedException.php b/vendor/phpunit/phpunit/src/Framework/Exception/ExpectationFailedException.php new file mode 100644 index 00000000..b9a595a8 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Exception/ExpectationFailedException.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use Exception; +use SebastianBergmann\Comparator\ComparisonFailure; + +/** + * Exception for expectations which failed their check. + * + * The exception contains the error message and optionally a + * SebastianBergmann\Comparator\ComparisonFailure which is used to + * generate diff output of the failed expectations. + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ExpectationFailedException extends AssertionFailedError +{ + /** + * @var ComparisonFailure + */ + protected $comparisonFailure; + + public function __construct(string $message, ComparisonFailure $comparisonFailure = null, Exception $previous = null) + { + $this->comparisonFailure = $comparisonFailure; + + parent::__construct($message, 0, $previous); + } + + public function getComparisonFailure(): ?ComparisonFailure + { + return $this->comparisonFailure; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Exception/IncompleteTestError.php b/vendor/phpunit/phpunit/src/Framework/Exception/IncompleteTestError.php new file mode 100644 index 00000000..65f9c8bc --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Exception/IncompleteTestError.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class IncompleteTestError extends AssertionFailedError implements IncompleteTest +{ +} diff --git a/vendor/phpunit/phpunit/src/Framework/Exception/InvalidArgumentException.php b/vendor/phpunit/phpunit/src/Framework/Exception/InvalidArgumentException.php new file mode 100644 index 00000000..888e9300 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Exception/InvalidArgumentException.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function debug_backtrace; +use function in_array; +use function lcfirst; +use function sprintf; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvalidArgumentException extends Exception +{ + public static function create(int $argument, string $type): self + { + $stack = debug_backtrace(); + $function = $stack[1]['function']; + + if (isset($stack[1]['class'])) { + $function = sprintf('%s::%s', $stack[1]['class'], $stack[1]['function']); + } + + return new self( + sprintf( + 'Argument #%d of %s() must be %s %s', + $argument, + $function, + in_array(lcfirst($type)[0], ['a', 'e', 'i', 'o', 'u'], true) ? 'an' : 'a', + $type, + ), + ); + } + + private function __construct(string $message = '', int $code = 0, \Exception $previous = null) + { + parent::__construct($message, $code, $previous); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Exception/InvalidCoversTargetException.php b/vendor/phpunit/phpunit/src/Framework/Exception/InvalidCoversTargetException.php new file mode 100644 index 00000000..ebf2994a --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Exception/InvalidCoversTargetException.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvalidCoversTargetException extends CodeCoverageException +{ +} diff --git a/vendor/phpunit/phpunit/src/Framework/Exception/InvalidDataProviderException.php b/vendor/phpunit/phpunit/src/Framework/Exception/InvalidDataProviderException.php new file mode 100644 index 00000000..7e2ef24c --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Exception/InvalidDataProviderException.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvalidDataProviderException extends Exception +{ +} diff --git a/vendor/phpunit/phpunit/src/Framework/Exception/MissingCoversAnnotationException.php b/vendor/phpunit/phpunit/src/Framework/Exception/MissingCoversAnnotationException.php new file mode 100644 index 00000000..567a6c4c --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Exception/MissingCoversAnnotationException.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MissingCoversAnnotationException extends RiskyTestError +{ +} diff --git a/vendor/phpunit/phpunit/src/Framework/Exception/NoChildTestSuiteException.php b/vendor/phpunit/phpunit/src/Framework/Exception/NoChildTestSuiteException.php new file mode 100644 index 00000000..7ef4153b --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Exception/NoChildTestSuiteException.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class NoChildTestSuiteException extends Exception +{ +} diff --git a/vendor/phpunit/phpunit/src/Framework/Exception/OutputError.php b/vendor/phpunit/phpunit/src/Framework/Exception/OutputError.php new file mode 100644 index 00000000..1c8b37e5 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Exception/OutputError.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class OutputError extends AssertionFailedError +{ +} diff --git a/vendor/phpunit/phpunit/src/Framework/Exception/PHPTAssertionFailedError.php b/vendor/phpunit/phpunit/src/Framework/Exception/PHPTAssertionFailedError.php new file mode 100644 index 00000000..17126139 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Exception/PHPTAssertionFailedError.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class PHPTAssertionFailedError extends SyntheticError +{ + /** + * @var string + */ + private $diff; + + public function __construct(string $message, int $code, string $file, int $line, array $trace, string $diff) + { + parent::__construct($message, $code, $file, $line, $trace); + $this->diff = $diff; + } + + public function getDiff(): string + { + return $this->diff; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Exception/RiskyTestError.php b/vendor/phpunit/phpunit/src/Framework/Exception/RiskyTestError.php new file mode 100644 index 00000000..a66552c0 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Exception/RiskyTestError.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +class RiskyTestError extends AssertionFailedError +{ +} diff --git a/vendor/phpunit/phpunit/src/Framework/Exception/SkippedTestError.php b/vendor/phpunit/phpunit/src/Framework/Exception/SkippedTestError.php new file mode 100644 index 00000000..7d553dcf --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Exception/SkippedTestError.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class SkippedTestError extends AssertionFailedError implements SkippedTest +{ +} diff --git a/vendor/phpunit/phpunit/src/Framework/Exception/SkippedTestSuiteError.php b/vendor/phpunit/phpunit/src/Framework/Exception/SkippedTestSuiteError.php new file mode 100644 index 00000000..5448508a --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Exception/SkippedTestSuiteError.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class SkippedTestSuiteError extends AssertionFailedError implements SkippedTest +{ +} diff --git a/vendor/phpunit/phpunit/src/Framework/Exception/SyntheticError.php b/vendor/phpunit/phpunit/src/Framework/Exception/SyntheticError.php new file mode 100644 index 00000000..c3124ba0 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Exception/SyntheticError.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +class SyntheticError extends AssertionFailedError +{ + /** + * The synthetic file. + * + * @var string + */ + protected $syntheticFile = ''; + + /** + * The synthetic line number. + * + * @var int + */ + protected $syntheticLine = 0; + + /** + * The synthetic trace. + * + * @var array + */ + protected $syntheticTrace = []; + + public function __construct(string $message, int $code, string $file, int $line, array $trace) + { + parent::__construct($message, $code); + + $this->syntheticFile = $file; + $this->syntheticLine = $line; + $this->syntheticTrace = $trace; + } + + public function getSyntheticFile(): string + { + return $this->syntheticFile; + } + + public function getSyntheticLine(): int + { + return $this->syntheticLine; + } + + public function getSyntheticTrace(): array + { + return $this->syntheticTrace; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Exception/SyntheticSkippedError.php b/vendor/phpunit/phpunit/src/Framework/Exception/SyntheticSkippedError.php new file mode 100644 index 00000000..f6e155d7 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Exception/SyntheticSkippedError.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class SyntheticSkippedError extends SyntheticError implements SkippedTest +{ +} diff --git a/vendor/phpunit/phpunit/src/Framework/Exception/UnintentionallyCoveredCodeError.php b/vendor/phpunit/phpunit/src/Framework/Exception/UnintentionallyCoveredCodeError.php new file mode 100644 index 00000000..fcd1d824 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Exception/UnintentionallyCoveredCodeError.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class UnintentionallyCoveredCodeError extends RiskyTestError +{ +} diff --git a/vendor/phpunit/phpunit/src/Framework/Exception/Warning.php b/vendor/phpunit/phpunit/src/Framework/Exception/Warning.php new file mode 100644 index 00000000..35e94493 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Exception/Warning.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Warning extends Exception implements SelfDescribing +{ + /** + * Wrapper for getMessage() which is declared as final. + */ + public function toString(): string + { + return $this->getMessage(); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/ExceptionWrapper.php b/vendor/phpunit/phpunit/src/Framework/ExceptionWrapper.php new file mode 100644 index 00000000..00d40353 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/ExceptionWrapper.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use const PHP_VERSION_ID; +use function array_keys; +use function get_class; +use function spl_object_hash; +use PHPUnit\Util\Filter; +use Throwable; +use WeakReference; + +/** + * Wraps Exceptions thrown by code under test. + * + * Re-instantiates Exceptions thrown by user-space code to retain their original + * class names, properties, and stack traces (but without arguments). + * + * Unlike PHPUnit\Framework\Exception, the complete stack of previous Exceptions + * is processed. + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ExceptionWrapper extends Exception +{ + /** + * @var string + */ + protected $className; + + /** + * @var null|ExceptionWrapper + */ + protected $previous; + + /** + * @var null|WeakReference + */ + private $originalException; + + public function __construct(Throwable $t) + { + // PDOException::getCode() is a string. + // @see https://php.net/manual/en/class.pdoexception.php#95812 + parent::__construct($t->getMessage(), (int) $t->getCode()); + + $this->setOriginalException($t); + } + + public function __toString(): string + { + $string = TestFailure::exceptionToString($this); + + if ($trace = Filter::getFilteredStacktrace($this)) { + $string .= "\n" . $trace; + } + + if ($this->previous) { + $string .= "\nCaused by\n" . $this->previous; + } + + return $string; + } + + public function getClassName(): string + { + return $this->className; + } + + public function getPreviousWrapped(): ?self + { + return $this->previous; + } + + public function setClassName(string $className): void + { + $this->className = $className; + } + + public function setOriginalException(Throwable $t): void + { + $this->originalException($t); + + $this->className = get_class($t); + $this->file = $t->getFile(); + $this->line = $t->getLine(); + + $this->serializableTrace = $t->getTrace(); + + foreach (array_keys($this->serializableTrace) as $key) { + unset($this->serializableTrace[$key]['args']); + } + + if ($t->getPrevious()) { + $this->previous = new self($t->getPrevious()); + } + } + + public function getOriginalException(): ?Throwable + { + return $this->originalException(); + } + + /** + * Method to contain static originalException to exclude it from stacktrace to prevent the stacktrace contents, + * which can be quite big, from being garbage-collected, thus blocking memory until shutdown. + * + * Approach works both for var_dump() and var_export() and print_r(). + */ + private function originalException(Throwable $exceptionToStore = null): ?Throwable + { + // drop once PHP 7.3 support is removed + if (PHP_VERSION_ID < 70400) { + static $originalExceptions; + + $instanceId = spl_object_hash($this); + + if ($exceptionToStore) { + $originalExceptions[$instanceId] = $exceptionToStore; + } + + return $originalExceptions[$instanceId] ?? null; + } + + if ($exceptionToStore) { + $this->originalException = WeakReference::create($exceptionToStore); + } + + return $this->originalException !== null ? $this->originalException->get() : null; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/ExecutionOrderDependency.php b/vendor/phpunit/phpunit/src/Framework/ExecutionOrderDependency.php new file mode 100644 index 00000000..89ecc5e2 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/ExecutionOrderDependency.php @@ -0,0 +1,206 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function array_filter; +use function array_map; +use function array_values; +use function count; +use function explode; +use function in_array; +use function strpos; +use function trim; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ExecutionOrderDependency +{ + /** + * @var string + */ + private $className = ''; + + /** + * @var string + */ + private $methodName = ''; + + /** + * @var bool + */ + private $useShallowClone = false; + + /** + * @var bool + */ + private $useDeepClone = false; + + public static function createFromDependsAnnotation(string $className, string $annotation): self + { + // Split clone option and target + $parts = explode(' ', trim($annotation), 2); + + if (count($parts) === 1) { + $cloneOption = ''; + $target = $parts[0]; + } else { + $cloneOption = $parts[0]; + $target = $parts[1]; + } + + // Prefix provided class for targets assumed to be in scope + if ($target !== '' && strpos($target, '::') === false) { + $target = $className . '::' . $target; + } + + return new self($target, null, $cloneOption); + } + + /** + * @psalm-param list $dependencies + * + * @psalm-return list + */ + public static function filterInvalid(array $dependencies): array + { + return array_values( + array_filter( + $dependencies, + static function (self $d) + { + return $d->isValid(); + }, + ), + ); + } + + /** + * @psalm-param list $existing + * @psalm-param list $additional + * + * @psalm-return list + */ + public static function mergeUnique(array $existing, array $additional): array + { + $existingTargets = array_map( + static function ($dependency) + { + return $dependency->getTarget(); + }, + $existing, + ); + + foreach ($additional as $dependency) { + if (in_array($dependency->getTarget(), $existingTargets, true)) { + continue; + } + + $existingTargets[] = $dependency->getTarget(); + $existing[] = $dependency; + } + + return $existing; + } + + /** + * @psalm-param list $left + * @psalm-param list $right + * + * @psalm-return list + */ + public static function diff(array $left, array $right): array + { + if ($right === []) { + return $left; + } + + if ($left === []) { + return []; + } + + $diff = []; + $rightTargets = array_map( + static function ($dependency) + { + return $dependency->getTarget(); + }, + $right, + ); + + foreach ($left as $dependency) { + if (in_array($dependency->getTarget(), $rightTargets, true)) { + continue; + } + + $diff[] = $dependency; + } + + return $diff; + } + + public function __construct(string $classOrCallableName, ?string $methodName = null, ?string $option = null) + { + if ($classOrCallableName === '') { + return; + } + + if (strpos($classOrCallableName, '::') !== false) { + [$this->className, $this->methodName] = explode('::', $classOrCallableName); + } else { + $this->className = $classOrCallableName; + $this->methodName = !empty($methodName) ? $methodName : 'class'; + } + + if ($option === 'clone') { + $this->useDeepClone = true; + } elseif ($option === 'shallowClone') { + $this->useShallowClone = true; + } + } + + public function __toString(): string + { + return $this->getTarget(); + } + + public function isValid(): bool + { + // Invalid dependencies can be declared and are skipped by the runner + return $this->className !== '' && $this->methodName !== ''; + } + + public function useShallowClone(): bool + { + return $this->useShallowClone; + } + + public function useDeepClone(): bool + { + return $this->useDeepClone; + } + + public function targetIsClass(): bool + { + return $this->methodName === 'class'; + } + + public function getTarget(): string + { + return $this->isValid() + ? $this->className . '::' . $this->methodName + : ''; + } + + public function getTargetClassName(): string + { + return $this->className; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/IncompleteTest.php b/vendor/phpunit/phpunit/src/Framework/IncompleteTest.php new file mode 100644 index 00000000..b77b1aff --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/IncompleteTest.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use Throwable; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface IncompleteTest extends Throwable +{ +} diff --git a/vendor/phpunit/phpunit/src/Framework/IncompleteTestCase.php b/vendor/phpunit/phpunit/src/Framework/IncompleteTestCase.php new file mode 100644 index 00000000..53e031a2 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/IncompleteTestCase.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class IncompleteTestCase extends TestCase +{ + /** + * @var ?bool + */ + protected $backupGlobals = false; + + /** + * @var ?bool + */ + protected $backupStaticAttributes = false; + + /** + * @var ?bool + */ + protected $runTestInSeparateProcess = false; + + /** + * @var string + */ + private $message; + + public function __construct(string $className, string $methodName, string $message = '') + { + parent::__construct($className . '::' . $methodName); + + $this->message = $message; + } + + public function getMessage(): string + { + return $this->message; + } + + /** + * Returns a string representation of the test case. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function toString(): string + { + return $this->getName(); + } + + /** + * @throws Exception + */ + protected function runTest(): void + { + $this->markTestIncomplete($this->message); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/InvalidParameterGroupException.php b/vendor/phpunit/phpunit/src/Framework/InvalidParameterGroupException.php new file mode 100644 index 00000000..feb9cc98 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/InvalidParameterGroupException.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvalidParameterGroupException extends Exception +{ +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Api/Api.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Api/Api.php new file mode 100644 index 00000000..56e6b69b --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Api/Api.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use PHPUnit\Framework\MockObject\Builder\InvocationMocker as InvocationMockerBuilder; +use PHPUnit\Framework\MockObject\Rule\InvocationOrder; + +/** + * @internal This trait is not covered by the backward compatibility promise for PHPUnit + */ +trait Api +{ + /** + * @var ConfigurableMethod[] + */ + private static $__phpunit_configurableMethods; + + /** + * @var object + */ + private $__phpunit_originalObject; + + /** + * @var bool + */ + private $__phpunit_returnValueGeneration = true; + + /** + * @var InvocationHandler + */ + private $__phpunit_invocationMocker; + + /** @noinspection MagicMethodsValidityInspection */ + public static function __phpunit_initConfigurableMethods(ConfigurableMethod ...$configurableMethods): void + { + if (isset(static::$__phpunit_configurableMethods)) { + throw new ConfigurableMethodsAlreadyInitializedException( + 'Configurable methods is already initialized and can not be reinitialized', + ); + } + + static::$__phpunit_configurableMethods = $configurableMethods; + } + + /** @noinspection MagicMethodsValidityInspection */ + public function __phpunit_setOriginalObject($originalObject): void + { + $this->__phpunit_originalObject = $originalObject; + } + + /** @noinspection MagicMethodsValidityInspection */ + public function __phpunit_setReturnValueGeneration(bool $returnValueGeneration): void + { + $this->__phpunit_returnValueGeneration = $returnValueGeneration; + } + + /** @noinspection MagicMethodsValidityInspection */ + public function __phpunit_getInvocationHandler(): InvocationHandler + { + if ($this->__phpunit_invocationMocker === null) { + $this->__phpunit_invocationMocker = new InvocationHandler( + static::$__phpunit_configurableMethods, + $this->__phpunit_returnValueGeneration, + ); + } + + return $this->__phpunit_invocationMocker; + } + + /** @noinspection MagicMethodsValidityInspection */ + public function __phpunit_hasMatchers(): bool + { + return $this->__phpunit_getInvocationHandler()->hasMatchers(); + } + + /** @noinspection MagicMethodsValidityInspection */ + public function __phpunit_verify(bool $unsetInvocationMocker = true): void + { + $this->__phpunit_getInvocationHandler()->verify(); + + if ($unsetInvocationMocker) { + $this->__phpunit_invocationMocker = null; + } + } + + public function expects(InvocationOrder $matcher): InvocationMockerBuilder + { + return $this->__phpunit_getInvocationHandler()->expects($matcher); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Api/Method.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Api/Method.php new file mode 100644 index 00000000..f8be3808 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Api/Method.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function call_user_func_array; +use function func_get_args; +use PHPUnit\Framework\MockObject\Rule\AnyInvokedCount; + +/** + * @internal This trait is not covered by the backward compatibility promise for PHPUnit + */ +trait Method +{ + public function method() + { + $expects = $this->expects(new AnyInvokedCount); + + return call_user_func_array( + [$expects, 'method'], + func_get_args(), + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Builder/Identity.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Builder/Identity.php new file mode 100644 index 00000000..a68bfadf --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Builder/Identity.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Builder; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface Identity +{ + /** + * Sets the identification of the expectation to $id. + * + * @note The identifier is unique per mock object. + * + * @param string $id unique identification of expectation + */ + public function id($id); +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Builder/InvocationMocker.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Builder/InvocationMocker.php new file mode 100644 index 00000000..626c33a7 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Builder/InvocationMocker.php @@ -0,0 +1,308 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Builder; + +use function array_map; +use function array_merge; +use function count; +use function in_array; +use function is_string; +use function strtolower; +use PHPUnit\Framework\Constraint\Constraint; +use PHPUnit\Framework\MockObject\ConfigurableMethod; +use PHPUnit\Framework\MockObject\IncompatibleReturnValueException; +use PHPUnit\Framework\MockObject\InvocationHandler; +use PHPUnit\Framework\MockObject\Matcher; +use PHPUnit\Framework\MockObject\MatcherAlreadyRegisteredException; +use PHPUnit\Framework\MockObject\MethodCannotBeConfiguredException; +use PHPUnit\Framework\MockObject\MethodNameAlreadyConfiguredException; +use PHPUnit\Framework\MockObject\MethodNameNotConfiguredException; +use PHPUnit\Framework\MockObject\MethodParametersAlreadyConfiguredException; +use PHPUnit\Framework\MockObject\Rule; +use PHPUnit\Framework\MockObject\Stub\ConsecutiveCalls; +use PHPUnit\Framework\MockObject\Stub\Exception; +use PHPUnit\Framework\MockObject\Stub\ReturnArgument; +use PHPUnit\Framework\MockObject\Stub\ReturnCallback; +use PHPUnit\Framework\MockObject\Stub\ReturnReference; +use PHPUnit\Framework\MockObject\Stub\ReturnSelf; +use PHPUnit\Framework\MockObject\Stub\ReturnStub; +use PHPUnit\Framework\MockObject\Stub\ReturnValueMap; +use PHPUnit\Framework\MockObject\Stub\Stub; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class InvocationMocker implements InvocationStubber, MethodNameMatch +{ + /** + * @var InvocationHandler + */ + private $invocationHandler; + + /** + * @var Matcher + */ + private $matcher; + + /** + * @var ConfigurableMethod[] + */ + private $configurableMethods; + + public function __construct(InvocationHandler $handler, Matcher $matcher, ConfigurableMethod ...$configurableMethods) + { + $this->invocationHandler = $handler; + $this->matcher = $matcher; + $this->configurableMethods = $configurableMethods; + } + + /** + * @throws MatcherAlreadyRegisteredException + * + * @return $this + */ + public function id($id): self + { + $this->invocationHandler->registerMatcher($id, $this->matcher); + + return $this; + } + + /** + * @return $this + */ + public function will(Stub $stub): Identity + { + $this->matcher->setStub($stub); + + return $this; + } + + /** + * @param mixed $value + * @param mixed[] $nextValues + * + * @throws IncompatibleReturnValueException + */ + public function willReturn($value, ...$nextValues): self + { + if (count($nextValues) === 0) { + $this->ensureTypeOfReturnValues([$value]); + + $stub = $value instanceof Stub ? $value : new ReturnStub($value); + } else { + $values = array_merge([$value], $nextValues); + + $this->ensureTypeOfReturnValues($values); + + $stub = new ConsecutiveCalls($values); + } + + return $this->will($stub); + } + + public function willReturnReference(&$reference): self + { + $stub = new ReturnReference($reference); + + return $this->will($stub); + } + + public function willReturnMap(array $valueMap): self + { + $stub = new ReturnValueMap($valueMap); + + return $this->will($stub); + } + + public function willReturnArgument($argumentIndex): self + { + $stub = new ReturnArgument($argumentIndex); + + return $this->will($stub); + } + + public function willReturnCallback($callback): self + { + $stub = new ReturnCallback($callback); + + return $this->will($stub); + } + + public function willReturnSelf(): self + { + $stub = new ReturnSelf; + + return $this->will($stub); + } + + public function willReturnOnConsecutiveCalls(...$values): self + { + $stub = new ConsecutiveCalls($values); + + return $this->will($stub); + } + + public function willThrowException(Throwable $exception): self + { + $stub = new Exception($exception); + + return $this->will($stub); + } + + /** + * @return $this + */ + public function after($id): self + { + $this->matcher->setAfterMatchBuilderId($id); + + return $this; + } + + /** + * @param mixed[] $arguments + * + * @throws \PHPUnit\Framework\Exception + * @throws MethodNameNotConfiguredException + * @throws MethodParametersAlreadyConfiguredException + * + * @return $this + */ + public function with(...$arguments): self + { + $this->ensureParametersCanBeConfigured(); + + $this->matcher->setParametersRule(new Rule\Parameters($arguments)); + + return $this; + } + + /** + * @param array ...$arguments + * + * @throws \PHPUnit\Framework\Exception + * @throws MethodNameNotConfiguredException + * @throws MethodParametersAlreadyConfiguredException + * + * @return $this + * + * @deprecated + */ + public function withConsecutive(...$arguments): self + { + $this->ensureParametersCanBeConfigured(); + + $this->matcher->setParametersRule(new Rule\ConsecutiveParameters($arguments)); + + return $this; + } + + /** + * @throws MethodNameNotConfiguredException + * @throws MethodParametersAlreadyConfiguredException + * + * @return $this + */ + public function withAnyParameters(): self + { + $this->ensureParametersCanBeConfigured(); + + $this->matcher->setParametersRule(new Rule\AnyParameters); + + return $this; + } + + /** + * @param Constraint|string $constraint + * + * @throws \PHPUnit\Framework\InvalidArgumentException + * @throws MethodCannotBeConfiguredException + * @throws MethodNameAlreadyConfiguredException + * + * @return $this + */ + public function method($constraint): self + { + if ($this->matcher->hasMethodNameRule()) { + throw new MethodNameAlreadyConfiguredException; + } + + $configurableMethodNames = array_map( + static function (ConfigurableMethod $configurable) + { + return strtolower($configurable->getName()); + }, + $this->configurableMethods, + ); + + if (is_string($constraint) && !in_array(strtolower($constraint), $configurableMethodNames, true)) { + throw new MethodCannotBeConfiguredException($constraint); + } + + $this->matcher->setMethodNameRule(new Rule\MethodName($constraint)); + + return $this; + } + + /** + * @throws MethodNameNotConfiguredException + * @throws MethodParametersAlreadyConfiguredException + */ + private function ensureParametersCanBeConfigured(): void + { + if (!$this->matcher->hasMethodNameRule()) { + throw new MethodNameNotConfiguredException; + } + + if ($this->matcher->hasParametersRule()) { + throw new MethodParametersAlreadyConfiguredException; + } + } + + private function getConfiguredMethod(): ?ConfigurableMethod + { + $configuredMethod = null; + + foreach ($this->configurableMethods as $configurableMethod) { + if ($this->matcher->getMethodNameRule()->matchesName($configurableMethod->getName())) { + if ($configuredMethod !== null) { + return null; + } + + $configuredMethod = $configurableMethod; + } + } + + return $configuredMethod; + } + + /** + * @throws IncompatibleReturnValueException + */ + private function ensureTypeOfReturnValues(array $values): void + { + $configuredMethod = $this->getConfiguredMethod(); + + if ($configuredMethod === null) { + return; + } + + foreach ($values as $value) { + if (!$configuredMethod->mayReturn($value)) { + throw new IncompatibleReturnValueException( + $configuredMethod, + $value, + ); + } + } + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Builder/InvocationStubber.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Builder/InvocationStubber.php new file mode 100644 index 00000000..1756cfc0 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Builder/InvocationStubber.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Builder; + +use PHPUnit\Framework\MockObject\Stub\Stub; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface InvocationStubber +{ + public function will(Stub $stub): Identity; + + /** @return self */ + public function willReturn($value, ...$nextValues)/* : self */; + + /** + * @param mixed $reference + * + * @return self + */ + public function willReturnReference(&$reference)/* : self */; + + /** + * @param array> $valueMap + * + * @return self + */ + public function willReturnMap(array $valueMap)/* : self */; + + /** + * @param int $argumentIndex + * + * @return self + */ + public function willReturnArgument($argumentIndex)/* : self */; + + /** + * @param callable $callback + * + * @return self + */ + public function willReturnCallback($callback)/* : self */; + + /** @return self */ + public function willReturnSelf()/* : self */; + + /** + * @param mixed $values + * + * @return self + */ + public function willReturnOnConsecutiveCalls(...$values)/* : self */; + + /** @return self */ + public function willThrowException(Throwable $exception)/* : self */; +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Builder/MethodNameMatch.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Builder/MethodNameMatch.php new file mode 100644 index 00000000..543d596c --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Builder/MethodNameMatch.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Builder; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface MethodNameMatch extends ParametersMatch +{ + /** + * Adds a new method name match and returns the parameter match object for + * further matching possibilities. + * + * @param \PHPUnit\Framework\Constraint\Constraint $constraint Constraint for matching method, if a string is passed it will use the PHPUnit_Framework_Constraint_IsEqual + * + * @return ParametersMatch + */ + public function method($constraint); +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Builder/ParametersMatch.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Builder/ParametersMatch.php new file mode 100644 index 00000000..707d8255 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Builder/ParametersMatch.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Builder; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface ParametersMatch extends Stub +{ + /** + * Defines the expectation which must occur before the current is valid. + * + * @param string $id the identification of the expectation that should + * occur before this one + * + * @return Stub + */ + public function after($id); + + /** + * Sets the parameters to match for, each parameter to this function will + * be part of match. To perform specific matches or constraints create a + * new PHPUnit\Framework\Constraint\Constraint and use it for the parameter. + * If the parameter value is not a constraint it will use the + * PHPUnit\Framework\Constraint\IsEqual for the value. + * + * Some examples: + * + * // match first parameter with value 2 + * $b->with(2); + * // match first parameter with value 'smock' and second identical to 42 + * $b->with('smock', new PHPUnit\Framework\Constraint\IsEqual(42)); + * + * + * @return ParametersMatch + */ + public function with(...$arguments); + + /** + * Sets a rule which allows any kind of parameters. + * + * Some examples: + * + * // match any number of parameters + * $b->withAnyParameters(); + * + * + * @return ParametersMatch + */ + public function withAnyParameters(); +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Builder/Stub.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Builder/Stub.php new file mode 100644 index 00000000..d7cb78fc --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Builder/Stub.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Builder; + +use PHPUnit\Framework\MockObject\Stub\Stub as BaseStub; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface Stub extends Identity +{ + /** + * Stubs the matching method with the stub object $stub. Any invocations of + * the matched method will now be handled by the stub instead. + */ + public function will(BaseStub $stub): Identity; +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/ConfigurableMethod.php b/vendor/phpunit/phpunit/src/Framework/MockObject/ConfigurableMethod.php new file mode 100644 index 00000000..4757dc63 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/ConfigurableMethod.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use SebastianBergmann\Type\Type; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ConfigurableMethod +{ + /** + * @var string + */ + private $name; + + /** + * @var Type + */ + private $returnType; + + public function __construct(string $name, Type $returnType) + { + $this->name = $name; + $this->returnType = $returnType; + } + + public function getName(): string + { + return $this->name; + } + + public function mayReturn($value): bool + { + if ($value === null && $this->returnType->allowsNull()) { + return true; + } + + return $this->returnType->isAssignable(Type::fromValue($value, false)); + } + + public function getReturnTypeDeclaration(): string + { + return $this->returnType->asString(); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/BadMethodCallException.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/BadMethodCallException.php new file mode 100644 index 00000000..7e655e23 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/BadMethodCallException.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class BadMethodCallException extends \BadMethodCallException implements Exception +{ +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/CannotUseAddMethodsException.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/CannotUseAddMethodsException.php new file mode 100644 index 00000000..848746b5 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/CannotUseAddMethodsException.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function sprintf; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class CannotUseAddMethodsException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct(string $type, string $methodName) + { + parent::__construct( + sprintf( + 'Trying to configure method "%s" with addMethods(), but it exists in class "%s". Use onlyMethods() for methods that exist in the class', + $methodName, + $type, + ), + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/CannotUseOnlyMethodsException.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/CannotUseOnlyMethodsException.php new file mode 100644 index 00000000..0efcd02a --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/CannotUseOnlyMethodsException.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function sprintf; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class CannotUseOnlyMethodsException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct(string $type, string $methodName) + { + parent::__construct( + sprintf( + 'Trying to configure method "%s" with onlyMethods(), but it does not exist in class "%s". Use addMethods() for methods that do not exist in the class', + $methodName, + $type, + ), + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ClassAlreadyExistsException.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ClassAlreadyExistsException.php new file mode 100644 index 00000000..8c9c0a52 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ClassAlreadyExistsException.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function sprintf; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ClassAlreadyExistsException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct(string $className) + { + parent::__construct( + sprintf( + 'Class "%s" already exists', + $className, + ), + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ClassIsFinalException.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ClassIsFinalException.php new file mode 100644 index 00000000..2bce2d88 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ClassIsFinalException.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function sprintf; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ClassIsFinalException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct(string $className) + { + parent::__construct( + sprintf( + 'Class "%s" is declared "final" and cannot be doubled', + $className, + ), + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ClassIsReadonlyException.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ClassIsReadonlyException.php new file mode 100644 index 00000000..f73570e1 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ClassIsReadonlyException.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function sprintf; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ClassIsReadonlyException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct(string $className) + { + parent::__construct( + sprintf( + 'Class "%s" is declared "readonly" and cannot be doubled', + $className, + ), + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ConfigurableMethodsAlreadyInitializedException.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ConfigurableMethodsAlreadyInitializedException.php new file mode 100644 index 00000000..d12ac997 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ConfigurableMethodsAlreadyInitializedException.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ConfigurableMethodsAlreadyInitializedException extends \PHPUnit\Framework\Exception implements Exception +{ +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/DuplicateMethodException.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/DuplicateMethodException.php new file mode 100644 index 00000000..f96a04ac --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/DuplicateMethodException.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function array_diff_assoc; +use function array_unique; +use function implode; +use function sprintf; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class DuplicateMethodException extends \PHPUnit\Framework\Exception implements Exception +{ + /** + * @psalm-param list $methods + */ + public function __construct(array $methods) + { + parent::__construct( + sprintf( + 'Cannot double using a method list that contains duplicates: "%s" (duplicate: "%s")', + implode(', ', $methods), + implode(', ', array_unique(array_diff_assoc($methods, array_unique($methods)))), + ), + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/Exception.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/Exception.php new file mode 100644 index 00000000..5880bc03 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/Exception.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use Throwable; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface Exception extends Throwable +{ +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/IncompatibleReturnValueException.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/IncompatibleReturnValueException.php new file mode 100644 index 00000000..1ca8e9c9 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/IncompatibleReturnValueException.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function get_class; +use function gettype; +use function is_object; +use function sprintf; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class IncompatibleReturnValueException extends \PHPUnit\Framework\Exception implements Exception +{ + /** + * @param mixed $value + */ + public function __construct(ConfigurableMethod $method, $value) + { + parent::__construct( + sprintf( + 'Method %s may not return value of type %s, its declared return type is "%s"', + $method->getName(), + is_object($value) ? get_class($value) : gettype($value), + $method->getReturnTypeDeclaration(), + ), + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/InvalidMethodNameException.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/InvalidMethodNameException.php new file mode 100644 index 00000000..0ab74cbb --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/InvalidMethodNameException.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function sprintf; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvalidMethodNameException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct(string $method) + { + parent::__construct( + sprintf( + 'Cannot double method with invalid name "%s"', + $method, + ), + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MatchBuilderNotFoundException.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MatchBuilderNotFoundException.php new file mode 100644 index 00000000..f2e1a31e --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MatchBuilderNotFoundException.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function sprintf; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MatchBuilderNotFoundException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct(string $id) + { + parent::__construct( + sprintf( + 'No builder found for match builder identification <%s>', + $id, + ), + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MatcherAlreadyRegisteredException.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MatcherAlreadyRegisteredException.php new file mode 100644 index 00000000..0972ffaf --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MatcherAlreadyRegisteredException.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function sprintf; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MatcherAlreadyRegisteredException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct(string $id) + { + parent::__construct( + sprintf( + 'Matcher with id <%s> is already registered', + $id, + ), + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MethodCannotBeConfiguredException.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MethodCannotBeConfiguredException.php new file mode 100644 index 00000000..2f0bb5a6 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MethodCannotBeConfiguredException.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function sprintf; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MethodCannotBeConfiguredException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct(string $method) + { + parent::__construct( + sprintf( + 'Trying to configure method "%s" which cannot be configured because it does not exist, has not been specified, is final, or is static', + $method, + ), + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MethodNameAlreadyConfiguredException.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MethodNameAlreadyConfiguredException.php new file mode 100644 index 00000000..1e9f2c04 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MethodNameAlreadyConfiguredException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MethodNameAlreadyConfiguredException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct() + { + parent::__construct('Method name is already configured'); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MethodNameNotConfiguredException.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MethodNameNotConfiguredException.php new file mode 100644 index 00000000..89565b77 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MethodNameNotConfiguredException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MethodNameNotConfiguredException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct() + { + parent::__construct('Method name is not configured'); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MethodParametersAlreadyConfiguredException.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MethodParametersAlreadyConfiguredException.php new file mode 100644 index 00000000..1609c6ff --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MethodParametersAlreadyConfiguredException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MethodParametersAlreadyConfiguredException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct() + { + parent::__construct('Method parameters already configured'); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/OriginalConstructorInvocationRequiredException.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/OriginalConstructorInvocationRequiredException.php new file mode 100644 index 00000000..ecb9b63c --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/OriginalConstructorInvocationRequiredException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class OriginalConstructorInvocationRequiredException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct() + { + parent::__construct('Proxying to original methods requires invoking the original constructor'); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ReflectionException.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ReflectionException.php new file mode 100644 index 00000000..d6319c69 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ReflectionException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use RuntimeException; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ReflectionException extends RuntimeException implements Exception +{ +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ReturnValueNotConfiguredException.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ReturnValueNotConfiguredException.php new file mode 100644 index 00000000..2bc4e882 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ReturnValueNotConfiguredException.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function sprintf; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ReturnValueNotConfiguredException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct(Invocation $invocation) + { + parent::__construct( + sprintf( + 'Return value inference disabled and no expectation set up for %s::%s()', + $invocation->getClassName(), + $invocation->getMethodName(), + ), + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/RuntimeException.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/RuntimeException.php new file mode 100644 index 00000000..33b6a5be --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/RuntimeException.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class RuntimeException extends \RuntimeException implements Exception +{ +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/SoapExtensionNotAvailableException.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/SoapExtensionNotAvailableException.php new file mode 100644 index 00000000..6ec5057a --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/SoapExtensionNotAvailableException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class SoapExtensionNotAvailableException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct() + { + parent::__construct( + 'The SOAP extension is required to generate a test double from WSDL', + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/UnknownClassException.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/UnknownClassException.php new file mode 100644 index 00000000..b08dead0 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/UnknownClassException.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function sprintf; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class UnknownClassException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct(string $className) + { + parent::__construct( + sprintf( + 'Class "%s" does not exist', + $className, + ), + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/UnknownTraitException.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/UnknownTraitException.php new file mode 100644 index 00000000..c689dae9 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/UnknownTraitException.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function sprintf; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class UnknownTraitException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct(string $traitName) + { + parent::__construct( + sprintf( + 'Trait "%s" does not exist', + $traitName, + ), + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/UnknownTypeException.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/UnknownTypeException.php new file mode 100644 index 00000000..c50b6911 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/UnknownTypeException.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function sprintf; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class UnknownTypeException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct(string $type) + { + parent::__construct( + sprintf( + 'Class or interface "%s" does not exist', + $type, + ), + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Generator.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Generator.php new file mode 100644 index 00000000..fa672479 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Generator.php @@ -0,0 +1,1160 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use const DIRECTORY_SEPARATOR; +use const PHP_EOL; +use const PHP_MAJOR_VERSION; +use const PREG_OFFSET_CAPTURE; +use const WSDL_CACHE_NONE; +use function array_merge; +use function array_pop; +use function array_unique; +use function class_exists; +use function count; +use function explode; +use function extension_loaded; +use function implode; +use function in_array; +use function interface_exists; +use function is_array; +use function is_object; +use function md5; +use function method_exists; +use function mt_rand; +use function preg_match; +use function preg_match_all; +use function range; +use function serialize; +use function sort; +use function sprintf; +use function str_replace; +use function strlen; +use function strpos; +use function strtolower; +use function substr; +use function trait_exists; +use Doctrine\Instantiator\Exception\ExceptionInterface as InstantiatorException; +use Doctrine\Instantiator\Instantiator; +use Exception; +use Iterator; +use IteratorAggregate; +use PHPUnit\Framework\InvalidArgumentException; +use ReflectionClass; +use ReflectionMethod; +use SebastianBergmann\Template\Exception as TemplateException; +use SebastianBergmann\Template\Template; +use SoapClient; +use SoapFault; +use Throwable; +use Traversable; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Generator +{ + private const MOCKED_CLONE_METHOD_WITH_VOID_RETURN_TYPE_TRAIT = <<<'EOT' +namespace PHPUnit\Framework\MockObject; + +trait MockedCloneMethodWithVoidReturnType +{ + public function __clone(): void + { + $this->__phpunit_invocationMocker = clone $this->__phpunit_getInvocationHandler(); + } +} +EOT; + private const MOCKED_CLONE_METHOD_WITHOUT_RETURN_TYPE_TRAIT = <<<'EOT' +namespace PHPUnit\Framework\MockObject; + +trait MockedCloneMethodWithoutReturnType +{ + public function __clone() + { + $this->__phpunit_invocationMocker = clone $this->__phpunit_getInvocationHandler(); + } +} +EOT; + private const UNMOCKED_CLONE_METHOD_WITH_VOID_RETURN_TYPE_TRAIT = <<<'EOT' +namespace PHPUnit\Framework\MockObject; + +trait UnmockedCloneMethodWithVoidReturnType +{ + public function __clone(): void + { + $this->__phpunit_invocationMocker = clone $this->__phpunit_getInvocationHandler(); + + parent::__clone(); + } +} +EOT; + private const UNMOCKED_CLONE_METHOD_WITHOUT_RETURN_TYPE_TRAIT = <<<'EOT' +namespace PHPUnit\Framework\MockObject; + +trait UnmockedCloneMethodWithoutReturnType +{ + public function __clone() + { + $this->__phpunit_invocationMocker = clone $this->__phpunit_getInvocationHandler(); + + parent::__clone(); + } +} +EOT; + + /** + * @var array + */ + private const EXCLUDED_METHOD_NAMES = [ + '__CLASS__' => true, + '__DIR__' => true, + '__FILE__' => true, + '__FUNCTION__' => true, + '__LINE__' => true, + '__METHOD__' => true, + '__NAMESPACE__' => true, + '__TRAIT__' => true, + '__clone' => true, + '__halt_compiler' => true, + ]; + + /** + * @var array + */ + private static $cache = []; + + /** + * @var Template[] + */ + private static $templates = []; + + /** + * Returns a mock object for the specified class. + * + * @param null|array $methods + * + * @throws ClassAlreadyExistsException + * @throws ClassIsFinalException + * @throws ClassIsReadonlyException + * @throws DuplicateMethodException + * @throws InvalidArgumentException + * @throws InvalidMethodNameException + * @throws OriginalConstructorInvocationRequiredException + * @throws ReflectionException + * @throws RuntimeException + * @throws UnknownTypeException + */ + public function getMock(string $type, $methods = [], array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = true, bool $callOriginalClone = true, bool $callAutoload = true, bool $cloneArguments = true, bool $callOriginalMethods = false, object $proxyTarget = null, bool $allowMockingUnknownTypes = true, bool $returnValueGeneration = true): MockObject + { + if (!is_array($methods) && null !== $methods) { + throw InvalidArgumentException::create(2, 'array'); + } + + if ($type === 'Traversable' || $type === '\\Traversable') { + $type = 'Iterator'; + } + + if (!$allowMockingUnknownTypes && !class_exists($type, $callAutoload) && !interface_exists($type, $callAutoload)) { + throw new UnknownTypeException($type); + } + + if (null !== $methods) { + foreach ($methods as $method) { + if (!preg_match('~[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*~', (string) $method)) { + throw new InvalidMethodNameException((string) $method); + } + } + + if ($methods !== array_unique($methods)) { + throw new DuplicateMethodException($methods); + } + } + + if ($mockClassName !== '' && class_exists($mockClassName, false)) { + try { + $reflector = new ReflectionClass($mockClassName); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + if (!$reflector->implementsInterface(MockObject::class)) { + throw new ClassAlreadyExistsException($mockClassName); + } + } + + if (!$callOriginalConstructor && $callOriginalMethods) { + throw new OriginalConstructorInvocationRequiredException; + } + + $mock = $this->generate( + $type, + $methods, + $mockClassName, + $callOriginalClone, + $callAutoload, + $cloneArguments, + $callOriginalMethods, + ); + + return $this->getObject( + $mock, + $type, + $callOriginalConstructor, + $callAutoload, + $arguments, + $callOriginalMethods, + $proxyTarget, + $returnValueGeneration, + ); + } + + /** + * @psalm-param list $interfaces + * + * @throws RuntimeException + * @throws UnknownTypeException + */ + public function getMockForInterfaces(array $interfaces, bool $callAutoload = true): MockObject + { + if (count($interfaces) < 2) { + throw new RuntimeException('At least two interfaces must be specified'); + } + + foreach ($interfaces as $interface) { + if (!interface_exists($interface, $callAutoload)) { + throw new UnknownTypeException($interface); + } + } + + sort($interfaces); + + $methods = []; + + foreach ($interfaces as $interface) { + $methods = array_merge($methods, $this->getClassMethods($interface)); + } + + if (count(array_unique($methods)) < count($methods)) { + throw new RuntimeException('Interfaces must not declare the same method'); + } + + $unqualifiedNames = []; + + foreach ($interfaces as $interface) { + $parts = explode('\\', $interface); + $unqualifiedNames[] = array_pop($parts); + } + + sort($unqualifiedNames); + + do { + $intersectionName = sprintf( + 'Intersection_%s_%s', + implode('_', $unqualifiedNames), + substr(md5((string) mt_rand()), 0, 8), + ); + } while (interface_exists($intersectionName, false)); + + $template = $this->getTemplate('intersection.tpl'); + + $template->setVar( + [ + 'intersection' => $intersectionName, + 'interfaces' => implode(', ', $interfaces), + ], + ); + + eval($template->render()); + + return $this->getMock($intersectionName); + } + + /** + * Returns a mock object for the specified abstract class with all abstract + * methods of the class mocked. + * + * Concrete methods to mock can be specified with the $mockedMethods parameter. + * + * @psalm-template RealInstanceType of object + * + * @psalm-param class-string $originalClassName + * + * @psalm-return MockObject&RealInstanceType + * + * @throws ClassAlreadyExistsException + * @throws ClassIsFinalException + * @throws ClassIsReadonlyException + * @throws DuplicateMethodException + * @throws InvalidArgumentException + * @throws InvalidMethodNameException + * @throws OriginalConstructorInvocationRequiredException + * @throws ReflectionException + * @throws RuntimeException + * @throws UnknownClassException + * @throws UnknownTypeException + */ + public function getMockForAbstractClass(string $originalClassName, array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = true, bool $callOriginalClone = true, bool $callAutoload = true, array $mockedMethods = null, bool $cloneArguments = true): MockObject + { + if (class_exists($originalClassName, $callAutoload) || + interface_exists($originalClassName, $callAutoload)) { + try { + $reflector = new ReflectionClass($originalClassName); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + $methods = $mockedMethods; + + foreach ($reflector->getMethods() as $method) { + if ($method->isAbstract() && !in_array($method->getName(), $methods ?? [], true)) { + $methods[] = $method->getName(); + } + } + + if (empty($methods)) { + $methods = null; + } + + return $this->getMock( + $originalClassName, + $methods, + $arguments, + $mockClassName, + $callOriginalConstructor, + $callOriginalClone, + $callAutoload, + $cloneArguments, + ); + } + + throw new UnknownClassException($originalClassName); + } + + /** + * Returns a mock object for the specified trait with all abstract methods + * of the trait mocked. Concrete methods to mock can be specified with the + * `$mockedMethods` parameter. + * + * @psalm-param trait-string $traitName + * + * @throws ClassAlreadyExistsException + * @throws ClassIsFinalException + * @throws ClassIsReadonlyException + * @throws DuplicateMethodException + * @throws InvalidArgumentException + * @throws InvalidMethodNameException + * @throws OriginalConstructorInvocationRequiredException + * @throws ReflectionException + * @throws RuntimeException + * @throws UnknownClassException + * @throws UnknownTraitException + * @throws UnknownTypeException + */ + public function getMockForTrait(string $traitName, array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = true, bool $callOriginalClone = true, bool $callAutoload = true, array $mockedMethods = null, bool $cloneArguments = true): MockObject + { + if (!trait_exists($traitName, $callAutoload)) { + throw new UnknownTraitException($traitName); + } + + $className = $this->generateClassName( + $traitName, + '', + 'Trait_', + ); + + $classTemplate = $this->getTemplate('trait_class.tpl'); + + $classTemplate->setVar( + [ + 'prologue' => 'abstract ', + 'class_name' => $className['className'], + 'trait_name' => $traitName, + ], + ); + + $mockTrait = new MockTrait($classTemplate->render(), $className['className']); + $mockTrait->generate(); + + return $this->getMockForAbstractClass($className['className'], $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $mockedMethods, $cloneArguments); + } + + /** + * Returns an object for the specified trait. + * + * @psalm-param trait-string $traitName + * + * @throws ReflectionException + * @throws RuntimeException + * @throws UnknownTraitException + */ + public function getObjectForTrait(string $traitName, string $traitClassName = '', bool $callAutoload = true, bool $callOriginalConstructor = false, array $arguments = []): object + { + if (!trait_exists($traitName, $callAutoload)) { + throw new UnknownTraitException($traitName); + } + + $className = $this->generateClassName( + $traitName, + $traitClassName, + 'Trait_', + ); + + $classTemplate = $this->getTemplate('trait_class.tpl'); + + $classTemplate->setVar( + [ + 'prologue' => '', + 'class_name' => $className['className'], + 'trait_name' => $traitName, + ], + ); + + return $this->getObject( + new MockTrait( + $classTemplate->render(), + $className['className'], + ), + '', + $callOriginalConstructor, + $callAutoload, + $arguments, + ); + } + + /** + * @throws ClassIsFinalException + * @throws ClassIsReadonlyException + * @throws ReflectionException + * @throws RuntimeException + */ + public function generate(string $type, array $methods = null, string $mockClassName = '', bool $callOriginalClone = true, bool $callAutoload = true, bool $cloneArguments = true, bool $callOriginalMethods = false): MockClass + { + if ($mockClassName !== '') { + return $this->generateMock( + $type, + $methods, + $mockClassName, + $callOriginalClone, + $callAutoload, + $cloneArguments, + $callOriginalMethods, + ); + } + + $key = md5( + $type . + serialize($methods) . + serialize($callOriginalClone) . + serialize($cloneArguments) . + serialize($callOriginalMethods), + ); + + if (!isset(self::$cache[$key])) { + self::$cache[$key] = $this->generateMock( + $type, + $methods, + $mockClassName, + $callOriginalClone, + $callAutoload, + $cloneArguments, + $callOriginalMethods, + ); + } + + return self::$cache[$key]; + } + + /** + * @throws RuntimeException + * @throws SoapExtensionNotAvailableException + */ + public function generateClassFromWsdl(string $wsdlFile, string $className, array $methods = [], array $options = []): string + { + if (!extension_loaded('soap')) { + throw new SoapExtensionNotAvailableException; + } + + $options = array_merge($options, ['cache_wsdl' => WSDL_CACHE_NONE]); + + try { + $client = new SoapClient($wsdlFile, $options); + $_methods = array_unique($client->__getFunctions()); + unset($client); + } catch (SoapFault $e) { + throw new RuntimeException( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + + sort($_methods); + + $methodTemplate = $this->getTemplate('wsdl_method.tpl'); + $methodsBuffer = ''; + + foreach ($_methods as $method) { + preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*\(/', $method, $matches, PREG_OFFSET_CAPTURE); + $lastFunction = array_pop($matches[0]); + $nameStart = $lastFunction[1]; + $nameEnd = $nameStart + strlen($lastFunction[0]) - 1; + $name = str_replace('(', '', $lastFunction[0]); + + if (empty($methods) || in_array($name, $methods, true)) { + $args = explode( + ',', + str_replace(')', '', substr($method, $nameEnd + 1)), + ); + + foreach (range(0, count($args) - 1) as $i) { + $parameterStart = strpos($args[$i], '$'); + + if (!$parameterStart) { + continue; + } + + $args[$i] = substr($args[$i], $parameterStart); + } + + $methodTemplate->setVar( + [ + 'method_name' => $name, + 'arguments' => implode(', ', $args), + ], + ); + + $methodsBuffer .= $methodTemplate->render(); + } + } + + $optionsBuffer = '['; + + foreach ($options as $key => $value) { + $optionsBuffer .= $key . ' => ' . $value; + } + + $optionsBuffer .= ']'; + + $classTemplate = $this->getTemplate('wsdl_class.tpl'); + $namespace = ''; + + if (strpos($className, '\\') !== false) { + $parts = explode('\\', $className); + $className = array_pop($parts); + $namespace = 'namespace ' . implode('\\', $parts) . ';' . "\n\n"; + } + + $classTemplate->setVar( + [ + 'namespace' => $namespace, + 'class_name' => $className, + 'wsdl' => $wsdlFile, + 'options' => $optionsBuffer, + 'methods' => $methodsBuffer, + ], + ); + + return $classTemplate->render(); + } + + /** + * @throws ReflectionException + * + * @return string[] + */ + public function getClassMethods(string $className): array + { + try { + $class = new ReflectionClass($className); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + $methods = []; + + foreach ($class->getMethods() as $method) { + if ($method->isPublic() || $method->isAbstract()) { + $methods[] = $method->getName(); + } + } + + return $methods; + } + + /** + * @throws ReflectionException + * + * @return MockMethod[] + */ + public function mockClassMethods(string $className, bool $callOriginalMethods, bool $cloneArguments): array + { + try { + $class = new ReflectionClass($className); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + $methods = []; + + foreach ($class->getMethods() as $method) { + if (($method->isPublic() || $method->isAbstract()) && $this->canMockMethod($method)) { + $methods[] = MockMethod::fromReflection($method, $callOriginalMethods, $cloneArguments); + } + } + + return $methods; + } + + /** + * @throws ReflectionException + * + * @return MockMethod[] + */ + public function mockInterfaceMethods(string $interfaceName, bool $cloneArguments): array + { + try { + $class = new ReflectionClass($interfaceName); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + $methods = []; + + foreach ($class->getMethods() as $method) { + $methods[] = MockMethod::fromReflection($method, false, $cloneArguments); + } + + return $methods; + } + + /** + * @psalm-param class-string $interfaceName + * + * @throws ReflectionException + * + * @return ReflectionMethod[] + */ + private function userDefinedInterfaceMethods(string $interfaceName): array + { + try { + // @codeCoverageIgnoreStart + $interface = new ReflectionClass($interfaceName); + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + $methods = []; + + foreach ($interface->getMethods() as $method) { + if (!$method->isUserDefined()) { + continue; + } + + $methods[] = $method; + } + + return $methods; + } + + /** + * @throws ReflectionException + * @throws RuntimeException + */ + private function getObject(MockType $mockClass, $type = '', bool $callOriginalConstructor = false, bool $callAutoload = false, array $arguments = [], bool $callOriginalMethods = false, object $proxyTarget = null, bool $returnValueGeneration = true) + { + $className = $mockClass->generate(); + + if ($callOriginalConstructor) { + if (count($arguments) === 0) { + $object = new $className; + } else { + try { + $class = new ReflectionClass($className); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + $object = $class->newInstanceArgs($arguments); + } + } else { + try { + $object = (new Instantiator)->instantiate($className); + } catch (InstantiatorException $e) { + throw new RuntimeException($e->getMessage()); + } + } + + if ($callOriginalMethods) { + if (!is_object($proxyTarget)) { + if (count($arguments) === 0) { + $proxyTarget = new $type; + } else { + try { + $class = new ReflectionClass($type); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + $proxyTarget = $class->newInstanceArgs($arguments); + } + } + + $object->__phpunit_setOriginalObject($proxyTarget); + } + + if ($object instanceof MockObject) { + $object->__phpunit_setReturnValueGeneration($returnValueGeneration); + } + + return $object; + } + + /** + * @throws ClassIsFinalException + * @throws ClassIsReadonlyException + * @throws ReflectionException + * @throws RuntimeException + */ + private function generateMock(string $type, ?array $explicitMethods, string $mockClassName, bool $callOriginalClone, bool $callAutoload, bool $cloneArguments, bool $callOriginalMethods): MockClass + { + $classTemplate = $this->getTemplate('mocked_class.tpl'); + $additionalInterfaces = []; + $mockedCloneMethod = false; + $unmockedCloneMethod = false; + $isClass = false; + $isInterface = false; + $class = null; + $mockMethods = new MockMethodSet; + + $_mockClassName = $this->generateClassName( + $type, + $mockClassName, + 'Mock_', + ); + + if (class_exists($_mockClassName['fullClassName'], $callAutoload)) { + $isClass = true; + } elseif (interface_exists($_mockClassName['fullClassName'], $callAutoload)) { + $isInterface = true; + } + + if (!$isClass && !$isInterface) { + $prologue = 'class ' . $_mockClassName['originalClassName'] . "\n{\n}\n\n"; + + if (!empty($_mockClassName['namespaceName'])) { + $prologue = 'namespace ' . $_mockClassName['namespaceName'] . + " {\n\n" . $prologue . "}\n\n" . + "namespace {\n\n"; + + $epilogue = "\n\n}"; + } + + $mockedCloneMethod = true; + } else { + try { + $class = new ReflectionClass($_mockClassName['fullClassName']); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + if ($class->isFinal()) { + throw new ClassIsFinalException($_mockClassName['fullClassName']); + } + + if (method_exists($class, 'isReadOnly') && $class->isReadOnly()) { + throw new ClassIsReadonlyException($_mockClassName['fullClassName']); + } + + // @see https://github.com/sebastianbergmann/phpunit/issues/2995 + if ($isInterface && $class->implementsInterface(Throwable::class)) { + $actualClassName = Exception::class; + $additionalInterfaces[] = $class->getName(); + $isInterface = false; + + try { + $class = new ReflectionClass($actualClassName); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + foreach ($this->userDefinedInterfaceMethods($_mockClassName['fullClassName']) as $method) { + $methodName = $method->getName(); + + if ($class->hasMethod($methodName)) { + try { + $classMethod = $class->getMethod($methodName); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + if (!$this->canMockMethod($classMethod)) { + continue; + } + } + + $mockMethods->addMethods( + MockMethod::fromReflection($method, $callOriginalMethods, $cloneArguments), + ); + } + + $_mockClassName = $this->generateClassName( + $actualClassName, + $_mockClassName['className'], + 'Mock_', + ); + } + + // @see https://github.com/sebastianbergmann/phpunit-mock-objects/issues/103 + if ($isInterface && $class->implementsInterface(Traversable::class) && + !$class->implementsInterface(Iterator::class) && + !$class->implementsInterface(IteratorAggregate::class)) { + $additionalInterfaces[] = Iterator::class; + + $mockMethods->addMethods( + ...$this->mockClassMethods(Iterator::class, $callOriginalMethods, $cloneArguments), + ); + } + + if ($class->hasMethod('__clone')) { + try { + $cloneMethod = $class->getMethod('__clone'); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + if (!$cloneMethod->isFinal()) { + if ($callOriginalClone && !$isInterface) { + $unmockedCloneMethod = true; + } else { + $mockedCloneMethod = true; + } + } + } else { + $mockedCloneMethod = true; + } + } + + if ($isClass && $explicitMethods === []) { + $mockMethods->addMethods( + ...$this->mockClassMethods($_mockClassName['fullClassName'], $callOriginalMethods, $cloneArguments), + ); + } + + if ($isInterface && ($explicitMethods === [] || $explicitMethods === null)) { + $mockMethods->addMethods( + ...$this->mockInterfaceMethods($_mockClassName['fullClassName'], $cloneArguments), + ); + } + + if (is_array($explicitMethods)) { + foreach ($explicitMethods as $methodName) { + if ($class !== null && $class->hasMethod($methodName)) { + try { + $method = $class->getMethod($methodName); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + if ($this->canMockMethod($method)) { + $mockMethods->addMethods( + MockMethod::fromReflection($method, $callOriginalMethods, $cloneArguments), + ); + } + } else { + $mockMethods->addMethods( + MockMethod::fromName( + $_mockClassName['fullClassName'], + $methodName, + $cloneArguments, + ), + ); + } + } + } + + $mockedMethods = ''; + $configurable = []; + + foreach ($mockMethods->asArray() as $mockMethod) { + $mockedMethods .= $mockMethod->generateCode(); + $configurable[] = new ConfigurableMethod($mockMethod->getName(), $mockMethod->getReturnType()); + } + + $method = ''; + + if (!$mockMethods->hasMethod('method') && (!isset($class) || !$class->hasMethod('method'))) { + $method = PHP_EOL . ' use \PHPUnit\Framework\MockObject\Method;'; + } + + $cloneTrait = ''; + + if ($mockedCloneMethod) { + $cloneTrait = $this->mockedCloneMethod(); + } + + if ($unmockedCloneMethod) { + $cloneTrait = $this->unmockedCloneMethod(); + } + + $classTemplate->setVar( + [ + 'prologue' => $prologue ?? '', + 'epilogue' => $epilogue ?? '', + 'class_declaration' => $this->generateMockClassDeclaration( + $_mockClassName, + $isInterface, + $additionalInterfaces, + ), + 'clone' => $cloneTrait, + 'mock_class_name' => $_mockClassName['className'], + 'mocked_methods' => $mockedMethods, + 'method' => $method, + ], + ); + + return new MockClass( + $classTemplate->render(), + $_mockClassName['className'], + $configurable, + ); + } + + private function generateClassName(string $type, string $className, string $prefix): array + { + if ($type[0] === '\\') { + $type = substr($type, 1); + } + + $classNameParts = explode('\\', $type); + + if (count($classNameParts) > 1) { + $type = array_pop($classNameParts); + $namespaceName = implode('\\', $classNameParts); + $fullClassName = $namespaceName . '\\' . $type; + } else { + $namespaceName = ''; + $fullClassName = $type; + } + + if ($className === '') { + do { + $className = $prefix . $type . '_' . + substr(md5((string) mt_rand()), 0, 8); + } while (class_exists($className, false)); + } + + return [ + 'className' => $className, + 'originalClassName' => $type, + 'fullClassName' => $fullClassName, + 'namespaceName' => $namespaceName, + ]; + } + + private function generateMockClassDeclaration(array $mockClassName, bool $isInterface, array $additionalInterfaces = []): string + { + $buffer = 'class '; + + $additionalInterfaces[] = MockObject::class; + $interfaces = implode(', ', $additionalInterfaces); + + if ($isInterface) { + $buffer .= sprintf( + '%s implements %s', + $mockClassName['className'], + $interfaces, + ); + + if (!in_array($mockClassName['originalClassName'], $additionalInterfaces, true)) { + $buffer .= ', '; + + if (!empty($mockClassName['namespaceName'])) { + $buffer .= $mockClassName['namespaceName'] . '\\'; + } + + $buffer .= $mockClassName['originalClassName']; + } + } else { + $buffer .= sprintf( + '%s extends %s%s implements %s', + $mockClassName['className'], + !empty($mockClassName['namespaceName']) ? $mockClassName['namespaceName'] . '\\' : '', + $mockClassName['originalClassName'], + $interfaces, + ); + } + + return $buffer; + } + + private function canMockMethod(ReflectionMethod $method): bool + { + return !($this->isConstructor($method) || $method->isFinal() || $method->isPrivate() || $this->isMethodNameExcluded($method->getName())); + } + + private function isMethodNameExcluded(string $name): bool + { + return isset(self::EXCLUDED_METHOD_NAMES[$name]); + } + + /** + * @throws RuntimeException + */ + private function getTemplate(string $template): Template + { + $filename = __DIR__ . DIRECTORY_SEPARATOR . 'Generator' . DIRECTORY_SEPARATOR . $template; + + if (!isset(self::$templates[$filename])) { + try { + self::$templates[$filename] = new Template($filename); + } catch (TemplateException $e) { + throw new RuntimeException( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + } + + return self::$templates[$filename]; + } + + /** + * @see https://github.com/sebastianbergmann/phpunit/issues/4139#issuecomment-605409765 + */ + private function isConstructor(ReflectionMethod $method): bool + { + $methodName = strtolower($method->getName()); + + if ($methodName === '__construct') { + return true; + } + + if (PHP_MAJOR_VERSION >= 8) { + return false; + } + + $className = strtolower($method->getDeclaringClass()->getName()); + + return $methodName === $className; + } + + private function mockedCloneMethod(): string + { + if (PHP_MAJOR_VERSION >= 8) { + if (!trait_exists('\PHPUnit\Framework\MockObject\MockedCloneMethodWithVoidReturnType')) { + eval(self::MOCKED_CLONE_METHOD_WITH_VOID_RETURN_TYPE_TRAIT); + } + + return PHP_EOL . ' use \PHPUnit\Framework\MockObject\MockedCloneMethodWithVoidReturnType;'; + } + + if (!trait_exists('\PHPUnit\Framework\MockObject\MockedCloneMethodWithoutReturnType')) { + eval(self::MOCKED_CLONE_METHOD_WITHOUT_RETURN_TYPE_TRAIT); + } + + return PHP_EOL . ' use \PHPUnit\Framework\MockObject\MockedCloneMethodWithoutReturnType;'; + } + + private function unmockedCloneMethod(): string + { + if (PHP_MAJOR_VERSION >= 8) { + if (!trait_exists('\PHPUnit\Framework\MockObject\UnmockedCloneMethodWithVoidReturnType')) { + eval(self::UNMOCKED_CLONE_METHOD_WITH_VOID_RETURN_TYPE_TRAIT); + } + + return PHP_EOL . ' use \PHPUnit\Framework\MockObject\UnmockedCloneMethodWithVoidReturnType;'; + } + + if (!trait_exists('\PHPUnit\Framework\MockObject\UnmockedCloneMethodWithoutReturnType')) { + eval(self::UNMOCKED_CLONE_METHOD_WITHOUT_RETURN_TYPE_TRAIT); + } + + return PHP_EOL . ' use \PHPUnit\Framework\MockObject\UnmockedCloneMethodWithoutReturnType;'; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/deprecation.tpl b/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/deprecation.tpl new file mode 100644 index 00000000..5bf06f52 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/deprecation.tpl @@ -0,0 +1,2 @@ + + @trigger_error({deprecation}, E_USER_DEPRECATED); diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/intersection.tpl b/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/intersection.tpl new file mode 100644 index 00000000..75cd27a6 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/intersection.tpl @@ -0,0 +1,5 @@ +declare(strict_types=1); + +interface {intersection} extends {interfaces} +{ +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/mocked_class.tpl b/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/mocked_class.tpl new file mode 100644 index 00000000..593119fb --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/mocked_class.tpl @@ -0,0 +1,6 @@ +declare(strict_types=1); + +{prologue}{class_declaration} +{ + use \PHPUnit\Framework\MockObject\Api;{method}{clone} +{mocked_methods}}{epilogue} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/mocked_method.tpl b/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/mocked_method.tpl new file mode 100644 index 00000000..114ff8d0 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/mocked_method.tpl @@ -0,0 +1,22 @@ + + {modifier} function {reference}{method_name}({arguments_decl}){return_declaration} + {{deprecation} + $__phpunit_arguments = [{arguments_call}]; + $__phpunit_count = func_num_args(); + + if ($__phpunit_count > {arguments_count}) { + $__phpunit_arguments_tmp = func_get_args(); + + for ($__phpunit_i = {arguments_count}; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { + $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; + } + } + + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + '{class_name}', '{method_name}', $__phpunit_arguments, '{return_type}', $this, {clone_arguments} + ) + ); + + return $__phpunit_result; + } diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/mocked_method_never_or_void.tpl b/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/mocked_method_never_or_void.tpl new file mode 100644 index 00000000..39020220 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/mocked_method_never_or_void.tpl @@ -0,0 +1,20 @@ + + {modifier} function {reference}{method_name}({arguments_decl}){return_declaration} + {{deprecation} + $__phpunit_arguments = [{arguments_call}]; + $__phpunit_count = func_num_args(); + + if ($__phpunit_count > {arguments_count}) { + $__phpunit_arguments_tmp = func_get_args(); + + for ($__phpunit_i = {arguments_count}; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { + $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; + } + } + + $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + '{class_name}', '{method_name}', $__phpunit_arguments, '{return_type}', $this, {clone_arguments} + ) + ); + } diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/mocked_static_method.tpl b/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/mocked_static_method.tpl new file mode 100644 index 00000000..5e5cf23c --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/mocked_static_method.tpl @@ -0,0 +1,5 @@ + + {modifier} function {reference}{method_name}({arguments_decl}){return_declaration} + { + throw new \PHPUnit\Framework\MockObject\BadMethodCallException('Static method "{method_name}" cannot be invoked on mock object'); + } diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/proxied_method.tpl b/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/proxied_method.tpl new file mode 100644 index 00000000..91bef463 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/proxied_method.tpl @@ -0,0 +1,22 @@ + + {modifier} function {reference}{method_name}({arguments_decl}){return_declaration} + { + $__phpunit_arguments = [{arguments_call}]; + $__phpunit_count = func_num_args(); + + if ($__phpunit_count > {arguments_count}) { + $__phpunit_arguments_tmp = func_get_args(); + + for ($__phpunit_i = {arguments_count}; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { + $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; + } + } + + $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + '{class_name}', '{method_name}', $__phpunit_arguments, '{return_type}', $this, {clone_arguments}, true + ) + ); + + return call_user_func_array(array($this->__phpunit_originalObject, "{method_name}"), $__phpunit_arguments); + } diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/proxied_method_never_or_void.tpl b/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/proxied_method_never_or_void.tpl new file mode 100644 index 00000000..cce19882 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/proxied_method_never_or_void.tpl @@ -0,0 +1,22 @@ + + {modifier} function {reference}{method_name}({arguments_decl}){return_declaration} + { + $__phpunit_arguments = [{arguments_call}]; + $__phpunit_count = func_num_args(); + + if ($__phpunit_count > {arguments_count}) { + $__phpunit_arguments_tmp = func_get_args(); + + for ($__phpunit_i = {arguments_count}; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { + $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; + } + } + + $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + '{class_name}', '{method_name}', $__phpunit_arguments, '{return_type}', $this, {clone_arguments}, true + ) + ); + + call_user_func_array(array($this->__phpunit_originalObject, "{method_name}"), $__phpunit_arguments); + } diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/trait_class.tpl b/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/trait_class.tpl new file mode 100644 index 00000000..a8fe470f --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/trait_class.tpl @@ -0,0 +1,6 @@ +declare(strict_types=1); + +{prologue}class {class_name} +{ + use {trait_name}; +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/wsdl_class.tpl b/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/wsdl_class.tpl new file mode 100644 index 00000000..b3100b41 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/wsdl_class.tpl @@ -0,0 +1,9 @@ +declare(strict_types=1); + +{namespace}class {class_name} extends \SoapClient +{ + public function __construct($wsdl, array $options) + { + parent::__construct('{wsdl}', $options); + } +{methods}} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/wsdl_method.tpl b/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/wsdl_method.tpl new file mode 100644 index 00000000..bb16e763 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/wsdl_method.tpl @@ -0,0 +1,4 @@ + + public function {method_name}({arguments}) + { + } diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Invocation.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Invocation.php new file mode 100644 index 00000000..ed8c4e92 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Invocation.php @@ -0,0 +1,301 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function array_map; +use function explode; +use function get_class; +use function implode; +use function in_array; +use function interface_exists; +use function is_object; +use function sprintf; +use function strpos; +use function strtolower; +use function substr; +use Doctrine\Instantiator\Instantiator; +use PHPUnit\Framework\SelfDescribing; +use PHPUnit\Util\Cloner; +use SebastianBergmann\Exporter\Exporter; +use stdClass; +use Throwable; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Invocation implements SelfDescribing +{ + /** + * @var string + */ + private $className; + + /** + * @var string + */ + private $methodName; + + /** + * @var array + */ + private $parameters; + + /** + * @var string + */ + private $returnType; + + /** + * @var bool + */ + private $isReturnTypeNullable = false; + + /** + * @var bool + */ + private $proxiedCall; + + /** + * @var object + */ + private $object; + + public function __construct(string $className, string $methodName, array $parameters, string $returnType, object $object, bool $cloneObjects = false, bool $proxiedCall = false) + { + $this->className = $className; + $this->methodName = $methodName; + $this->parameters = $parameters; + $this->object = $object; + $this->proxiedCall = $proxiedCall; + + if (strtolower($methodName) === '__tostring') { + $returnType = 'string'; + } + + if (strpos($returnType, '?') === 0) { + $returnType = substr($returnType, 1); + $this->isReturnTypeNullable = true; + } + + $this->returnType = $returnType; + + if (!$cloneObjects) { + return; + } + + foreach ($this->parameters as $key => $value) { + if (is_object($value)) { + $this->parameters[$key] = Cloner::clone($value); + } + } + } + + public function getClassName(): string + { + return $this->className; + } + + public function getMethodName(): string + { + return $this->methodName; + } + + public function getParameters(): array + { + return $this->parameters; + } + + /** + * @throws RuntimeException + * + * @return mixed Mocked return value + */ + public function generateReturnValue() + { + if ($this->isReturnTypeNullable || $this->proxiedCall) { + return null; + } + + $intersection = false; + $union = false; + $unionContainsIntersections = false; + + if (strpos($this->returnType, '|') !== false) { + $types = explode('|', $this->returnType); + $union = true; + + if (strpos($this->returnType, '(') !== false) { + $unionContainsIntersections = true; + } + } elseif (strpos($this->returnType, '&') !== false) { + $types = explode('&', $this->returnType); + $intersection = true; + } else { + $types = [$this->returnType]; + } + + $types = array_map('strtolower', $types); + + if (!$intersection && !$unionContainsIntersections) { + if (in_array('', $types, true) || + in_array('null', $types, true) || + in_array('mixed', $types, true) || + in_array('void', $types, true)) { + return null; + } + + if (in_array('true', $types, true)) { + return true; + } + + if (in_array('false', $types, true) || + in_array('bool', $types, true)) { + return false; + } + + if (in_array('float', $types, true)) { + return 0.0; + } + + if (in_array('int', $types, true)) { + return 0; + } + + if (in_array('string', $types, true)) { + return ''; + } + + if (in_array('array', $types, true)) { + return []; + } + + if (in_array('static', $types, true)) { + try { + return (new Instantiator)->instantiate(get_class($this->object)); + } catch (Throwable $t) { + throw new RuntimeException( + $t->getMessage(), + (int) $t->getCode(), + $t, + ); + } + } + + if (in_array('object', $types, true)) { + return new stdClass; + } + + if (in_array('callable', $types, true) || + in_array('closure', $types, true)) { + return static function (): void + { + }; + } + + if (in_array('traversable', $types, true) || + in_array('generator', $types, true) || + in_array('iterable', $types, true)) { + $generator = static function (): \Generator + { + yield from []; + }; + + return $generator(); + } + + if (!$union) { + try { + return (new Generator)->getMock($this->returnType, [], [], '', false); + } catch (Throwable $t) { + if ($t instanceof Exception) { + throw $t; + } + + throw new RuntimeException( + $t->getMessage(), + (int) $t->getCode(), + $t, + ); + } + } + } + + if ($intersection && $this->onlyInterfaces($types)) { + try { + return (new Generator)->getMockForInterfaces($types); + } catch (Throwable $t) { + throw new RuntimeException( + sprintf( + 'Return value for %s::%s() cannot be generated: %s', + $this->className, + $this->methodName, + $t->getMessage(), + ), + (int) $t->getCode(), + ); + } + } + + $reason = ''; + + if ($union) { + $reason = ' because the declared return type is a union'; + } elseif ($intersection) { + $reason = ' because the declared return type is an intersection'; + } + + throw new RuntimeException( + sprintf( + 'Return value for %s::%s() cannot be generated%s, please configure a return value for this method', + $this->className, + $this->methodName, + $reason, + ), + ); + } + + public function toString(): string + { + $exporter = new Exporter; + + return sprintf( + '%s::%s(%s)%s', + $this->className, + $this->methodName, + implode( + ', ', + array_map( + [$exporter, 'shortenedExport'], + $this->parameters, + ), + ), + $this->returnType ? sprintf(': %s', $this->returnType) : '', + ); + } + + public function getObject(): object + { + return $this->object; + } + + /** + * @psalm-param non-empty-list $types + */ + private function onlyInterfaces(array $types): bool + { + foreach ($types as $type) { + if (!interface_exists($type)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/InvocationHandler.php b/vendor/phpunit/phpunit/src/Framework/MockObject/InvocationHandler.php new file mode 100644 index 00000000..f8ee16e0 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/InvocationHandler.php @@ -0,0 +1,186 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function strtolower; +use Exception; +use PHPUnit\Framework\MockObject\Builder\InvocationMocker; +use PHPUnit\Framework\MockObject\Rule\InvocationOrder; +use Throwable; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvocationHandler +{ + /** + * @var Matcher[] + */ + private $matchers = []; + + /** + * @var Matcher[] + */ + private $matcherMap = []; + + /** + * @var ConfigurableMethod[] + */ + private $configurableMethods; + + /** + * @var bool + */ + private $returnValueGeneration; + + /** + * @var Throwable + */ + private $deferredError; + + public function __construct(array $configurableMethods, bool $returnValueGeneration) + { + $this->configurableMethods = $configurableMethods; + $this->returnValueGeneration = $returnValueGeneration; + } + + public function hasMatchers(): bool + { + foreach ($this->matchers as $matcher) { + if ($matcher->hasMatchers()) { + return true; + } + } + + return false; + } + + /** + * Looks up the match builder with identification $id and returns it. + * + * @param string $id The identification of the match builder + */ + public function lookupMatcher(string $id): ?Matcher + { + if (isset($this->matcherMap[$id])) { + return $this->matcherMap[$id]; + } + + return null; + } + + /** + * Registers a matcher with the identification $id. The matcher can later be + * looked up using lookupMatcher() to figure out if it has been invoked. + * + * @param string $id The identification of the matcher + * @param Matcher $matcher The builder which is being registered + * + * @throws MatcherAlreadyRegisteredException + */ + public function registerMatcher(string $id, Matcher $matcher): void + { + if (isset($this->matcherMap[$id])) { + throw new MatcherAlreadyRegisteredException($id); + } + + $this->matcherMap[$id] = $matcher; + } + + public function expects(InvocationOrder $rule): InvocationMocker + { + $matcher = new Matcher($rule); + $this->addMatcher($matcher); + + return new InvocationMocker( + $this, + $matcher, + ...$this->configurableMethods, + ); + } + + /** + * @throws Exception + * @throws RuntimeException + */ + public function invoke(Invocation $invocation) + { + $exception = null; + $hasReturnValue = false; + $returnValue = null; + + foreach ($this->matchers as $match) { + try { + if ($match->matches($invocation)) { + $value = $match->invoked($invocation); + + if (!$hasReturnValue) { + $returnValue = $value; + $hasReturnValue = true; + } + } + } catch (Exception $e) { + $exception = $e; + } + } + + if ($exception !== null) { + throw $exception; + } + + if ($hasReturnValue) { + return $returnValue; + } + + if (!$this->returnValueGeneration) { + $exception = new ReturnValueNotConfiguredException($invocation); + + if (strtolower($invocation->getMethodName()) === '__tostring') { + $this->deferredError = $exception; + + return ''; + } + + throw $exception; + } + + return $invocation->generateReturnValue(); + } + + public function matches(Invocation $invocation): bool + { + foreach ($this->matchers as $matcher) { + if (!$matcher->matches($invocation)) { + return false; + } + } + + return true; + } + + /** + * @throws Throwable + */ + public function verify(): void + { + foreach ($this->matchers as $matcher) { + $matcher->verify(); + } + + if ($this->deferredError) { + throw $this->deferredError; + } + } + + private function addMatcher(Matcher $matcher): void + { + $this->matchers[] = $matcher; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Matcher.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Matcher.php new file mode 100644 index 00000000..a8ebe14f --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Matcher.php @@ -0,0 +1,274 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function assert; +use function implode; +use function sprintf; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\MockObject\Rule\AnyInvokedCount; +use PHPUnit\Framework\MockObject\Rule\AnyParameters; +use PHPUnit\Framework\MockObject\Rule\InvocationOrder; +use PHPUnit\Framework\MockObject\Rule\InvokedAtMostCount; +use PHPUnit\Framework\MockObject\Rule\InvokedCount; +use PHPUnit\Framework\MockObject\Rule\MethodName; +use PHPUnit\Framework\MockObject\Rule\ParametersRule; +use PHPUnit\Framework\MockObject\Stub\Stub; +use PHPUnit\Framework\TestFailure; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Matcher +{ + /** + * @var InvocationOrder + */ + private $invocationRule; + + /** + * @var mixed + */ + private $afterMatchBuilderId; + + /** + * @var bool + */ + private $afterMatchBuilderIsInvoked = false; + + /** + * @var MethodName + */ + private $methodNameRule; + + /** + * @var ParametersRule + */ + private $parametersRule; + + /** + * @var Stub + */ + private $stub; + + public function __construct(InvocationOrder $rule) + { + $this->invocationRule = $rule; + } + + public function hasMatchers(): bool + { + return !$this->invocationRule instanceof AnyInvokedCount; + } + + public function hasMethodNameRule(): bool + { + return $this->methodNameRule !== null; + } + + public function getMethodNameRule(): MethodName + { + return $this->methodNameRule; + } + + public function setMethodNameRule(MethodName $rule): void + { + $this->methodNameRule = $rule; + } + + public function hasParametersRule(): bool + { + return $this->parametersRule !== null; + } + + public function setParametersRule(ParametersRule $rule): void + { + $this->parametersRule = $rule; + } + + public function setStub(Stub $stub): void + { + $this->stub = $stub; + } + + public function setAfterMatchBuilderId(string $id): void + { + $this->afterMatchBuilderId = $id; + } + + /** + * @throws ExpectationFailedException + * @throws MatchBuilderNotFoundException + * @throws MethodNameNotConfiguredException + * @throws RuntimeException + */ + public function invoked(Invocation $invocation) + { + if ($this->methodNameRule === null) { + throw new MethodNameNotConfiguredException; + } + + if ($this->afterMatchBuilderId !== null) { + $matcher = $invocation->getObject() + ->__phpunit_getInvocationHandler() + ->lookupMatcher($this->afterMatchBuilderId); + + if (!$matcher) { + throw new MatchBuilderNotFoundException($this->afterMatchBuilderId); + } + + assert($matcher instanceof self); + + if ($matcher->invocationRule->hasBeenInvoked()) { + $this->afterMatchBuilderIsInvoked = true; + } + } + + $this->invocationRule->invoked($invocation); + + try { + if ($this->parametersRule !== null) { + $this->parametersRule->apply($invocation); + } + } catch (ExpectationFailedException $e) { + throw new ExpectationFailedException( + sprintf( + "Expectation failed for %s when %s\n%s", + $this->methodNameRule->toString(), + $this->invocationRule->toString(), + $e->getMessage(), + ), + $e->getComparisonFailure(), + ); + } + + if ($this->stub) { + return $this->stub->invoke($invocation); + } + + return $invocation->generateReturnValue(); + } + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * @throws MatchBuilderNotFoundException + * @throws MethodNameNotConfiguredException + * @throws RuntimeException + */ + public function matches(Invocation $invocation): bool + { + if ($this->afterMatchBuilderId !== null) { + $matcher = $invocation->getObject() + ->__phpunit_getInvocationHandler() + ->lookupMatcher($this->afterMatchBuilderId); + + if (!$matcher) { + throw new MatchBuilderNotFoundException($this->afterMatchBuilderId); + } + + assert($matcher instanceof self); + + if (!$matcher->invocationRule->hasBeenInvoked()) { + return false; + } + } + + if ($this->methodNameRule === null) { + throw new MethodNameNotConfiguredException; + } + + if (!$this->invocationRule->matches($invocation)) { + return false; + } + + try { + if (!$this->methodNameRule->matches($invocation)) { + return false; + } + } catch (ExpectationFailedException $e) { + throw new ExpectationFailedException( + sprintf( + "Expectation failed for %s when %s\n%s", + $this->methodNameRule->toString(), + $this->invocationRule->toString(), + $e->getMessage(), + ), + $e->getComparisonFailure(), + ); + } + + return true; + } + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + * @throws MethodNameNotConfiguredException + */ + public function verify(): void + { + if ($this->methodNameRule === null) { + throw new MethodNameNotConfiguredException; + } + + try { + $this->invocationRule->verify(); + + if ($this->parametersRule === null) { + $this->parametersRule = new AnyParameters; + } + + $invocationIsAny = $this->invocationRule instanceof AnyInvokedCount; + $invocationIsNever = $this->invocationRule instanceof InvokedCount && $this->invocationRule->isNever(); + $invocationIsAtMost = $this->invocationRule instanceof InvokedAtMostCount; + + if (!$invocationIsAny && !$invocationIsNever && !$invocationIsAtMost) { + $this->parametersRule->verify(); + } + } catch (ExpectationFailedException $e) { + throw new ExpectationFailedException( + sprintf( + "Expectation failed for %s when %s.\n%s", + $this->methodNameRule->toString(), + $this->invocationRule->toString(), + TestFailure::exceptionToString($e), + ), + ); + } + } + + public function toString(): string + { + $list = []; + + if ($this->invocationRule !== null) { + $list[] = $this->invocationRule->toString(); + } + + if ($this->methodNameRule !== null) { + $list[] = 'where ' . $this->methodNameRule->toString(); + } + + if ($this->parametersRule !== null) { + $list[] = 'and ' . $this->parametersRule->toString(); + } + + if ($this->afterMatchBuilderId !== null) { + $list[] = 'after ' . $this->afterMatchBuilderId; + } + + if ($this->stub !== null) { + $list[] = 'will ' . $this->stub->toString(); + } + + return implode(' ', $list); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/MethodNameConstraint.php b/vendor/phpunit/phpunit/src/Framework/MockObject/MethodNameConstraint.php new file mode 100644 index 00000000..e5c955d3 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/MethodNameConstraint.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function is_string; +use function sprintf; +use function strtolower; +use PHPUnit\Framework\Constraint\Constraint; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MethodNameConstraint extends Constraint +{ + /** + * @var string + */ + private $methodName; + + public function __construct(string $methodName) + { + $this->methodName = $methodName; + } + + public function toString(): string + { + return sprintf( + 'is "%s"', + $this->methodName, + ); + } + + protected function matches($other): bool + { + if (!is_string($other)) { + return false; + } + + return strtolower($this->methodName) === strtolower($other); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/MockBuilder.php b/vendor/phpunit/phpunit/src/Framework/MockObject/MockBuilder.php new file mode 100644 index 00000000..69ec2502 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/MockBuilder.php @@ -0,0 +1,517 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function array_diff; +use function array_merge; +use PHPUnit\Framework\TestCase; +use ReflectionClass; + +/** + * @psalm-template MockedType + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class MockBuilder +{ + /** + * @var TestCase + */ + private $testCase; + + /** + * @var string + */ + private $type; + + /** + * @var null|string[] + */ + private $methods = []; + + /** + * @var bool + */ + private $emptyMethodsArray = false; + + /** + * @var string + */ + private $mockClassName = ''; + + /** + * @var array + */ + private $constructorArgs = []; + + /** + * @var bool + */ + private $originalConstructor = true; + + /** + * @var bool + */ + private $originalClone = true; + + /** + * @var bool + */ + private $autoload = true; + + /** + * @var bool + */ + private $cloneArguments = false; + + /** + * @var bool + */ + private $callOriginalMethods = false; + + /** + * @var ?object + */ + private $proxyTarget; + + /** + * @var bool + */ + private $allowMockingUnknownTypes = true; + + /** + * @var bool + */ + private $returnValueGeneration = true; + + /** + * @var Generator + */ + private $generator; + + /** + * @param string|string[] $type + * + * @psalm-param class-string|string|string[] $type + */ + public function __construct(TestCase $testCase, $type) + { + $this->testCase = $testCase; + $this->type = $type; + $this->generator = new Generator; + } + + /** + * Creates a mock object using a fluent interface. + * + * @throws \PHPUnit\Framework\InvalidArgumentException + * @throws ClassAlreadyExistsException + * @throws ClassIsFinalException + * @throws ClassIsReadonlyException + * @throws DuplicateMethodException + * @throws InvalidMethodNameException + * @throws OriginalConstructorInvocationRequiredException + * @throws ReflectionException + * @throws RuntimeException + * @throws UnknownTypeException + * + * @psalm-return MockObject&MockedType + */ + public function getMock(): MockObject + { + $object = $this->generator->getMock( + $this->type, + !$this->emptyMethodsArray ? $this->methods : null, + $this->constructorArgs, + $this->mockClassName, + $this->originalConstructor, + $this->originalClone, + $this->autoload, + $this->cloneArguments, + $this->callOriginalMethods, + $this->proxyTarget, + $this->allowMockingUnknownTypes, + $this->returnValueGeneration, + ); + + $this->testCase->registerMockObject($object); + + return $object; + } + + /** + * Creates a mock object for an abstract class using a fluent interface. + * + * @psalm-return MockObject&MockedType + * + * @throws \PHPUnit\Framework\Exception + * @throws ReflectionException + * @throws RuntimeException + */ + public function getMockForAbstractClass(): MockObject + { + $object = $this->generator->getMockForAbstractClass( + $this->type, + $this->constructorArgs, + $this->mockClassName, + $this->originalConstructor, + $this->originalClone, + $this->autoload, + $this->methods, + $this->cloneArguments, + ); + + $this->testCase->registerMockObject($object); + + return $object; + } + + /** + * Creates a mock object for a trait using a fluent interface. + * + * @psalm-return MockObject&MockedType + * + * @throws \PHPUnit\Framework\Exception + * @throws ReflectionException + * @throws RuntimeException + */ + public function getMockForTrait(): MockObject + { + $object = $this->generator->getMockForTrait( + $this->type, + $this->constructorArgs, + $this->mockClassName, + $this->originalConstructor, + $this->originalClone, + $this->autoload, + $this->methods, + $this->cloneArguments, + ); + + $this->testCase->registerMockObject($object); + + return $object; + } + + /** + * Specifies the subset of methods to mock. Default is to mock none of them. + * + * @deprecated https://github.com/sebastianbergmann/phpunit/pull/3687 + * + * @return $this + */ + public function setMethods(?array $methods = null): self + { + if ($methods === null) { + $this->methods = $methods; + } else { + $this->methods = array_merge($this->methods ?? [], $methods); + } + + return $this; + } + + /** + * Specifies the subset of methods to mock, requiring each to exist in the class. + * + * @param string[] $methods + * + * @throws CannotUseOnlyMethodsException + * @throws ReflectionException + * + * @return $this + */ + public function onlyMethods(array $methods): self + { + if (empty($methods)) { + $this->emptyMethodsArray = true; + + return $this; + } + + try { + $reflector = new ReflectionClass($this->type); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + foreach ($methods as $method) { + if (!$reflector->hasMethod($method)) { + throw new CannotUseOnlyMethodsException($this->type, $method); + } + } + + $this->methods = array_merge($this->methods ?? [], $methods); + + return $this; + } + + /** + * Specifies methods that don't exist in the class which you want to mock. + * + * @param string[] $methods + * + * @throws CannotUseAddMethodsException + * @throws ReflectionException + * @throws RuntimeException + * + * @return $this + */ + public function addMethods(array $methods): self + { + if (empty($methods)) { + $this->emptyMethodsArray = true; + + return $this; + } + + try { + $reflector = new ReflectionClass($this->type); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + foreach ($methods as $method) { + if ($reflector->hasMethod($method)) { + throw new CannotUseAddMethodsException($this->type, $method); + } + } + + $this->methods = array_merge($this->methods ?? [], $methods); + + return $this; + } + + /** + * Specifies the subset of methods to not mock. Default is to mock all of them. + * + * @deprecated https://github.com/sebastianbergmann/phpunit/pull/3687 + * + * @throws ReflectionException + */ + public function setMethodsExcept(array $methods = []): self + { + return $this->setMethods( + array_diff( + $this->generator->getClassMethods($this->type), + $methods, + ), + ); + } + + /** + * Specifies the arguments for the constructor. + * + * @return $this + */ + public function setConstructorArgs(array $args): self + { + $this->constructorArgs = $args; + + return $this; + } + + /** + * Specifies the name for the mock class. + * + * @return $this + */ + public function setMockClassName(string $name): self + { + $this->mockClassName = $name; + + return $this; + } + + /** + * Disables the invocation of the original constructor. + * + * @return $this + */ + public function disableOriginalConstructor(): self + { + $this->originalConstructor = false; + + return $this; + } + + /** + * Enables the invocation of the original constructor. + * + * @return $this + */ + public function enableOriginalConstructor(): self + { + $this->originalConstructor = true; + + return $this; + } + + /** + * Disables the invocation of the original clone constructor. + * + * @return $this + */ + public function disableOriginalClone(): self + { + $this->originalClone = false; + + return $this; + } + + /** + * Enables the invocation of the original clone constructor. + * + * @return $this + */ + public function enableOriginalClone(): self + { + $this->originalClone = true; + + return $this; + } + + /** + * Disables the use of class autoloading while creating the mock object. + * + * @return $this + */ + public function disableAutoload(): self + { + $this->autoload = false; + + return $this; + } + + /** + * Enables the use of class autoloading while creating the mock object. + * + * @return $this + */ + public function enableAutoload(): self + { + $this->autoload = true; + + return $this; + } + + /** + * Disables the cloning of arguments passed to mocked methods. + * + * @return $this + */ + public function disableArgumentCloning(): self + { + $this->cloneArguments = false; + + return $this; + } + + /** + * Enables the cloning of arguments passed to mocked methods. + * + * @return $this + */ + public function enableArgumentCloning(): self + { + $this->cloneArguments = true; + + return $this; + } + + /** + * Enables the invocation of the original methods. + * + * @return $this + */ + public function enableProxyingToOriginalMethods(): self + { + $this->callOriginalMethods = true; + + return $this; + } + + /** + * Disables the invocation of the original methods. + * + * @return $this + */ + public function disableProxyingToOriginalMethods(): self + { + $this->callOriginalMethods = false; + $this->proxyTarget = null; + + return $this; + } + + /** + * Sets the proxy target. + * + * @return $this + */ + public function setProxyTarget(object $object): self + { + $this->proxyTarget = $object; + + return $this; + } + + /** + * @return $this + */ + public function allowMockingUnknownTypes(): self + { + $this->allowMockingUnknownTypes = true; + + return $this; + } + + /** + * @return $this + */ + public function disallowMockingUnknownTypes(): self + { + $this->allowMockingUnknownTypes = false; + + return $this; + } + + /** + * @return $this + */ + public function enableAutoReturnValueGeneration(): self + { + $this->returnValueGeneration = true; + + return $this; + } + + /** + * @return $this + */ + public function disableAutoReturnValueGeneration(): self + { + $this->returnValueGeneration = false; + + return $this; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/MockClass.php b/vendor/phpunit/phpunit/src/Framework/MockObject/MockClass.php new file mode 100644 index 00000000..8f5c276d --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/MockClass.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function call_user_func; +use function class_exists; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MockClass implements MockType +{ + /** + * @var string + */ + private $classCode; + + /** + * @var class-string + */ + private $mockName; + + /** + * @var ConfigurableMethod[] + */ + private $configurableMethods; + + /** + * @psalm-param class-string $mockName + */ + public function __construct(string $classCode, string $mockName, array $configurableMethods) + { + $this->classCode = $classCode; + $this->mockName = $mockName; + $this->configurableMethods = $configurableMethods; + } + + /** + * @psalm-return class-string + */ + public function generate(): string + { + if (!class_exists($this->mockName, false)) { + eval($this->classCode); + + call_user_func( + [ + $this->mockName, + '__phpunit_initConfigurableMethods', + ], + ...$this->configurableMethods, + ); + } + + return $this->mockName; + } + + public function getClassCode(): string + { + return $this->classCode; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/MockMethod.php b/vendor/phpunit/phpunit/src/Framework/MockObject/MockMethod.php new file mode 100644 index 00000000..88462dfe --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/MockMethod.php @@ -0,0 +1,380 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use const DIRECTORY_SEPARATOR; +use function explode; +use function implode; +use function is_object; +use function is_string; +use function preg_match; +use function preg_replace; +use function sprintf; +use function strlen; +use function strpos; +use function substr; +use function substr_count; +use function trim; +use function var_export; +use ReflectionMethod; +use ReflectionParameter; +use SebastianBergmann\Template\Exception as TemplateException; +use SebastianBergmann\Template\Template; +use SebastianBergmann\Type\ReflectionMapper; +use SebastianBergmann\Type\Type; +use SebastianBergmann\Type\UnknownType; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MockMethod +{ + /** + * @var Template[] + */ + private static $templates = []; + + /** + * @var string + */ + private $className; + + /** + * @var string + */ + private $methodName; + + /** + * @var bool + */ + private $cloneArguments; + + /** + * @var string string + */ + private $modifier; + + /** + * @var string + */ + private $argumentsForDeclaration; + + /** + * @var string + */ + private $argumentsForCall; + + /** + * @var Type + */ + private $returnType; + + /** + * @var string + */ + private $reference; + + /** + * @var bool + */ + private $callOriginalMethod; + + /** + * @var bool + */ + private $static; + + /** + * @var ?string + */ + private $deprecation; + + /** + * @throws ReflectionException + * @throws RuntimeException + */ + public static function fromReflection(ReflectionMethod $method, bool $callOriginalMethod, bool $cloneArguments): self + { + if ($method->isPrivate()) { + $modifier = 'private'; + } elseif ($method->isProtected()) { + $modifier = 'protected'; + } else { + $modifier = 'public'; + } + + if ($method->isStatic()) { + $modifier .= ' static'; + } + + if ($method->returnsReference()) { + $reference = '&'; + } else { + $reference = ''; + } + + $docComment = $method->getDocComment(); + + if (is_string($docComment) && + preg_match('#\*[ \t]*+@deprecated[ \t]*+(.*?)\r?+\n[ \t]*+\*(?:[ \t]*+@|/$)#s', $docComment, $deprecation)) { + $deprecation = trim(preg_replace('#[ \t]*\r?\n[ \t]*+\*[ \t]*+#', ' ', $deprecation[1])); + } else { + $deprecation = null; + } + + return new self( + $method->getDeclaringClass()->getName(), + $method->getName(), + $cloneArguments, + $modifier, + self::getMethodParametersForDeclaration($method), + self::getMethodParametersForCall($method), + (new ReflectionMapper)->fromReturnType($method), + $reference, + $callOriginalMethod, + $method->isStatic(), + $deprecation, + ); + } + + public static function fromName(string $fullClassName, string $methodName, bool $cloneArguments): self + { + return new self( + $fullClassName, + $methodName, + $cloneArguments, + 'public', + '', + '', + new UnknownType, + '', + false, + false, + null, + ); + } + + public function __construct(string $className, string $methodName, bool $cloneArguments, string $modifier, string $argumentsForDeclaration, string $argumentsForCall, Type $returnType, string $reference, bool $callOriginalMethod, bool $static, ?string $deprecation) + { + $this->className = $className; + $this->methodName = $methodName; + $this->cloneArguments = $cloneArguments; + $this->modifier = $modifier; + $this->argumentsForDeclaration = $argumentsForDeclaration; + $this->argumentsForCall = $argumentsForCall; + $this->returnType = $returnType; + $this->reference = $reference; + $this->callOriginalMethod = $callOriginalMethod; + $this->static = $static; + $this->deprecation = $deprecation; + } + + public function getName(): string + { + return $this->methodName; + } + + /** + * @throws RuntimeException + */ + public function generateCode(): string + { + if ($this->static) { + $templateFile = 'mocked_static_method.tpl'; + } elseif ($this->returnType->isNever() || $this->returnType->isVoid()) { + $templateFile = sprintf( + '%s_method_never_or_void.tpl', + $this->callOriginalMethod ? 'proxied' : 'mocked', + ); + } else { + $templateFile = sprintf( + '%s_method.tpl', + $this->callOriginalMethod ? 'proxied' : 'mocked', + ); + } + + $deprecation = $this->deprecation; + + if (null !== $this->deprecation) { + $deprecation = "The {$this->className}::{$this->methodName} method is deprecated ({$this->deprecation})."; + $deprecationTemplate = $this->getTemplate('deprecation.tpl'); + + $deprecationTemplate->setVar( + [ + 'deprecation' => var_export($deprecation, true), + ], + ); + + $deprecation = $deprecationTemplate->render(); + } + + $template = $this->getTemplate($templateFile); + + $template->setVar( + [ + 'arguments_decl' => $this->argumentsForDeclaration, + 'arguments_call' => $this->argumentsForCall, + 'return_declaration' => !empty($this->returnType->asString()) ? (': ' . $this->returnType->asString()) : '', + 'return_type' => $this->returnType->asString(), + 'arguments_count' => !empty($this->argumentsForCall) ? substr_count($this->argumentsForCall, ',') + 1 : 0, + 'class_name' => $this->className, + 'method_name' => $this->methodName, + 'modifier' => $this->modifier, + 'reference' => $this->reference, + 'clone_arguments' => $this->cloneArguments ? 'true' : 'false', + 'deprecation' => $deprecation, + ], + ); + + return $template->render(); + } + + public function getReturnType(): Type + { + return $this->returnType; + } + + /** + * @throws RuntimeException + */ + private function getTemplate(string $template): Template + { + $filename = __DIR__ . DIRECTORY_SEPARATOR . 'Generator' . DIRECTORY_SEPARATOR . $template; + + if (!isset(self::$templates[$filename])) { + try { + self::$templates[$filename] = new Template($filename); + } catch (TemplateException $e) { + throw new RuntimeException( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + } + + return self::$templates[$filename]; + } + + /** + * Returns the parameters of a function or method. + * + * @throws RuntimeException + */ + private static function getMethodParametersForDeclaration(ReflectionMethod $method): string + { + $parameters = []; + $types = (new ReflectionMapper)->fromParameterTypes($method); + + foreach ($method->getParameters() as $i => $parameter) { + $name = '$' . $parameter->getName(); + + /* Note: PHP extensions may use empty names for reference arguments + * or "..." for methods taking a variable number of arguments. + */ + if ($name === '$' || $name === '$...') { + $name = '$arg' . $i; + } + + $default = ''; + $reference = ''; + $typeDeclaration = ''; + + if (!$types[$i]->type()->isUnknown()) { + $typeDeclaration = $types[$i]->type()->asString() . ' '; + } + + if ($parameter->isPassedByReference()) { + $reference = '&'; + } + + if ($parameter->isVariadic()) { + $name = '...' . $name; + } elseif ($parameter->isDefaultValueAvailable()) { + $default = ' = ' . self::exportDefaultValue($parameter); + } elseif ($parameter->isOptional()) { + $default = ' = null'; + } + + $parameters[] = $typeDeclaration . $reference . $name . $default; + } + + return implode(', ', $parameters); + } + + /** + * Returns the parameters of a function or method. + * + * @throws ReflectionException + */ + private static function getMethodParametersForCall(ReflectionMethod $method): string + { + $parameters = []; + + foreach ($method->getParameters() as $i => $parameter) { + $name = '$' . $parameter->getName(); + + /* Note: PHP extensions may use empty names for reference arguments + * or "..." for methods taking a variable number of arguments. + */ + if ($name === '$' || $name === '$...') { + $name = '$arg' . $i; + } + + if ($parameter->isVariadic()) { + continue; + } + + if ($parameter->isPassedByReference()) { + $parameters[] = '&' . $name; + } else { + $parameters[] = $name; + } + } + + return implode(', ', $parameters); + } + + /** + * @throws ReflectionException + */ + private static function exportDefaultValue(ReflectionParameter $parameter): string + { + try { + $defaultValue = $parameter->getDefaultValue(); + + if (!is_object($defaultValue)) { + return (string) var_export($defaultValue, true); + } + + $parameterAsString = $parameter->__toString(); + + return (string) explode( + ' = ', + substr( + substr( + $parameterAsString, + strpos($parameterAsString, ' ') + strlen(' '), + ), + 0, + -2, + ), + )[1]; + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/MockMethodSet.php b/vendor/phpunit/phpunit/src/Framework/MockObject/MockMethodSet.php new file mode 100644 index 00000000..1c78963c --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/MockMethodSet.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function array_key_exists; +use function array_values; +use function strtolower; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MockMethodSet +{ + /** + * @var MockMethod[] + */ + private $methods = []; + + public function addMethods(MockMethod ...$methods): void + { + foreach ($methods as $method) { + $this->methods[strtolower($method->getName())] = $method; + } + } + + /** + * @return MockMethod[] + */ + public function asArray(): array + { + return array_values($this->methods); + } + + public function hasMethod(string $methodName): bool + { + return array_key_exists(strtolower($methodName), $this->methods); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/MockObject.php b/vendor/phpunit/phpunit/src/Framework/MockObject/MockObject.php new file mode 100644 index 00000000..094decf4 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/MockObject.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use PHPUnit\Framework\MockObject\Builder\InvocationMocker as BuilderInvocationMocker; +use PHPUnit\Framework\MockObject\Rule\InvocationOrder; + +/** + * @method BuilderInvocationMocker method($constraint) + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface MockObject extends Stub +{ + public function __phpunit_setOriginalObject($originalObject): void; + + public function __phpunit_verify(bool $unsetInvocationMocker = true): void; + + public function expects(InvocationOrder $invocationRule): BuilderInvocationMocker; +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/MockTrait.php b/vendor/phpunit/phpunit/src/Framework/MockObject/MockTrait.php new file mode 100644 index 00000000..7c326988 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/MockTrait.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function class_exists; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MockTrait implements MockType +{ + /** + * @var string + */ + private $classCode; + + /** + * @var class-string + */ + private $mockName; + + /** + * @psalm-param class-string $mockName + */ + public function __construct(string $classCode, string $mockName) + { + $this->classCode = $classCode; + $this->mockName = $mockName; + } + + /** + * @psalm-return class-string + */ + public function generate(): string + { + if (!class_exists($this->mockName, false)) { + eval($this->classCode); + } + + return $this->mockName; + } + + public function getClassCode(): string + { + return $this->classCode; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/MockType.php b/vendor/phpunit/phpunit/src/Framework/MockObject/MockType.php new file mode 100644 index 00000000..6a03fb51 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/MockType.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface MockType +{ + /** + * @psalm-return class-string + */ + public function generate(): string; +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/AnyInvokedCount.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/AnyInvokedCount.php new file mode 100644 index 00000000..f93e5686 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/AnyInvokedCount.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Rule; + +use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class AnyInvokedCount extends InvocationOrder +{ + public function toString(): string + { + return 'invoked zero or more times'; + } + + public function verify(): void + { + } + + public function matches(BaseInvocation $invocation): bool + { + return true; + } + + protected function invokedDo(BaseInvocation $invocation): void + { + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/AnyParameters.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/AnyParameters.php new file mode 100644 index 00000000..61de7887 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/AnyParameters.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Rule; + +use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class AnyParameters implements ParametersRule +{ + public function toString(): string + { + return 'with any parameters'; + } + + public function apply(BaseInvocation $invocation): void + { + } + + public function verify(): void + { + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/ConsecutiveParameters.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/ConsecutiveParameters.php new file mode 100644 index 00000000..c2de01a4 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/ConsecutiveParameters.php @@ -0,0 +1,132 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Rule; + +use function count; +use function gettype; +use function is_iterable; +use function sprintf; +use PHPUnit\Framework\Constraint\Constraint; +use PHPUnit\Framework\Constraint\IsEqual; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\InvalidParameterGroupException; +use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @deprecated + */ +final class ConsecutiveParameters implements ParametersRule +{ + /** + * @var array + */ + private $parameterGroups = []; + + /** + * @var array + */ + private $invocations = []; + + /** + * @throws \PHPUnit\Framework\Exception + */ + public function __construct(array $parameterGroups) + { + foreach ($parameterGroups as $index => $parameters) { + if (!is_iterable($parameters)) { + throw new InvalidParameterGroupException( + sprintf( + 'Parameter group #%d must be an array or Traversable, got %s', + $index, + gettype($parameters), + ), + ); + } + + foreach ($parameters as $parameter) { + if (!$parameter instanceof Constraint) { + $parameter = new IsEqual($parameter); + } + + $this->parameterGroups[$index][] = $parameter; + } + } + } + + public function toString(): string + { + return 'with consecutive parameters'; + } + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public function apply(BaseInvocation $invocation): void + { + $this->invocations[] = $invocation; + $callIndex = count($this->invocations) - 1; + + $this->verifyInvocation($invocation, $callIndex); + } + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public function verify(): void + { + foreach ($this->invocations as $callIndex => $invocation) { + $this->verifyInvocation($invocation, $callIndex); + } + } + + /** + * Verify a single invocation. + * + * @param int $callIndex + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + private function verifyInvocation(BaseInvocation $invocation, $callIndex): void + { + if (!isset($this->parameterGroups[$callIndex])) { + // no parameter assertion for this call index + return; + } + + $parameters = $this->parameterGroups[$callIndex]; + + if (count($invocation->getParameters()) < count($parameters)) { + throw new ExpectationFailedException( + sprintf( + 'Parameter count for invocation %s is too low.', + $invocation->toString(), + ), + ); + } + + foreach ($parameters as $i => $parameter) { + $parameter->evaluate( + $invocation->getParameters()[$i], + sprintf( + 'Parameter %s for invocation #%d %s does not match expected ' . + 'value.', + $i, + $callIndex, + $invocation->toString(), + ), + ); + } + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvocationOrder.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvocationOrder.php new file mode 100644 index 00000000..90aa4935 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvocationOrder.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Rule; + +use function count; +use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; +use PHPUnit\Framework\MockObject\Verifiable; +use PHPUnit\Framework\SelfDescribing; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract class InvocationOrder implements SelfDescribing, Verifiable +{ + /** + * @var BaseInvocation[] + */ + private $invocations = []; + + public function getInvocationCount(): int + { + return count($this->invocations); + } + + public function hasBeenInvoked(): bool + { + return count($this->invocations) > 0; + } + + final public function invoked(BaseInvocation $invocation) + { + $this->invocations[] = $invocation; + + return $this->invokedDo($invocation); + } + + abstract public function matches(BaseInvocation $invocation): bool; + + abstract protected function invokedDo(BaseInvocation $invocation); +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtIndex.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtIndex.php new file mode 100644 index 00000000..d56618cb --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtIndex.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Rule; + +use function sprintf; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4297 + * + * @codeCoverageIgnore + */ +final class InvokedAtIndex extends InvocationOrder +{ + /** + * @var int + */ + private $sequenceIndex; + + /** + * @var int + */ + private $currentIndex = -1; + + /** + * @param int $sequenceIndex + */ + public function __construct($sequenceIndex) + { + $this->sequenceIndex = $sequenceIndex; + } + + public function toString(): string + { + return 'invoked at sequence index ' . $this->sequenceIndex; + } + + public function matches(BaseInvocation $invocation): bool + { + $this->currentIndex++; + + return $this->currentIndex == $this->sequenceIndex; + } + + /** + * Verifies that the current expectation is valid. If everything is OK the + * code should just return, if not it must throw an exception. + * + * @throws ExpectationFailedException + */ + public function verify(): void + { + if ($this->currentIndex < $this->sequenceIndex) { + throw new ExpectationFailedException( + sprintf( + 'The expected invocation at index %s was never reached.', + $this->sequenceIndex, + ), + ); + } + } + + protected function invokedDo(BaseInvocation $invocation): void + { + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtLeastCount.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtLeastCount.php new file mode 100644 index 00000000..afc880e1 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtLeastCount.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Rule; + +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvokedAtLeastCount extends InvocationOrder +{ + /** + * @var int + */ + private $requiredInvocations; + + /** + * @param int $requiredInvocations + */ + public function __construct($requiredInvocations) + { + $this->requiredInvocations = $requiredInvocations; + } + + public function toString(): string + { + return 'invoked at least ' . $this->requiredInvocations . ' times'; + } + + /** + * Verifies that the current expectation is valid. If everything is OK the + * code should just return, if not it must throw an exception. + * + * @throws ExpectationFailedException + */ + public function verify(): void + { + $count = $this->getInvocationCount(); + + if ($count < $this->requiredInvocations) { + throw new ExpectationFailedException( + 'Expected invocation at least ' . $this->requiredInvocations . + ' times but it occurred ' . $count . ' time(s).', + ); + } + } + + public function matches(BaseInvocation $invocation): bool + { + return true; + } + + protected function invokedDo(BaseInvocation $invocation): void + { + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtLeastOnce.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtLeastOnce.php new file mode 100644 index 00000000..645ed309 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtLeastOnce.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Rule; + +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvokedAtLeastOnce extends InvocationOrder +{ + public function toString(): string + { + return 'invoked at least once'; + } + + /** + * Verifies that the current expectation is valid. If everything is OK the + * code should just return, if not it must throw an exception. + * + * @throws ExpectationFailedException + */ + public function verify(): void + { + $count = $this->getInvocationCount(); + + if ($count < 1) { + throw new ExpectationFailedException( + 'Expected invocation at least once but it never occurred.', + ); + } + } + + public function matches(BaseInvocation $invocation): bool + { + return true; + } + + protected function invokedDo(BaseInvocation $invocation): void + { + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtMostCount.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtMostCount.php new file mode 100644 index 00000000..df81a613 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtMostCount.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Rule; + +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvokedAtMostCount extends InvocationOrder +{ + /** + * @var int + */ + private $allowedInvocations; + + /** + * @param int $allowedInvocations + */ + public function __construct($allowedInvocations) + { + $this->allowedInvocations = $allowedInvocations; + } + + public function toString(): string + { + return 'invoked at most ' . $this->allowedInvocations . ' times'; + } + + /** + * Verifies that the current expectation is valid. If everything is OK the + * code should just return, if not it must throw an exception. + * + * @throws ExpectationFailedException + */ + public function verify(): void + { + $count = $this->getInvocationCount(); + + if ($count > $this->allowedInvocations) { + throw new ExpectationFailedException( + 'Expected invocation at most ' . $this->allowedInvocations . + ' times but it occurred ' . $count . ' time(s).', + ); + } + } + + public function matches(BaseInvocation $invocation): bool + { + return true; + } + + protected function invokedDo(BaseInvocation $invocation): void + { + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedCount.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedCount.php new file mode 100644 index 00000000..a962118e --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedCount.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Rule; + +use function sprintf; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvokedCount extends InvocationOrder +{ + /** + * @var int + */ + private $expectedCount; + + /** + * @param int $expectedCount + */ + public function __construct($expectedCount) + { + $this->expectedCount = $expectedCount; + } + + public function isNever(): bool + { + return $this->expectedCount === 0; + } + + public function toString(): string + { + return 'invoked ' . $this->expectedCount . ' time(s)'; + } + + public function matches(BaseInvocation $invocation): bool + { + return true; + } + + /** + * Verifies that the current expectation is valid. If everything is OK the + * code should just return, if not it must throw an exception. + * + * @throws ExpectationFailedException + */ + public function verify(): void + { + $count = $this->getInvocationCount(); + + if ($count !== $this->expectedCount) { + throw new ExpectationFailedException( + sprintf( + 'Method was expected to be called %d times, ' . + 'actually called %d times.', + $this->expectedCount, + $count, + ), + ); + } + } + + /** + * @throws ExpectationFailedException + */ + protected function invokedDo(BaseInvocation $invocation): void + { + $count = $this->getInvocationCount(); + + if ($count > $this->expectedCount) { + $message = $invocation->toString() . ' '; + + switch ($this->expectedCount) { + case 0: + $message .= 'was not expected to be called.'; + + break; + + case 1: + $message .= 'was not expected to be called more than once.'; + + break; + + default: + $message .= sprintf( + 'was not expected to be called more than %d times.', + $this->expectedCount, + ); + } + + throw new ExpectationFailedException($message); + } + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/MethodName.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/MethodName.php new file mode 100644 index 00000000..83ba3b8d --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/MethodName.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Rule; + +use function is_string; +use PHPUnit\Framework\Constraint\Constraint; +use PHPUnit\Framework\InvalidArgumentException; +use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; +use PHPUnit\Framework\MockObject\MethodNameConstraint; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MethodName +{ + /** + * @var Constraint + */ + private $constraint; + + /** + * @param Constraint|string $constraint + * + * @throws InvalidArgumentException + */ + public function __construct($constraint) + { + if (is_string($constraint)) { + $constraint = new MethodNameConstraint($constraint); + } + + if (!$constraint instanceof Constraint) { + throw InvalidArgumentException::create(1, 'PHPUnit\Framework\Constraint\Constraint object or string'); + } + + $this->constraint = $constraint; + } + + public function toString(): string + { + return 'method name ' . $this->constraint->toString(); + } + + /** + * @throws \PHPUnit\Framework\ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function matches(BaseInvocation $invocation): bool + { + return $this->matchesName($invocation->getMethodName()); + } + + /** + * @throws \PHPUnit\Framework\ExpectationFailedException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function matchesName(string $methodName): bool + { + return (bool) $this->constraint->evaluate($methodName, '', true); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/Parameters.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/Parameters.php new file mode 100644 index 00000000..cb71b271 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/Parameters.php @@ -0,0 +1,160 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Rule; + +use function count; +use function get_class; +use function sprintf; +use Exception; +use PHPUnit\Framework\Constraint\Constraint; +use PHPUnit\Framework\Constraint\IsAnything; +use PHPUnit\Framework\Constraint\IsEqual; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Parameters implements ParametersRule +{ + /** + * @var Constraint[] + */ + private $parameters = []; + + /** + * @var BaseInvocation + */ + private $invocation; + + /** + * @var bool|ExpectationFailedException + */ + private $parameterVerificationResult; + + /** + * @throws \PHPUnit\Framework\Exception + */ + public function __construct(array $parameters) + { + foreach ($parameters as $parameter) { + if (!($parameter instanceof Constraint)) { + $parameter = new IsEqual( + $parameter, + ); + } + + $this->parameters[] = $parameter; + } + } + + public function toString(): string + { + $text = 'with parameter'; + + foreach ($this->parameters as $index => $parameter) { + if ($index > 0) { + $text .= ' and'; + } + + $text .= ' ' . $index . ' ' . $parameter->toString(); + } + + return $text; + } + + /** + * @throws Exception + */ + public function apply(BaseInvocation $invocation): void + { + $this->invocation = $invocation; + $this->parameterVerificationResult = null; + + try { + $this->parameterVerificationResult = $this->doVerify(); + } catch (ExpectationFailedException $e) { + $this->parameterVerificationResult = $e; + + throw $this->parameterVerificationResult; + } + } + + /** + * Checks if the invocation $invocation matches the current rules. If it + * does the rule will get the invoked() method called which should check + * if an expectation is met. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + public function verify(): void + { + $this->doVerify(); + } + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws ExpectationFailedException + */ + private function doVerify(): bool + { + if (isset($this->parameterVerificationResult)) { + return $this->guardAgainstDuplicateEvaluationOfParameterConstraints(); + } + + if ($this->invocation === null) { + throw new ExpectationFailedException('Mocked method does not exist.'); + } + + if (count($this->invocation->getParameters()) < count($this->parameters)) { + $message = 'Parameter count for invocation %s is too low.'; + + // The user called `->with($this->anything())`, but may have meant + // `->withAnyParameters()`. + // + // @see https://github.com/sebastianbergmann/phpunit-mock-objects/issues/199 + if (count($this->parameters) === 1 && + get_class($this->parameters[0]) === IsAnything::class) { + $message .= "\nTo allow 0 or more parameters with any value, omit ->with() or use ->withAnyParameters() instead."; + } + + throw new ExpectationFailedException( + sprintf($message, $this->invocation->toString()), + ); + } + + foreach ($this->parameters as $i => $parameter) { + $parameter->evaluate( + $this->invocation->getParameters()[$i], + sprintf( + 'Parameter %s for invocation %s does not match expected ' . + 'value.', + $i, + $this->invocation->toString(), + ), + ); + } + + return true; + } + + /** + * @throws ExpectationFailedException + */ + private function guardAgainstDuplicateEvaluationOfParameterConstraints(): bool + { + if ($this->parameterVerificationResult instanceof ExpectationFailedException) { + throw $this->parameterVerificationResult; + } + + return (bool) $this->parameterVerificationResult; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/ParametersRule.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/ParametersRule.php new file mode 100644 index 00000000..70c47fe3 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/ParametersRule.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Rule; + +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; +use PHPUnit\Framework\MockObject\Verifiable; +use PHPUnit\Framework\SelfDescribing; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface ParametersRule extends SelfDescribing, Verifiable +{ + /** + * @throws ExpectationFailedException if the invocation violates the rule + */ + public function apply(BaseInvocation $invocation): void; + + public function verify(): void; +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Stub.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Stub.php new file mode 100644 index 00000000..2b032e2d --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Stub.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use PHPUnit\Framework\MockObject\Builder\InvocationStubber; + +/** + * @method InvocationStubber method($constraint) + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface Stub +{ + public function __phpunit_getInvocationHandler(): InvocationHandler; + + public function __phpunit_hasMatchers(): bool; + + public function __phpunit_setReturnValueGeneration(bool $returnValueGeneration): void; +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ConsecutiveCalls.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ConsecutiveCalls.php new file mode 100644 index 00000000..8b01656f --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ConsecutiveCalls.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Stub; + +use function array_shift; +use function sprintf; +use PHPUnit\Framework\MockObject\Invocation; +use SebastianBergmann\Exporter\Exporter; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ConsecutiveCalls implements Stub +{ + /** + * @var array + */ + private $stack; + + /** + * @var mixed + */ + private $value; + + public function __construct(array $stack) + { + $this->stack = $stack; + } + + public function invoke(Invocation $invocation) + { + $this->value = array_shift($this->stack); + + if ($this->value instanceof Stub) { + $this->value = $this->value->invoke($invocation); + } + + return $this->value; + } + + public function toString(): string + { + $exporter = new Exporter; + + return sprintf( + 'return user-specified value %s', + $exporter->export($this->value), + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/Exception.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/Exception.php new file mode 100644 index 00000000..aa9074eb --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/Exception.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Stub; + +use function sprintf; +use PHPUnit\Framework\MockObject\Invocation; +use SebastianBergmann\Exporter\Exporter; +use Throwable; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Exception implements Stub +{ + private $exception; + + public function __construct(Throwable $exception) + { + $this->exception = $exception; + } + + /** + * @throws Throwable + */ + public function invoke(Invocation $invocation): void + { + throw $this->exception; + } + + public function toString(): string + { + $exporter = new Exporter; + + return sprintf( + 'raise user-specified exception %s', + $exporter->export($this->exception), + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnArgument.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnArgument.php new file mode 100644 index 00000000..c7b3f8f4 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnArgument.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Stub; + +use function sprintf; +use PHPUnit\Framework\MockObject\Invocation; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ReturnArgument implements Stub +{ + /** + * @var int + */ + private $argumentIndex; + + public function __construct($argumentIndex) + { + $this->argumentIndex = $argumentIndex; + } + + public function invoke(Invocation $invocation) + { + if (isset($invocation->getParameters()[$this->argumentIndex])) { + return $invocation->getParameters()[$this->argumentIndex]; + } + } + + public function toString(): string + { + return sprintf('return argument #%d', $this->argumentIndex); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnCallback.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnCallback.php new file mode 100644 index 00000000..0f24aafc --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnCallback.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Stub; + +use function call_user_func_array; +use function get_class; +use function is_array; +use function is_object; +use function sprintf; +use PHPUnit\Framework\MockObject\Invocation; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ReturnCallback implements Stub +{ + private $callback; + + public function __construct($callback) + { + $this->callback = $callback; + } + + public function invoke(Invocation $invocation) + { + return call_user_func_array($this->callback, $invocation->getParameters()); + } + + public function toString(): string + { + if (is_array($this->callback)) { + if (is_object($this->callback[0])) { + $class = get_class($this->callback[0]); + $type = '->'; + } else { + $class = $this->callback[0]; + $type = '::'; + } + + return sprintf( + 'return result of user defined callback %s%s%s() with the ' . + 'passed arguments', + $class, + $type, + $this->callback[1], + ); + } + + return 'return result of user defined callback ' . $this->callback . + ' with the passed arguments'; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnReference.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnReference.php new file mode 100644 index 00000000..ea2bb735 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnReference.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Stub; + +use function sprintf; +use PHPUnit\Framework\MockObject\Invocation; +use SebastianBergmann\Exporter\Exporter; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ReturnReference implements Stub +{ + /** + * @var mixed + */ + private $reference; + + public function __construct(&$reference) + { + $this->reference = &$reference; + } + + public function invoke(Invocation $invocation) + { + return $this->reference; + } + + public function toString(): string + { + $exporter = new Exporter; + + return sprintf( + 'return user-specified reference %s', + $exporter->export($this->reference), + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnSelf.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnSelf.php new file mode 100644 index 00000000..6d2137bf --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnSelf.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Stub; + +use PHPUnit\Framework\MockObject\Invocation; +use PHPUnit\Framework\MockObject\RuntimeException; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ReturnSelf implements Stub +{ + /** + * @throws RuntimeException + */ + public function invoke(Invocation $invocation) + { + return $invocation->getObject(); + } + + public function toString(): string + { + return 'return the current object'; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnStub.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnStub.php new file mode 100644 index 00000000..4ecbc3b9 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnStub.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Stub; + +use function sprintf; +use PHPUnit\Framework\MockObject\Invocation; +use SebastianBergmann\Exporter\Exporter; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ReturnStub implements Stub +{ + /** + * @var mixed + */ + private $value; + + public function __construct($value) + { + $this->value = $value; + } + + public function invoke(Invocation $invocation) + { + return $this->value; + } + + public function toString(): string + { + $exporter = new Exporter; + + return sprintf( + 'return user-specified value %s', + $exporter->export($this->value), + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnValueMap.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnValueMap.php new file mode 100644 index 00000000..5fcd3a09 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnValueMap.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Stub; + +use function array_pop; +use function count; +use function is_array; +use PHPUnit\Framework\MockObject\Invocation; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ReturnValueMap implements Stub +{ + /** + * @var array + */ + private $valueMap; + + public function __construct(array $valueMap) + { + $this->valueMap = $valueMap; + } + + public function invoke(Invocation $invocation) + { + $parameterCount = count($invocation->getParameters()); + + foreach ($this->valueMap as $map) { + if (!is_array($map) || $parameterCount !== (count($map) - 1)) { + continue; + } + + $return = array_pop($map); + + if ($invocation->getParameters() === $map) { + return $return; + } + } + } + + public function toString(): string + { + return 'return value from a map'; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/Stub.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/Stub.php new file mode 100644 index 00000000..15cfce5c --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/Stub.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Stub; + +use PHPUnit\Framework\MockObject\Invocation; +use PHPUnit\Framework\SelfDescribing; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface Stub extends SelfDescribing +{ + /** + * Fakes the processing of the invocation $invocation by returning a + * specific value. + * + * @param Invocation $invocation The invocation which was mocked and matched by the current method and argument matchers + */ + public function invoke(Invocation $invocation); +} diff --git a/vendor/phpunit/phpunit/src/Framework/MockObject/Verifiable.php b/vendor/phpunit/phpunit/src/Framework/MockObject/Verifiable.php new file mode 100644 index 00000000..8c9a82c5 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/MockObject/Verifiable.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use PHPUnit\Framework\ExpectationFailedException; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface Verifiable +{ + /** + * Verifies that the current expectation is valid. If everything is OK the + * code should just return, if not it must throw an exception. + * + * @throws ExpectationFailedException + */ + public function verify(): void; +} diff --git a/vendor/phpunit/phpunit/src/Framework/Reorderable.php b/vendor/phpunit/phpunit/src/Framework/Reorderable.php new file mode 100644 index 00000000..34951f8d --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Reorderable.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface Reorderable +{ + public function sortId(): string; + + /** + * @return list + */ + public function provides(): array; + + /** + * @return list + */ + public function requires(): array; +} diff --git a/vendor/phpunit/phpunit/src/Framework/SelfDescribing.php b/vendor/phpunit/phpunit/src/Framework/SelfDescribing.php new file mode 100644 index 00000000..73034f65 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/SelfDescribing.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface SelfDescribing +{ + /** + * Returns a string representation of the object. + */ + public function toString(): string; +} diff --git a/vendor/phpunit/phpunit/src/Framework/SkippedTest.php b/vendor/phpunit/phpunit/src/Framework/SkippedTest.php new file mode 100644 index 00000000..a12aa402 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/SkippedTest.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use Throwable; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface SkippedTest extends Throwable +{ +} diff --git a/vendor/phpunit/phpunit/src/Framework/SkippedTestCase.php b/vendor/phpunit/phpunit/src/Framework/SkippedTestCase.php new file mode 100644 index 00000000..6f8a267c --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/SkippedTestCase.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class SkippedTestCase extends TestCase +{ + /** + * @var ?bool + */ + protected $backupGlobals = false; + + /** + * @var ?bool + */ + protected $backupStaticAttributes = false; + + /** + * @var ?bool + */ + protected $runTestInSeparateProcess = false; + + /** + * @var string + */ + private $message; + + public function __construct(string $className, string $methodName, string $message = '') + { + parent::__construct($className . '::' . $methodName); + + $this->message = $message; + } + + public function getMessage(): string + { + return $this->message; + } + + /** + * Returns a string representation of the test case. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function toString(): string + { + return $this->getName(); + } + + /** + * @throws Exception + */ + protected function runTest(): void + { + $this->markTestSkipped($this->message); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/Test.php b/vendor/phpunit/phpunit/src/Framework/Test.php new file mode 100644 index 00000000..be0dcd0e --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/Test.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use Countable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface Test extends Countable +{ + /** + * Runs a test and collects its result in a TestResult instance. + */ + public function run(TestResult $result = null): TestResult; +} diff --git a/vendor/phpunit/phpunit/src/Framework/TestBuilder.php b/vendor/phpunit/phpunit/src/Framework/TestBuilder.php new file mode 100644 index 00000000..77404df0 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/TestBuilder.php @@ -0,0 +1,239 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function assert; +use function count; +use function get_class; +use function sprintf; +use function trim; +use PHPUnit\Util\Filter; +use PHPUnit\Util\InvalidDataSetException; +use PHPUnit\Util\Test as TestUtil; +use ReflectionClass; +use Throwable; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TestBuilder +{ + public function build(ReflectionClass $theClass, string $methodName): Test + { + $className = $theClass->getName(); + + if (!$theClass->isInstantiable()) { + return new ErrorTestCase( + sprintf('Cannot instantiate class "%s".', $className), + ); + } + + $backupSettings = TestUtil::getBackupSettings( + $className, + $methodName, + ); + + $preserveGlobalState = TestUtil::getPreserveGlobalStateSettings( + $className, + $methodName, + ); + + $runTestInSeparateProcess = TestUtil::getProcessIsolationSettings( + $className, + $methodName, + ); + + $runClassInSeparateProcess = TestUtil::getClassProcessIsolationSettings( + $className, + $methodName, + ); + + $constructor = $theClass->getConstructor(); + + if ($constructor === null) { + throw new Exception('No valid test provided.'); + } + + $parameters = $constructor->getParameters(); + + // TestCase() or TestCase($name) + if (count($parameters) < 2) { + $test = $this->buildTestWithoutData($className); + } // TestCase($name, $data) + else { + try { + $data = TestUtil::getProvidedData( + $className, + $methodName, + ); + } catch (IncompleteTestError $e) { + $message = sprintf( + "Test for %s::%s marked incomplete by data provider\n%s", + $className, + $methodName, + $this->throwableToString($e), + ); + + $data = new IncompleteTestCase($className, $methodName, $message); + } catch (SkippedTestError $e) { + $message = sprintf( + "Test for %s::%s skipped by data provider\n%s", + $className, + $methodName, + $this->throwableToString($e), + ); + + $data = new SkippedTestCase($className, $methodName, $message); + } catch (Throwable $t) { + $message = sprintf( + "The data provider specified for %s::%s is invalid.\n%s", + $className, + $methodName, + $this->throwableToString($t), + ); + + $data = new ErrorTestCase($message); + } + + // Test method with @dataProvider. + if (isset($data)) { + $test = $this->buildDataProviderTestSuite( + $methodName, + $className, + $data, + $runTestInSeparateProcess, + $preserveGlobalState, + $runClassInSeparateProcess, + $backupSettings, + ); + } else { + $test = $this->buildTestWithoutData($className); + } + } + + if ($test instanceof TestCase) { + $test->setName($methodName); + $this->configureTestCase( + $test, + $runTestInSeparateProcess, + $preserveGlobalState, + $runClassInSeparateProcess, + $backupSettings, + ); + } + + return $test; + } + + /** @psalm-param class-string $className */ + private function buildTestWithoutData(string $className) + { + return new $className; + } + + /** @psalm-param class-string $className */ + private function buildDataProviderTestSuite( + string $methodName, + string $className, + $data, + bool $runTestInSeparateProcess, + ?bool $preserveGlobalState, + bool $runClassInSeparateProcess, + array $backupSettings + ): DataProviderTestSuite { + $dataProviderTestSuite = new DataProviderTestSuite( + $className . '::' . $methodName, + ); + + $groups = TestUtil::getGroups($className, $methodName); + + if ($data instanceof ErrorTestCase || + $data instanceof SkippedTestCase || + $data instanceof IncompleteTestCase) { + $dataProviderTestSuite->addTest($data, $groups); + } else { + foreach ($data as $_dataName => $_data) { + $_test = new $className($methodName, $_data, $_dataName); + + assert($_test instanceof TestCase); + + $this->configureTestCase( + $_test, + $runTestInSeparateProcess, + $preserveGlobalState, + $runClassInSeparateProcess, + $backupSettings, + ); + + $dataProviderTestSuite->addTest($_test, $groups); + } + } + + return $dataProviderTestSuite; + } + + private function configureTestCase( + TestCase $test, + bool $runTestInSeparateProcess, + ?bool $preserveGlobalState, + bool $runClassInSeparateProcess, + array $backupSettings + ): void { + if ($runTestInSeparateProcess) { + $test->setRunTestInSeparateProcess(true); + + if ($preserveGlobalState !== null) { + $test->setPreserveGlobalState($preserveGlobalState); + } + } + + if ($runClassInSeparateProcess) { + $test->setRunClassInSeparateProcess(true); + + if ($preserveGlobalState !== null) { + $test->setPreserveGlobalState($preserveGlobalState); + } + } + + if ($backupSettings['backupGlobals'] !== null) { + $test->setBackupGlobals($backupSettings['backupGlobals']); + } + + if ($backupSettings['backupStaticAttributes'] !== null) { + $test->setBackupStaticAttributes( + $backupSettings['backupStaticAttributes'], + ); + } + } + + private function throwableToString(Throwable $t): string + { + $message = $t->getMessage(); + + if (empty(trim($message))) { + $message = ''; + } + + if ($t instanceof InvalidDataSetException) { + return sprintf( + "%s\n%s", + $message, + Filter::getFilteredStacktrace($t), + ); + } + + return sprintf( + "%s: %s\n%s", + get_class($t), + $message, + Filter::getFilteredStacktrace($t), + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/TestCase.php b/vendor/phpunit/phpunit/src/Framework/TestCase.php new file mode 100644 index 00000000..acb5cf40 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/TestCase.php @@ -0,0 +1,2673 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use const LC_ALL; +use const LC_COLLATE; +use const LC_CTYPE; +use const LC_MONETARY; +use const LC_NUMERIC; +use const LC_TIME; +use const PATHINFO_FILENAME; +use const PHP_EOL; +use const PHP_URL_PATH; +use function array_filter; +use function array_flip; +use function array_keys; +use function array_merge; +use function array_pop; +use function array_search; +use function array_unique; +use function array_values; +use function basename; +use function call_user_func; +use function chdir; +use function class_exists; +use function clearstatcache; +use function count; +use function debug_backtrace; +use function defined; +use function explode; +use function get_class; +use function get_include_path; +use function getcwd; +use function implode; +use function in_array; +use function ini_set; +use function is_array; +use function is_callable; +use function is_int; +use function is_object; +use function is_string; +use function libxml_clear_errors; +use function method_exists; +use function ob_end_clean; +use function ob_get_contents; +use function ob_get_level; +use function ob_start; +use function parse_url; +use function pathinfo; +use function preg_replace; +use function serialize; +use function setlocale; +use function sprintf; +use function strpos; +use function substr; +use function sys_get_temp_dir; +use function tempnam; +use function trim; +use function var_export; +use DeepCopy\DeepCopy; +use PHPUnit\Framework\Constraint\Exception as ExceptionConstraint; +use PHPUnit\Framework\Constraint\ExceptionCode; +use PHPUnit\Framework\Constraint\ExceptionMessage; +use PHPUnit\Framework\Constraint\ExceptionMessageRegularExpression; +use PHPUnit\Framework\Constraint\LogicalOr; +use PHPUnit\Framework\Error\Deprecated; +use PHPUnit\Framework\Error\Error; +use PHPUnit\Framework\Error\Notice; +use PHPUnit\Framework\Error\Warning as WarningError; +use PHPUnit\Framework\MockObject\Generator as MockGenerator; +use PHPUnit\Framework\MockObject\MockBuilder; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\MockObject\Rule\AnyInvokedCount as AnyInvokedCountMatcher; +use PHPUnit\Framework\MockObject\Rule\InvokedAtIndex as InvokedAtIndexMatcher; +use PHPUnit\Framework\MockObject\Rule\InvokedAtLeastCount as InvokedAtLeastCountMatcher; +use PHPUnit\Framework\MockObject\Rule\InvokedAtLeastOnce as InvokedAtLeastOnceMatcher; +use PHPUnit\Framework\MockObject\Rule\InvokedAtMostCount as InvokedAtMostCountMatcher; +use PHPUnit\Framework\MockObject\Rule\InvokedCount as InvokedCountMatcher; +use PHPUnit\Framework\MockObject\Stub; +use PHPUnit\Framework\MockObject\Stub\ConsecutiveCalls as ConsecutiveCallsStub; +use PHPUnit\Framework\MockObject\Stub\Exception as ExceptionStub; +use PHPUnit\Framework\MockObject\Stub\ReturnArgument as ReturnArgumentStub; +use PHPUnit\Framework\MockObject\Stub\ReturnCallback as ReturnCallbackStub; +use PHPUnit\Framework\MockObject\Stub\ReturnSelf as ReturnSelfStub; +use PHPUnit\Framework\MockObject\Stub\ReturnStub; +use PHPUnit\Framework\MockObject\Stub\ReturnValueMap as ReturnValueMapStub; +use PHPUnit\Runner\BaseTestRunner; +use PHPUnit\Runner\PhptTestCase; +use PHPUnit\Util\Cloner; +use PHPUnit\Util\Exception as UtilException; +use PHPUnit\Util\GlobalState; +use PHPUnit\Util\PHP\AbstractPhpProcess; +use PHPUnit\Util\Test as TestUtil; +use Prophecy\Exception\Prediction\PredictionException; +use Prophecy\Prophecy\MethodProphecy; +use Prophecy\Prophecy\ObjectProphecy; +use Prophecy\Prophet; +use ReflectionClass; +use ReflectionException; +use SebastianBergmann\Comparator\Comparator; +use SebastianBergmann\Comparator\Factory as ComparatorFactory; +use SebastianBergmann\Diff\Differ; +use SebastianBergmann\Exporter\Exporter; +use SebastianBergmann\GlobalState\ExcludeList; +use SebastianBergmann\GlobalState\Restorer; +use SebastianBergmann\GlobalState\Snapshot; +use SebastianBergmann\ObjectEnumerator\Enumerator; +use SebastianBergmann\Template\Template; +use SoapClient; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +abstract class TestCase extends Assert implements Reorderable, SelfDescribing, Test +{ + private const LOCALE_CATEGORIES = [LC_ALL, LC_COLLATE, LC_CTYPE, LC_MONETARY, LC_NUMERIC, LC_TIME]; + + /** + * @var ?bool + */ + protected $backupGlobals; + + /** + * @var string[] + */ + protected $backupGlobalsExcludeList = []; + + /** + * @var string[] + * + * @deprecated Use $backupGlobalsExcludeList instead + */ + protected $backupGlobalsBlacklist = []; + + /** + * @var ?bool + */ + protected $backupStaticAttributes; + + /** + * @var array> + */ + protected $backupStaticAttributesExcludeList = []; + + /** + * @var array> + * + * @deprecated Use $backupStaticAttributesExcludeList instead + */ + protected $backupStaticAttributesBlacklist = []; + + /** + * @var ?bool + */ + protected $runTestInSeparateProcess; + + /** + * @var bool + */ + protected $preserveGlobalState = true; + + /** + * @var list + */ + protected $providedTests = []; + + /** + * @var ?bool + */ + private $runClassInSeparateProcess; + + /** + * @var bool + */ + private $inIsolation = false; + + /** + * @var array + */ + private $data; + + /** + * @var int|string + */ + private $dataName; + + /** + * @var null|string + */ + private $expectedException; + + /** + * @var null|string + */ + private $expectedExceptionMessage; + + /** + * @var null|string + */ + private $expectedExceptionMessageRegExp; + + /** + * @var null|int|string + */ + private $expectedExceptionCode; + + /** + * @var string + */ + private $name = ''; + + /** + * @var list + */ + private $dependencies = []; + + /** + * @var array + */ + private $dependencyInput = []; + + /** + * @var array + */ + private $iniSettings = []; + + /** + * @var array + */ + private $locale = []; + + /** + * @var MockObject[] + */ + private $mockObjects = []; + + /** + * @var MockGenerator + */ + private $mockObjectGenerator; + + /** + * @var int + */ + private $status = BaseTestRunner::STATUS_UNKNOWN; + + /** + * @var string + */ + private $statusMessage = ''; + + /** + * @var int + */ + private $numAssertions = 0; + + /** + * @var TestResult + */ + private $result; + + /** + * @var mixed + */ + private $testResult; + + /** + * @var string + */ + private $output = ''; + + /** + * @var ?string + */ + private $outputExpectedRegex; + + /** + * @var ?string + */ + private $outputExpectedString; + + /** + * @var mixed + */ + private $outputCallback = false; + + /** + * @var bool + */ + private $outputBufferingActive = false; + + /** + * @var int + */ + private $outputBufferingLevel; + + /** + * @var bool + */ + private $outputRetrievedForAssertion = false; + + /** + * @var ?Snapshot + */ + private $snapshot; + + /** + * @var Prophet + */ + private $prophet; + + /** + * @var bool + */ + private $beStrictAboutChangesToGlobalState = false; + + /** + * @var bool + */ + private $registerMockObjectsFromTestArgumentsRecursively = false; + + /** + * @var string[] + */ + private $warnings = []; + + /** + * @var string[] + */ + private $groups = []; + + /** + * @var bool + */ + private $doesNotPerformAssertions = false; + + /** + * @var Comparator[] + */ + private $customComparators = []; + + /** + * @var string[] + */ + private $doubledTypes = []; + + /** + * Returns a matcher that matches when the method is executed + * zero or more times. + */ + public static function any(): AnyInvokedCountMatcher + { + return new AnyInvokedCountMatcher; + } + + /** + * Returns a matcher that matches when the method is never executed. + */ + public static function never(): InvokedCountMatcher + { + return new InvokedCountMatcher(0); + } + + /** + * Returns a matcher that matches when the method is executed + * at least N times. + */ + public static function atLeast(int $requiredInvocations): InvokedAtLeastCountMatcher + { + return new InvokedAtLeastCountMatcher( + $requiredInvocations, + ); + } + + /** + * Returns a matcher that matches when the method is executed at least once. + */ + public static function atLeastOnce(): InvokedAtLeastOnceMatcher + { + return new InvokedAtLeastOnceMatcher; + } + + /** + * Returns a matcher that matches when the method is executed exactly once. + */ + public static function once(): InvokedCountMatcher + { + return new InvokedCountMatcher(1); + } + + /** + * Returns a matcher that matches when the method is executed + * exactly $count times. + */ + public static function exactly(int $count): InvokedCountMatcher + { + return new InvokedCountMatcher($count); + } + + /** + * Returns a matcher that matches when the method is executed + * at most N times. + */ + public static function atMost(int $allowedInvocations): InvokedAtMostCountMatcher + { + return new InvokedAtMostCountMatcher($allowedInvocations); + } + + /** + * Returns a matcher that matches when the method is executed + * at the given index. + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4297 + * + * @codeCoverageIgnore + */ + public static function at(int $index): InvokedAtIndexMatcher + { + $stack = debug_backtrace(); + + while (!empty($stack)) { + $frame = array_pop($stack); + + if (isset($frame['object']) && $frame['object'] instanceof self) { + $frame['object']->addWarning( + 'The at() matcher has been deprecated. It will be removed in PHPUnit 10. Please refactor your test to not rely on the order in which methods are invoked.', + ); + + break; + } + } + + return new InvokedAtIndexMatcher($index); + } + + public static function returnValue($value): ReturnStub + { + return new ReturnStub($value); + } + + public static function returnValueMap(array $valueMap): ReturnValueMapStub + { + return new ReturnValueMapStub($valueMap); + } + + public static function returnArgument(int $argumentIndex): ReturnArgumentStub + { + return new ReturnArgumentStub($argumentIndex); + } + + public static function returnCallback($callback): ReturnCallbackStub + { + return new ReturnCallbackStub($callback); + } + + /** + * Returns the current object. + * + * This method is useful when mocking a fluent interface. + */ + public static function returnSelf(): ReturnSelfStub + { + return new ReturnSelfStub; + } + + public static function throwException(Throwable $exception): ExceptionStub + { + return new ExceptionStub($exception); + } + + public static function onConsecutiveCalls(...$args): ConsecutiveCallsStub + { + return new ConsecutiveCallsStub($args); + } + + /** + * @param int|string $dataName + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function __construct(?string $name = null, array $data = [], $dataName = '') + { + if ($name !== null) { + $this->setName($name); + } + + $this->data = $data; + $this->dataName = $dataName; + } + + /** + * This method is called before the first test of this test class is run. + */ + public static function setUpBeforeClass(): void + { + } + + /** + * This method is called after the last test of this test class is run. + */ + public static function tearDownAfterClass(): void + { + } + + /** + * This method is called before each test. + */ + protected function setUp(): void + { + } + + /** + * Performs assertions shared by all tests of a test case. + * + * This method is called between setUp() and test. + */ + protected function assertPreConditions(): void + { + } + + /** + * Performs assertions shared by all tests of a test case. + * + * This method is called between test and tearDown(). + */ + protected function assertPostConditions(): void + { + } + + /** + * This method is called after each test. + */ + protected function tearDown(): void + { + } + + /** + * Returns a string representation of the test case. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + */ + public function toString(): string + { + try { + $class = new ReflectionClass($this); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + $buffer = sprintf( + '%s::%s', + $class->name, + $this->getName(false), + ); + + return $buffer . $this->getDataSetAsString(); + } + + public function count(): int + { + return 1; + } + + public function getActualOutputForAssertion(): string + { + $this->outputRetrievedForAssertion = true; + + return $this->getActualOutput(); + } + + public function expectOutputRegex(string $expectedRegex): void + { + $this->outputExpectedRegex = $expectedRegex; + } + + public function expectOutputString(string $expectedString): void + { + $this->outputExpectedString = $expectedString; + } + + /** + * @psalm-param class-string<\Throwable> $exception + */ + public function expectException(string $exception): void + { + // @codeCoverageIgnoreStart + switch ($exception) { + case Deprecated::class: + $this->addWarning('Expecting E_DEPRECATED and E_USER_DEPRECATED is deprecated and will no longer be possible in PHPUnit 10.'); + + break; + + case Error::class: + $this->addWarning('Expecting E_ERROR and E_USER_ERROR is deprecated and will no longer be possible in PHPUnit 10.'); + + break; + + case Notice::class: + $this->addWarning('Expecting E_STRICT, E_NOTICE, and E_USER_NOTICE is deprecated and will no longer be possible in PHPUnit 10.'); + + break; + + case WarningError::class: + $this->addWarning('Expecting E_WARNING and E_USER_WARNING is deprecated and will no longer be possible in PHPUnit 10.'); + + break; + } + // @codeCoverageIgnoreEnd + + $this->expectedException = $exception; + } + + /** + * @param int|string $code + */ + public function expectExceptionCode($code): void + { + $this->expectedExceptionCode = $code; + } + + public function expectExceptionMessage(string $message): void + { + $this->expectedExceptionMessage = $message; + } + + public function expectExceptionMessageMatches(string $regularExpression): void + { + $this->expectedExceptionMessageRegExp = $regularExpression; + } + + /** + * Sets up an expectation for an exception to be raised by the code under test. + * Information for expected exception class, expected exception message, and + * expected exception code are retrieved from a given Exception object. + */ + public function expectExceptionObject(\Exception $exception): void + { + $this->expectException(get_class($exception)); + $this->expectExceptionMessage($exception->getMessage()); + $this->expectExceptionCode($exception->getCode()); + } + + public function expectNotToPerformAssertions(): void + { + $this->doesNotPerformAssertions = true; + } + + /** + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/5062 + */ + public function expectDeprecation(): void + { + $this->addWarning('Expecting E_DEPRECATED and E_USER_DEPRECATED is deprecated and will no longer be possible in PHPUnit 10.'); + + $this->expectedException = Deprecated::class; + } + + /** + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/5062 + */ + public function expectDeprecationMessage(string $message): void + { + $this->addWarning('Expecting E_DEPRECATED and E_USER_DEPRECATED is deprecated and will no longer be possible in PHPUnit 10.'); + + $this->expectExceptionMessage($message); + } + + /** + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/5062 + */ + public function expectDeprecationMessageMatches(string $regularExpression): void + { + $this->addWarning('Expecting E_DEPRECATED and E_USER_DEPRECATED is deprecated and will no longer be possible in PHPUnit 10.'); + + $this->expectExceptionMessageMatches($regularExpression); + } + + /** + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/5062 + */ + public function expectNotice(): void + { + $this->addWarning('Expecting E_STRICT, E_NOTICE, and E_USER_NOTICE is deprecated and will no longer be possible in PHPUnit 10.'); + + $this->expectedException = Notice::class; + } + + /** + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/5062 + */ + public function expectNoticeMessage(string $message): void + { + $this->addWarning('Expecting E_STRICT, E_NOTICE, and E_USER_NOTICE is deprecated and will no longer be possible in PHPUnit 10.'); + + $this->expectExceptionMessage($message); + } + + /** + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/5062 + */ + public function expectNoticeMessageMatches(string $regularExpression): void + { + $this->addWarning('Expecting E_STRICT, E_NOTICE, and E_USER_NOTICE is deprecated and will no longer be possible in PHPUnit 10.'); + + $this->expectExceptionMessageMatches($regularExpression); + } + + /** + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/5062 + */ + public function expectWarning(): void + { + $this->addWarning('Expecting E_WARNING and E_USER_WARNING is deprecated and will no longer be possible in PHPUnit 10.'); + + $this->expectedException = WarningError::class; + } + + /** + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/5062 + */ + public function expectWarningMessage(string $message): void + { + $this->addWarning('Expecting E_WARNING and E_USER_WARNING is deprecated and will no longer be possible in PHPUnit 10.'); + + $this->expectExceptionMessage($message); + } + + /** + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/5062 + */ + public function expectWarningMessageMatches(string $regularExpression): void + { + $this->addWarning('Expecting E_WARNING and E_USER_WARNING is deprecated and will no longer be possible in PHPUnit 10.'); + + $this->expectExceptionMessageMatches($regularExpression); + } + + /** + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/5062 + */ + public function expectError(): void + { + $this->addWarning('Expecting E_ERROR and E_USER_ERROR is deprecated and will no longer be possible in PHPUnit 10.'); + + $this->expectedException = Error::class; + } + + /** + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/5062 + */ + public function expectErrorMessage(string $message): void + { + $this->addWarning('Expecting E_ERROR and E_USER_ERROR is deprecated and will no longer be possible in PHPUnit 10.'); + + $this->expectExceptionMessage($message); + } + + /** + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/5062 + */ + public function expectErrorMessageMatches(string $regularExpression): void + { + $this->addWarning('Expecting E_ERROR and E_USER_ERROR is deprecated and will no longer be possible in PHPUnit 10.'); + + $this->expectExceptionMessageMatches($regularExpression); + } + + public function getStatus(): int + { + return $this->status; + } + + public function markAsRisky(): void + { + $this->status = BaseTestRunner::STATUS_RISKY; + } + + public function getStatusMessage(): string + { + return $this->statusMessage; + } + + public function hasFailed(): bool + { + $status = $this->getStatus(); + + return $status === BaseTestRunner::STATUS_FAILURE || $status === BaseTestRunner::STATUS_ERROR; + } + + /** + * Runs the test case and collects the results in a TestResult object. + * If no TestResult object is passed a new one will be created. + * + * @throws \SebastianBergmann\CodeCoverage\InvalidArgumentException + * @throws \SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws CodeCoverageException + * @throws UtilException + */ + public function run(TestResult $result = null): TestResult + { + if ($result === null) { + $result = $this->createResult(); + } + + if (!$this instanceof ErrorTestCase && !$this instanceof WarningTestCase) { + $this->setTestResultObject($result); + } + + if (!$this instanceof ErrorTestCase && + !$this instanceof WarningTestCase && + !$this instanceof SkippedTestCase && + !$this->handleDependencies()) { + return $result; + } + + if ($this->runInSeparateProcess()) { + $runEntireClass = $this->runClassInSeparateProcess && !$this->runTestInSeparateProcess; + + try { + $class = new ReflectionClass($this); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + if ($runEntireClass) { + $template = new Template( + __DIR__ . '/../Util/PHP/Template/TestCaseClass.tpl', + ); + } else { + $template = new Template( + __DIR__ . '/../Util/PHP/Template/TestCaseMethod.tpl', + ); + } + + if ($this->preserveGlobalState) { + $constants = GlobalState::getConstantsAsString(); + $globals = GlobalState::getGlobalsAsString(); + $includedFiles = GlobalState::getIncludedFilesAsString(); + $iniSettings = GlobalState::getIniSettingsAsString(); + } else { + $constants = ''; + + if (!empty($GLOBALS['__PHPUNIT_BOOTSTRAP'])) { + $globals = '$GLOBALS[\'__PHPUNIT_BOOTSTRAP\'] = ' . var_export($GLOBALS['__PHPUNIT_BOOTSTRAP'], true) . ";\n"; + } else { + $globals = ''; + } + + $includedFiles = ''; + $iniSettings = ''; + } + + $coverage = $result->getCollectCodeCoverageInformation() ? 'true' : 'false'; + $isStrictAboutTestsThatDoNotTestAnything = $result->isStrictAboutTestsThatDoNotTestAnything() ? 'true' : 'false'; + $isStrictAboutOutputDuringTests = $result->isStrictAboutOutputDuringTests() ? 'true' : 'false'; + $enforcesTimeLimit = $result->enforcesTimeLimit() ? 'true' : 'false'; + $isStrictAboutTodoAnnotatedTests = $result->isStrictAboutTodoAnnotatedTests() ? 'true' : 'false'; + $isStrictAboutResourceUsageDuringSmallTests = $result->isStrictAboutResourceUsageDuringSmallTests() ? 'true' : 'false'; + + if (defined('PHPUNIT_COMPOSER_INSTALL')) { + $composerAutoload = var_export(PHPUNIT_COMPOSER_INSTALL, true); + } else { + $composerAutoload = '\'\''; + } + + if (defined('__PHPUNIT_PHAR__')) { + $phar = var_export(__PHPUNIT_PHAR__, true); + } else { + $phar = '\'\''; + } + + $codeCoverage = $result->getCodeCoverage(); + $codeCoverageFilter = null; + $cachesStaticAnalysis = 'false'; + $codeCoverageCacheDirectory = null; + $driverMethod = 'forLineCoverage'; + + if ($codeCoverage) { + $codeCoverageFilter = $codeCoverage->filter(); + + if ($codeCoverage->collectsBranchAndPathCoverage()) { + $driverMethod = 'forLineAndPathCoverage'; + } + + if ($codeCoverage->cachesStaticAnalysis()) { + $cachesStaticAnalysis = 'true'; + $codeCoverageCacheDirectory = $codeCoverage->cacheDirectory(); + } + } + + $data = var_export(serialize($this->data), true); + $dataName = var_export($this->dataName, true); + $dependencyInput = var_export(serialize($this->dependencyInput), true); + $includePath = var_export(get_include_path(), true); + $codeCoverageFilter = var_export(serialize($codeCoverageFilter), true); + $codeCoverageCacheDirectory = var_export(serialize($codeCoverageCacheDirectory), true); + // must do these fixes because TestCaseMethod.tpl has unserialize('{data}') in it, and we can't break BC + // the lines above used to use addcslashes() rather than var_export(), which breaks null byte escape sequences + $data = "'." . $data . ".'"; + $dataName = "'.(" . $dataName . ").'"; + $dependencyInput = "'." . $dependencyInput . ".'"; + $includePath = "'." . $includePath . ".'"; + $codeCoverageFilter = "'." . $codeCoverageFilter . ".'"; + $codeCoverageCacheDirectory = "'." . $codeCoverageCacheDirectory . ".'"; + + $configurationFilePath = $GLOBALS['__PHPUNIT_CONFIGURATION_FILE'] ?? ''; + $processResultFile = tempnam(sys_get_temp_dir(), 'phpunit_'); + + $var = [ + 'composerAutoload' => $composerAutoload, + 'phar' => $phar, + 'filename' => $class->getFileName(), + 'className' => $class->getName(), + 'collectCodeCoverageInformation' => $coverage, + 'cachesStaticAnalysis' => $cachesStaticAnalysis, + 'codeCoverageCacheDirectory' => $codeCoverageCacheDirectory, + 'driverMethod' => $driverMethod, + 'data' => $data, + 'dataName' => $dataName, + 'dependencyInput' => $dependencyInput, + 'constants' => $constants, + 'globals' => $globals, + 'include_path' => $includePath, + 'included_files' => $includedFiles, + 'iniSettings' => $iniSettings, + 'isStrictAboutTestsThatDoNotTestAnything' => $isStrictAboutTestsThatDoNotTestAnything, + 'isStrictAboutOutputDuringTests' => $isStrictAboutOutputDuringTests, + 'enforcesTimeLimit' => $enforcesTimeLimit, + 'isStrictAboutTodoAnnotatedTests' => $isStrictAboutTodoAnnotatedTests, + 'isStrictAboutResourceUsageDuringSmallTests' => $isStrictAboutResourceUsageDuringSmallTests, + 'codeCoverageFilter' => $codeCoverageFilter, + 'configurationFilePath' => $configurationFilePath, + 'name' => $this->getName(false), + 'processResultFile' => $processResultFile, + ]; + + if (!$runEntireClass) { + $var['methodName'] = $this->name; + } + + $template->setVar($var); + + $php = AbstractPhpProcess::factory(); + $php->runTestJob($template->render(), $this, $result, $processResultFile); + } else { + $result->run($this); + } + + $this->result = null; + + return $result; + } + + /** + * Returns a builder object to create mock objects using a fluent interface. + * + * @psalm-template RealInstanceType of object + * + * @psalm-param class-string $className + * + * @psalm-return MockBuilder + */ + public function getMockBuilder(string $className): MockBuilder + { + $this->recordDoubledType($className); + + return new MockBuilder($this, $className); + } + + public function registerComparator(Comparator $comparator): void + { + ComparatorFactory::getInstance()->register($comparator); + + $this->customComparators[] = $comparator; + } + + /** + * @return string[] + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function doubledTypes(): array + { + return array_unique($this->doubledTypes); + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function getGroups(): array + { + return $this->groups; + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function setGroups(array $groups): void + { + $this->groups = $groups; + } + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function getName(bool $withDataSet = true): string + { + if ($withDataSet) { + return $this->name . $this->getDataSetAsString(false); + } + + return $this->name; + } + + /** + * Returns the size of the test. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function getSize(): int + { + return TestUtil::getSize( + static::class, + $this->getName(false), + ); + } + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function hasSize(): bool + { + return $this->getSize() !== TestUtil::UNKNOWN; + } + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function isSmall(): bool + { + return $this->getSize() === TestUtil::SMALL; + } + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function isMedium(): bool + { + return $this->getSize() === TestUtil::MEDIUM; + } + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function isLarge(): bool + { + return $this->getSize() === TestUtil::LARGE; + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function getActualOutput(): string + { + if (!$this->outputBufferingActive) { + return $this->output; + } + + return (string) ob_get_contents(); + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function hasOutput(): bool + { + if ($this->output === '') { + return false; + } + + if ($this->hasExpectationOnOutput()) { + return false; + } + + return true; + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function doesNotPerformAssertions(): bool + { + return $this->doesNotPerformAssertions; + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function hasExpectationOnOutput(): bool + { + return is_string($this->outputExpectedString) || is_string($this->outputExpectedRegex) || $this->outputRetrievedForAssertion; + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function getExpectedException(): ?string + { + return $this->expectedException; + } + + /** + * @return null|int|string + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function getExpectedExceptionCode() + { + return $this->expectedExceptionCode; + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function getExpectedExceptionMessage(): ?string + { + return $this->expectedExceptionMessage; + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function getExpectedExceptionMessageRegExp(): ?string + { + return $this->expectedExceptionMessageRegExp; + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function setRegisterMockObjectsFromTestArgumentsRecursively(bool $flag): void + { + $this->registerMockObjectsFromTestArgumentsRecursively = $flag; + } + + /** + * @throws Throwable + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function runBare(): void + { + $this->numAssertions = 0; + + $this->snapshotGlobalState(); + $this->startOutputBuffering(); + clearstatcache(); + $currentWorkingDirectory = getcwd(); + + $hookMethods = TestUtil::getHookMethods(static::class); + + $hasMetRequirements = false; + + try { + $this->checkRequirements(); + $hasMetRequirements = true; + + if ($this->inIsolation) { + foreach ($hookMethods['beforeClass'] as $method) { + $this->{$method}(); + } + } + + $this->setDoesNotPerformAssertionsFromAnnotation(); + + foreach ($hookMethods['before'] as $method) { + $this->{$method}(); + } + + foreach ($hookMethods['preCondition'] as $method) { + $this->{$method}(); + } + + $this->testResult = $this->runTest(); + $this->verifyMockObjects(); + + foreach ($hookMethods['postCondition'] as $method) { + $this->{$method}(); + } + + if (!empty($this->warnings)) { + throw new Warning( + implode( + "\n", + array_unique($this->warnings), + ), + ); + } + + $this->status = BaseTestRunner::STATUS_PASSED; + } catch (IncompleteTest $e) { + $this->status = BaseTestRunner::STATUS_INCOMPLETE; + $this->statusMessage = $e->getMessage(); + } catch (SkippedTest $e) { + $this->status = BaseTestRunner::STATUS_SKIPPED; + $this->statusMessage = $e->getMessage(); + } catch (Warning $e) { + $this->status = BaseTestRunner::STATUS_WARNING; + $this->statusMessage = $e->getMessage(); + } catch (AssertionFailedError $e) { + $this->status = BaseTestRunner::STATUS_FAILURE; + $this->statusMessage = $e->getMessage(); + } catch (PredictionException $e) { + $this->status = BaseTestRunner::STATUS_FAILURE; + $this->statusMessage = $e->getMessage(); + } catch (Throwable $_e) { + $e = $_e; + $this->status = BaseTestRunner::STATUS_ERROR; + $this->statusMessage = $_e->getMessage(); + } + + $this->mockObjects = []; + $this->prophet = null; + + // Tear down the fixture. An exception raised in tearDown() will be + // caught and passed on when no exception was raised before. + try { + if ($hasMetRequirements) { + foreach ($hookMethods['after'] as $method) { + $this->{$method}(); + } + + if ($this->inIsolation) { + foreach ($hookMethods['afterClass'] as $method) { + $this->{$method}(); + } + } + } + } catch (Throwable $_e) { + $e = $e ?? $_e; + } + + try { + $this->stopOutputBuffering(); + } catch (RiskyTestError $_e) { + $e = $e ?? $_e; + } + + if (isset($_e)) { + $this->status = BaseTestRunner::STATUS_ERROR; + $this->statusMessage = $_e->getMessage(); + } + + clearstatcache(); + + if ($currentWorkingDirectory !== getcwd()) { + chdir($currentWorkingDirectory); + } + + $this->restoreGlobalState(); + $this->unregisterCustomComparators(); + $this->cleanupIniSettings(); + $this->cleanupLocaleSettings(); + libxml_clear_errors(); + + // Perform assertion on output. + if (!isset($e)) { + try { + if ($this->outputExpectedRegex !== null) { + $this->assertMatchesRegularExpression($this->outputExpectedRegex, $this->output); + } elseif ($this->outputExpectedString !== null) { + $this->assertEquals($this->outputExpectedString, $this->output); + } + } catch (Throwable $_e) { + $e = $_e; + } + } + + // Workaround for missing "finally". + if (isset($e)) { + if ($e instanceof PredictionException) { + $e = new AssertionFailedError($e->getMessage()); + } + + $this->onNotSuccessfulTest($e); + } + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function setName(string $name): void + { + $this->name = $name; + + if (is_callable($this->sortId(), true)) { + $this->providedTests = [new ExecutionOrderDependency($this->sortId())]; + } + } + + /** + * @param list $dependencies + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function setDependencies(array $dependencies): void + { + $this->dependencies = $dependencies; + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function setDependencyInput(array $dependencyInput): void + { + $this->dependencyInput = $dependencyInput; + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function setBeStrictAboutChangesToGlobalState(?bool $beStrictAboutChangesToGlobalState): void + { + $this->beStrictAboutChangesToGlobalState = $beStrictAboutChangesToGlobalState; + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function setBackupGlobals(?bool $backupGlobals): void + { + if ($this->backupGlobals === null && $backupGlobals !== null) { + $this->backupGlobals = $backupGlobals; + } + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function setBackupStaticAttributes(?bool $backupStaticAttributes): void + { + if ($this->backupStaticAttributes === null && $backupStaticAttributes !== null) { + $this->backupStaticAttributes = $backupStaticAttributes; + } + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function setRunTestInSeparateProcess(bool $runTestInSeparateProcess): void + { + if ($this->runTestInSeparateProcess === null) { + $this->runTestInSeparateProcess = $runTestInSeparateProcess; + } + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function setRunClassInSeparateProcess(bool $runClassInSeparateProcess): void + { + if ($this->runClassInSeparateProcess === null) { + $this->runClassInSeparateProcess = $runClassInSeparateProcess; + } + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function setPreserveGlobalState(bool $preserveGlobalState): void + { + $this->preserveGlobalState = $preserveGlobalState; + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function setInIsolation(bool $inIsolation): void + { + $this->inIsolation = $inIsolation; + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function isInIsolation(): bool + { + return $this->inIsolation; + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function getResult() + { + return $this->testResult; + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function setResult($result): void + { + $this->testResult = $result; + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function setOutputCallback(callable $callback): void + { + $this->outputCallback = $callback; + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function getTestResultObject(): ?TestResult + { + return $this->result; + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function setTestResultObject(TestResult $result): void + { + $this->result = $result; + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function registerMockObject(MockObject $mockObject): void + { + $this->mockObjects[] = $mockObject; + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function addToAssertionCount(int $count): void + { + $this->numAssertions += $count; + } + + /** + * Returns the number of assertions performed by this test. + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function getNumAssertions(): int + { + return $this->numAssertions; + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function usesDataProvider(): bool + { + return !empty($this->data); + } + + /** + * @return int|string + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function dataName() + { + return $this->dataName; + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function getDataSetAsString(bool $includeData = true): string + { + $buffer = ''; + + if (!empty($this->data)) { + if (is_int($this->dataName)) { + $buffer .= sprintf(' with data set #%d', $this->dataName); + } else { + $buffer .= sprintf(' with data set "%s"', $this->dataName); + } + + if ($includeData) { + $exporter = new Exporter; + + $buffer .= sprintf(' (%s)', $exporter->shortenedRecursiveExport($this->data)); + } + } + + return $buffer; + } + + /** + * Gets the data set of a TestCase. + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function getProvidedData(): array + { + return $this->data; + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function addWarning(string $warning): void + { + $this->warnings[] = $warning; + } + + public function sortId(): string + { + $id = $this->name; + + if (strpos($id, '::') === false) { + $id = static::class . '::' . $id; + } + + if ($this->usesDataProvider()) { + $id .= $this->getDataSetAsString(false); + } + + return $id; + } + + /** + * Returns the normalized test name as class::method. + * + * @return list + */ + public function provides(): array + { + return $this->providedTests; + } + + /** + * Returns a list of normalized dependency names, class::method. + * + * This list can differ from the raw dependencies as the resolver has + * no need for the [!][shallow]clone prefix that is filtered out + * during normalization. + * + * @return list + */ + public function requires(): array + { + return $this->dependencies; + } + + /** + * Override to run the test and assert its state. + * + * @throws \SebastianBergmann\ObjectEnumerator\InvalidArgumentException + * @throws AssertionFailedError + * @throws Exception + * @throws ExpectationFailedException + * @throws Throwable + */ + protected function runTest() + { + if (trim($this->name) === '') { + throw new Exception( + 'PHPUnit\Framework\TestCase::$name must be a non-blank string.', + ); + } + + $testArguments = array_merge($this->data, $this->dependencyInput); + + $this->registerMockObjectsFromTestArguments($testArguments); + + try { + $testResult = $this->{$this->name}(...array_values($testArguments)); + } catch (Throwable $exception) { + if (!$this->checkExceptionExpectations($exception)) { + throw $exception; + } + + if ($this->expectedException !== null) { + if ($this->expectedException === Error::class) { + $this->assertThat( + $exception, + LogicalOr::fromConstraints( + new ExceptionConstraint(Error::class), + new ExceptionConstraint(\Error::class), + ), + ); + } else { + $this->assertThat( + $exception, + new ExceptionConstraint( + $this->expectedException, + ), + ); + } + } + + if ($this->expectedExceptionMessage !== null) { + $this->assertThat( + $exception, + new ExceptionMessage( + $this->expectedExceptionMessage, + ), + ); + } + + if ($this->expectedExceptionMessageRegExp !== null) { + $this->assertThat( + $exception, + new ExceptionMessageRegularExpression( + $this->expectedExceptionMessageRegExp, + ), + ); + } + + if ($this->expectedExceptionCode !== null) { + $this->assertThat( + $exception, + new ExceptionCode( + $this->expectedExceptionCode, + ), + ); + } + + return; + } + + if ($this->expectedException !== null) { + $this->assertThat( + null, + new ExceptionConstraint( + $this->expectedException, + ), + ); + } elseif ($this->expectedExceptionMessage !== null) { + $this->numAssertions++; + + throw new AssertionFailedError( + sprintf( + 'Failed asserting that exception with message "%s" is thrown', + $this->expectedExceptionMessage, + ), + ); + } elseif ($this->expectedExceptionMessageRegExp !== null) { + $this->numAssertions++; + + throw new AssertionFailedError( + sprintf( + 'Failed asserting that exception with message matching "%s" is thrown', + $this->expectedExceptionMessageRegExp, + ), + ); + } elseif ($this->expectedExceptionCode !== null) { + $this->numAssertions++; + + throw new AssertionFailedError( + sprintf( + 'Failed asserting that exception with code "%s" is thrown', + $this->expectedExceptionCode, + ), + ); + } + + return $testResult; + } + + /** + * This method is a wrapper for the ini_set() function that automatically + * resets the modified php.ini setting to its original value after the + * test is run. + * + * @throws Exception + */ + protected function iniSet(string $varName, string $newValue): void + { + $currentValue = ini_set($varName, $newValue); + + if ($currentValue !== false) { + $this->iniSettings[$varName] = $currentValue; + } else { + throw new Exception( + sprintf( + 'INI setting "%s" could not be set to "%s".', + $varName, + $newValue, + ), + ); + } + } + + /** + * This method is a wrapper for the setlocale() function that automatically + * resets the locale to its original value after the test is run. + * + * @throws Exception + */ + protected function setLocale(...$args): void + { + if (count($args) < 2) { + throw new Exception; + } + + [$category, $locale] = $args; + + if (!in_array($category, self::LOCALE_CATEGORIES, true)) { + throw new Exception; + } + + if (!is_array($locale) && !is_string($locale)) { + throw new Exception; + } + + $this->locale[$category] = setlocale($category, 0); + + $result = setlocale(...$args); + + if ($result === false) { + throw new Exception( + 'The locale functionality is not implemented on your platform, ' . + 'the specified locale does not exist or the category name is ' . + 'invalid.', + ); + } + } + + /** + * Makes configurable stub for the specified class. + * + * @psalm-template RealInstanceType of object + * + * @psalm-param class-string $originalClassName + * + * @psalm-return Stub&RealInstanceType + */ + protected function createStub(string $originalClassName): Stub + { + return $this->createMockObject($originalClassName); + } + + /** + * Returns a mock object for the specified class. + * + * @psalm-template RealInstanceType of object + * + * @psalm-param class-string $originalClassName + * + * @psalm-return MockObject&RealInstanceType + */ + protected function createMock(string $originalClassName): MockObject + { + return $this->createMockObject($originalClassName); + } + + /** + * Returns a configured mock object for the specified class. + * + * @psalm-template RealInstanceType of object + * + * @psalm-param class-string $originalClassName + * + * @psalm-return MockObject&RealInstanceType + */ + protected function createConfiguredMock(string $originalClassName, array $configuration): MockObject + { + $o = $this->createMockObject($originalClassName); + + foreach ($configuration as $method => $return) { + $o->method($method)->willReturn($return); + } + + return $o; + } + + /** + * Returns a partial mock object for the specified class. + * + * @param string[] $methods + * + * @psalm-template RealInstanceType of object + * + * @psalm-param class-string $originalClassName + * + * @psalm-return MockObject&RealInstanceType + */ + protected function createPartialMock(string $originalClassName, array $methods): MockObject + { + try { + $reflector = new ReflectionClass($originalClassName); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + $mockedMethodsThatDontExist = array_filter( + $methods, + static function (string $method) use ($reflector) + { + return !$reflector->hasMethod($method); + }, + ); + + if ($mockedMethodsThatDontExist) { + $this->addWarning( + sprintf( + 'createPartialMock() called with method(s) %s that do not exist in %s. This will not be allowed in future versions of PHPUnit.', + implode(', ', $mockedMethodsThatDontExist), + $originalClassName, + ), + ); + } + + return $this->getMockBuilder($originalClassName) + ->disableOriginalConstructor() + ->disableOriginalClone() + ->disableArgumentCloning() + ->disallowMockingUnknownTypes() + ->setMethods(empty($methods) ? null : $methods) + ->getMock(); + } + + /** + * Returns a test proxy for the specified class. + * + * @psalm-template RealInstanceType of object + * + * @psalm-param class-string $originalClassName + * + * @psalm-return MockObject&RealInstanceType + */ + protected function createTestProxy(string $originalClassName, array $constructorArguments = []): MockObject + { + return $this->getMockBuilder($originalClassName) + ->setConstructorArgs($constructorArguments) + ->enableProxyingToOriginalMethods() + ->getMock(); + } + + /** + * Mocks the specified class and returns the name of the mocked class. + * + * @param null|array $methods $methods + * + * @psalm-template RealInstanceType of object + * + * @psalm-param class-string|string $originalClassName + * + * @psalm-return class-string + * + * @deprecated + */ + protected function getMockClass(string $originalClassName, $methods = [], array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = false, bool $callOriginalClone = true, bool $callAutoload = true, bool $cloneArguments = false): string + { + $this->addWarning('PHPUnit\Framework\TestCase::getMockClass() is deprecated and will be removed in PHPUnit 10.'); + + $this->recordDoubledType($originalClassName); + + $mock = $this->getMockObjectGenerator()->getMock( + $originalClassName, + $methods, + $arguments, + $mockClassName, + $callOriginalConstructor, + $callOriginalClone, + $callAutoload, + $cloneArguments, + ); + + return get_class($mock); + } + + /** + * Returns a mock object for the specified abstract class with all abstract + * methods of the class mocked. Concrete methods are not mocked by default. + * To mock concrete methods, use the 7th parameter ($mockedMethods). + * + * @psalm-template RealInstanceType of object + * + * @psalm-param class-string $originalClassName + * + * @psalm-return MockObject&RealInstanceType + */ + protected function getMockForAbstractClass(string $originalClassName, array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = true, bool $callOriginalClone = true, bool $callAutoload = true, array $mockedMethods = [], bool $cloneArguments = false): MockObject + { + $this->recordDoubledType($originalClassName); + + $mockObject = $this->getMockObjectGenerator()->getMockForAbstractClass( + $originalClassName, + $arguments, + $mockClassName, + $callOriginalConstructor, + $callOriginalClone, + $callAutoload, + $mockedMethods, + $cloneArguments, + ); + + $this->registerMockObject($mockObject); + + return $mockObject; + } + + /** + * Returns a mock object based on the given WSDL file. + * + * @psalm-template RealInstanceType of object + * + * @psalm-param class-string|string $originalClassName + * + * @psalm-return MockObject&RealInstanceType + */ + protected function getMockFromWsdl(string $wsdlFile, string $originalClassName = '', string $mockClassName = '', array $methods = [], bool $callOriginalConstructor = true, array $options = []): MockObject + { + $this->recordDoubledType(SoapClient::class); + + if ($originalClassName === '') { + $fileName = pathinfo(basename(parse_url($wsdlFile, PHP_URL_PATH)), PATHINFO_FILENAME); + $originalClassName = preg_replace('/\W/', '', $fileName); + } + + if (!class_exists($originalClassName)) { + eval( + $this->getMockObjectGenerator()->generateClassFromWsdl( + $wsdlFile, + $originalClassName, + $methods, + $options, + ) + ); + } + + $mockObject = $this->getMockObjectGenerator()->getMock( + $originalClassName, + $methods, + ['', $options], + $mockClassName, + $callOriginalConstructor, + false, + false, + ); + + $this->registerMockObject($mockObject); + + return $mockObject; + } + + /** + * Returns a mock object for the specified trait with all abstract methods + * of the trait mocked. Concrete methods to mock can be specified with the + * `$mockedMethods` parameter. + * + * @psalm-param trait-string $traitName + */ + protected function getMockForTrait(string $traitName, array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = true, bool $callOriginalClone = true, bool $callAutoload = true, array $mockedMethods = [], bool $cloneArguments = false): MockObject + { + $this->recordDoubledType($traitName); + + $mockObject = $this->getMockObjectGenerator()->getMockForTrait( + $traitName, + $arguments, + $mockClassName, + $callOriginalConstructor, + $callOriginalClone, + $callAutoload, + $mockedMethods, + $cloneArguments, + ); + + $this->registerMockObject($mockObject); + + return $mockObject; + } + + /** + * Returns an object for the specified trait. + * + * @psalm-param trait-string $traitName + */ + protected function getObjectForTrait(string $traitName, array $arguments = [], string $traitClassName = '', bool $callOriginalConstructor = true, bool $callOriginalClone = true, bool $callAutoload = true): object + { + $this->recordDoubledType($traitName); + + return $this->getMockObjectGenerator()->getObjectForTrait( + $traitName, + $traitClassName, + $callAutoload, + $callOriginalConstructor, + $arguments, + ); + } + + /** + * @throws \Prophecy\Exception\Doubler\ClassNotFoundException + * @throws \Prophecy\Exception\Doubler\DoubleException + * @throws \Prophecy\Exception\Doubler\InterfaceNotFoundException + * + * @psalm-param class-string|null $classOrInterface + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4141 + */ + protected function prophesize(?string $classOrInterface = null): ObjectProphecy + { + if (!class_exists(Prophet::class)) { + throw new Exception('This test uses TestCase::prophesize(), but phpspec/prophecy is not installed. Please run "composer require --dev phpspec/prophecy".'); + } + + $this->addWarning('PHPUnit\Framework\TestCase::prophesize() is deprecated and will be removed in PHPUnit 10. Please use the trait provided by phpspec/prophecy-phpunit.'); + + if (is_string($classOrInterface)) { + $this->recordDoubledType($classOrInterface); + } + + return $this->getProphet()->prophesize($classOrInterface); + } + + /** + * Creates a default TestResult object. + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + protected function createResult(): TestResult + { + return new TestResult; + } + + /** + * This method is called when a test method did not execute successfully. + * + * @throws Throwable + */ + protected function onNotSuccessfulTest(Throwable $t): void + { + throw $t; + } + + protected function recordDoubledType(string $originalClassName): void + { + $this->doubledTypes[] = $originalClassName; + } + + /** + * @throws Throwable + */ + private function verifyMockObjects(): void + { + foreach ($this->mockObjects as $mockObject) { + if ($mockObject->__phpunit_hasMatchers()) { + $this->numAssertions++; + } + + $mockObject->__phpunit_verify( + $this->shouldInvocationMockerBeReset($mockObject), + ); + } + + if ($this->prophet !== null) { + try { + $this->prophet->checkPredictions(); + } finally { + foreach ($this->prophet->getProphecies() as $objectProphecy) { + foreach ($objectProphecy->getMethodProphecies() as $methodProphecies) { + foreach ($methodProphecies as $methodProphecy) { + /* @var MethodProphecy $methodProphecy */ + $this->numAssertions += count($methodProphecy->getCheckedPredictions()); + } + } + } + } + } + } + + /** + * @throws SkippedTestError + * @throws SyntheticSkippedError + * @throws Warning + */ + private function checkRequirements(): void + { + if (!$this->name || !method_exists($this, $this->name)) { + return; + } + + $missingRequirements = TestUtil::getMissingRequirements( + static::class, + $this->name, + ); + + if (!empty($missingRequirements)) { + $this->markTestSkipped(implode(PHP_EOL, $missingRequirements)); + } + } + + private function handleDependencies(): bool + { + if ([] === $this->dependencies || $this->inIsolation) { + return true; + } + + $passed = $this->result->passed(); + $passedKeys = array_keys($passed); + $numKeys = count($passedKeys); + + for ($i = 0; $i < $numKeys; $i++) { + $pos = strpos($passedKeys[$i], ' with data set'); + + if ($pos !== false) { + $passedKeys[$i] = substr($passedKeys[$i], 0, $pos); + } + } + + $passedKeys = array_flip(array_unique($passedKeys)); + + foreach ($this->dependencies as $dependency) { + if (!$dependency->isValid()) { + $this->markSkippedForNotSpecifyingDependency(); + + return false; + } + + if ($dependency->targetIsClass()) { + $dependencyClassName = $dependency->getTargetClassName(); + + if (array_search($dependencyClassName, $this->result->passedClasses(), true) === false) { + $this->markSkippedForMissingDependency($dependency); + + return false; + } + + continue; + } + + $dependencyTarget = $dependency->getTarget(); + + if (!isset($passedKeys[$dependencyTarget])) { + if (!$this->isCallableTestMethod($dependencyTarget)) { + $this->markWarningForUncallableDependency($dependency); + } else { + $this->markSkippedForMissingDependency($dependency); + } + + return false; + } + + if (isset($passed[$dependencyTarget])) { + if ($passed[$dependencyTarget]['size'] != TestUtil::UNKNOWN && + $this->getSize() != TestUtil::UNKNOWN && + $passed[$dependencyTarget]['size'] > $this->getSize()) { + $this->result->addError( + $this, + new SkippedTestError( + 'This test depends on a test that is larger than itself.', + ), + 0, + ); + + return false; + } + + if ($dependency->useDeepClone()) { + $deepCopy = new DeepCopy; + $deepCopy->skipUncloneable(false); + + $this->dependencyInput[$dependencyTarget] = $deepCopy->copy($passed[$dependencyTarget]['result']); + } elseif ($dependency->useShallowClone()) { + $this->dependencyInput[$dependencyTarget] = clone $passed[$dependencyTarget]['result']; + } else { + $this->dependencyInput[$dependencyTarget] = $passed[$dependencyTarget]['result']; + } + } else { + $this->dependencyInput[$dependencyTarget] = null; + } + } + + return true; + } + + private function markSkippedForNotSpecifyingDependency(): void + { + $this->status = BaseTestRunner::STATUS_SKIPPED; + + $this->result->startTest($this); + + $this->result->addError( + $this, + new SkippedTestError( + 'This method has an invalid @depends annotation.', + ), + 0, + ); + + $this->result->endTest($this, 0); + } + + private function markSkippedForMissingDependency(ExecutionOrderDependency $dependency): void + { + $this->status = BaseTestRunner::STATUS_SKIPPED; + + $this->result->startTest($this); + + $this->result->addError( + $this, + new SkippedTestError( + sprintf( + 'This test depends on "%s" to pass.', + $dependency->getTarget(), + ), + ), + 0, + ); + + $this->result->endTest($this, 0); + } + + private function markWarningForUncallableDependency(ExecutionOrderDependency $dependency): void + { + $this->status = BaseTestRunner::STATUS_WARNING; + + $this->result->startTest($this); + + $this->result->addWarning( + $this, + new Warning( + sprintf( + 'This test depends on "%s" which does not exist.', + $dependency->getTarget(), + ), + ), + 0, + ); + + $this->result->endTest($this, 0); + } + + /** + * Get the mock object generator, creating it if it doesn't exist. + */ + private function getMockObjectGenerator(): MockGenerator + { + if ($this->mockObjectGenerator === null) { + $this->mockObjectGenerator = new MockGenerator; + } + + return $this->mockObjectGenerator; + } + + private function startOutputBuffering(): void + { + ob_start(); + + $this->outputBufferingActive = true; + $this->outputBufferingLevel = ob_get_level(); + } + + /** + * @throws RiskyTestError + */ + private function stopOutputBuffering(): void + { + if (ob_get_level() !== $this->outputBufferingLevel) { + while (ob_get_level() >= $this->outputBufferingLevel) { + ob_end_clean(); + } + + throw new RiskyTestError( + 'Test code or tested code did not (only) close its own output buffers', + ); + } + + $this->output = ob_get_contents(); + + if ($this->outputCallback !== false) { + $this->output = (string) call_user_func($this->outputCallback, $this->output); + } + + ob_end_clean(); + + $this->outputBufferingActive = false; + $this->outputBufferingLevel = ob_get_level(); + } + + private function snapshotGlobalState(): void + { + if ($this->runTestInSeparateProcess || $this->inIsolation || + (!$this->backupGlobals && !$this->backupStaticAttributes)) { + return; + } + + $this->snapshot = $this->createGlobalStateSnapshot($this->backupGlobals === true); + } + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws RiskyTestError + */ + private function restoreGlobalState(): void + { + if (!$this->snapshot instanceof Snapshot) { + return; + } + + if ($this->beStrictAboutChangesToGlobalState) { + try { + $this->compareGlobalStateSnapshots( + $this->snapshot, + $this->createGlobalStateSnapshot($this->backupGlobals === true), + ); + } catch (RiskyTestError $rte) { + // Intentionally left empty + } + } + + $restorer = new Restorer; + + if ($this->backupGlobals) { + $restorer->restoreGlobalVariables($this->snapshot); + } + + if ($this->backupStaticAttributes) { + $restorer->restoreStaticAttributes($this->snapshot); + } + + $this->snapshot = null; + + if (isset($rte)) { + throw $rte; + } + } + + private function createGlobalStateSnapshot(bool $backupGlobals): Snapshot + { + $excludeList = new ExcludeList; + + foreach ($this->backupGlobalsExcludeList as $globalVariable) { + $excludeList->addGlobalVariable($globalVariable); + } + + if (!empty($this->backupGlobalsBlacklist)) { + $this->addWarning('PHPUnit\Framework\TestCase::$backupGlobalsBlacklist is deprecated and will be removed in PHPUnit 10. Please use PHPUnit\Framework\TestCase::$backupGlobalsExcludeList instead.'); + + foreach ($this->backupGlobalsBlacklist as $globalVariable) { + $excludeList->addGlobalVariable($globalVariable); + } + } + + if (!defined('PHPUNIT_TESTSUITE')) { + $excludeList->addClassNamePrefix('PHPUnit'); + $excludeList->addClassNamePrefix('SebastianBergmann\CodeCoverage'); + $excludeList->addClassNamePrefix('SebastianBergmann\FileIterator'); + $excludeList->addClassNamePrefix('SebastianBergmann\Invoker'); + $excludeList->addClassNamePrefix('SebastianBergmann\Template'); + $excludeList->addClassNamePrefix('SebastianBergmann\Timer'); + $excludeList->addClassNamePrefix('Doctrine\Instantiator'); + $excludeList->addClassNamePrefix('Prophecy'); + $excludeList->addStaticAttribute(ComparatorFactory::class, 'instance'); + + foreach ($this->backupStaticAttributesExcludeList as $class => $attributes) { + foreach ($attributes as $attribute) { + $excludeList->addStaticAttribute($class, $attribute); + } + } + + if (!empty($this->backupStaticAttributesBlacklist)) { + $this->addWarning('PHPUnit\Framework\TestCase::$backupStaticAttributesBlacklist is deprecated and will be removed in PHPUnit 10. Please use PHPUnit\Framework\TestCase::$backupStaticAttributesExcludeList instead.'); + + foreach ($this->backupStaticAttributesBlacklist as $class => $attributes) { + foreach ($attributes as $attribute) { + $excludeList->addStaticAttribute($class, $attribute); + } + } + } + } + + return new Snapshot( + $excludeList, + $backupGlobals, + (bool) $this->backupStaticAttributes, + false, + false, + false, + false, + false, + false, + false, + ); + } + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws RiskyTestError + */ + private function compareGlobalStateSnapshots(Snapshot $before, Snapshot $after): void + { + $backupGlobals = $this->backupGlobals === null || $this->backupGlobals; + + if ($backupGlobals) { + $this->compareGlobalStateSnapshotPart( + $before->globalVariables(), + $after->globalVariables(), + "--- Global variables before the test\n+++ Global variables after the test\n", + ); + + $this->compareGlobalStateSnapshotPart( + $before->superGlobalVariables(), + $after->superGlobalVariables(), + "--- Super-global variables before the test\n+++ Super-global variables after the test\n", + ); + } + + if ($this->backupStaticAttributes) { + $this->compareGlobalStateSnapshotPart( + $before->staticAttributes(), + $after->staticAttributes(), + "--- Static attributes before the test\n+++ Static attributes after the test\n", + ); + } + } + + /** + * @throws RiskyTestError + */ + private function compareGlobalStateSnapshotPart(array $before, array $after, string $header): void + { + if ($before != $after) { + $differ = new Differ($header); + $exporter = new Exporter; + + $diff = $differ->diff( + $exporter->export($before), + $exporter->export($after), + ); + + throw new RiskyTestError( + $diff, + ); + } + } + + private function getProphet(): Prophet + { + if ($this->prophet === null) { + $this->prophet = new Prophet; + } + + return $this->prophet; + } + + /** + * @throws \SebastianBergmann\ObjectEnumerator\InvalidArgumentException + */ + private function shouldInvocationMockerBeReset(MockObject $mock): bool + { + $enumerator = new Enumerator; + + foreach ($enumerator->enumerate($this->dependencyInput) as $object) { + if ($mock === $object) { + return false; + } + } + + if (!is_array($this->testResult) && !is_object($this->testResult)) { + return true; + } + + return !in_array($mock, $enumerator->enumerate($this->testResult), true); + } + + /** + * @throws \SebastianBergmann\ObjectEnumerator\InvalidArgumentException + * @throws \SebastianBergmann\ObjectReflector\InvalidArgumentException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + private function registerMockObjectsFromTestArguments(array $testArguments, array &$visited = []): void + { + if ($this->registerMockObjectsFromTestArgumentsRecursively) { + foreach ((new Enumerator)->enumerate($testArguments) as $object) { + if ($object instanceof MockObject) { + $this->registerMockObject($object); + } + } + } else { + foreach ($testArguments as $testArgument) { + if ($testArgument instanceof MockObject) { + $testArgument = Cloner::clone($testArgument); + + $this->registerMockObject($testArgument); + } elseif (is_array($testArgument) && !in_array($testArgument, $visited, true)) { + $visited[] = $testArgument; + + $this->registerMockObjectsFromTestArguments( + $testArgument, + $visited, + ); + } + } + } + } + + private function setDoesNotPerformAssertionsFromAnnotation(): void + { + $annotations = TestUtil::parseTestMethodAnnotations( + static::class, + $this->name, + ); + + if (isset($annotations['method']['doesNotPerformAssertions'])) { + $this->doesNotPerformAssertions = true; + } + } + + private function unregisterCustomComparators(): void + { + $factory = ComparatorFactory::getInstance(); + + foreach ($this->customComparators as $comparator) { + $factory->unregister($comparator); + } + + $this->customComparators = []; + } + + private function cleanupIniSettings(): void + { + foreach ($this->iniSettings as $varName => $oldValue) { + ini_set($varName, $oldValue); + } + + $this->iniSettings = []; + } + + private function cleanupLocaleSettings(): void + { + foreach ($this->locale as $category => $locale) { + setlocale($category, $locale); + } + + $this->locale = []; + } + + /** + * @throws Exception + */ + private function checkExceptionExpectations(Throwable $throwable): bool + { + $result = false; + + if ($this->expectedException !== null || $this->expectedExceptionCode !== null || $this->expectedExceptionMessage !== null || $this->expectedExceptionMessageRegExp !== null) { + $result = true; + } + + if ($throwable instanceof Exception) { + $result = false; + } + + if (is_string($this->expectedException)) { + try { + $reflector = new ReflectionClass($this->expectedException); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + if ($this->expectedException === 'PHPUnit\Framework\Exception' || + $this->expectedException === '\PHPUnit\Framework\Exception' || + $reflector->isSubclassOf(Exception::class)) { + $result = true; + } + } + + return $result; + } + + private function runInSeparateProcess(): bool + { + return ($this->runTestInSeparateProcess || $this->runClassInSeparateProcess) && + !$this->inIsolation && !$this instanceof PhptTestCase; + } + + private function isCallableTestMethod(string $dependency): bool + { + [$className, $methodName] = explode('::', $dependency); + + if (!class_exists($className)) { + return false; + } + + try { + $class = new ReflectionClass($className); + } catch (ReflectionException $e) { + return false; + } + + if (!$class->isSubclassOf(__CLASS__)) { + return false; + } + + if (!$class->hasMethod($methodName)) { + return false; + } + + try { + $method = $class->getMethod($methodName); + } catch (ReflectionException $e) { + return false; + } + + return TestUtil::isTestMethod($method); + } + + /** + * @psalm-template RealInstanceType of object + * + * @psalm-param class-string $originalClassName + * + * @psalm-return MockObject&RealInstanceType + */ + private function createMockObject(string $originalClassName): MockObject + { + return $this->getMockBuilder($originalClassName) + ->disableOriginalConstructor() + ->disableOriginalClone() + ->disableArgumentCloning() + ->disallowMockingUnknownTypes() + ->getMock(); + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/TestFailure.php b/vendor/phpunit/phpunit/src/Framework/TestFailure.php new file mode 100644 index 00000000..f49dfc35 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/TestFailure.php @@ -0,0 +1,155 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function get_class; +use function sprintf; +use function trim; +use PHPUnit\Framework\Error\Error; +use Throwable; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TestFailure +{ + /** + * @var null|Test + */ + private $failedTest; + + /** + * @var Throwable + */ + private $thrownException; + + /** + * @var string + */ + private $testName; + + /** + * Returns a description for an exception. + */ + public static function exceptionToString(Throwable $e): string + { + if ($e instanceof SelfDescribing) { + $buffer = $e->toString(); + + if ($e instanceof ExpectationFailedException && $e->getComparisonFailure()) { + $buffer .= $e->getComparisonFailure()->getDiff(); + } + + if ($e instanceof PHPTAssertionFailedError) { + $buffer .= $e->getDiff(); + } + + if (!empty($buffer)) { + $buffer = trim($buffer) . "\n"; + } + + return $buffer; + } + + if ($e instanceof Error) { + return $e->getMessage() . "\n"; + } + + if ($e instanceof ExceptionWrapper) { + return $e->getClassName() . ': ' . $e->getMessage() . "\n"; + } + + return get_class($e) . ': ' . $e->getMessage() . "\n"; + } + + /** + * Constructs a TestFailure with the given test and exception. + */ + public function __construct(Test $failedTest, Throwable $t) + { + if ($failedTest instanceof SelfDescribing) { + $this->testName = $failedTest->toString(); + } else { + $this->testName = get_class($failedTest); + } + + if (!$failedTest instanceof TestCase || !$failedTest->isInIsolation()) { + $this->failedTest = $failedTest; + } + + $this->thrownException = $t; + } + + /** + * Returns a short description of the failure. + */ + public function toString(): string + { + return sprintf( + '%s: %s', + $this->testName, + $this->thrownException->getMessage(), + ); + } + + /** + * Returns a description for the thrown exception. + */ + public function getExceptionAsString(): string + { + return self::exceptionToString($this->thrownException); + } + + /** + * Returns the name of the failing test (including data set, if any). + */ + public function getTestName(): string + { + return $this->testName; + } + + /** + * Returns the failing test. + * + * Note: The test object is not set when the test is executed in process + * isolation. + * + * @see Exception + */ + public function failedTest(): ?Test + { + return $this->failedTest; + } + + /** + * Gets the thrown exception. + */ + public function thrownException(): Throwable + { + return $this->thrownException; + } + + /** + * Returns the exception's message. + */ + public function exceptionMessage(): string + { + return $this->thrownException()->getMessage(); + } + + /** + * Returns true if the thrown exception + * is of type AssertionFailedError. + */ + public function isFailure(): bool + { + return $this->thrownException() instanceof AssertionFailedError; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/TestListener.php b/vendor/phpunit/phpunit/src/Framework/TestListener.php new file mode 100644 index 00000000..eade600f --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/TestListener.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use Throwable; + +/** + * This interface, as well as the associated mechanism for extending PHPUnit, + * will be removed in PHPUnit 10. There is no alternative available in this + * version of PHPUnit. + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @deprecated + * @see https://github.com/sebastianbergmann/phpunit/issues/4676 + */ +interface TestListener +{ + public function addError(Test $test, Throwable $t, float $time): void; + + public function addWarning(Test $test, Warning $e, float $time): void; + + public function addFailure(Test $test, AssertionFailedError $e, float $time): void; + + public function addIncompleteTest(Test $test, Throwable $t, float $time): void; + + public function addRiskyTest(Test $test, Throwable $t, float $time): void; + + public function addSkippedTest(Test $test, Throwable $t, float $time): void; + + public function startTestSuite(TestSuite $suite): void; + + public function endTestSuite(TestSuite $suite): void; + + public function startTest(Test $test): void; + + public function endTest(Test $test, float $time): void; +} diff --git a/vendor/phpunit/phpunit/src/Framework/TestListenerDefaultImplementation.php b/vendor/phpunit/phpunit/src/Framework/TestListenerDefaultImplementation.php new file mode 100644 index 00000000..5731d98d --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/TestListenerDefaultImplementation.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use Throwable; + +/** + * @deprecated The `TestListener` interface is deprecated + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +trait TestListenerDefaultImplementation +{ + public function addError(Test $test, Throwable $t, float $time): void + { + } + + public function addWarning(Test $test, Warning $e, float $time): void + { + } + + public function addFailure(Test $test, AssertionFailedError $e, float $time): void + { + } + + public function addIncompleteTest(Test $test, Throwable $t, float $time): void + { + } + + public function addRiskyTest(Test $test, Throwable $t, float $time): void + { + } + + public function addSkippedTest(Test $test, Throwable $t, float $time): void + { + } + + public function startTestSuite(TestSuite $suite): void + { + } + + public function endTestSuite(TestSuite $suite): void + { + } + + public function startTest(Test $test): void + { + } + + public function endTest(Test $test, float $time): void + { + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/TestResult.php b/vendor/phpunit/phpunit/src/Framework/TestResult.php new file mode 100644 index 00000000..2c487255 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/TestResult.php @@ -0,0 +1,1323 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use const PHP_EOL; +use function class_exists; +use function count; +use function extension_loaded; +use function function_exists; +use function get_class; +use function sprintf; +use function xdebug_get_monitored_functions; +use function xdebug_is_debugger_active; +use function xdebug_start_function_monitor; +use function xdebug_stop_function_monitor; +use AssertionError; +use Countable; +use Error; +use PHPUnit\Util\ErrorHandler; +use PHPUnit\Util\ExcludeList; +use PHPUnit\Util\Printer; +use PHPUnit\Util\Test as TestUtil; +use ReflectionClass; +use ReflectionException; +use SebastianBergmann\CodeCoverage\CodeCoverage; +use SebastianBergmann\CodeCoverage\Exception as OriginalCodeCoverageException; +use SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException; +use SebastianBergmann\Invoker\Invoker; +use SebastianBergmann\Invoker\TimeoutException; +use SebastianBergmann\ResourceOperations\ResourceOperations; +use SebastianBergmann\Timer\Timer; +use Throwable; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TestResult implements Countable +{ + /** + * @var array + */ + private $passed = []; + + /** + * @var array + */ + private $passedTestClasses = []; + + /** + * @var bool + */ + private $currentTestSuiteFailed = false; + + /** + * @var TestFailure[] + */ + private $errors = []; + + /** + * @var TestFailure[] + */ + private $failures = []; + + /** + * @var TestFailure[] + */ + private $warnings = []; + + /** + * @var TestFailure[] + */ + private $notImplemented = []; + + /** + * @var TestFailure[] + */ + private $risky = []; + + /** + * @var TestFailure[] + */ + private $skipped = []; + + /** + * @deprecated Use the `TestHook` interfaces instead + * + * @var TestListener[] + */ + private $listeners = []; + + /** + * @var int + */ + private $runTests = 0; + + /** + * @var float + */ + private $time = 0; + + /** + * Code Coverage information. + * + * @var CodeCoverage + */ + private $codeCoverage; + + /** + * @var bool + */ + private $convertDeprecationsToExceptions = false; + + /** + * @var bool + */ + private $convertErrorsToExceptions = true; + + /** + * @var bool + */ + private $convertNoticesToExceptions = true; + + /** + * @var bool + */ + private $convertWarningsToExceptions = true; + + /** + * @var bool + */ + private $stop = false; + + /** + * @var bool + */ + private $stopOnError = false; + + /** + * @var bool + */ + private $stopOnFailure = false; + + /** + * @var bool + */ + private $stopOnWarning = false; + + /** + * @var bool + */ + private $beStrictAboutTestsThatDoNotTestAnything = true; + + /** + * @var bool + */ + private $beStrictAboutOutputDuringTests = false; + + /** + * @var bool + */ + private $beStrictAboutTodoAnnotatedTests = false; + + /** + * @var bool + */ + private $beStrictAboutResourceUsageDuringSmallTests = false; + + /** + * @var bool + */ + private $enforceTimeLimit = false; + + /** + * @var bool + */ + private $forceCoversAnnotation = false; + + /** + * @var int + */ + private $timeoutForSmallTests = 1; + + /** + * @var int + */ + private $timeoutForMediumTests = 10; + + /** + * @var int + */ + private $timeoutForLargeTests = 60; + + /** + * @var bool + */ + private $stopOnRisky = false; + + /** + * @var bool + */ + private $stopOnIncomplete = false; + + /** + * @var bool + */ + private $stopOnSkipped = false; + + /** + * @var bool + */ + private $lastTestFailed = false; + + /** + * @var int + */ + private $defaultTimeLimit = 0; + + /** + * @var bool + */ + private $stopOnDefect = false; + + /** + * @var bool + */ + private $registerMockObjectsFromTestArgumentsRecursively = false; + + /** + * @deprecated Use the `TestHook` interfaces instead + * + * @codeCoverageIgnore + * + * Registers a TestListener. + */ + public function addListener(TestListener $listener): void + { + $this->listeners[] = $listener; + } + + /** + * @deprecated Use the `TestHook` interfaces instead + * + * @codeCoverageIgnore + * + * Unregisters a TestListener. + */ + public function removeListener(TestListener $listener): void + { + foreach ($this->listeners as $key => $_listener) { + if ($listener === $_listener) { + unset($this->listeners[$key]); + } + } + } + + /** + * @deprecated Use the `TestHook` interfaces instead + * + * @codeCoverageIgnore + * + * Flushes all flushable TestListeners. + */ + public function flushListeners(): void + { + foreach ($this->listeners as $listener) { + if ($listener instanceof Printer) { + $listener->flush(); + } + } + } + + /** + * Adds an error to the list of errors. + */ + public function addError(Test $test, Throwable $t, float $time): void + { + if ($t instanceof RiskyTestError) { + $this->recordRisky($test, $t); + + $notifyMethod = 'addRiskyTest'; + + if ($test instanceof TestCase) { + $test->markAsRisky(); + } + + if ($this->stopOnRisky || $this->stopOnDefect) { + $this->stop(); + } + } elseif ($t instanceof IncompleteTest) { + $this->recordNotImplemented($test, $t); + + $notifyMethod = 'addIncompleteTest'; + + if ($this->stopOnIncomplete) { + $this->stop(); + } + } elseif ($t instanceof SkippedTest) { + $this->recordSkipped($test, $t); + + $notifyMethod = 'addSkippedTest'; + + if ($this->stopOnSkipped) { + $this->stop(); + } + } else { + $this->recordError($test, $t); + + $notifyMethod = 'addError'; + + if ($this->stopOnError || $this->stopOnFailure) { + $this->stop(); + } + } + + // @see https://github.com/sebastianbergmann/phpunit/issues/1953 + if ($t instanceof Error) { + $t = new ExceptionWrapper($t); + } + + foreach ($this->listeners as $listener) { + $listener->{$notifyMethod}($test, $t, $time); + } + + $this->lastTestFailed = true; + $this->time += $time; + } + + /** + * Adds a warning to the list of warnings. + * The passed in exception caused the warning. + */ + public function addWarning(Test $test, Warning $e, float $time): void + { + if ($this->stopOnWarning || $this->stopOnDefect) { + $this->stop(); + } + + $this->recordWarning($test, $e); + + foreach ($this->listeners as $listener) { + $listener->addWarning($test, $e, $time); + } + + $this->time += $time; + } + + /** + * Adds a failure to the list of failures. + * The passed in exception caused the failure. + */ + public function addFailure(Test $test, AssertionFailedError $e, float $time): void + { + if ($e instanceof RiskyTestError || $e instanceof OutputError) { + $this->recordRisky($test, $e); + + $notifyMethod = 'addRiskyTest'; + + if ($test instanceof TestCase) { + $test->markAsRisky(); + } + + if ($this->stopOnRisky || $this->stopOnDefect) { + $this->stop(); + } + } elseif ($e instanceof IncompleteTest) { + $this->recordNotImplemented($test, $e); + + $notifyMethod = 'addIncompleteTest'; + + if ($this->stopOnIncomplete) { + $this->stop(); + } + } elseif ($e instanceof SkippedTest) { + $this->recordSkipped($test, $e); + + $notifyMethod = 'addSkippedTest'; + + if ($this->stopOnSkipped) { + $this->stop(); + } + } else { + $this->failures[] = new TestFailure($test, $e); + $notifyMethod = 'addFailure'; + + if ($this->stopOnFailure || $this->stopOnDefect) { + $this->stop(); + } + } + + foreach ($this->listeners as $listener) { + $listener->{$notifyMethod}($test, $e, $time); + } + + $this->lastTestFailed = true; + $this->time += $time; + } + + /** + * Informs the result that a test suite will be started. + */ + public function startTestSuite(TestSuite $suite): void + { + $this->currentTestSuiteFailed = false; + + foreach ($this->listeners as $listener) { + $listener->startTestSuite($suite); + } + } + + /** + * Informs the result that a test suite was completed. + */ + public function endTestSuite(TestSuite $suite): void + { + if (!$this->currentTestSuiteFailed) { + $this->passedTestClasses[] = $suite->getName(); + } + + foreach ($this->listeners as $listener) { + $listener->endTestSuite($suite); + } + } + + /** + * Informs the result that a test will be started. + */ + public function startTest(Test $test): void + { + $this->lastTestFailed = false; + $this->runTests += count($test); + + foreach ($this->listeners as $listener) { + $listener->startTest($test); + } + } + + /** + * Informs the result that a test was completed. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function endTest(Test $test, float $time): void + { + foreach ($this->listeners as $listener) { + $listener->endTest($test, $time); + } + + if (!$this->lastTestFailed && $test instanceof TestCase) { + $class = get_class($test); + $key = $class . '::' . $test->getName(); + + $this->passed[$key] = [ + 'result' => $test->getResult(), + 'size' => TestUtil::getSize( + $class, + $test->getName(false), + ), + ]; + + $this->time += $time; + } + + if ($this->lastTestFailed && $test instanceof TestCase) { + $this->currentTestSuiteFailed = true; + } + } + + /** + * Returns true if no risky test occurred. + */ + public function allHarmless(): bool + { + return $this->riskyCount() === 0; + } + + /** + * Gets the number of risky tests. + */ + public function riskyCount(): int + { + return count($this->risky); + } + + /** + * Returns true if no incomplete test occurred. + */ + public function allCompletelyImplemented(): bool + { + return $this->notImplementedCount() === 0; + } + + /** + * Gets the number of incomplete tests. + */ + public function notImplementedCount(): int + { + return count($this->notImplemented); + } + + /** + * Returns an array of TestFailure objects for the risky tests. + * + * @return TestFailure[] + */ + public function risky(): array + { + return $this->risky; + } + + /** + * Returns an array of TestFailure objects for the incomplete tests. + * + * @return TestFailure[] + */ + public function notImplemented(): array + { + return $this->notImplemented; + } + + /** + * Returns true if no test has been skipped. + */ + public function noneSkipped(): bool + { + return $this->skippedCount() === 0; + } + + /** + * Gets the number of skipped tests. + */ + public function skippedCount(): int + { + return count($this->skipped); + } + + /** + * Returns an array of TestFailure objects for the skipped tests. + * + * @return TestFailure[] + */ + public function skipped(): array + { + return $this->skipped; + } + + /** + * Gets the number of detected errors. + */ + public function errorCount(): int + { + return count($this->errors); + } + + /** + * Returns an array of TestFailure objects for the errors. + * + * @return TestFailure[] + */ + public function errors(): array + { + return $this->errors; + } + + /** + * Gets the number of detected failures. + */ + public function failureCount(): int + { + return count($this->failures); + } + + /** + * Returns an array of TestFailure objects for the failures. + * + * @return TestFailure[] + */ + public function failures(): array + { + return $this->failures; + } + + /** + * Gets the number of detected warnings. + */ + public function warningCount(): int + { + return count($this->warnings); + } + + /** + * Returns an array of TestFailure objects for the warnings. + * + * @return TestFailure[] + */ + public function warnings(): array + { + return $this->warnings; + } + + /** + * Returns the names of the tests that have passed. + */ + public function passed(): array + { + return $this->passed; + } + + /** + * Returns the names of the TestSuites that have passed. + * + * This enables @depends-annotations for TestClassName::class + */ + public function passedClasses(): array + { + return $this->passedTestClasses; + } + + /** + * Returns whether code coverage information should be collected. + */ + public function getCollectCodeCoverageInformation(): bool + { + return $this->codeCoverage !== null; + } + + /** + * Runs a TestCase. + * + * @throws \SebastianBergmann\CodeCoverage\InvalidArgumentException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws CodeCoverageException + * @throws UnintentionallyCoveredCodeException + */ + public function run(Test $test): void + { + Assert::resetCount(); + + $size = TestUtil::UNKNOWN; + + if ($test instanceof TestCase) { + $test->setRegisterMockObjectsFromTestArgumentsRecursively( + $this->registerMockObjectsFromTestArgumentsRecursively, + ); + + $isAnyCoverageRequired = TestUtil::requiresCodeCoverageDataCollection($test); + $size = $test->getSize(); + } + + $error = false; + $failure = false; + $warning = false; + $incomplete = false; + $risky = false; + $skipped = false; + + $this->startTest($test); + + if ($this->convertDeprecationsToExceptions || $this->convertErrorsToExceptions || $this->convertNoticesToExceptions || $this->convertWarningsToExceptions) { + $errorHandler = new ErrorHandler( + $this->convertDeprecationsToExceptions, + $this->convertErrorsToExceptions, + $this->convertNoticesToExceptions, + $this->convertWarningsToExceptions, + ); + + $errorHandler->register(); + } + + $collectCodeCoverage = $this->codeCoverage !== null && + !$test instanceof ErrorTestCase && + !$test instanceof WarningTestCase && + $isAnyCoverageRequired; + + if ($collectCodeCoverage) { + $this->codeCoverage->start($test); + } + + $monitorFunctions = $this->beStrictAboutResourceUsageDuringSmallTests && + !$test instanceof ErrorTestCase && + !$test instanceof WarningTestCase && + $size === TestUtil::SMALL && + function_exists('xdebug_start_function_monitor'); + + if ($monitorFunctions) { + /* @noinspection ForgottenDebugOutputInspection */ + xdebug_start_function_monitor(ResourceOperations::getFunctions()); + } + + $timer = new Timer; + $timer->start(); + + try { + $invoker = new Invoker; + + if (!$test instanceof ErrorTestCase && + !$test instanceof WarningTestCase && + $this->shouldTimeLimitBeEnforced($size) && + $invoker->canInvokeWithTimeout()) { + switch ($size) { + case TestUtil::SMALL: + $_timeout = $this->timeoutForSmallTests; + + break; + + case TestUtil::MEDIUM: + $_timeout = $this->timeoutForMediumTests; + + break; + + case TestUtil::LARGE: + $_timeout = $this->timeoutForLargeTests; + + break; + + default: + $_timeout = $this->defaultTimeLimit; + } + + $invoker->invoke([$test, 'runBare'], [], $_timeout); + } else { + $test->runBare(); + } + } catch (TimeoutException $e) { + $this->addFailure( + $test, + new RiskyTestError( + $e->getMessage(), + ), + $_timeout, + ); + + $risky = true; + } catch (AssertionFailedError $e) { + $failure = true; + + if ($e instanceof RiskyTestError) { + $risky = true; + } elseif ($e instanceof IncompleteTestError) { + $incomplete = true; + } elseif ($e instanceof SkippedTestError) { + $skipped = true; + } + } catch (AssertionError $e) { + $test->addToAssertionCount(1); + + $failure = true; + $frame = $e->getTrace()[0]; + + $e = new AssertionFailedError( + sprintf( + '%s in %s:%s', + $e->getMessage(), + $frame['file'] ?? $e->getFile(), + $frame['line'] ?? $e->getLine(), + ), + 0, + $e, + ); + } catch (Warning $e) { + $warning = true; + } catch (Exception $e) { + $error = true; + } catch (Throwable $e) { + $e = new ExceptionWrapper($e); + $error = true; + } + + $time = $timer->stop()->asSeconds(); + + $test->addToAssertionCount(Assert::getCount()); + + if ($monitorFunctions) { + $excludeList = new ExcludeList; + + /** @noinspection ForgottenDebugOutputInspection */ + $functions = xdebug_get_monitored_functions(); + + /* @noinspection ForgottenDebugOutputInspection */ + xdebug_stop_function_monitor(); + + foreach ($functions as $function) { + if (!$excludeList->isExcluded($function['filename'])) { + $this->addFailure( + $test, + new RiskyTestError( + sprintf( + '%s() used in %s:%s', + $function['function'], + $function['filename'], + $function['lineno'], + ), + ), + $time, + ); + } + } + } + + if ($this->beStrictAboutTestsThatDoNotTestAnything && + !$test->doesNotPerformAssertions() && + $test->getNumAssertions() === 0) { + $risky = true; + } + + if ($this->forceCoversAnnotation && !$error && !$failure && !$warning && !$incomplete && !$skipped && !$risky) { + $annotations = TestUtil::parseTestMethodAnnotations( + get_class($test), + $test->getName(false), + ); + + if (!isset($annotations['class']['covers']) && + !isset($annotations['method']['covers']) && + !isset($annotations['class']['coversNothing']) && + !isset($annotations['method']['coversNothing'])) { + $this->addFailure( + $test, + new MissingCoversAnnotationException( + 'This test does not have a @covers annotation but is expected to have one', + ), + $time, + ); + + $risky = true; + } + } + + if ($collectCodeCoverage) { + $append = !$risky && !$incomplete && !$skipped; + $linesToBeCovered = []; + $linesToBeUsed = []; + + if ($append && $test instanceof TestCase) { + try { + $linesToBeCovered = TestUtil::getLinesToBeCovered( + get_class($test), + $test->getName(false), + ); + + $linesToBeUsed = TestUtil::getLinesToBeUsed( + get_class($test), + $test->getName(false), + ); + } catch (InvalidCoversTargetException $cce) { + $this->addWarning( + $test, + new Warning( + $cce->getMessage(), + ), + $time, + ); + } + } + + try { + $this->codeCoverage->stop( + $append, + $linesToBeCovered, + $linesToBeUsed, + ); + } catch (UnintentionallyCoveredCodeException $cce) { + $unintentionallyCoveredCodeError = new UnintentionallyCoveredCodeError( + 'This test executed code that is not listed as code to be covered or used:' . + PHP_EOL . $cce->getMessage(), + ); + } catch (OriginalCodeCoverageException $cce) { + $error = true; + + $e = $e ?? $cce; + } + } + + if (isset($errorHandler)) { + $errorHandler->unregister(); + + unset($errorHandler); + } + + if ($error) { + $this->addError($test, $e, $time); + } elseif ($failure) { + $this->addFailure($test, $e, $time); + } elseif ($warning) { + $this->addWarning($test, $e, $time); + } elseif (isset($unintentionallyCoveredCodeError)) { + $this->addFailure( + $test, + $unintentionallyCoveredCodeError, + $time, + ); + } elseif ($this->beStrictAboutTestsThatDoNotTestAnything && + !$test->doesNotPerformAssertions() && + $test->getNumAssertions() === 0) { + try { + $reflected = new ReflectionClass($test); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + $name = $test->getName(false); + + if ($name && $reflected->hasMethod($name)) { + try { + $reflected = $reflected->getMethod($name); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + } + + $this->addFailure( + $test, + new RiskyTestError( + sprintf( + "This test did not perform any assertions\n\n%s:%d", + $reflected->getFileName(), + $reflected->getStartLine(), + ), + ), + $time, + ); + } elseif ($this->beStrictAboutTestsThatDoNotTestAnything && + $test->doesNotPerformAssertions() && + $test->getNumAssertions() > 0) { + $this->addFailure( + $test, + new RiskyTestError( + sprintf( + 'This test is annotated with "@doesNotPerformAssertions" but performed %d assertions', + $test->getNumAssertions(), + ), + ), + $time, + ); + } elseif ($this->beStrictAboutOutputDuringTests && $test->hasOutput()) { + $this->addFailure( + $test, + new OutputError( + sprintf( + 'This test printed output: %s', + $test->getActualOutput(), + ), + ), + $time, + ); + } elseif ($this->beStrictAboutTodoAnnotatedTests && $test instanceof TestCase) { + $annotations = TestUtil::parseTestMethodAnnotations( + get_class($test), + $test->getName(false), + ); + + if (isset($annotations['method']['todo'])) { + $this->addFailure( + $test, + new RiskyTestError( + 'Test method is annotated with @todo', + ), + $time, + ); + } + } + + $this->endTest($test, $time); + } + + /** + * Gets the number of run tests. + */ + public function count(): int + { + return $this->runTests; + } + + /** + * Checks whether the test run should stop. + */ + public function shouldStop(): bool + { + return $this->stop; + } + + /** + * Marks that the test run should stop. + */ + public function stop(): void + { + $this->stop = true; + } + + /** + * Returns the code coverage object. + */ + public function getCodeCoverage(): ?CodeCoverage + { + return $this->codeCoverage; + } + + /** + * Sets the code coverage object. + */ + public function setCodeCoverage(CodeCoverage $codeCoverage): void + { + $this->codeCoverage = $codeCoverage; + } + + /** + * Enables or disables the deprecation-to-exception conversion. + */ + public function convertDeprecationsToExceptions(bool $flag): void + { + $this->convertDeprecationsToExceptions = $flag; + } + + /** + * Returns the deprecation-to-exception conversion setting. + */ + public function getConvertDeprecationsToExceptions(): bool + { + return $this->convertDeprecationsToExceptions; + } + + /** + * Enables or disables the error-to-exception conversion. + */ + public function convertErrorsToExceptions(bool $flag): void + { + $this->convertErrorsToExceptions = $flag; + } + + /** + * Returns the error-to-exception conversion setting. + */ + public function getConvertErrorsToExceptions(): bool + { + return $this->convertErrorsToExceptions; + } + + /** + * Enables or disables the notice-to-exception conversion. + */ + public function convertNoticesToExceptions(bool $flag): void + { + $this->convertNoticesToExceptions = $flag; + } + + /** + * Returns the notice-to-exception conversion setting. + */ + public function getConvertNoticesToExceptions(): bool + { + return $this->convertNoticesToExceptions; + } + + /** + * Enables or disables the warning-to-exception conversion. + */ + public function convertWarningsToExceptions(bool $flag): void + { + $this->convertWarningsToExceptions = $flag; + } + + /** + * Returns the warning-to-exception conversion setting. + */ + public function getConvertWarningsToExceptions(): bool + { + return $this->convertWarningsToExceptions; + } + + /** + * Enables or disables the stopping when an error occurs. + */ + public function stopOnError(bool $flag): void + { + $this->stopOnError = $flag; + } + + /** + * Enables or disables the stopping when a failure occurs. + */ + public function stopOnFailure(bool $flag): void + { + $this->stopOnFailure = $flag; + } + + /** + * Enables or disables the stopping when a warning occurs. + */ + public function stopOnWarning(bool $flag): void + { + $this->stopOnWarning = $flag; + } + + public function beStrictAboutTestsThatDoNotTestAnything(bool $flag): void + { + $this->beStrictAboutTestsThatDoNotTestAnything = $flag; + } + + public function isStrictAboutTestsThatDoNotTestAnything(): bool + { + return $this->beStrictAboutTestsThatDoNotTestAnything; + } + + public function beStrictAboutOutputDuringTests(bool $flag): void + { + $this->beStrictAboutOutputDuringTests = $flag; + } + + public function isStrictAboutOutputDuringTests(): bool + { + return $this->beStrictAboutOutputDuringTests; + } + + public function beStrictAboutResourceUsageDuringSmallTests(bool $flag): void + { + $this->beStrictAboutResourceUsageDuringSmallTests = $flag; + } + + public function isStrictAboutResourceUsageDuringSmallTests(): bool + { + return $this->beStrictAboutResourceUsageDuringSmallTests; + } + + public function enforceTimeLimit(bool $flag): void + { + $this->enforceTimeLimit = $flag; + } + + public function enforcesTimeLimit(): bool + { + return $this->enforceTimeLimit; + } + + public function beStrictAboutTodoAnnotatedTests(bool $flag): void + { + $this->beStrictAboutTodoAnnotatedTests = $flag; + } + + public function isStrictAboutTodoAnnotatedTests(): bool + { + return $this->beStrictAboutTodoAnnotatedTests; + } + + public function forceCoversAnnotation(): void + { + $this->forceCoversAnnotation = true; + } + + public function forcesCoversAnnotation(): bool + { + return $this->forceCoversAnnotation; + } + + /** + * Enables or disables the stopping for risky tests. + */ + public function stopOnRisky(bool $flag): void + { + $this->stopOnRisky = $flag; + } + + /** + * Enables or disables the stopping for incomplete tests. + */ + public function stopOnIncomplete(bool $flag): void + { + $this->stopOnIncomplete = $flag; + } + + /** + * Enables or disables the stopping for skipped tests. + */ + public function stopOnSkipped(bool $flag): void + { + $this->stopOnSkipped = $flag; + } + + /** + * Enables or disables the stopping for defects: error, failure, warning. + */ + public function stopOnDefect(bool $flag): void + { + $this->stopOnDefect = $flag; + } + + /** + * Returns the time spent running the tests. + */ + public function time(): float + { + return $this->time; + } + + /** + * Returns whether the entire test was successful or not. + */ + public function wasSuccessful(): bool + { + return $this->wasSuccessfulIgnoringWarnings() && empty($this->warnings); + } + + public function wasSuccessfulIgnoringWarnings(): bool + { + return empty($this->errors) && empty($this->failures); + } + + public function wasSuccessfulAndNoTestIsRiskyOrSkippedOrIncomplete(): bool + { + return $this->wasSuccessful() && $this->allHarmless() && $this->allCompletelyImplemented() && $this->noneSkipped(); + } + + /** + * Sets the default timeout for tests. + */ + public function setDefaultTimeLimit(int $timeout): void + { + $this->defaultTimeLimit = $timeout; + } + + /** + * Sets the timeout for small tests. + */ + public function setTimeoutForSmallTests(int $timeout): void + { + $this->timeoutForSmallTests = $timeout; + } + + /** + * Sets the timeout for medium tests. + */ + public function setTimeoutForMediumTests(int $timeout): void + { + $this->timeoutForMediumTests = $timeout; + } + + /** + * Sets the timeout for large tests. + */ + public function setTimeoutForLargeTests(int $timeout): void + { + $this->timeoutForLargeTests = $timeout; + } + + /** + * Returns the set timeout for large tests. + */ + public function getTimeoutForLargeTests(): int + { + return $this->timeoutForLargeTests; + } + + public function setRegisterMockObjectsFromTestArgumentsRecursively(bool $flag): void + { + $this->registerMockObjectsFromTestArgumentsRecursively = $flag; + } + + private function recordError(Test $test, Throwable $t): void + { + $this->errors[] = new TestFailure($test, $t); + } + + private function recordNotImplemented(Test $test, Throwable $t): void + { + $this->notImplemented[] = new TestFailure($test, $t); + } + + private function recordRisky(Test $test, Throwable $t): void + { + $this->risky[] = new TestFailure($test, $t); + } + + private function recordSkipped(Test $test, Throwable $t): void + { + $this->skipped[] = new TestFailure($test, $t); + } + + private function recordWarning(Test $test, Throwable $t): void + { + $this->warnings[] = new TestFailure($test, $t); + } + + private function shouldTimeLimitBeEnforced(int $size): bool + { + if (!$this->enforceTimeLimit) { + return false; + } + + if (!(($this->defaultTimeLimit || $size !== TestUtil::UNKNOWN))) { + return false; + } + + if (!extension_loaded('pcntl')) { + return false; + } + + if (!class_exists(Invoker::class)) { + return false; + } + + if (extension_loaded('xdebug') && xdebug_is_debugger_active()) { + return false; + } + + return true; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/TestSuite.php b/vendor/phpunit/phpunit/src/Framework/TestSuite.php new file mode 100644 index 00000000..c60678d0 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/TestSuite.php @@ -0,0 +1,921 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use const PHP_EOL; +use function array_keys; +use function array_map; +use function array_merge; +use function array_slice; +use function array_unique; +use function basename; +use function call_user_func; +use function class_exists; +use function count; +use function dirname; +use function get_declared_classes; +use function implode; +use function is_bool; +use function is_callable; +use function is_file; +use function is_object; +use function is_string; +use function method_exists; +use function preg_match; +use function preg_quote; +use function sprintf; +use function strpos; +use function substr; +use Iterator; +use IteratorAggregate; +use PHPUnit\Runner\BaseTestRunner; +use PHPUnit\Runner\Filter\Factory; +use PHPUnit\Runner\PhptTestCase; +use PHPUnit\Util\FileLoader; +use PHPUnit\Util\Reflection; +use PHPUnit\Util\Test as TestUtil; +use ReflectionClass; +use ReflectionException; +use ReflectionMethod; +use Throwable; + +/** + * @template-implements IteratorAggregate + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +class TestSuite implements IteratorAggregate, Reorderable, SelfDescribing, Test +{ + /** + * Enable or disable the backup and restoration of the $GLOBALS array. + * + * @var bool + */ + protected $backupGlobals; + + /** + * Enable or disable the backup and restoration of static attributes. + * + * @var bool + */ + protected $backupStaticAttributes; + + /** + * @var bool + */ + protected $runTestInSeparateProcess = false; + + /** + * The name of the test suite. + * + * @var string + */ + protected $name = ''; + + /** + * The test groups of the test suite. + * + * @psalm-var array> + */ + protected $groups = []; + + /** + * The tests in the test suite. + * + * @var Test[] + */ + protected $tests = []; + + /** + * The number of tests in the test suite. + * + * @var int + */ + protected $numTests = -1; + + /** + * @var bool + */ + protected $testCase = false; + + /** + * @var string[] + */ + protected $foundClasses = []; + + /** + * @var null|list + */ + protected $providedTests; + + /** + * @var null|list + */ + protected $requiredTests; + + /** + * @var bool + */ + private $beStrictAboutChangesToGlobalState; + + /** + * @var Factory + */ + private $iteratorFilter; + + /** + * @var int + */ + private $declaredClassesPointer; + + /** + * @psalm-var array + */ + private $warnings = []; + + /** + * Constructs a new TestSuite. + * + * - PHPUnit\Framework\TestSuite() constructs an empty TestSuite. + * + * - PHPUnit\Framework\TestSuite(ReflectionClass) constructs a + * TestSuite from the given class. + * + * - PHPUnit\Framework\TestSuite(ReflectionClass, String) + * constructs a TestSuite from the given class with the given + * name. + * + * - PHPUnit\Framework\TestSuite(String) either constructs a + * TestSuite from the given class (if the passed string is the + * name of an existing class) or constructs an empty TestSuite + * with the given name. + * + * @param ReflectionClass|string $theClass + * + * @throws Exception + */ + public function __construct($theClass = '', string $name = '') + { + if (!is_string($theClass) && !$theClass instanceof ReflectionClass) { + throw InvalidArgumentException::create( + 1, + 'ReflectionClass object or string', + ); + } + + $this->declaredClassesPointer = count(get_declared_classes()); + + if (!$theClass instanceof ReflectionClass) { + if (class_exists($theClass, true)) { + if ($name === '') { + $name = $theClass; + } + + try { + $theClass = new ReflectionClass($theClass); + } catch (ReflectionException $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + } else { + $this->setName($theClass); + + return; + } + } + + if (!$theClass->isSubclassOf(TestCase::class)) { + $this->setName((string) $theClass); + + return; + } + + if ($name !== '') { + $this->setName($name); + } else { + $this->setName($theClass->getName()); + } + + $constructor = $theClass->getConstructor(); + + if ($constructor !== null && + !$constructor->isPublic()) { + $this->addTest( + new WarningTestCase( + sprintf( + 'Class "%s" has no public constructor.', + $theClass->getName(), + ), + ), + ); + + return; + } + + foreach ((new Reflection)->publicMethodsInTestClass($theClass) as $method) { + if (!TestUtil::isTestMethod($method)) { + continue; + } + + $this->addTestMethod($theClass, $method); + } + + if (empty($this->tests)) { + $this->addTest( + new WarningTestCase( + sprintf( + 'No tests found in class "%s".', + $theClass->getName(), + ), + ), + ); + } + + $this->testCase = true; + } + + /** + * Returns a string representation of the test suite. + */ + public function toString(): string + { + return $this->getName(); + } + + /** + * Adds a test to the suite. + * + * @param array $groups + */ + public function addTest(Test $test, $groups = []): void + { + try { + $class = new ReflectionClass($test); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + if (!$class->isAbstract()) { + $this->tests[] = $test; + $this->clearCaches(); + + if ($test instanceof self && empty($groups)) { + $groups = $test->getGroups(); + } + + if ($this->containsOnlyVirtualGroups($groups)) { + $groups[] = 'default'; + } + + foreach ($groups as $group) { + if (!isset($this->groups[$group])) { + $this->groups[$group] = [$test]; + } else { + $this->groups[$group][] = $test; + } + } + + if ($test instanceof TestCase) { + $test->setGroups($groups); + } + } + } + + /** + * Adds the tests from the given class to the suite. + * + * @psalm-param object|class-string $testClass + * + * @throws Exception + */ + public function addTestSuite($testClass): void + { + if (!(is_object($testClass) || (is_string($testClass) && class_exists($testClass)))) { + throw InvalidArgumentException::create( + 1, + 'class name or object', + ); + } + + if (!is_object($testClass)) { + try { + $testClass = new ReflectionClass($testClass); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + } + + if ($testClass instanceof self) { + $this->addTest($testClass); + } elseif ($testClass instanceof ReflectionClass) { + $suiteMethod = false; + + if (!$testClass->isAbstract() && $testClass->hasMethod(BaseTestRunner::SUITE_METHODNAME)) { + try { + $method = $testClass->getMethod( + BaseTestRunner::SUITE_METHODNAME, + ); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + if ($method->isStatic()) { + $this->addTest( + $method->invoke(null, $testClass->getName()), + ); + + $suiteMethod = true; + } + } + + if (!$suiteMethod && !$testClass->isAbstract() && $testClass->isSubclassOf(TestCase::class)) { + $this->addTest(new self($testClass)); + } + } else { + throw new Exception; + } + } + + public function addWarning(string $warning): void + { + $this->warnings[] = $warning; + } + + /** + * Wraps both addTest() and addTestSuite + * as well as the separate import statements for the user's convenience. + * + * If the named file cannot be read or there are no new tests that can be + * added, a PHPUnit\Framework\WarningTestCase will be created instead, + * leaving the current test run untouched. + * + * @throws Exception + */ + public function addTestFile(string $filename): void + { + if (is_file($filename) && substr($filename, -5) === '.phpt') { + $this->addTest(new PhptTestCase($filename)); + + $this->declaredClassesPointer = count(get_declared_classes()); + + return; + } + + $numTests = count($this->tests); + + // The given file may contain further stub classes in addition to the + // test class itself. Figure out the actual test class. + $filename = FileLoader::checkAndLoad($filename); + $newClasses = array_slice(get_declared_classes(), $this->declaredClassesPointer); + + // The diff is empty in case a parent class (with test methods) is added + // AFTER a child class that inherited from it. To account for that case, + // accumulate all discovered classes, so the parent class may be found in + // a later invocation. + if (!empty($newClasses)) { + // On the assumption that test classes are defined first in files, + // process discovered classes in approximate LIFO order, so as to + // avoid unnecessary reflection. + $this->foundClasses = array_merge($newClasses, $this->foundClasses); + $this->declaredClassesPointer = count(get_declared_classes()); + } + + // The test class's name must match the filename, either in full, or as + // a PEAR/PSR-0 prefixed short name ('NameSpace_ShortName'), or as a + // PSR-1 local short name ('NameSpace\ShortName'). The comparison must be + // anchored to prevent false-positive matches (e.g., 'OtherShortName'). + $shortName = basename($filename, '.php'); + $shortNameRegEx = '/(?:^|_|\\\\)' . preg_quote($shortName, '/') . '$/'; + + foreach ($this->foundClasses as $i => $className) { + if (preg_match($shortNameRegEx, $className)) { + try { + $class = new ReflectionClass($className); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + if ($class->getFileName() == $filename) { + $newClasses = [$className]; + unset($this->foundClasses[$i]); + + break; + } + } + } + + foreach ($newClasses as $className) { + try { + $class = new ReflectionClass($className); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + if (dirname($class->getFileName()) === __DIR__) { + continue; + } + + if ($class->isAbstract() && $class->isSubclassOf(TestCase::class)) { + $this->addWarning( + sprintf( + 'Abstract test case classes with "Test" suffix are deprecated (%s)', + $class->getName(), + ), + ); + } + + if (!$class->isAbstract()) { + if ($class->hasMethod(BaseTestRunner::SUITE_METHODNAME)) { + try { + $method = $class->getMethod( + BaseTestRunner::SUITE_METHODNAME, + ); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + if ($method->isStatic()) { + $this->addTest($method->invoke(null, $className)); + } + } elseif ($class->implementsInterface(Test::class)) { + // Do we have modern namespacing ('Foo\Bar\WhizBangTest') or old-school namespacing ('Foo_Bar_WhizBangTest')? + $isPsr0 = (!$class->inNamespace()) && (strpos($class->getName(), '_') !== false); + $expectedClassName = $isPsr0 ? $className : $shortName; + + if (($pos = strpos($expectedClassName, '.')) !== false) { + $expectedClassName = substr( + $expectedClassName, + 0, + $pos, + ); + } + + if ($class->getShortName() !== $expectedClassName) { + $this->addWarning( + sprintf( + "Test case class not matching filename is deprecated\n in %s\n Class name was '%s', expected '%s'", + $filename, + $class->getShortName(), + $expectedClassName, + ), + ); + } + + $this->addTestSuite($class); + } + } + } + + if (count($this->tests) > ++$numTests) { + $this->addWarning( + sprintf( + "Multiple test case classes per file is deprecated\n in %s", + $filename, + ), + ); + } + + $this->numTests = -1; + } + + /** + * Wrapper for addTestFile() that adds multiple test files. + * + * @throws Exception + */ + public function addTestFiles(iterable $fileNames): void + { + foreach ($fileNames as $filename) { + $this->addTestFile((string) $filename); + } + } + + /** + * Counts the number of test cases that will be run by this test. + * + * @todo refactor usage of numTests in DefaultResultPrinter + */ + public function count(): int + { + $this->numTests = 0; + + foreach ($this as $test) { + $this->numTests += count($test); + } + + return $this->numTests; + } + + /** + * Returns the name of the suite. + */ + public function getName(): string + { + return $this->name; + } + + /** + * Returns the test groups of the suite. + * + * @psalm-return list + */ + public function getGroups(): array + { + return array_map( + static function ($key): string + { + return (string) $key; + }, + array_keys($this->groups), + ); + } + + public function getGroupDetails(): array + { + return $this->groups; + } + + /** + * Set tests groups of the test case. + */ + public function setGroupDetails(array $groups): void + { + $this->groups = $groups; + } + + /** + * Runs the tests and collects their result in a TestResult. + * + * @throws \SebastianBergmann\CodeCoverage\InvalidArgumentException + * @throws \SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws CodeCoverageException + * @throws Warning + */ + public function run(TestResult $result = null): TestResult + { + if ($result === null) { + $result = $this->createResult(); + } + + if (count($this) === 0) { + return $result; + } + + /** @psalm-var class-string $className */ + $className = $this->name; + $hookMethods = TestUtil::getHookMethods($className); + + $result->startTestSuite($this); + + $test = null; + + if ($this->testCase && class_exists($this->name, false)) { + try { + foreach ($hookMethods['beforeClass'] as $beforeClassMethod) { + if (method_exists($this->name, $beforeClassMethod)) { + if ($missingRequirements = TestUtil::getMissingRequirements($this->name, $beforeClassMethod)) { + $this->markTestSuiteSkipped(implode(PHP_EOL, $missingRequirements)); + } + + call_user_func([$this->name, $beforeClassMethod]); + } + } + } catch (SkippedTestError|SkippedTestSuiteError $error) { + foreach ($this->tests() as $test) { + $result->startTest($test); + $result->addFailure($test, $error, 0); + $result->endTest($test, 0); + } + + $result->endTestSuite($this); + + return $result; + } catch (Throwable $t) { + $errorAdded = false; + + foreach ($this->tests() as $test) { + if ($result->shouldStop()) { + break; + } + + $result->startTest($test); + + if (!$errorAdded) { + $result->addError($test, $t, 0); + + $errorAdded = true; + } else { + $result->addFailure( + $test, + new SkippedTestError('Test skipped because of an error in hook method'), + 0, + ); + } + + $result->endTest($test, 0); + } + + $result->endTestSuite($this); + + return $result; + } + } + + foreach ($this as $test) { + if ($result->shouldStop()) { + break; + } + + if ($test instanceof TestCase || $test instanceof self) { + $test->setBeStrictAboutChangesToGlobalState($this->beStrictAboutChangesToGlobalState); + $test->setBackupGlobals($this->backupGlobals); + $test->setBackupStaticAttributes($this->backupStaticAttributes); + $test->setRunTestInSeparateProcess($this->runTestInSeparateProcess); + } + + $test->run($result); + } + + if ($this->testCase && class_exists($this->name, false)) { + foreach ($hookMethods['afterClass'] as $afterClassMethod) { + if (method_exists($this->name, $afterClassMethod)) { + try { + call_user_func([$this->name, $afterClassMethod]); + } catch (Throwable $t) { + $message = "Exception in {$this->name}::{$afterClassMethod}" . PHP_EOL . $t->getMessage(); + $error = new SyntheticError($message, 0, $t->getFile(), $t->getLine(), $t->getTrace()); + + $placeholderTest = clone $test; + $placeholderTest->setName($afterClassMethod); + + $result->startTest($placeholderTest); + $result->addFailure($placeholderTest, $error, 0); + $result->endTest($placeholderTest, 0); + } + } + } + } + + $result->endTestSuite($this); + + return $result; + } + + public function setRunTestInSeparateProcess(bool $runTestInSeparateProcess): void + { + $this->runTestInSeparateProcess = $runTestInSeparateProcess; + } + + public function setName(string $name): void + { + $this->name = $name; + } + + /** + * Returns the tests as an enumeration. + * + * @return Test[] + */ + public function tests(): array + { + return $this->tests; + } + + /** + * Set tests of the test suite. + * + * @param Test[] $tests + */ + public function setTests(array $tests): void + { + $this->tests = $tests; + } + + /** + * Mark the test suite as skipped. + * + * @param string $message + * + * @throws SkippedTestSuiteError + * + * @psalm-return never-return + */ + public function markTestSuiteSkipped($message = ''): void + { + throw new SkippedTestSuiteError($message); + } + + /** + * @param bool $beStrictAboutChangesToGlobalState + */ + public function setBeStrictAboutChangesToGlobalState($beStrictAboutChangesToGlobalState): void + { + if (null === $this->beStrictAboutChangesToGlobalState && is_bool($beStrictAboutChangesToGlobalState)) { + $this->beStrictAboutChangesToGlobalState = $beStrictAboutChangesToGlobalState; + } + } + + /** + * @param bool $backupGlobals + */ + public function setBackupGlobals($backupGlobals): void + { + if (null === $this->backupGlobals && is_bool($backupGlobals)) { + $this->backupGlobals = $backupGlobals; + } + } + + /** + * @param bool $backupStaticAttributes + */ + public function setBackupStaticAttributes($backupStaticAttributes): void + { + if (null === $this->backupStaticAttributes && is_bool($backupStaticAttributes)) { + $this->backupStaticAttributes = $backupStaticAttributes; + } + } + + /** + * Returns an iterator for this test suite. + */ + public function getIterator(): Iterator + { + $iterator = new TestSuiteIterator($this); + + if ($this->iteratorFilter !== null) { + $iterator = $this->iteratorFilter->factory($iterator, $this); + } + + return $iterator; + } + + public function injectFilter(Factory $filter): void + { + $this->iteratorFilter = $filter; + + foreach ($this as $test) { + if ($test instanceof self) { + $test->injectFilter($filter); + } + } + } + + /** + * @psalm-return array + */ + public function warnings(): array + { + return array_unique($this->warnings); + } + + /** + * @return list + */ + public function provides(): array + { + if ($this->providedTests === null) { + $this->providedTests = []; + + if (is_callable($this->sortId(), true)) { + $this->providedTests[] = new ExecutionOrderDependency($this->sortId()); + } + + foreach ($this->tests as $test) { + if (!($test instanceof Reorderable)) { + // @codeCoverageIgnoreStart + continue; + // @codeCoverageIgnoreEnd + } + $this->providedTests = ExecutionOrderDependency::mergeUnique($this->providedTests, $test->provides()); + } + } + + return $this->providedTests; + } + + /** + * @return list + */ + public function requires(): array + { + if ($this->requiredTests === null) { + $this->requiredTests = []; + + foreach ($this->tests as $test) { + if (!($test instanceof Reorderable)) { + // @codeCoverageIgnoreStart + continue; + // @codeCoverageIgnoreEnd + } + $this->requiredTests = ExecutionOrderDependency::mergeUnique( + ExecutionOrderDependency::filterInvalid($this->requiredTests), + $test->requires(), + ); + } + + $this->requiredTests = ExecutionOrderDependency::diff($this->requiredTests, $this->provides()); + } + + return $this->requiredTests; + } + + public function sortId(): string + { + return $this->getName() . '::class'; + } + + /** + * Creates a default TestResult object. + */ + protected function createResult(): TestResult + { + return new TestResult; + } + + /** + * @throws Exception + */ + protected function addTestMethod(ReflectionClass $class, ReflectionMethod $method): void + { + $methodName = $method->getName(); + + $test = (new TestBuilder)->build($class, $methodName); + + if ($test instanceof TestCase || $test instanceof DataProviderTestSuite) { + $test->setDependencies( + TestUtil::getDependencies($class->getName(), $methodName), + ); + } + + $this->addTest( + $test, + TestUtil::getGroups($class->getName(), $methodName), + ); + } + + private function clearCaches(): void + { + $this->numTests = -1; + $this->providedTests = null; + $this->requiredTests = null; + } + + private function containsOnlyVirtualGroups(array $groups): bool + { + foreach ($groups as $group) { + if (strpos($group, '__phpunit_') !== 0) { + return false; + } + } + + return true; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/TestSuiteIterator.php b/vendor/phpunit/phpunit/src/Framework/TestSuiteIterator.php new file mode 100644 index 00000000..27c9d8b4 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/TestSuiteIterator.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function assert; +use function count; +use RecursiveIterator; + +/** + * @template-implements RecursiveIterator + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TestSuiteIterator implements RecursiveIterator +{ + /** + * @var int + */ + private $position = 0; + + /** + * @var Test[] + */ + private $tests; + + public function __construct(TestSuite $testSuite) + { + $this->tests = $testSuite->tests(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->tests); + } + + public function key(): int + { + return $this->position; + } + + public function current(): Test + { + return $this->tests[$this->position]; + } + + public function next(): void + { + $this->position++; + } + + /** + * @throws NoChildTestSuiteException + */ + public function getChildren(): self + { + if (!$this->hasChildren()) { + throw new NoChildTestSuiteException( + 'The current item is not a TestSuite instance and therefore does not have any children.', + ); + } + + $current = $this->current(); + + assert($current instanceof TestSuite); + + return new self($current); + } + + public function hasChildren(): bool + { + return $this->valid() && $this->current() instanceof TestSuite; + } +} diff --git a/vendor/phpunit/phpunit/src/Framework/WarningTestCase.php b/vendor/phpunit/phpunit/src/Framework/WarningTestCase.php new file mode 100644 index 00000000..d27c6b57 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Framework/WarningTestCase.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class WarningTestCase extends TestCase +{ + /** + * @var ?bool + */ + protected $backupGlobals = false; + + /** + * @var ?bool + */ + protected $backupStaticAttributes = false; + + /** + * @var ?bool + */ + protected $runTestInSeparateProcess = false; + + /** + * @var string + */ + private $message; + + public function __construct(string $message = '') + { + $this->message = $message; + + parent::__construct('Warning'); + } + + public function getMessage(): string + { + return $this->message; + } + + /** + * Returns a string representation of the test case. + */ + public function toString(): string + { + return 'Warning'; + } + + /** + * @throws Exception + * + * @psalm-return never-return + */ + protected function runTest(): void + { + throw new Warning($this->message); + } +} diff --git a/vendor/phpunit/phpunit/src/Runner/BaseTestRunner.php b/vendor/phpunit/phpunit/src/Runner/BaseTestRunner.php new file mode 100644 index 00000000..bbef329f --- /dev/null +++ b/vendor/phpunit/phpunit/src/Runner/BaseTestRunner.php @@ -0,0 +1,161 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +use function is_dir; +use function is_file; +use function substr; +use PHPUnit\Framework\Exception; +use PHPUnit\Framework\TestSuite; +use ReflectionClass; +use ReflectionException; +use SebastianBergmann\FileIterator\Facade as FileIteratorFacade; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract class BaseTestRunner +{ + /** + * @var int + */ + public const STATUS_UNKNOWN = -1; + + /** + * @var int + */ + public const STATUS_PASSED = 0; + + /** + * @var int + */ + public const STATUS_SKIPPED = 1; + + /** + * @var int + */ + public const STATUS_INCOMPLETE = 2; + + /** + * @var int + */ + public const STATUS_FAILURE = 3; + + /** + * @var int + */ + public const STATUS_ERROR = 4; + + /** + * @var int + */ + public const STATUS_RISKY = 5; + + /** + * @var int + */ + public const STATUS_WARNING = 6; + + /** + * @var string + */ + public const SUITE_METHODNAME = 'suite'; + + /** + * Returns the loader to be used. + */ + public function getLoader(): TestSuiteLoader + { + return new StandardTestSuiteLoader; + } + + /** + * Returns the Test corresponding to the given suite. + * This is a template method, subclasses override + * the runFailed() and clearStatus() methods. + * + * @param string|string[] $suffixes + * + * @throws Exception + */ + public function getTest(string $suiteClassFile, $suffixes = ''): ?TestSuite + { + if (is_dir($suiteClassFile)) { + /** @var string[] $files */ + $files = (new FileIteratorFacade)->getFilesAsArray( + $suiteClassFile, + $suffixes, + ); + + $suite = new TestSuite($suiteClassFile); + $suite->addTestFiles($files); + + return $suite; + } + + if (is_file($suiteClassFile) && substr($suiteClassFile, -5, 5) === '.phpt') { + $suite = new TestSuite; + $suite->addTestFile($suiteClassFile); + + return $suite; + } + + try { + $testClass = $this->loadSuiteClass( + $suiteClassFile, + ); + } catch (\PHPUnit\Exception $e) { + $this->runFailed($e->getMessage()); + + return null; + } + + try { + $suiteMethod = $testClass->getMethod(self::SUITE_METHODNAME); + + if (!$suiteMethod->isStatic()) { + $this->runFailed( + 'suite() method must be static.', + ); + + return null; + } + + $test = $suiteMethod->invoke(null, $testClass->getName()); + } catch (ReflectionException $e) { + $test = new TestSuite($testClass); + } + + $this->clearStatus(); + + return $test; + } + + /** + * Returns the loaded ReflectionClass for a suite name. + */ + protected function loadSuiteClass(string $suiteClassFile): ReflectionClass + { + return $this->getLoader()->load($suiteClassFile); + } + + /** + * Clears the status message. + */ + protected function clearStatus(): void + { + } + + /** + * Override to define how to handle a failed loading of + * a test suite. + */ + abstract protected function runFailed(string $message): void; +} diff --git a/vendor/phpunit/phpunit/src/Runner/DefaultTestResultCache.php b/vendor/phpunit/phpunit/src/Runner/DefaultTestResultCache.php new file mode 100644 index 00000000..f9d8a90d --- /dev/null +++ b/vendor/phpunit/phpunit/src/Runner/DefaultTestResultCache.php @@ -0,0 +1,158 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +use const DIRECTORY_SEPARATOR; +use const LOCK_EX; +use function assert; +use function dirname; +use function file_get_contents; +use function file_put_contents; +use function in_array; +use function is_array; +use function is_dir; +use function is_file; +use function json_decode; +use function json_encode; +use function sprintf; +use PHPUnit\Util\Filesystem; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class DefaultTestResultCache implements TestResultCache +{ + /** + * @var int + */ + private const VERSION = 1; + + /** + * @psalm-var list + */ + private const ALLOWED_TEST_STATUSES = [ + BaseTestRunner::STATUS_SKIPPED, + BaseTestRunner::STATUS_INCOMPLETE, + BaseTestRunner::STATUS_FAILURE, + BaseTestRunner::STATUS_ERROR, + BaseTestRunner::STATUS_RISKY, + BaseTestRunner::STATUS_WARNING, + ]; + + /** + * @var string + */ + private const DEFAULT_RESULT_CACHE_FILENAME = '.phpunit.result.cache'; + + /** + * @var string + */ + private $cacheFilename; + + /** + * @psalm-var array + */ + private $defects = []; + + /** + * @psalm-var array + */ + private $times = []; + + public function __construct(?string $filepath = null) + { + if ($filepath !== null && is_dir($filepath)) { + $filepath .= DIRECTORY_SEPARATOR . self::DEFAULT_RESULT_CACHE_FILENAME; + } + + $this->cacheFilename = $filepath ?? $_ENV['PHPUNIT_RESULT_CACHE'] ?? self::DEFAULT_RESULT_CACHE_FILENAME; + } + + public function setState(string $testName, int $state): void + { + if (!in_array($state, self::ALLOWED_TEST_STATUSES, true)) { + return; + } + + $this->defects[$testName] = $state; + } + + public function getState(string $testName): int + { + return $this->defects[$testName] ?? BaseTestRunner::STATUS_UNKNOWN; + } + + public function setTime(string $testName, float $time): void + { + $this->times[$testName] = $time; + } + + public function getTime(string $testName): float + { + return $this->times[$testName] ?? 0.0; + } + + public function load(): void + { + if (!is_file($this->cacheFilename)) { + return; + } + + $data = json_decode( + file_get_contents($this->cacheFilename), + true, + ); + + if ($data === null) { + return; + } + + if (!isset($data['version'])) { + return; + } + + if ($data['version'] !== self::VERSION) { + return; + } + + assert(isset($data['defects']) && is_array($data['defects'])); + assert(isset($data['times']) && is_array($data['times'])); + + $this->defects = $data['defects']; + $this->times = $data['times']; + } + + /** + * @throws Exception + */ + public function persist(): void + { + if (!Filesystem::createDirectory(dirname($this->cacheFilename))) { + throw new Exception( + sprintf( + 'Cannot create directory "%s" for result cache file', + $this->cacheFilename, + ), + ); + } + + file_put_contents( + $this->cacheFilename, + json_encode( + [ + 'version' => self::VERSION, + 'defects' => $this->defects, + 'times' => $this->times, + ], + ), + LOCK_EX, + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Runner/Exception.php b/vendor/phpunit/phpunit/src/Runner/Exception.php new file mode 100644 index 00000000..adcd1155 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Runner/Exception.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +use RuntimeException; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Exception extends RuntimeException implements \PHPUnit\Exception +{ +} diff --git a/vendor/phpunit/phpunit/src/Runner/Extension/ExtensionHandler.php b/vendor/phpunit/phpunit/src/Runner/Extension/ExtensionHandler.php new file mode 100644 index 00000000..c57e70e7 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Runner/Extension/ExtensionHandler.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Extension; + +use function class_exists; +use function sprintf; +use PHPUnit\Framework\TestListener; +use PHPUnit\Runner\Exception; +use PHPUnit\Runner\Hook; +use PHPUnit\TextUI\TestRunner; +use PHPUnit\TextUI\XmlConfiguration\Extension; +use ReflectionClass; +use ReflectionException; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ExtensionHandler +{ + /** + * @throws Exception + */ + public function registerExtension(Extension $extensionConfiguration, TestRunner $runner): void + { + $extension = $this->createInstance($extensionConfiguration); + + if (!$extension instanceof Hook) { + throw new Exception( + sprintf( + 'Class "%s" does not implement a PHPUnit\Runner\Hook interface', + $extensionConfiguration->className(), + ), + ); + } + + $runner->addExtension($extension); + } + + /** + * @throws Exception + * + * @deprecated + */ + public function createTestListenerInstance(Extension $listenerConfiguration): TestListener + { + $listener = $this->createInstance($listenerConfiguration); + + if (!$listener instanceof TestListener) { + throw new Exception( + sprintf( + 'Class "%s" does not implement the PHPUnit\Framework\TestListener interface', + $listenerConfiguration->className(), + ), + ); + } + + return $listener; + } + + /** + * @throws Exception + */ + private function createInstance(Extension $extensionConfiguration): object + { + $this->ensureClassExists($extensionConfiguration); + + try { + $reflector = new ReflectionClass($extensionConfiguration->className()); + } catch (ReflectionException $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + + if (!$extensionConfiguration->hasArguments()) { + return $reflector->newInstance(); + } + + return $reflector->newInstanceArgs($extensionConfiguration->arguments()); + } + + /** + * @throws Exception + */ + private function ensureClassExists(Extension $extensionConfiguration): void + { + if (class_exists($extensionConfiguration->className(), false)) { + return; + } + + if ($extensionConfiguration->hasSourceFile()) { + /** + * @noinspection PhpIncludeInspection + * + * @psalm-suppress UnresolvableInclude + */ + require_once $extensionConfiguration->sourceFile(); + } + + if (!class_exists($extensionConfiguration->className())) { + throw new Exception( + sprintf( + 'Class "%s" does not exist', + $extensionConfiguration->className(), + ), + ); + } + } +} diff --git a/vendor/phpunit/phpunit/src/Runner/Extension/PharLoader.php b/vendor/phpunit/phpunit/src/Runner/Extension/PharLoader.php new file mode 100644 index 00000000..c65b1948 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Runner/Extension/PharLoader.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Extension; + +use function count; +use function explode; +use function implode; +use function is_file; +use function strpos; +use PharIo\Manifest\ApplicationName; +use PharIo\Manifest\Exception as ManifestException; +use PharIo\Manifest\ManifestLoader; +use PharIo\Version\Version as PharIoVersion; +use PHPUnit\Runner\Version; +use SebastianBergmann\FileIterator\Facade as FileIteratorFacade; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class PharLoader +{ + /** + * @psalm-return array{loadedExtensions: list, notLoadedExtensions: list} + */ + public function loadPharExtensionsInDirectory(string $directory): array + { + $loadedExtensions = []; + $notLoadedExtensions = []; + + foreach ((new FileIteratorFacade)->getFilesAsArray($directory, '.phar') as $file) { + if (!is_file('phar://' . $file . '/manifest.xml')) { + $notLoadedExtensions[] = $file . ' is not an extension for PHPUnit'; + + continue; + } + + try { + $applicationName = new ApplicationName('phpunit/phpunit'); + $version = new PharIoVersion($this->phpunitVersion()); + $manifest = ManifestLoader::fromFile('phar://' . $file . '/manifest.xml'); + + if (!$manifest->isExtensionFor($applicationName)) { + $notLoadedExtensions[] = $file . ' is not an extension for PHPUnit'; + + continue; + } + + if (!$manifest->isExtensionFor($applicationName, $version)) { + $notLoadedExtensions[] = $file . ' is not compatible with this version of PHPUnit'; + + continue; + } + } catch (ManifestException $e) { + $notLoadedExtensions[] = $file . ': ' . $e->getMessage(); + + continue; + } + + /** + * @noinspection PhpIncludeInspection + * + * @psalm-suppress UnresolvableInclude + */ + require $file; + + $loadedExtensions[] = $manifest->getName()->asString() . ' ' . $manifest->getVersion()->getVersionString(); + } + + return [ + 'loadedExtensions' => $loadedExtensions, + 'notLoadedExtensions' => $notLoadedExtensions, + ]; + } + + private function phpunitVersion(): string + { + $version = Version::id(); + + if (strpos($version, '-') === false) { + return $version; + } + + $parts = explode('.', explode('-', $version)[0]); + + if (count($parts) === 2) { + $parts[] = 0; + } + + return implode('.', $parts); + } +} diff --git a/vendor/phpunit/phpunit/src/Runner/Filter/ExcludeGroupFilterIterator.php b/vendor/phpunit/phpunit/src/Runner/Filter/ExcludeGroupFilterIterator.php new file mode 100644 index 00000000..4b26e571 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Runner/Filter/ExcludeGroupFilterIterator.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Filter; + +use function in_array; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ExcludeGroupFilterIterator extends GroupFilterIterator +{ + protected function doAccept(string $hash): bool + { + return !in_array($hash, $this->groupTests, true); + } +} diff --git a/vendor/phpunit/phpunit/src/Runner/Filter/Factory.php b/vendor/phpunit/phpunit/src/Runner/Filter/Factory.php new file mode 100644 index 00000000..b7d83b9f --- /dev/null +++ b/vendor/phpunit/phpunit/src/Runner/Filter/Factory.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Filter; + +use function assert; +use function sprintf; +use FilterIterator; +use Iterator; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Runner\Exception; +use RecursiveFilterIterator; +use ReflectionClass; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Factory +{ + /** + * @psalm-var array + */ + private $filters = []; + + /** + * @param array|string $args + * + * @throws Exception + */ + public function addFilter(ReflectionClass $filter, $args): void + { + if (!$filter->isSubclassOf(RecursiveFilterIterator::class)) { + throw new Exception( + sprintf( + 'Class "%s" does not extend RecursiveFilterIterator', + $filter->name, + ), + ); + } + + $this->filters[] = [$filter, $args]; + } + + public function factory(Iterator $iterator, TestSuite $suite): FilterIterator + { + foreach ($this->filters as $filter) { + [$class, $args] = $filter; + $iterator = $class->newInstance($iterator, $args, $suite); + } + + assert($iterator instanceof FilterIterator); + + return $iterator; + } +} diff --git a/vendor/phpunit/phpunit/src/Runner/Filter/GroupFilterIterator.php b/vendor/phpunit/phpunit/src/Runner/Filter/GroupFilterIterator.php new file mode 100644 index 00000000..b203c196 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Runner/Filter/GroupFilterIterator.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Filter; + +use function array_map; +use function array_merge; +use function in_array; +use function spl_object_hash; +use PHPUnit\Framework\TestSuite; +use RecursiveFilterIterator; +use RecursiveIterator; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract class GroupFilterIterator extends RecursiveFilterIterator +{ + /** + * @var string[] + */ + protected $groupTests = []; + + public function __construct(RecursiveIterator $iterator, array $groups, TestSuite $suite) + { + parent::__construct($iterator); + + foreach ($suite->getGroupDetails() as $group => $tests) { + if (in_array((string) $group, $groups, true)) { + $testHashes = array_map( + 'spl_object_hash', + $tests, + ); + + $this->groupTests = array_merge($this->groupTests, $testHashes); + } + } + } + + public function accept(): bool + { + $test = $this->getInnerIterator()->current(); + + if ($test instanceof TestSuite) { + return true; + } + + return $this->doAccept(spl_object_hash($test)); + } + + abstract protected function doAccept(string $hash); +} diff --git a/vendor/phpunit/phpunit/src/Runner/Filter/IncludeGroupFilterIterator.php b/vendor/phpunit/phpunit/src/Runner/Filter/IncludeGroupFilterIterator.php new file mode 100644 index 00000000..0346c601 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Runner/Filter/IncludeGroupFilterIterator.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Filter; + +use function in_array; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class IncludeGroupFilterIterator extends GroupFilterIterator +{ + protected function doAccept(string $hash): bool + { + return in_array($hash, $this->groupTests, true); + } +} diff --git a/vendor/phpunit/phpunit/src/Runner/Filter/NameFilterIterator.php b/vendor/phpunit/phpunit/src/Runner/Filter/NameFilterIterator.php new file mode 100644 index 00000000..45c62f05 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Runner/Filter/NameFilterIterator.php @@ -0,0 +1,136 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Filter; + +use function end; +use function implode; +use function preg_match; +use function sprintf; +use function str_replace; +use Exception; +use PHPUnit\Framework\ErrorTestCase; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Framework\WarningTestCase; +use PHPUnit\Util\RegularExpression; +use RecursiveFilterIterator; +use RecursiveIterator; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class NameFilterIterator extends RecursiveFilterIterator +{ + /** + * @var string + */ + private $filter; + + /** + * @var int + */ + private $filterMin; + + /** + * @var int + */ + private $filterMax; + + /** + * @throws Exception + */ + public function __construct(RecursiveIterator $iterator, string $filter) + { + parent::__construct($iterator); + + $this->setFilter($filter); + } + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function accept(): bool + { + $test = $this->getInnerIterator()->current(); + + if ($test instanceof TestSuite) { + return true; + } + + $tmp = \PHPUnit\Util\Test::describe($test); + + if ($test instanceof ErrorTestCase || $test instanceof WarningTestCase) { + $name = $test->getMessage(); + } elseif ($tmp[0] !== '') { + $name = implode('::', $tmp); + } else { + $name = $tmp[1]; + } + + $accepted = @preg_match($this->filter, $name, $matches); + + if ($accepted && isset($this->filterMax)) { + $set = end($matches); + $accepted = $set >= $this->filterMin && $set <= $this->filterMax; + } + + return (bool) $accepted; + } + + /** + * @throws Exception + */ + private function setFilter(string $filter): void + { + if (RegularExpression::safeMatch($filter, '') === false) { + // Handles: + // * testAssertEqualsSucceeds#4 + // * testAssertEqualsSucceeds#4-8 + if (preg_match('/^(.*?)#(\d+)(?:-(\d+))?$/', $filter, $matches)) { + if (isset($matches[3]) && $matches[2] < $matches[3]) { + $filter = sprintf( + '%s.*with data set #(\d+)$', + $matches[1], + ); + + $this->filterMin = (int) $matches[2]; + $this->filterMax = (int) $matches[3]; + } else { + $filter = sprintf( + '%s.*with data set #%s$', + $matches[1], + $matches[2], + ); + } + } // Handles: + // * testDetermineJsonError@JSON_ERROR_NONE + // * testDetermineJsonError@JSON.* + elseif (preg_match('/^(.*?)@(.+)$/', $filter, $matches)) { + $filter = sprintf( + '%s.*with data set "%s"$', + $matches[1], + $matches[2], + ); + } + + // Escape delimiters in regular expression. Do NOT use preg_quote, + // to keep magic characters. + $filter = sprintf( + '/%s/i', + str_replace( + '/', + '\\/', + $filter, + ), + ); + } + + $this->filter = $filter; + } +} diff --git a/vendor/phpunit/phpunit/src/Runner/Hook/AfterIncompleteTestHook.php b/vendor/phpunit/phpunit/src/Runner/Hook/AfterIncompleteTestHook.php new file mode 100644 index 00000000..432be9a9 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Runner/Hook/AfterIncompleteTestHook.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +/** + * This interface, as well as the associated mechanism for extending PHPUnit, + * will be removed in PHPUnit 10. There is no alternative available in this + * version of PHPUnit. + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see https://github.com/sebastianbergmann/phpunit/issues/4676 + */ +interface AfterIncompleteTestHook extends TestHook +{ + public function executeAfterIncompleteTest(string $test, string $message, float $time): void; +} diff --git a/vendor/phpunit/phpunit/src/Runner/Hook/AfterLastTestHook.php b/vendor/phpunit/phpunit/src/Runner/Hook/AfterLastTestHook.php new file mode 100644 index 00000000..eb789f26 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Runner/Hook/AfterLastTestHook.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +/** + * This interface, as well as the associated mechanism for extending PHPUnit, + * will be removed in PHPUnit 10. There is no alternative available in this + * version of PHPUnit. + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see https://github.com/sebastianbergmann/phpunit/issues/4676 + */ +interface AfterLastTestHook extends Hook +{ + public function executeAfterLastTest(): void; +} diff --git a/vendor/phpunit/phpunit/src/Runner/Hook/AfterRiskyTestHook.php b/vendor/phpunit/phpunit/src/Runner/Hook/AfterRiskyTestHook.php new file mode 100644 index 00000000..31cc91ab --- /dev/null +++ b/vendor/phpunit/phpunit/src/Runner/Hook/AfterRiskyTestHook.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +/** + * This interface, as well as the associated mechanism for extending PHPUnit, + * will be removed in PHPUnit 10. There is no alternative available in this + * version of PHPUnit. + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see https://github.com/sebastianbergmann/phpunit/issues/4676 + */ +interface AfterRiskyTestHook extends TestHook +{ + public function executeAfterRiskyTest(string $test, string $message, float $time): void; +} diff --git a/vendor/phpunit/phpunit/src/Runner/Hook/AfterSkippedTestHook.php b/vendor/phpunit/phpunit/src/Runner/Hook/AfterSkippedTestHook.php new file mode 100644 index 00000000..76980b3f --- /dev/null +++ b/vendor/phpunit/phpunit/src/Runner/Hook/AfterSkippedTestHook.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +/** + * This interface, as well as the associated mechanism for extending PHPUnit, + * will be removed in PHPUnit 10. There is no alternative available in this + * version of PHPUnit. + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see https://github.com/sebastianbergmann/phpunit/issues/4676 + */ +interface AfterSkippedTestHook extends TestHook +{ + public function executeAfterSkippedTest(string $test, string $message, float $time): void; +} diff --git a/vendor/phpunit/phpunit/src/Runner/Hook/AfterSuccessfulTestHook.php b/vendor/phpunit/phpunit/src/Runner/Hook/AfterSuccessfulTestHook.php new file mode 100644 index 00000000..d0a10dd1 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Runner/Hook/AfterSuccessfulTestHook.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +/** + * This interface, as well as the associated mechanism for extending PHPUnit, + * will be removed in PHPUnit 10. There is no alternative available in this + * version of PHPUnit. + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see https://github.com/sebastianbergmann/phpunit/issues/4676 + */ +interface AfterSuccessfulTestHook extends TestHook +{ + public function executeAfterSuccessfulTest(string $test, float $time): void; +} diff --git a/vendor/phpunit/phpunit/src/Runner/Hook/AfterTestErrorHook.php b/vendor/phpunit/phpunit/src/Runner/Hook/AfterTestErrorHook.php new file mode 100644 index 00000000..12ecebd3 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Runner/Hook/AfterTestErrorHook.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +/** + * This interface, as well as the associated mechanism for extending PHPUnit, + * will be removed in PHPUnit 10. There is no alternative available in this + * version of PHPUnit. + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see https://github.com/sebastianbergmann/phpunit/issues/4676 + */ +interface AfterTestErrorHook extends TestHook +{ + public function executeAfterTestError(string $test, string $message, float $time): void; +} diff --git a/vendor/phpunit/phpunit/src/Runner/Hook/AfterTestFailureHook.php b/vendor/phpunit/phpunit/src/Runner/Hook/AfterTestFailureHook.php new file mode 100644 index 00000000..94b2f300 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Runner/Hook/AfterTestFailureHook.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +/** + * This interface, as well as the associated mechanism for extending PHPUnit, + * will be removed in PHPUnit 10. There is no alternative available in this + * version of PHPUnit. + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see https://github.com/sebastianbergmann/phpunit/issues/4676 + */ +interface AfterTestFailureHook extends TestHook +{ + public function executeAfterTestFailure(string $test, string $message, float $time): void; +} diff --git a/vendor/phpunit/phpunit/src/Runner/Hook/AfterTestHook.php b/vendor/phpunit/phpunit/src/Runner/Hook/AfterTestHook.php new file mode 100644 index 00000000..3d5bcaa9 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Runner/Hook/AfterTestHook.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +/** + * This interface, as well as the associated mechanism for extending PHPUnit, + * will be removed in PHPUnit 10. There is no alternative available in this + * version of PHPUnit. + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see https://github.com/sebastianbergmann/phpunit/issues/4676 + */ +interface AfterTestHook extends TestHook +{ + /** + * This hook will fire after any test, regardless of the result. + * + * For more fine grained control, have a look at the other hooks + * that extend PHPUnit\Runner\Hook. + */ + public function executeAfterTest(string $test, float $time): void; +} diff --git a/vendor/phpunit/phpunit/src/Runner/Hook/AfterTestWarningHook.php b/vendor/phpunit/phpunit/src/Runner/Hook/AfterTestWarningHook.php new file mode 100644 index 00000000..860fccee --- /dev/null +++ b/vendor/phpunit/phpunit/src/Runner/Hook/AfterTestWarningHook.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +/** + * This interface, as well as the associated mechanism for extending PHPUnit, + * will be removed in PHPUnit 10. There is no alternative available in this + * version of PHPUnit. + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see https://github.com/sebastianbergmann/phpunit/issues/4676 + */ +interface AfterTestWarningHook extends TestHook +{ + public function executeAfterTestWarning(string $test, string $message, float $time): void; +} diff --git a/vendor/phpunit/phpunit/src/Runner/Hook/BeforeFirstTestHook.php b/vendor/phpunit/phpunit/src/Runner/Hook/BeforeFirstTestHook.php new file mode 100644 index 00000000..feeb90fb --- /dev/null +++ b/vendor/phpunit/phpunit/src/Runner/Hook/BeforeFirstTestHook.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +/** + * This interface, as well as the associated mechanism for extending PHPUnit, + * will be removed in PHPUnit 10. There is no alternative available in this + * version of PHPUnit. + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see https://github.com/sebastianbergmann/phpunit/issues/4676 + */ +interface BeforeFirstTestHook extends Hook +{ + public function executeBeforeFirstTest(): void; +} diff --git a/vendor/phpunit/phpunit/src/Runner/Hook/BeforeTestHook.php b/vendor/phpunit/phpunit/src/Runner/Hook/BeforeTestHook.php new file mode 100644 index 00000000..b7e0827d --- /dev/null +++ b/vendor/phpunit/phpunit/src/Runner/Hook/BeforeTestHook.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +/** + * This interface, as well as the associated mechanism for extending PHPUnit, + * will be removed in PHPUnit 10. There is no alternative available in this + * version of PHPUnit. + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see https://github.com/sebastianbergmann/phpunit/issues/4676 + */ +interface BeforeTestHook extends TestHook +{ + public function executeBeforeTest(string $test): void; +} diff --git a/vendor/phpunit/phpunit/src/Runner/Hook/Hook.php b/vendor/phpunit/phpunit/src/Runner/Hook/Hook.php new file mode 100644 index 00000000..a08dc72b --- /dev/null +++ b/vendor/phpunit/phpunit/src/Runner/Hook/Hook.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +/** + * This interface, as well as the associated mechanism for extending PHPUnit, + * will be removed in PHPUnit 10. There is no alternative available in this + * version of PHPUnit. + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see https://github.com/sebastianbergmann/phpunit/issues/4676 + */ +interface Hook +{ +} diff --git a/vendor/phpunit/phpunit/src/Runner/Hook/TestHook.php b/vendor/phpunit/phpunit/src/Runner/Hook/TestHook.php new file mode 100644 index 00000000..31e880e2 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Runner/Hook/TestHook.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +/** + * This interface, as well as the associated mechanism for extending PHPUnit, + * will be removed in PHPUnit 10. There is no alternative available in this + * version of PHPUnit. + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see https://github.com/sebastianbergmann/phpunit/issues/4676 + */ +interface TestHook extends Hook +{ +} diff --git a/vendor/phpunit/phpunit/src/Runner/Hook/TestListenerAdapter.php b/vendor/phpunit/phpunit/src/Runner/Hook/TestListenerAdapter.php new file mode 100644 index 00000000..60fbfba3 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Runner/Hook/TestListenerAdapter.php @@ -0,0 +1,141 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +use PHPUnit\Framework\AssertionFailedError; +use PHPUnit\Framework\Test; +use PHPUnit\Framework\TestListener; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Framework\Warning; +use PHPUnit\Util\Test as TestUtil; +use Throwable; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TestListenerAdapter implements TestListener +{ + /** + * @var TestHook[] + */ + private $hooks = []; + + /** + * @var bool + */ + private $lastTestWasNotSuccessful; + + public function add(TestHook $hook): void + { + $this->hooks[] = $hook; + } + + public function startTest(Test $test): void + { + foreach ($this->hooks as $hook) { + if ($hook instanceof BeforeTestHook) { + $hook->executeBeforeTest(TestUtil::describeAsString($test)); + } + } + + $this->lastTestWasNotSuccessful = false; + } + + public function addError(Test $test, Throwable $t, float $time): void + { + foreach ($this->hooks as $hook) { + if ($hook instanceof AfterTestErrorHook) { + $hook->executeAfterTestError(TestUtil::describeAsString($test), $t->getMessage(), $time); + } + } + + $this->lastTestWasNotSuccessful = true; + } + + public function addWarning(Test $test, Warning $e, float $time): void + { + foreach ($this->hooks as $hook) { + if ($hook instanceof AfterTestWarningHook) { + $hook->executeAfterTestWarning(TestUtil::describeAsString($test), $e->getMessage(), $time); + } + } + + $this->lastTestWasNotSuccessful = true; + } + + public function addFailure(Test $test, AssertionFailedError $e, float $time): void + { + foreach ($this->hooks as $hook) { + if ($hook instanceof AfterTestFailureHook) { + $hook->executeAfterTestFailure(TestUtil::describeAsString($test), $e->getMessage(), $time); + } + } + + $this->lastTestWasNotSuccessful = true; + } + + public function addIncompleteTest(Test $test, Throwable $t, float $time): void + { + foreach ($this->hooks as $hook) { + if ($hook instanceof AfterIncompleteTestHook) { + $hook->executeAfterIncompleteTest(TestUtil::describeAsString($test), $t->getMessage(), $time); + } + } + + $this->lastTestWasNotSuccessful = true; + } + + public function addRiskyTest(Test $test, Throwable $t, float $time): void + { + foreach ($this->hooks as $hook) { + if ($hook instanceof AfterRiskyTestHook) { + $hook->executeAfterRiskyTest(TestUtil::describeAsString($test), $t->getMessage(), $time); + } + } + + $this->lastTestWasNotSuccessful = true; + } + + public function addSkippedTest(Test $test, Throwable $t, float $time): void + { + foreach ($this->hooks as $hook) { + if ($hook instanceof AfterSkippedTestHook) { + $hook->executeAfterSkippedTest(TestUtil::describeAsString($test), $t->getMessage(), $time); + } + } + + $this->lastTestWasNotSuccessful = true; + } + + public function endTest(Test $test, float $time): void + { + if (!$this->lastTestWasNotSuccessful) { + foreach ($this->hooks as $hook) { + if ($hook instanceof AfterSuccessfulTestHook) { + $hook->executeAfterSuccessfulTest(TestUtil::describeAsString($test), $time); + } + } + } + + foreach ($this->hooks as $hook) { + if ($hook instanceof AfterTestHook) { + $hook->executeAfterTest(TestUtil::describeAsString($test), $time); + } + } + } + + public function startTestSuite(TestSuite $suite): void + { + } + + public function endTestSuite(TestSuite $suite): void + { + } +} diff --git a/vendor/phpunit/phpunit/src/Runner/NullTestResultCache.php b/vendor/phpunit/phpunit/src/Runner/NullTestResultCache.php new file mode 100644 index 00000000..2aa86534 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Runner/NullTestResultCache.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class NullTestResultCache implements TestResultCache +{ + public function setState(string $testName, int $state): void + { + } + + public function getState(string $testName): int + { + return BaseTestRunner::STATUS_UNKNOWN; + } + + public function setTime(string $testName, float $time): void + { + } + + public function getTime(string $testName): float + { + return 0; + } + + public function load(): void + { + } + + public function persist(): void + { + } +} diff --git a/vendor/phpunit/phpunit/src/Runner/PhptTestCase.php b/vendor/phpunit/phpunit/src/Runner/PhptTestCase.php new file mode 100644 index 00000000..988b0ece --- /dev/null +++ b/vendor/phpunit/phpunit/src/Runner/PhptTestCase.php @@ -0,0 +1,864 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +use const DEBUG_BACKTRACE_IGNORE_ARGS; +use const DIRECTORY_SEPARATOR; +use function array_merge; +use function basename; +use function debug_backtrace; +use function defined; +use function dirname; +use function explode; +use function extension_loaded; +use function file; +use function file_get_contents; +use function file_put_contents; +use function is_array; +use function is_file; +use function is_readable; +use function is_string; +use function ltrim; +use function phpversion; +use function preg_match; +use function preg_replace; +use function preg_split; +use function realpath; +use function rtrim; +use function sprintf; +use function str_replace; +use function strncasecmp; +use function strpos; +use function substr; +use function trim; +use function unlink; +use function unserialize; +use function var_export; +use function version_compare; +use PHPUnit\Framework\Assert; +use PHPUnit\Framework\AssertionFailedError; +use PHPUnit\Framework\ExecutionOrderDependency; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\IncompleteTestError; +use PHPUnit\Framework\PHPTAssertionFailedError; +use PHPUnit\Framework\Reorderable; +use PHPUnit\Framework\SelfDescribing; +use PHPUnit\Framework\SkippedTestError; +use PHPUnit\Framework\SyntheticSkippedError; +use PHPUnit\Framework\Test; +use PHPUnit\Framework\TestResult; +use PHPUnit\Util\PHP\AbstractPhpProcess; +use SebastianBergmann\CodeCoverage\RawCodeCoverageData; +use SebastianBergmann\Template\Template; +use SebastianBergmann\Timer\Timer; +use Throwable; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class PhptTestCase implements Reorderable, SelfDescribing, Test +{ + /** + * @var string + */ + private $filename; + + /** + * @var AbstractPhpProcess + */ + private $phpUtil; + + /** + * @var string + */ + private $output = ''; + + /** + * Constructs a test case with the given filename. + * + * @throws Exception + */ + public function __construct(string $filename, AbstractPhpProcess $phpUtil = null) + { + if (!is_file($filename)) { + throw new Exception( + sprintf( + 'File "%s" does not exist.', + $filename, + ), + ); + } + + $this->filename = $filename; + $this->phpUtil = $phpUtil ?: AbstractPhpProcess::factory(); + } + + /** + * Counts the number of test cases executed by run(TestResult result). + */ + public function count(): int + { + return 1; + } + + /** + * Runs a test and collects its result in a TestResult instance. + * + * @throws \SebastianBergmann\CodeCoverage\InvalidArgumentException + * @throws \SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + */ + public function run(TestResult $result = null): TestResult + { + if ($result === null) { + $result = new TestResult; + } + + try { + $sections = $this->parse(); + } catch (Exception $e) { + $result->startTest($this); + $result->addFailure($this, new SkippedTestError($e->getMessage()), 0); + $result->endTest($this, 0); + + return $result; + } + + $code = $this->render($sections['FILE']); + $xfail = false; + $settings = $this->parseIniSection($this->settings($result->getCollectCodeCoverageInformation())); + + $result->startTest($this); + + if (isset($sections['INI'])) { + $settings = $this->parseIniSection($sections['INI'], $settings); + } + + if (isset($sections['ENV'])) { + $env = $this->parseEnvSection($sections['ENV']); + $this->phpUtil->setEnv($env); + } + + $this->phpUtil->setUseStderrRedirection(true); + + if ($result->enforcesTimeLimit()) { + $this->phpUtil->setTimeout($result->getTimeoutForLargeTests()); + } + + $skip = $this->runSkip($sections, $result, $settings); + + if ($skip) { + return $result; + } + + if (isset($sections['XFAIL'])) { + $xfail = trim($sections['XFAIL']); + } + + if (isset($sections['STDIN'])) { + $this->phpUtil->setStdin($sections['STDIN']); + } + + if (isset($sections['ARGS'])) { + $this->phpUtil->setArgs($sections['ARGS']); + } + + if ($result->getCollectCodeCoverageInformation()) { + $codeCoverageCacheDirectory = null; + $pathCoverage = false; + + $codeCoverage = $result->getCodeCoverage(); + + if ($codeCoverage) { + if ($codeCoverage->cachesStaticAnalysis()) { + $codeCoverageCacheDirectory = $codeCoverage->cacheDirectory(); + } + + $pathCoverage = $codeCoverage->collectsBranchAndPathCoverage(); + } + + $this->renderForCoverage($code, $pathCoverage, $codeCoverageCacheDirectory); + } + + $timer = new Timer; + $timer->start(); + + $jobResult = $this->phpUtil->runJob($code, $this->stringifyIni($settings)); + $time = $timer->stop()->asSeconds(); + $this->output = $jobResult['stdout'] ?? ''; + + if (isset($codeCoverage) && ($coverage = $this->cleanupForCoverage())) { + $codeCoverage->append($coverage, $this, true, [], []); + } + + try { + $this->assertPhptExpectation($sections, $this->output); + } catch (AssertionFailedError $e) { + $failure = $e; + + if ($xfail !== false) { + $failure = new IncompleteTestError($xfail, 0, $e); + } elseif ($e instanceof ExpectationFailedException) { + $comparisonFailure = $e->getComparisonFailure(); + + if ($comparisonFailure) { + $diff = $comparisonFailure->getDiff(); + } else { + $diff = $e->getMessage(); + } + + $hint = $this->getLocationHintFromDiff($diff, $sections); + $trace = array_merge($hint, debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)); + $failure = new PHPTAssertionFailedError( + $e->getMessage(), + 0, + $trace[0]['file'], + $trace[0]['line'], + $trace, + $comparisonFailure ? $diff : '', + ); + } + + $result->addFailure($this, $failure, $time); + } catch (Throwable $t) { + $result->addError($this, $t, $time); + } + + if ($xfail !== false && $result->allCompletelyImplemented()) { + $result->addFailure($this, new IncompleteTestError('XFAIL section but test passes'), $time); + } + + $this->runClean($sections, $result->getCollectCodeCoverageInformation()); + + $result->endTest($this, $time); + + return $result; + } + + /** + * Returns the name of the test case. + */ + public function getName(): string + { + return $this->toString(); + } + + /** + * Returns a string representation of the test case. + */ + public function toString(): string + { + return $this->filename; + } + + public function usesDataProvider(): bool + { + return false; + } + + public function getNumAssertions(): int + { + return 1; + } + + public function getActualOutput(): string + { + return $this->output; + } + + public function hasOutput(): bool + { + return !empty($this->output); + } + + public function sortId(): string + { + return $this->filename; + } + + /** + * @return list + */ + public function provides(): array + { + return []; + } + + /** + * @return list + */ + public function requires(): array + { + return []; + } + + /** + * Parse --INI-- section key value pairs and return as array. + * + * @param array|string $content + */ + private function parseIniSection($content, array $ini = []): array + { + if (is_string($content)) { + $content = explode("\n", trim($content)); + } + + foreach ($content as $setting) { + if (strpos($setting, '=') === false) { + continue; + } + + $setting = explode('=', $setting, 2); + $name = trim($setting[0]); + $value = trim($setting[1]); + + if ($name === 'extension' || $name === 'zend_extension') { + if (!isset($ini[$name])) { + $ini[$name] = []; + } + + $ini[$name][] = $value; + + continue; + } + + $ini[$name] = $value; + } + + return $ini; + } + + private function parseEnvSection(string $content): array + { + $env = []; + + foreach (explode("\n", trim($content)) as $e) { + $e = explode('=', trim($e), 2); + + if (!empty($e[0]) && isset($e[1])) { + $env[$e[0]] = $e[1]; + } + } + + return $env; + } + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + * @throws ExpectationFailedException + */ + private function assertPhptExpectation(array $sections, string $output): void + { + $assertions = [ + 'EXPECT' => 'assertEquals', + 'EXPECTF' => 'assertStringMatchesFormat', + 'EXPECTREGEX' => 'assertMatchesRegularExpression', + ]; + + $actual = preg_replace('/\r\n/', "\n", trim($output)); + + foreach ($assertions as $sectionName => $sectionAssertion) { + if (isset($sections[$sectionName])) { + $sectionContent = preg_replace('/\r\n/', "\n", trim($sections[$sectionName])); + $expected = $sectionName === 'EXPECTREGEX' ? "/{$sectionContent}/" : $sectionContent; + + if ($expected === '') { + throw new Exception('No PHPT expectation found'); + } + + Assert::$sectionAssertion($expected, $actual); + + return; + } + } + + throw new Exception('No PHPT assertion found'); + } + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + private function runSkip(array &$sections, TestResult $result, array $settings): bool + { + if (!isset($sections['SKIPIF'])) { + return false; + } + + $skipif = $this->render($sections['SKIPIF']); + $jobResult = $this->phpUtil->runJob($skipif, $this->stringifyIni($settings)); + + if (!strncasecmp('skip', ltrim($jobResult['stdout']), 4)) { + $message = ''; + + if (preg_match('/^\s*skip\s*(.+)\s*/i', $jobResult['stdout'], $skipMatch)) { + $message = substr($skipMatch[1], 2); + } + + $hint = $this->getLocationHint($message, $sections, 'SKIPIF'); + $trace = array_merge($hint, debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)); + $result->addFailure( + $this, + new SyntheticSkippedError($message, 0, $trace[0]['file'], $trace[0]['line'], $trace), + 0, + ); + $result->endTest($this, 0); + + return true; + } + + return false; + } + + private function runClean(array &$sections, bool $collectCoverage): void + { + $this->phpUtil->setStdin(''); + $this->phpUtil->setArgs(''); + + if (isset($sections['CLEAN'])) { + $cleanCode = $this->render($sections['CLEAN']); + + $this->phpUtil->runJob($cleanCode, $this->settings($collectCoverage)); + } + } + + /** + * @throws Exception + */ + private function parse(): array + { + $sections = []; + $section = ''; + + $unsupportedSections = [ + 'CGI', + 'COOKIE', + 'DEFLATE_POST', + 'EXPECTHEADERS', + 'EXTENSIONS', + 'GET', + 'GZIP_POST', + 'HEADERS', + 'PHPDBG', + 'POST', + 'POST_RAW', + 'PUT', + 'REDIRECTTEST', + 'REQUEST', + ]; + + $lineNr = 0; + + foreach (file($this->filename) as $line) { + $lineNr++; + + if (preg_match('/^--([_A-Z]+)--/', $line, $result)) { + $section = $result[1]; + $sections[$section] = ''; + $sections[$section . '_offset'] = $lineNr; + + continue; + } + + if (empty($section)) { + throw new Exception('Invalid PHPT file: empty section header'); + } + + $sections[$section] .= $line; + } + + if (isset($sections['FILEEOF'])) { + $sections['FILE'] = rtrim($sections['FILEEOF'], "\r\n"); + unset($sections['FILEEOF']); + } + + $this->parseExternal($sections); + + if (!$this->validate($sections)) { + throw new Exception('Invalid PHPT file'); + } + + foreach ($unsupportedSections as $section) { + if (isset($sections[$section])) { + throw new Exception( + "PHPUnit does not support PHPT {$section} sections", + ); + } + } + + return $sections; + } + + /** + * @throws Exception + */ + private function parseExternal(array &$sections): void + { + $allowSections = [ + 'FILE', + 'EXPECT', + 'EXPECTF', + 'EXPECTREGEX', + ]; + $testDirectory = dirname($this->filename) . DIRECTORY_SEPARATOR; + + foreach ($allowSections as $section) { + if (isset($sections[$section . '_EXTERNAL'])) { + $externalFilename = trim($sections[$section . '_EXTERNAL']); + + if (!is_file($testDirectory . $externalFilename) || + !is_readable($testDirectory . $externalFilename)) { + throw new Exception( + sprintf( + 'Could not load --%s-- %s for PHPT file', + $section . '_EXTERNAL', + $testDirectory . $externalFilename, + ), + ); + } + + $sections[$section] = file_get_contents($testDirectory . $externalFilename); + } + } + } + + private function validate(array &$sections): bool + { + $requiredSections = [ + 'FILE', + [ + 'EXPECT', + 'EXPECTF', + 'EXPECTREGEX', + ], + ]; + + foreach ($requiredSections as $section) { + if (is_array($section)) { + $foundSection = false; + + foreach ($section as $anySection) { + if (isset($sections[$anySection])) { + $foundSection = true; + + break; + } + } + + if (!$foundSection) { + return false; + } + + continue; + } + + if (!isset($sections[$section])) { + return false; + } + } + + return true; + } + + private function render(string $code): string + { + return str_replace( + [ + '__DIR__', + '__FILE__', + ], + [ + "'" . dirname($this->filename) . "'", + "'" . $this->filename . "'", + ], + $code, + ); + } + + private function getCoverageFiles(): array + { + $baseDir = dirname(realpath($this->filename)) . DIRECTORY_SEPARATOR; + $basename = basename($this->filename, 'phpt'); + + return [ + 'coverage' => $baseDir . $basename . 'coverage', + 'job' => $baseDir . $basename . 'php', + ]; + } + + private function renderForCoverage(string &$job, bool $pathCoverage, ?string $codeCoverageCacheDirectory): void + { + $files = $this->getCoverageFiles(); + + $template = new Template( + __DIR__ . '/../Util/PHP/Template/PhptTestCase.tpl', + ); + + $composerAutoload = '\'\''; + + if (defined('PHPUNIT_COMPOSER_INSTALL')) { + $composerAutoload = var_export(PHPUNIT_COMPOSER_INSTALL, true); + } + + $phar = '\'\''; + + if (defined('__PHPUNIT_PHAR__')) { + $phar = var_export(__PHPUNIT_PHAR__, true); + } + + $globals = ''; + + if (!empty($GLOBALS['__PHPUNIT_BOOTSTRAP'])) { + $globals = '$GLOBALS[\'__PHPUNIT_BOOTSTRAP\'] = ' . var_export( + $GLOBALS['__PHPUNIT_BOOTSTRAP'], + true, + ) . ";\n"; + } + + if ($codeCoverageCacheDirectory === null) { + $codeCoverageCacheDirectory = 'null'; + } else { + $codeCoverageCacheDirectory = "'" . $codeCoverageCacheDirectory . "'"; + } + + $template->setVar( + [ + 'composerAutoload' => $composerAutoload, + 'phar' => $phar, + 'globals' => $globals, + 'job' => $files['job'], + 'coverageFile' => $files['coverage'], + 'driverMethod' => $pathCoverage ? 'forLineAndPathCoverage' : 'forLineCoverage', + 'codeCoverageCacheDirectory' => $codeCoverageCacheDirectory, + ], + ); + + file_put_contents($files['job'], $job); + + $job = $template->render(); + } + + private function cleanupForCoverage(): RawCodeCoverageData + { + $coverage = RawCodeCoverageData::fromXdebugWithoutPathCoverage([]); + $files = $this->getCoverageFiles(); + + if (is_file($files['coverage'])) { + $buffer = @file_get_contents($files['coverage']); + + if ($buffer !== false) { + $coverage = @unserialize($buffer); + + if ($coverage === false) { + $coverage = RawCodeCoverageData::fromXdebugWithoutPathCoverage([]); + } + } + } + + foreach ($files as $file) { + @unlink($file); + } + + return $coverage; + } + + private function stringifyIni(array $ini): array + { + $settings = []; + + foreach ($ini as $key => $value) { + if (is_array($value)) { + foreach ($value as $val) { + $settings[] = $key . '=' . $val; + } + + continue; + } + + $settings[] = $key . '=' . $value; + } + + return $settings; + } + + private function getLocationHintFromDiff(string $message, array $sections): array + { + $needle = ''; + $previousLine = ''; + $block = 'message'; + + foreach (preg_split('/\r\n|\r|\n/', $message) as $line) { + $line = trim($line); + + if ($block === 'message' && $line === '--- Expected') { + $block = 'expected'; + } + + if ($block === 'expected' && $line === '@@ @@') { + $block = 'diff'; + } + + if ($block === 'diff') { + if (strpos($line, '+') === 0) { + $needle = $this->getCleanDiffLine($previousLine); + + break; + } + + if (strpos($line, '-') === 0) { + $needle = $this->getCleanDiffLine($line); + + break; + } + } + + if (!empty($line)) { + $previousLine = $line; + } + } + + return $this->getLocationHint($needle, $sections); + } + + private function getCleanDiffLine(string $line): string + { + if (preg_match('/^[\-+]([\'\"]?)(.*)\1$/', $line, $matches)) { + $line = $matches[2]; + } + + return $line; + } + + private function getLocationHint(string $needle, array $sections, ?string $sectionName = null): array + { + $needle = trim($needle); + + if (empty($needle)) { + return [[ + 'file' => realpath($this->filename), + 'line' => 1, + ]]; + } + + if ($sectionName) { + $search = [$sectionName]; + } else { + $search = [ + // 'FILE', + 'EXPECT', + 'EXPECTF', + 'EXPECTREGEX', + ]; + } + + $sectionOffset = null; + + foreach ($search as $section) { + if (!isset($sections[$section])) { + continue; + } + + if (isset($sections[$section . '_EXTERNAL'])) { + $externalFile = trim($sections[$section . '_EXTERNAL']); + + return [ + [ + 'file' => realpath(dirname($this->filename) . DIRECTORY_SEPARATOR . $externalFile), + 'line' => 1, + ], + [ + 'file' => realpath($this->filename), + 'line' => ($sections[$section . '_EXTERNAL_offset'] ?? 0) + 1, + ], + ]; + } + + $sectionOffset = $sections[$section . '_offset'] ?? 0; + $offset = $sectionOffset + 1; + + foreach (preg_split('/\r\n|\r|\n/', $sections[$section]) as $line) { + if (strpos($line, $needle) !== false) { + return [[ + 'file' => realpath($this->filename), + 'line' => $offset, + ]]; + } + $offset++; + } + } + + if ($sectionName) { + // String not found in specified section, show user the start of the named section + return [[ + 'file' => realpath($this->filename), + 'line' => $sectionOffset, + ]]; + } + + // No section specified, show user start of code + return [[ + 'file' => realpath($this->filename), + 'line' => 1, + ]]; + } + + /** + * @psalm-return list + */ + private function settings(bool $collectCoverage): array + { + $settings = [ + 'allow_url_fopen=1', + 'auto_append_file=', + 'auto_prepend_file=', + 'disable_functions=', + 'display_errors=1', + 'docref_ext=.html', + 'docref_root=', + 'error_append_string=', + 'error_prepend_string=', + 'error_reporting=-1', + 'html_errors=0', + 'log_errors=0', + 'open_basedir=', + 'output_buffering=Off', + 'output_handler=', + 'report_memleaks=0', + 'report_zend_debug=0', + ]; + + if (extension_loaded('pcov')) { + if ($collectCoverage) { + $settings[] = 'pcov.enabled=1'; + } else { + $settings[] = 'pcov.enabled=0'; + } + } + + if (extension_loaded('xdebug')) { + if (version_compare(phpversion('xdebug'), '3', '>=')) { + if ($collectCoverage) { + $settings[] = 'xdebug.mode=coverage'; + } else { + $settings[] = 'xdebug.mode=off'; + } + } else { + $settings[] = 'xdebug.default_enable=0'; + + if ($collectCoverage) { + $settings[] = 'xdebug.coverage_enable=1'; + } + } + } + + return $settings; + } +} diff --git a/vendor/phpunit/phpunit/src/Runner/ResultCacheExtension.php b/vendor/phpunit/phpunit/src/Runner/ResultCacheExtension.php new file mode 100644 index 00000000..31d7610e --- /dev/null +++ b/vendor/phpunit/phpunit/src/Runner/ResultCacheExtension.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +use function preg_match; +use function round; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ResultCacheExtension implements AfterIncompleteTestHook, AfterLastTestHook, AfterRiskyTestHook, AfterSkippedTestHook, AfterSuccessfulTestHook, AfterTestErrorHook, AfterTestFailureHook, AfterTestWarningHook +{ + /** + * @var TestResultCache + */ + private $cache; + + public function __construct(TestResultCache $cache) + { + $this->cache = $cache; + } + + public function flush(): void + { + $this->cache->persist(); + } + + public function executeAfterSuccessfulTest(string $test, float $time): void + { + $testName = $this->getTestName($test); + + $this->cache->setTime($testName, round($time, 3)); + } + + public function executeAfterIncompleteTest(string $test, string $message, float $time): void + { + $testName = $this->getTestName($test); + + $this->cache->setTime($testName, round($time, 3)); + $this->cache->setState($testName, BaseTestRunner::STATUS_INCOMPLETE); + } + + public function executeAfterRiskyTest(string $test, string $message, float $time): void + { + $testName = $this->getTestName($test); + + $this->cache->setTime($testName, round($time, 3)); + $this->cache->setState($testName, BaseTestRunner::STATUS_RISKY); + } + + public function executeAfterSkippedTest(string $test, string $message, float $time): void + { + $testName = $this->getTestName($test); + + $this->cache->setTime($testName, round($time, 3)); + $this->cache->setState($testName, BaseTestRunner::STATUS_SKIPPED); + } + + public function executeAfterTestError(string $test, string $message, float $time): void + { + $testName = $this->getTestName($test); + + $this->cache->setTime($testName, round($time, 3)); + $this->cache->setState($testName, BaseTestRunner::STATUS_ERROR); + } + + public function executeAfterTestFailure(string $test, string $message, float $time): void + { + $testName = $this->getTestName($test); + + $this->cache->setTime($testName, round($time, 3)); + $this->cache->setState($testName, BaseTestRunner::STATUS_FAILURE); + } + + public function executeAfterTestWarning(string $test, string $message, float $time): void + { + $testName = $this->getTestName($test); + + $this->cache->setTime($testName, round($time, 3)); + $this->cache->setState($testName, BaseTestRunner::STATUS_WARNING); + } + + public function executeAfterLastTest(): void + { + $this->flush(); + } + + /** + * @param string $test A long description format of the current test + * + * @return string The test name without TestSuiteClassName:: and @dataprovider details + */ + private function getTestName(string $test): string + { + $matches = []; + + if (preg_match('/^(?\S+::\S+)(?:(? with data set (?:#\d+|"[^"]+"))\s\()?/', $test, $matches)) { + $test = $matches['name'] . ($matches['dataname'] ?? ''); + } + + return $test; + } +} diff --git a/vendor/phpunit/phpunit/src/Runner/StandardTestSuiteLoader.php b/vendor/phpunit/phpunit/src/Runner/StandardTestSuiteLoader.php new file mode 100644 index 00000000..f957e81a --- /dev/null +++ b/vendor/phpunit/phpunit/src/Runner/StandardTestSuiteLoader.php @@ -0,0 +1,152 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +use function array_diff; +use function array_values; +use function basename; +use function class_exists; +use function get_declared_classes; +use function sprintf; +use function stripos; +use function strlen; +use function substr; +use PHPUnit\Framework\TestCase; +use PHPUnit\Util\FileLoader; +use ReflectionClass; +use ReflectionException; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 + */ +final class StandardTestSuiteLoader implements TestSuiteLoader +{ + /** + * @throws Exception + */ + public function load(string $suiteClassFile): ReflectionClass + { + $suiteClassName = basename($suiteClassFile, '.php'); + $loadedClasses = get_declared_classes(); + + if (!class_exists($suiteClassName, false)) { + /* @noinspection UnusedFunctionResultInspection */ + FileLoader::checkAndLoad($suiteClassFile); + + $loadedClasses = array_values( + array_diff(get_declared_classes(), $loadedClasses), + ); + + if (empty($loadedClasses)) { + throw new Exception( + sprintf( + 'Class %s could not be found in %s', + $suiteClassName, + $suiteClassFile, + ), + ); + } + } + + if (!class_exists($suiteClassName, false)) { + $offset = 0 - strlen($suiteClassName); + + foreach ($loadedClasses as $loadedClass) { + // @see https://github.com/sebastianbergmann/phpunit/issues/5020 + if (stripos(substr($loadedClass, $offset - 1), '\\' . $suiteClassName) === 0 || + stripos(substr($loadedClass, $offset - 1), '_' . $suiteClassName) === 0) { + $suiteClassName = $loadedClass; + + break; + } + } + } + + if (!class_exists($suiteClassName, false)) { + throw new Exception( + sprintf( + 'Class %s could not be found in %s', + $suiteClassName, + $suiteClassFile, + ), + ); + } + + try { + $class = new ReflectionClass($suiteClassName); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + if ($class->isSubclassOf(TestCase::class)) { + if ($class->isAbstract()) { + throw new Exception( + sprintf( + 'Class %s declared in %s is abstract', + $suiteClassName, + $suiteClassFile, + ), + ); + } + + return $class; + } + + if ($class->hasMethod('suite')) { + try { + $method = $class->getMethod('suite'); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new Exception( + sprintf( + 'Method %s::suite() declared in %s is abstract', + $suiteClassName, + $suiteClassFile, + ), + ); + } + + if (!$method->isPublic()) { + throw new Exception( + sprintf( + 'Method %s::suite() declared in %s is not public', + $suiteClassName, + $suiteClassFile, + ), + ); + } + + if (!$method->isStatic()) { + throw new Exception( + sprintf( + 'Method %s::suite() declared in %s is not static', + $suiteClassName, + $suiteClassFile, + ), + ); + } + } + + return $class; + } + + public function reload(ReflectionClass $aClass): ReflectionClass + { + return $aClass; + } +} diff --git a/vendor/phpunit/phpunit/src/Runner/TestResultCache.php b/vendor/phpunit/phpunit/src/Runner/TestResultCache.php new file mode 100644 index 00000000..69e62828 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Runner/TestResultCache.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface TestResultCache +{ + public function setState(string $testName, int $state): void; + + public function getState(string $testName): int; + + public function setTime(string $testName, float $time): void; + + public function getTime(string $testName): float; + + public function load(): void; + + public function persist(): void; +} diff --git a/vendor/phpunit/phpunit/src/Runner/TestSuiteLoader.php b/vendor/phpunit/phpunit/src/Runner/TestSuiteLoader.php new file mode 100644 index 00000000..c9d8e01b --- /dev/null +++ b/vendor/phpunit/phpunit/src/Runner/TestSuiteLoader.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +use ReflectionClass; + +/** + * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface TestSuiteLoader +{ + public function load(string $suiteClassFile): ReflectionClass; + + public function reload(ReflectionClass $aClass): ReflectionClass; +} diff --git a/vendor/phpunit/phpunit/src/Runner/TestSuiteSorter.php b/vendor/phpunit/phpunit/src/Runner/TestSuiteSorter.php new file mode 100644 index 00000000..9ec82b60 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Runner/TestSuiteSorter.php @@ -0,0 +1,394 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +use function array_diff; +use function array_merge; +use function array_reverse; +use function array_splice; +use function count; +use function in_array; +use function max; +use function shuffle; +use function usort; +use PHPUnit\Framework\DataProviderTestSuite; +use PHPUnit\Framework\Reorderable; +use PHPUnit\Framework\Test; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Util\Test as TestUtil; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TestSuiteSorter +{ + /** + * @var int + */ + public const ORDER_DEFAULT = 0; + + /** + * @var int + */ + public const ORDER_RANDOMIZED = 1; + + /** + * @var int + */ + public const ORDER_REVERSED = 2; + + /** + * @var int + */ + public const ORDER_DEFECTS_FIRST = 3; + + /** + * @var int + */ + public const ORDER_DURATION = 4; + + /** + * Order tests by @size annotation 'small', 'medium', 'large'. + * + * @var int + */ + public const ORDER_SIZE = 5; + + /** + * List of sorting weights for all test result codes. A higher number gives higher priority. + */ + private const DEFECT_SORT_WEIGHT = [ + BaseTestRunner::STATUS_ERROR => 6, + BaseTestRunner::STATUS_FAILURE => 5, + BaseTestRunner::STATUS_WARNING => 4, + BaseTestRunner::STATUS_INCOMPLETE => 3, + BaseTestRunner::STATUS_RISKY => 2, + BaseTestRunner::STATUS_SKIPPED => 1, + BaseTestRunner::STATUS_UNKNOWN => 0, + ]; + + private const SIZE_SORT_WEIGHT = [ + TestUtil::SMALL => 1, + TestUtil::MEDIUM => 2, + TestUtil::LARGE => 3, + TestUtil::UNKNOWN => 4, + ]; + + /** + * @var array Associative array of (string => DEFECT_SORT_WEIGHT) elements + */ + private $defectSortOrder = []; + + /** + * @var TestResultCache + */ + private $cache; + + /** + * @var array A list of normalized names of tests before reordering + */ + private $originalExecutionOrder = []; + + /** + * @var array A list of normalized names of tests affected by reordering + */ + private $executionOrder = []; + + public function __construct(?TestResultCache $cache = null) + { + $this->cache = $cache ?? new NullTestResultCache; + } + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception + */ + public function reorderTestsInSuite(Test $suite, int $order, bool $resolveDependencies, int $orderDefects, bool $isRootTestSuite = true): void + { + $allowedOrders = [ + self::ORDER_DEFAULT, + self::ORDER_REVERSED, + self::ORDER_RANDOMIZED, + self::ORDER_DURATION, + self::ORDER_SIZE, + ]; + + if (!in_array($order, $allowedOrders, true)) { + throw new Exception( + '$order must be one of TestSuiteSorter::ORDER_[DEFAULT|REVERSED|RANDOMIZED|DURATION|SIZE]', + ); + } + + $allowedOrderDefects = [ + self::ORDER_DEFAULT, + self::ORDER_DEFECTS_FIRST, + ]; + + if (!in_array($orderDefects, $allowedOrderDefects, true)) { + throw new Exception( + '$orderDefects must be one of TestSuiteSorter::ORDER_DEFAULT, TestSuiteSorter::ORDER_DEFECTS_FIRST', + ); + } + + if ($isRootTestSuite) { + $this->originalExecutionOrder = $this->calculateTestExecutionOrder($suite); + } + + if ($suite instanceof TestSuite) { + foreach ($suite as $_suite) { + $this->reorderTestsInSuite($_suite, $order, $resolveDependencies, $orderDefects, false); + } + + if ($orderDefects === self::ORDER_DEFECTS_FIRST) { + $this->addSuiteToDefectSortOrder($suite); + } + + $this->sort($suite, $order, $resolveDependencies, $orderDefects); + } + + if ($isRootTestSuite) { + $this->executionOrder = $this->calculateTestExecutionOrder($suite); + } + } + + public function getOriginalExecutionOrder(): array + { + return $this->originalExecutionOrder; + } + + public function getExecutionOrder(): array + { + return $this->executionOrder; + } + + private function sort(TestSuite $suite, int $order, bool $resolveDependencies, int $orderDefects): void + { + if (empty($suite->tests())) { + return; + } + + if ($order === self::ORDER_REVERSED) { + $suite->setTests($this->reverse($suite->tests())); + } elseif ($order === self::ORDER_RANDOMIZED) { + $suite->setTests($this->randomize($suite->tests())); + } elseif ($order === self::ORDER_DURATION && $this->cache !== null) { + $suite->setTests($this->sortByDuration($suite->tests())); + } elseif ($order === self::ORDER_SIZE) { + $suite->setTests($this->sortBySize($suite->tests())); + } + + if ($orderDefects === self::ORDER_DEFECTS_FIRST && $this->cache !== null) { + $suite->setTests($this->sortDefectsFirst($suite->tests())); + } + + if ($resolveDependencies && !($suite instanceof DataProviderTestSuite)) { + /** @var TestCase[] $tests */ + $tests = $suite->tests(); + + $suite->setTests($this->resolveDependencies($tests)); + } + } + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + private function addSuiteToDefectSortOrder(TestSuite $suite): void + { + $max = 0; + + foreach ($suite->tests() as $test) { + if (!$test instanceof Reorderable) { + continue; + } + + if (!isset($this->defectSortOrder[$test->sortId()])) { + $this->defectSortOrder[$test->sortId()] = self::DEFECT_SORT_WEIGHT[$this->cache->getState($test->sortId())]; + $max = max($max, $this->defectSortOrder[$test->sortId()]); + } + } + + $this->defectSortOrder[$suite->sortId()] = $max; + } + + private function reverse(array $tests): array + { + return array_reverse($tests); + } + + private function randomize(array $tests): array + { + shuffle($tests); + + return $tests; + } + + private function sortDefectsFirst(array $tests): array + { + usort( + $tests, + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + function ($left, $right) + { + return $this->cmpDefectPriorityAndTime($left, $right); + }, + ); + + return $tests; + } + + private function sortByDuration(array $tests): array + { + usort( + $tests, + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + function ($left, $right) + { + return $this->cmpDuration($left, $right); + }, + ); + + return $tests; + } + + private function sortBySize(array $tests): array + { + usort( + $tests, + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + function ($left, $right) + { + return $this->cmpSize($left, $right); + }, + ); + + return $tests; + } + + /** + * Comparator callback function to sort tests for "reach failure as fast as possible". + * + * 1. sort tests by defect weight defined in self::DEFECT_SORT_WEIGHT + * 2. when tests are equally defective, sort the fastest to the front + * 3. do not reorder successful tests + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + private function cmpDefectPriorityAndTime(Test $a, Test $b): int + { + if (!($a instanceof Reorderable && $b instanceof Reorderable)) { + return 0; + } + + $priorityA = $this->defectSortOrder[$a->sortId()] ?? 0; + $priorityB = $this->defectSortOrder[$b->sortId()] ?? 0; + + if ($priorityB <=> $priorityA) { + // Sort defect weight descending + return $priorityB <=> $priorityA; + } + + if ($priorityA || $priorityB) { + return $this->cmpDuration($a, $b); + } + + // do not change execution order + return 0; + } + + /** + * Compares test duration for sorting tests by duration ascending. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + private function cmpDuration(Test $a, Test $b): int + { + if (!($a instanceof Reorderable && $b instanceof Reorderable)) { + return 0; + } + + return $this->cache->getTime($a->sortId()) <=> $this->cache->getTime($b->sortId()); + } + + /** + * Compares test size for sorting tests small->medium->large->unknown. + */ + private function cmpSize(Test $a, Test $b): int + { + $sizeA = ($a instanceof TestCase || $a instanceof DataProviderTestSuite) + ? $a->getSize() + : TestUtil::UNKNOWN; + $sizeB = ($b instanceof TestCase || $b instanceof DataProviderTestSuite) + ? $b->getSize() + : TestUtil::UNKNOWN; + + return self::SIZE_SORT_WEIGHT[$sizeA] <=> self::SIZE_SORT_WEIGHT[$sizeB]; + } + + /** + * Reorder Tests within a TestCase in such a way as to resolve as many dependencies as possible. + * The algorithm will leave the tests in original running order when it can. + * For more details see the documentation for test dependencies. + * + * Short description of algorithm: + * 1. Pick the next Test from remaining tests to be checked for dependencies. + * 2. If the test has no dependencies: mark done, start again from the top + * 3. If the test has dependencies but none left to do: mark done, start again from the top + * 4. When we reach the end add any leftover tests to the end. These will be marked 'skipped' during execution. + * + * @param array $tests + * + * @return array + */ + private function resolveDependencies(array $tests): array + { + $newTestOrder = []; + $i = 0; + $provided = []; + + do { + if ([] === array_diff($tests[$i]->requires(), $provided)) { + $provided = array_merge($provided, $tests[$i]->provides()); + $newTestOrder = array_merge($newTestOrder, array_splice($tests, $i, 1)); + $i = 0; + } else { + $i++; + } + } while (!empty($tests) && ($i < count($tests))); + + return array_merge($newTestOrder, $tests); + } + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + private function calculateTestExecutionOrder(Test $suite): array + { + $tests = []; + + if ($suite instanceof TestSuite) { + foreach ($suite->tests() as $test) { + if (!$test instanceof TestSuite && $test instanceof Reorderable) { + $tests[] = $test->sortId(); + } else { + $tests = array_merge($tests, $this->calculateTestExecutionOrder($test)); + } + } + } + + return $tests; + } +} diff --git a/vendor/phpunit/phpunit/src/Runner/Version.php b/vendor/phpunit/phpunit/src/Runner/Version.php new file mode 100644 index 00000000..ba589b2a --- /dev/null +++ b/vendor/phpunit/phpunit/src/Runner/Version.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +use function array_slice; +use function assert; +use function dirname; +use function explode; +use function implode; +use function strpos; +use SebastianBergmann\Version as VersionId; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class Version +{ + /** + * @var string + */ + private static $pharVersion = ''; + + /** + * @var string + */ + private static $version = ''; + + /** + * Returns the current version of PHPUnit. + * + * @psalm-return non-empty-string + */ + public static function id(): string + { + if (self::$pharVersion !== '') { + return self::$pharVersion; + } + + if (self::$version === '') { + self::$version = (new VersionId('9.6.17', dirname(__DIR__, 2)))->getVersion(); + + assert(!empty(self::$version)); + } + + return self::$version; + } + + /** + * @psalm-return non-empty-string + */ + public static function series(): string + { + if (strpos(self::id(), '-')) { + $version = explode('-', self::id())[0]; + } else { + $version = self::id(); + } + + return implode('.', array_slice(explode('.', $version), 0, 2)); + } + + /** + * @psalm-return non-empty-string + */ + public static function getVersionString(): string + { + return 'PHPUnit ' . self::id() . ' by Sebastian Bergmann and contributors.'; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/CliArguments/Builder.php b/vendor/phpunit/phpunit/src/TextUI/CliArguments/Builder.php new file mode 100644 index 00000000..51f0a513 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/CliArguments/Builder.php @@ -0,0 +1,886 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\CliArguments; + +use function array_map; +use function array_merge; +use function class_exists; +use function explode; +use function is_numeric; +use function str_replace; +use PHPUnit\Runner\TestSuiteSorter; +use PHPUnit\TextUI\DefaultResultPrinter; +use PHPUnit\TextUI\XmlConfiguration\Extension; +use PHPUnit\Util\Log\TeamCity; +use PHPUnit\Util\TestDox\CliTestDoxPrinter; +use SebastianBergmann\CliParser\Exception as CliParserException; +use SebastianBergmann\CliParser\Parser as CliParser; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Builder +{ + private const LONG_OPTIONS = [ + 'atleast-version=', + 'prepend=', + 'bootstrap=', + 'cache-result', + 'do-not-cache-result', + 'cache-result-file=', + 'check-version', + 'colors==', + 'columns=', + 'configuration=', + 'coverage-cache=', + 'warm-coverage-cache', + 'coverage-filter=', + 'coverage-clover=', + 'coverage-cobertura=', + 'coverage-crap4j=', + 'coverage-html=', + 'coverage-php=', + 'coverage-text==', + 'coverage-xml=', + 'path-coverage', + 'debug', + 'disallow-test-output', + 'disallow-resource-usage', + 'disallow-todo-tests', + 'default-time-limit=', + 'enforce-time-limit', + 'exclude-group=', + 'extensions=', + 'filter=', + 'generate-configuration', + 'globals-backup', + 'group=', + 'covers=', + 'uses=', + 'help', + 'resolve-dependencies', + 'ignore-dependencies', + 'include-path=', + 'list-groups', + 'list-suites', + 'list-tests', + 'list-tests-xml=', + 'loader=', + 'log-junit=', + 'log-teamcity=', + 'migrate-configuration', + 'no-configuration', + 'no-coverage', + 'no-logging', + 'no-interaction', + 'no-extensions', + 'order-by=', + 'printer=', + 'process-isolation', + 'repeat=', + 'dont-report-useless-tests', + 'random-order', + 'random-order-seed=', + 'reverse-order', + 'reverse-list', + 'static-backup', + 'stderr', + 'stop-on-defect', + 'stop-on-error', + 'stop-on-failure', + 'stop-on-warning', + 'stop-on-incomplete', + 'stop-on-risky', + 'stop-on-skipped', + 'fail-on-empty-test-suite', + 'fail-on-incomplete', + 'fail-on-risky', + 'fail-on-skipped', + 'fail-on-warning', + 'strict-coverage', + 'disable-coverage-ignore', + 'strict-global-state', + 'teamcity', + 'testdox', + 'testdox-group=', + 'testdox-exclude-group=', + 'testdox-html=', + 'testdox-text=', + 'testdox-xml=', + 'test-suffix=', + 'testsuite=', + 'verbose', + 'version', + 'whitelist=', + 'dump-xdebug-filter=', + ]; + private const SHORT_OPTIONS = 'd:c:hv'; + + public function fromParameters(array $parameters, array $additionalLongOptions): Configuration + { + try { + $options = (new CliParser)->parse( + $parameters, + self::SHORT_OPTIONS, + array_merge(self::LONG_OPTIONS, $additionalLongOptions), + ); + } catch (CliParserException $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + + $argument = null; + $atLeastVersion = null; + $backupGlobals = null; + $backupStaticAttributes = null; + $beStrictAboutChangesToGlobalState = null; + $beStrictAboutResourceUsageDuringSmallTests = null; + $bootstrap = null; + $cacheResult = null; + $cacheResultFile = null; + $checkVersion = null; + $colors = null; + $columns = null; + $configuration = null; + $coverageCacheDirectory = null; + $warmCoverageCache = null; + $coverageFilter = null; + $coverageClover = null; + $coverageCobertura = null; + $coverageCrap4J = null; + $coverageHtml = null; + $coveragePhp = null; + $coverageText = null; + $coverageTextShowUncoveredFiles = null; + $coverageTextShowOnlySummary = null; + $coverageXml = null; + $pathCoverage = null; + $debug = null; + $defaultTimeLimit = null; + $disableCodeCoverageIgnore = null; + $disallowTestOutput = null; + $disallowTodoAnnotatedTests = null; + $enforceTimeLimit = null; + $excludeGroups = null; + $executionOrder = null; + $executionOrderDefects = null; + $extensions = []; + $unavailableExtensions = []; + $failOnEmptyTestSuite = null; + $failOnIncomplete = null; + $failOnRisky = null; + $failOnSkipped = null; + $failOnWarning = null; + $filter = null; + $generateConfiguration = null; + $migrateConfiguration = null; + $groups = null; + $testsCovering = null; + $testsUsing = null; + $help = null; + $includePath = null; + $iniSettings = []; + $junitLogfile = null; + $listGroups = null; + $listSuites = null; + $listTests = null; + $listTestsXml = null; + $loader = null; + $noCoverage = null; + $noExtensions = null; + $noInteraction = null; + $noLogging = null; + $printer = null; + $processIsolation = null; + $randomOrderSeed = null; + $repeat = null; + $reportUselessTests = null; + $resolveDependencies = null; + $reverseList = null; + $stderr = null; + $strictCoverage = null; + $stopOnDefect = null; + $stopOnError = null; + $stopOnFailure = null; + $stopOnIncomplete = null; + $stopOnRisky = null; + $stopOnSkipped = null; + $stopOnWarning = null; + $teamcityLogfile = null; + $testdoxExcludeGroups = null; + $testdoxGroups = null; + $testdoxHtmlFile = null; + $testdoxTextFile = null; + $testdoxXmlFile = null; + $testSuffixes = null; + $testSuite = null; + $unrecognizedOptions = []; + $unrecognizedOrderBy = null; + $useDefaultConfiguration = null; + $verbose = null; + $version = null; + $xdebugFilterFile = null; + + if (isset($options[1][0])) { + $argument = $options[1][0]; + } + + foreach ($options[0] as $option) { + switch ($option[0]) { + case '--colors': + $colors = $option[1] ?: DefaultResultPrinter::COLOR_AUTO; + + break; + + case '--bootstrap': + $bootstrap = $option[1]; + + break; + + case '--cache-result': + $cacheResult = true; + + break; + + case '--do-not-cache-result': + $cacheResult = false; + + break; + + case '--cache-result-file': + $cacheResultFile = $option[1]; + + break; + + case '--columns': + if (is_numeric($option[1])) { + $columns = (int) $option[1]; + } elseif ($option[1] === 'max') { + $columns = 'max'; + } + + break; + + case 'c': + case '--configuration': + $configuration = $option[1]; + + break; + + case '--coverage-cache': + $coverageCacheDirectory = $option[1]; + + break; + + case '--warm-coverage-cache': + $warmCoverageCache = true; + + break; + + case '--coverage-clover': + $coverageClover = $option[1]; + + break; + + case '--coverage-cobertura': + $coverageCobertura = $option[1]; + + break; + + case '--coverage-crap4j': + $coverageCrap4J = $option[1]; + + break; + + case '--coverage-html': + $coverageHtml = $option[1]; + + break; + + case '--coverage-php': + $coveragePhp = $option[1]; + + break; + + case '--coverage-text': + if ($option[1] === null) { + $option[1] = 'php://stdout'; + } + + $coverageText = $option[1]; + $coverageTextShowUncoveredFiles = false; + $coverageTextShowOnlySummary = false; + + break; + + case '--coverage-xml': + $coverageXml = $option[1]; + + break; + + case '--path-coverage': + $pathCoverage = true; + + break; + + case 'd': + $tmp = explode('=', $option[1]); + + if (isset($tmp[0])) { + if (isset($tmp[1])) { + $iniSettings[$tmp[0]] = $tmp[1]; + } else { + $iniSettings[$tmp[0]] = '1'; + } + } + + break; + + case '--debug': + $debug = true; + + break; + + case 'h': + case '--help': + $help = true; + + break; + + case '--filter': + $filter = $option[1]; + + break; + + case '--testsuite': + $testSuite = $option[1]; + + break; + + case '--generate-configuration': + $generateConfiguration = true; + + break; + + case '--migrate-configuration': + $migrateConfiguration = true; + + break; + + case '--group': + $groups = explode(',', $option[1]); + + break; + + case '--exclude-group': + $excludeGroups = explode(',', $option[1]); + + break; + + case '--covers': + $testsCovering = array_map('strtolower', explode(',', $option[1])); + + break; + + case '--uses': + $testsUsing = array_map('strtolower', explode(',', $option[1])); + + break; + + case '--test-suffix': + $testSuffixes = explode(',', $option[1]); + + break; + + case '--include-path': + $includePath = $option[1]; + + break; + + case '--list-groups': + $listGroups = true; + + break; + + case '--list-suites': + $listSuites = true; + + break; + + case '--list-tests': + $listTests = true; + + break; + + case '--list-tests-xml': + $listTestsXml = $option[1]; + + break; + + case '--printer': + $printer = $option[1]; + + break; + + case '--loader': + $loader = $option[1]; + + break; + + case '--log-junit': + $junitLogfile = $option[1]; + + break; + + case '--log-teamcity': + $teamcityLogfile = $option[1]; + + break; + + case '--order-by': + foreach (explode(',', $option[1]) as $order) { + switch ($order) { + case 'default': + $executionOrder = TestSuiteSorter::ORDER_DEFAULT; + $executionOrderDefects = TestSuiteSorter::ORDER_DEFAULT; + $resolveDependencies = true; + + break; + + case 'defects': + $executionOrderDefects = TestSuiteSorter::ORDER_DEFECTS_FIRST; + + break; + + case 'depends': + $resolveDependencies = true; + + break; + + case 'duration': + $executionOrder = TestSuiteSorter::ORDER_DURATION; + + break; + + case 'no-depends': + $resolveDependencies = false; + + break; + + case 'random': + $executionOrder = TestSuiteSorter::ORDER_RANDOMIZED; + + break; + + case 'reverse': + $executionOrder = TestSuiteSorter::ORDER_REVERSED; + + break; + + case 'size': + $executionOrder = TestSuiteSorter::ORDER_SIZE; + + break; + + default: + $unrecognizedOrderBy = $order; + } + } + + break; + + case '--process-isolation': + $processIsolation = true; + + break; + + case '--repeat': + $repeat = (int) $option[1]; + + break; + + case '--stderr': + $stderr = true; + + break; + + case '--stop-on-defect': + $stopOnDefect = true; + + break; + + case '--stop-on-error': + $stopOnError = true; + + break; + + case '--stop-on-failure': + $stopOnFailure = true; + + break; + + case '--stop-on-warning': + $stopOnWarning = true; + + break; + + case '--stop-on-incomplete': + $stopOnIncomplete = true; + + break; + + case '--stop-on-risky': + $stopOnRisky = true; + + break; + + case '--stop-on-skipped': + $stopOnSkipped = true; + + break; + + case '--fail-on-empty-test-suite': + $failOnEmptyTestSuite = true; + + break; + + case '--fail-on-incomplete': + $failOnIncomplete = true; + + break; + + case '--fail-on-risky': + $failOnRisky = true; + + break; + + case '--fail-on-skipped': + $failOnSkipped = true; + + break; + + case '--fail-on-warning': + $failOnWarning = true; + + break; + + case '--teamcity': + $printer = TeamCity::class; + + break; + + case '--testdox': + $printer = CliTestDoxPrinter::class; + + break; + + case '--testdox-group': + $testdoxGroups = explode(',', $option[1]); + + break; + + case '--testdox-exclude-group': + $testdoxExcludeGroups = explode(',', $option[1]); + + break; + + case '--testdox-html': + $testdoxHtmlFile = $option[1]; + + break; + + case '--testdox-text': + $testdoxTextFile = $option[1]; + + break; + + case '--testdox-xml': + $testdoxXmlFile = $option[1]; + + break; + + case '--no-configuration': + $useDefaultConfiguration = false; + + break; + + case '--extensions': + foreach (explode(',', $option[1]) as $extensionClass) { + if (!class_exists($extensionClass)) { + $unavailableExtensions[] = $extensionClass; + + continue; + } + + $extensions[] = new Extension($extensionClass, '', []); + } + + break; + + case '--no-extensions': + $noExtensions = true; + + break; + + case '--no-coverage': + $noCoverage = true; + + break; + + case '--no-logging': + $noLogging = true; + + break; + + case '--no-interaction': + $noInteraction = true; + + break; + + case '--globals-backup': + $backupGlobals = true; + + break; + + case '--static-backup': + $backupStaticAttributes = true; + + break; + + case 'v': + case '--verbose': + $verbose = true; + + break; + + case '--atleast-version': + $atLeastVersion = $option[1]; + + break; + + case '--version': + $version = true; + + break; + + case '--dont-report-useless-tests': + $reportUselessTests = false; + + break; + + case '--strict-coverage': + $strictCoverage = true; + + break; + + case '--disable-coverage-ignore': + $disableCodeCoverageIgnore = true; + + break; + + case '--strict-global-state': + $beStrictAboutChangesToGlobalState = true; + + break; + + case '--disallow-test-output': + $disallowTestOutput = true; + + break; + + case '--disallow-resource-usage': + $beStrictAboutResourceUsageDuringSmallTests = true; + + break; + + case '--default-time-limit': + $defaultTimeLimit = (int) $option[1]; + + break; + + case '--enforce-time-limit': + $enforceTimeLimit = true; + + break; + + case '--disallow-todo-tests': + $disallowTodoAnnotatedTests = true; + + break; + + case '--reverse-list': + $reverseList = true; + + break; + + case '--check-version': + $checkVersion = true; + + break; + + case '--coverage-filter': + case '--whitelist': + if ($coverageFilter === null) { + $coverageFilter = []; + } + + $coverageFilter[] = $option[1]; + + break; + + case '--random-order': + $executionOrder = TestSuiteSorter::ORDER_RANDOMIZED; + + break; + + case '--random-order-seed': + $randomOrderSeed = (int) $option[1]; + + break; + + case '--resolve-dependencies': + $resolveDependencies = true; + + break; + + case '--ignore-dependencies': + $resolveDependencies = false; + + break; + + case '--reverse-order': + $executionOrder = TestSuiteSorter::ORDER_REVERSED; + + break; + + case '--dump-xdebug-filter': + $xdebugFilterFile = $option[1]; + + break; + + default: + $unrecognizedOptions[str_replace('--', '', $option[0])] = $option[1]; + } + } + + if (empty($extensions)) { + $extensions = null; + } + + if (empty($unavailableExtensions)) { + $unavailableExtensions = null; + } + + if (empty($iniSettings)) { + $iniSettings = null; + } + + if (empty($coverageFilter)) { + $coverageFilter = null; + } + + return new Configuration( + $argument, + $atLeastVersion, + $backupGlobals, + $backupStaticAttributes, + $beStrictAboutChangesToGlobalState, + $beStrictAboutResourceUsageDuringSmallTests, + $bootstrap, + $cacheResult, + $cacheResultFile, + $checkVersion, + $colors, + $columns, + $configuration, + $coverageClover, + $coverageCobertura, + $coverageCrap4J, + $coverageHtml, + $coveragePhp, + $coverageText, + $coverageTextShowUncoveredFiles, + $coverageTextShowOnlySummary, + $coverageXml, + $pathCoverage, + $coverageCacheDirectory, + $warmCoverageCache, + $debug, + $defaultTimeLimit, + $disableCodeCoverageIgnore, + $disallowTestOutput, + $disallowTodoAnnotatedTests, + $enforceTimeLimit, + $excludeGroups, + $executionOrder, + $executionOrderDefects, + $extensions, + $unavailableExtensions, + $failOnEmptyTestSuite, + $failOnIncomplete, + $failOnRisky, + $failOnSkipped, + $failOnWarning, + $filter, + $generateConfiguration, + $migrateConfiguration, + $groups, + $testsCovering, + $testsUsing, + $help, + $includePath, + $iniSettings, + $junitLogfile, + $listGroups, + $listSuites, + $listTests, + $listTestsXml, + $loader, + $noCoverage, + $noExtensions, + $noInteraction, + $noLogging, + $printer, + $processIsolation, + $randomOrderSeed, + $repeat, + $reportUselessTests, + $resolveDependencies, + $reverseList, + $stderr, + $strictCoverage, + $stopOnDefect, + $stopOnError, + $stopOnFailure, + $stopOnIncomplete, + $stopOnRisky, + $stopOnSkipped, + $stopOnWarning, + $teamcityLogfile, + $testdoxExcludeGroups, + $testdoxGroups, + $testdoxHtmlFile, + $testdoxTextFile, + $testdoxXmlFile, + $testSuffixes, + $testSuite, + $unrecognizedOptions, + $unrecognizedOrderBy, + $useDefaultConfiguration, + $verbose, + $version, + $coverageFilter, + $xdebugFilterFile, + ); + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/CliArguments/Configuration.php b/vendor/phpunit/phpunit/src/TextUI/CliArguments/Configuration.php new file mode 100644 index 00000000..51bf5cb8 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/CliArguments/Configuration.php @@ -0,0 +1,2108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\CliArguments; + +use PHPUnit\TextUI\XmlConfiguration\Extension; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Configuration +{ + /** + * @var ?string + */ + private $argument; + + /** + * @var ?string + */ + private $atLeastVersion; + + /** + * @var ?bool + */ + private $backupGlobals; + + /** + * @var ?bool + */ + private $backupStaticAttributes; + + /** + * @var ?bool + */ + private $beStrictAboutChangesToGlobalState; + + /** + * @var ?bool + */ + private $beStrictAboutResourceUsageDuringSmallTests; + + /** + * @var ?string + */ + private $bootstrap; + + /** + * @var ?bool + */ + private $cacheResult; + + /** + * @var ?string + */ + private $cacheResultFile; + + /** + * @var ?bool + */ + private $checkVersion; + + /** + * @var ?string + */ + private $colors; + + /** + * @var null|int|string + */ + private $columns; + + /** + * @var ?string + */ + private $configuration; + + /** + * @var null|string[] + */ + private $coverageFilter; + + /** + * @var ?string + */ + private $coverageClover; + + /** + * @var ?string + */ + private $coverageCobertura; + + /** + * @var ?string + */ + private $coverageCrap4J; + + /** + * @var ?string + */ + private $coverageHtml; + + /** + * @var ?string + */ + private $coveragePhp; + + /** + * @var ?string + */ + private $coverageText; + + /** + * @var ?bool + */ + private $coverageTextShowUncoveredFiles; + + /** + * @var ?bool + */ + private $coverageTextShowOnlySummary; + + /** + * @var ?string + */ + private $coverageXml; + + /** + * @var ?bool + */ + private $pathCoverage; + + /** + * @var ?string + */ + private $coverageCacheDirectory; + + /** + * @var ?bool + */ + private $warmCoverageCache; + + /** + * @var ?bool + */ + private $debug; + + /** + * @var ?int + */ + private $defaultTimeLimit; + + /** + * @var ?bool + */ + private $disableCodeCoverageIgnore; + + /** + * @var ?bool + */ + private $disallowTestOutput; + + /** + * @var ?bool + */ + private $disallowTodoAnnotatedTests; + + /** + * @var ?bool + */ + private $enforceTimeLimit; + + /** + * @var null|string[] + */ + private $excludeGroups; + + /** + * @var ?int + */ + private $executionOrder; + + /** + * @var ?int + */ + private $executionOrderDefects; + + /** + * @var null|Extension[] + */ + private $extensions; + + /** + * @var null|string[] + */ + private $unavailableExtensions; + + /** + * @var ?bool + */ + private $failOnEmptyTestSuite; + + /** + * @var ?bool + */ + private $failOnIncomplete; + + /** + * @var ?bool + */ + private $failOnRisky; + + /** + * @var ?bool + */ + private $failOnSkipped; + + /** + * @var ?bool + */ + private $failOnWarning; + + /** + * @var ?string + */ + private $filter; + + /** + * @var ?bool + */ + private $generateConfiguration; + + /** + * @var ?bool + */ + private $migrateConfiguration; + + /** + * @var null|string[] + */ + private $groups; + + /** + * @var null|string[] + */ + private $testsCovering; + + /** + * @var null|string[] + */ + private $testsUsing; + + /** + * @var ?bool + */ + private $help; + + /** + * @var ?string + */ + private $includePath; + + /** + * @var null|string[] + */ + private $iniSettings; + + /** + * @var ?string + */ + private $junitLogfile; + + /** + * @var ?bool + */ + private $listGroups; + + /** + * @var ?bool + */ + private $listSuites; + + /** + * @var ?bool + */ + private $listTests; + + /** + * @var ?string + */ + private $listTestsXml; + + /** + * @var ?string + */ + private $loader; + + /** + * @var ?bool + */ + private $noCoverage; + + /** + * @var ?bool + */ + private $noExtensions; + + /** + * @var ?bool + */ + private $noInteraction; + + /** + * @var ?bool + */ + private $noLogging; + + /** + * @var ?string + */ + private $printer; + + /** + * @var ?bool + */ + private $processIsolation; + + /** + * @var ?int + */ + private $randomOrderSeed; + + /** + * @var ?int + */ + private $repeat; + + /** + * @var ?bool + */ + private $reportUselessTests; + + /** + * @var ?bool + */ + private $resolveDependencies; + + /** + * @var ?bool + */ + private $reverseList; + + /** + * @var ?bool + */ + private $stderr; + + /** + * @var ?bool + */ + private $strictCoverage; + + /** + * @var ?bool + */ + private $stopOnDefect; + + /** + * @var ?bool + */ + private $stopOnError; + + /** + * @var ?bool + */ + private $stopOnFailure; + + /** + * @var ?bool + */ + private $stopOnIncomplete; + + /** + * @var ?bool + */ + private $stopOnRisky; + + /** + * @var ?bool + */ + private $stopOnSkipped; + + /** + * @var ?bool + */ + private $stopOnWarning; + + /** + * @var ?string + */ + private $teamcityLogfile; + + /** + * @var null|string[] + */ + private $testdoxExcludeGroups; + + /** + * @var null|string[] + */ + private $testdoxGroups; + + /** + * @var ?string + */ + private $testdoxHtmlFile; + + /** + * @var ?string + */ + private $testdoxTextFile; + + /** + * @var ?string + */ + private $testdoxXmlFile; + + /** + * @var null|string[] + */ + private $testSuffixes; + + /** + * @var ?string + */ + private $testSuite; + + /** + * @var string[] + */ + private $unrecognizedOptions; + + /** + * @var ?string + */ + private $unrecognizedOrderBy; + + /** + * @var ?bool + */ + private $useDefaultConfiguration; + + /** + * @var ?bool + */ + private $verbose; + + /** + * @var ?bool + */ + private $version; + + /** + * @var ?string + */ + private $xdebugFilterFile; + + /** + * @param null|int|string $columns + */ + public function __construct(?string $argument, ?string $atLeastVersion, ?bool $backupGlobals, ?bool $backupStaticAttributes, ?bool $beStrictAboutChangesToGlobalState, ?bool $beStrictAboutResourceUsageDuringSmallTests, ?string $bootstrap, ?bool $cacheResult, ?string $cacheResultFile, ?bool $checkVersion, ?string $colors, $columns, ?string $configuration, ?string $coverageClover, ?string $coverageCobertura, ?string $coverageCrap4J, ?string $coverageHtml, ?string $coveragePhp, ?string $coverageText, ?bool $coverageTextShowUncoveredFiles, ?bool $coverageTextShowOnlySummary, ?string $coverageXml, ?bool $pathCoverage, ?string $coverageCacheDirectory, ?bool $warmCoverageCache, ?bool $debug, ?int $defaultTimeLimit, ?bool $disableCodeCoverageIgnore, ?bool $disallowTestOutput, ?bool $disallowTodoAnnotatedTests, ?bool $enforceTimeLimit, ?array $excludeGroups, ?int $executionOrder, ?int $executionOrderDefects, ?array $extensions, ?array $unavailableExtensions, ?bool $failOnEmptyTestSuite, ?bool $failOnIncomplete, ?bool $failOnRisky, ?bool $failOnSkipped, ?bool $failOnWarning, ?string $filter, ?bool $generateConfiguration, ?bool $migrateConfiguration, ?array $groups, ?array $testsCovering, ?array $testsUsing, ?bool $help, ?string $includePath, ?array $iniSettings, ?string $junitLogfile, ?bool $listGroups, ?bool $listSuites, ?bool $listTests, ?string $listTestsXml, ?string $loader, ?bool $noCoverage, ?bool $noExtensions, ?bool $noInteraction, ?bool $noLogging, ?string $printer, ?bool $processIsolation, ?int $randomOrderSeed, ?int $repeat, ?bool $reportUselessTests, ?bool $resolveDependencies, ?bool $reverseList, ?bool $stderr, ?bool $strictCoverage, ?bool $stopOnDefect, ?bool $stopOnError, ?bool $stopOnFailure, ?bool $stopOnIncomplete, ?bool $stopOnRisky, ?bool $stopOnSkipped, ?bool $stopOnWarning, ?string $teamcityLogfile, ?array $testdoxExcludeGroups, ?array $testdoxGroups, ?string $testdoxHtmlFile, ?string $testdoxTextFile, ?string $testdoxXmlFile, ?array $testSuffixes, ?string $testSuite, array $unrecognizedOptions, ?string $unrecognizedOrderBy, ?bool $useDefaultConfiguration, ?bool $verbose, ?bool $version, ?array $coverageFilter, ?string $xdebugFilterFile) + { + $this->argument = $argument; + $this->atLeastVersion = $atLeastVersion; + $this->backupGlobals = $backupGlobals; + $this->backupStaticAttributes = $backupStaticAttributes; + $this->beStrictAboutChangesToGlobalState = $beStrictAboutChangesToGlobalState; + $this->beStrictAboutResourceUsageDuringSmallTests = $beStrictAboutResourceUsageDuringSmallTests; + $this->bootstrap = $bootstrap; + $this->cacheResult = $cacheResult; + $this->cacheResultFile = $cacheResultFile; + $this->checkVersion = $checkVersion; + $this->colors = $colors; + $this->columns = $columns; + $this->configuration = $configuration; + $this->coverageFilter = $coverageFilter; + $this->coverageClover = $coverageClover; + $this->coverageCobertura = $coverageCobertura; + $this->coverageCrap4J = $coverageCrap4J; + $this->coverageHtml = $coverageHtml; + $this->coveragePhp = $coveragePhp; + $this->coverageText = $coverageText; + $this->coverageTextShowUncoveredFiles = $coverageTextShowUncoveredFiles; + $this->coverageTextShowOnlySummary = $coverageTextShowOnlySummary; + $this->coverageXml = $coverageXml; + $this->pathCoverage = $pathCoverage; + $this->coverageCacheDirectory = $coverageCacheDirectory; + $this->warmCoverageCache = $warmCoverageCache; + $this->debug = $debug; + $this->defaultTimeLimit = $defaultTimeLimit; + $this->disableCodeCoverageIgnore = $disableCodeCoverageIgnore; + $this->disallowTestOutput = $disallowTestOutput; + $this->disallowTodoAnnotatedTests = $disallowTodoAnnotatedTests; + $this->enforceTimeLimit = $enforceTimeLimit; + $this->excludeGroups = $excludeGroups; + $this->executionOrder = $executionOrder; + $this->executionOrderDefects = $executionOrderDefects; + $this->extensions = $extensions; + $this->unavailableExtensions = $unavailableExtensions; + $this->failOnEmptyTestSuite = $failOnEmptyTestSuite; + $this->failOnIncomplete = $failOnIncomplete; + $this->failOnRisky = $failOnRisky; + $this->failOnSkipped = $failOnSkipped; + $this->failOnWarning = $failOnWarning; + $this->filter = $filter; + $this->generateConfiguration = $generateConfiguration; + $this->migrateConfiguration = $migrateConfiguration; + $this->groups = $groups; + $this->testsCovering = $testsCovering; + $this->testsUsing = $testsUsing; + $this->help = $help; + $this->includePath = $includePath; + $this->iniSettings = $iniSettings; + $this->junitLogfile = $junitLogfile; + $this->listGroups = $listGroups; + $this->listSuites = $listSuites; + $this->listTests = $listTests; + $this->listTestsXml = $listTestsXml; + $this->loader = $loader; + $this->noCoverage = $noCoverage; + $this->noExtensions = $noExtensions; + $this->noInteraction = $noInteraction; + $this->noLogging = $noLogging; + $this->printer = $printer; + $this->processIsolation = $processIsolation; + $this->randomOrderSeed = $randomOrderSeed; + $this->repeat = $repeat; + $this->reportUselessTests = $reportUselessTests; + $this->resolveDependencies = $resolveDependencies; + $this->reverseList = $reverseList; + $this->stderr = $stderr; + $this->strictCoverage = $strictCoverage; + $this->stopOnDefect = $stopOnDefect; + $this->stopOnError = $stopOnError; + $this->stopOnFailure = $stopOnFailure; + $this->stopOnIncomplete = $stopOnIncomplete; + $this->stopOnRisky = $stopOnRisky; + $this->stopOnSkipped = $stopOnSkipped; + $this->stopOnWarning = $stopOnWarning; + $this->teamcityLogfile = $teamcityLogfile; + $this->testdoxExcludeGroups = $testdoxExcludeGroups; + $this->testdoxGroups = $testdoxGroups; + $this->testdoxHtmlFile = $testdoxHtmlFile; + $this->testdoxTextFile = $testdoxTextFile; + $this->testdoxXmlFile = $testdoxXmlFile; + $this->testSuffixes = $testSuffixes; + $this->testSuite = $testSuite; + $this->unrecognizedOptions = $unrecognizedOptions; + $this->unrecognizedOrderBy = $unrecognizedOrderBy; + $this->useDefaultConfiguration = $useDefaultConfiguration; + $this->verbose = $verbose; + $this->version = $version; + $this->xdebugFilterFile = $xdebugFilterFile; + } + + public function hasArgument(): bool + { + return $this->argument !== null; + } + + /** + * @throws Exception + */ + public function argument(): string + { + if ($this->argument === null) { + throw new Exception; + } + + return $this->argument; + } + + public function hasAtLeastVersion(): bool + { + return $this->atLeastVersion !== null; + } + + /** + * @throws Exception + */ + public function atLeastVersion(): string + { + if ($this->atLeastVersion === null) { + throw new Exception; + } + + return $this->atLeastVersion; + } + + public function hasBackupGlobals(): bool + { + return $this->backupGlobals !== null; + } + + /** + * @throws Exception + */ + public function backupGlobals(): bool + { + if ($this->backupGlobals === null) { + throw new Exception; + } + + return $this->backupGlobals; + } + + public function hasBackupStaticAttributes(): bool + { + return $this->backupStaticAttributes !== null; + } + + /** + * @throws Exception + */ + public function backupStaticAttributes(): bool + { + if ($this->backupStaticAttributes === null) { + throw new Exception; + } + + return $this->backupStaticAttributes; + } + + public function hasBeStrictAboutChangesToGlobalState(): bool + { + return $this->beStrictAboutChangesToGlobalState !== null; + } + + /** + * @throws Exception + */ + public function beStrictAboutChangesToGlobalState(): bool + { + if ($this->beStrictAboutChangesToGlobalState === null) { + throw new Exception; + } + + return $this->beStrictAboutChangesToGlobalState; + } + + public function hasBeStrictAboutResourceUsageDuringSmallTests(): bool + { + return $this->beStrictAboutResourceUsageDuringSmallTests !== null; + } + + /** + * @throws Exception + */ + public function beStrictAboutResourceUsageDuringSmallTests(): bool + { + if ($this->beStrictAboutResourceUsageDuringSmallTests === null) { + throw new Exception; + } + + return $this->beStrictAboutResourceUsageDuringSmallTests; + } + + public function hasBootstrap(): bool + { + return $this->bootstrap !== null; + } + + /** + * @throws Exception + */ + public function bootstrap(): string + { + if ($this->bootstrap === null) { + throw new Exception; + } + + return $this->bootstrap; + } + + public function hasCacheResult(): bool + { + return $this->cacheResult !== null; + } + + /** + * @throws Exception + */ + public function cacheResult(): bool + { + if ($this->cacheResult === null) { + throw new Exception; + } + + return $this->cacheResult; + } + + public function hasCacheResultFile(): bool + { + return $this->cacheResultFile !== null; + } + + /** + * @throws Exception + */ + public function cacheResultFile(): string + { + if ($this->cacheResultFile === null) { + throw new Exception; + } + + return $this->cacheResultFile; + } + + public function hasCheckVersion(): bool + { + return $this->checkVersion !== null; + } + + /** + * @throws Exception + */ + public function checkVersion(): bool + { + if ($this->checkVersion === null) { + throw new Exception; + } + + return $this->checkVersion; + } + + public function hasColors(): bool + { + return $this->colors !== null; + } + + /** + * @throws Exception + */ + public function colors(): string + { + if ($this->colors === null) { + throw new Exception; + } + + return $this->colors; + } + + public function hasColumns(): bool + { + return $this->columns !== null; + } + + /** + * @throws Exception + */ + public function columns() + { + if ($this->columns === null) { + throw new Exception; + } + + return $this->columns; + } + + public function hasConfiguration(): bool + { + return $this->configuration !== null; + } + + /** + * @throws Exception + */ + public function configuration(): string + { + if ($this->configuration === null) { + throw new Exception; + } + + return $this->configuration; + } + + public function hasCoverageFilter(): bool + { + return $this->coverageFilter !== null; + } + + /** + * @throws Exception + */ + public function coverageFilter(): array + { + if ($this->coverageFilter === null) { + throw new Exception; + } + + return $this->coverageFilter; + } + + public function hasCoverageClover(): bool + { + return $this->coverageClover !== null; + } + + /** + * @throws Exception + */ + public function coverageClover(): string + { + if ($this->coverageClover === null) { + throw new Exception; + } + + return $this->coverageClover; + } + + public function hasCoverageCobertura(): bool + { + return $this->coverageCobertura !== null; + } + + /** + * @throws Exception + */ + public function coverageCobertura(): string + { + if ($this->coverageCobertura === null) { + throw new Exception; + } + + return $this->coverageCobertura; + } + + public function hasCoverageCrap4J(): bool + { + return $this->coverageCrap4J !== null; + } + + /** + * @throws Exception + */ + public function coverageCrap4J(): string + { + if ($this->coverageCrap4J === null) { + throw new Exception; + } + + return $this->coverageCrap4J; + } + + public function hasCoverageHtml(): bool + { + return $this->coverageHtml !== null; + } + + /** + * @throws Exception + */ + public function coverageHtml(): string + { + if ($this->coverageHtml === null) { + throw new Exception; + } + + return $this->coverageHtml; + } + + public function hasCoveragePhp(): bool + { + return $this->coveragePhp !== null; + } + + /** + * @throws Exception + */ + public function coveragePhp(): string + { + if ($this->coveragePhp === null) { + throw new Exception; + } + + return $this->coveragePhp; + } + + public function hasCoverageText(): bool + { + return $this->coverageText !== null; + } + + /** + * @throws Exception + */ + public function coverageText(): string + { + if ($this->coverageText === null) { + throw new Exception; + } + + return $this->coverageText; + } + + public function hasCoverageTextShowUncoveredFiles(): bool + { + return $this->coverageTextShowUncoveredFiles !== null; + } + + /** + * @throws Exception + */ + public function coverageTextShowUncoveredFiles(): bool + { + if ($this->coverageTextShowUncoveredFiles === null) { + throw new Exception; + } + + return $this->coverageTextShowUncoveredFiles; + } + + public function hasCoverageTextShowOnlySummary(): bool + { + return $this->coverageTextShowOnlySummary !== null; + } + + /** + * @throws Exception + */ + public function coverageTextShowOnlySummary(): bool + { + if ($this->coverageTextShowOnlySummary === null) { + throw new Exception; + } + + return $this->coverageTextShowOnlySummary; + } + + public function hasCoverageXml(): bool + { + return $this->coverageXml !== null; + } + + /** + * @throws Exception + */ + public function coverageXml(): string + { + if ($this->coverageXml === null) { + throw new Exception; + } + + return $this->coverageXml; + } + + public function hasPathCoverage(): bool + { + return $this->pathCoverage !== null; + } + + /** + * @throws Exception + */ + public function pathCoverage(): bool + { + if ($this->pathCoverage === null) { + throw new Exception; + } + + return $this->pathCoverage; + } + + public function hasCoverageCacheDirectory(): bool + { + return $this->coverageCacheDirectory !== null; + } + + /** + * @throws Exception + */ + public function coverageCacheDirectory(): string + { + if ($this->coverageCacheDirectory === null) { + throw new Exception; + } + + return $this->coverageCacheDirectory; + } + + public function hasWarmCoverageCache(): bool + { + return $this->warmCoverageCache !== null; + } + + /** + * @throws Exception + */ + public function warmCoverageCache(): bool + { + if ($this->warmCoverageCache === null) { + throw new Exception; + } + + return $this->warmCoverageCache; + } + + public function hasDebug(): bool + { + return $this->debug !== null; + } + + /** + * @throws Exception + */ + public function debug(): bool + { + if ($this->debug === null) { + throw new Exception; + } + + return $this->debug; + } + + public function hasDefaultTimeLimit(): bool + { + return $this->defaultTimeLimit !== null; + } + + /** + * @throws Exception + */ + public function defaultTimeLimit(): int + { + if ($this->defaultTimeLimit === null) { + throw new Exception; + } + + return $this->defaultTimeLimit; + } + + public function hasDisableCodeCoverageIgnore(): bool + { + return $this->disableCodeCoverageIgnore !== null; + } + + /** + * @throws Exception + */ + public function disableCodeCoverageIgnore(): bool + { + if ($this->disableCodeCoverageIgnore === null) { + throw new Exception; + } + + return $this->disableCodeCoverageIgnore; + } + + public function hasDisallowTestOutput(): bool + { + return $this->disallowTestOutput !== null; + } + + /** + * @throws Exception + */ + public function disallowTestOutput(): bool + { + if ($this->disallowTestOutput === null) { + throw new Exception; + } + + return $this->disallowTestOutput; + } + + public function hasDisallowTodoAnnotatedTests(): bool + { + return $this->disallowTodoAnnotatedTests !== null; + } + + /** + * @throws Exception + */ + public function disallowTodoAnnotatedTests(): bool + { + if ($this->disallowTodoAnnotatedTests === null) { + throw new Exception; + } + + return $this->disallowTodoAnnotatedTests; + } + + public function hasEnforceTimeLimit(): bool + { + return $this->enforceTimeLimit !== null; + } + + /** + * @throws Exception + */ + public function enforceTimeLimit(): bool + { + if ($this->enforceTimeLimit === null) { + throw new Exception; + } + + return $this->enforceTimeLimit; + } + + public function hasExcludeGroups(): bool + { + return $this->excludeGroups !== null; + } + + /** + * @throws Exception + */ + public function excludeGroups(): array + { + if ($this->excludeGroups === null) { + throw new Exception; + } + + return $this->excludeGroups; + } + + public function hasExecutionOrder(): bool + { + return $this->executionOrder !== null; + } + + /** + * @throws Exception + */ + public function executionOrder(): int + { + if ($this->executionOrder === null) { + throw new Exception; + } + + return $this->executionOrder; + } + + public function hasExecutionOrderDefects(): bool + { + return $this->executionOrderDefects !== null; + } + + /** + * @throws Exception + */ + public function executionOrderDefects(): int + { + if ($this->executionOrderDefects === null) { + throw new Exception; + } + + return $this->executionOrderDefects; + } + + public function hasFailOnEmptyTestSuite(): bool + { + return $this->failOnEmptyTestSuite !== null; + } + + /** + * @throws Exception + */ + public function failOnEmptyTestSuite(): bool + { + if ($this->failOnEmptyTestSuite === null) { + throw new Exception; + } + + return $this->failOnEmptyTestSuite; + } + + public function hasFailOnIncomplete(): bool + { + return $this->failOnIncomplete !== null; + } + + /** + * @throws Exception + */ + public function failOnIncomplete(): bool + { + if ($this->failOnIncomplete === null) { + throw new Exception; + } + + return $this->failOnIncomplete; + } + + public function hasFailOnRisky(): bool + { + return $this->failOnRisky !== null; + } + + /** + * @throws Exception + */ + public function failOnRisky(): bool + { + if ($this->failOnRisky === null) { + throw new Exception; + } + + return $this->failOnRisky; + } + + public function hasFailOnSkipped(): bool + { + return $this->failOnSkipped !== null; + } + + /** + * @throws Exception + */ + public function failOnSkipped(): bool + { + if ($this->failOnSkipped === null) { + throw new Exception; + } + + return $this->failOnSkipped; + } + + public function hasFailOnWarning(): bool + { + return $this->failOnWarning !== null; + } + + /** + * @throws Exception + */ + public function failOnWarning(): bool + { + if ($this->failOnWarning === null) { + throw new Exception; + } + + return $this->failOnWarning; + } + + public function hasFilter(): bool + { + return $this->filter !== null; + } + + /** + * @throws Exception + */ + public function filter(): string + { + if ($this->filter === null) { + throw new Exception; + } + + return $this->filter; + } + + public function hasGenerateConfiguration(): bool + { + return $this->generateConfiguration !== null; + } + + /** + * @throws Exception + */ + public function generateConfiguration(): bool + { + if ($this->generateConfiguration === null) { + throw new Exception; + } + + return $this->generateConfiguration; + } + + public function hasMigrateConfiguration(): bool + { + return $this->migrateConfiguration !== null; + } + + /** + * @throws Exception + */ + public function migrateConfiguration(): bool + { + if ($this->migrateConfiguration === null) { + throw new Exception; + } + + return $this->migrateConfiguration; + } + + public function hasGroups(): bool + { + return $this->groups !== null; + } + + /** + * @throws Exception + */ + public function groups(): array + { + if ($this->groups === null) { + throw new Exception; + } + + return $this->groups; + } + + public function hasTestsCovering(): bool + { + return $this->testsCovering !== null; + } + + /** + * @throws Exception + */ + public function testsCovering(): array + { + if ($this->testsCovering === null) { + throw new Exception; + } + + return $this->testsCovering; + } + + public function hasTestsUsing(): bool + { + return $this->testsUsing !== null; + } + + /** + * @throws Exception + */ + public function testsUsing(): array + { + if ($this->testsUsing === null) { + throw new Exception; + } + + return $this->testsUsing; + } + + public function hasHelp(): bool + { + return $this->help !== null; + } + + /** + * @throws Exception + */ + public function help(): bool + { + if ($this->help === null) { + throw new Exception; + } + + return $this->help; + } + + public function hasIncludePath(): bool + { + return $this->includePath !== null; + } + + /** + * @throws Exception + */ + public function includePath(): string + { + if ($this->includePath === null) { + throw new Exception; + } + + return $this->includePath; + } + + public function hasIniSettings(): bool + { + return $this->iniSettings !== null; + } + + /** + * @throws Exception + */ + public function iniSettings(): array + { + if ($this->iniSettings === null) { + throw new Exception; + } + + return $this->iniSettings; + } + + public function hasJunitLogfile(): bool + { + return $this->junitLogfile !== null; + } + + /** + * @throws Exception + */ + public function junitLogfile(): string + { + if ($this->junitLogfile === null) { + throw new Exception; + } + + return $this->junitLogfile; + } + + public function hasListGroups(): bool + { + return $this->listGroups !== null; + } + + /** + * @throws Exception + */ + public function listGroups(): bool + { + if ($this->listGroups === null) { + throw new Exception; + } + + return $this->listGroups; + } + + public function hasListSuites(): bool + { + return $this->listSuites !== null; + } + + /** + * @throws Exception + */ + public function listSuites(): bool + { + if ($this->listSuites === null) { + throw new Exception; + } + + return $this->listSuites; + } + + public function hasListTests(): bool + { + return $this->listTests !== null; + } + + /** + * @throws Exception + */ + public function listTests(): bool + { + if ($this->listTests === null) { + throw new Exception; + } + + return $this->listTests; + } + + public function hasListTestsXml(): bool + { + return $this->listTestsXml !== null; + } + + /** + * @throws Exception + */ + public function listTestsXml(): string + { + if ($this->listTestsXml === null) { + throw new Exception; + } + + return $this->listTestsXml; + } + + public function hasLoader(): bool + { + return $this->loader !== null; + } + + /** + * @throws Exception + */ + public function loader(): string + { + if ($this->loader === null) { + throw new Exception; + } + + return $this->loader; + } + + public function hasNoCoverage(): bool + { + return $this->noCoverage !== null; + } + + /** + * @throws Exception + */ + public function noCoverage(): bool + { + if ($this->noCoverage === null) { + throw new Exception; + } + + return $this->noCoverage; + } + + public function hasNoExtensions(): bool + { + return $this->noExtensions !== null; + } + + /** + * @throws Exception + */ + public function noExtensions(): bool + { + if ($this->noExtensions === null) { + throw new Exception; + } + + return $this->noExtensions; + } + + public function hasExtensions(): bool + { + return $this->extensions !== null; + } + + /** + * @throws Exception + */ + public function extensions(): array + { + if ($this->extensions === null) { + throw new Exception; + } + + return $this->extensions; + } + + public function hasUnavailableExtensions(): bool + { + return $this->unavailableExtensions !== null; + } + + /** + * @throws Exception + */ + public function unavailableExtensions(): array + { + if ($this->unavailableExtensions === null) { + throw new Exception; + } + + return $this->unavailableExtensions; + } + + public function hasNoInteraction(): bool + { + return $this->noInteraction !== null; + } + + /** + * @throws Exception + */ + public function noInteraction(): bool + { + if ($this->noInteraction === null) { + throw new Exception; + } + + return $this->noInteraction; + } + + public function hasNoLogging(): bool + { + return $this->noLogging !== null; + } + + /** + * @throws Exception + */ + public function noLogging(): bool + { + if ($this->noLogging === null) { + throw new Exception; + } + + return $this->noLogging; + } + + public function hasPrinter(): bool + { + return $this->printer !== null; + } + + /** + * @throws Exception + */ + public function printer(): string + { + if ($this->printer === null) { + throw new Exception; + } + + return $this->printer; + } + + public function hasProcessIsolation(): bool + { + return $this->processIsolation !== null; + } + + /** + * @throws Exception + */ + public function processIsolation(): bool + { + if ($this->processIsolation === null) { + throw new Exception; + } + + return $this->processIsolation; + } + + public function hasRandomOrderSeed(): bool + { + return $this->randomOrderSeed !== null; + } + + /** + * @throws Exception + */ + public function randomOrderSeed(): int + { + if ($this->randomOrderSeed === null) { + throw new Exception; + } + + return $this->randomOrderSeed; + } + + public function hasRepeat(): bool + { + return $this->repeat !== null; + } + + /** + * @throws Exception + */ + public function repeat(): int + { + if ($this->repeat === null) { + throw new Exception; + } + + return $this->repeat; + } + + public function hasReportUselessTests(): bool + { + return $this->reportUselessTests !== null; + } + + /** + * @throws Exception + */ + public function reportUselessTests(): bool + { + if ($this->reportUselessTests === null) { + throw new Exception; + } + + return $this->reportUselessTests; + } + + public function hasResolveDependencies(): bool + { + return $this->resolveDependencies !== null; + } + + /** + * @throws Exception + */ + public function resolveDependencies(): bool + { + if ($this->resolveDependencies === null) { + throw new Exception; + } + + return $this->resolveDependencies; + } + + public function hasReverseList(): bool + { + return $this->reverseList !== null; + } + + /** + * @throws Exception + */ + public function reverseList(): bool + { + if ($this->reverseList === null) { + throw new Exception; + } + + return $this->reverseList; + } + + public function hasStderr(): bool + { + return $this->stderr !== null; + } + + /** + * @throws Exception + */ + public function stderr(): bool + { + if ($this->stderr === null) { + throw new Exception; + } + + return $this->stderr; + } + + public function hasStrictCoverage(): bool + { + return $this->strictCoverage !== null; + } + + /** + * @throws Exception + */ + public function strictCoverage(): bool + { + if ($this->strictCoverage === null) { + throw new Exception; + } + + return $this->strictCoverage; + } + + public function hasStopOnDefect(): bool + { + return $this->stopOnDefect !== null; + } + + /** + * @throws Exception + */ + public function stopOnDefect(): bool + { + if ($this->stopOnDefect === null) { + throw new Exception; + } + + return $this->stopOnDefect; + } + + public function hasStopOnError(): bool + { + return $this->stopOnError !== null; + } + + /** + * @throws Exception + */ + public function stopOnError(): bool + { + if ($this->stopOnError === null) { + throw new Exception; + } + + return $this->stopOnError; + } + + public function hasStopOnFailure(): bool + { + return $this->stopOnFailure !== null; + } + + /** + * @throws Exception + */ + public function stopOnFailure(): bool + { + if ($this->stopOnFailure === null) { + throw new Exception; + } + + return $this->stopOnFailure; + } + + public function hasStopOnIncomplete(): bool + { + return $this->stopOnIncomplete !== null; + } + + /** + * @throws Exception + */ + public function stopOnIncomplete(): bool + { + if ($this->stopOnIncomplete === null) { + throw new Exception; + } + + return $this->stopOnIncomplete; + } + + public function hasStopOnRisky(): bool + { + return $this->stopOnRisky !== null; + } + + /** + * @throws Exception + */ + public function stopOnRisky(): bool + { + if ($this->stopOnRisky === null) { + throw new Exception; + } + + return $this->stopOnRisky; + } + + public function hasStopOnSkipped(): bool + { + return $this->stopOnSkipped !== null; + } + + /** + * @throws Exception + */ + public function stopOnSkipped(): bool + { + if ($this->stopOnSkipped === null) { + throw new Exception; + } + + return $this->stopOnSkipped; + } + + public function hasStopOnWarning(): bool + { + return $this->stopOnWarning !== null; + } + + /** + * @throws Exception + */ + public function stopOnWarning(): bool + { + if ($this->stopOnWarning === null) { + throw new Exception; + } + + return $this->stopOnWarning; + } + + public function hasTeamcityLogfile(): bool + { + return $this->teamcityLogfile !== null; + } + + /** + * @throws Exception + */ + public function teamcityLogfile(): string + { + if ($this->teamcityLogfile === null) { + throw new Exception; + } + + return $this->teamcityLogfile; + } + + public function hasTestdoxExcludeGroups(): bool + { + return $this->testdoxExcludeGroups !== null; + } + + /** + * @throws Exception + */ + public function testdoxExcludeGroups(): array + { + if ($this->testdoxExcludeGroups === null) { + throw new Exception; + } + + return $this->testdoxExcludeGroups; + } + + public function hasTestdoxGroups(): bool + { + return $this->testdoxGroups !== null; + } + + /** + * @throws Exception + */ + public function testdoxGroups(): array + { + if ($this->testdoxGroups === null) { + throw new Exception; + } + + return $this->testdoxGroups; + } + + public function hasTestdoxHtmlFile(): bool + { + return $this->testdoxHtmlFile !== null; + } + + /** + * @throws Exception + */ + public function testdoxHtmlFile(): string + { + if ($this->testdoxHtmlFile === null) { + throw new Exception; + } + + return $this->testdoxHtmlFile; + } + + public function hasTestdoxTextFile(): bool + { + return $this->testdoxTextFile !== null; + } + + /** + * @throws Exception + */ + public function testdoxTextFile(): string + { + if ($this->testdoxTextFile === null) { + throw new Exception; + } + + return $this->testdoxTextFile; + } + + public function hasTestdoxXmlFile(): bool + { + return $this->testdoxXmlFile !== null; + } + + /** + * @throws Exception + */ + public function testdoxXmlFile(): string + { + if ($this->testdoxXmlFile === null) { + throw new Exception; + } + + return $this->testdoxXmlFile; + } + + public function hasTestSuffixes(): bool + { + return $this->testSuffixes !== null; + } + + /** + * @throws Exception + */ + public function testSuffixes(): array + { + if ($this->testSuffixes === null) { + throw new Exception; + } + + return $this->testSuffixes; + } + + public function hasTestSuite(): bool + { + return $this->testSuite !== null; + } + + /** + * @throws Exception + */ + public function testSuite(): string + { + if ($this->testSuite === null) { + throw new Exception; + } + + return $this->testSuite; + } + + public function unrecognizedOptions(): array + { + return $this->unrecognizedOptions; + } + + public function hasUnrecognizedOrderBy(): bool + { + return $this->unrecognizedOrderBy !== null; + } + + /** + * @throws Exception + */ + public function unrecognizedOrderBy(): string + { + if ($this->unrecognizedOrderBy === null) { + throw new Exception; + } + + return $this->unrecognizedOrderBy; + } + + public function hasUseDefaultConfiguration(): bool + { + return $this->useDefaultConfiguration !== null; + } + + /** + * @throws Exception + */ + public function useDefaultConfiguration(): bool + { + if ($this->useDefaultConfiguration === null) { + throw new Exception; + } + + return $this->useDefaultConfiguration; + } + + public function hasVerbose(): bool + { + return $this->verbose !== null; + } + + /** + * @throws Exception + */ + public function verbose(): bool + { + if ($this->verbose === null) { + throw new Exception; + } + + return $this->verbose; + } + + public function hasVersion(): bool + { + return $this->version !== null; + } + + /** + * @throws Exception + */ + public function version(): bool + { + if ($this->version === null) { + throw new Exception; + } + + return $this->version; + } + + public function hasXdebugFilterFile(): bool + { + return $this->xdebugFilterFile !== null; + } + + /** + * @throws Exception + */ + public function xdebugFilterFile(): string + { + if ($this->xdebugFilterFile === null) { + throw new Exception; + } + + return $this->xdebugFilterFile; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/CliArguments/Exception.php b/vendor/phpunit/phpunit/src/TextUI/CliArguments/Exception.php new file mode 100644 index 00000000..dd5536ea --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/CliArguments/Exception.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\CliArguments; + +use RuntimeException; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Exception extends RuntimeException implements \PHPUnit\Exception +{ +} diff --git a/vendor/phpunit/phpunit/src/TextUI/CliArguments/Mapper.php b/vendor/phpunit/phpunit/src/TextUI/CliArguments/Mapper.php new file mode 100644 index 00000000..9ceb8ab4 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/CliArguments/Mapper.php @@ -0,0 +1,365 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\CliArguments; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Mapper +{ + /** + * @throws Exception + */ + public function mapToLegacyArray(Configuration $arguments): array + { + $result = [ + 'extensions' => [], + 'listGroups' => false, + 'listSuites' => false, + 'listTests' => false, + 'listTestsXml' => false, + 'loader' => null, + 'useDefaultConfiguration' => true, + 'loadedExtensions' => [], + 'unavailableExtensions' => [], + 'notLoadedExtensions' => [], + ]; + + if ($arguments->hasColors()) { + $result['colors'] = $arguments->colors(); + } + + if ($arguments->hasBootstrap()) { + $result['bootstrap'] = $arguments->bootstrap(); + } + + if ($arguments->hasCacheResult()) { + $result['cacheResult'] = $arguments->cacheResult(); + } + + if ($arguments->hasCacheResultFile()) { + $result['cacheResultFile'] = $arguments->cacheResultFile(); + } + + if ($arguments->hasColumns()) { + $result['columns'] = $arguments->columns(); + } + + if ($arguments->hasConfiguration()) { + $result['configuration'] = $arguments->configuration(); + } + + if ($arguments->hasCoverageCacheDirectory()) { + $result['coverageCacheDirectory'] = $arguments->coverageCacheDirectory(); + } + + if ($arguments->hasWarmCoverageCache()) { + $result['warmCoverageCache'] = $arguments->warmCoverageCache(); + } + + if ($arguments->hasCoverageClover()) { + $result['coverageClover'] = $arguments->coverageClover(); + } + + if ($arguments->hasCoverageCobertura()) { + $result['coverageCobertura'] = $arguments->coverageCobertura(); + } + + if ($arguments->hasCoverageCrap4J()) { + $result['coverageCrap4J'] = $arguments->coverageCrap4J(); + } + + if ($arguments->hasCoverageHtml()) { + $result['coverageHtml'] = $arguments->coverageHtml(); + } + + if ($arguments->hasCoveragePhp()) { + $result['coveragePHP'] = $arguments->coveragePhp(); + } + + if ($arguments->hasCoverageText()) { + $result['coverageText'] = $arguments->coverageText(); + } + + if ($arguments->hasCoverageTextShowUncoveredFiles()) { + $result['coverageTextShowUncoveredFiles'] = $arguments->hasCoverageTextShowUncoveredFiles(); + } + + if ($arguments->hasCoverageTextShowOnlySummary()) { + $result['coverageTextShowOnlySummary'] = $arguments->coverageTextShowOnlySummary(); + } + + if ($arguments->hasCoverageXml()) { + $result['coverageXml'] = $arguments->coverageXml(); + } + + if ($arguments->hasPathCoverage()) { + $result['pathCoverage'] = $arguments->pathCoverage(); + } + + if ($arguments->hasDebug()) { + $result['debug'] = $arguments->debug(); + } + + if ($arguments->hasHelp()) { + $result['help'] = $arguments->help(); + } + + if ($arguments->hasFilter()) { + $result['filter'] = $arguments->filter(); + } + + if ($arguments->hasTestSuite()) { + $result['testsuite'] = $arguments->testSuite(); + } + + if ($arguments->hasGroups()) { + $result['groups'] = $arguments->groups(); + } + + if ($arguments->hasExcludeGroups()) { + $result['excludeGroups'] = $arguments->excludeGroups(); + } + + if ($arguments->hasTestsCovering()) { + $result['testsCovering'] = $arguments->testsCovering(); + } + + if ($arguments->hasTestsUsing()) { + $result['testsUsing'] = $arguments->testsUsing(); + } + + if ($arguments->hasTestSuffixes()) { + $result['testSuffixes'] = $arguments->testSuffixes(); + } + + if ($arguments->hasIncludePath()) { + $result['includePath'] = $arguments->includePath(); + } + + if ($arguments->hasListGroups()) { + $result['listGroups'] = $arguments->listGroups(); + } + + if ($arguments->hasListSuites()) { + $result['listSuites'] = $arguments->listSuites(); + } + + if ($arguments->hasListTests()) { + $result['listTests'] = $arguments->listTests(); + } + + if ($arguments->hasListTestsXml()) { + $result['listTestsXml'] = $arguments->listTestsXml(); + } + + if ($arguments->hasPrinter()) { + $result['printer'] = $arguments->printer(); + } + + if ($arguments->hasLoader()) { + $result['loader'] = $arguments->loader(); + } + + if ($arguments->hasJunitLogfile()) { + $result['junitLogfile'] = $arguments->junitLogfile(); + } + + if ($arguments->hasTeamcityLogfile()) { + $result['teamcityLogfile'] = $arguments->teamcityLogfile(); + } + + if ($arguments->hasExecutionOrder()) { + $result['executionOrder'] = $arguments->executionOrder(); + } + + if ($arguments->hasExecutionOrderDefects()) { + $result['executionOrderDefects'] = $arguments->executionOrderDefects(); + } + + if ($arguments->hasExtensions()) { + $result['extensions'] = $arguments->extensions(); + } + + if ($arguments->hasUnavailableExtensions()) { + $result['unavailableExtensions'] = $arguments->unavailableExtensions(); + } + + if ($arguments->hasResolveDependencies()) { + $result['resolveDependencies'] = $arguments->resolveDependencies(); + } + + if ($arguments->hasProcessIsolation()) { + $result['processIsolation'] = $arguments->processIsolation(); + } + + if ($arguments->hasRepeat()) { + $result['repeat'] = $arguments->repeat(); + } + + if ($arguments->hasStderr()) { + $result['stderr'] = $arguments->stderr(); + } + + if ($arguments->hasStopOnDefect()) { + $result['stopOnDefect'] = $arguments->stopOnDefect(); + } + + if ($arguments->hasStopOnError()) { + $result['stopOnError'] = $arguments->stopOnError(); + } + + if ($arguments->hasStopOnFailure()) { + $result['stopOnFailure'] = $arguments->stopOnFailure(); + } + + if ($arguments->hasStopOnWarning()) { + $result['stopOnWarning'] = $arguments->stopOnWarning(); + } + + if ($arguments->hasStopOnIncomplete()) { + $result['stopOnIncomplete'] = $arguments->stopOnIncomplete(); + } + + if ($arguments->hasStopOnRisky()) { + $result['stopOnRisky'] = $arguments->stopOnRisky(); + } + + if ($arguments->hasStopOnSkipped()) { + $result['stopOnSkipped'] = $arguments->stopOnSkipped(); + } + + if ($arguments->hasFailOnEmptyTestSuite()) { + $result['failOnEmptyTestSuite'] = $arguments->failOnEmptyTestSuite(); + } + + if ($arguments->hasFailOnIncomplete()) { + $result['failOnIncomplete'] = $arguments->failOnIncomplete(); + } + + if ($arguments->hasFailOnRisky()) { + $result['failOnRisky'] = $arguments->failOnRisky(); + } + + if ($arguments->hasFailOnSkipped()) { + $result['failOnSkipped'] = $arguments->failOnSkipped(); + } + + if ($arguments->hasFailOnWarning()) { + $result['failOnWarning'] = $arguments->failOnWarning(); + } + + if ($arguments->hasTestdoxGroups()) { + $result['testdoxGroups'] = $arguments->testdoxGroups(); + } + + if ($arguments->hasTestdoxExcludeGroups()) { + $result['testdoxExcludeGroups'] = $arguments->testdoxExcludeGroups(); + } + + if ($arguments->hasTestdoxHtmlFile()) { + $result['testdoxHTMLFile'] = $arguments->testdoxHtmlFile(); + } + + if ($arguments->hasTestdoxTextFile()) { + $result['testdoxTextFile'] = $arguments->testdoxTextFile(); + } + + if ($arguments->hasTestdoxXmlFile()) { + $result['testdoxXMLFile'] = $arguments->testdoxXmlFile(); + } + + if ($arguments->hasUseDefaultConfiguration()) { + $result['useDefaultConfiguration'] = $arguments->useDefaultConfiguration(); + } + + if ($arguments->hasNoExtensions()) { + $result['noExtensions'] = $arguments->noExtensions(); + } + + if ($arguments->hasNoCoverage()) { + $result['noCoverage'] = $arguments->noCoverage(); + } + + if ($arguments->hasNoLogging()) { + $result['noLogging'] = $arguments->noLogging(); + } + + if ($arguments->hasNoInteraction()) { + $result['noInteraction'] = $arguments->noInteraction(); + } + + if ($arguments->hasBackupGlobals()) { + $result['backupGlobals'] = $arguments->backupGlobals(); + } + + if ($arguments->hasBackupStaticAttributes()) { + $result['backupStaticAttributes'] = $arguments->backupStaticAttributes(); + } + + if ($arguments->hasVerbose()) { + $result['verbose'] = $arguments->verbose(); + } + + if ($arguments->hasReportUselessTests()) { + $result['reportUselessTests'] = $arguments->reportUselessTests(); + } + + if ($arguments->hasStrictCoverage()) { + $result['strictCoverage'] = $arguments->strictCoverage(); + } + + if ($arguments->hasDisableCodeCoverageIgnore()) { + $result['disableCodeCoverageIgnore'] = $arguments->disableCodeCoverageIgnore(); + } + + if ($arguments->hasBeStrictAboutChangesToGlobalState()) { + $result['beStrictAboutChangesToGlobalState'] = $arguments->beStrictAboutChangesToGlobalState(); + } + + if ($arguments->hasDisallowTestOutput()) { + $result['disallowTestOutput'] = $arguments->disallowTestOutput(); + } + + if ($arguments->hasBeStrictAboutResourceUsageDuringSmallTests()) { + $result['beStrictAboutResourceUsageDuringSmallTests'] = $arguments->beStrictAboutResourceUsageDuringSmallTests(); + } + + if ($arguments->hasDefaultTimeLimit()) { + $result['defaultTimeLimit'] = $arguments->defaultTimeLimit(); + } + + if ($arguments->hasEnforceTimeLimit()) { + $result['enforceTimeLimit'] = $arguments->enforceTimeLimit(); + } + + if ($arguments->hasDisallowTodoAnnotatedTests()) { + $result['disallowTodoAnnotatedTests'] = $arguments->disallowTodoAnnotatedTests(); + } + + if ($arguments->hasReverseList()) { + $result['reverseList'] = $arguments->reverseList(); + } + + if ($arguments->hasCoverageFilter()) { + $result['coverageFilter'] = $arguments->coverageFilter(); + } + + if ($arguments->hasRandomOrderSeed()) { + $result['randomOrderSeed'] = $arguments->randomOrderSeed(); + } + + if ($arguments->hasXdebugFilterFile()) { + $result['xdebugFilterFile'] = $arguments->xdebugFilterFile(); + } + + return $result; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/Command.php b/vendor/phpunit/phpunit/src/TextUI/Command.php new file mode 100644 index 00000000..b656b0fc --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/Command.php @@ -0,0 +1,1041 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI; + +use const PATH_SEPARATOR; +use const PHP_EOL; +use const STDIN; +use function array_keys; +use function assert; +use function class_exists; +use function copy; +use function explode; +use function extension_loaded; +use function fgets; +use function file_get_contents; +use function file_put_contents; +use function get_class; +use function getcwd; +use function ini_get; +use function ini_set; +use function is_array; +use function is_callable; +use function is_dir; +use function is_file; +use function is_string; +use function printf; +use function realpath; +use function sort; +use function sprintf; +use function stream_resolve_include_path; +use function strpos; +use function trim; +use function version_compare; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Runner\Extension\PharLoader; +use PHPUnit\Runner\StandardTestSuiteLoader; +use PHPUnit\Runner\TestSuiteLoader; +use PHPUnit\Runner\Version; +use PHPUnit\TextUI\CliArguments\Builder; +use PHPUnit\TextUI\CliArguments\Configuration; +use PHPUnit\TextUI\CliArguments\Exception as ArgumentsException; +use PHPUnit\TextUI\CliArguments\Mapper; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\FilterMapper; +use PHPUnit\TextUI\XmlConfiguration\Generator; +use PHPUnit\TextUI\XmlConfiguration\Loader; +use PHPUnit\TextUI\XmlConfiguration\Migrator; +use PHPUnit\TextUI\XmlConfiguration\PhpHandler; +use PHPUnit\Util\FileLoader; +use PHPUnit\Util\Filesystem; +use PHPUnit\Util\Printer; +use PHPUnit\Util\TextTestListRenderer; +use PHPUnit\Util\Xml\SchemaDetector; +use PHPUnit\Util\XmlTestListRenderer; +use ReflectionClass; +use SebastianBergmann\CodeCoverage\Filter; +use SebastianBergmann\CodeCoverage\StaticAnalysis\CacheWarmer; +use SebastianBergmann\Timer\Timer; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +class Command +{ + /** + * @var array + */ + protected $arguments = []; + + /** + * @var array + */ + protected $longOptions = []; + + /** + * @var bool + */ + private $versionStringPrinted = false; + + /** + * @psalm-var list + */ + private $warnings = []; + + /** + * @throws Exception + */ + public static function main(bool $exit = true): int + { + try { + return (new static)->run($_SERVER['argv'], $exit); + } catch (Throwable $t) { + throw new RuntimeException( + $t->getMessage(), + (int) $t->getCode(), + $t, + ); + } + } + + /** + * @throws Exception + */ + public function run(array $argv, bool $exit = true): int + { + $this->handleArguments($argv); + + $runner = $this->createRunner(); + + if ($this->arguments['test'] instanceof TestSuite) { + $suite = $this->arguments['test']; + } else { + $suite = $runner->getTest( + $this->arguments['test'], + $this->arguments['testSuffixes'], + ); + } + + if ($this->arguments['listGroups']) { + return $this->handleListGroups($suite, $exit); + } + + if ($this->arguments['listSuites']) { + return $this->handleListSuites($exit); + } + + if ($this->arguments['listTests']) { + return $this->handleListTests($suite, $exit); + } + + if ($this->arguments['listTestsXml']) { + return $this->handleListTestsXml($suite, $this->arguments['listTestsXml'], $exit); + } + + unset($this->arguments['test'], $this->arguments['testFile']); + + try { + $result = $runner->run($suite, $this->arguments, $this->warnings, $exit); + } catch (Throwable $t) { + print $t->getMessage() . PHP_EOL; + } + + $return = TestRunner::FAILURE_EXIT; + + if (isset($result) && $result->wasSuccessful()) { + $return = TestRunner::SUCCESS_EXIT; + } elseif (!isset($result) || $result->errorCount() > 0) { + $return = TestRunner::EXCEPTION_EXIT; + } + + if ($exit) { + exit($return); + } + + return $return; + } + + /** + * Create a TestRunner, override in subclasses. + */ + protected function createRunner(): TestRunner + { + return new TestRunner($this->arguments['loader']); + } + + /** + * Handles the command-line arguments. + * + * A child class of PHPUnit\TextUI\Command can hook into the argument + * parsing by adding the switch(es) to the $longOptions array and point to a + * callback method that handles the switch(es) in the child class like this + * + * + * longOptions['my-switch'] = 'myHandler'; + * // my-secondswitch will accept a value - note the equals sign + * $this->longOptions['my-secondswitch='] = 'myOtherHandler'; + * } + * + * // --my-switch -> myHandler() + * protected function myHandler() + * { + * } + * + * // --my-secondswitch foo -> myOtherHandler('foo') + * protected function myOtherHandler ($value) + * { + * } + * + * // You will also need this - the static keyword in the + * // PHPUnit\TextUI\Command will mean that it'll be + * // PHPUnit\TextUI\Command that gets instantiated, + * // not MyCommand + * public static function main($exit = true) + * { + * $command = new static; + * + * return $command->run($_SERVER['argv'], $exit); + * } + * + * } + * + * + * @throws Exception + */ + protected function handleArguments(array $argv): void + { + try { + $arguments = (new Builder)->fromParameters($argv, array_keys($this->longOptions)); + } catch (ArgumentsException $e) { + $this->exitWithErrorMessage($e->getMessage()); + } + + assert(isset($arguments) && $arguments instanceof Configuration); + + if ($arguments->hasGenerateConfiguration() && $arguments->generateConfiguration()) { + $this->generateConfiguration(); + } + + if ($arguments->hasAtLeastVersion()) { + if (version_compare(Version::id(), $arguments->atLeastVersion(), '>=')) { + exit(TestRunner::SUCCESS_EXIT); + } + + exit(TestRunner::FAILURE_EXIT); + } + + if ($arguments->hasVersion() && $arguments->version()) { + $this->printVersionString(); + + exit(TestRunner::SUCCESS_EXIT); + } + + if ($arguments->hasCheckVersion() && $arguments->checkVersion()) { + $this->handleVersionCheck(); + } + + if ($arguments->hasHelp()) { + $this->showHelp(); + + exit(TestRunner::SUCCESS_EXIT); + } + + if ($arguments->hasUnrecognizedOrderBy()) { + $this->exitWithErrorMessage( + sprintf( + 'unrecognized --order-by option: %s', + $arguments->unrecognizedOrderBy(), + ), + ); + } + + if ($arguments->hasIniSettings()) { + foreach ($arguments->iniSettings() as $name => $value) { + ini_set($name, $value); + } + } + + if ($arguments->hasIncludePath()) { + ini_set( + 'include_path', + $arguments->includePath() . PATH_SEPARATOR . ini_get('include_path'), + ); + } + + $this->arguments = (new Mapper)->mapToLegacyArray($arguments); + + $this->handleCustomOptions($arguments->unrecognizedOptions()); + $this->handleCustomTestSuite(); + + if (!isset($this->arguments['testSuffixes'])) { + $this->arguments['testSuffixes'] = ['Test.php', '.phpt']; + } + + if (!isset($this->arguments['test']) && $arguments->hasArgument()) { + $this->arguments['test'] = realpath($arguments->argument()); + + if ($this->arguments['test'] === false) { + $this->exitWithErrorMessage( + sprintf( + 'Cannot open file "%s".', + $arguments->argument(), + ), + ); + } + } + + if ($this->arguments['loader'] !== null) { + $this->arguments['loader'] = $this->handleLoader($this->arguments['loader']); + } + + if (isset($this->arguments['configuration'])) { + if (is_dir($this->arguments['configuration'])) { + $candidate = $this->configurationFileInDirectory($this->arguments['configuration']); + + if ($candidate !== null) { + $this->arguments['configuration'] = $candidate; + } + } + } elseif ($this->arguments['useDefaultConfiguration']) { + $candidate = $this->configurationFileInDirectory(getcwd()); + + if ($candidate !== null) { + $this->arguments['configuration'] = $candidate; + } + } + + if ($arguments->hasMigrateConfiguration() && $arguments->migrateConfiguration()) { + if (!isset($this->arguments['configuration'])) { + print 'No configuration file found to migrate.' . PHP_EOL; + + exit(TestRunner::EXCEPTION_EXIT); + } + + $this->migrateConfiguration(realpath($this->arguments['configuration'])); + } + + if (isset($this->arguments['configuration'])) { + try { + $this->arguments['configurationObject'] = (new Loader)->load($this->arguments['configuration']); + } catch (Throwable $e) { + print $e->getMessage() . PHP_EOL; + + exit(TestRunner::FAILURE_EXIT); + } + + $phpunitConfiguration = $this->arguments['configurationObject']->phpunit(); + + (new PhpHandler)->handle($this->arguments['configurationObject']->php()); + + if (isset($this->arguments['bootstrap'])) { + $this->handleBootstrap($this->arguments['bootstrap']); + } elseif ($phpunitConfiguration->hasBootstrap()) { + $this->handleBootstrap($phpunitConfiguration->bootstrap()); + } + + if (!isset($this->arguments['stderr'])) { + $this->arguments['stderr'] = $phpunitConfiguration->stderr(); + } + + if (!isset($this->arguments['noExtensions']) && $phpunitConfiguration->hasExtensionsDirectory() && extension_loaded('phar')) { + $result = (new PharLoader)->loadPharExtensionsInDirectory($phpunitConfiguration->extensionsDirectory()); + + $this->arguments['loadedExtensions'] = $result['loadedExtensions']; + $this->arguments['notLoadedExtensions'] = $result['notLoadedExtensions']; + + unset($result); + } + + if (!isset($this->arguments['columns'])) { + $this->arguments['columns'] = $phpunitConfiguration->columns(); + } + + if (!isset($this->arguments['printer']) && $phpunitConfiguration->hasPrinterClass()) { + $file = $phpunitConfiguration->hasPrinterFile() ? $phpunitConfiguration->printerFile() : ''; + + $this->arguments['printer'] = $this->handlePrinter( + $phpunitConfiguration->printerClass(), + $file, + ); + } + + if ($phpunitConfiguration->hasTestSuiteLoaderClass()) { + $file = $phpunitConfiguration->hasTestSuiteLoaderFile() ? $phpunitConfiguration->testSuiteLoaderFile() : ''; + + $this->arguments['loader'] = $this->handleLoader( + $phpunitConfiguration->testSuiteLoaderClass(), + $file, + ); + } + + if (!isset($this->arguments['testsuite']) && $phpunitConfiguration->hasDefaultTestSuite()) { + $this->arguments['testsuite'] = $phpunitConfiguration->defaultTestSuite(); + } + + if (!isset($this->arguments['test'])) { + try { + $this->arguments['test'] = (new TestSuiteMapper)->map( + $this->arguments['configurationObject']->testSuite(), + $this->arguments['testsuite'] ?? '', + ); + } catch (Exception $e) { + $this->printVersionString(); + + print $e->getMessage() . PHP_EOL; + + exit(TestRunner::EXCEPTION_EXIT); + } + } + } elseif (isset($this->arguments['bootstrap'])) { + $this->handleBootstrap($this->arguments['bootstrap']); + } + + if (isset($this->arguments['printer']) && is_string($this->arguments['printer'])) { + $this->arguments['printer'] = $this->handlePrinter($this->arguments['printer']); + } + + if (isset($this->arguments['configurationObject'], $this->arguments['warmCoverageCache'])) { + $this->handleWarmCoverageCache($this->arguments['configurationObject']); + } + + if (!isset($this->arguments['test'])) { + $this->showHelp(); + + exit(TestRunner::EXCEPTION_EXIT); + } + } + + /** + * Handles the loading of the PHPUnit\Runner\TestSuiteLoader implementation. + * + * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 + */ + protected function handleLoader(string $loaderClass, string $loaderFile = ''): ?TestSuiteLoader + { + $this->warnings[] = 'Using a custom test suite loader is deprecated'; + + if (!class_exists($loaderClass, false)) { + if ($loaderFile == '') { + $loaderFile = Filesystem::classNameToFilename( + $loaderClass, + ); + } + + $loaderFile = stream_resolve_include_path($loaderFile); + + if ($loaderFile) { + /** + * @noinspection PhpIncludeInspection + * + * @psalm-suppress UnresolvableInclude + */ + require $loaderFile; + } + } + + if (class_exists($loaderClass, false)) { + try { + $class = new ReflectionClass($loaderClass); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + if ($class->implementsInterface(TestSuiteLoader::class) && $class->isInstantiable()) { + $object = $class->newInstance(); + + assert($object instanceof TestSuiteLoader); + + return $object; + } + } + + if ($loaderClass == StandardTestSuiteLoader::class) { + return null; + } + + $this->exitWithErrorMessage( + sprintf( + 'Could not use "%s" as loader.', + $loaderClass, + ), + ); + + return null; + } + + /** + * Handles the loading of the PHPUnit\Util\Printer implementation. + * + * @return null|Printer|string + */ + protected function handlePrinter(string $printerClass, string $printerFile = '') + { + if (!class_exists($printerClass, false)) { + if ($printerFile === '') { + $printerFile = Filesystem::classNameToFilename( + $printerClass, + ); + } + + $printerFile = stream_resolve_include_path($printerFile); + + if ($printerFile) { + /** + * @noinspection PhpIncludeInspection + * + * @psalm-suppress UnresolvableInclude + */ + require $printerFile; + } + } + + if (!class_exists($printerClass)) { + $this->exitWithErrorMessage( + sprintf( + 'Could not use "%s" as printer: class does not exist', + $printerClass, + ), + ); + } + + try { + $class = new ReflectionClass($printerClass); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + $e->getCode(), + $e, + ); + // @codeCoverageIgnoreEnd + } + + if (!$class->implementsInterface(ResultPrinter::class)) { + $this->exitWithErrorMessage( + sprintf( + 'Could not use "%s" as printer: class does not implement %s', + $printerClass, + ResultPrinter::class, + ), + ); + } + + if (!$class->isInstantiable()) { + $this->exitWithErrorMessage( + sprintf( + 'Could not use "%s" as printer: class cannot be instantiated', + $printerClass, + ), + ); + } + + if ($class->isSubclassOf(ResultPrinter::class)) { + return $printerClass; + } + + $outputStream = isset($this->arguments['stderr']) ? 'php://stderr' : null; + + return $class->newInstance($outputStream); + } + + /** + * Loads a bootstrap file. + */ + protected function handleBootstrap(string $filename): void + { + try { + FileLoader::checkAndLoad($filename); + } catch (Throwable $t) { + if ($t instanceof \PHPUnit\Exception) { + $this->exitWithErrorMessage($t->getMessage()); + } + + $message = sprintf( + 'Error in bootstrap script: %s:%s%s%s%s', + get_class($t), + PHP_EOL, + $t->getMessage(), + PHP_EOL, + $t->getTraceAsString(), + ); + + while ($t = $t->getPrevious()) { + $message .= sprintf( + '%s%sPrevious error: %s:%s%s%s%s', + PHP_EOL, + PHP_EOL, + get_class($t), + PHP_EOL, + $t->getMessage(), + PHP_EOL, + $t->getTraceAsString(), + ); + } + + $this->exitWithErrorMessage($message); + } + } + + protected function handleVersionCheck(): void + { + $this->printVersionString(); + + $latestVersion = file_get_contents('https://phar.phpunit.de/latest-version-of/phpunit'); + $latestCompatibleVersion = file_get_contents('https://phar.phpunit.de/latest-version-of/phpunit-' . explode('.', Version::series())[0]); + + $notLatest = version_compare($latestVersion, Version::id(), '>'); + $notLatestCompatible = version_compare($latestCompatibleVersion, Version::id(), '>'); + + if ($notLatest || $notLatestCompatible) { + print 'You are not using the latest version of PHPUnit.' . PHP_EOL; + } else { + print 'You are using the latest version of PHPUnit.' . PHP_EOL; + } + + if ($notLatestCompatible) { + printf( + 'The latest version compatible with PHPUnit %s is PHPUnit %s.' . PHP_EOL, + Version::id(), + $latestCompatibleVersion, + ); + } + + if ($notLatest) { + printf( + 'The latest version is PHPUnit %s.' . PHP_EOL, + $latestVersion, + ); + } + + exit(TestRunner::SUCCESS_EXIT); + } + + /** + * Show the help message. + */ + protected function showHelp(): void + { + $this->printVersionString(); + (new Help)->writeToConsole(); + } + + /** + * Custom callback for test suite discovery. + */ + protected function handleCustomTestSuite(): void + { + } + + private function printVersionString(): void + { + if ($this->versionStringPrinted) { + return; + } + + print Version::getVersionString() . PHP_EOL . PHP_EOL; + + $this->versionStringPrinted = true; + } + + private function exitWithErrorMessage(string $message): void + { + $this->printVersionString(); + + print $message . PHP_EOL; + + exit(TestRunner::FAILURE_EXIT); + } + + private function handleListGroups(TestSuite $suite, bool $exit): int + { + $this->printVersionString(); + + $this->warnAboutConflictingOptions( + 'listGroups', + [ + 'filter', + 'groups', + 'excludeGroups', + 'testsuite', + ], + ); + + print 'Available test group(s):' . PHP_EOL; + + $groups = $suite->getGroups(); + sort($groups); + + foreach ($groups as $group) { + if (strpos($group, '__phpunit_') === 0) { + continue; + } + + printf( + ' - %s' . PHP_EOL, + $group, + ); + } + + if ($exit) { + exit(TestRunner::SUCCESS_EXIT); + } + + return TestRunner::SUCCESS_EXIT; + } + + /** + * @throws \PHPUnit\Framework\Exception + * @throws XmlConfiguration\Exception + */ + private function handleListSuites(bool $exit): int + { + $this->printVersionString(); + + $this->warnAboutConflictingOptions( + 'listSuites', + [ + 'filter', + 'groups', + 'excludeGroups', + 'testsuite', + ], + ); + + print 'Available test suite(s):' . PHP_EOL; + + foreach ($this->arguments['configurationObject']->testSuite() as $testSuite) { + printf( + ' - %s' . PHP_EOL, + $testSuite->name(), + ); + } + + if ($exit) { + exit(TestRunner::SUCCESS_EXIT); + } + + return TestRunner::SUCCESS_EXIT; + } + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + private function handleListTests(TestSuite $suite, bool $exit): int + { + $this->printVersionString(); + + $this->warnAboutConflictingOptions( + 'listTests', + [ + 'filter', + 'groups', + 'excludeGroups', + ], + ); + + $renderer = new TextTestListRenderer; + + print $renderer->render($suite); + + if ($exit) { + exit(TestRunner::SUCCESS_EXIT); + } + + return TestRunner::SUCCESS_EXIT; + } + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + private function handleListTestsXml(TestSuite $suite, string $target, bool $exit): int + { + $this->printVersionString(); + + $this->warnAboutConflictingOptions( + 'listTestsXml', + [ + 'filter', + 'groups', + 'excludeGroups', + ], + ); + + $renderer = new XmlTestListRenderer; + + file_put_contents($target, $renderer->render($suite)); + + printf( + 'Wrote list of tests that would have been run to %s' . PHP_EOL, + $target, + ); + + if ($exit) { + exit(TestRunner::SUCCESS_EXIT); + } + + return TestRunner::SUCCESS_EXIT; + } + + private function generateConfiguration(): void + { + $this->printVersionString(); + + print 'Generating phpunit.xml in ' . getcwd() . PHP_EOL . PHP_EOL; + print 'Bootstrap script (relative to path shown above; default: vendor/autoload.php): '; + + $bootstrapScript = trim(fgets(STDIN)); + + print 'Tests directory (relative to path shown above; default: tests): '; + + $testsDirectory = trim(fgets(STDIN)); + + print 'Source directory (relative to path shown above; default: src): '; + + $src = trim(fgets(STDIN)); + + print 'Cache directory (relative to path shown above; default: .phpunit.cache): '; + + $cacheDirectory = trim(fgets(STDIN)); + + if ($bootstrapScript === '') { + $bootstrapScript = 'vendor/autoload.php'; + } + + if ($testsDirectory === '') { + $testsDirectory = 'tests'; + } + + if ($src === '') { + $src = 'src'; + } + + if ($cacheDirectory === '') { + $cacheDirectory = '.phpunit.cache'; + } + + $generator = new Generator; + + file_put_contents( + 'phpunit.xml', + $generator->generateDefaultConfiguration( + Version::series(), + $bootstrapScript, + $testsDirectory, + $src, + $cacheDirectory, + ), + ); + + print PHP_EOL . 'Generated phpunit.xml in ' . getcwd() . '.' . PHP_EOL; + print 'Make sure to exclude the ' . $cacheDirectory . ' directory from version control.' . PHP_EOL; + + exit(TestRunner::SUCCESS_EXIT); + } + + private function migrateConfiguration(string $filename): void + { + $this->printVersionString(); + + $result = (new SchemaDetector)->detect($filename); + + if (!$result->detected()) { + print $filename . ' does not validate against any known schema.' . PHP_EOL; + + exit(TestRunner::EXCEPTION_EXIT); + } + + /** @psalm-suppress MissingThrowsDocblock */ + if ($result->version() === Version::series()) { + print $filename . ' does not need to be migrated.' . PHP_EOL; + + exit(TestRunner::EXCEPTION_EXIT); + } + + copy($filename, $filename . '.bak'); + + print 'Created backup: ' . $filename . '.bak' . PHP_EOL; + + try { + file_put_contents( + $filename, + (new Migrator)->migrate($filename), + ); + + print 'Migrated configuration: ' . $filename . PHP_EOL; + } catch (Throwable $t) { + print 'Migration failed: ' . $t->getMessage() . PHP_EOL; + + exit(TestRunner::EXCEPTION_EXIT); + } + + exit(TestRunner::SUCCESS_EXIT); + } + + private function handleCustomOptions(array $unrecognizedOptions): void + { + foreach ($unrecognizedOptions as $name => $value) { + if (isset($this->longOptions[$name])) { + $handler = $this->longOptions[$name]; + } + + $name .= '='; + + if (isset($this->longOptions[$name])) { + $handler = $this->longOptions[$name]; + } + + if (isset($handler) && is_callable([$this, $handler])) { + $this->{$handler}($value); + + unset($handler); + } + } + } + + private function handleWarmCoverageCache(XmlConfiguration\Configuration $configuration): void + { + $this->printVersionString(); + + if (isset($this->arguments['coverageCacheDirectory'])) { + $cacheDirectory = $this->arguments['coverageCacheDirectory']; + } elseif ($configuration->codeCoverage()->hasCacheDirectory()) { + $cacheDirectory = $configuration->codeCoverage()->cacheDirectory()->path(); + } else { + print 'Cache for static analysis has not been configured' . PHP_EOL; + + exit(TestRunner::EXCEPTION_EXIT); + } + + $filter = new Filter; + + if ($configuration->codeCoverage()->hasNonEmptyListOfFilesToBeIncludedInCodeCoverageReport()) { + (new FilterMapper)->map( + $filter, + $configuration->codeCoverage(), + ); + } elseif (isset($this->arguments['coverageFilter'])) { + if (!is_array($this->arguments['coverageFilter'])) { + $coverageFilterDirectories = [$this->arguments['coverageFilter']]; + } else { + $coverageFilterDirectories = $this->arguments['coverageFilter']; + } + + foreach ($coverageFilterDirectories as $coverageFilterDirectory) { + $filter->includeDirectory($coverageFilterDirectory); + } + } else { + print 'Filter for code coverage has not been configured' . PHP_EOL; + + exit(TestRunner::EXCEPTION_EXIT); + } + + $timer = new Timer; + $timer->start(); + + print 'Warming cache for static analysis ... '; + + (new CacheWarmer)->warmCache( + $cacheDirectory, + !$configuration->codeCoverage()->disableCodeCoverageIgnore(), + $configuration->codeCoverage()->ignoreDeprecatedCodeUnits(), + $filter, + ); + + print 'done [' . $timer->stop()->asString() . ']' . PHP_EOL; + + exit(TestRunner::SUCCESS_EXIT); + } + + private function configurationFileInDirectory(string $directory): ?string + { + $candidates = [ + $directory . '/phpunit.xml', + $directory . '/phpunit.xml.dist', + ]; + + foreach ($candidates as $candidate) { + if (is_file($candidate)) { + return realpath($candidate); + } + } + + return null; + } + + /** + * @psalm-param "listGroups"|"listSuites"|"listTests"|"listTestsXml"|"filter"|"groups"|"excludeGroups"|"testsuite" $key + * @psalm-param list<"listGroups"|"listSuites"|"listTests"|"listTestsXml"|"filter"|"groups"|"excludeGroups"|"testsuite"> $keys + */ + private function warnAboutConflictingOptions(string $key, array $keys): void + { + $warningPrinted = false; + + foreach ($keys as $_key) { + if (!empty($this->arguments[$_key])) { + printf( + 'The %s and %s options cannot be combined, %s is ignored' . PHP_EOL, + $this->mapKeyToOptionForWarning($_key), + $this->mapKeyToOptionForWarning($key), + $this->mapKeyToOptionForWarning($_key), + ); + + $warningPrinted = true; + } + } + + if ($warningPrinted) { + print PHP_EOL; + } + } + + /** + * @psalm-param "listGroups"|"listSuites"|"listTests"|"listTestsXml"|"filter"|"groups"|"excludeGroups"|"testsuite" $key + */ + private function mapKeyToOptionForWarning(string $key): string + { + switch ($key) { + case 'listGroups': + return '--list-groups'; + + case 'listSuites': + return '--list-suites'; + + case 'listTests': + return '--list-tests'; + + case 'listTestsXml': + return '--list-tests-xml'; + + case 'filter': + return '--filter'; + + case 'groups': + return '--group'; + + case 'excludeGroups': + return '--exclude-group'; + + case 'testsuite': + return '--testsuite'; + } + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/DefaultResultPrinter.php b/vendor/phpunit/phpunit/src/TextUI/DefaultResultPrinter.php new file mode 100644 index 00000000..09de8588 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/DefaultResultPrinter.php @@ -0,0 +1,585 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI; + +use const PHP_EOL; +use function array_map; +use function array_reverse; +use function count; +use function floor; +use function implode; +use function in_array; +use function is_int; +use function max; +use function preg_split; +use function sprintf; +use function str_pad; +use function str_repeat; +use function strlen; +use function trim; +use function vsprintf; +use PHPUnit\Framework\AssertionFailedError; +use PHPUnit\Framework\Exception; +use PHPUnit\Framework\InvalidArgumentException; +use PHPUnit\Framework\Test; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\TestFailure; +use PHPUnit\Framework\TestResult; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Framework\Warning; +use PHPUnit\Runner\PhptTestCase; +use PHPUnit\Util\Color; +use PHPUnit\Util\Printer; +use SebastianBergmann\Environment\Console; +use SebastianBergmann\Timer\ResourceUsageFormatter; +use SebastianBergmann\Timer\Timer; +use Throwable; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +class DefaultResultPrinter extends Printer implements ResultPrinter +{ + public const EVENT_TEST_START = 0; + public const EVENT_TEST_END = 1; + public const EVENT_TESTSUITE_START = 2; + public const EVENT_TESTSUITE_END = 3; + public const COLOR_NEVER = 'never'; + public const COLOR_AUTO = 'auto'; + public const COLOR_ALWAYS = 'always'; + public const COLOR_DEFAULT = self::COLOR_NEVER; + private const AVAILABLE_COLORS = [self::COLOR_NEVER, self::COLOR_AUTO, self::COLOR_ALWAYS]; + + /** + * @var int + */ + protected $column = 0; + + /** + * @var int + */ + protected $maxColumn; + + /** + * @var bool + */ + protected $lastTestFailed = false; + + /** + * @var int + */ + protected $numAssertions = 0; + + /** + * @var int + */ + protected $numTests = -1; + + /** + * @var int + */ + protected $numTestsRun = 0; + + /** + * @var int + */ + protected $numTestsWidth; + + /** + * @var bool + */ + protected $colors = false; + + /** + * @var bool + */ + protected $debug = false; + + /** + * @var bool + */ + protected $verbose = false; + + /** + * @var int + */ + private $numberOfColumns; + + /** + * @var bool + */ + private $reverse; + + /** + * @var bool + */ + private $defectListPrinted = false; + + /** + * @var Timer + */ + private $timer; + + /** + * Constructor. + * + * @param null|resource|string $out + * @param int|string $numberOfColumns + * + * @throws Exception + */ + public function __construct($out = null, bool $verbose = false, string $colors = self::COLOR_DEFAULT, bool $debug = false, $numberOfColumns = 80, bool $reverse = false) + { + parent::__construct($out); + + if (!in_array($colors, self::AVAILABLE_COLORS, true)) { + throw InvalidArgumentException::create( + 3, + vsprintf('value from "%s", "%s" or "%s"', self::AVAILABLE_COLORS), + ); + } + + if (!is_int($numberOfColumns) && $numberOfColumns !== 'max') { + throw InvalidArgumentException::create(5, 'integer or "max"'); + } + + $console = new Console; + $maxNumberOfColumns = $console->getNumberOfColumns(); + + if ($numberOfColumns === 'max' || ($numberOfColumns !== 80 && $numberOfColumns > $maxNumberOfColumns)) { + $numberOfColumns = $maxNumberOfColumns; + } + + $this->numberOfColumns = $numberOfColumns; + $this->verbose = $verbose; + $this->debug = $debug; + $this->reverse = $reverse; + + if ($colors === self::COLOR_AUTO && $console->hasColorSupport()) { + $this->colors = true; + } else { + $this->colors = (self::COLOR_ALWAYS === $colors); + } + + $this->timer = new Timer; + + $this->timer->start(); + } + + public function printResult(TestResult $result): void + { + $this->printHeader($result); + $this->printErrors($result); + $this->printWarnings($result); + $this->printFailures($result); + $this->printRisky($result); + + if ($this->verbose) { + $this->printIncompletes($result); + $this->printSkipped($result); + } + + $this->printFooter($result); + } + + /** + * An error occurred. + */ + public function addError(Test $test, Throwable $t, float $time): void + { + $this->writeProgressWithColor('fg-red, bold', 'E'); + $this->lastTestFailed = true; + } + + /** + * A failure occurred. + */ + public function addFailure(Test $test, AssertionFailedError $e, float $time): void + { + $this->writeProgressWithColor('bg-red, fg-white', 'F'); + $this->lastTestFailed = true; + } + + /** + * A warning occurred. + */ + public function addWarning(Test $test, Warning $e, float $time): void + { + $this->writeProgressWithColor('fg-yellow, bold', 'W'); + $this->lastTestFailed = true; + } + + /** + * Incomplete test. + */ + public function addIncompleteTest(Test $test, Throwable $t, float $time): void + { + $this->writeProgressWithColor('fg-yellow, bold', 'I'); + $this->lastTestFailed = true; + } + + /** + * Risky test. + */ + public function addRiskyTest(Test $test, Throwable $t, float $time): void + { + $this->writeProgressWithColor('fg-yellow, bold', 'R'); + $this->lastTestFailed = true; + } + + /** + * Skipped test. + */ + public function addSkippedTest(Test $test, Throwable $t, float $time): void + { + $this->writeProgressWithColor('fg-cyan, bold', 'S'); + $this->lastTestFailed = true; + } + + /** + * A testsuite started. + */ + public function startTestSuite(TestSuite $suite): void + { + if ($this->numTests == -1) { + $this->numTests = count($suite); + $this->numTestsWidth = strlen((string) $this->numTests); + $this->maxColumn = $this->numberOfColumns - strlen(' / (XXX%)') - (2 * $this->numTestsWidth); + } + } + + /** + * A testsuite ended. + */ + public function endTestSuite(TestSuite $suite): void + { + } + + /** + * A test started. + */ + public function startTest(Test $test): void + { + if ($this->debug) { + $this->write( + sprintf( + "Test '%s' started\n", + \PHPUnit\Util\Test::describeAsString($test), + ), + ); + } + } + + /** + * A test ended. + */ + public function endTest(Test $test, float $time): void + { + if ($this->debug) { + $this->write( + sprintf( + "Test '%s' ended\n", + \PHPUnit\Util\Test::describeAsString($test), + ), + ); + } + + if (!$this->lastTestFailed) { + $this->writeProgress('.'); + } + + if ($test instanceof TestCase) { + $this->numAssertions += $test->getNumAssertions(); + } elseif ($test instanceof PhptTestCase) { + $this->numAssertions++; + } + + $this->lastTestFailed = false; + + if ($test instanceof TestCase && !$test->hasExpectationOnOutput()) { + $this->write($test->getActualOutput()); + } + } + + protected function printDefects(array $defects, string $type): void + { + $count = count($defects); + + if ($count == 0) { + return; + } + + if ($this->defectListPrinted) { + $this->write("\n--\n\n"); + } + + $this->write( + sprintf( + "There %s %d %s%s:\n", + ($count == 1) ? 'was' : 'were', + $count, + $type, + ($count == 1) ? '' : 's', + ), + ); + + $i = 1; + + if ($this->reverse) { + $defects = array_reverse($defects); + } + + foreach ($defects as $defect) { + $this->printDefect($defect, $i++); + } + + $this->defectListPrinted = true; + } + + protected function printDefect(TestFailure $defect, int $count): void + { + $this->printDefectHeader($defect, $count); + $this->printDefectTrace($defect); + } + + protected function printDefectHeader(TestFailure $defect, int $count): void + { + $this->write( + sprintf( + "\n%d) %s\n", + $count, + $defect->getTestName(), + ), + ); + } + + protected function printDefectTrace(TestFailure $defect): void + { + $e = $defect->thrownException(); + + $this->write((string) $e); + + while ($e = $e->getPrevious()) { + $this->write("\nCaused by\n" . trim((string) $e) . "\n"); + } + } + + protected function printErrors(TestResult $result): void + { + $this->printDefects($result->errors(), 'error'); + } + + protected function printFailures(TestResult $result): void + { + $this->printDefects($result->failures(), 'failure'); + } + + protected function printWarnings(TestResult $result): void + { + $this->printDefects($result->warnings(), 'warning'); + } + + protected function printIncompletes(TestResult $result): void + { + $this->printDefects($result->notImplemented(), 'incomplete test'); + } + + protected function printRisky(TestResult $result): void + { + $this->printDefects($result->risky(), 'risky test'); + } + + protected function printSkipped(TestResult $result): void + { + $this->printDefects($result->skipped(), 'skipped test'); + } + + protected function printHeader(TestResult $result): void + { + if (count($result) > 0) { + $this->write(PHP_EOL . PHP_EOL . (new ResourceUsageFormatter)->resourceUsage($this->timer->stop()) . PHP_EOL . PHP_EOL); + } + } + + protected function printFooter(TestResult $result): void + { + if (count($result) === 0) { + $this->writeWithColor( + 'fg-black, bg-yellow', + 'No tests executed!', + ); + + return; + } + + if ($result->wasSuccessfulAndNoTestIsRiskyOrSkippedOrIncomplete()) { + $this->writeWithColor( + 'fg-black, bg-green', + sprintf( + 'OK (%d test%s, %d assertion%s)', + count($result), + (count($result) === 1) ? '' : 's', + $this->numAssertions, + ($this->numAssertions === 1) ? '' : 's', + ), + ); + + return; + } + + $color = 'fg-black, bg-yellow'; + + if ($result->wasSuccessful()) { + if ($this->verbose || !$result->allHarmless()) { + $this->write("\n"); + } + + $this->writeWithColor( + $color, + 'OK, but incomplete, skipped, or risky tests!', + ); + } else { + $this->write("\n"); + + if ($result->errorCount()) { + $color = 'fg-white, bg-red'; + + $this->writeWithColor( + $color, + 'ERRORS!', + ); + } elseif ($result->failureCount()) { + $color = 'fg-white, bg-red'; + + $this->writeWithColor( + $color, + 'FAILURES!', + ); + } elseif ($result->warningCount()) { + $color = 'fg-black, bg-yellow'; + + $this->writeWithColor( + $color, + 'WARNINGS!', + ); + } + } + + $this->writeCountString(count($result), 'Tests', $color, true); + $this->writeCountString($this->numAssertions, 'Assertions', $color, true); + $this->writeCountString($result->errorCount(), 'Errors', $color); + $this->writeCountString($result->failureCount(), 'Failures', $color); + $this->writeCountString($result->warningCount(), 'Warnings', $color); + $this->writeCountString($result->skippedCount(), 'Skipped', $color); + $this->writeCountString($result->notImplementedCount(), 'Incomplete', $color); + $this->writeCountString($result->riskyCount(), 'Risky', $color); + $this->writeWithColor($color, '.'); + } + + protected function writeProgress(string $progress): void + { + if ($this->debug) { + return; + } + + $this->write($progress); + $this->column++; + $this->numTestsRun++; + + if ($this->column == $this->maxColumn || $this->numTestsRun == $this->numTests) { + if ($this->numTestsRun == $this->numTests) { + $this->write(str_repeat(' ', $this->maxColumn - $this->column)); + } + + $this->write( + sprintf( + ' %' . $this->numTestsWidth . 'd / %' . + $this->numTestsWidth . 'd (%3s%%)', + $this->numTestsRun, + $this->numTests, + floor(($this->numTestsRun / $this->numTests) * 100), + ), + ); + + if ($this->column == $this->maxColumn) { + $this->writeNewLine(); + } + } + } + + protected function writeNewLine(): void + { + $this->column = 0; + $this->write("\n"); + } + + /** + * Formats a buffer with a specified ANSI color sequence if colors are + * enabled. + */ + protected function colorizeTextBox(string $color, string $buffer): string + { + if (!$this->colors) { + return $buffer; + } + + $lines = preg_split('/\r\n|\r|\n/', $buffer); + $padding = max(array_map('\strlen', $lines)); + + $styledLines = []; + + foreach ($lines as $line) { + $styledLines[] = Color::colorize($color, str_pad($line, $padding)); + } + + return implode(PHP_EOL, $styledLines); + } + + /** + * Writes a buffer out with a color sequence if colors are enabled. + */ + protected function writeWithColor(string $color, string $buffer, bool $lf = true): void + { + $this->write($this->colorizeTextBox($color, $buffer)); + + if ($lf) { + $this->write(PHP_EOL); + } + } + + /** + * Writes progress with a color sequence if colors are enabled. + */ + protected function writeProgressWithColor(string $color, string $buffer): void + { + $buffer = $this->colorizeTextBox($color, $buffer); + $this->writeProgress($buffer); + } + + private function writeCountString(int $count, string $name, string $color, bool $always = false): void + { + static $first = true; + + if ($always || $count > 0) { + $this->writeWithColor( + $color, + sprintf( + '%s%s: %d', + !$first ? ', ' : '', + $name, + $count, + ), + false, + ); + + $first = false; + } + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/Exception/Exception.php b/vendor/phpunit/phpunit/src/TextUI/Exception/Exception.php new file mode 100644 index 00000000..ee2ae4ff --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/Exception/Exception.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI; + +use Throwable; + +/** + * @internal This interface is not covered by the backward compatibility promise for PHPUnit + */ +interface Exception extends Throwable +{ +} diff --git a/vendor/phpunit/phpunit/src/TextUI/Exception/ReflectionException.php b/vendor/phpunit/phpunit/src/TextUI/Exception/ReflectionException.php new file mode 100644 index 00000000..74e9d25d --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/Exception/ReflectionException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI; + +use RuntimeException; + +/** + * @internal This interface is not covered by the backward compatibility promise for PHPUnit + */ +final class ReflectionException extends RuntimeException implements Exception +{ +} diff --git a/vendor/phpunit/phpunit/src/TextUI/Exception/RuntimeException.php b/vendor/phpunit/phpunit/src/TextUI/Exception/RuntimeException.php new file mode 100644 index 00000000..790a8463 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/Exception/RuntimeException.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI; + +/** + * @internal This interface is not covered by the backward compatibility promise for PHPUnit + */ +final class RuntimeException extends \RuntimeException implements Exception +{ +} diff --git a/vendor/phpunit/phpunit/src/TextUI/Exception/TestDirectoryNotFoundException.php b/vendor/phpunit/phpunit/src/TextUI/Exception/TestDirectoryNotFoundException.php new file mode 100644 index 00000000..af387bea --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/Exception/TestDirectoryNotFoundException.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI; + +use function sprintf; +use RuntimeException; + +/** + * @internal This interface is not covered by the backward compatibility promise for PHPUnit + */ +final class TestDirectoryNotFoundException extends RuntimeException implements Exception +{ + public function __construct(string $path) + { + parent::__construct( + sprintf( + 'Test directory "%s" not found', + $path, + ), + ); + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/Exception/TestFileNotFoundException.php b/vendor/phpunit/phpunit/src/TextUI/Exception/TestFileNotFoundException.php new file mode 100644 index 00000000..3b534f7e --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/Exception/TestFileNotFoundException.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI; + +use function sprintf; +use RuntimeException; + +/** + * @internal This interface is not covered by the backward compatibility promise for PHPUnit + */ +final class TestFileNotFoundException extends RuntimeException implements Exception +{ + public function __construct(string $path) + { + parent::__construct( + sprintf( + 'Test file "%s" not found', + $path, + ), + ); + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/Help.php b/vendor/phpunit/phpunit/src/TextUI/Help.php new file mode 100644 index 00000000..cfa51acb --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/Help.php @@ -0,0 +1,281 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI; + +use const PHP_EOL; +use function count; +use function defined; +use function explode; +use function max; +use function preg_replace_callback; +use function str_pad; +use function str_repeat; +use function strlen; +use function wordwrap; +use PHPUnit\Util\Color; +use SebastianBergmann\Environment\Console; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Help +{ + private const LEFT_MARGIN = ' '; + + /** + * @var int Number of columns required to write the longest option name to the console + */ + private $maxArgLength = 0; + + /** + * @var int Number of columns left for the description field after padding and option + */ + private $maxDescLength; + + /** + * @var bool Use color highlights for sections, options and parameters + */ + private $hasColor = false; + + public function __construct(?int $width = null, ?bool $withColor = null) + { + if ($width === null) { + $width = (new Console)->getNumberOfColumns(); + } + + if ($withColor === null) { + $this->hasColor = (new Console)->hasColorSupport(); + } else { + $this->hasColor = $withColor; + } + + foreach ($this->elements() as $options) { + foreach ($options as $option) { + if (isset($option['arg'])) { + $this->maxArgLength = max($this->maxArgLength, isset($option['arg']) ? strlen($option['arg']) : 0); + } + } + } + + $this->maxDescLength = $width - $this->maxArgLength - 4; + } + + /** + * Write the help file to the CLI, adapting width and colors to the console. + */ + public function writeToConsole(): void + { + if ($this->hasColor) { + $this->writeWithColor(); + } else { + $this->writePlaintext(); + } + } + + private function writePlaintext(): void + { + foreach ($this->elements() as $section => $options) { + print "{$section}:" . PHP_EOL; + + if ($section !== 'Usage') { + print PHP_EOL; + } + + foreach ($options as $option) { + if (isset($option['spacer'])) { + print PHP_EOL; + } + + if (isset($option['text'])) { + print self::LEFT_MARGIN . $option['text'] . PHP_EOL; + } + + if (isset($option['arg'])) { + $arg = str_pad($option['arg'], $this->maxArgLength); + print self::LEFT_MARGIN . $arg . ' ' . $option['desc'] . PHP_EOL; + } + } + + print PHP_EOL; + } + } + + private function writeWithColor(): void + { + foreach ($this->elements() as $section => $options) { + print Color::colorize('fg-yellow', "{$section}:") . PHP_EOL; + + foreach ($options as $option) { + if (isset($option['spacer'])) { + print PHP_EOL; + } + + if (isset($option['text'])) { + print self::LEFT_MARGIN . $option['text'] . PHP_EOL; + } + + if (isset($option['arg'])) { + $arg = Color::colorize('fg-green', str_pad($option['arg'], $this->maxArgLength)); + $arg = preg_replace_callback( + '/(<[^>]+>)/', + static function ($matches) + { + return Color::colorize('fg-cyan', $matches[0]); + }, + $arg, + ); + $desc = explode(PHP_EOL, wordwrap($option['desc'], $this->maxDescLength, PHP_EOL)); + + print self::LEFT_MARGIN . $arg . ' ' . $desc[0] . PHP_EOL; + + for ($i = 1; $i < count($desc); $i++) { + print str_repeat(' ', $this->maxArgLength + 3) . $desc[$i] . PHP_EOL; + } + } + } + + print PHP_EOL; + } + } + + /** + * @psalm-return array> + */ + private function elements(): array + { + $elements = [ + 'Usage' => [ + ['text' => 'phpunit [options] UnitTest.php'], + ['text' => 'phpunit [options] '], + ], + + 'Code Coverage Options' => [ + ['arg' => '--coverage-clover ', 'desc' => 'Generate code coverage report in Clover XML format'], + ['arg' => '--coverage-cobertura ', 'desc' => 'Generate code coverage report in Cobertura XML format'], + ['arg' => '--coverage-crap4j ', 'desc' => 'Generate code coverage report in Crap4J XML format'], + ['arg' => '--coverage-html ', 'desc' => 'Generate code coverage report in HTML format'], + ['arg' => '--coverage-php ', 'desc' => 'Export PHP_CodeCoverage object to file'], + ['arg' => '--coverage-text=', 'desc' => 'Generate code coverage report in text format [default: standard output]'], + ['arg' => '--coverage-xml ', 'desc' => 'Generate code coverage report in PHPUnit XML format'], + ['arg' => '--coverage-cache ', 'desc' => 'Cache static analysis results'], + ['arg' => '--warm-coverage-cache', 'desc' => 'Warm static analysis cache'], + ['arg' => '--coverage-filter ', 'desc' => 'Include in code coverage analysis'], + ['arg' => '--path-coverage', 'desc' => 'Perform path coverage analysis'], + ['arg' => '--disable-coverage-ignore', 'desc' => 'Disable annotations for ignoring code coverage'], + ['arg' => '--no-coverage', 'desc' => 'Ignore code coverage configuration'], + ], + + 'Logging Options' => [ + ['arg' => '--log-junit ', 'desc' => 'Log test execution in JUnit XML format to file'], + ['arg' => '--log-teamcity ', 'desc' => 'Log test execution in TeamCity format to file'], + ['arg' => '--testdox-html ', 'desc' => 'Write agile documentation in HTML format to file'], + ['arg' => '--testdox-text ', 'desc' => 'Write agile documentation in Text format to file'], + ['arg' => '--testdox-xml ', 'desc' => 'Write agile documentation in XML format to file'], + ['arg' => '--reverse-list', 'desc' => 'Print defects in reverse order'], + ['arg' => '--no-logging', 'desc' => 'Ignore logging configuration'], + ], + + 'Test Selection Options' => [ + ['arg' => '--list-suites', 'desc' => 'List available test suites'], + ['arg' => '--testsuite ', 'desc' => 'Filter which testsuite to run'], + ['arg' => '--list-groups', 'desc' => 'List available test groups'], + ['arg' => '--group ', 'desc' => 'Only runs tests from the specified group(s)'], + ['arg' => '--exclude-group ', 'desc' => 'Exclude tests from the specified group(s)'], + ['arg' => '--covers ', 'desc' => 'Only runs tests annotated with "@covers "'], + ['arg' => '--uses ', 'desc' => 'Only runs tests annotated with "@uses "'], + ['arg' => '--list-tests', 'desc' => 'List available tests'], + ['arg' => '--list-tests-xml ', 'desc' => 'List available tests in XML format'], + ['arg' => '--filter ', 'desc' => 'Filter which tests to run'], + ['arg' => '--test-suffix ', 'desc' => 'Only search for test in files with specified suffix(es). Default: Test.php,.phpt'], + ], + + 'Test Execution Options' => [ + ['arg' => '--dont-report-useless-tests', 'desc' => 'Do not report tests that do not test anything'], + ['arg' => '--strict-coverage', 'desc' => 'Be strict about @covers annotation usage'], + ['arg' => '--strict-global-state', 'desc' => 'Be strict about changes to global state'], + ['arg' => '--disallow-test-output', 'desc' => 'Be strict about output during tests'], + ['arg' => '--disallow-resource-usage', 'desc' => 'Be strict about resource usage during small tests'], + ['arg' => '--enforce-time-limit', 'desc' => 'Enforce time limit based on test size'], + ['arg' => '--default-time-limit ', 'desc' => 'Timeout in seconds for tests without @small, @medium or @large'], + ['arg' => '--disallow-todo-tests', 'desc' => 'Disallow @todo-annotated tests'], + ['spacer' => ''], + + ['arg' => '--process-isolation', 'desc' => 'Run each test in a separate PHP process'], + ['arg' => '--globals-backup', 'desc' => 'Backup and restore $GLOBALS for each test'], + ['arg' => '--static-backup', 'desc' => 'Backup and restore static attributes for each test'], + ['spacer' => ''], + + ['arg' => '--colors ', 'desc' => 'Use colors in output ("never", "auto" or "always")'], + ['arg' => '--columns ', 'desc' => 'Number of columns to use for progress output'], + ['arg' => '--columns max', 'desc' => 'Use maximum number of columns for progress output'], + ['arg' => '--stderr', 'desc' => 'Write to STDERR instead of STDOUT'], + ['arg' => '--stop-on-defect', 'desc' => 'Stop execution upon first not-passed test'], + ['arg' => '--stop-on-error', 'desc' => 'Stop execution upon first error'], + ['arg' => '--stop-on-failure', 'desc' => 'Stop execution upon first error or failure'], + ['arg' => '--stop-on-warning', 'desc' => 'Stop execution upon first warning'], + ['arg' => '--stop-on-risky', 'desc' => 'Stop execution upon first risky test'], + ['arg' => '--stop-on-skipped', 'desc' => 'Stop execution upon first skipped test'], + ['arg' => '--stop-on-incomplete', 'desc' => 'Stop execution upon first incomplete test'], + ['arg' => '--fail-on-incomplete', 'desc' => 'Treat incomplete tests as failures'], + ['arg' => '--fail-on-risky', 'desc' => 'Treat risky tests as failures'], + ['arg' => '--fail-on-skipped', 'desc' => 'Treat skipped tests as failures'], + ['arg' => '--fail-on-warning', 'desc' => 'Treat tests with warnings as failures'], + ['arg' => '-v|--verbose', 'desc' => 'Output more verbose information'], + ['arg' => '--debug', 'desc' => 'Display debugging information'], + ['spacer' => ''], + + ['arg' => '--repeat ', 'desc' => 'Runs the test(s) repeatedly'], + ['arg' => '--teamcity', 'desc' => 'Report test execution progress in TeamCity format'], + ['arg' => '--testdox', 'desc' => 'Report test execution progress in TestDox format'], + ['arg' => '--testdox-group', 'desc' => 'Only include tests from the specified group(s)'], + ['arg' => '--testdox-exclude-group', 'desc' => 'Exclude tests from the specified group(s)'], + ['arg' => '--no-interaction', 'desc' => 'Disable TestDox progress animation'], + ['arg' => '--printer ', 'desc' => 'TestListener implementation to use'], + ['spacer' => ''], + + ['arg' => '--order-by ', 'desc' => 'Run tests in order: default|defects|duration|no-depends|random|reverse|size'], + ['arg' => '--random-order-seed ', 'desc' => 'Use a specific random seed for random order'], + ['arg' => '--cache-result', 'desc' => 'Write test results to cache file'], + ['arg' => '--do-not-cache-result', 'desc' => 'Do not write test results to cache file'], + ], + + 'Configuration Options' => [ + ['arg' => '--prepend ', 'desc' => 'A PHP script that is included as early as possible'], + ['arg' => '--bootstrap ', 'desc' => 'A PHP script that is included before the tests run'], + ['arg' => '-c|--configuration ', 'desc' => 'Read configuration from XML file'], + ['arg' => '--no-configuration', 'desc' => 'Ignore default configuration file (phpunit.xml)'], + ['arg' => '--extensions ', 'desc' => 'A comma separated list of PHPUnit extensions to load'], + ['arg' => '--no-extensions', 'desc' => 'Do not load PHPUnit extensions'], + ['arg' => '--include-path ', 'desc' => 'Prepend PHP\'s include_path with given path(s)'], + ['arg' => '-d ', 'desc' => 'Sets a php.ini value'], + ['arg' => '--cache-result-file ', 'desc' => 'Specify result cache path and filename'], + ['arg' => '--generate-configuration', 'desc' => 'Generate configuration file with suggested settings'], + ['arg' => '--migrate-configuration', 'desc' => 'Migrate configuration file to current format'], + ], + ]; + + if (defined('__PHPUNIT_PHAR__')) { + $elements['PHAR Options'] = [ + ['arg' => '--manifest', 'desc' => 'Print Software Bill of Materials (SBOM) in plain-text format'], + ['arg' => '--sbom', 'desc' => 'Print Software Bill of Materials (SBOM) in CycloneDX XML format'], + ['arg' => '--composer-lock', 'desc' => 'Print composer.lock file used to build the PHAR'], + ]; + } + + $elements['Miscellaneous Options'] = [ + ['arg' => '-h|--help', 'desc' => 'Prints this usage information'], + ['arg' => '--version', 'desc' => 'Prints the version and exits'], + ['arg' => '--atleast-version ', 'desc' => 'Checks that version is greater than min and exits'], + ['arg' => '--check-version', 'desc' => 'Checks whether PHPUnit is the latest version and exits'], + ]; + + return $elements; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/ResultPrinter.php b/vendor/phpunit/phpunit/src/TextUI/ResultPrinter.php new file mode 100644 index 00000000..ec89f600 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/ResultPrinter.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI; + +use PHPUnit\Framework\TestListener; +use PHPUnit\Framework\TestResult; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface ResultPrinter extends TestListener +{ + public function printResult(TestResult $result): void; + + public function write(string $buffer): void; +} diff --git a/vendor/phpunit/phpunit/src/TextUI/TestRunner.php b/vendor/phpunit/phpunit/src/TextUI/TestRunner.php new file mode 100644 index 00000000..8907c470 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/TestRunner.php @@ -0,0 +1,1262 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI; + +use const PHP_EOL; +use const PHP_SAPI; +use const PHP_VERSION; +use function array_diff; +use function array_map; +use function array_merge; +use function assert; +use function class_exists; +use function count; +use function dirname; +use function file_put_contents; +use function htmlspecialchars; +use function is_array; +use function is_int; +use function is_string; +use function mt_srand; +use function range; +use function realpath; +use function sort; +use function sprintf; +use function time; +use PHPUnit\Framework\Exception; +use PHPUnit\Framework\TestResult; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Runner\AfterLastTestHook; +use PHPUnit\Runner\BaseTestRunner; +use PHPUnit\Runner\BeforeFirstTestHook; +use PHPUnit\Runner\DefaultTestResultCache; +use PHPUnit\Runner\Extension\ExtensionHandler; +use PHPUnit\Runner\Filter\ExcludeGroupFilterIterator; +use PHPUnit\Runner\Filter\Factory; +use PHPUnit\Runner\Filter\IncludeGroupFilterIterator; +use PHPUnit\Runner\Filter\NameFilterIterator; +use PHPUnit\Runner\Hook; +use PHPUnit\Runner\NullTestResultCache; +use PHPUnit\Runner\ResultCacheExtension; +use PHPUnit\Runner\StandardTestSuiteLoader; +use PHPUnit\Runner\TestHook; +use PHPUnit\Runner\TestListenerAdapter; +use PHPUnit\Runner\TestSuiteLoader; +use PHPUnit\Runner\TestSuiteSorter; +use PHPUnit\Runner\Version; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\FilterMapper; +use PHPUnit\TextUI\XmlConfiguration\Configuration; +use PHPUnit\TextUI\XmlConfiguration\Loader; +use PHPUnit\TextUI\XmlConfiguration\PhpHandler; +use PHPUnit\Util\Filesystem; +use PHPUnit\Util\Log\JUnit; +use PHPUnit\Util\Log\TeamCity; +use PHPUnit\Util\Printer; +use PHPUnit\Util\TestDox\CliTestDoxPrinter; +use PHPUnit\Util\TestDox\HtmlResultPrinter; +use PHPUnit\Util\TestDox\TextResultPrinter; +use PHPUnit\Util\TestDox\XmlResultPrinter; +use PHPUnit\Util\XdebugFilterScriptGenerator; +use PHPUnit\Util\Xml\SchemaDetector; +use ReflectionClass; +use ReflectionException; +use SebastianBergmann\CodeCoverage\CodeCoverage; +use SebastianBergmann\CodeCoverage\Driver\Selector; +use SebastianBergmann\CodeCoverage\Exception as CodeCoverageException; +use SebastianBergmann\CodeCoverage\Filter as CodeCoverageFilter; +use SebastianBergmann\CodeCoverage\Report\Clover as CloverReport; +use SebastianBergmann\CodeCoverage\Report\Cobertura as CoberturaReport; +use SebastianBergmann\CodeCoverage\Report\Crap4j as Crap4jReport; +use SebastianBergmann\CodeCoverage\Report\Html\Facade as HtmlReport; +use SebastianBergmann\CodeCoverage\Report\PHP as PhpReport; +use SebastianBergmann\CodeCoverage\Report\Text as TextReport; +use SebastianBergmann\CodeCoverage\Report\Xml\Facade as XmlReport; +use SebastianBergmann\Comparator\Comparator; +use SebastianBergmann\Environment\Runtime; +use SebastianBergmann\Invoker\Invoker; +use SebastianBergmann\Timer\Timer; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TestRunner extends BaseTestRunner +{ + public const SUCCESS_EXIT = 0; + public const FAILURE_EXIT = 1; + public const EXCEPTION_EXIT = 2; + + /** + * @var CodeCoverageFilter + */ + private $codeCoverageFilter; + + /** + * @var TestSuiteLoader + */ + private $loader; + + /** + * @var ResultPrinter + */ + private $printer; + + /** + * @var bool + */ + private $messagePrinted = false; + + /** + * @var Hook[] + */ + private $extensions = []; + + /** + * @var Timer + */ + private $timer; + + public function __construct(TestSuiteLoader $loader = null, CodeCoverageFilter $filter = null) + { + if ($filter === null) { + $filter = new CodeCoverageFilter; + } + + $this->codeCoverageFilter = $filter; + $this->loader = $loader; + $this->timer = new Timer; + } + + /** + * @throws \PHPUnit\Runner\Exception + * @throws Exception + * @throws XmlConfiguration\Exception + */ + public function run(TestSuite $suite, array $arguments = [], array $warnings = [], bool $exit = true): TestResult + { + if (isset($arguments['configuration'])) { + $GLOBALS['__PHPUNIT_CONFIGURATION_FILE'] = $arguments['configuration']; + } + + $this->handleConfiguration($arguments); + + $warnings = array_merge($warnings, $arguments['warnings']); + + if (is_int($arguments['columns']) && $arguments['columns'] < 16) { + $arguments['columns'] = 16; + $tooFewColumnsRequested = true; + } + + if (isset($arguments['bootstrap'])) { + $GLOBALS['__PHPUNIT_BOOTSTRAP'] = $arguments['bootstrap']; + } + + if ($arguments['backupGlobals'] === true) { + $suite->setBackupGlobals(true); + } + + if ($arguments['backupStaticAttributes'] === true) { + $suite->setBackupStaticAttributes(true); + } + + if ($arguments['beStrictAboutChangesToGlobalState'] === true) { + $suite->setBeStrictAboutChangesToGlobalState(true); + } + + if ($arguments['executionOrder'] === TestSuiteSorter::ORDER_RANDOMIZED) { + mt_srand($arguments['randomOrderSeed']); + } + + if ($arguments['cacheResult']) { + if (!isset($arguments['cacheResultFile'])) { + if (isset($arguments['configurationObject'])) { + assert($arguments['configurationObject'] instanceof Configuration); + + $cacheLocation = $arguments['configurationObject']->filename(); + } else { + $cacheLocation = $_SERVER['PHP_SELF']; + } + + $arguments['cacheResultFile'] = null; + + $cacheResultFile = realpath($cacheLocation); + + if ($cacheResultFile !== false) { + $arguments['cacheResultFile'] = dirname($cacheResultFile); + } + } + + $cache = new DefaultTestResultCache($arguments['cacheResultFile']); + + $this->addExtension(new ResultCacheExtension($cache)); + } + + if ($arguments['executionOrder'] !== TestSuiteSorter::ORDER_DEFAULT || $arguments['executionOrderDefects'] !== TestSuiteSorter::ORDER_DEFAULT || $arguments['resolveDependencies']) { + $cache = $cache ?? new NullTestResultCache; + + $cache->load(); + + $sorter = new TestSuiteSorter($cache); + + $sorter->reorderTestsInSuite($suite, $arguments['executionOrder'], $arguments['resolveDependencies'], $arguments['executionOrderDefects']); + $originalExecutionOrder = $sorter->getOriginalExecutionOrder(); + + unset($sorter); + } + + if (is_int($arguments['repeat']) && $arguments['repeat'] > 0) { + $_suite = new TestSuite; + + /* @noinspection PhpUnusedLocalVariableInspection */ + foreach (range(1, $arguments['repeat']) as $step) { + $_suite->addTest($suite); + } + + $suite = $_suite; + + unset($_suite); + } + + $result = $this->createTestResult(); + + $listener = new TestListenerAdapter; + $listenerNeeded = false; + + foreach ($this->extensions as $extension) { + if ($extension instanceof TestHook) { + $listener->add($extension); + + $listenerNeeded = true; + } + } + + if ($listenerNeeded) { + $result->addListener($listener); + } + + unset($listener, $listenerNeeded); + + if ($arguments['convertDeprecationsToExceptions']) { + $result->convertDeprecationsToExceptions(true); + } + + if (!$arguments['convertErrorsToExceptions']) { + $result->convertErrorsToExceptions(false); + } + + if (!$arguments['convertNoticesToExceptions']) { + $result->convertNoticesToExceptions(false); + } + + if (!$arguments['convertWarningsToExceptions']) { + $result->convertWarningsToExceptions(false); + } + + if ($arguments['stopOnError']) { + $result->stopOnError(true); + } + + if ($arguments['stopOnFailure']) { + $result->stopOnFailure(true); + } + + if ($arguments['stopOnWarning']) { + $result->stopOnWarning(true); + } + + if ($arguments['stopOnIncomplete']) { + $result->stopOnIncomplete(true); + } + + if ($arguments['stopOnRisky']) { + $result->stopOnRisky(true); + } + + if ($arguments['stopOnSkipped']) { + $result->stopOnSkipped(true); + } + + if ($arguments['stopOnDefect']) { + $result->stopOnDefect(true); + } + + if ($arguments['registerMockObjectsFromTestArgumentsRecursively']) { + $result->setRegisterMockObjectsFromTestArgumentsRecursively(true); + } + + if ($this->printer === null) { + if (isset($arguments['printer'])) { + if ($arguments['printer'] instanceof ResultPrinter) { + $this->printer = $arguments['printer']; + } elseif (is_string($arguments['printer']) && class_exists($arguments['printer'], false)) { + try { + $reflector = new ReflectionClass($arguments['printer']); + + if ($reflector->implementsInterface(ResultPrinter::class)) { + $this->printer = $this->createPrinter($arguments['printer'], $arguments); + } + + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + } + } else { + $this->printer = $this->createPrinter(DefaultResultPrinter::class, $arguments); + } + } + + if (isset($originalExecutionOrder) && $this->printer instanceof CliTestDoxPrinter) { + assert($this->printer instanceof CliTestDoxPrinter); + + $this->printer->setOriginalExecutionOrder($originalExecutionOrder); + $this->printer->setShowProgressAnimation(!$arguments['noInteraction']); + } + + $this->write(Version::getVersionString() . "\n"); + + foreach ($arguments['listeners'] as $listener) { + $result->addListener($listener); + } + + $result->addListener($this->printer); + + $coverageFilterFromConfigurationFile = false; + $coverageFilterFromOption = false; + $codeCoverageReports = 0; + + if (isset($arguments['testdoxHTMLFile'])) { + $result->addListener( + new HtmlResultPrinter( + $arguments['testdoxHTMLFile'], + $arguments['testdoxGroups'], + $arguments['testdoxExcludeGroups'], + ), + ); + } + + if (isset($arguments['testdoxTextFile'])) { + $result->addListener( + new TextResultPrinter( + $arguments['testdoxTextFile'], + $arguments['testdoxGroups'], + $arguments['testdoxExcludeGroups'], + ), + ); + } + + if (isset($arguments['testdoxXMLFile'])) { + $result->addListener( + new XmlResultPrinter( + $arguments['testdoxXMLFile'], + ), + ); + } + + if (isset($arguments['teamcityLogfile'])) { + $result->addListener( + new TeamCity($arguments['teamcityLogfile']), + ); + } + + if (isset($arguments['junitLogfile'])) { + $result->addListener( + new JUnit( + $arguments['junitLogfile'], + $arguments['reportUselessTests'], + ), + ); + } + + if (isset($arguments['coverageClover'])) { + $codeCoverageReports++; + } + + if (isset($arguments['coverageCobertura'])) { + $codeCoverageReports++; + } + + if (isset($arguments['coverageCrap4J'])) { + $codeCoverageReports++; + } + + if (isset($arguments['coverageHtml'])) { + $codeCoverageReports++; + } + + if (isset($arguments['coveragePHP'])) { + $codeCoverageReports++; + } + + if (isset($arguments['coverageText'])) { + $codeCoverageReports++; + } + + if (isset($arguments['coverageXml'])) { + $codeCoverageReports++; + } + + if ($codeCoverageReports > 0 || isset($arguments['xdebugFilterFile'])) { + if (isset($arguments['coverageFilter'])) { + if (!is_array($arguments['coverageFilter'])) { + $coverageFilterDirectories = [$arguments['coverageFilter']]; + } else { + $coverageFilterDirectories = $arguments['coverageFilter']; + } + + foreach ($coverageFilterDirectories as $coverageFilterDirectory) { + $this->codeCoverageFilter->includeDirectory($coverageFilterDirectory); + } + + $coverageFilterFromOption = true; + } + + if (isset($arguments['configurationObject'])) { + assert($arguments['configurationObject'] instanceof Configuration); + + $codeCoverageConfiguration = $arguments['configurationObject']->codeCoverage(); + + if ($codeCoverageConfiguration->hasNonEmptyListOfFilesToBeIncludedInCodeCoverageReport()) { + $coverageFilterFromConfigurationFile = true; + + (new FilterMapper)->map( + $this->codeCoverageFilter, + $codeCoverageConfiguration, + ); + } + } + } + + if ($codeCoverageReports > 0) { + try { + if (isset($codeCoverageConfiguration) && + ($codeCoverageConfiguration->pathCoverage() || (isset($arguments['pathCoverage']) && $arguments['pathCoverage'] === true))) { + $codeCoverageDriver = (new Selector)->forLineAndPathCoverage($this->codeCoverageFilter); + } else { + $codeCoverageDriver = (new Selector)->forLineCoverage($this->codeCoverageFilter); + } + + $codeCoverage = new CodeCoverage( + $codeCoverageDriver, + $this->codeCoverageFilter, + ); + + if (isset($codeCoverageConfiguration) && $codeCoverageConfiguration->hasCacheDirectory()) { + $codeCoverage->cacheStaticAnalysis($codeCoverageConfiguration->cacheDirectory()->path()); + } + + if (isset($arguments['coverageCacheDirectory'])) { + $codeCoverage->cacheStaticAnalysis($arguments['coverageCacheDirectory']); + } + + $codeCoverage->excludeSubclassesOfThisClassFromUnintentionallyCoveredCodeCheck(Comparator::class); + + if ($arguments['strictCoverage']) { + $codeCoverage->enableCheckForUnintentionallyCoveredCode(); + } + + if (isset($arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'])) { + if ($arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage']) { + $codeCoverage->ignoreDeprecatedCode(); + } else { + $codeCoverage->doNotIgnoreDeprecatedCode(); + } + } + + if (isset($arguments['disableCodeCoverageIgnore'])) { + if ($arguments['disableCodeCoverageIgnore']) { + $codeCoverage->disableAnnotationsForIgnoringCode(); + } else { + $codeCoverage->enableAnnotationsForIgnoringCode(); + } + } + + if (isset($arguments['configurationObject'])) { + $codeCoverageConfiguration = $arguments['configurationObject']->codeCoverage(); + + if ($codeCoverageConfiguration->hasNonEmptyListOfFilesToBeIncludedInCodeCoverageReport()) { + if ($codeCoverageConfiguration->includeUncoveredFiles()) { + $codeCoverage->includeUncoveredFiles(); + } else { + $codeCoverage->excludeUncoveredFiles(); + } + + if ($codeCoverageConfiguration->processUncoveredFiles()) { + $codeCoverage->processUncoveredFiles(); + } else { + $codeCoverage->doNotProcessUncoveredFiles(); + } + } + } + + if ($this->codeCoverageFilter->isEmpty()) { + if (!$coverageFilterFromConfigurationFile && !$coverageFilterFromOption) { + $warnings[] = 'No filter is configured, code coverage will not be processed'; + } else { + $warnings[] = 'Incorrect filter configuration, code coverage will not be processed'; + } + + unset($codeCoverage); + } + } catch (CodeCoverageException $e) { + $warnings[] = $e->getMessage(); + } + } + + if ($arguments['verbose']) { + if (PHP_SAPI === 'phpdbg') { + $this->writeMessage('Runtime', 'PHPDBG ' . PHP_VERSION); + } else { + $runtime = 'PHP ' . PHP_VERSION; + + if (isset($codeCoverageDriver)) { + $runtime .= ' with ' . $codeCoverageDriver->nameAndVersion(); + } + + $this->writeMessage('Runtime', $runtime); + } + + if (isset($arguments['configurationObject'])) { + assert($arguments['configurationObject'] instanceof Configuration); + + $this->writeMessage( + 'Configuration', + $arguments['configurationObject']->filename(), + ); + } + + foreach ($arguments['loadedExtensions'] as $extension) { + $this->writeMessage( + 'Extension', + $extension, + ); + } + + foreach ($arguments['notLoadedExtensions'] as $extension) { + $this->writeMessage( + 'Extension', + $extension, + ); + } + } + + if ($arguments['executionOrder'] === TestSuiteSorter::ORDER_RANDOMIZED) { + $this->writeMessage( + 'Random Seed', + (string) $arguments['randomOrderSeed'], + ); + } + + if (isset($tooFewColumnsRequested)) { + $warnings[] = 'Less than 16 columns requested, number of columns set to 16'; + } + + if ((new Runtime)->discardsComments()) { + $warnings[] = 'opcache.save_comments=0 set; annotations will not work'; + } + + if (isset($arguments['conflictBetweenPrinterClassAndTestdox'])) { + $warnings[] = 'Directives printerClass and testdox are mutually exclusive'; + } + + $warnings = array_merge($warnings, $suite->warnings()); + sort($warnings); + + foreach ($warnings as $warning) { + $this->writeMessage('Warning', $warning); + } + + if (isset($arguments['configurationObject'])) { + assert($arguments['configurationObject'] instanceof Configuration); + + if ($arguments['configurationObject']->hasValidationErrors()) { + if ((new SchemaDetector)->detect($arguments['configurationObject']->filename())->detected()) { + $this->writeMessage('Warning', 'Your XML configuration validates against a deprecated schema.'); + $this->writeMessage('Suggestion', 'Migrate your XML configuration using "--migrate-configuration"!'); + } else { + $this->write( + "\n Warning - The configuration file did not pass validation!\n The following problems have been detected:\n", + ); + + $this->write($arguments['configurationObject']->validationErrors()); + + $this->write("\n Test results may not be as expected.\n\n"); + } + } + } + + if (isset($arguments['xdebugFilterFile'], $codeCoverageConfiguration)) { + $this->write(PHP_EOL . 'Please note that --dump-xdebug-filter and --prepend are deprecated and will be removed in PHPUnit 10.' . PHP_EOL); + + $script = (new XdebugFilterScriptGenerator)->generate($codeCoverageConfiguration); + + if ($arguments['xdebugFilterFile'] !== 'php://stdout' && $arguments['xdebugFilterFile'] !== 'php://stderr' && !Filesystem::createDirectory(dirname($arguments['xdebugFilterFile']))) { + $this->write(sprintf('Cannot write Xdebug filter script to %s ' . PHP_EOL, $arguments['xdebugFilterFile'])); + + exit(self::EXCEPTION_EXIT); + } + + file_put_contents($arguments['xdebugFilterFile'], $script); + + $this->write(sprintf('Wrote Xdebug filter script to %s ' . PHP_EOL . PHP_EOL, $arguments['xdebugFilterFile'])); + + exit(self::SUCCESS_EXIT); + } + + $this->write("\n"); + + if (isset($codeCoverage)) { + $result->setCodeCoverage($codeCoverage); + } + + $result->beStrictAboutTestsThatDoNotTestAnything($arguments['reportUselessTests']); + $result->beStrictAboutOutputDuringTests($arguments['disallowTestOutput']); + $result->beStrictAboutTodoAnnotatedTests($arguments['disallowTodoAnnotatedTests']); + $result->beStrictAboutResourceUsageDuringSmallTests($arguments['beStrictAboutResourceUsageDuringSmallTests']); + + if ($arguments['enforceTimeLimit'] === true && !(new Invoker)->canInvokeWithTimeout()) { + $this->writeMessage('Error', 'PHP extension pcntl is required for enforcing time limits'); + } + + $result->enforceTimeLimit($arguments['enforceTimeLimit']); + $result->setDefaultTimeLimit($arguments['defaultTimeLimit']); + $result->setTimeoutForSmallTests($arguments['timeoutForSmallTests']); + $result->setTimeoutForMediumTests($arguments['timeoutForMediumTests']); + $result->setTimeoutForLargeTests($arguments['timeoutForLargeTests']); + + if (isset($arguments['forceCoversAnnotation']) && $arguments['forceCoversAnnotation'] === true) { + $result->forceCoversAnnotation(); + } + + $this->processSuiteFilters($suite, $arguments); + $suite->setRunTestInSeparateProcess($arguments['processIsolation']); + + foreach ($this->extensions as $extension) { + if ($extension instanceof BeforeFirstTestHook) { + $extension->executeBeforeFirstTest(); + } + } + + $suite->run($result); + + foreach ($this->extensions as $extension) { + if ($extension instanceof AfterLastTestHook) { + $extension->executeAfterLastTest(); + } + } + + $result->flushListeners(); + $this->printer->printResult($result); + + if (isset($codeCoverage)) { + if (isset($arguments['coveragePHP'])) { + $this->codeCoverageGenerationStart('PHP'); + + try { + $writer = new PhpReport; + $writer->process($codeCoverage, $arguments['coveragePHP']); + + $this->codeCoverageGenerationSucceeded(); + + unset($writer); + } catch (CodeCoverageException $e) { + $this->codeCoverageGenerationFailed($e); + } + } + + if (isset($arguments['coverageClover'])) { + $this->codeCoverageGenerationStart('Clover XML'); + + try { + $writer = new CloverReport; + $writer->process($codeCoverage, $arguments['coverageClover']); + + $this->codeCoverageGenerationSucceeded(); + + unset($writer); + } catch (CodeCoverageException $e) { + $this->codeCoverageGenerationFailed($e); + } + } + + if (isset($arguments['coverageCobertura'])) { + $this->codeCoverageGenerationStart('Cobertura XML'); + + try { + $writer = new CoberturaReport; + $writer->process($codeCoverage, $arguments['coverageCobertura']); + + $this->codeCoverageGenerationSucceeded(); + + unset($writer); + } catch (CodeCoverageException $e) { + $this->codeCoverageGenerationFailed($e); + } + } + + if (isset($arguments['coverageCrap4J'])) { + $this->codeCoverageGenerationStart('Crap4J XML'); + + try { + $writer = new Crap4jReport($arguments['crap4jThreshold']); + $writer->process($codeCoverage, $arguments['coverageCrap4J']); + + $this->codeCoverageGenerationSucceeded(); + + unset($writer); + } catch (CodeCoverageException $e) { + $this->codeCoverageGenerationFailed($e); + } + } + + if (isset($arguments['coverageHtml'])) { + $this->codeCoverageGenerationStart('HTML'); + + try { + $writer = new HtmlReport( + $arguments['reportLowUpperBound'], + $arguments['reportHighLowerBound'], + sprintf( + ' and PHPUnit %s', + Version::id(), + ), + ); + + $writer->process($codeCoverage, $arguments['coverageHtml']); + + $this->codeCoverageGenerationSucceeded(); + + unset($writer); + } catch (CodeCoverageException $e) { + $this->codeCoverageGenerationFailed($e); + } + } + + if (isset($arguments['coverageText'])) { + if ($arguments['coverageText'] === 'php://stdout') { + $outputStream = $this->printer; + $colors = $arguments['colors'] && $arguments['colors'] !== DefaultResultPrinter::COLOR_NEVER; + } else { + $outputStream = new Printer($arguments['coverageText']); + $colors = false; + } + + $processor = new TextReport( + $arguments['reportLowUpperBound'], + $arguments['reportHighLowerBound'], + $arguments['coverageTextShowUncoveredFiles'], + $arguments['coverageTextShowOnlySummary'], + ); + + $outputStream->write( + $processor->process($codeCoverage, $colors), + ); + } + + if (isset($arguments['coverageXml'])) { + $this->codeCoverageGenerationStart('PHPUnit XML'); + + try { + $writer = new XmlReport(Version::id()); + $writer->process($codeCoverage, $arguments['coverageXml']); + + $this->codeCoverageGenerationSucceeded(); + + unset($writer); + } catch (CodeCoverageException $e) { + $this->codeCoverageGenerationFailed($e); + } + } + } + + if ($exit) { + if (isset($arguments['failOnEmptyTestSuite']) && $arguments['failOnEmptyTestSuite'] === true && count($result) === 0) { + exit(self::FAILURE_EXIT); + } + + if ($result->wasSuccessfulIgnoringWarnings()) { + if ($arguments['failOnRisky'] && !$result->allHarmless()) { + exit(self::FAILURE_EXIT); + } + + if ($arguments['failOnWarning'] && $result->warningCount() > 0) { + exit(self::FAILURE_EXIT); + } + + if ($arguments['failOnIncomplete'] && $result->notImplementedCount() > 0) { + exit(self::FAILURE_EXIT); + } + + if ($arguments['failOnSkipped'] && $result->skippedCount() > 0) { + exit(self::FAILURE_EXIT); + } + + exit(self::SUCCESS_EXIT); + } + + if ($result->errorCount() > 0) { + exit(self::EXCEPTION_EXIT); + } + + if ($result->failureCount() > 0) { + exit(self::FAILURE_EXIT); + } + } + + return $result; + } + + /** + * Returns the loader to be used. + */ + public function getLoader(): TestSuiteLoader + { + if ($this->loader === null) { + $this->loader = new StandardTestSuiteLoader; + } + + return $this->loader; + } + + public function addExtension(Hook $extension): void + { + $this->extensions[] = $extension; + } + + /** + * Override to define how to handle a failed loading of + * a test suite. + */ + protected function runFailed(string $message): void + { + $this->write($message . PHP_EOL); + + exit(self::FAILURE_EXIT); + } + + private function createTestResult(): TestResult + { + return new TestResult; + } + + private function write(string $buffer): void + { + if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg') { + $buffer = htmlspecialchars($buffer); + } + + if ($this->printer !== null) { + $this->printer->write($buffer); + } else { + print $buffer; + } + } + + /** + * @throws Exception + * @throws XmlConfiguration\Exception + */ + private function handleConfiguration(array &$arguments): void + { + if (!isset($arguments['configurationObject']) && isset($arguments['configuration'])) { + $arguments['configurationObject'] = (new Loader)->load($arguments['configuration']); + } + + if (!isset($arguments['warnings'])) { + $arguments['warnings'] = []; + } + + $arguments['debug'] = $arguments['debug'] ?? false; + $arguments['filter'] = $arguments['filter'] ?? false; + $arguments['listeners'] = $arguments['listeners'] ?? []; + + if (isset($arguments['configurationObject'])) { + (new PhpHandler)->handle($arguments['configurationObject']->php()); + + $codeCoverageConfiguration = $arguments['configurationObject']->codeCoverage(); + + if (!isset($arguments['noCoverage'])) { + if (!isset($arguments['coverageClover']) && $codeCoverageConfiguration->hasClover()) { + $arguments['coverageClover'] = $codeCoverageConfiguration->clover()->target()->path(); + } + + if (!isset($arguments['coverageCobertura']) && $codeCoverageConfiguration->hasCobertura()) { + $arguments['coverageCobertura'] = $codeCoverageConfiguration->cobertura()->target()->path(); + } + + if (!isset($arguments['coverageCrap4J']) && $codeCoverageConfiguration->hasCrap4j()) { + $arguments['coverageCrap4J'] = $codeCoverageConfiguration->crap4j()->target()->path(); + + if (!isset($arguments['crap4jThreshold'])) { + $arguments['crap4jThreshold'] = $codeCoverageConfiguration->crap4j()->threshold(); + } + } + + if (!isset($arguments['coverageHtml']) && $codeCoverageConfiguration->hasHtml()) { + $arguments['coverageHtml'] = $codeCoverageConfiguration->html()->target()->path(); + + if (!isset($arguments['reportLowUpperBound'])) { + $arguments['reportLowUpperBound'] = $codeCoverageConfiguration->html()->lowUpperBound(); + } + + if (!isset($arguments['reportHighLowerBound'])) { + $arguments['reportHighLowerBound'] = $codeCoverageConfiguration->html()->highLowerBound(); + } + } + + if (!isset($arguments['coveragePHP']) && $codeCoverageConfiguration->hasPhp()) { + $arguments['coveragePHP'] = $codeCoverageConfiguration->php()->target()->path(); + } + + if (!isset($arguments['coverageText']) && $codeCoverageConfiguration->hasText()) { + $arguments['coverageText'] = $codeCoverageConfiguration->text()->target()->path(); + $arguments['coverageTextShowUncoveredFiles'] = $codeCoverageConfiguration->text()->showUncoveredFiles(); + $arguments['coverageTextShowOnlySummary'] = $codeCoverageConfiguration->text()->showOnlySummary(); + } + + if (!isset($arguments['coverageXml']) && $codeCoverageConfiguration->hasXml()) { + $arguments['coverageXml'] = $codeCoverageConfiguration->xml()->target()->path(); + } + } + + $phpunitConfiguration = $arguments['configurationObject']->phpunit(); + + $arguments['backupGlobals'] = $arguments['backupGlobals'] ?? $phpunitConfiguration->backupGlobals(); + $arguments['backupStaticAttributes'] = $arguments['backupStaticAttributes'] ?? $phpunitConfiguration->backupStaticAttributes(); + $arguments['beStrictAboutChangesToGlobalState'] = $arguments['beStrictAboutChangesToGlobalState'] ?? $phpunitConfiguration->beStrictAboutChangesToGlobalState(); + $arguments['cacheResult'] = $arguments['cacheResult'] ?? $phpunitConfiguration->cacheResult(); + $arguments['colors'] = $arguments['colors'] ?? $phpunitConfiguration->colors(); + $arguments['convertDeprecationsToExceptions'] = $arguments['convertDeprecationsToExceptions'] ?? $phpunitConfiguration->convertDeprecationsToExceptions(); + $arguments['convertErrorsToExceptions'] = $arguments['convertErrorsToExceptions'] ?? $phpunitConfiguration->convertErrorsToExceptions(); + $arguments['convertNoticesToExceptions'] = $arguments['convertNoticesToExceptions'] ?? $phpunitConfiguration->convertNoticesToExceptions(); + $arguments['convertWarningsToExceptions'] = $arguments['convertWarningsToExceptions'] ?? $phpunitConfiguration->convertWarningsToExceptions(); + $arguments['processIsolation'] = $arguments['processIsolation'] ?? $phpunitConfiguration->processIsolation(); + $arguments['stopOnDefect'] = $arguments['stopOnDefect'] ?? $phpunitConfiguration->stopOnDefect(); + $arguments['stopOnError'] = $arguments['stopOnError'] ?? $phpunitConfiguration->stopOnError(); + $arguments['stopOnFailure'] = $arguments['stopOnFailure'] ?? $phpunitConfiguration->stopOnFailure(); + $arguments['stopOnWarning'] = $arguments['stopOnWarning'] ?? $phpunitConfiguration->stopOnWarning(); + $arguments['stopOnIncomplete'] = $arguments['stopOnIncomplete'] ?? $phpunitConfiguration->stopOnIncomplete(); + $arguments['stopOnRisky'] = $arguments['stopOnRisky'] ?? $phpunitConfiguration->stopOnRisky(); + $arguments['stopOnSkipped'] = $arguments['stopOnSkipped'] ?? $phpunitConfiguration->stopOnSkipped(); + $arguments['failOnEmptyTestSuite'] = $arguments['failOnEmptyTestSuite'] ?? $phpunitConfiguration->failOnEmptyTestSuite(); + $arguments['failOnIncomplete'] = $arguments['failOnIncomplete'] ?? $phpunitConfiguration->failOnIncomplete(); + $arguments['failOnRisky'] = $arguments['failOnRisky'] ?? $phpunitConfiguration->failOnRisky(); + $arguments['failOnSkipped'] = $arguments['failOnSkipped'] ?? $phpunitConfiguration->failOnSkipped(); + $arguments['failOnWarning'] = $arguments['failOnWarning'] ?? $phpunitConfiguration->failOnWarning(); + $arguments['enforceTimeLimit'] = $arguments['enforceTimeLimit'] ?? $phpunitConfiguration->enforceTimeLimit(); + $arguments['defaultTimeLimit'] = $arguments['defaultTimeLimit'] ?? $phpunitConfiguration->defaultTimeLimit(); + $arguments['timeoutForSmallTests'] = $arguments['timeoutForSmallTests'] ?? $phpunitConfiguration->timeoutForSmallTests(); + $arguments['timeoutForMediumTests'] = $arguments['timeoutForMediumTests'] ?? $phpunitConfiguration->timeoutForMediumTests(); + $arguments['timeoutForLargeTests'] = $arguments['timeoutForLargeTests'] ?? $phpunitConfiguration->timeoutForLargeTests(); + $arguments['reportUselessTests'] = $arguments['reportUselessTests'] ?? $phpunitConfiguration->beStrictAboutTestsThatDoNotTestAnything(); + $arguments['strictCoverage'] = $arguments['strictCoverage'] ?? $phpunitConfiguration->beStrictAboutCoversAnnotation(); + $arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'] = $arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'] ?? $codeCoverageConfiguration->ignoreDeprecatedCodeUnits(); + $arguments['disallowTestOutput'] = $arguments['disallowTestOutput'] ?? $phpunitConfiguration->beStrictAboutOutputDuringTests(); + $arguments['disallowTodoAnnotatedTests'] = $arguments['disallowTodoAnnotatedTests'] ?? $phpunitConfiguration->beStrictAboutTodoAnnotatedTests(); + $arguments['beStrictAboutResourceUsageDuringSmallTests'] = $arguments['beStrictAboutResourceUsageDuringSmallTests'] ?? $phpunitConfiguration->beStrictAboutResourceUsageDuringSmallTests(); + $arguments['verbose'] = $arguments['verbose'] ?? $phpunitConfiguration->verbose(); + $arguments['reverseDefectList'] = $arguments['reverseDefectList'] ?? $phpunitConfiguration->reverseDefectList(); + $arguments['forceCoversAnnotation'] = $arguments['forceCoversAnnotation'] ?? $phpunitConfiguration->forceCoversAnnotation(); + $arguments['disableCodeCoverageIgnore'] = $arguments['disableCodeCoverageIgnore'] ?? $codeCoverageConfiguration->disableCodeCoverageIgnore(); + $arguments['registerMockObjectsFromTestArgumentsRecursively'] = $arguments['registerMockObjectsFromTestArgumentsRecursively'] ?? $phpunitConfiguration->registerMockObjectsFromTestArgumentsRecursively(); + $arguments['noInteraction'] = $arguments['noInteraction'] ?? $phpunitConfiguration->noInteraction(); + $arguments['executionOrder'] = $arguments['executionOrder'] ?? $phpunitConfiguration->executionOrder(); + $arguments['resolveDependencies'] = $arguments['resolveDependencies'] ?? $phpunitConfiguration->resolveDependencies(); + + if (!isset($arguments['bootstrap']) && $phpunitConfiguration->hasBootstrap()) { + $arguments['bootstrap'] = $phpunitConfiguration->bootstrap(); + } + + if (!isset($arguments['cacheResultFile']) && $phpunitConfiguration->hasCacheResultFile()) { + $arguments['cacheResultFile'] = $phpunitConfiguration->cacheResultFile(); + } + + if (!isset($arguments['executionOrderDefects'])) { + $arguments['executionOrderDefects'] = $phpunitConfiguration->defectsFirst() ? TestSuiteSorter::ORDER_DEFECTS_FIRST : TestSuiteSorter::ORDER_DEFAULT; + } + + if ($phpunitConfiguration->conflictBetweenPrinterClassAndTestdox()) { + $arguments['conflictBetweenPrinterClassAndTestdox'] = true; + } + + $groupCliArgs = []; + + if (!empty($arguments['groups'])) { + $groupCliArgs = $arguments['groups']; + } + + $groupConfiguration = $arguments['configurationObject']->groups(); + + if (!isset($arguments['groups']) && $groupConfiguration->hasInclude()) { + $arguments['groups'] = $groupConfiguration->include()->asArrayOfStrings(); + } + + if (!isset($arguments['excludeGroups']) && $groupConfiguration->hasExclude()) { + $arguments['excludeGroups'] = array_diff($groupConfiguration->exclude()->asArrayOfStrings(), $groupCliArgs); + } + + if (!isset($arguments['noExtensions'])) { + $extensionHandler = new ExtensionHandler; + + foreach ($arguments['configurationObject']->extensions() as $extension) { + $extensionHandler->registerExtension($extension, $this); + } + + foreach ($arguments['configurationObject']->listeners() as $listener) { + $arguments['listeners'][] = $extensionHandler->createTestListenerInstance($listener); + } + + unset($extensionHandler); + } + + foreach ($arguments['unavailableExtensions'] as $extension) { + $arguments['warnings'][] = sprintf( + 'Extension "%s" is not available', + $extension, + ); + } + + $loggingConfiguration = $arguments['configurationObject']->logging(); + + if (!isset($arguments['noLogging'])) { + if ($loggingConfiguration->hasText()) { + $arguments['listeners'][] = new DefaultResultPrinter( + $loggingConfiguration->text()->target()->path(), + true, + ); + } + + if (!isset($arguments['teamcityLogfile']) && $loggingConfiguration->hasTeamCity()) { + $arguments['teamcityLogfile'] = $loggingConfiguration->teamCity()->target()->path(); + } + + if (!isset($arguments['junitLogfile']) && $loggingConfiguration->hasJunit()) { + $arguments['junitLogfile'] = $loggingConfiguration->junit()->target()->path(); + } + + if (!isset($arguments['testdoxHTMLFile']) && $loggingConfiguration->hasTestDoxHtml()) { + $arguments['testdoxHTMLFile'] = $loggingConfiguration->testDoxHtml()->target()->path(); + } + + if (!isset($arguments['testdoxTextFile']) && $loggingConfiguration->hasTestDoxText()) { + $arguments['testdoxTextFile'] = $loggingConfiguration->testDoxText()->target()->path(); + } + + if (!isset($arguments['testdoxXMLFile']) && $loggingConfiguration->hasTestDoxXml()) { + $arguments['testdoxXMLFile'] = $loggingConfiguration->testDoxXml()->target()->path(); + } + } + + $testdoxGroupConfiguration = $arguments['configurationObject']->testdoxGroups(); + + if (!isset($arguments['testdoxGroups']) && $testdoxGroupConfiguration->hasInclude()) { + $arguments['testdoxGroups'] = $testdoxGroupConfiguration->include()->asArrayOfStrings(); + } + + if (!isset($arguments['testdoxExcludeGroups']) && $testdoxGroupConfiguration->hasExclude()) { + $arguments['testdoxExcludeGroups'] = $testdoxGroupConfiguration->exclude()->asArrayOfStrings(); + } + } + + $extensionHandler = new ExtensionHandler; + + foreach ($arguments['extensions'] as $extension) { + $extensionHandler->registerExtension($extension, $this); + } + + unset($extensionHandler); + + $arguments['backupGlobals'] = $arguments['backupGlobals'] ?? null; + $arguments['backupStaticAttributes'] = $arguments['backupStaticAttributes'] ?? null; + $arguments['beStrictAboutChangesToGlobalState'] = $arguments['beStrictAboutChangesToGlobalState'] ?? null; + $arguments['beStrictAboutResourceUsageDuringSmallTests'] = $arguments['beStrictAboutResourceUsageDuringSmallTests'] ?? false; + $arguments['cacheResult'] = $arguments['cacheResult'] ?? true; + $arguments['colors'] = $arguments['colors'] ?? DefaultResultPrinter::COLOR_DEFAULT; + $arguments['columns'] = $arguments['columns'] ?? 80; + $arguments['convertDeprecationsToExceptions'] = $arguments['convertDeprecationsToExceptions'] ?? false; + $arguments['convertErrorsToExceptions'] = $arguments['convertErrorsToExceptions'] ?? true; + $arguments['convertNoticesToExceptions'] = $arguments['convertNoticesToExceptions'] ?? true; + $arguments['convertWarningsToExceptions'] = $arguments['convertWarningsToExceptions'] ?? true; + $arguments['crap4jThreshold'] = $arguments['crap4jThreshold'] ?? 30; + $arguments['disallowTestOutput'] = $arguments['disallowTestOutput'] ?? false; + $arguments['disallowTodoAnnotatedTests'] = $arguments['disallowTodoAnnotatedTests'] ?? false; + $arguments['defaultTimeLimit'] = $arguments['defaultTimeLimit'] ?? 0; + $arguments['enforceTimeLimit'] = $arguments['enforceTimeLimit'] ?? false; + $arguments['excludeGroups'] = $arguments['excludeGroups'] ?? []; + $arguments['executionOrder'] = $arguments['executionOrder'] ?? TestSuiteSorter::ORDER_DEFAULT; + $arguments['executionOrderDefects'] = $arguments['executionOrderDefects'] ?? TestSuiteSorter::ORDER_DEFAULT; + $arguments['failOnIncomplete'] = $arguments['failOnIncomplete'] ?? false; + $arguments['failOnRisky'] = $arguments['failOnRisky'] ?? false; + $arguments['failOnSkipped'] = $arguments['failOnSkipped'] ?? false; + $arguments['failOnWarning'] = $arguments['failOnWarning'] ?? false; + $arguments['groups'] = $arguments['groups'] ?? []; + $arguments['noInteraction'] = $arguments['noInteraction'] ?? false; + $arguments['processIsolation'] = $arguments['processIsolation'] ?? false; + $arguments['randomOrderSeed'] = $arguments['randomOrderSeed'] ?? time(); + $arguments['registerMockObjectsFromTestArgumentsRecursively'] = $arguments['registerMockObjectsFromTestArgumentsRecursively'] ?? false; + $arguments['repeat'] = $arguments['repeat'] ?? false; + $arguments['reportHighLowerBound'] = $arguments['reportHighLowerBound'] ?? 90; + $arguments['reportLowUpperBound'] = $arguments['reportLowUpperBound'] ?? 50; + $arguments['reportUselessTests'] = $arguments['reportUselessTests'] ?? true; + $arguments['reverseList'] = $arguments['reverseList'] ?? false; + $arguments['resolveDependencies'] = $arguments['resolveDependencies'] ?? true; + $arguments['stopOnError'] = $arguments['stopOnError'] ?? false; + $arguments['stopOnFailure'] = $arguments['stopOnFailure'] ?? false; + $arguments['stopOnIncomplete'] = $arguments['stopOnIncomplete'] ?? false; + $arguments['stopOnRisky'] = $arguments['stopOnRisky'] ?? false; + $arguments['stopOnSkipped'] = $arguments['stopOnSkipped'] ?? false; + $arguments['stopOnWarning'] = $arguments['stopOnWarning'] ?? false; + $arguments['stopOnDefect'] = $arguments['stopOnDefect'] ?? false; + $arguments['strictCoverage'] = $arguments['strictCoverage'] ?? false; + $arguments['testdoxExcludeGroups'] = $arguments['testdoxExcludeGroups'] ?? []; + $arguments['testdoxGroups'] = $arguments['testdoxGroups'] ?? []; + $arguments['timeoutForLargeTests'] = $arguments['timeoutForLargeTests'] ?? 60; + $arguments['timeoutForMediumTests'] = $arguments['timeoutForMediumTests'] ?? 10; + $arguments['timeoutForSmallTests'] = $arguments['timeoutForSmallTests'] ?? 1; + $arguments['verbose'] = $arguments['verbose'] ?? false; + + if ($arguments['reportLowUpperBound'] > $arguments['reportHighLowerBound']) { + $arguments['reportLowUpperBound'] = 50; + $arguments['reportHighLowerBound'] = 90; + } + } + + private function processSuiteFilters(TestSuite $suite, array $arguments): void + { + if (!$arguments['filter'] && + empty($arguments['groups']) && + empty($arguments['excludeGroups']) && + empty($arguments['testsCovering']) && + empty($arguments['testsUsing'])) { + return; + } + + $filterFactory = new Factory; + + if (!empty($arguments['excludeGroups'])) { + $filterFactory->addFilter( + new ReflectionClass(ExcludeGroupFilterIterator::class), + $arguments['excludeGroups'], + ); + } + + if (!empty($arguments['groups'])) { + $filterFactory->addFilter( + new ReflectionClass(IncludeGroupFilterIterator::class), + $arguments['groups'], + ); + } + + if (!empty($arguments['testsCovering'])) { + $filterFactory->addFilter( + new ReflectionClass(IncludeGroupFilterIterator::class), + array_map( + static function (string $name): string + { + return '__phpunit_covers_' . $name; + }, + $arguments['testsCovering'], + ), + ); + } + + if (!empty($arguments['testsUsing'])) { + $filterFactory->addFilter( + new ReflectionClass(IncludeGroupFilterIterator::class), + array_map( + static function (string $name): string + { + return '__phpunit_uses_' . $name; + }, + $arguments['testsUsing'], + ), + ); + } + + if ($arguments['filter']) { + $filterFactory->addFilter( + new ReflectionClass(NameFilterIterator::class), + $arguments['filter'], + ); + } + + $suite->injectFilter($filterFactory); + } + + private function writeMessage(string $type, string $message): void + { + if (!$this->messagePrinted) { + $this->write("\n"); + } + + $this->write( + sprintf( + "%-15s%s\n", + $type . ':', + $message, + ), + ); + + $this->messagePrinted = true; + } + + private function createPrinter(string $class, array $arguments): ResultPrinter + { + $object = new $class( + (isset($arguments['stderr']) && $arguments['stderr'] === true) ? 'php://stderr' : null, + $arguments['verbose'], + $arguments['colors'], + $arguments['debug'], + $arguments['columns'], + $arguments['reverseList'], + ); + + assert($object instanceof ResultPrinter); + + return $object; + } + + private function codeCoverageGenerationStart(string $format): void + { + $this->write( + sprintf( + "\nGenerating code coverage report in %s format ... ", + $format, + ), + ); + + $this->timer->start(); + } + + private function codeCoverageGenerationSucceeded(): void + { + $this->write( + sprintf( + "done [%s]\n", + $this->timer->stop()->asString(), + ), + ); + } + + private function codeCoverageGenerationFailed(\Exception $e): void + { + $this->write( + sprintf( + "failed [%s]\n%s\n", + $this->timer->stop()->asString(), + $e->getMessage(), + ), + ); + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/TestSuiteMapper.php b/vendor/phpunit/phpunit/src/TextUI/TestSuiteMapper.php new file mode 100644 index 00000000..a0ea593a --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/TestSuiteMapper.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI; + +use const PHP_VERSION; +use function explode; +use function in_array; +use function is_dir; +use function is_file; +use function strpos; +use function version_compare; +use PHPUnit\Framework\Exception as FrameworkException; +use PHPUnit\Framework\TestSuite as TestSuiteObject; +use PHPUnit\TextUI\XmlConfiguration\TestSuiteCollection; +use SebastianBergmann\FileIterator\Facade; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TestSuiteMapper +{ + /** + * @throws RuntimeException + * @throws TestDirectoryNotFoundException + * @throws TestFileNotFoundException + */ + public function map(TestSuiteCollection $configuration, string $filter): TestSuiteObject + { + try { + $filterAsArray = $filter ? explode(',', $filter) : []; + $result = new TestSuiteObject; + + foreach ($configuration as $testSuiteConfiguration) { + if (!empty($filterAsArray) && !in_array($testSuiteConfiguration->name(), $filterAsArray, true)) { + continue; + } + + $testSuite = new TestSuiteObject($testSuiteConfiguration->name()); + $testSuiteEmpty = true; + + $exclude = []; + + foreach ($testSuiteConfiguration->exclude()->asArray() as $file) { + $exclude[] = $file->path(); + } + + foreach ($testSuiteConfiguration->directories() as $directory) { + if (!version_compare(PHP_VERSION, $directory->phpVersion(), $directory->phpVersionOperator()->asString())) { + continue; + } + + $files = (new Facade)->getFilesAsArray( + $directory->path(), + $directory->suffix(), + $directory->prefix(), + $exclude, + ); + + if (!empty($files)) { + $testSuite->addTestFiles($files); + + $testSuiteEmpty = false; + } elseif (strpos($directory->path(), '*') === false && !is_dir($directory->path())) { + throw new TestDirectoryNotFoundException($directory->path()); + } + } + + foreach ($testSuiteConfiguration->files() as $file) { + if (!is_file($file->path())) { + throw new TestFileNotFoundException($file->path()); + } + + if (!version_compare(PHP_VERSION, $file->phpVersion(), $file->phpVersionOperator()->asString())) { + continue; + } + + $testSuite->addTestFile($file->path()); + + $testSuiteEmpty = false; + } + + if (!$testSuiteEmpty) { + $result->addTest($testSuite); + } + } + + return $result; + } catch (FrameworkException $e) { + throw new RuntimeException( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/CodeCoverage.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/CodeCoverage.php new file mode 100644 index 00000000..191113c6 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/CodeCoverage.php @@ -0,0 +1,363 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage; + +use function count; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter\DirectoryCollection; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Clover; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Cobertura; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Crap4j; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Html; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Php; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Text; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Xml; +use PHPUnit\TextUI\XmlConfiguration\Directory; +use PHPUnit\TextUI\XmlConfiguration\Exception; +use PHPUnit\TextUI\XmlConfiguration\FileCollection; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class CodeCoverage +{ + /** + * @var ?Directory + */ + private $cacheDirectory; + + /** + * @var DirectoryCollection + */ + private $directories; + + /** + * @var FileCollection + */ + private $files; + + /** + * @var DirectoryCollection + */ + private $excludeDirectories; + + /** + * @var FileCollection + */ + private $excludeFiles; + + /** + * @var bool + */ + private $pathCoverage; + + /** + * @var bool + */ + private $includeUncoveredFiles; + + /** + * @var bool + */ + private $processUncoveredFiles; + + /** + * @var bool + */ + private $ignoreDeprecatedCodeUnits; + + /** + * @var bool + */ + private $disableCodeCoverageIgnore; + + /** + * @var ?Clover + */ + private $clover; + + /** + * @var ?Cobertura + */ + private $cobertura; + + /** + * @var ?Crap4j + */ + private $crap4j; + + /** + * @var ?Html + */ + private $html; + + /** + * @var ?Php + */ + private $php; + + /** + * @var ?Text + */ + private $text; + + /** + * @var ?Xml + */ + private $xml; + + public function __construct(?Directory $cacheDirectory, DirectoryCollection $directories, FileCollection $files, DirectoryCollection $excludeDirectories, FileCollection $excludeFiles, bool $pathCoverage, bool $includeUncoveredFiles, bool $processUncoveredFiles, bool $ignoreDeprecatedCodeUnits, bool $disableCodeCoverageIgnore, ?Clover $clover, ?Cobertura $cobertura, ?Crap4j $crap4j, ?Html $html, ?Php $php, ?Text $text, ?Xml $xml) + { + $this->cacheDirectory = $cacheDirectory; + $this->directories = $directories; + $this->files = $files; + $this->excludeDirectories = $excludeDirectories; + $this->excludeFiles = $excludeFiles; + $this->pathCoverage = $pathCoverage; + $this->includeUncoveredFiles = $includeUncoveredFiles; + $this->processUncoveredFiles = $processUncoveredFiles; + $this->ignoreDeprecatedCodeUnits = $ignoreDeprecatedCodeUnits; + $this->disableCodeCoverageIgnore = $disableCodeCoverageIgnore; + $this->clover = $clover; + $this->cobertura = $cobertura; + $this->crap4j = $crap4j; + $this->html = $html; + $this->php = $php; + $this->text = $text; + $this->xml = $xml; + } + + /** + * @psalm-assert-if-true !null $this->cacheDirectory + */ + public function hasCacheDirectory(): bool + { + return $this->cacheDirectory !== null; + } + + /** + * @throws Exception + */ + public function cacheDirectory(): Directory + { + if (!$this->hasCacheDirectory()) { + throw new Exception( + 'No cache directory has been configured', + ); + } + + return $this->cacheDirectory; + } + + public function hasNonEmptyListOfFilesToBeIncludedInCodeCoverageReport(): bool + { + return count($this->directories) > 0 || count($this->files) > 0; + } + + public function directories(): DirectoryCollection + { + return $this->directories; + } + + public function files(): FileCollection + { + return $this->files; + } + + public function excludeDirectories(): DirectoryCollection + { + return $this->excludeDirectories; + } + + public function excludeFiles(): FileCollection + { + return $this->excludeFiles; + } + + public function pathCoverage(): bool + { + return $this->pathCoverage; + } + + public function includeUncoveredFiles(): bool + { + return $this->includeUncoveredFiles; + } + + public function ignoreDeprecatedCodeUnits(): bool + { + return $this->ignoreDeprecatedCodeUnits; + } + + public function disableCodeCoverageIgnore(): bool + { + return $this->disableCodeCoverageIgnore; + } + + public function processUncoveredFiles(): bool + { + return $this->processUncoveredFiles; + } + + /** + * @psalm-assert-if-true !null $this->clover + */ + public function hasClover(): bool + { + return $this->clover !== null; + } + + /** + * @throws Exception + */ + public function clover(): Clover + { + if (!$this->hasClover()) { + throw new Exception( + 'Code Coverage report "Clover XML" has not been configured', + ); + } + + return $this->clover; + } + + /** + * @psalm-assert-if-true !null $this->cobertura + */ + public function hasCobertura(): bool + { + return $this->cobertura !== null; + } + + /** + * @throws Exception + */ + public function cobertura(): Cobertura + { + if (!$this->hasCobertura()) { + throw new Exception( + 'Code Coverage report "Cobertura XML" has not been configured', + ); + } + + return $this->cobertura; + } + + /** + * @psalm-assert-if-true !null $this->crap4j + */ + public function hasCrap4j(): bool + { + return $this->crap4j !== null; + } + + /** + * @throws Exception + */ + public function crap4j(): Crap4j + { + if (!$this->hasCrap4j()) { + throw new Exception( + 'Code Coverage report "Crap4J" has not been configured', + ); + } + + return $this->crap4j; + } + + /** + * @psalm-assert-if-true !null $this->html + */ + public function hasHtml(): bool + { + return $this->html !== null; + } + + /** + * @throws Exception + */ + public function html(): Html + { + if (!$this->hasHtml()) { + throw new Exception( + 'Code Coverage report "HTML" has not been configured', + ); + } + + return $this->html; + } + + /** + * @psalm-assert-if-true !null $this->php + */ + public function hasPhp(): bool + { + return $this->php !== null; + } + + /** + * @throws Exception + */ + public function php(): Php + { + if (!$this->hasPhp()) { + throw new Exception( + 'Code Coverage report "PHP" has not been configured', + ); + } + + return $this->php; + } + + /** + * @psalm-assert-if-true !null $this->text + */ + public function hasText(): bool + { + return $this->text !== null; + } + + /** + * @throws Exception + */ + public function text(): Text + { + if (!$this->hasText()) { + throw new Exception( + 'Code Coverage report "Text" has not been configured', + ); + } + + return $this->text; + } + + /** + * @psalm-assert-if-true !null $this->xml + */ + public function hasXml(): bool + { + return $this->xml !== null; + } + + /** + * @throws Exception + */ + public function xml(): Xml + { + if (!$this->hasXml()) { + throw new Exception( + 'Code Coverage report "XML" has not been configured', + ); + } + + return $this->xml; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Filter/Directory.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Filter/Directory.php new file mode 100644 index 00000000..91659f4d --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Filter/Directory.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Directory +{ + /** + * @var string + */ + private $path; + + /** + * @var string + */ + private $prefix; + + /** + * @var string + */ + private $suffix; + + /** + * @var string + */ + private $group; + + public function __construct(string $path, string $prefix, string $suffix, string $group) + { + $this->path = $path; + $this->prefix = $prefix; + $this->suffix = $suffix; + $this->group = $group; + } + + public function path(): string + { + return $this->path; + } + + public function prefix(): string + { + return $this->prefix; + } + + public function suffix(): string + { + return $this->suffix; + } + + public function group(): string + { + return $this->group; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Filter/DirectoryCollection.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Filter/DirectoryCollection.php new file mode 100644 index 00000000..88ec1e38 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Filter/DirectoryCollection.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter; + +use function count; +use Countable; +use IteratorAggregate; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + * + * @template-implements IteratorAggregate + */ +final class DirectoryCollection implements Countable, IteratorAggregate +{ + /** + * @var Directory[] + */ + private $directories; + + /** + * @param Directory[] $directories + */ + public static function fromArray(array $directories): self + { + return new self(...$directories); + } + + private function __construct(Directory ...$directories) + { + $this->directories = $directories; + } + + /** + * @return Directory[] + */ + public function asArray(): array + { + return $this->directories; + } + + public function count(): int + { + return count($this->directories); + } + + public function getIterator(): DirectoryCollectionIterator + { + return new DirectoryCollectionIterator($this); + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Filter/DirectoryCollectionIterator.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Filter/DirectoryCollectionIterator.php new file mode 100644 index 00000000..f2fee25d --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Filter/DirectoryCollectionIterator.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter; + +use function count; +use function iterator_count; +use Countable; +use Iterator; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @template-implements Iterator + */ +final class DirectoryCollectionIterator implements Countable, Iterator +{ + /** + * @var Directory[] + */ + private $directories; + + /** + * @var int + */ + private $position; + + public function __construct(DirectoryCollection $directories) + { + $this->directories = $directories->asArray(); + } + + public function count(): int + { + return iterator_count($this); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->directories); + } + + public function key(): int + { + return $this->position; + } + + public function current(): Directory + { + return $this->directories[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/FilterMapper.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/FilterMapper.php new file mode 100644 index 00000000..82be6032 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/FilterMapper.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage; + +use SebastianBergmann\CodeCoverage\Filter; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class FilterMapper +{ + public function map(Filter $filter, CodeCoverage $configuration): void + { + foreach ($configuration->directories() as $directory) { + $filter->includeDirectory( + $directory->path(), + $directory->suffix(), + $directory->prefix(), + ); + } + + foreach ($configuration->files() as $file) { + $filter->includeFile($file->path()); + } + + foreach ($configuration->excludeDirectories() as $directory) { + $filter->excludeDirectory( + $directory->path(), + $directory->suffix(), + $directory->prefix(), + ); + } + + foreach ($configuration->excludeFiles() as $file) { + $filter->excludeFile($file->path()); + } + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Clover.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Clover.php new file mode 100644 index 00000000..b1094ec3 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Clover.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; + +use PHPUnit\TextUI\XmlConfiguration\File; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Clover +{ + /** + * @var File + */ + private $target; + + public function __construct(File $target) + { + $this->target = $target; + } + + public function target(): File + { + return $this->target; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Cobertura.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Cobertura.php new file mode 100644 index 00000000..f831ac09 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Cobertura.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; + +use PHPUnit\TextUI\XmlConfiguration\File; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Cobertura +{ + /** + * @var File + */ + private $target; + + public function __construct(File $target) + { + $this->target = $target; + } + + public function target(): File + { + return $this->target; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Crap4j.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Crap4j.php new file mode 100644 index 00000000..4904775d --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Crap4j.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; + +use PHPUnit\TextUI\XmlConfiguration\File; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Crap4j +{ + /** + * @var File + */ + private $target; + + /** + * @var int + */ + private $threshold; + + public function __construct(File $target, int $threshold) + { + $this->target = $target; + $this->threshold = $threshold; + } + + public function target(): File + { + return $this->target; + } + + public function threshold(): int + { + return $this->threshold; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Html.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Html.php new file mode 100644 index 00000000..ce3d0284 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Html.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; + +use PHPUnit\TextUI\XmlConfiguration\Directory; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Html +{ + /** + * @var Directory + */ + private $target; + + /** + * @var int + */ + private $lowUpperBound; + + /** + * @var int + */ + private $highLowerBound; + + public function __construct(Directory $target, int $lowUpperBound, int $highLowerBound) + { + $this->target = $target; + $this->lowUpperBound = $lowUpperBound; + $this->highLowerBound = $highLowerBound; + } + + public function target(): Directory + { + return $this->target; + } + + public function lowUpperBound(): int + { + return $this->lowUpperBound; + } + + public function highLowerBound(): int + { + return $this->highLowerBound; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Php.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Php.php new file mode 100644 index 00000000..dc5d32ea --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Php.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; + +use PHPUnit\TextUI\XmlConfiguration\File; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Php +{ + /** + * @var File + */ + private $target; + + public function __construct(File $target) + { + $this->target = $target; + } + + public function target(): File + { + return $this->target; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Text.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Text.php new file mode 100644 index 00000000..cb7470d3 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Text.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; + +use PHPUnit\TextUI\XmlConfiguration\File; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Text +{ + /** + * @var File + */ + private $target; + + /** + * @var bool + */ + private $showUncoveredFiles; + + /** + * @var bool + */ + private $showOnlySummary; + + public function __construct(File $target, bool $showUncoveredFiles, bool $showOnlySummary) + { + $this->target = $target; + $this->showUncoveredFiles = $showUncoveredFiles; + $this->showOnlySummary = $showOnlySummary; + } + + public function target(): File + { + return $this->target; + } + + public function showUncoveredFiles(): bool + { + return $this->showUncoveredFiles; + } + + public function showOnlySummary(): bool + { + return $this->showOnlySummary; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Xml.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Xml.php new file mode 100644 index 00000000..34073bd5 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Xml.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; + +use PHPUnit\TextUI\XmlConfiguration\Directory; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Xml +{ + /** + * @var Directory + */ + private $target; + + public function __construct(Directory $target) + { + $this->target = $target; + } + + public function target(): Directory + { + return $this->target; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Configuration.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Configuration.php new file mode 100644 index 00000000..4067e2f8 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Configuration.php @@ -0,0 +1,152 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\CodeCoverage; +use PHPUnit\TextUI\XmlConfiguration\Logging\Logging; +use PHPUnit\Util\Xml\ValidationResult; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Configuration +{ + /** + * @var string + */ + private $filename; + + /** + * @var ValidationResult + */ + private $validationResult; + + /** + * @var ExtensionCollection + */ + private $extensions; + + /** + * @var CodeCoverage + */ + private $codeCoverage; + + /** + * @var Groups + */ + private $groups; + + /** + * @var Groups + */ + private $testdoxGroups; + + /** + * @var ExtensionCollection + */ + private $listeners; + + /** + * @var Logging + */ + private $logging; + + /** + * @var Php + */ + private $php; + + /** + * @var PHPUnit + */ + private $phpunit; + + /** + * @var TestSuiteCollection + */ + private $testSuite; + + public function __construct(string $filename, ValidationResult $validationResult, ExtensionCollection $extensions, CodeCoverage $codeCoverage, Groups $groups, Groups $testdoxGroups, ExtensionCollection $listeners, Logging $logging, Php $php, PHPUnit $phpunit, TestSuiteCollection $testSuite) + { + $this->filename = $filename; + $this->validationResult = $validationResult; + $this->extensions = $extensions; + $this->codeCoverage = $codeCoverage; + $this->groups = $groups; + $this->testdoxGroups = $testdoxGroups; + $this->listeners = $listeners; + $this->logging = $logging; + $this->php = $php; + $this->phpunit = $phpunit; + $this->testSuite = $testSuite; + } + + public function filename(): string + { + return $this->filename; + } + + public function hasValidationErrors(): bool + { + return $this->validationResult->hasValidationErrors(); + } + + public function validationErrors(): string + { + return $this->validationResult->asString(); + } + + public function extensions(): ExtensionCollection + { + return $this->extensions; + } + + public function codeCoverage(): CodeCoverage + { + return $this->codeCoverage; + } + + public function groups(): Groups + { + return $this->groups; + } + + public function testdoxGroups(): Groups + { + return $this->testdoxGroups; + } + + public function listeners(): ExtensionCollection + { + return $this->listeners; + } + + public function logging(): Logging + { + return $this->logging; + } + + public function php(): Php + { + return $this->php; + } + + public function phpunit(): PHPUnit + { + return $this->phpunit; + } + + public function testSuite(): TestSuiteCollection + { + return $this->testSuite; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Exception.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Exception.php new file mode 100644 index 00000000..162b37e8 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Exception.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use RuntimeException; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Exception extends RuntimeException implements \PHPUnit\Exception +{ +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/Directory.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/Directory.php new file mode 100644 index 00000000..b0fdf64f --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/Directory.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Directory +{ + /** + * @var string + */ + private $path; + + public function __construct(string $path) + { + $this->path = $path; + } + + public function path(): string + { + return $this->path; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/DirectoryCollection.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/DirectoryCollection.php new file mode 100644 index 00000000..cb840892 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/DirectoryCollection.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function count; +use Countable; +use IteratorAggregate; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + * + * @template-implements IteratorAggregate + */ +final class DirectoryCollection implements Countable, IteratorAggregate +{ + /** + * @var Directory[] + */ + private $directories; + + /** + * @param Directory[] $directories + */ + public static function fromArray(array $directories): self + { + return new self(...$directories); + } + + private function __construct(Directory ...$directories) + { + $this->directories = $directories; + } + + /** + * @return Directory[] + */ + public function asArray(): array + { + return $this->directories; + } + + public function count(): int + { + return count($this->directories); + } + + public function getIterator(): DirectoryCollectionIterator + { + return new DirectoryCollectionIterator($this); + } + + public function isEmpty(): bool + { + return $this->count() === 0; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/DirectoryCollectionIterator.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/DirectoryCollectionIterator.php new file mode 100644 index 00000000..4b092744 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/DirectoryCollectionIterator.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function count; +use function iterator_count; +use Countable; +use Iterator; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @template-implements Iterator + */ +final class DirectoryCollectionIterator implements Countable, Iterator +{ + /** + * @var Directory[] + */ + private $directories; + + /** + * @var int + */ + private $position; + + public function __construct(DirectoryCollection $directories) + { + $this->directories = $directories->asArray(); + } + + public function count(): int + { + return iterator_count($this); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->directories); + } + + public function key(): int + { + return $this->position; + } + + public function current(): Directory + { + return $this->directories[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/File.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/File.php new file mode 100644 index 00000000..6bdd1c24 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/File.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class File +{ + /** + * @var string + */ + private $path; + + public function __construct(string $path) + { + $this->path = $path; + } + + public function path(): string + { + return $this->path; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/FileCollection.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/FileCollection.php new file mode 100644 index 00000000..60e7e401 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/FileCollection.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function count; +use Countable; +use IteratorAggregate; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + * + * @template-implements IteratorAggregate + */ +final class FileCollection implements Countable, IteratorAggregate +{ + /** + * @var File[] + */ + private $files; + + /** + * @param File[] $files + */ + public static function fromArray(array $files): self + { + return new self(...$files); + } + + private function __construct(File ...$files) + { + $this->files = $files; + } + + /** + * @return File[] + */ + public function asArray(): array + { + return $this->files; + } + + public function count(): int + { + return count($this->files); + } + + public function getIterator(): FileCollectionIterator + { + return new FileCollectionIterator($this); + } + + public function isEmpty(): bool + { + return $this->count() === 0; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/FileCollectionIterator.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/FileCollectionIterator.php new file mode 100644 index 00000000..0ce4273d --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/FileCollectionIterator.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function count; +use function iterator_count; +use Countable; +use Iterator; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @template-implements Iterator + */ +final class FileCollectionIterator implements Countable, Iterator +{ + /** + * @var File[] + */ + private $files; + + /** + * @var int + */ + private $position; + + public function __construct(FileCollection $files) + { + $this->files = $files->asArray(); + } + + public function count(): int + { + return iterator_count($this); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->files); + } + + public function key(): int + { + return $this->position; + } + + public function current(): File + { + return $this->files[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Generator.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Generator.php new file mode 100644 index 00000000..9f6a812a --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Generator.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function str_replace; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Generator +{ + /** + * @var string + */ + private const TEMPLATE = <<<'EOT' + + + + + {tests_directory} + + + + + + {src_directory} + + + + +EOT; + + public function generateDefaultConfiguration(string $phpunitVersion, string $bootstrapScript, string $testsDirectory, string $srcDirectory, string $cacheDirectory): string + { + return str_replace( + [ + '{phpunit_version}', + '{bootstrap_script}', + '{tests_directory}', + '{src_directory}', + '{cache_directory}', + ], + [ + $phpunitVersion, + $bootstrapScript, + $testsDirectory, + $srcDirectory, + $cacheDirectory, + ], + self::TEMPLATE, + ); + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Group/Group.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Group/Group.php new file mode 100644 index 00000000..bb0d9252 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Group/Group.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Group +{ + /** + * @var string + */ + private $name; + + public function __construct(string $name) + { + $this->name = $name; + } + + public function name(): string + { + return $this->name; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Group/GroupCollection.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Group/GroupCollection.php new file mode 100644 index 00000000..735d8af1 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Group/GroupCollection.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use IteratorAggregate; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + * + * @template-implements IteratorAggregate + */ +final class GroupCollection implements IteratorAggregate +{ + /** + * @var Group[] + */ + private $groups; + + /** + * @param Group[] $groups + */ + public static function fromArray(array $groups): self + { + return new self(...$groups); + } + + private function __construct(Group ...$groups) + { + $this->groups = $groups; + } + + /** + * @return Group[] + */ + public function asArray(): array + { + return $this->groups; + } + + /** + * @return string[] + */ + public function asArrayOfStrings(): array + { + $result = []; + + foreach ($this->groups as $group) { + $result[] = $group->name(); + } + + return $result; + } + + public function isEmpty(): bool + { + return empty($this->groups); + } + + public function getIterator(): GroupCollectionIterator + { + return new GroupCollectionIterator($this); + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Group/GroupCollectionIterator.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Group/GroupCollectionIterator.php new file mode 100644 index 00000000..843a708e --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Group/GroupCollectionIterator.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function count; +use function iterator_count; +use Countable; +use Iterator; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @template-implements Iterator + */ +final class GroupCollectionIterator implements Countable, Iterator +{ + /** + * @var Group[] + */ + private $groups; + + /** + * @var int + */ + private $position; + + public function __construct(GroupCollection $groups) + { + $this->groups = $groups->asArray(); + } + + public function count(): int + { + return iterator_count($this); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->groups); + } + + public function key(): int + { + return $this->position; + } + + public function current(): Group + { + return $this->groups[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Group/Groups.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Group/Groups.php new file mode 100644 index 00000000..0604ce32 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Group/Groups.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Groups +{ + /** + * @var GroupCollection + */ + private $include; + + /** + * @var GroupCollection + */ + private $exclude; + + public function __construct(GroupCollection $include, GroupCollection $exclude) + { + $this->include = $include; + $this->exclude = $exclude; + } + + public function hasInclude(): bool + { + return !$this->include->isEmpty(); + } + + public function include(): GroupCollection + { + return $this->include; + } + + public function hasExclude(): bool + { + return !$this->exclude->isEmpty(); + } + + public function exclude(): GroupCollection + { + return $this->exclude; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Loader.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Loader.php new file mode 100644 index 00000000..7a7786b6 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Loader.php @@ -0,0 +1,1273 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use const DIRECTORY_SEPARATOR; +use const PHP_VERSION; +use function assert; +use function defined; +use function dirname; +use function explode; +use function is_file; +use function is_numeric; +use function preg_match; +use function stream_resolve_include_path; +use function strlen; +use function strpos; +use function strtolower; +use function substr; +use function trim; +use DOMDocument; +use DOMElement; +use DOMNode; +use DOMNodeList; +use DOMXPath; +use PHPUnit\Runner\TestSuiteSorter; +use PHPUnit\Runner\Version; +use PHPUnit\TextUI\DefaultResultPrinter; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\CodeCoverage; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter\Directory as FilterDirectory; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter\DirectoryCollection as FilterDirectoryCollection; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Clover; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Cobertura; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Crap4j; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Html as CodeCoverageHtml; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Php as CodeCoveragePhp; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Text as CodeCoverageText; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Xml as CodeCoverageXml; +use PHPUnit\TextUI\XmlConfiguration\Logging\Junit; +use PHPUnit\TextUI\XmlConfiguration\Logging\Logging; +use PHPUnit\TextUI\XmlConfiguration\Logging\TeamCity; +use PHPUnit\TextUI\XmlConfiguration\Logging\TestDox\Html as TestDoxHtml; +use PHPUnit\TextUI\XmlConfiguration\Logging\TestDox\Text as TestDoxText; +use PHPUnit\TextUI\XmlConfiguration\Logging\TestDox\Xml as TestDoxXml; +use PHPUnit\TextUI\XmlConfiguration\Logging\Text; +use PHPUnit\TextUI\XmlConfiguration\TestSuite as TestSuiteConfiguration; +use PHPUnit\Util\TestDox\CliTestDoxPrinter; +use PHPUnit\Util\VersionComparisonOperator; +use PHPUnit\Util\Xml; +use PHPUnit\Util\Xml\Exception as XmlException; +use PHPUnit\Util\Xml\Loader as XmlLoader; +use PHPUnit\Util\Xml\SchemaFinder; +use PHPUnit\Util\Xml\Validator; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Loader +{ + /** + * @throws Exception + */ + public function load(string $filename): Configuration + { + try { + $document = (new XmlLoader)->loadFile($filename, false, true, true); + } catch (XmlException $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + + $xpath = new DOMXPath($document); + + try { + $xsdFilename = (new SchemaFinder)->find(Version::series()); + } catch (XmlException $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + + return new Configuration( + $filename, + (new Validator)->validate($document, $xsdFilename), + $this->extensions($filename, $xpath), + $this->codeCoverage($filename, $xpath, $document), + $this->groups($xpath), + $this->testdoxGroups($xpath), + $this->listeners($filename, $xpath), + $this->logging($filename, $xpath), + $this->php($filename, $xpath), + $this->phpunit($filename, $document), + $this->testSuite($filename, $xpath), + ); + } + + public function logging(string $filename, DOMXPath $xpath): Logging + { + if ($xpath->query('logging/log')->length !== 0) { + return $this->legacyLogging($filename, $xpath); + } + + $junit = null; + $element = $this->element($xpath, 'logging/junit'); + + if ($element) { + $junit = new Junit( + new File( + $this->toAbsolutePath( + $filename, + (string) $this->getStringAttribute($element, 'outputFile'), + ), + ), + ); + } + + $text = null; + $element = $this->element($xpath, 'logging/text'); + + if ($element) { + $text = new Text( + new File( + $this->toAbsolutePath( + $filename, + (string) $this->getStringAttribute($element, 'outputFile'), + ), + ), + ); + } + + $teamCity = null; + $element = $this->element($xpath, 'logging/teamcity'); + + if ($element) { + $teamCity = new TeamCity( + new File( + $this->toAbsolutePath( + $filename, + (string) $this->getStringAttribute($element, 'outputFile'), + ), + ), + ); + } + + $testDoxHtml = null; + $element = $this->element($xpath, 'logging/testdoxHtml'); + + if ($element) { + $testDoxHtml = new TestDoxHtml( + new File( + $this->toAbsolutePath( + $filename, + (string) $this->getStringAttribute($element, 'outputFile'), + ), + ), + ); + } + + $testDoxText = null; + $element = $this->element($xpath, 'logging/testdoxText'); + + if ($element) { + $testDoxText = new TestDoxText( + new File( + $this->toAbsolutePath( + $filename, + (string) $this->getStringAttribute($element, 'outputFile'), + ), + ), + ); + } + + $testDoxXml = null; + $element = $this->element($xpath, 'logging/testdoxXml'); + + if ($element) { + $testDoxXml = new TestDoxXml( + new File( + $this->toAbsolutePath( + $filename, + (string) $this->getStringAttribute($element, 'outputFile'), + ), + ), + ); + } + + return new Logging( + $junit, + $text, + $teamCity, + $testDoxHtml, + $testDoxText, + $testDoxXml, + ); + } + + public function legacyLogging(string $filename, DOMXPath $xpath): Logging + { + $junit = null; + $teamCity = null; + $testDoxHtml = null; + $testDoxText = null; + $testDoxXml = null; + $text = null; + + foreach ($xpath->query('logging/log') as $log) { + assert($log instanceof DOMElement); + + $type = (string) $log->getAttribute('type'); + $target = (string) $log->getAttribute('target'); + + if (!$target) { + continue; + } + + $target = $this->toAbsolutePath($filename, $target); + + switch ($type) { + case 'plain': + $text = new Text( + new File($target), + ); + + break; + + case 'junit': + $junit = new Junit( + new File($target), + ); + + break; + + case 'teamcity': + $teamCity = new TeamCity( + new File($target), + ); + + break; + + case 'testdox-html': + $testDoxHtml = new TestDoxHtml( + new File($target), + ); + + break; + + case 'testdox-text': + $testDoxText = new TestDoxText( + new File($target), + ); + + break; + + case 'testdox-xml': + $testDoxXml = new TestDoxXml( + new File($target), + ); + + break; + } + } + + return new Logging( + $junit, + $text, + $teamCity, + $testDoxHtml, + $testDoxText, + $testDoxXml, + ); + } + + private function extensions(string $filename, DOMXPath $xpath): ExtensionCollection + { + $extensions = []; + + foreach ($xpath->query('extensions/extension') as $extension) { + assert($extension instanceof DOMElement); + + $extensions[] = $this->getElementConfigurationParameters($filename, $extension); + } + + return ExtensionCollection::fromArray($extensions); + } + + private function getElementConfigurationParameters(string $filename, DOMElement $element): Extension + { + /** @psalm-var class-string $class */ + $class = (string) $element->getAttribute('class'); + $file = ''; + $arguments = $this->getConfigurationArguments($filename, $element->childNodes); + + if ($element->getAttribute('file')) { + $file = $this->toAbsolutePath( + $filename, + (string) $element->getAttribute('file'), + true, + ); + } + + return new Extension($class, $file, $arguments); + } + + private function toAbsolutePath(string $filename, string $path, bool $useIncludePath = false): string + { + $path = trim($path); + + if (strpos($path, '/') === 0) { + return $path; + } + + // Matches the following on Windows: + // - \\NetworkComputer\Path + // - \\.\D: + // - \\.\c: + // - C:\Windows + // - C:\windows + // - C:/windows + // - c:/windows + if (defined('PHP_WINDOWS_VERSION_BUILD') && + ($path[0] === '\\' || (strlen($path) >= 3 && preg_match('#^[A-Z]\:[/\\\]#i', substr($path, 0, 3))))) { + return $path; + } + + if (strpos($path, '://') !== false) { + return $path; + } + + $file = dirname($filename) . DIRECTORY_SEPARATOR . $path; + + if ($useIncludePath && !is_file($file)) { + $includePathFile = stream_resolve_include_path($path); + + if ($includePathFile) { + $file = $includePathFile; + } + } + + return $file; + } + + private function getConfigurationArguments(string $filename, DOMNodeList $nodes): array + { + $arguments = []; + + if ($nodes->length === 0) { + return $arguments; + } + + foreach ($nodes as $node) { + if (!$node instanceof DOMElement) { + continue; + } + + if ($node->tagName !== 'arguments') { + continue; + } + + foreach ($node->childNodes as $argument) { + if (!$argument instanceof DOMElement) { + continue; + } + + if ($argument->tagName === 'file' || $argument->tagName === 'directory') { + $arguments[] = $this->toAbsolutePath($filename, (string) $argument->textContent); + } else { + $arguments[] = Xml::xmlToVariable($argument); + } + } + } + + return $arguments; + } + + private function codeCoverage(string $filename, DOMXPath $xpath, DOMDocument $document): CodeCoverage + { + if ($xpath->query('filter/whitelist')->length !== 0) { + return $this->legacyCodeCoverage($filename, $xpath, $document); + } + + $cacheDirectory = null; + $pathCoverage = false; + $includeUncoveredFiles = true; + $processUncoveredFiles = false; + $ignoreDeprecatedCodeUnits = false; + $disableCodeCoverageIgnore = false; + + $element = $this->element($xpath, 'coverage'); + + if ($element) { + $cacheDirectory = $this->getStringAttribute($element, 'cacheDirectory'); + + if ($cacheDirectory !== null) { + $cacheDirectory = new Directory( + $this->toAbsolutePath($filename, $cacheDirectory), + ); + } + + $pathCoverage = $this->getBooleanAttribute( + $element, + 'pathCoverage', + false, + ); + + $includeUncoveredFiles = $this->getBooleanAttribute( + $element, + 'includeUncoveredFiles', + true, + ); + + $processUncoveredFiles = $this->getBooleanAttribute( + $element, + 'processUncoveredFiles', + false, + ); + + $ignoreDeprecatedCodeUnits = $this->getBooleanAttribute( + $element, + 'ignoreDeprecatedCodeUnits', + false, + ); + + $disableCodeCoverageIgnore = $this->getBooleanAttribute( + $element, + 'disableCodeCoverageIgnore', + false, + ); + } + + $clover = null; + $element = $this->element($xpath, 'coverage/report/clover'); + + if ($element) { + $clover = new Clover( + new File( + $this->toAbsolutePath( + $filename, + (string) $this->getStringAttribute($element, 'outputFile'), + ), + ), + ); + } + + $cobertura = null; + $element = $this->element($xpath, 'coverage/report/cobertura'); + + if ($element) { + $cobertura = new Cobertura( + new File( + $this->toAbsolutePath( + $filename, + (string) $this->getStringAttribute($element, 'outputFile'), + ), + ), + ); + } + + $crap4j = null; + $element = $this->element($xpath, 'coverage/report/crap4j'); + + if ($element) { + $crap4j = new Crap4j( + new File( + $this->toAbsolutePath( + $filename, + (string) $this->getStringAttribute($element, 'outputFile'), + ), + ), + $this->getIntegerAttribute($element, 'threshold', 30), + ); + } + + $html = null; + $element = $this->element($xpath, 'coverage/report/html'); + + if ($element) { + $html = new CodeCoverageHtml( + new Directory( + $this->toAbsolutePath( + $filename, + (string) $this->getStringAttribute($element, 'outputDirectory'), + ), + ), + $this->getIntegerAttribute($element, 'lowUpperBound', 50), + $this->getIntegerAttribute($element, 'highLowerBound', 90), + ); + } + + $php = null; + $element = $this->element($xpath, 'coverage/report/php'); + + if ($element) { + $php = new CodeCoveragePhp( + new File( + $this->toAbsolutePath( + $filename, + (string) $this->getStringAttribute($element, 'outputFile'), + ), + ), + ); + } + + $text = null; + $element = $this->element($xpath, 'coverage/report/text'); + + if ($element) { + $text = new CodeCoverageText( + new File( + $this->toAbsolutePath( + $filename, + (string) $this->getStringAttribute($element, 'outputFile'), + ), + ), + $this->getBooleanAttribute($element, 'showUncoveredFiles', false), + $this->getBooleanAttribute($element, 'showOnlySummary', false), + ); + } + + $xml = null; + $element = $this->element($xpath, 'coverage/report/xml'); + + if ($element) { + $xml = new CodeCoverageXml( + new Directory( + $this->toAbsolutePath( + $filename, + (string) $this->getStringAttribute($element, 'outputDirectory'), + ), + ), + ); + } + + return new CodeCoverage( + $cacheDirectory, + $this->readFilterDirectories($filename, $xpath, 'coverage/include/directory'), + $this->readFilterFiles($filename, $xpath, 'coverage/include/file'), + $this->readFilterDirectories($filename, $xpath, 'coverage/exclude/directory'), + $this->readFilterFiles($filename, $xpath, 'coverage/exclude/file'), + $pathCoverage, + $includeUncoveredFiles, + $processUncoveredFiles, + $ignoreDeprecatedCodeUnits, + $disableCodeCoverageIgnore, + $clover, + $cobertura, + $crap4j, + $html, + $php, + $text, + $xml, + ); + } + + /** + * @deprecated + */ + private function legacyCodeCoverage(string $filename, DOMXPath $xpath, DOMDocument $document): CodeCoverage + { + $ignoreDeprecatedCodeUnits = $this->getBooleanAttribute( + $document->documentElement, + 'ignoreDeprecatedCodeUnitsFromCodeCoverage', + false, + ); + + $disableCodeCoverageIgnore = $this->getBooleanAttribute( + $document->documentElement, + 'disableCodeCoverageIgnore', + false, + ); + + $includeUncoveredFiles = true; + $processUncoveredFiles = false; + + $element = $this->element($xpath, 'filter/whitelist'); + + if ($element) { + if ($element->hasAttribute('addUncoveredFilesFromWhitelist')) { + $includeUncoveredFiles = (bool) $this->getBoolean( + (string) $element->getAttribute('addUncoveredFilesFromWhitelist'), + true, + ); + } + + if ($element->hasAttribute('processUncoveredFilesFromWhitelist')) { + $processUncoveredFiles = (bool) $this->getBoolean( + (string) $element->getAttribute('processUncoveredFilesFromWhitelist'), + false, + ); + } + } + + $clover = null; + $cobertura = null; + $crap4j = null; + $html = null; + $php = null; + $text = null; + $xml = null; + + foreach ($xpath->query('logging/log') as $log) { + assert($log instanceof DOMElement); + + $type = (string) $log->getAttribute('type'); + $target = (string) $log->getAttribute('target'); + + if (!$target) { + continue; + } + + $target = $this->toAbsolutePath($filename, $target); + + switch ($type) { + case 'coverage-clover': + $clover = new Clover( + new File($target), + ); + + break; + + case 'coverage-cobertura': + $cobertura = new Cobertura( + new File($target), + ); + + break; + + case 'coverage-crap4j': + $crap4j = new Crap4j( + new File($target), + $this->getIntegerAttribute($log, 'threshold', 30), + ); + + break; + + case 'coverage-html': + $html = new CodeCoverageHtml( + new Directory($target), + $this->getIntegerAttribute($log, 'lowUpperBound', 50), + $this->getIntegerAttribute($log, 'highLowerBound', 90), + ); + + break; + + case 'coverage-php': + $php = new CodeCoveragePhp( + new File($target), + ); + + break; + + case 'coverage-text': + $text = new CodeCoverageText( + new File($target), + $this->getBooleanAttribute($log, 'showUncoveredFiles', false), + $this->getBooleanAttribute($log, 'showOnlySummary', false), + ); + + break; + + case 'coverage-xml': + $xml = new CodeCoverageXml( + new Directory($target), + ); + + break; + } + } + + return new CodeCoverage( + null, + $this->readFilterDirectories($filename, $xpath, 'filter/whitelist/directory'), + $this->readFilterFiles($filename, $xpath, 'filter/whitelist/file'), + $this->readFilterDirectories($filename, $xpath, 'filter/whitelist/exclude/directory'), + $this->readFilterFiles($filename, $xpath, 'filter/whitelist/exclude/file'), + false, + $includeUncoveredFiles, + $processUncoveredFiles, + $ignoreDeprecatedCodeUnits, + $disableCodeCoverageIgnore, + $clover, + $cobertura, + $crap4j, + $html, + $php, + $text, + $xml, + ); + } + + /** + * If $value is 'false' or 'true', this returns the value that $value represents. + * Otherwise, returns $default, which may be a string in rare cases. + * + * @see \PHPUnit\TextUI\XmlConfigurationTest::testPHPConfigurationIsReadCorrectly + * + * @param bool|string $default + * + * @return bool|string + */ + private function getBoolean(string $value, $default) + { + if (strtolower($value) === 'false') { + return false; + } + + if (strtolower($value) === 'true') { + return true; + } + + return $default; + } + + private function readFilterDirectories(string $filename, DOMXPath $xpath, string $query): FilterDirectoryCollection + { + $directories = []; + + foreach ($xpath->query($query) as $directoryNode) { + assert($directoryNode instanceof DOMElement); + + $directoryPath = (string) $directoryNode->textContent; + + if (!$directoryPath) { + continue; + } + + $directories[] = new FilterDirectory( + $this->toAbsolutePath($filename, $directoryPath), + $directoryNode->hasAttribute('prefix') ? (string) $directoryNode->getAttribute('prefix') : '', + $directoryNode->hasAttribute('suffix') ? (string) $directoryNode->getAttribute('suffix') : '.php', + $directoryNode->hasAttribute('group') ? (string) $directoryNode->getAttribute('group') : 'DEFAULT', + ); + } + + return FilterDirectoryCollection::fromArray($directories); + } + + private function readFilterFiles(string $filename, DOMXPath $xpath, string $query): FileCollection + { + $files = []; + + foreach ($xpath->query($query) as $file) { + assert($file instanceof DOMNode); + + $filePath = (string) $file->textContent; + + if ($filePath) { + $files[] = new File($this->toAbsolutePath($filename, $filePath)); + } + } + + return FileCollection::fromArray($files); + } + + private function groups(DOMXPath $xpath): Groups + { + return $this->parseGroupConfiguration($xpath, 'groups'); + } + + private function testdoxGroups(DOMXPath $xpath): Groups + { + return $this->parseGroupConfiguration($xpath, 'testdoxGroups'); + } + + private function parseGroupConfiguration(DOMXPath $xpath, string $root): Groups + { + $include = []; + $exclude = []; + + foreach ($xpath->query($root . '/include/group') as $group) { + assert($group instanceof DOMNode); + + $include[] = new Group((string) $group->textContent); + } + + foreach ($xpath->query($root . '/exclude/group') as $group) { + assert($group instanceof DOMNode); + + $exclude[] = new Group((string) $group->textContent); + } + + return new Groups( + GroupCollection::fromArray($include), + GroupCollection::fromArray($exclude), + ); + } + + private function listeners(string $filename, DOMXPath $xpath): ExtensionCollection + { + $listeners = []; + + foreach ($xpath->query('listeners/listener') as $listener) { + assert($listener instanceof DOMElement); + + $listeners[] = $this->getElementConfigurationParameters($filename, $listener); + } + + return ExtensionCollection::fromArray($listeners); + } + + private function getBooleanAttribute(DOMElement $element, string $attribute, bool $default): bool + { + if (!$element->hasAttribute($attribute)) { + return $default; + } + + return (bool) $this->getBoolean( + (string) $element->getAttribute($attribute), + false, + ); + } + + private function getIntegerAttribute(DOMElement $element, string $attribute, int $default): int + { + if (!$element->hasAttribute($attribute)) { + return $default; + } + + return $this->getInteger( + (string) $element->getAttribute($attribute), + $default, + ); + } + + private function getStringAttribute(DOMElement $element, string $attribute): ?string + { + if (!$element->hasAttribute($attribute)) { + return null; + } + + return (string) $element->getAttribute($attribute); + } + + private function getInteger(string $value, int $default): int + { + if (is_numeric($value)) { + return (int) $value; + } + + return $default; + } + + private function php(string $filename, DOMXPath $xpath): Php + { + $includePaths = []; + + foreach ($xpath->query('php/includePath') as $includePath) { + assert($includePath instanceof DOMNode); + + $path = (string) $includePath->textContent; + + if ($path) { + $includePaths[] = new Directory($this->toAbsolutePath($filename, $path)); + } + } + + $iniSettings = []; + + foreach ($xpath->query('php/ini') as $ini) { + assert($ini instanceof DOMElement); + + $iniSettings[] = new IniSetting( + (string) $ini->getAttribute('name'), + (string) $ini->getAttribute('value'), + ); + } + + $constants = []; + + foreach ($xpath->query('php/const') as $const) { + assert($const instanceof DOMElement); + + $value = (string) $const->getAttribute('value'); + + $constants[] = new Constant( + (string) $const->getAttribute('name'), + $this->getBoolean($value, $value), + ); + } + + $variables = [ + 'var' => [], + 'env' => [], + 'post' => [], + 'get' => [], + 'cookie' => [], + 'server' => [], + 'files' => [], + 'request' => [], + ]; + + foreach (['var', 'env', 'post', 'get', 'cookie', 'server', 'files', 'request'] as $array) { + foreach ($xpath->query('php/' . $array) as $var) { + assert($var instanceof DOMElement); + + $name = (string) $var->getAttribute('name'); + $value = (string) $var->getAttribute('value'); + $force = false; + $verbatim = false; + + if ($var->hasAttribute('force')) { + $force = (bool) $this->getBoolean($var->getAttribute('force'), false); + } + + if ($var->hasAttribute('verbatim')) { + $verbatim = $this->getBoolean($var->getAttribute('verbatim'), false); + } + + if (!$verbatim) { + $value = $this->getBoolean($value, $value); + } + + $variables[$array][] = new Variable($name, $value, $force); + } + } + + return new Php( + DirectoryCollection::fromArray($includePaths), + IniSettingCollection::fromArray($iniSettings), + ConstantCollection::fromArray($constants), + VariableCollection::fromArray($variables['var']), + VariableCollection::fromArray($variables['env']), + VariableCollection::fromArray($variables['post']), + VariableCollection::fromArray($variables['get']), + VariableCollection::fromArray($variables['cookie']), + VariableCollection::fromArray($variables['server']), + VariableCollection::fromArray($variables['files']), + VariableCollection::fromArray($variables['request']), + ); + } + + private function phpunit(string $filename, DOMDocument $document): PHPUnit + { + $executionOrder = TestSuiteSorter::ORDER_DEFAULT; + $defectsFirst = false; + $resolveDependencies = $this->getBooleanAttribute($document->documentElement, 'resolveDependencies', true); + + if ($document->documentElement->hasAttribute('executionOrder')) { + foreach (explode(',', $document->documentElement->getAttribute('executionOrder')) as $order) { + switch ($order) { + case 'default': + $executionOrder = TestSuiteSorter::ORDER_DEFAULT; + $defectsFirst = false; + $resolveDependencies = true; + + break; + + case 'depends': + $resolveDependencies = true; + + break; + + case 'no-depends': + $resolveDependencies = false; + + break; + + case 'defects': + $defectsFirst = true; + + break; + + case 'duration': + $executionOrder = TestSuiteSorter::ORDER_DURATION; + + break; + + case 'random': + $executionOrder = TestSuiteSorter::ORDER_RANDOMIZED; + + break; + + case 'reverse': + $executionOrder = TestSuiteSorter::ORDER_REVERSED; + + break; + + case 'size': + $executionOrder = TestSuiteSorter::ORDER_SIZE; + + break; + } + } + } + + $printerClass = $this->getStringAttribute($document->documentElement, 'printerClass'); + $testdox = $this->getBooleanAttribute($document->documentElement, 'testdox', false); + $conflictBetweenPrinterClassAndTestdox = false; + + if ($testdox) { + if ($printerClass !== null) { + $conflictBetweenPrinterClassAndTestdox = true; + } + + $printerClass = CliTestDoxPrinter::class; + } + + $cacheResultFile = $this->getStringAttribute($document->documentElement, 'cacheResultFile'); + + if ($cacheResultFile !== null) { + $cacheResultFile = $this->toAbsolutePath($filename, $cacheResultFile); + } + + $bootstrap = $this->getStringAttribute($document->documentElement, 'bootstrap'); + + if ($bootstrap !== null) { + $bootstrap = $this->toAbsolutePath($filename, $bootstrap); + } + + $extensionsDirectory = $this->getStringAttribute($document->documentElement, 'extensionsDirectory'); + + if ($extensionsDirectory !== null) { + $extensionsDirectory = $this->toAbsolutePath($filename, $extensionsDirectory); + } + + $testSuiteLoaderFile = $this->getStringAttribute($document->documentElement, 'testSuiteLoaderFile'); + + if ($testSuiteLoaderFile !== null) { + $testSuiteLoaderFile = $this->toAbsolutePath($filename, $testSuiteLoaderFile); + } + + $printerFile = $this->getStringAttribute($document->documentElement, 'printerFile'); + + if ($printerFile !== null) { + $printerFile = $this->toAbsolutePath($filename, $printerFile); + } + + return new PHPUnit( + $this->getBooleanAttribute($document->documentElement, 'cacheResult', true), + $cacheResultFile, + $this->getColumns($document), + $this->getColors($document), + $this->getBooleanAttribute($document->documentElement, 'stderr', false), + $this->getBooleanAttribute($document->documentElement, 'noInteraction', false), + $this->getBooleanAttribute($document->documentElement, 'verbose', false), + $this->getBooleanAttribute($document->documentElement, 'reverseDefectList', false), + $this->getBooleanAttribute($document->documentElement, 'convertDeprecationsToExceptions', false), + $this->getBooleanAttribute($document->documentElement, 'convertErrorsToExceptions', true), + $this->getBooleanAttribute($document->documentElement, 'convertNoticesToExceptions', true), + $this->getBooleanAttribute($document->documentElement, 'convertWarningsToExceptions', true), + $this->getBooleanAttribute($document->documentElement, 'forceCoversAnnotation', false), + $bootstrap, + $this->getBooleanAttribute($document->documentElement, 'processIsolation', false), + $this->getBooleanAttribute($document->documentElement, 'failOnEmptyTestSuite', false), + $this->getBooleanAttribute($document->documentElement, 'failOnIncomplete', false), + $this->getBooleanAttribute($document->documentElement, 'failOnRisky', false), + $this->getBooleanAttribute($document->documentElement, 'failOnSkipped', false), + $this->getBooleanAttribute($document->documentElement, 'failOnWarning', false), + $this->getBooleanAttribute($document->documentElement, 'stopOnDefect', false), + $this->getBooleanAttribute($document->documentElement, 'stopOnError', false), + $this->getBooleanAttribute($document->documentElement, 'stopOnFailure', false), + $this->getBooleanAttribute($document->documentElement, 'stopOnWarning', false), + $this->getBooleanAttribute($document->documentElement, 'stopOnIncomplete', false), + $this->getBooleanAttribute($document->documentElement, 'stopOnRisky', false), + $this->getBooleanAttribute($document->documentElement, 'stopOnSkipped', false), + $extensionsDirectory, + $this->getStringAttribute($document->documentElement, 'testSuiteLoaderClass'), + $testSuiteLoaderFile, + $printerClass, + $printerFile, + $this->getBooleanAttribute($document->documentElement, 'beStrictAboutChangesToGlobalState', false), + $this->getBooleanAttribute($document->documentElement, 'beStrictAboutOutputDuringTests', false), + $this->getBooleanAttribute($document->documentElement, 'beStrictAboutResourceUsageDuringSmallTests', false), + $this->getBooleanAttribute($document->documentElement, 'beStrictAboutTestsThatDoNotTestAnything', true), + $this->getBooleanAttribute($document->documentElement, 'beStrictAboutTodoAnnotatedTests', false), + $this->getBooleanAttribute($document->documentElement, 'beStrictAboutCoversAnnotation', false), + $this->getBooleanAttribute($document->documentElement, 'enforceTimeLimit', false), + $this->getIntegerAttribute($document->documentElement, 'defaultTimeLimit', 1), + $this->getIntegerAttribute($document->documentElement, 'timeoutForSmallTests', 1), + $this->getIntegerAttribute($document->documentElement, 'timeoutForMediumTests', 10), + $this->getIntegerAttribute($document->documentElement, 'timeoutForLargeTests', 60), + $this->getStringAttribute($document->documentElement, 'defaultTestSuite'), + $executionOrder, + $resolveDependencies, + $defectsFirst, + $this->getBooleanAttribute($document->documentElement, 'backupGlobals', false), + $this->getBooleanAttribute($document->documentElement, 'backupStaticAttributes', false), + $this->getBooleanAttribute($document->documentElement, 'registerMockObjectsFromTestArgumentsRecursively', false), + $conflictBetweenPrinterClassAndTestdox, + ); + } + + private function getColors(DOMDocument $document): string + { + $colors = DefaultResultPrinter::COLOR_DEFAULT; + + if ($document->documentElement->hasAttribute('colors')) { + /* only allow boolean for compatibility with previous versions + 'always' only allowed from command line */ + if ($this->getBoolean($document->documentElement->getAttribute('colors'), false)) { + $colors = DefaultResultPrinter::COLOR_AUTO; + } else { + $colors = DefaultResultPrinter::COLOR_NEVER; + } + } + + return $colors; + } + + /** + * @return int|string + */ + private function getColumns(DOMDocument $document) + { + $columns = 80; + + if ($document->documentElement->hasAttribute('columns')) { + $columns = (string) $document->documentElement->getAttribute('columns'); + + if ($columns !== 'max') { + $columns = $this->getInteger($columns, 80); + } + } + + return $columns; + } + + private function testSuite(string $filename, DOMXPath $xpath): TestSuiteCollection + { + $testSuites = []; + + foreach ($this->getTestSuiteElements($xpath) as $element) { + $exclude = []; + + foreach ($element->getElementsByTagName('exclude') as $excludeNode) { + $excludeFile = (string) $excludeNode->textContent; + + if ($excludeFile) { + $exclude[] = new File($this->toAbsolutePath($filename, $excludeFile)); + } + } + + $directories = []; + + foreach ($element->getElementsByTagName('directory') as $directoryNode) { + assert($directoryNode instanceof DOMElement); + + $directory = (string) $directoryNode->textContent; + + if (empty($directory)) { + continue; + } + + $prefix = ''; + + if ($directoryNode->hasAttribute('prefix')) { + $prefix = (string) $directoryNode->getAttribute('prefix'); + } + + $suffix = 'Test.php'; + + if ($directoryNode->hasAttribute('suffix')) { + $suffix = (string) $directoryNode->getAttribute('suffix'); + } + + $phpVersion = PHP_VERSION; + + if ($directoryNode->hasAttribute('phpVersion')) { + $phpVersion = (string) $directoryNode->getAttribute('phpVersion'); + } + + $phpVersionOperator = new VersionComparisonOperator('>='); + + if ($directoryNode->hasAttribute('phpVersionOperator')) { + $phpVersionOperator = new VersionComparisonOperator((string) $directoryNode->getAttribute('phpVersionOperator')); + } + + $directories[] = new TestDirectory( + $this->toAbsolutePath($filename, $directory), + $prefix, + $suffix, + $phpVersion, + $phpVersionOperator, + ); + } + + $files = []; + + foreach ($element->getElementsByTagName('file') as $fileNode) { + assert($fileNode instanceof DOMElement); + + $file = (string) $fileNode->textContent; + + if (empty($file)) { + continue; + } + + $phpVersion = PHP_VERSION; + + if ($fileNode->hasAttribute('phpVersion')) { + $phpVersion = (string) $fileNode->getAttribute('phpVersion'); + } + + $phpVersionOperator = new VersionComparisonOperator('>='); + + if ($fileNode->hasAttribute('phpVersionOperator')) { + $phpVersionOperator = new VersionComparisonOperator((string) $fileNode->getAttribute('phpVersionOperator')); + } + + $files[] = new TestFile( + $this->toAbsolutePath($filename, $file), + $phpVersion, + $phpVersionOperator, + ); + } + + $testSuites[] = new TestSuiteConfiguration( + (string) $element->getAttribute('name'), + TestDirectoryCollection::fromArray($directories), + TestFileCollection::fromArray($files), + FileCollection::fromArray($exclude), + ); + } + + return TestSuiteCollection::fromArray($testSuites); + } + + /** + * @return DOMElement[] + */ + private function getTestSuiteElements(DOMXPath $xpath): array + { + /** @var DOMElement[] $elements */ + $elements = []; + + $testSuiteNodes = $xpath->query('testsuites/testsuite'); + + if ($testSuiteNodes->length === 0) { + $testSuiteNodes = $xpath->query('testsuite'); + } + + if ($testSuiteNodes->length === 1) { + $element = $testSuiteNodes->item(0); + + assert($element instanceof DOMElement); + + $elements[] = $element; + } else { + foreach ($testSuiteNodes as $testSuiteNode) { + assert($testSuiteNode instanceof DOMElement); + + $elements[] = $testSuiteNode; + } + } + + return $elements; + } + + private function element(DOMXPath $xpath, string $element): ?DOMElement + { + $nodes = $xpath->query($element); + + if ($nodes->length === 1) { + $node = $nodes->item(0); + + assert($node instanceof DOMElement); + + return $node; + } + + return null; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/Junit.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/Junit.php new file mode 100644 index 00000000..9fca1852 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/Junit.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\Logging; + +use PHPUnit\TextUI\XmlConfiguration\File; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Junit +{ + /** + * @var File + */ + private $target; + + public function __construct(File $target) + { + $this->target = $target; + } + + public function target(): File + { + return $this->target; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/Logging.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/Logging.php new file mode 100644 index 00000000..bce03019 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/Logging.php @@ -0,0 +1,147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\Logging; + +use PHPUnit\TextUI\XmlConfiguration\Exception; +use PHPUnit\TextUI\XmlConfiguration\Logging\TestDox\Html as TestDoxHtml; +use PHPUnit\TextUI\XmlConfiguration\Logging\TestDox\Text as TestDoxText; +use PHPUnit\TextUI\XmlConfiguration\Logging\TestDox\Xml as TestDoxXml; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Logging +{ + /** + * @var ?Junit + */ + private $junit; + + /** + * @var ?Text + */ + private $text; + + /** + * @var ?TeamCity + */ + private $teamCity; + + /** + * @var ?TestDoxHtml + */ + private $testDoxHtml; + + /** + * @var ?TestDoxText + */ + private $testDoxText; + + /** + * @var ?TestDoxXml + */ + private $testDoxXml; + + public function __construct(?Junit $junit, ?Text $text, ?TeamCity $teamCity, ?TestDoxHtml $testDoxHtml, ?TestDoxText $testDoxText, ?TestDoxXml $testDoxXml) + { + $this->junit = $junit; + $this->text = $text; + $this->teamCity = $teamCity; + $this->testDoxHtml = $testDoxHtml; + $this->testDoxText = $testDoxText; + $this->testDoxXml = $testDoxXml; + } + + public function hasJunit(): bool + { + return $this->junit !== null; + } + + public function junit(): Junit + { + if ($this->junit === null) { + throw new Exception('Logger "JUnit XML" is not configured'); + } + + return $this->junit; + } + + public function hasText(): bool + { + return $this->text !== null; + } + + public function text(): Text + { + if ($this->text === null) { + throw new Exception('Logger "Text" is not configured'); + } + + return $this->text; + } + + public function hasTeamCity(): bool + { + return $this->teamCity !== null; + } + + public function teamCity(): TeamCity + { + if ($this->teamCity === null) { + throw new Exception('Logger "Team City" is not configured'); + } + + return $this->teamCity; + } + + public function hasTestDoxHtml(): bool + { + return $this->testDoxHtml !== null; + } + + public function testDoxHtml(): TestDoxHtml + { + if ($this->testDoxHtml === null) { + throw new Exception('Logger "TestDox HTML" is not configured'); + } + + return $this->testDoxHtml; + } + + public function hasTestDoxText(): bool + { + return $this->testDoxText !== null; + } + + public function testDoxText(): TestDoxText + { + if ($this->testDoxText === null) { + throw new Exception('Logger "TestDox Text" is not configured'); + } + + return $this->testDoxText; + } + + public function hasTestDoxXml(): bool + { + return $this->testDoxXml !== null; + } + + public function testDoxXml(): TestDoxXml + { + if ($this->testDoxXml === null) { + throw new Exception('Logger "TestDox XML" is not configured'); + } + + return $this->testDoxXml; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/TeamCity.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/TeamCity.php new file mode 100644 index 00000000..804a7ea6 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/TeamCity.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\Logging; + +use PHPUnit\TextUI\XmlConfiguration\File; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class TeamCity +{ + /** + * @var File + */ + private $target; + + public function __construct(File $target) + { + $this->target = $target; + } + + public function target(): File + { + return $this->target; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/TestDox/Html.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/TestDox/Html.php new file mode 100644 index 00000000..5b198352 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/TestDox/Html.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\Logging\TestDox; + +use PHPUnit\TextUI\XmlConfiguration\File; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Html +{ + /** + * @var File + */ + private $target; + + public function __construct(File $target) + { + $this->target = $target; + } + + public function target(): File + { + return $this->target; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/TestDox/Text.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/TestDox/Text.php new file mode 100644 index 00000000..5c742d3a --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/TestDox/Text.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\Logging\TestDox; + +use PHPUnit\TextUI\XmlConfiguration\File; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Text +{ + /** + * @var File + */ + private $target; + + public function __construct(File $target) + { + $this->target = $target; + } + + public function target(): File + { + return $this->target; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/TestDox/Xml.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/TestDox/Xml.php new file mode 100644 index 00000000..92dd3b7b --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/TestDox/Xml.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\Logging\TestDox; + +use PHPUnit\TextUI\XmlConfiguration\File; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Xml +{ + /** + * @var File + */ + private $target; + + public function __construct(File $target) + { + $this->target = $target; + } + + public function target(): File + { + return $this->target; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/Text.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/Text.php new file mode 100644 index 00000000..fd37942f --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/Text.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\Logging; + +use PHPUnit\TextUI\XmlConfiguration\File; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Text +{ + /** + * @var File + */ + private $target; + + public function __construct(File $target) + { + $this->target = $target; + } + + public function target(): File + { + return $this->target; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/MigrationBuilder.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/MigrationBuilder.php new file mode 100644 index 00000000..a6b26423 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/MigrationBuilder.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function version_compare; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MigrationBuilder +{ + private const AVAILABLE_MIGRATIONS = [ + '8.5' => [ + RemoveLogTypes::class, + ], + + '9.2' => [ + RemoveCacheTokensAttribute::class, + IntroduceCoverageElement::class, + MoveAttributesFromRootToCoverage::class, + MoveAttributesFromFilterWhitelistToCoverage::class, + MoveWhitelistIncludesToCoverage::class, + MoveWhitelistExcludesToCoverage::class, + RemoveEmptyFilter::class, + CoverageCloverToReport::class, + CoverageCrap4jToReport::class, + CoverageHtmlToReport::class, + CoveragePhpToReport::class, + CoverageTextToReport::class, + CoverageXmlToReport::class, + ConvertLogTypes::class, + UpdateSchemaLocationTo93::class, + ], + ]; + + /** + * @throws MigrationBuilderException + */ + public function build(string $fromVersion): array + { + $stack = []; + + foreach (self::AVAILABLE_MIGRATIONS as $version => $migrations) { + if (version_compare($version, $fromVersion, '<')) { + continue; + } + + foreach ($migrations as $migration) { + $stack[] = new $migration; + } + } + + return $stack; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/MigrationBuilderException.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/MigrationBuilderException.php new file mode 100644 index 00000000..3d3c767a --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/MigrationBuilderException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use RuntimeException; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MigrationBuilderException extends RuntimeException implements \PHPUnit\Exception +{ +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/MigrationException.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/MigrationException.php new file mode 100644 index 00000000..f92b2db3 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/MigrationException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use RuntimeException; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MigrationException extends RuntimeException implements \PHPUnit\Exception +{ +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/ConvertLogTypes.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/ConvertLogTypes.php new file mode 100644 index 00000000..697bbe08 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/ConvertLogTypes.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMDocument; +use DOMElement; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ConvertLogTypes implements Migration +{ + public function migrate(DOMDocument $document): void + { + $logging = $document->getElementsByTagName('logging')->item(0); + + if (!$logging instanceof DOMElement) { + return; + } + $types = [ + 'junit' => 'junit', + 'teamcity' => 'teamcity', + 'testdox-html' => 'testdoxHtml', + 'testdox-text' => 'testdoxText', + 'testdox-xml' => 'testdoxXml', + 'plain' => 'text', + ]; + + $logNodes = []; + + foreach ($logging->getElementsByTagName('log') as $logNode) { + if (!isset($types[$logNode->getAttribute('type')])) { + continue; + } + + $logNodes[] = $logNode; + } + + foreach ($logNodes as $oldNode) { + $newLogNode = $document->createElement($types[$oldNode->getAttribute('type')]); + $newLogNode->setAttribute('outputFile', $oldNode->getAttribute('target')); + + $logging->replaceChild($newLogNode, $oldNode); + } + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageCloverToReport.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageCloverToReport.php new file mode 100644 index 00000000..5f1522b9 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageCloverToReport.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMElement; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class CoverageCloverToReport extends LogToReportMigration +{ + protected function forType(): string + { + return 'coverage-clover'; + } + + protected function toReportFormat(DOMElement $logNode): DOMElement + { + $clover = $logNode->ownerDocument->createElement('clover'); + $clover->setAttribute('outputFile', $logNode->getAttribute('target')); + + return $clover; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageCrap4jToReport.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageCrap4jToReport.php new file mode 100644 index 00000000..afbaaec1 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageCrap4jToReport.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMElement; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class CoverageCrap4jToReport extends LogToReportMigration +{ + protected function forType(): string + { + return 'coverage-crap4j'; + } + + protected function toReportFormat(DOMElement $logNode): DOMElement + { + $crap4j = $logNode->ownerDocument->createElement('crap4j'); + $crap4j->setAttribute('outputFile', $logNode->getAttribute('target')); + + $this->migrateAttributes($logNode, $crap4j, ['threshold']); + + return $crap4j; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageHtmlToReport.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageHtmlToReport.php new file mode 100644 index 00000000..7e12095b --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageHtmlToReport.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMElement; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class CoverageHtmlToReport extends LogToReportMigration +{ + protected function forType(): string + { + return 'coverage-html'; + } + + protected function toReportFormat(DOMElement $logNode): DOMElement + { + $html = $logNode->ownerDocument->createElement('html'); + $html->setAttribute('outputDirectory', $logNode->getAttribute('target')); + + $this->migrateAttributes($logNode, $html, ['lowUpperBound', 'highLowerBound']); + + return $html; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoveragePhpToReport.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoveragePhpToReport.php new file mode 100644 index 00000000..bfa10030 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoveragePhpToReport.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMElement; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class CoveragePhpToReport extends LogToReportMigration +{ + protected function forType(): string + { + return 'coverage-php'; + } + + protected function toReportFormat(DOMElement $logNode): DOMElement + { + $php = $logNode->ownerDocument->createElement('php'); + $php->setAttribute('outputFile', $logNode->getAttribute('target')); + + return $php; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageTextToReport.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageTextToReport.php new file mode 100644 index 00000000..063d8df0 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageTextToReport.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMElement; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class CoverageTextToReport extends LogToReportMigration +{ + protected function forType(): string + { + return 'coverage-text'; + } + + protected function toReportFormat(DOMElement $logNode): DOMElement + { + $text = $logNode->ownerDocument->createElement('text'); + $text->setAttribute('outputFile', $logNode->getAttribute('target')); + + $this->migrateAttributes($logNode, $text, ['showUncoveredFiles', 'showOnlySummary']); + + return $text; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageXmlToReport.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageXmlToReport.php new file mode 100644 index 00000000..480d7777 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageXmlToReport.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMElement; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class CoverageXmlToReport extends LogToReportMigration +{ + protected function forType(): string + { + return 'coverage-xml'; + } + + protected function toReportFormat(DOMElement $logNode): DOMElement + { + $xml = $logNode->ownerDocument->createElement('xml'); + $xml->setAttribute('outputDirectory', $logNode->getAttribute('target')); + + return $xml; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/IntroduceCoverageElement.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/IntroduceCoverageElement.php new file mode 100644 index 00000000..de52857e --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/IntroduceCoverageElement.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMDocument; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class IntroduceCoverageElement implements Migration +{ + public function migrate(DOMDocument $document): void + { + $coverage = $document->createElement('coverage'); + + $document->documentElement->insertBefore( + $coverage, + $document->documentElement->firstChild, + ); + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/LogToReportMigration.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/LogToReportMigration.php new file mode 100644 index 00000000..c07de0ec --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/LogToReportMigration.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function sprintf; +use DOMDocument; +use DOMElement; +use DOMXPath; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract class LogToReportMigration implements Migration +{ + /** + * @throws MigrationException + */ + public function migrate(DOMDocument $document): void + { + $coverage = $document->getElementsByTagName('coverage')->item(0); + + if (!$coverage instanceof DOMElement) { + throw new MigrationException('Unexpected state - No coverage element'); + } + + $logNode = $this->findLogNode($document); + + if ($logNode === null) { + return; + } + + $reportChild = $this->toReportFormat($logNode); + + $report = $coverage->getElementsByTagName('report')->item(0); + + if ($report === null) { + $report = $coverage->appendChild($document->createElement('report')); + } + + $report->appendChild($reportChild); + $logNode->parentNode->removeChild($logNode); + } + + protected function migrateAttributes(DOMElement $src, DOMElement $dest, array $attributes): void + { + foreach ($attributes as $attr) { + if (!$src->hasAttribute($attr)) { + continue; + } + + $dest->setAttribute($attr, $src->getAttribute($attr)); + $src->removeAttribute($attr); + } + } + + abstract protected function forType(): string; + + abstract protected function toReportFormat(DOMElement $logNode): DOMElement; + + private function findLogNode(DOMDocument $document): ?DOMElement + { + $logNode = (new DOMXPath($document))->query( + sprintf('//logging/log[@type="%s"]', $this->forType()), + )->item(0); + + if (!$logNode instanceof DOMElement) { + return null; + } + + return $logNode; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/Migration.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/Migration.php new file mode 100644 index 00000000..fa4092a9 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/Migration.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMDocument; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface Migration +{ + public function migrate(DOMDocument $document): void; +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/MoveAttributesFromFilterWhitelistToCoverage.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/MoveAttributesFromFilterWhitelistToCoverage.php new file mode 100644 index 00000000..a7aab5e5 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/MoveAttributesFromFilterWhitelistToCoverage.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMDocument; +use DOMElement; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MoveAttributesFromFilterWhitelistToCoverage implements Migration +{ + /** + * @throws MigrationException + */ + public function migrate(DOMDocument $document): void + { + $whitelist = $document->getElementsByTagName('whitelist')->item(0); + + if (!$whitelist) { + return; + } + + $coverage = $document->getElementsByTagName('coverage')->item(0); + + if (!$coverage instanceof DOMElement) { + throw new MigrationException('Unexpected state - No coverage element'); + } + + $map = [ + 'addUncoveredFilesFromWhitelist' => 'includeUncoveredFiles', + 'processUncoveredFilesFromWhitelist' => 'processUncoveredFiles', + ]; + + foreach ($map as $old => $new) { + if (!$whitelist->hasAttribute($old)) { + continue; + } + + $coverage->setAttribute($new, $whitelist->getAttribute($old)); + $whitelist->removeAttribute($old); + } + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/MoveAttributesFromRootToCoverage.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/MoveAttributesFromRootToCoverage.php new file mode 100644 index 00000000..b86b259c --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/MoveAttributesFromRootToCoverage.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMDocument; +use DOMElement; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MoveAttributesFromRootToCoverage implements Migration +{ + /** + * @throws MigrationException + */ + public function migrate(DOMDocument $document): void + { + $map = [ + 'disableCodeCoverageIgnore' => 'disableCodeCoverageIgnore', + 'ignoreDeprecatedCodeUnitsFromCodeCoverage' => 'ignoreDeprecatedCodeUnits', + ]; + + $root = $document->documentElement; + + $coverage = $document->getElementsByTagName('coverage')->item(0); + + if (!$coverage instanceof DOMElement) { + throw new MigrationException('Unexpected state - No coverage element'); + } + + foreach ($map as $old => $new) { + if (!$root->hasAttribute($old)) { + continue; + } + + $coverage->setAttribute($new, $root->getAttribute($old)); + $root->removeAttribute($old); + } + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/MoveWhitelistExcludesToCoverage.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/MoveWhitelistExcludesToCoverage.php new file mode 100644 index 00000000..17d5f4db --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/MoveWhitelistExcludesToCoverage.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use function in_array; +use DOMDocument; +use DOMElement; +use PHPUnit\Util\Xml\SnapshotNodeList; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MoveWhitelistExcludesToCoverage implements Migration +{ + /** + * @throws MigrationException + */ + public function migrate(DOMDocument $document): void + { + $whitelist = $document->getElementsByTagName('whitelist')->item(0); + + if ($whitelist === null) { + return; + } + + $excludeNodes = SnapshotNodeList::fromNodeList($whitelist->getElementsByTagName('exclude')); + + if ($excludeNodes->count() === 0) { + return; + } + + $coverage = $document->getElementsByTagName('coverage')->item(0); + + if (!$coverage instanceof DOMElement) { + throw new MigrationException('Unexpected state - No coverage element'); + } + + $targetExclude = $coverage->getElementsByTagName('exclude')->item(0); + + if ($targetExclude === null) { + $targetExclude = $coverage->appendChild( + $document->createElement('exclude'), + ); + } + + foreach ($excludeNodes as $excludeNode) { + assert($excludeNode instanceof DOMElement); + + foreach (SnapshotNodeList::fromNodeList($excludeNode->childNodes) as $child) { + if (!$child instanceof DOMElement || !in_array($child->nodeName, ['directory', 'file'], true)) { + continue; + } + + $targetExclude->appendChild($child); + } + + if ($excludeNode->getElementsByTagName('*')->count() !== 0) { + throw new MigrationException('Dangling child elements in exclude found.'); + } + + $whitelist->removeChild($excludeNode); + } + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/MoveWhitelistIncludesToCoverage.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/MoveWhitelistIncludesToCoverage.php new file mode 100644 index 00000000..c75a6d84 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/MoveWhitelistIncludesToCoverage.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMDocument; +use DOMElement; +use PHPUnit\Util\Xml\SnapshotNodeList; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MoveWhitelistIncludesToCoverage implements Migration +{ + /** + * @throws MigrationException + */ + public function migrate(DOMDocument $document): void + { + $whitelist = $document->getElementsByTagName('whitelist')->item(0); + + if ($whitelist === null) { + return; + } + + $coverage = $document->getElementsByTagName('coverage')->item(0); + + if (!$coverage instanceof DOMElement) { + throw new MigrationException('Unexpected state - No coverage element'); + } + + $include = $document->createElement('include'); + $coverage->appendChild($include); + + foreach (SnapshotNodeList::fromNodeList($whitelist->childNodes) as $child) { + if (!$child instanceof DOMElement) { + continue; + } + + if (!($child->nodeName === 'directory' || $child->nodeName === 'file')) { + continue; + } + + $include->appendChild($child); + } + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/RemoveCacheTokensAttribute.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/RemoveCacheTokensAttribute.php new file mode 100644 index 00000000..0eec12ac --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/RemoveCacheTokensAttribute.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMDocument; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class RemoveCacheTokensAttribute implements Migration +{ + public function migrate(DOMDocument $document): void + { + $root = $document->documentElement; + + if ($root->hasAttribute('cacheTokens')) { + $root->removeAttribute('cacheTokens'); + } + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/RemoveEmptyFilter.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/RemoveEmptyFilter.php new file mode 100644 index 00000000..8f1a6d54 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/RemoveEmptyFilter.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function sprintf; +use DOMDocument; +use DOMElement; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class RemoveEmptyFilter implements Migration +{ + /** + * @throws MigrationException + */ + public function migrate(DOMDocument $document): void + { + $whitelist = $document->getElementsByTagName('whitelist')->item(0); + + if ($whitelist instanceof DOMElement) { + $this->ensureEmpty($whitelist); + $whitelist->parentNode->removeChild($whitelist); + } + + $filter = $document->getElementsByTagName('filter')->item(0); + + if ($filter instanceof DOMElement) { + $this->ensureEmpty($filter); + $filter->parentNode->removeChild($filter); + } + } + + /** + * @throws MigrationException + */ + private function ensureEmpty(DOMElement $element): void + { + if ($element->attributes->length > 0) { + throw new MigrationException(sprintf('%s element has unexpected attributes', $element->nodeName)); + } + + if ($element->getElementsByTagName('*')->length > 0) { + throw new MigrationException(sprintf('%s element has unexpected children', $element->nodeName)); + } + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/RemoveLogTypes.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/RemoveLogTypes.php new file mode 100644 index 00000000..962ff13c --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/RemoveLogTypes.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; +use PHPUnit\Util\Xml\SnapshotNodeList; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class RemoveLogTypes implements Migration +{ + public function migrate(DOMDocument $document): void + { + $logging = $document->getElementsByTagName('logging')->item(0); + + if (!$logging instanceof DOMElement) { + return; + } + + foreach (SnapshotNodeList::fromNodeList($logging->getElementsByTagName('log')) as $logNode) { + assert($logNode instanceof DOMElement); + + switch ($logNode->getAttribute('type')) { + case 'json': + case 'tap': + $logging->removeChild($logNode); + } + } + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/UpdateSchemaLocationTo93.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/UpdateSchemaLocationTo93.php new file mode 100644 index 00000000..ddcfcf07 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/UpdateSchemaLocationTo93.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMDocument; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class UpdateSchemaLocationTo93 implements Migration +{ + public function migrate(DOMDocument $document): void + { + $document->documentElement->setAttributeNS( + 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:noNamespaceSchemaLocation', + 'https://schema.phpunit.de/9.3/phpunit.xsd', + ); + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrator.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrator.php new file mode 100644 index 00000000..57bc9f2e --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrator.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function sprintf; +use PHPUnit\Util\Xml\Exception as XmlException; +use PHPUnit\Util\Xml\Loader as XmlLoader; +use PHPUnit\Util\Xml\SchemaDetector; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Migrator +{ + /** + * @throws Exception + * @throws MigrationBuilderException + * @throws MigrationException + * @throws XmlException + */ + public function migrate(string $filename): string + { + $origin = (new SchemaDetector)->detect($filename); + + if (!$origin->detected()) { + throw new Exception( + sprintf( + '"%s" is not a valid PHPUnit XML configuration file that can be migrated', + $filename, + ), + ); + } + + $configurationDocument = (new XmlLoader)->loadFile( + $filename, + false, + true, + true, + ); + + foreach ((new MigrationBuilder)->build($origin->version()) as $migration) { + $migration->migrate($configurationDocument); + } + + $configurationDocument->formatOutput = true; + $configurationDocument->preserveWhiteSpace = false; + + return $configurationDocument->saveXML(); + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/Constant.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/Constant.php new file mode 100644 index 00000000..6d4bc94c --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/Constant.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Constant +{ + /** + * @var string + */ + private $name; + + /** + * @var mixed + */ + private $value; + + public function __construct(string $name, $value) + { + $this->name = $name; + $this->value = $value; + } + + public function name(): string + { + return $this->name; + } + + public function value() + { + return $this->value; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/ConstantCollection.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/ConstantCollection.php new file mode 100644 index 00000000..440b0b0b --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/ConstantCollection.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function count; +use Countable; +use IteratorAggregate; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + * + * @template-implements IteratorAggregate + */ +final class ConstantCollection implements Countable, IteratorAggregate +{ + /** + * @var Constant[] + */ + private $constants; + + /** + * @param Constant[] $constants + */ + public static function fromArray(array $constants): self + { + return new self(...$constants); + } + + private function __construct(Constant ...$constants) + { + $this->constants = $constants; + } + + /** + * @return Constant[] + */ + public function asArray(): array + { + return $this->constants; + } + + public function count(): int + { + return count($this->constants); + } + + public function getIterator(): ConstantCollectionIterator + { + return new ConstantCollectionIterator($this); + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/ConstantCollectionIterator.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/ConstantCollectionIterator.php new file mode 100644 index 00000000..623de961 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/ConstantCollectionIterator.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function count; +use function iterator_count; +use Countable; +use Iterator; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @template-implements Iterator + */ +final class ConstantCollectionIterator implements Countable, Iterator +{ + /** + * @var Constant[] + */ + private $constants; + + /** + * @var int + */ + private $position; + + public function __construct(ConstantCollection $constants) + { + $this->constants = $constants->asArray(); + } + + public function count(): int + { + return iterator_count($this); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->constants); + } + + public function key(): int + { + return $this->position; + } + + public function current(): Constant + { + return $this->constants[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/IniSetting.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/IniSetting.php new file mode 100644 index 00000000..4786618d --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/IniSetting.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class IniSetting +{ + /** + * @var string + */ + private $name; + + /** + * @var string + */ + private $value; + + public function __construct(string $name, string $value) + { + $this->name = $name; + $this->value = $value; + } + + public function name(): string + { + return $this->name; + } + + public function value(): string + { + return $this->value; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/IniSettingCollection.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/IniSettingCollection.php new file mode 100644 index 00000000..28e40d93 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/IniSettingCollection.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function count; +use Countable; +use IteratorAggregate; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + * + * @template-implements IteratorAggregate + */ +final class IniSettingCollection implements Countable, IteratorAggregate +{ + /** + * @var IniSetting[] + */ + private $iniSettings; + + /** + * @param IniSetting[] $iniSettings + */ + public static function fromArray(array $iniSettings): self + { + return new self(...$iniSettings); + } + + private function __construct(IniSetting ...$iniSettings) + { + $this->iniSettings = $iniSettings; + } + + /** + * @return IniSetting[] + */ + public function asArray(): array + { + return $this->iniSettings; + } + + public function count(): int + { + return count($this->iniSettings); + } + + public function getIterator(): IniSettingCollectionIterator + { + return new IniSettingCollectionIterator($this); + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/IniSettingCollectionIterator.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/IniSettingCollectionIterator.php new file mode 100644 index 00000000..6c348b48 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/IniSettingCollectionIterator.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function count; +use function iterator_count; +use Countable; +use Iterator; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @template-implements Iterator + */ +final class IniSettingCollectionIterator implements Countable, Iterator +{ + /** + * @var IniSetting[] + */ + private $iniSettings; + + /** + * @var int + */ + private $position; + + public function __construct(IniSettingCollection $iniSettings) + { + $this->iniSettings = $iniSettings->asArray(); + } + + public function count(): int + { + return iterator_count($this); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->iniSettings); + } + + public function key(): int + { + return $this->position; + } + + public function current(): IniSetting + { + return $this->iniSettings[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/Php.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/Php.php new file mode 100644 index 00000000..c1e9c6fd --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/Php.php @@ -0,0 +1,143 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Php +{ + /** + * @var DirectoryCollection + */ + private $includePaths; + + /** + * @var IniSettingCollection + */ + private $iniSettings; + + /** + * @var ConstantCollection + */ + private $constants; + + /** + * @var VariableCollection + */ + private $globalVariables; + + /** + * @var VariableCollection + */ + private $envVariables; + + /** + * @var VariableCollection + */ + private $postVariables; + + /** + * @var VariableCollection + */ + private $getVariables; + + /** + * @var VariableCollection + */ + private $cookieVariables; + + /** + * @var VariableCollection + */ + private $serverVariables; + + /** + * @var VariableCollection + */ + private $filesVariables; + + /** + * @var VariableCollection + */ + private $requestVariables; + + public function __construct(DirectoryCollection $includePaths, IniSettingCollection $iniSettings, ConstantCollection $constants, VariableCollection $globalVariables, VariableCollection $envVariables, VariableCollection $postVariables, VariableCollection $getVariables, VariableCollection $cookieVariables, VariableCollection $serverVariables, VariableCollection $filesVariables, VariableCollection $requestVariables) + { + $this->includePaths = $includePaths; + $this->iniSettings = $iniSettings; + $this->constants = $constants; + $this->globalVariables = $globalVariables; + $this->envVariables = $envVariables; + $this->postVariables = $postVariables; + $this->getVariables = $getVariables; + $this->cookieVariables = $cookieVariables; + $this->serverVariables = $serverVariables; + $this->filesVariables = $filesVariables; + $this->requestVariables = $requestVariables; + } + + public function includePaths(): DirectoryCollection + { + return $this->includePaths; + } + + public function iniSettings(): IniSettingCollection + { + return $this->iniSettings; + } + + public function constants(): ConstantCollection + { + return $this->constants; + } + + public function globalVariables(): VariableCollection + { + return $this->globalVariables; + } + + public function envVariables(): VariableCollection + { + return $this->envVariables; + } + + public function postVariables(): VariableCollection + { + return $this->postVariables; + } + + public function getVariables(): VariableCollection + { + return $this->getVariables; + } + + public function cookieVariables(): VariableCollection + { + return $this->cookieVariables; + } + + public function serverVariables(): VariableCollection + { + return $this->serverVariables; + } + + public function filesVariables(): VariableCollection + { + return $this->filesVariables; + } + + public function requestVariables(): VariableCollection + { + return $this->requestVariables; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/PhpHandler.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/PhpHandler.php new file mode 100644 index 00000000..f5969945 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/PhpHandler.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use const PATH_SEPARATOR; +use function constant; +use function define; +use function defined; +use function getenv; +use function implode; +use function ini_get; +use function ini_set; +use function putenv; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class PhpHandler +{ + public function handle(Php $configuration): void + { + $this->handleIncludePaths($configuration->includePaths()); + $this->handleIniSettings($configuration->iniSettings()); + $this->handleConstants($configuration->constants()); + $this->handleGlobalVariables($configuration->globalVariables()); + $this->handleServerVariables($configuration->serverVariables()); + $this->handleEnvVariables($configuration->envVariables()); + $this->handleVariables('_POST', $configuration->postVariables()); + $this->handleVariables('_GET', $configuration->getVariables()); + $this->handleVariables('_COOKIE', $configuration->cookieVariables()); + $this->handleVariables('_FILES', $configuration->filesVariables()); + $this->handleVariables('_REQUEST', $configuration->requestVariables()); + } + + private function handleIncludePaths(DirectoryCollection $includePaths): void + { + if (!$includePaths->isEmpty()) { + $includePathsAsStrings = []; + + foreach ($includePaths as $includePath) { + $includePathsAsStrings[] = $includePath->path(); + } + + ini_set( + 'include_path', + implode(PATH_SEPARATOR, $includePathsAsStrings) . + PATH_SEPARATOR . + ini_get('include_path'), + ); + } + } + + private function handleIniSettings(IniSettingCollection $iniSettings): void + { + foreach ($iniSettings as $iniSetting) { + $value = $iniSetting->value(); + + if (defined($value)) { + $value = (string) constant($value); + } + + ini_set($iniSetting->name(), $value); + } + } + + private function handleConstants(ConstantCollection $constants): void + { + foreach ($constants as $constant) { + if (!defined($constant->name())) { + define($constant->name(), $constant->value()); + } + } + } + + private function handleGlobalVariables(VariableCollection $variables): void + { + foreach ($variables as $variable) { + $GLOBALS[$variable->name()] = $variable->value(); + } + } + + private function handleServerVariables(VariableCollection $variables): void + { + foreach ($variables as $variable) { + $_SERVER[$variable->name()] = $variable->value(); + } + } + + private function handleVariables(string $target, VariableCollection $variables): void + { + foreach ($variables as $variable) { + $GLOBALS[$target][$variable->name()] = $variable->value(); + } + } + + private function handleEnvVariables(VariableCollection $variables): void + { + foreach ($variables as $variable) { + $name = $variable->name(); + $value = $variable->value(); + $force = $variable->force(); + + if ($force || getenv($name) === false) { + putenv("{$name}={$value}"); + } + + $value = getenv($name); + + if ($force || !isset($_ENV[$name])) { + $_ENV[$name] = $value; + } + } + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/Variable.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/Variable.php new file mode 100644 index 00000000..37c572ae --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/Variable.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Variable +{ + /** + * @var string + */ + private $name; + + /** + * @var mixed + */ + private $value; + + /** + * @var bool + */ + private $force; + + public function __construct(string $name, $value, bool $force) + { + $this->name = $name; + $this->value = $value; + $this->force = $force; + } + + public function name(): string + { + return $this->name; + } + + public function value() + { + return $this->value; + } + + public function force(): bool + { + return $this->force; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/VariableCollection.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/VariableCollection.php new file mode 100644 index 00000000..6662db64 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/VariableCollection.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function count; +use Countable; +use IteratorAggregate; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + * + * @template-implements IteratorAggregate + */ +final class VariableCollection implements Countable, IteratorAggregate +{ + /** + * @var Variable[] + */ + private $variables; + + /** + * @param Variable[] $variables + */ + public static function fromArray(array $variables): self + { + return new self(...$variables); + } + + private function __construct(Variable ...$variables) + { + $this->variables = $variables; + } + + /** + * @return Variable[] + */ + public function asArray(): array + { + return $this->variables; + } + + public function count(): int + { + return count($this->variables); + } + + public function getIterator(): VariableCollectionIterator + { + return new VariableCollectionIterator($this); + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/VariableCollectionIterator.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/VariableCollectionIterator.php new file mode 100644 index 00000000..032d0be1 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/VariableCollectionIterator.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function count; +use function iterator_count; +use Countable; +use Iterator; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @template-implements Iterator + */ +final class VariableCollectionIterator implements Countable, Iterator +{ + /** + * @var Variable[] + */ + private $variables; + + /** + * @var int + */ + private $position; + + public function __construct(VariableCollection $variables) + { + $this->variables = $variables->asArray(); + } + + public function count(): int + { + return iterator_count($this); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->variables); + } + + public function key(): int + { + return $this->position; + } + + public function current(): Variable + { + return $this->variables[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHPUnit/Extension.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHPUnit/Extension.php new file mode 100644 index 00000000..09fe8cc9 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHPUnit/Extension.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class Extension +{ + /** + * @var string + * + * @psalm-var class-string + */ + private $className; + + /** + * @var string + */ + private $sourceFile; + + /** + * @var array + */ + private $arguments; + + /** + * @psalm-param class-string $className + */ + public function __construct(string $className, string $sourceFile, array $arguments) + { + $this->className = $className; + $this->sourceFile = $sourceFile; + $this->arguments = $arguments; + } + + /** + * @psalm-return class-string + */ + public function className(): string + { + return $this->className; + } + + public function hasSourceFile(): bool + { + return $this->sourceFile !== ''; + } + + public function sourceFile(): string + { + return $this->sourceFile; + } + + public function hasArguments(): bool + { + return !empty($this->arguments); + } + + public function arguments(): array + { + return $this->arguments; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHPUnit/ExtensionCollection.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHPUnit/ExtensionCollection.php new file mode 100644 index 00000000..76d07ebc --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHPUnit/ExtensionCollection.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use IteratorAggregate; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + * + * @template-implements IteratorAggregate + */ +final class ExtensionCollection implements IteratorAggregate +{ + /** + * @var Extension[] + */ + private $extensions; + + /** + * @param Extension[] $extensions + */ + public static function fromArray(array $extensions): self + { + return new self(...$extensions); + } + + private function __construct(Extension ...$extensions) + { + $this->extensions = $extensions; + } + + /** + * @return Extension[] + */ + public function asArray(): array + { + return $this->extensions; + } + + public function getIterator(): ExtensionCollectionIterator + { + return new ExtensionCollectionIterator($this); + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHPUnit/ExtensionCollectionIterator.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHPUnit/ExtensionCollectionIterator.php new file mode 100644 index 00000000..a9fc1af8 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHPUnit/ExtensionCollectionIterator.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function count; +use function iterator_count; +use Countable; +use Iterator; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @template-implements Iterator + */ +final class ExtensionCollectionIterator implements Countable, Iterator +{ + /** + * @var Extension[] + */ + private $extensions; + + /** + * @var int + */ + private $position; + + public function __construct(ExtensionCollection $extensions) + { + $this->extensions = $extensions->asArray(); + } + + public function count(): int + { + return iterator_count($this); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->extensions); + } + + public function key(): int + { + return $this->position; + } + + public function current(): Extension + { + return $this->extensions[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHPUnit/PHPUnit.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHPUnit/PHPUnit.php new file mode 100644 index 00000000..5b3ce9b8 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHPUnit/PHPUnit.php @@ -0,0 +1,715 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class PHPUnit +{ + /** + * @var bool + */ + private $cacheResult; + + /** + * @var ?string + */ + private $cacheResultFile; + + /** + * @var int|string + */ + private $columns; + + /** + * @var string + */ + private $colors; + + /** + * @var bool + */ + private $stderr; + + /** + * @var bool + */ + private $noInteraction; + + /** + * @var bool + */ + private $verbose; + + /** + * @var bool + */ + private $reverseDefectList; + + /** + * @var bool + */ + private $convertDeprecationsToExceptions; + + /** + * @var bool + */ + private $convertErrorsToExceptions; + + /** + * @var bool + */ + private $convertNoticesToExceptions; + + /** + * @var bool + */ + private $convertWarningsToExceptions; + + /** + * @var bool + */ + private $forceCoversAnnotation; + + /** + * @var ?string + */ + private $bootstrap; + + /** + * @var bool + */ + private $processIsolation; + + /** + * @var bool + */ + private $failOnEmptyTestSuite; + + /** + * @var bool + */ + private $failOnIncomplete; + + /** + * @var bool + */ + private $failOnRisky; + + /** + * @var bool + */ + private $failOnSkipped; + + /** + * @var bool + */ + private $failOnWarning; + + /** + * @var bool + */ + private $stopOnDefect; + + /** + * @var bool + */ + private $stopOnError; + + /** + * @var bool + */ + private $stopOnFailure; + + /** + * @var bool + */ + private $stopOnWarning; + + /** + * @var bool + */ + private $stopOnIncomplete; + + /** + * @var bool + */ + private $stopOnRisky; + + /** + * @var bool + */ + private $stopOnSkipped; + + /** + * @var ?string + */ + private $extensionsDirectory; + + /** + * @var ?string + * + * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 + */ + private $testSuiteLoaderClass; + + /** + * @var ?string + * + * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 + */ + private $testSuiteLoaderFile; + + /** + * @var ?string + */ + private $printerClass; + + /** + * @var ?string + */ + private $printerFile; + + /** + * @var bool + */ + private $beStrictAboutChangesToGlobalState; + + /** + * @var bool + */ + private $beStrictAboutOutputDuringTests; + + /** + * @var bool + */ + private $beStrictAboutResourceUsageDuringSmallTests; + + /** + * @var bool + */ + private $beStrictAboutTestsThatDoNotTestAnything; + + /** + * @var bool + */ + private $beStrictAboutTodoAnnotatedTests; + + /** + * @var bool + */ + private $beStrictAboutCoversAnnotation; + + /** + * @var bool + */ + private $enforceTimeLimit; + + /** + * @var int + */ + private $defaultTimeLimit; + + /** + * @var int + */ + private $timeoutForSmallTests; + + /** + * @var int + */ + private $timeoutForMediumTests; + + /** + * @var int + */ + private $timeoutForLargeTests; + + /** + * @var ?string + */ + private $defaultTestSuite; + + /** + * @var int + */ + private $executionOrder; + + /** + * @var bool + */ + private $resolveDependencies; + + /** + * @var bool + */ + private $defectsFirst; + + /** + * @var bool + */ + private $backupGlobals; + + /** + * @var bool + */ + private $backupStaticAttributes; + + /** + * @var bool + */ + private $registerMockObjectsFromTestArgumentsRecursively; + + /** + * @var bool + */ + private $conflictBetweenPrinterClassAndTestdox; + + public function __construct(bool $cacheResult, ?string $cacheResultFile, $columns, string $colors, bool $stderr, bool $noInteraction, bool $verbose, bool $reverseDefectList, bool $convertDeprecationsToExceptions, bool $convertErrorsToExceptions, bool $convertNoticesToExceptions, bool $convertWarningsToExceptions, bool $forceCoversAnnotation, ?string $bootstrap, bool $processIsolation, bool $failOnEmptyTestSuite, bool $failOnIncomplete, bool $failOnRisky, bool $failOnSkipped, bool $failOnWarning, bool $stopOnDefect, bool $stopOnError, bool $stopOnFailure, bool $stopOnWarning, bool $stopOnIncomplete, bool $stopOnRisky, bool $stopOnSkipped, ?string $extensionsDirectory, ?string $testSuiteLoaderClass, ?string $testSuiteLoaderFile, ?string $printerClass, ?string $printerFile, bool $beStrictAboutChangesToGlobalState, bool $beStrictAboutOutputDuringTests, bool $beStrictAboutResourceUsageDuringSmallTests, bool $beStrictAboutTestsThatDoNotTestAnything, bool $beStrictAboutTodoAnnotatedTests, bool $beStrictAboutCoversAnnotation, bool $enforceTimeLimit, int $defaultTimeLimit, int $timeoutForSmallTests, int $timeoutForMediumTests, int $timeoutForLargeTests, ?string $defaultTestSuite, int $executionOrder, bool $resolveDependencies, bool $defectsFirst, bool $backupGlobals, bool $backupStaticAttributes, bool $registerMockObjectsFromTestArgumentsRecursively, bool $conflictBetweenPrinterClassAndTestdox) + { + $this->cacheResult = $cacheResult; + $this->cacheResultFile = $cacheResultFile; + $this->columns = $columns; + $this->colors = $colors; + $this->stderr = $stderr; + $this->noInteraction = $noInteraction; + $this->verbose = $verbose; + $this->reverseDefectList = $reverseDefectList; + $this->convertDeprecationsToExceptions = $convertDeprecationsToExceptions; + $this->convertErrorsToExceptions = $convertErrorsToExceptions; + $this->convertNoticesToExceptions = $convertNoticesToExceptions; + $this->convertWarningsToExceptions = $convertWarningsToExceptions; + $this->forceCoversAnnotation = $forceCoversAnnotation; + $this->bootstrap = $bootstrap; + $this->processIsolation = $processIsolation; + $this->failOnEmptyTestSuite = $failOnEmptyTestSuite; + $this->failOnIncomplete = $failOnIncomplete; + $this->failOnRisky = $failOnRisky; + $this->failOnSkipped = $failOnSkipped; + $this->failOnWarning = $failOnWarning; + $this->stopOnDefect = $stopOnDefect; + $this->stopOnError = $stopOnError; + $this->stopOnFailure = $stopOnFailure; + $this->stopOnWarning = $stopOnWarning; + $this->stopOnIncomplete = $stopOnIncomplete; + $this->stopOnRisky = $stopOnRisky; + $this->stopOnSkipped = $stopOnSkipped; + $this->extensionsDirectory = $extensionsDirectory; + $this->testSuiteLoaderClass = $testSuiteLoaderClass; + $this->testSuiteLoaderFile = $testSuiteLoaderFile; + $this->printerClass = $printerClass; + $this->printerFile = $printerFile; + $this->beStrictAboutChangesToGlobalState = $beStrictAboutChangesToGlobalState; + $this->beStrictAboutOutputDuringTests = $beStrictAboutOutputDuringTests; + $this->beStrictAboutResourceUsageDuringSmallTests = $beStrictAboutResourceUsageDuringSmallTests; + $this->beStrictAboutTestsThatDoNotTestAnything = $beStrictAboutTestsThatDoNotTestAnything; + $this->beStrictAboutTodoAnnotatedTests = $beStrictAboutTodoAnnotatedTests; + $this->beStrictAboutCoversAnnotation = $beStrictAboutCoversAnnotation; + $this->enforceTimeLimit = $enforceTimeLimit; + $this->defaultTimeLimit = $defaultTimeLimit; + $this->timeoutForSmallTests = $timeoutForSmallTests; + $this->timeoutForMediumTests = $timeoutForMediumTests; + $this->timeoutForLargeTests = $timeoutForLargeTests; + $this->defaultTestSuite = $defaultTestSuite; + $this->executionOrder = $executionOrder; + $this->resolveDependencies = $resolveDependencies; + $this->defectsFirst = $defectsFirst; + $this->backupGlobals = $backupGlobals; + $this->backupStaticAttributes = $backupStaticAttributes; + $this->registerMockObjectsFromTestArgumentsRecursively = $registerMockObjectsFromTestArgumentsRecursively; + $this->conflictBetweenPrinterClassAndTestdox = $conflictBetweenPrinterClassAndTestdox; + } + + public function cacheResult(): bool + { + return $this->cacheResult; + } + + /** + * @psalm-assert-if-true !null $this->cacheResultFile + */ + public function hasCacheResultFile(): bool + { + return $this->cacheResultFile !== null; + } + + /** + * @throws Exception + */ + public function cacheResultFile(): string + { + if (!$this->hasCacheResultFile()) { + throw new Exception('Cache result file is not configured'); + } + + return (string) $this->cacheResultFile; + } + + public function columns() + { + return $this->columns; + } + + public function colors(): string + { + return $this->colors; + } + + public function stderr(): bool + { + return $this->stderr; + } + + public function noInteraction(): bool + { + return $this->noInteraction; + } + + public function verbose(): bool + { + return $this->verbose; + } + + public function reverseDefectList(): bool + { + return $this->reverseDefectList; + } + + public function convertDeprecationsToExceptions(): bool + { + return $this->convertDeprecationsToExceptions; + } + + public function convertErrorsToExceptions(): bool + { + return $this->convertErrorsToExceptions; + } + + public function convertNoticesToExceptions(): bool + { + return $this->convertNoticesToExceptions; + } + + public function convertWarningsToExceptions(): bool + { + return $this->convertWarningsToExceptions; + } + + public function forceCoversAnnotation(): bool + { + return $this->forceCoversAnnotation; + } + + /** + * @psalm-assert-if-true !null $this->bootstrap + */ + public function hasBootstrap(): bool + { + return $this->bootstrap !== null; + } + + /** + * @throws Exception + */ + public function bootstrap(): string + { + if (!$this->hasBootstrap()) { + throw new Exception('Bootstrap script is not configured'); + } + + return (string) $this->bootstrap; + } + + public function processIsolation(): bool + { + return $this->processIsolation; + } + + public function failOnEmptyTestSuite(): bool + { + return $this->failOnEmptyTestSuite; + } + + public function failOnIncomplete(): bool + { + return $this->failOnIncomplete; + } + + public function failOnRisky(): bool + { + return $this->failOnRisky; + } + + public function failOnSkipped(): bool + { + return $this->failOnSkipped; + } + + public function failOnWarning(): bool + { + return $this->failOnWarning; + } + + public function stopOnDefect(): bool + { + return $this->stopOnDefect; + } + + public function stopOnError(): bool + { + return $this->stopOnError; + } + + public function stopOnFailure(): bool + { + return $this->stopOnFailure; + } + + public function stopOnWarning(): bool + { + return $this->stopOnWarning; + } + + public function stopOnIncomplete(): bool + { + return $this->stopOnIncomplete; + } + + public function stopOnRisky(): bool + { + return $this->stopOnRisky; + } + + public function stopOnSkipped(): bool + { + return $this->stopOnSkipped; + } + + /** + * @psalm-assert-if-true !null $this->extensionsDirectory + */ + public function hasExtensionsDirectory(): bool + { + return $this->extensionsDirectory !== null; + } + + /** + * @throws Exception + */ + public function extensionsDirectory(): string + { + if (!$this->hasExtensionsDirectory()) { + throw new Exception('Extensions directory is not configured'); + } + + return (string) $this->extensionsDirectory; + } + + /** + * @psalm-assert-if-true !null $this->testSuiteLoaderClass + * + * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 + */ + public function hasTestSuiteLoaderClass(): bool + { + return $this->testSuiteLoaderClass !== null; + } + + /** + * @throws Exception + * + * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 + */ + public function testSuiteLoaderClass(): string + { + if (!$this->hasTestSuiteLoaderClass()) { + throw new Exception('TestSuiteLoader class is not configured'); + } + + return (string) $this->testSuiteLoaderClass; + } + + /** + * @psalm-assert-if-true !null $this->testSuiteLoaderFile + * + * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 + */ + public function hasTestSuiteLoaderFile(): bool + { + return $this->testSuiteLoaderFile !== null; + } + + /** + * @throws Exception + * + * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 + */ + public function testSuiteLoaderFile(): string + { + if (!$this->hasTestSuiteLoaderFile()) { + throw new Exception('TestSuiteLoader sourcecode file is not configured'); + } + + return (string) $this->testSuiteLoaderFile; + } + + /** + * @psalm-assert-if-true !null $this->printerClass + */ + public function hasPrinterClass(): bool + { + return $this->printerClass !== null; + } + + /** + * @throws Exception + */ + public function printerClass(): string + { + if (!$this->hasPrinterClass()) { + throw new Exception('ResultPrinter class is not configured'); + } + + return (string) $this->printerClass; + } + + /** + * @psalm-assert-if-true !null $this->printerFile + */ + public function hasPrinterFile(): bool + { + return $this->printerFile !== null; + } + + /** + * @throws Exception + */ + public function printerFile(): string + { + if (!$this->hasPrinterFile()) { + throw new Exception('ResultPrinter sourcecode file is not configured'); + } + + return (string) $this->printerFile; + } + + public function beStrictAboutChangesToGlobalState(): bool + { + return $this->beStrictAboutChangesToGlobalState; + } + + public function beStrictAboutOutputDuringTests(): bool + { + return $this->beStrictAboutOutputDuringTests; + } + + public function beStrictAboutResourceUsageDuringSmallTests(): bool + { + return $this->beStrictAboutResourceUsageDuringSmallTests; + } + + public function beStrictAboutTestsThatDoNotTestAnything(): bool + { + return $this->beStrictAboutTestsThatDoNotTestAnything; + } + + public function beStrictAboutTodoAnnotatedTests(): bool + { + return $this->beStrictAboutTodoAnnotatedTests; + } + + public function beStrictAboutCoversAnnotation(): bool + { + return $this->beStrictAboutCoversAnnotation; + } + + public function enforceTimeLimit(): bool + { + return $this->enforceTimeLimit; + } + + public function defaultTimeLimit(): int + { + return $this->defaultTimeLimit; + } + + public function timeoutForSmallTests(): int + { + return $this->timeoutForSmallTests; + } + + public function timeoutForMediumTests(): int + { + return $this->timeoutForMediumTests; + } + + public function timeoutForLargeTests(): int + { + return $this->timeoutForLargeTests; + } + + /** + * @psalm-assert-if-true !null $this->defaultTestSuite + */ + public function hasDefaultTestSuite(): bool + { + return $this->defaultTestSuite !== null; + } + + /** + * @throws Exception + */ + public function defaultTestSuite(): string + { + if (!$this->hasDefaultTestSuite()) { + throw new Exception('Default test suite is not configured'); + } + + return (string) $this->defaultTestSuite; + } + + public function executionOrder(): int + { + return $this->executionOrder; + } + + public function resolveDependencies(): bool + { + return $this->resolveDependencies; + } + + public function defectsFirst(): bool + { + return $this->defectsFirst; + } + + public function backupGlobals(): bool + { + return $this->backupGlobals; + } + + public function backupStaticAttributes(): bool + { + return $this->backupStaticAttributes; + } + + public function registerMockObjectsFromTestArgumentsRecursively(): bool + { + return $this->registerMockObjectsFromTestArgumentsRecursively; + } + + public function conflictBetweenPrinterClassAndTestdox(): bool + { + return $this->conflictBetweenPrinterClassAndTestdox; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestDirectory.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestDirectory.php new file mode 100644 index 00000000..ecefbb7c --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestDirectory.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use PHPUnit\Util\VersionComparisonOperator; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class TestDirectory +{ + /** + * @var string + */ + private $path; + + /** + * @var string + */ + private $prefix; + + /** + * @var string + */ + private $suffix; + + /** + * @var string + */ + private $phpVersion; + + /** + * @var VersionComparisonOperator + */ + private $phpVersionOperator; + + public function __construct(string $path, string $prefix, string $suffix, string $phpVersion, VersionComparisonOperator $phpVersionOperator) + { + $this->path = $path; + $this->prefix = $prefix; + $this->suffix = $suffix; + $this->phpVersion = $phpVersion; + $this->phpVersionOperator = $phpVersionOperator; + } + + public function path(): string + { + return $this->path; + } + + public function prefix(): string + { + return $this->prefix; + } + + public function suffix(): string + { + return $this->suffix; + } + + public function phpVersion(): string + { + return $this->phpVersion; + } + + public function phpVersionOperator(): VersionComparisonOperator + { + return $this->phpVersionOperator; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestDirectoryCollection.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestDirectoryCollection.php new file mode 100644 index 00000000..5f581c21 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestDirectoryCollection.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function count; +use Countable; +use IteratorAggregate; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + * + * @template-implements IteratorAggregate + */ +final class TestDirectoryCollection implements Countable, IteratorAggregate +{ + /** + * @var TestDirectory[] + */ + private $directories; + + /** + * @param TestDirectory[] $directories + */ + public static function fromArray(array $directories): self + { + return new self(...$directories); + } + + private function __construct(TestDirectory ...$directories) + { + $this->directories = $directories; + } + + /** + * @return TestDirectory[] + */ + public function asArray(): array + { + return $this->directories; + } + + public function count(): int + { + return count($this->directories); + } + + public function getIterator(): TestDirectoryCollectionIterator + { + return new TestDirectoryCollectionIterator($this); + } + + public function isEmpty(): bool + { + return $this->count() === 0; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestDirectoryCollectionIterator.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestDirectoryCollectionIterator.php new file mode 100644 index 00000000..b2312a38 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestDirectoryCollectionIterator.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function count; +use function iterator_count; +use Countable; +use Iterator; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @template-implements Iterator + */ +final class TestDirectoryCollectionIterator implements Countable, Iterator +{ + /** + * @var TestDirectory[] + */ + private $directories; + + /** + * @var int + */ + private $position; + + public function __construct(TestDirectoryCollection $directories) + { + $this->directories = $directories->asArray(); + } + + public function count(): int + { + return iterator_count($this); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->directories); + } + + public function key(): int + { + return $this->position; + } + + public function current(): TestDirectory + { + return $this->directories[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestFile.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestFile.php new file mode 100644 index 00000000..21d1cf7b --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestFile.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use PHPUnit\Util\VersionComparisonOperator; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class TestFile +{ + /** + * @var string + */ + private $path; + + /** + * @var string + */ + private $phpVersion; + + /** + * @var VersionComparisonOperator + */ + private $phpVersionOperator; + + public function __construct(string $path, string $phpVersion, VersionComparisonOperator $phpVersionOperator) + { + $this->path = $path; + $this->phpVersion = $phpVersion; + $this->phpVersionOperator = $phpVersionOperator; + } + + public function path(): string + { + return $this->path; + } + + public function phpVersion(): string + { + return $this->phpVersion; + } + + public function phpVersionOperator(): VersionComparisonOperator + { + return $this->phpVersionOperator; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestFileCollection.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestFileCollection.php new file mode 100644 index 00000000..27ba9bd2 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestFileCollection.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function count; +use Countable; +use IteratorAggregate; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + * + * @template-implements IteratorAggregate + */ +final class TestFileCollection implements Countable, IteratorAggregate +{ + /** + * @var TestFile[] + */ + private $files; + + /** + * @param TestFile[] $files + */ + public static function fromArray(array $files): self + { + return new self(...$files); + } + + private function __construct(TestFile ...$files) + { + $this->files = $files; + } + + /** + * @return TestFile[] + */ + public function asArray(): array + { + return $this->files; + } + + public function count(): int + { + return count($this->files); + } + + public function getIterator(): TestFileCollectionIterator + { + return new TestFileCollectionIterator($this); + } + + public function isEmpty(): bool + { + return $this->count() === 0; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestFileCollectionIterator.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestFileCollectionIterator.php new file mode 100644 index 00000000..45a5f160 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestFileCollectionIterator.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function count; +use function iterator_count; +use Countable; +use Iterator; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @template-implements Iterator + */ +final class TestFileCollectionIterator implements Countable, Iterator +{ + /** + * @var TestFile[] + */ + private $files; + + /** + * @var int + */ + private $position; + + public function __construct(TestFileCollection $files) + { + $this->files = $files->asArray(); + } + + public function count(): int + { + return iterator_count($this); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->files); + } + + public function key(): int + { + return $this->position; + } + + public function current(): TestFile + { + return $this->files[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestSuite.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestSuite.php new file mode 100644 index 00000000..035376cb --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestSuite.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class TestSuite +{ + /** + * @var string + */ + private $name; + + /** + * @var TestDirectoryCollection + */ + private $directories; + + /** + * @var TestFileCollection + */ + private $files; + + /** + * @var FileCollection + */ + private $exclude; + + public function __construct(string $name, TestDirectoryCollection $directories, TestFileCollection $files, FileCollection $exclude) + { + $this->name = $name; + $this->directories = $directories; + $this->files = $files; + $this->exclude = $exclude; + } + + public function name(): string + { + return $this->name; + } + + public function directories(): TestDirectoryCollection + { + return $this->directories; + } + + public function files(): TestFileCollection + { + return $this->files; + } + + public function exclude(): FileCollection + { + return $this->exclude; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestSuiteCollection.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestSuiteCollection.php new file mode 100644 index 00000000..f632e519 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestSuiteCollection.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function count; +use Countable; +use IteratorAggregate; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + * + * @template-implements IteratorAggregate + */ +final class TestSuiteCollection implements Countable, IteratorAggregate +{ + /** + * @var TestSuite[] + */ + private $testSuites; + + /** + * @param TestSuite[] $testSuites + */ + public static function fromArray(array $testSuites): self + { + return new self(...$testSuites); + } + + private function __construct(TestSuite ...$testSuites) + { + $this->testSuites = $testSuites; + } + + /** + * @return TestSuite[] + */ + public function asArray(): array + { + return $this->testSuites; + } + + public function count(): int + { + return count($this->testSuites); + } + + public function getIterator(): TestSuiteCollectionIterator + { + return new TestSuiteCollectionIterator($this); + } + + public function isEmpty(): bool + { + return $this->count() === 0; + } +} diff --git a/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestSuiteCollectionIterator.php b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestSuiteCollectionIterator.php new file mode 100644 index 00000000..42d03db0 --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestSuiteCollectionIterator.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function count; +use function iterator_count; +use Countable; +use Iterator; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @template-implements Iterator + */ +final class TestSuiteCollectionIterator implements Countable, Iterator +{ + /** + * @var TestSuite[] + */ + private $testSuites; + + /** + * @var int + */ + private $position; + + public function __construct(TestSuiteCollection $testSuites) + { + $this->testSuites = $testSuites->asArray(); + } + + public function count(): int + { + return iterator_count($this); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->testSuites); + } + + public function key(): int + { + return $this->position; + } + + public function current(): TestSuite + { + return $this->testSuites[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/vendor/phpunit/phpunit/src/Util/Annotation/DocBlock.php b/vendor/phpunit/phpunit/src/Util/Annotation/DocBlock.php new file mode 100644 index 00000000..764bbbfb --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/Annotation/DocBlock.php @@ -0,0 +1,546 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\Annotation; + +use const JSON_ERROR_NONE; +use const PREG_OFFSET_CAPTURE; +use function array_filter; +use function array_key_exists; +use function array_map; +use function array_merge; +use function array_pop; +use function array_slice; +use function array_values; +use function count; +use function explode; +use function file; +use function implode; +use function is_array; +use function is_int; +use function json_decode; +use function json_last_error; +use function json_last_error_msg; +use function preg_match; +use function preg_match_all; +use function preg_replace; +use function preg_split; +use function realpath; +use function rtrim; +use function sprintf; +use function str_replace; +use function strlen; +use function strpos; +use function strtolower; +use function substr; +use function trim; +use PharIo\Version\VersionConstraintParser; +use PHPUnit\Framework\InvalidDataProviderException; +use PHPUnit\Framework\SkippedTestError; +use PHPUnit\Framework\Warning; +use PHPUnit\Util\Exception; +use PHPUnit\Util\InvalidDataSetException; +use ReflectionClass; +use ReflectionException; +use ReflectionFunctionAbstract; +use ReflectionMethod; +use Reflector; +use Traversable; + +/** + * This is an abstraction around a PHPUnit-specific docBlock, + * allowing us to ask meaningful questions about a specific + * reflection symbol. + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class DocBlock +{ + /** + * @todo This constant should be private (it's public because of TestTest::testGetProvidedDataRegEx) + */ + public const REGEX_DATA_PROVIDER = '/@dataProvider\s+([a-zA-Z0-9._:-\\\\x7f-\xff]+)/'; + + private const REGEX_REQUIRES_VERSION = '/@requires\s+(?PPHP(?:Unit)?)\s+(?P[<>=!]{0,2})\s*(?P[\d\.-]+(dev|(RC|alpha|beta)[\d\.])?)[ \t]*\r?$/m'; + private const REGEX_REQUIRES_VERSION_CONSTRAINT = '/@requires\s+(?PPHP(?:Unit)?)\s+(?P[\d\t \-.|~^]+)[ \t]*\r?$/m'; + private const REGEX_REQUIRES_OS = '/@requires\s+(?POS(?:FAMILY)?)\s+(?P.+?)[ \t]*\r?$/m'; + private const REGEX_REQUIRES_SETTING = '/@requires\s+(?Psetting)\s+(?P([^ ]+?))\s*(?P[\w\.-]+[\w\.]?)?[ \t]*\r?$/m'; + private const REGEX_REQUIRES = '/@requires\s+(?Pfunction|extension)\s+(?P([^\s<>=!]+))\s*(?P[<>=!]{0,2})\s*(?P[\d\.-]+[\d\.]?)?[ \t]*\r?$/m'; + private const REGEX_TEST_WITH = '/@testWith\s+/'; + + /** @var string */ + private $docComment; + + /** @var bool */ + private $isMethod; + + /** @var array> pre-parsed annotations indexed by name and occurrence index */ + private $symbolAnnotations; + + /** + * @var null|array + * + * @psalm-var null|(array{ + * __OFFSET: array&array{__FILE: string}, + * setting?: array, + * extension_versions?: array + * }&array< + * string, + * string|array{version: string, operator: string}|array{constraint: string}|array + * >) + */ + private $parsedRequirements; + + /** @var int */ + private $startLine; + + /** @var int */ + private $endLine; + + /** @var string */ + private $fileName; + + /** @var string */ + private $name; + + /** + * @var string + * + * @psalm-var class-string + */ + private $className; + + public static function ofClass(ReflectionClass $class): self + { + $className = $class->getName(); + + return new self( + (string) $class->getDocComment(), + false, + self::extractAnnotationsFromReflector($class), + $class->getStartLine(), + $class->getEndLine(), + $class->getFileName(), + $className, + $className, + ); + } + + /** + * @psalm-param class-string $classNameInHierarchy + */ + public static function ofMethod(ReflectionMethod $method, string $classNameInHierarchy): self + { + return new self( + (string) $method->getDocComment(), + true, + self::extractAnnotationsFromReflector($method), + $method->getStartLine(), + $method->getEndLine(), + $method->getFileName(), + $method->getName(), + $classNameInHierarchy, + ); + } + + /** + * Note: we do not preserve an instance of the reflection object, since it cannot be safely (de-)serialized. + * + * @param array> $symbolAnnotations + * + * @psalm-param class-string $className + */ + private function __construct(string $docComment, bool $isMethod, array $symbolAnnotations, int $startLine, int $endLine, string $fileName, string $name, string $className) + { + $this->docComment = $docComment; + $this->isMethod = $isMethod; + $this->symbolAnnotations = $symbolAnnotations; + $this->startLine = $startLine; + $this->endLine = $endLine; + $this->fileName = $fileName; + $this->name = $name; + $this->className = $className; + } + + /** + * @psalm-return array{ + * __OFFSET: array&array{__FILE: string}, + * setting?: array, + * extension_versions?: array + * }&array< + * string, + * string|array{version: string, operator: string}|array{constraint: string}|array + * > + * + * @throws Warning if the requirements version constraint is not well-formed + */ + public function requirements(): array + { + if ($this->parsedRequirements !== null) { + return $this->parsedRequirements; + } + + $offset = $this->startLine; + $requires = []; + $recordedSettings = []; + $extensionVersions = []; + $recordedOffsets = [ + '__FILE' => realpath($this->fileName), + ]; + + // Trim docblock markers, split it into lines and rewind offset to start of docblock + $lines = preg_replace(['#^/\*{2}#', '#\*/$#'], '', preg_split('/\r\n|\r|\n/', $this->docComment)); + $offset -= count($lines); + + foreach ($lines as $line) { + if (preg_match(self::REGEX_REQUIRES_OS, $line, $matches)) { + $requires[$matches['name']] = $matches['value']; + $recordedOffsets[$matches['name']] = $offset; + } + + if (preg_match(self::REGEX_REQUIRES_VERSION, $line, $matches)) { + $requires[$matches['name']] = [ + 'version' => $matches['version'], + 'operator' => $matches['operator'], + ]; + $recordedOffsets[$matches['name']] = $offset; + } + + if (preg_match(self::REGEX_REQUIRES_VERSION_CONSTRAINT, $line, $matches)) { + if (!empty($requires[$matches['name']])) { + $offset++; + + continue; + } + + try { + $versionConstraintParser = new VersionConstraintParser; + + $requires[$matches['name'] . '_constraint'] = [ + 'constraint' => $versionConstraintParser->parse(trim($matches['constraint'])), + ]; + $recordedOffsets[$matches['name'] . '_constraint'] = $offset; + } catch (\PharIo\Version\Exception $e) { + throw new Warning($e->getMessage(), $e->getCode(), $e); + } + } + + if (preg_match(self::REGEX_REQUIRES_SETTING, $line, $matches)) { + $recordedSettings[$matches['setting']] = $matches['value']; + $recordedOffsets['__SETTING_' . $matches['setting']] = $offset; + } + + if (preg_match(self::REGEX_REQUIRES, $line, $matches)) { + $name = $matches['name'] . 's'; + + if (!isset($requires[$name])) { + $requires[$name] = []; + } + + $requires[$name][] = $matches['value']; + $recordedOffsets[$matches['name'] . '_' . $matches['value']] = $offset; + + if ($name === 'extensions' && !empty($matches['version'])) { + $extensionVersions[$matches['value']] = [ + 'version' => $matches['version'], + 'operator' => $matches['operator'], + ]; + } + } + + $offset++; + } + + return $this->parsedRequirements = array_merge( + $requires, + ['__OFFSET' => $recordedOffsets], + array_filter([ + 'setting' => $recordedSettings, + 'extension_versions' => $extensionVersions, + ]), + ); + } + + /** + * Returns the provided data for a method. + * + * @throws Exception + */ + public function getProvidedData(): ?array + { + /** @noinspection SuspiciousBinaryOperationInspection */ + $data = $this->getDataFromDataProviderAnnotation($this->docComment) ?? $this->getDataFromTestWithAnnotation($this->docComment); + + if ($data === null) { + return null; + } + + if ($data === []) { + throw new SkippedTestError; + } + + foreach ($data as $key => $value) { + if (!is_array($value)) { + throw new InvalidDataSetException( + sprintf( + 'Data set %s is invalid.', + is_int($key) ? '#' . $key : '"' . $key . '"', + ), + ); + } + } + + return $data; + } + + /** + * @psalm-return array + */ + public function getInlineAnnotations(): array + { + $code = file($this->fileName); + $lineNumber = $this->startLine; + $startLine = $this->startLine - 1; + $endLine = $this->endLine - 1; + $codeLines = array_slice($code, $startLine, $endLine - $startLine + 1); + $annotations = []; + + foreach ($codeLines as $line) { + if (preg_match('#/\*\*?\s*@(?P[A-Za-z_-]+)(?:[ \t]+(?P.*?))?[ \t]*\r?\*/$#m', $line, $matches)) { + $annotations[strtolower($matches['name'])] = [ + 'line' => $lineNumber, + 'value' => $matches['value'], + ]; + } + + $lineNumber++; + } + + return $annotations; + } + + public function symbolAnnotations(): array + { + return $this->symbolAnnotations; + } + + public function isHookToBeExecutedBeforeClass(): bool + { + return $this->isMethod && + false !== strpos($this->docComment, '@beforeClass'); + } + + public function isHookToBeExecutedAfterClass(): bool + { + return $this->isMethod && + false !== strpos($this->docComment, '@afterClass'); + } + + public function isToBeExecutedBeforeTest(): bool + { + return 1 === preg_match('/@before\b/', $this->docComment); + } + + public function isToBeExecutedAfterTest(): bool + { + return 1 === preg_match('/@after\b/', $this->docComment); + } + + public function isToBeExecutedAsPreCondition(): bool + { + return 1 === preg_match('/@preCondition\b/', $this->docComment); + } + + public function isToBeExecutedAsPostCondition(): bool + { + return 1 === preg_match('/@postCondition\b/', $this->docComment); + } + + private function getDataFromDataProviderAnnotation(string $docComment): ?array + { + $methodName = null; + $className = $this->className; + + if ($this->isMethod) { + $methodName = $this->name; + } + + if (!preg_match_all(self::REGEX_DATA_PROVIDER, $docComment, $matches)) { + return null; + } + + $result = []; + + foreach ($matches[1] as $match) { + $dataProviderMethodNameNamespace = explode('\\', $match); + $leaf = explode('::', array_pop($dataProviderMethodNameNamespace)); + $dataProviderMethodName = array_pop($leaf); + + if (empty($dataProviderMethodNameNamespace)) { + $dataProviderMethodNameNamespace = ''; + } else { + $dataProviderMethodNameNamespace = implode('\\', $dataProviderMethodNameNamespace) . '\\'; + } + + if (empty($leaf)) { + $dataProviderClassName = $className; + } else { + /** @psalm-var class-string $dataProviderClassName */ + $dataProviderClassName = $dataProviderMethodNameNamespace . array_pop($leaf); + } + + try { + $dataProviderClass = new ReflectionClass($dataProviderClassName); + + $dataProviderMethod = $dataProviderClass->getMethod( + $dataProviderMethodName, + ); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + // @codeCoverageIgnoreEnd + } + + if ($dataProviderMethod->isStatic()) { + $object = null; + } else { + $object = $dataProviderClass->newInstance(); + } + + if ($dataProviderMethod->getNumberOfParameters() === 0) { + $data = $dataProviderMethod->invoke($object); + } else { + $data = $dataProviderMethod->invoke($object, $methodName); + } + + if ($data instanceof Traversable) { + $origData = $data; + $data = []; + + foreach ($origData as $key => $value) { + if (is_int($key)) { + $data[] = $value; + } elseif (array_key_exists($key, $data)) { + throw new InvalidDataProviderException( + sprintf( + 'The key "%s" has already been defined in the data provider "%s".', + $key, + $match, + ), + ); + } else { + $data[$key] = $value; + } + } + } + + if (is_array($data)) { + $result = array_merge($result, $data); + } + } + + return $result; + } + + /** + * @throws Exception + */ + private function getDataFromTestWithAnnotation(string $docComment): ?array + { + $docComment = $this->cleanUpMultiLineAnnotation($docComment); + + if (!preg_match(self::REGEX_TEST_WITH, $docComment, $matches, PREG_OFFSET_CAPTURE)) { + return null; + } + + $offset = strlen($matches[0][0]) + $matches[0][1]; + $annotationContent = substr($docComment, $offset); + $data = []; + + foreach (explode("\n", $annotationContent) as $candidateRow) { + $candidateRow = trim($candidateRow); + + if ($candidateRow[0] !== '[') { + break; + } + + $dataSet = json_decode($candidateRow, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new Exception( + 'The data set for the @testWith annotation cannot be parsed: ' . json_last_error_msg(), + ); + } + + $data[] = $dataSet; + } + + if (!$data) { + throw new Exception('The data set for the @testWith annotation cannot be parsed.'); + } + + return $data; + } + + private function cleanUpMultiLineAnnotation(string $docComment): string + { + // removing initial ' * ' for docComment + $docComment = str_replace("\r\n", "\n", $docComment); + $docComment = preg_replace('/\n\s*\*\s?/', "\n", $docComment); + $docComment = (string) substr($docComment, 0, -1); + + return rtrim($docComment, "\n"); + } + + /** @return array> */ + private static function parseDocBlock(string $docBlock): array + { + // Strip away the docblock header and footer to ease parsing of one line annotations + $docBlock = (string) substr($docBlock, 3, -2); + $annotations = []; + + if (preg_match_all('/@(?P[A-Za-z_-]+)(?:[ \t]+(?P.*?))?[ \t]*\r?$/m', $docBlock, $matches)) { + $numMatches = count($matches[0]); + + for ($i = 0; $i < $numMatches; $i++) { + $annotations[$matches['name'][$i]][] = (string) $matches['value'][$i]; + } + } + + return $annotations; + } + + /** @param ReflectionClass|ReflectionFunctionAbstract $reflector */ + private static function extractAnnotationsFromReflector(Reflector $reflector): array + { + $annotations = []; + + if ($reflector instanceof ReflectionClass) { + $annotations = array_merge( + $annotations, + ...array_map( + static function (ReflectionClass $trait): array + { + return self::parseDocBlock((string) $trait->getDocComment()); + }, + array_values($reflector->getTraits()), + ), + ); + } + + return array_merge( + $annotations, + self::parseDocBlock((string) $reflector->getDocComment()), + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Util/Annotation/Registry.php b/vendor/phpunit/phpunit/src/Util/Annotation/Registry.php new file mode 100644 index 00000000..a34cb9ad --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/Annotation/Registry.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\Annotation; + +use function array_key_exists; +use PHPUnit\Util\Exception; +use ReflectionClass; +use ReflectionException; +use ReflectionMethod; + +/** + * Reflection information, and therefore DocBlock information, is static within + * a single PHP process. It is therefore okay to use a Singleton registry here. + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Registry +{ + /** @var null|self */ + private static $instance; + + /** @var array indexed by class name */ + private $classDocBlocks = []; + + /** @var array> indexed by class name and method name */ + private $methodDocBlocks = []; + + public static function getInstance(): self + { + return self::$instance ?? self::$instance = new self; + } + + private function __construct() + { + } + + /** + * @throws Exception + * + * @psalm-param class-string $class + */ + public function forClassName(string $class): DocBlock + { + if (array_key_exists($class, $this->classDocBlocks)) { + return $this->classDocBlocks[$class]; + } + + try { + $reflection = new ReflectionClass($class); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + return $this->classDocBlocks[$class] = DocBlock::ofClass($reflection); + } + + /** + * @throws Exception + * + * @psalm-param class-string $classInHierarchy + */ + public function forMethod(string $classInHierarchy, string $method): DocBlock + { + if (isset($this->methodDocBlocks[$classInHierarchy][$method])) { + return $this->methodDocBlocks[$classInHierarchy][$method]; + } + + try { + $reflection = new ReflectionMethod($classInHierarchy, $method); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + return $this->methodDocBlocks[$classInHierarchy][$method] = DocBlock::ofMethod($reflection, $classInHierarchy); + } +} diff --git a/vendor/phpunit/phpunit/src/Util/Blacklist.php b/vendor/phpunit/phpunit/src/Util/Blacklist.php new file mode 100644 index 00000000..3b416e14 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/Blacklist.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +/** + * @deprecated Use ExcludeList instead + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class Blacklist +{ + public static function addDirectory(string $directory): void + { + ExcludeList::addDirectory($directory); + } + + /** + * @throws Exception + * + * @return string[] + */ + public function getBlacklistedDirectories(): array + { + return (new ExcludeList)->getExcludedDirectories(); + } + + /** + * @throws Exception + */ + public function isBlacklisted(string $file): bool + { + return (new ExcludeList)->isExcluded($file); + } +} diff --git a/vendor/phpunit/phpunit/src/Util/Cloner.php b/vendor/phpunit/phpunit/src/Util/Cloner.php new file mode 100644 index 00000000..38bd59ff --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/Cloner.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use Throwable; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Cloner +{ + /** + * @psalm-template OriginalType + * + * @psalm-param OriginalType $original + * + * @psalm-return OriginalType + */ + public static function clone(object $original): object + { + try { + return clone $original; + } catch (Throwable $t) { + return $original; + } + } +} diff --git a/vendor/phpunit/phpunit/src/Util/Color.php b/vendor/phpunit/phpunit/src/Util/Color.php new file mode 100644 index 00000000..ee0f412d --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/Color.php @@ -0,0 +1,159 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use const DIRECTORY_SEPARATOR; +use function array_keys; +use function array_map; +use function array_values; +use function count; +use function explode; +use function implode; +use function min; +use function preg_replace; +use function preg_replace_callback; +use function sprintf; +use function strtr; +use function trim; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Color +{ + /** + * @var array + */ + private const WHITESPACE_MAP = [ + ' ' => '·', + "\t" => '⇥', + ]; + + /** + * @var array + */ + private const WHITESPACE_EOL_MAP = [ + ' ' => '·', + "\t" => '⇥', + "\n" => '↵', + "\r" => '⟵', + ]; + + /** + * @var array + */ + private static $ansiCodes = [ + 'reset' => '0', + 'bold' => '1', + 'dim' => '2', + 'dim-reset' => '22', + 'underlined' => '4', + 'fg-default' => '39', + 'fg-black' => '30', + 'fg-red' => '31', + 'fg-green' => '32', + 'fg-yellow' => '33', + 'fg-blue' => '34', + 'fg-magenta' => '35', + 'fg-cyan' => '36', + 'fg-white' => '37', + 'bg-default' => '49', + 'bg-black' => '40', + 'bg-red' => '41', + 'bg-green' => '42', + 'bg-yellow' => '43', + 'bg-blue' => '44', + 'bg-magenta' => '45', + 'bg-cyan' => '46', + 'bg-white' => '47', + ]; + + public static function colorize(string $color, string $buffer): string + { + if (trim($buffer) === '') { + return $buffer; + } + + $codes = array_map('\trim', explode(',', $color)); + $styles = []; + + foreach ($codes as $code) { + if (isset(self::$ansiCodes[$code])) { + $styles[] = self::$ansiCodes[$code] ?? ''; + } + } + + if (empty($styles)) { + return $buffer; + } + + return self::optimizeColor(sprintf("\x1b[%sm", implode(';', $styles)) . $buffer . "\x1b[0m"); + } + + public static function colorizePath(string $path, ?string $prevPath = null, bool $colorizeFilename = false): string + { + if ($prevPath === null) { + $prevPath = ''; + } + + $path = explode(DIRECTORY_SEPARATOR, $path); + $prevPath = explode(DIRECTORY_SEPARATOR, $prevPath); + + for ($i = 0; $i < min(count($path), count($prevPath)); $i++) { + if ($path[$i] == $prevPath[$i]) { + $path[$i] = self::dim($path[$i]); + } + } + + if ($colorizeFilename) { + $last = count($path) - 1; + $path[$last] = preg_replace_callback( + '/([\-_\.]+|phpt$)/', + static function ($matches) + { + return self::dim($matches[0]); + }, + $path[$last], + ); + } + + return self::optimizeColor(implode(self::dim(DIRECTORY_SEPARATOR), $path)); + } + + public static function dim(string $buffer): string + { + if (trim($buffer) === '') { + return $buffer; + } + + return "\e[2m{$buffer}\e[22m"; + } + + public static function visualizeWhitespace(string $buffer, bool $visualizeEOL = false): string + { + $replaceMap = $visualizeEOL ? self::WHITESPACE_EOL_MAP : self::WHITESPACE_MAP; + + return preg_replace_callback('/\s+/', static function ($matches) use ($replaceMap) + { + return self::dim(strtr($matches[0], $replaceMap)); + }, $buffer); + } + + private static function optimizeColor(string $buffer): string + { + $patterns = [ + "/\e\\[22m\e\\[2m/" => '', + "/\e\\[([^m]*)m\e\\[([1-9][0-9;]*)m/" => "\e[$1;$2m", + "/(\e\\[[^m]*m)+(\e\\[0m)/" => '$2', + ]; + + return preg_replace(array_keys($patterns), array_values($patterns), $buffer); + } +} diff --git a/vendor/phpunit/phpunit/src/Util/ErrorHandler.php b/vendor/phpunit/phpunit/src/Util/ErrorHandler.php new file mode 100644 index 00000000..3bdf9bf1 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/ErrorHandler.php @@ -0,0 +1,156 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use const E_DEPRECATED; +use const E_NOTICE; +use const E_STRICT; +use const E_USER_DEPRECATED; +use const E_USER_NOTICE; +use const E_USER_WARNING; +use const E_WARNING; +use function error_reporting; +use function restore_error_handler; +use function set_error_handler; +use PHPUnit\Framework\Error\Deprecated; +use PHPUnit\Framework\Error\Error; +use PHPUnit\Framework\Error\Notice; +use PHPUnit\Framework\Error\Warning; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ErrorHandler +{ + /** + * @var bool + */ + private $convertDeprecationsToExceptions; + + /** + * @var bool + */ + private $convertErrorsToExceptions; + + /** + * @var bool + */ + private $convertNoticesToExceptions; + + /** + * @var bool + */ + private $convertWarningsToExceptions; + + /** + * @var bool + */ + private $registered = false; + + public static function invokeIgnoringWarnings(callable $callable) + { + set_error_handler( + static function ($errorNumber, $errorString) + { + if ($errorNumber === E_WARNING) { + return; + } + + return false; + }, + ); + + $result = $callable(); + + restore_error_handler(); + + return $result; + } + + public function __construct(bool $convertDeprecationsToExceptions, bool $convertErrorsToExceptions, bool $convertNoticesToExceptions, bool $convertWarningsToExceptions) + { + $this->convertDeprecationsToExceptions = $convertDeprecationsToExceptions; + $this->convertErrorsToExceptions = $convertErrorsToExceptions; + $this->convertNoticesToExceptions = $convertNoticesToExceptions; + $this->convertWarningsToExceptions = $convertWarningsToExceptions; + } + + public function __invoke(int $errorNumber, string $errorString, string $errorFile, int $errorLine): bool + { + /* + * Do not raise an exception when the error suppression operator (@) was used. + * + * @see https://github.com/sebastianbergmann/phpunit/issues/3739 + */ + if (!($errorNumber & error_reporting())) { + return false; + } + + switch ($errorNumber) { + case E_NOTICE: + case E_USER_NOTICE: + case E_STRICT: + if (!$this->convertNoticesToExceptions) { + return false; + } + + throw new Notice($errorString, $errorNumber, $errorFile, $errorLine); + + case E_WARNING: + case E_USER_WARNING: + if (!$this->convertWarningsToExceptions) { + return false; + } + + throw new Warning($errorString, $errorNumber, $errorFile, $errorLine); + + case E_DEPRECATED: + case E_USER_DEPRECATED: + if (!$this->convertDeprecationsToExceptions) { + return false; + } + + throw new Deprecated($errorString, $errorNumber, $errorFile, $errorLine); + + default: + if (!$this->convertErrorsToExceptions) { + return false; + } + + throw new Error($errorString, $errorNumber, $errorFile, $errorLine); + } + } + + public function register(): void + { + if ($this->registered) { + return; + } + + $oldErrorHandler = set_error_handler($this); + + if ($oldErrorHandler !== null) { + restore_error_handler(); + + return; + } + + $this->registered = true; + } + + public function unregister(): void + { + if (!$this->registered) { + return; + } + + restore_error_handler(); + } +} diff --git a/vendor/phpunit/phpunit/src/Util/Exception.php b/vendor/phpunit/phpunit/src/Util/Exception.php new file mode 100644 index 00000000..6bcb3d14 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/Exception.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use RuntimeException; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Exception extends RuntimeException implements \PHPUnit\Exception +{ +} diff --git a/vendor/phpunit/phpunit/src/Util/ExcludeList.php b/vendor/phpunit/phpunit/src/Util/ExcludeList.php new file mode 100644 index 00000000..d539ec57 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/ExcludeList.php @@ -0,0 +1,240 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use const DIRECTORY_SEPARATOR; +use function class_exists; +use function defined; +use function dirname; +use function is_dir; +use function realpath; +use function sprintf; +use function strpos; +use function sys_get_temp_dir; +use Composer\Autoload\ClassLoader; +use DeepCopy\DeepCopy; +use Doctrine\Instantiator\Instantiator; +use PharIo\Manifest\Manifest; +use PharIo\Version\Version as PharIoVersion; +use PhpParser\Parser; +use PHPUnit\Framework\TestCase; +use ReflectionClass; +use SebastianBergmann\CliParser\Parser as CliParser; +use SebastianBergmann\CodeCoverage\CodeCoverage; +use SebastianBergmann\CodeUnit\CodeUnit; +use SebastianBergmann\CodeUnitReverseLookup\Wizard; +use SebastianBergmann\Comparator\Comparator; +use SebastianBergmann\Complexity\Calculator; +use SebastianBergmann\Diff\Diff; +use SebastianBergmann\Environment\Runtime; +use SebastianBergmann\Exporter\Exporter; +use SebastianBergmann\FileIterator\Facade as FileIteratorFacade; +use SebastianBergmann\GlobalState\Snapshot; +use SebastianBergmann\Invoker\Invoker; +use SebastianBergmann\LinesOfCode\Counter; +use SebastianBergmann\ObjectEnumerator\Enumerator; +use SebastianBergmann\ObjectReflector\ObjectReflector; +use SebastianBergmann\RecursionContext\Context; +use SebastianBergmann\ResourceOperations\ResourceOperations; +use SebastianBergmann\Template\Template; +use SebastianBergmann\Timer\Timer; +use SebastianBergmann\Type\TypeName; +use SebastianBergmann\Version; +use TheSeer\Tokenizer\Tokenizer; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class ExcludeList +{ + /** + * @var array + */ + private const EXCLUDED_CLASS_NAMES = [ + // composer + ClassLoader::class => 1, + + // doctrine/instantiator + Instantiator::class => 1, + + // myclabs/deepcopy + DeepCopy::class => 1, + + // nikic/php-parser + Parser::class => 1, + + // phar-io/manifest + Manifest::class => 1, + + // phar-io/version + PharIoVersion::class => 1, + + // phpdocumentor/type-resolver + Type::class => 1, + + // phpunit/phpunit + TestCase::class => 2, + + // phpunit/php-code-coverage + CodeCoverage::class => 1, + + // phpunit/php-file-iterator + FileIteratorFacade::class => 1, + + // phpunit/php-invoker + Invoker::class => 1, + + // phpunit/php-text-template + Template::class => 1, + + // phpunit/php-timer + Timer::class => 1, + + // sebastian/cli-parser + CliParser::class => 1, + + // sebastian/code-unit + CodeUnit::class => 1, + + // sebastian/code-unit-reverse-lookup + Wizard::class => 1, + + // sebastian/comparator + Comparator::class => 1, + + // sebastian/complexity + Calculator::class => 1, + + // sebastian/diff + Diff::class => 1, + + // sebastian/environment + Runtime::class => 1, + + // sebastian/exporter + Exporter::class => 1, + + // sebastian/global-state + Snapshot::class => 1, + + // sebastian/lines-of-code + Counter::class => 1, + + // sebastian/object-enumerator + Enumerator::class => 1, + + // sebastian/object-reflector + ObjectReflector::class => 1, + + // sebastian/recursion-context + Context::class => 1, + + // sebastian/resource-operations + ResourceOperations::class => 1, + + // sebastian/type + TypeName::class => 1, + + // sebastian/version + Version::class => 1, + + // theseer/tokenizer + Tokenizer::class => 1, + ]; + + /** + * @var string[] + */ + private static $directories = []; + + /** + * @var bool + */ + private static $initialized = false; + + public static function addDirectory(string $directory): void + { + if (!is_dir($directory)) { + throw new Exception( + sprintf( + '"%s" is not a directory', + $directory, + ), + ); + } + + self::$directories[] = realpath($directory); + } + + /** + * @throws Exception + * + * @return string[] + */ + public function getExcludedDirectories(): array + { + $this->initialize(); + + return self::$directories; + } + + /** + * @throws Exception + */ + public function isExcluded(string $file): bool + { + if (defined('PHPUNIT_TESTSUITE')) { + return false; + } + + $this->initialize(); + + foreach (self::$directories as $directory) { + if (strpos($file, $directory) === 0) { + return true; + } + } + + return false; + } + + /** + * @throws Exception + */ + private function initialize(): void + { + if (self::$initialized) { + return; + } + + foreach (self::EXCLUDED_CLASS_NAMES as $className => $parent) { + if (!class_exists($className)) { + continue; + } + + $directory = (new ReflectionClass($className))->getFileName(); + + for ($i = 0; $i < $parent; $i++) { + $directory = dirname($directory); + } + + self::$directories[] = $directory; + } + + // Hide process isolation workaround on Windows. + if (DIRECTORY_SEPARATOR === '\\') { + // tempnam() prefix is limited to first 3 chars. + // @see https://php.net/manual/en/function.tempnam.php + self::$directories[] = sys_get_temp_dir() . '\\PHP'; + } + + self::$initialized = true; + } +} diff --git a/vendor/phpunit/phpunit/src/Util/FileLoader.php b/vendor/phpunit/phpunit/src/Util/FileLoader.php new file mode 100644 index 00000000..e0a66506 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/FileLoader.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use const DIRECTORY_SEPARATOR; +use function array_diff; +use function array_keys; +use function fopen; +use function get_defined_vars; +use function sprintf; +use function stream_resolve_include_path; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class FileLoader +{ + /** + * Checks if a PHP sourcecode file is readable. The sourcecode file is loaded through the load() method. + * + * As a fallback, PHP looks in the directory of the file executing the stream_resolve_include_path function. + * We do not want to load the Test.php file here, so skip it if it found that. + * PHP prioritizes the include_path setting, so if the current directory is in there, it will first look in the + * current working directory. + * + * @throws Exception + */ + public static function checkAndLoad(string $filename): string + { + $includePathFilename = stream_resolve_include_path($filename); + + $localFile = __DIR__ . DIRECTORY_SEPARATOR . $filename; + + if (!$includePathFilename || + $includePathFilename === $localFile || + !self::isReadable($includePathFilename)) { + throw new Exception( + sprintf('Cannot open file "%s".' . "\n", $filename), + ); + } + + self::load($includePathFilename); + + return $includePathFilename; + } + + /** + * Loads a PHP sourcefile. + */ + public static function load(string $filename): void + { + $oldVariableNames = array_keys(get_defined_vars()); + + /** + * @noinspection PhpIncludeInspection + * + * @psalm-suppress UnresolvableInclude + */ + include_once $filename; + + $newVariables = get_defined_vars(); + + foreach (array_diff(array_keys($newVariables), $oldVariableNames) as $variableName) { + if ($variableName !== 'oldVariableNames') { + $GLOBALS[$variableName] = $newVariables[$variableName]; + } + } + } + + /** + * @see https://github.com/sebastianbergmann/phpunit/pull/2751 + */ + private static function isReadable(string $filename): bool + { + return @fopen($filename, 'r') !== false; + } +} diff --git a/vendor/phpunit/phpunit/src/Util/Filesystem.php b/vendor/phpunit/phpunit/src/Util/Filesystem.php new file mode 100644 index 00000000..886829d2 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/Filesystem.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use const DIRECTORY_SEPARATOR; +use function is_dir; +use function mkdir; +use function str_replace; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Filesystem +{ + /** + * Maps class names to source file names. + * + * - PEAR CS: Foo_Bar_Baz -> Foo/Bar/Baz.php + * - Namespace: Foo\Bar\Baz -> Foo/Bar/Baz.php + */ + public static function classNameToFilename(string $className): string + { + return str_replace( + ['_', '\\'], + DIRECTORY_SEPARATOR, + $className, + ) . '.php'; + } + + public static function createDirectory(string $directory): bool + { + return !(!is_dir($directory) && !@mkdir($directory, 0777, true) && !is_dir($directory)); + } +} diff --git a/vendor/phpunit/phpunit/src/Util/Filter.php b/vendor/phpunit/phpunit/src/Util/Filter.php new file mode 100644 index 00000000..94b7e77d --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/Filter.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use function array_unshift; +use function defined; +use function in_array; +use function is_file; +use function realpath; +use function sprintf; +use function strpos; +use PHPUnit\Framework\Exception; +use PHPUnit\Framework\SyntheticError; +use Throwable; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Filter +{ + /** + * @throws Exception + */ + public static function getFilteredStacktrace(Throwable $t): string + { + $filteredStacktrace = ''; + + if ($t instanceof SyntheticError) { + $eTrace = $t->getSyntheticTrace(); + $eFile = $t->getSyntheticFile(); + $eLine = $t->getSyntheticLine(); + } elseif ($t instanceof Exception) { + $eTrace = $t->getSerializableTrace(); + $eFile = $t->getFile(); + $eLine = $t->getLine(); + } else { + if ($t->getPrevious()) { + $t = $t->getPrevious(); + } + + $eTrace = $t->getTrace(); + $eFile = $t->getFile(); + $eLine = $t->getLine(); + } + + if (!self::frameExists($eTrace, $eFile, $eLine)) { + array_unshift( + $eTrace, + ['file' => $eFile, 'line' => $eLine], + ); + } + + $prefix = defined('__PHPUNIT_PHAR_ROOT__') ? __PHPUNIT_PHAR_ROOT__ : false; + $excludeList = new ExcludeList; + + foreach ($eTrace as $frame) { + if (self::shouldPrintFrame($frame, $prefix, $excludeList)) { + $filteredStacktrace .= sprintf( + "%s:%s\n", + $frame['file'], + $frame['line'] ?? '?', + ); + } + } + + return $filteredStacktrace; + } + + private static function shouldPrintFrame(array $frame, $prefix, ExcludeList $excludeList): bool + { + if (!isset($frame['file'])) { + return false; + } + + $file = $frame['file']; + $fileIsNotPrefixed = $prefix === false || strpos($file, $prefix) !== 0; + + // @see https://github.com/sebastianbergmann/phpunit/issues/4033 + if (isset($GLOBALS['_SERVER']['SCRIPT_NAME'])) { + $script = realpath($GLOBALS['_SERVER']['SCRIPT_NAME']); + } else { + $script = ''; + } + + return is_file($file) && + self::fileIsExcluded($file, $excludeList) && + $fileIsNotPrefixed && + $file !== $script; + } + + private static function fileIsExcluded(string $file, ExcludeList $excludeList): bool + { + return (empty($GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST']) || + !in_array($file, $GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST'], true)) && + !$excludeList->isExcluded($file); + } + + private static function frameExists(array $trace, string $file, int $line): bool + { + foreach ($trace as $frame) { + if (isset($frame['file'], $frame['line']) && $frame['file'] === $file && $frame['line'] === $line) { + return true; + } + } + + return false; + } +} diff --git a/vendor/phpunit/phpunit/src/Util/GlobalState.php b/vendor/phpunit/phpunit/src/Util/GlobalState.php new file mode 100644 index 00000000..18b5a646 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/GlobalState.php @@ -0,0 +1,287 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use const PHP_MAJOR_VERSION; +use const PHP_MINOR_VERSION; +use function array_keys; +use function array_reverse; +use function array_shift; +use function defined; +use function get_defined_constants; +use function get_included_files; +use function in_array; +use function ini_get_all; +use function is_array; +use function is_file; +use function is_scalar; +use function preg_match; +use function serialize; +use function sprintf; +use function strpos; +use function strtr; +use function substr; +use function var_export; +use Closure; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class GlobalState +{ + /** + * @var string[] + */ + private const SUPER_GLOBAL_ARRAYS = [ + '_ENV', + '_POST', + '_GET', + '_COOKIE', + '_SERVER', + '_FILES', + '_REQUEST', + ]; + + /** + * @psalm-var array> + */ + private const DEPRECATED_INI_SETTINGS = [ + '7.3' => [ + 'iconv.input_encoding' => true, + 'iconv.output_encoding' => true, + 'iconv.internal_encoding' => true, + 'mbstring.func_overload' => true, + 'mbstring.http_input' => true, + 'mbstring.http_output' => true, + 'mbstring.internal_encoding' => true, + 'string.strip_tags' => true, + ], + + '7.4' => [ + 'iconv.input_encoding' => true, + 'iconv.output_encoding' => true, + 'iconv.internal_encoding' => true, + 'mbstring.func_overload' => true, + 'mbstring.http_input' => true, + 'mbstring.http_output' => true, + 'mbstring.internal_encoding' => true, + 'pdo_odbc.db2_instance_name' => true, + 'string.strip_tags' => true, + ], + + '8.0' => [ + 'iconv.input_encoding' => true, + 'iconv.output_encoding' => true, + 'iconv.internal_encoding' => true, + 'mbstring.http_input' => true, + 'mbstring.http_output' => true, + 'mbstring.internal_encoding' => true, + ], + + '8.1' => [ + 'auto_detect_line_endings' => true, + 'filter.default' => true, + 'iconv.input_encoding' => true, + 'iconv.output_encoding' => true, + 'iconv.internal_encoding' => true, + 'mbstring.http_input' => true, + 'mbstring.http_output' => true, + 'mbstring.internal_encoding' => true, + 'oci8.old_oci_close_semantics' => true, + ], + + '8.2' => [ + 'auto_detect_line_endings' => true, + 'filter.default' => true, + 'iconv.input_encoding' => true, + 'iconv.output_encoding' => true, + 'iconv.internal_encoding' => true, + 'mbstring.http_input' => true, + 'mbstring.http_output' => true, + 'mbstring.internal_encoding' => true, + 'oci8.old_oci_close_semantics' => true, + ], + + '8.3' => [ + 'auto_detect_line_endings' => true, + 'filter.default' => true, + 'iconv.input_encoding' => true, + 'iconv.output_encoding' => true, + 'iconv.internal_encoding' => true, + 'mbstring.http_input' => true, + 'mbstring.http_output' => true, + 'mbstring.internal_encoding' => true, + 'oci8.old_oci_close_semantics' => true, + ], + ]; + + /** + * @throws Exception + */ + public static function getIncludedFilesAsString(): string + { + return self::processIncludedFilesAsString(get_included_files()); + } + + /** + * @param string[] $files + * + * @throws Exception + */ + public static function processIncludedFilesAsString(array $files): string + { + $excludeList = new ExcludeList; + $prefix = false; + $result = ''; + + if (defined('__PHPUNIT_PHAR__')) { + $prefix = 'phar://' . __PHPUNIT_PHAR__ . '/'; + } + + // Do not process bootstrap script + array_shift($files); + + // If bootstrap script was a Composer bin proxy, skip the second entry as well + if (substr(strtr($files[0], '\\', '/'), -24) === '/phpunit/phpunit/phpunit') { + array_shift($files); + } + + foreach (array_reverse($files) as $file) { + if (!empty($GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST']) && + in_array($file, $GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST'], true)) { + continue; + } + + if ($prefix !== false && strpos($file, $prefix) === 0) { + continue; + } + + // Skip virtual file system protocols + if (preg_match('/^(vfs|phpvfs[a-z0-9]+):/', $file)) { + continue; + } + + if (!$excludeList->isExcluded($file) && is_file($file)) { + $result = 'require_once \'' . $file . "';\n" . $result; + } + } + + return $result; + } + + public static function getIniSettingsAsString(): string + { + $result = ''; + + foreach (ini_get_all(null, false) as $key => $value) { + if (self::isIniSettingDeprecated($key)) { + continue; + } + + $result .= sprintf( + '@ini_set(%s, %s);' . "\n", + self::exportVariable($key), + self::exportVariable((string) $value), + ); + } + + return $result; + } + + public static function getConstantsAsString(): string + { + $constants = get_defined_constants(true); + $result = ''; + + if (isset($constants['user'])) { + foreach ($constants['user'] as $name => $value) { + $result .= sprintf( + 'if (!defined(\'%s\')) define(\'%s\', %s);' . "\n", + $name, + $name, + self::exportVariable($value), + ); + } + } + + return $result; + } + + public static function getGlobalsAsString(): string + { + $result = ''; + + foreach (self::SUPER_GLOBAL_ARRAYS as $superGlobalArray) { + if (isset($GLOBALS[$superGlobalArray]) && is_array($GLOBALS[$superGlobalArray])) { + foreach (array_keys($GLOBALS[$superGlobalArray]) as $key) { + if ($GLOBALS[$superGlobalArray][$key] instanceof Closure) { + continue; + } + + $result .= sprintf( + '$GLOBALS[\'%s\'][\'%s\'] = %s;' . "\n", + $superGlobalArray, + $key, + self::exportVariable($GLOBALS[$superGlobalArray][$key]), + ); + } + } + } + + $excludeList = self::SUPER_GLOBAL_ARRAYS; + $excludeList[] = 'GLOBALS'; + + foreach (array_keys($GLOBALS) as $key) { + if (!$GLOBALS[$key] instanceof Closure && !in_array($key, $excludeList, true)) { + $result .= sprintf( + '$GLOBALS[\'%s\'] = %s;' . "\n", + $key, + self::exportVariable($GLOBALS[$key]), + ); + } + } + + return $result; + } + + private static function exportVariable($variable): string + { + if (is_scalar($variable) || $variable === null || + (is_array($variable) && self::arrayOnlyContainsScalars($variable))) { + return var_export($variable, true); + } + + return 'unserialize(' . var_export(serialize($variable), true) . ')'; + } + + private static function arrayOnlyContainsScalars(array $array): bool + { + $result = true; + + foreach ($array as $element) { + if (is_array($element)) { + $result = self::arrayOnlyContainsScalars($element); + } elseif (!is_scalar($element) && $element !== null) { + $result = false; + } + + if (!$result) { + break; + } + } + + return $result; + } + + private static function isIniSettingDeprecated(string $iniSetting): bool + { + return isset(self::DEPRECATED_INI_SETTINGS[PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION][$iniSetting]); + } +} diff --git a/vendor/phpunit/phpunit/src/Util/InvalidDataSetException.php b/vendor/phpunit/phpunit/src/Util/InvalidDataSetException.php new file mode 100644 index 00000000..3493d113 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/InvalidDataSetException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use RuntimeException; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvalidDataSetException extends RuntimeException implements \PHPUnit\Exception +{ +} diff --git a/vendor/phpunit/phpunit/src/Util/Json.php b/vendor/phpunit/phpunit/src/Util/Json.php new file mode 100644 index 00000000..0428bc03 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/Json.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use const JSON_PRETTY_PRINT; +use const JSON_UNESCAPED_SLASHES; +use const JSON_UNESCAPED_UNICODE; +use function count; +use function is_array; +use function is_object; +use function json_decode; +use function json_encode; +use function json_last_error; +use function ksort; +use PHPUnit\Framework\Exception; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Json +{ + /** + * Prettify json string. + * + * @throws Exception + */ + public static function prettify(string $json): string + { + $decodedJson = json_decode($json, false); + + if (json_last_error()) { + throw new Exception( + 'Cannot prettify invalid json', + ); + } + + return json_encode($decodedJson, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } + + /** + * To allow comparison of JSON strings, first process them into a consistent + * format so that they can be compared as strings. + * + * @return array ($error, $canonicalized_json) The $error parameter is used + * to indicate an error decoding the json. This is used to avoid ambiguity + * with JSON strings consisting entirely of 'null' or 'false'. + */ + public static function canonicalize(string $json): array + { + $decodedJson = json_decode($json); + + if (json_last_error()) { + return [true, null]; + } + + self::recursiveSort($decodedJson); + + $reencodedJson = json_encode($decodedJson); + + return [false, $reencodedJson]; + } + + /** + * JSON object keys are unordered while PHP array keys are ordered. + * + * Sort all array keys to ensure both the expected and actual values have + * their keys in the same order. + */ + private static function recursiveSort(&$json): void + { + if (!is_array($json)) { + // If the object is not empty, change it to an associative array + // so we can sort the keys (and we will still re-encode it + // correctly, since PHP encodes associative arrays as JSON objects.) + // But EMPTY objects MUST remain empty objects. (Otherwise we will + // re-encode it as a JSON array rather than a JSON object.) + // See #2919. + if (is_object($json) && count((array) $json) > 0) { + $json = (array) $json; + } else { + return; + } + } + + ksort($json); + + foreach ($json as $key => &$value) { + self::recursiveSort($value); + } + } +} diff --git a/vendor/phpunit/phpunit/src/Util/Log/JUnit.php b/vendor/phpunit/phpunit/src/Util/Log/JUnit.php new file mode 100644 index 00000000..c7ba4fc5 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/Log/JUnit.php @@ -0,0 +1,424 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\Log; + +use function class_exists; +use function get_class; +use function method_exists; +use function sprintf; +use function str_replace; +use function trim; +use DOMDocument; +use DOMElement; +use PHPUnit\Framework\AssertionFailedError; +use PHPUnit\Framework\ExceptionWrapper; +use PHPUnit\Framework\SelfDescribing; +use PHPUnit\Framework\Test; +use PHPUnit\Framework\TestFailure; +use PHPUnit\Framework\TestListener; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Framework\Warning; +use PHPUnit\Util\Exception; +use PHPUnit\Util\Filter; +use PHPUnit\Util\Printer; +use PHPUnit\Util\Xml; +use ReflectionClass; +use ReflectionException; +use Throwable; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class JUnit extends Printer implements TestListener +{ + /** + * @var DOMDocument + */ + private $document; + + /** + * @var DOMElement + */ + private $root; + + /** + * @var bool + */ + private $reportRiskyTests = false; + + /** + * @var DOMElement[] + */ + private $testSuites = []; + + /** + * @var int[] + */ + private $testSuiteTests = [0]; + + /** + * @var int[] + */ + private $testSuiteAssertions = [0]; + + /** + * @var int[] + */ + private $testSuiteErrors = [0]; + + /** + * @var int[] + */ + private $testSuiteWarnings = [0]; + + /** + * @var int[] + */ + private $testSuiteFailures = [0]; + + /** + * @var int[] + */ + private $testSuiteSkipped = [0]; + + /** + * @var int[] + */ + private $testSuiteTimes = [0]; + + /** + * @var int + */ + private $testSuiteLevel = 0; + + /** + * @var DOMElement + */ + private $currentTestCase; + + /** + * @param null|mixed $out + */ + public function __construct($out = null, bool $reportRiskyTests = false) + { + $this->document = new DOMDocument('1.0', 'UTF-8'); + $this->document->formatOutput = true; + + $this->root = $this->document->createElement('testsuites'); + $this->document->appendChild($this->root); + + parent::__construct($out); + + $this->reportRiskyTests = $reportRiskyTests; + } + + /** + * Flush buffer and close output. + */ + public function flush(): void + { + $this->write($this->getXML()); + + parent::flush(); + } + + /** + * An error occurred. + */ + public function addError(Test $test, Throwable $t, float $time): void + { + $this->doAddFault($test, $t, 'error'); + $this->testSuiteErrors[$this->testSuiteLevel]++; + } + + /** + * A warning occurred. + */ + public function addWarning(Test $test, Warning $e, float $time): void + { + $this->doAddFault($test, $e, 'warning'); + $this->testSuiteWarnings[$this->testSuiteLevel]++; + } + + /** + * A failure occurred. + */ + public function addFailure(Test $test, AssertionFailedError $e, float $time): void + { + $this->doAddFault($test, $e, 'failure'); + $this->testSuiteFailures[$this->testSuiteLevel]++; + } + + /** + * Incomplete test. + */ + public function addIncompleteTest(Test $test, Throwable $t, float $time): void + { + $this->doAddSkipped(); + } + + /** + * Risky test. + */ + public function addRiskyTest(Test $test, Throwable $t, float $time): void + { + if (!$this->reportRiskyTests) { + return; + } + + $this->doAddFault($test, $t, 'error'); + $this->testSuiteErrors[$this->testSuiteLevel]++; + } + + /** + * Skipped test. + */ + public function addSkippedTest(Test $test, Throwable $t, float $time): void + { + $this->doAddSkipped(); + } + + /** + * A testsuite started. + */ + public function startTestSuite(TestSuite $suite): void + { + $testSuite = $this->document->createElement('testsuite'); + $testSuite->setAttribute('name', $suite->getName()); + + if (class_exists($suite->getName(), false)) { + try { + $class = new ReflectionClass($suite->getName()); + + $testSuite->setAttribute('file', $class->getFileName()); + } catch (ReflectionException $e) { + } + } + + if ($this->testSuiteLevel > 0) { + $this->testSuites[$this->testSuiteLevel]->appendChild($testSuite); + } else { + $this->root->appendChild($testSuite); + } + + $this->testSuiteLevel++; + $this->testSuites[$this->testSuiteLevel] = $testSuite; + $this->testSuiteTests[$this->testSuiteLevel] = 0; + $this->testSuiteAssertions[$this->testSuiteLevel] = 0; + $this->testSuiteErrors[$this->testSuiteLevel] = 0; + $this->testSuiteWarnings[$this->testSuiteLevel] = 0; + $this->testSuiteFailures[$this->testSuiteLevel] = 0; + $this->testSuiteSkipped[$this->testSuiteLevel] = 0; + $this->testSuiteTimes[$this->testSuiteLevel] = 0; + } + + /** + * A testsuite ended. + */ + public function endTestSuite(TestSuite $suite): void + { + $this->testSuites[$this->testSuiteLevel]->setAttribute( + 'tests', + (string) $this->testSuiteTests[$this->testSuiteLevel], + ); + + $this->testSuites[$this->testSuiteLevel]->setAttribute( + 'assertions', + (string) $this->testSuiteAssertions[$this->testSuiteLevel], + ); + + $this->testSuites[$this->testSuiteLevel]->setAttribute( + 'errors', + (string) $this->testSuiteErrors[$this->testSuiteLevel], + ); + + $this->testSuites[$this->testSuiteLevel]->setAttribute( + 'warnings', + (string) $this->testSuiteWarnings[$this->testSuiteLevel], + ); + + $this->testSuites[$this->testSuiteLevel]->setAttribute( + 'failures', + (string) $this->testSuiteFailures[$this->testSuiteLevel], + ); + + $this->testSuites[$this->testSuiteLevel]->setAttribute( + 'skipped', + (string) $this->testSuiteSkipped[$this->testSuiteLevel], + ); + + $this->testSuites[$this->testSuiteLevel]->setAttribute( + 'time', + sprintf('%F', $this->testSuiteTimes[$this->testSuiteLevel]), + ); + + if ($this->testSuiteLevel > 1) { + $this->testSuiteTests[$this->testSuiteLevel - 1] += $this->testSuiteTests[$this->testSuiteLevel]; + $this->testSuiteAssertions[$this->testSuiteLevel - 1] += $this->testSuiteAssertions[$this->testSuiteLevel]; + $this->testSuiteErrors[$this->testSuiteLevel - 1] += $this->testSuiteErrors[$this->testSuiteLevel]; + $this->testSuiteWarnings[$this->testSuiteLevel - 1] += $this->testSuiteWarnings[$this->testSuiteLevel]; + $this->testSuiteFailures[$this->testSuiteLevel - 1] += $this->testSuiteFailures[$this->testSuiteLevel]; + $this->testSuiteSkipped[$this->testSuiteLevel - 1] += $this->testSuiteSkipped[$this->testSuiteLevel]; + $this->testSuiteTimes[$this->testSuiteLevel - 1] += $this->testSuiteTimes[$this->testSuiteLevel]; + } + + $this->testSuiteLevel--; + } + + /** + * A test started. + */ + public function startTest(Test $test): void + { + $usesDataprovider = false; + + if (method_exists($test, 'usesDataProvider')) { + $usesDataprovider = $test->usesDataProvider(); + } + + $testCase = $this->document->createElement('testcase'); + $testCase->setAttribute('name', $test->getName()); + + try { + $class = new ReflectionClass($test); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + $methodName = $test->getName(!$usesDataprovider); + + if ($class->hasMethod($methodName)) { + try { + $method = $class->getMethod($methodName); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + $testCase->setAttribute('class', $class->getName()); + $testCase->setAttribute('classname', str_replace('\\', '.', $class->getName())); + $testCase->setAttribute('file', $class->getFileName()); + $testCase->setAttribute('line', (string) $method->getStartLine()); + } + + $this->currentTestCase = $testCase; + } + + /** + * A test ended. + */ + public function endTest(Test $test, float $time): void + { + $numAssertions = 0; + + if (method_exists($test, 'getNumAssertions')) { + $numAssertions = $test->getNumAssertions(); + } + + $this->testSuiteAssertions[$this->testSuiteLevel] += $numAssertions; + + $this->currentTestCase->setAttribute( + 'assertions', + (string) $numAssertions, + ); + + $this->currentTestCase->setAttribute( + 'time', + sprintf('%F', $time), + ); + + $this->testSuites[$this->testSuiteLevel]->appendChild( + $this->currentTestCase, + ); + + $this->testSuiteTests[$this->testSuiteLevel]++; + $this->testSuiteTimes[$this->testSuiteLevel] += $time; + + $testOutput = ''; + + if (method_exists($test, 'hasOutput') && method_exists($test, 'getActualOutput')) { + $testOutput = $test->hasOutput() ? $test->getActualOutput() : ''; + } + + if (!empty($testOutput)) { + $systemOut = $this->document->createElement( + 'system-out', + Xml::prepareString($testOutput), + ); + + $this->currentTestCase->appendChild($systemOut); + } + + $this->currentTestCase = null; + } + + /** + * Returns the XML as a string. + */ + public function getXML(): string + { + return $this->document->saveXML(); + } + + private function doAddFault(Test $test, Throwable $t, string $type): void + { + if ($this->currentTestCase === null) { + return; + } + + if ($test instanceof SelfDescribing) { + $buffer = $test->toString() . "\n"; + } else { + $buffer = ''; + } + + $buffer .= trim( + TestFailure::exceptionToString($t) . "\n" . + Filter::getFilteredStacktrace($t), + ); + + $fault = $this->document->createElement( + $type, + Xml::prepareString($buffer), + ); + + if ($t instanceof ExceptionWrapper) { + $fault->setAttribute('type', $t->getClassName()); + } else { + $fault->setAttribute('type', get_class($t)); + } + + $this->currentTestCase->appendChild($fault); + } + + private function doAddSkipped(): void + { + if ($this->currentTestCase === null) { + return; + } + + $skipped = $this->document->createElement('skipped'); + + $this->currentTestCase->appendChild($skipped); + + $this->testSuiteSkipped[$this->testSuiteLevel]++; + } +} diff --git a/vendor/phpunit/phpunit/src/Util/Log/TeamCity.php b/vendor/phpunit/phpunit/src/Util/Log/TeamCity.php new file mode 100644 index 00000000..30375bd3 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/Log/TeamCity.php @@ -0,0 +1,383 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\Log; + +use function class_exists; +use function count; +use function explode; +use function get_class; +use function getmypid; +use function ini_get; +use function is_bool; +use function is_scalar; +use function method_exists; +use function print_r; +use function round; +use function str_replace; +use function stripos; +use PHPUnit\Framework\AssertionFailedError; +use PHPUnit\Framework\ExceptionWrapper; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\Test; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\TestFailure; +use PHPUnit\Framework\TestResult; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Framework\Warning; +use PHPUnit\TextUI\DefaultResultPrinter; +use PHPUnit\Util\Exception; +use PHPUnit\Util\Filter; +use ReflectionClass; +use ReflectionException; +use SebastianBergmann\Comparator\ComparisonFailure; +use Throwable; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TeamCity extends DefaultResultPrinter +{ + /** + * @var bool + */ + private $isSummaryTestCountPrinted = false; + + /** + * @var string + */ + private $startedTestName; + + /** + * @var false|int + */ + private $flowId; + + public function printResult(TestResult $result): void + { + $this->printHeader($result); + $this->printFooter($result); + } + + /** + * An error occurred. + */ + public function addError(Test $test, Throwable $t, float $time): void + { + $this->printEvent( + 'testFailed', + [ + 'name' => $test->getName(), + 'message' => self::getMessage($t), + 'details' => self::getDetails($t), + 'duration' => self::toMilliseconds($time), + ], + ); + } + + /** + * A warning occurred. + */ + public function addWarning(Test $test, Warning $e, float $time): void + { + $this->write(self::getMessage($e) . PHP_EOL); + } + + /** + * A failure occurred. + */ + public function addFailure(Test $test, AssertionFailedError $e, float $time): void + { + $parameters = [ + 'name' => $test->getName(), + 'message' => self::getMessage($e), + 'details' => self::getDetails($e), + 'duration' => self::toMilliseconds($time), + ]; + + if ($e instanceof ExpectationFailedException) { + $comparisonFailure = $e->getComparisonFailure(); + + if ($comparisonFailure instanceof ComparisonFailure) { + $expectedString = $comparisonFailure->getExpectedAsString(); + + if ($expectedString === null || empty($expectedString)) { + $expectedString = self::getPrimitiveValueAsString($comparisonFailure->getExpected()); + } + + $actualString = $comparisonFailure->getActualAsString(); + + if ($actualString === null || empty($actualString)) { + $actualString = self::getPrimitiveValueAsString($comparisonFailure->getActual()); + } + + if ($actualString !== null && $expectedString !== null) { + $parameters['type'] = 'comparisonFailure'; + $parameters['actual'] = $actualString; + $parameters['expected'] = $expectedString; + } + } + } + + $this->printEvent('testFailed', $parameters); + } + + /** + * Incomplete test. + */ + public function addIncompleteTest(Test $test, Throwable $t, float $time): void + { + $this->printIgnoredTest($test->getName(), $t, $time); + } + + /** + * Risky test. + */ + public function addRiskyTest(Test $test, Throwable $t, float $time): void + { + $this->addError($test, $t, $time); + } + + /** + * Skipped test. + */ + public function addSkippedTest(Test $test, Throwable $t, float $time): void + { + $testName = $test->getName(); + + if ($this->startedTestName !== $testName) { + $this->startTest($test); + $this->printIgnoredTest($testName, $t, $time); + $this->endTest($test, $time); + } else { + $this->printIgnoredTest($testName, $t, $time); + } + } + + public function printIgnoredTest(string $testName, Throwable $t, float $time): void + { + $this->printEvent( + 'testIgnored', + [ + 'name' => $testName, + 'message' => self::getMessage($t), + 'details' => self::getDetails($t), + 'duration' => self::toMilliseconds($time), + ], + ); + } + + /** + * A testsuite started. + */ + public function startTestSuite(TestSuite $suite): void + { + if (stripos(ini_get('disable_functions'), 'getmypid') === false) { + $this->flowId = getmypid(); + } else { + $this->flowId = false; + } + + if (!$this->isSummaryTestCountPrinted) { + $this->isSummaryTestCountPrinted = true; + + $this->printEvent( + 'testCount', + ['count' => count($suite)], + ); + } + + $suiteName = $suite->getName(); + + if (empty($suiteName)) { + return; + } + + $parameters = ['name' => $suiteName]; + + if (class_exists($suiteName, false)) { + $fileName = self::getFileName($suiteName); + $parameters['locationHint'] = "php_qn://{$fileName}::\\{$suiteName}"; + } else { + $split = explode('::', $suiteName); + + if (count($split) === 2 && class_exists($split[0]) && method_exists($split[0], $split[1])) { + $fileName = self::getFileName($split[0]); + $parameters['locationHint'] = "php_qn://{$fileName}::\\{$suiteName}"; + $parameters['name'] = $split[1]; + } + } + + $this->printEvent('testSuiteStarted', $parameters); + } + + /** + * A testsuite ended. + */ + public function endTestSuite(TestSuite $suite): void + { + $suiteName = $suite->getName(); + + if (empty($suiteName)) { + return; + } + + $parameters = ['name' => $suiteName]; + + if (!class_exists($suiteName, false)) { + $split = explode('::', $suiteName); + + if (count($split) === 2 && class_exists($split[0]) && method_exists($split[0], $split[1])) { + $parameters['name'] = $split[1]; + } + } + + $this->printEvent('testSuiteFinished', $parameters); + } + + /** + * A test started. + */ + public function startTest(Test $test): void + { + $testName = $test->getName(); + $this->startedTestName = $testName; + $params = ['name' => $testName]; + + if ($test instanceof TestCase) { + $className = get_class($test); + $fileName = self::getFileName($className); + $params['locationHint'] = "php_qn://{$fileName}::\\{$className}::{$testName}"; + } + + $this->printEvent('testStarted', $params); + } + + /** + * A test ended. + */ + public function endTest(Test $test, float $time): void + { + parent::endTest($test, $time); + + $this->printEvent( + 'testFinished', + [ + 'name' => $test->getName(), + 'duration' => self::toMilliseconds($time), + ], + ); + } + + protected function writeProgress(string $progress): void + { + } + + private function printEvent(string $eventName, array $params = []): void + { + $this->write("\n##teamcity[{$eventName}"); + + if ($this->flowId) { + $params['flowId'] = $this->flowId; + } + + foreach ($params as $key => $value) { + $escapedValue = self::escapeValue((string) $value); + $this->write(" {$key}='{$escapedValue}'"); + } + + $this->write("]\n"); + } + + private static function getMessage(Throwable $t): string + { + $message = ''; + + if ($t instanceof ExceptionWrapper) { + if ($t->getClassName() !== '') { + $message .= $t->getClassName(); + } + + if ($message !== '' && $t->getMessage() !== '') { + $message .= ' : '; + } + } + + return $message . $t->getMessage(); + } + + private static function getDetails(Throwable $t): string + { + $stackTrace = Filter::getFilteredStacktrace($t); + $previous = $t instanceof ExceptionWrapper ? $t->getPreviousWrapped() : $t->getPrevious(); + + while ($previous) { + $stackTrace .= "\nCaused by\n" . + TestFailure::exceptionToString($previous) . "\n" . + Filter::getFilteredStacktrace($previous); + + $previous = $previous instanceof ExceptionWrapper ? + $previous->getPreviousWrapped() : $previous->getPrevious(); + } + + return ' ' . str_replace("\n", "\n ", $stackTrace); + } + + private static function getPrimitiveValueAsString($value): ?string + { + if ($value === null) { + return 'null'; + } + + if (is_bool($value)) { + return $value ? 'true' : 'false'; + } + + if (is_scalar($value)) { + return print_r($value, true); + } + + return null; + } + + private static function escapeValue(string $text): string + { + return str_replace( + ['|', "'", "\n", "\r", ']', '['], + ['||', "|'", '|n', '|r', '|]', '|['], + $text, + ); + } + + /** + * @param string $className + */ + private static function getFileName($className): string + { + try { + return (new ReflectionClass($className))->getFileName(); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + } + + /** + * @param float $time microseconds + */ + private static function toMilliseconds(float $time): int + { + return (int) round($time * 1000); + } +} diff --git a/vendor/phpunit/phpunit/src/Util/PHP/AbstractPhpProcess.php b/vendor/phpunit/phpunit/src/Util/PHP/AbstractPhpProcess.php new file mode 100644 index 00000000..0405d1f3 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/PHP/AbstractPhpProcess.php @@ -0,0 +1,426 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\PHP; + +use const DIRECTORY_SEPARATOR; +use const PHP_SAPI; +use function array_keys; +use function array_merge; +use function assert; +use function escapeshellarg; +use function file_exists; +use function file_get_contents; +use function ini_get_all; +use function restore_error_handler; +use function set_error_handler; +use function sprintf; +use function str_replace; +use function strpos; +use function strrpos; +use function substr; +use function trim; +use function unlink; +use function unserialize; +use __PHP_Incomplete_Class; +use ErrorException; +use PHPUnit\Framework\AssertionFailedError; +use PHPUnit\Framework\Exception; +use PHPUnit\Framework\SyntheticError; +use PHPUnit\Framework\Test; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\TestFailure; +use PHPUnit\Framework\TestResult; +use SebastianBergmann\Environment\Runtime; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract class AbstractPhpProcess +{ + /** + * @var Runtime + */ + protected $runtime; + + /** + * @var bool + */ + protected $stderrRedirection = false; + + /** + * @var string + */ + protected $stdin = ''; + + /** + * @var string + */ + protected $args = ''; + + /** + * @var array + */ + protected $env = []; + + /** + * @var int + */ + protected $timeout = 0; + + public static function factory(): self + { + if (DIRECTORY_SEPARATOR === '\\') { + return new WindowsPhpProcess; + } + + return new DefaultPhpProcess; + } + + public function __construct() + { + $this->runtime = new Runtime; + } + + /** + * Defines if should use STDERR redirection or not. + * + * Then $stderrRedirection is TRUE, STDERR is redirected to STDOUT. + */ + public function setUseStderrRedirection(bool $stderrRedirection): void + { + $this->stderrRedirection = $stderrRedirection; + } + + /** + * Returns TRUE if uses STDERR redirection or FALSE if not. + */ + public function useStderrRedirection(): bool + { + return $this->stderrRedirection; + } + + /** + * Sets the input string to be sent via STDIN. + */ + public function setStdin(string $stdin): void + { + $this->stdin = $stdin; + } + + /** + * Returns the input string to be sent via STDIN. + */ + public function getStdin(): string + { + return $this->stdin; + } + + /** + * Sets the string of arguments to pass to the php job. + */ + public function setArgs(string $args): void + { + $this->args = $args; + } + + /** + * Returns the string of arguments to pass to the php job. + */ + public function getArgs(): string + { + return $this->args; + } + + /** + * Sets the array of environment variables to start the child process with. + * + * @param array $env + */ + public function setEnv(array $env): void + { + $this->env = $env; + } + + /** + * Returns the array of environment variables to start the child process with. + */ + public function getEnv(): array + { + return $this->env; + } + + /** + * Sets the amount of seconds to wait before timing out. + */ + public function setTimeout(int $timeout): void + { + $this->timeout = $timeout; + } + + /** + * Returns the amount of seconds to wait before timing out. + */ + public function getTimeout(): int + { + return $this->timeout; + } + + /** + * Runs a single test in a separate PHP process. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function runTestJob(string $job, Test $test, TestResult $result, string $processResultFile): void + { + $result->startTest($test); + + $processResult = ''; + $_result = $this->runJob($job); + + if (file_exists($processResultFile)) { + $processResult = file_get_contents($processResultFile); + + @unlink($processResultFile); + } + + $this->processChildResult( + $test, + $result, + $processResult, + $_result['stderr'], + ); + } + + /** + * Returns the command based into the configurations. + */ + public function getCommand(array $settings, string $file = null): string + { + $command = $this->runtime->getBinary(); + + if ($this->runtime->hasPCOV()) { + $settings = array_merge( + $settings, + $this->runtime->getCurrentSettings( + array_keys(ini_get_all('pcov')), + ), + ); + } elseif ($this->runtime->hasXdebug()) { + $settings = array_merge( + $settings, + $this->runtime->getCurrentSettings( + array_keys(ini_get_all('xdebug')), + ), + ); + } + + $command .= $this->settingsToParameters($settings); + + if (PHP_SAPI === 'phpdbg') { + $command .= ' -qrr'; + + if (!$file) { + $command .= 's='; + } + } + + if ($file) { + $command .= ' ' . escapeshellarg($file); + } + + if ($this->args) { + if (!$file) { + $command .= ' --'; + } + $command .= ' ' . $this->args; + } + + if ($this->stderrRedirection) { + $command .= ' 2>&1'; + } + + return $command; + } + + /** + * Runs a single job (PHP code) using a separate PHP process. + */ + abstract public function runJob(string $job, array $settings = []): array; + + protected function settingsToParameters(array $settings): string + { + $buffer = ''; + + foreach ($settings as $setting) { + $buffer .= ' -d ' . escapeshellarg($setting); + } + + return $buffer; + } + + /** + * Processes the TestResult object from an isolated process. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + private function processChildResult(Test $test, TestResult $result, string $stdout, string $stderr): void + { + $time = 0; + + if (!empty($stderr)) { + $result->addError( + $test, + new Exception(trim($stderr)), + $time, + ); + } else { + set_error_handler( + /** + * @throws ErrorException + */ + static function ($errno, $errstr, $errfile, $errline): void + { + throw new ErrorException($errstr, $errno, $errno, $errfile, $errline); + }, + ); + + try { + if (strpos($stdout, "#!/usr/bin/env php\n") === 0) { + $stdout = substr($stdout, 19); + } + + $childResult = unserialize(str_replace("#!/usr/bin/env php\n", '', $stdout)); + restore_error_handler(); + + if ($childResult === false) { + $result->addFailure( + $test, + new AssertionFailedError('Test was run in child process and ended unexpectedly'), + $time, + ); + } + } catch (ErrorException $e) { + restore_error_handler(); + $childResult = false; + + $result->addError( + $test, + new Exception(trim($stdout), 0, $e), + $time, + ); + } + + if ($childResult !== false) { + if (!empty($childResult['output'])) { + $output = $childResult['output']; + } + + /* @var TestCase $test */ + + $test->setResult($childResult['testResult']); + $test->addToAssertionCount($childResult['numAssertions']); + + $childResult = $childResult['result']; + assert($childResult instanceof TestResult); + + if ($result->getCollectCodeCoverageInformation()) { + $result->getCodeCoverage()->merge( + $childResult->getCodeCoverage(), + ); + } + + $time = $childResult->time(); + $notImplemented = $childResult->notImplemented(); + $risky = $childResult->risky(); + $skipped = $childResult->skipped(); + $errors = $childResult->errors(); + $warnings = $childResult->warnings(); + $failures = $childResult->failures(); + + if (!empty($notImplemented)) { + $result->addError( + $test, + $this->getException($notImplemented[0]), + $time, + ); + } elseif (!empty($risky)) { + $result->addError( + $test, + $this->getException($risky[0]), + $time, + ); + } elseif (!empty($skipped)) { + $result->addError( + $test, + $this->getException($skipped[0]), + $time, + ); + } elseif (!empty($errors)) { + $result->addError( + $test, + $this->getException($errors[0]), + $time, + ); + } elseif (!empty($warnings)) { + $result->addWarning( + $test, + $this->getException($warnings[0]), + $time, + ); + } elseif (!empty($failures)) { + $result->addFailure( + $test, + $this->getException($failures[0]), + $time, + ); + } + } + } + + $result->endTest($test, $time); + + if (!empty($output)) { + print $output; + } + } + + /** + * Gets the thrown exception from a PHPUnit\Framework\TestFailure. + * + * @see https://github.com/sebastianbergmann/phpunit/issues/74 + */ + private function getException(TestFailure $error): Exception + { + $exception = $error->thrownException(); + + if ($exception instanceof __PHP_Incomplete_Class) { + $exceptionArray = []; + + foreach ((array) $exception as $key => $value) { + $key = substr($key, strrpos($key, "\0") + 1); + $exceptionArray[$key] = $value; + } + + $exception = new SyntheticError( + sprintf( + '%s: %s', + $exceptionArray['_PHP_Incomplete_Class_Name'], + $exceptionArray['message'], + ), + $exceptionArray['code'], + $exceptionArray['file'], + $exceptionArray['line'], + $exceptionArray['trace'], + ); + } + + return $exception; + } +} diff --git a/vendor/phpunit/phpunit/src/Util/PHP/DefaultPhpProcess.php b/vendor/phpunit/phpunit/src/Util/PHP/DefaultPhpProcess.php new file mode 100644 index 00000000..64974f1d --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/PHP/DefaultPhpProcess.php @@ -0,0 +1,236 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\PHP; + +use function array_merge; +use function fclose; +use function file_put_contents; +use function fread; +use function fwrite; +use function is_array; +use function is_resource; +use function proc_close; +use function proc_open; +use function proc_terminate; +use function rewind; +use function sprintf; +use function stream_get_contents; +use function stream_select; +use function sys_get_temp_dir; +use function tempnam; +use function unlink; +use PHPUnit\Framework\Exception; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +class DefaultPhpProcess extends AbstractPhpProcess +{ + /** + * @var string + */ + protected $tempFile; + + /** + * Runs a single job (PHP code) using a separate PHP process. + * + * @throws Exception + */ + public function runJob(string $job, array $settings = []): array + { + if ($this->stdin || $this->useTemporaryFile()) { + if (!($this->tempFile = tempnam(sys_get_temp_dir(), 'PHPUnit')) || + file_put_contents($this->tempFile, $job) === false) { + throw new Exception( + 'Unable to write temporary file', + ); + } + + $job = $this->stdin; + } + + return $this->runProcess($job, $settings); + } + + /** + * Returns an array of file handles to be used in place of pipes. + */ + protected function getHandles(): array + { + return []; + } + + /** + * Handles creating the child process and returning the STDOUT and STDERR. + * + * @throws Exception + */ + protected function runProcess(string $job, array $settings): array + { + $handles = $this->getHandles(); + + $env = null; + + if ($this->env) { + $env = $_SERVER ?? []; + unset($env['argv'], $env['argc']); + $env = array_merge($env, $this->env); + + foreach ($env as $envKey => $envVar) { + if (is_array($envVar)) { + unset($env[$envKey]); + } + } + } + + $pipeSpec = [ + 0 => $handles[0] ?? ['pipe', 'r'], + 1 => $handles[1] ?? ['pipe', 'w'], + 2 => $handles[2] ?? ['pipe', 'w'], + ]; + + $process = proc_open( + $this->getCommand($settings, $this->tempFile), + $pipeSpec, + $pipes, + null, + $env, + ); + + if (!is_resource($process)) { + throw new Exception( + 'Unable to spawn worker process', + ); + } + + if ($job) { + $this->process($pipes[0], $job); + } + + fclose($pipes[0]); + + $stderr = $stdout = ''; + + if ($this->timeout) { + unset($pipes[0]); + + while (true) { + $r = $pipes; + $w = null; + $e = null; + + $n = @stream_select($r, $w, $e, $this->timeout); + + if ($n === false) { + break; + } + + if ($n === 0) { + proc_terminate($process, 9); + + throw new Exception( + sprintf( + 'Job execution aborted after %d seconds', + $this->timeout, + ), + ); + } + + if ($n > 0) { + foreach ($r as $pipe) { + $pipeOffset = 0; + + foreach ($pipes as $i => $origPipe) { + if ($pipe === $origPipe) { + $pipeOffset = $i; + + break; + } + } + + if (!$pipeOffset) { + break; + } + + $line = fread($pipe, 8192); + + if ($line === '' || $line === false) { + fclose($pipes[$pipeOffset]); + + unset($pipes[$pipeOffset]); + } elseif ($pipeOffset === 1) { + $stdout .= $line; + } else { + $stderr .= $line; + } + } + + if (empty($pipes)) { + break; + } + } + } + } else { + if (isset($pipes[1])) { + $stdout = stream_get_contents($pipes[1]); + + fclose($pipes[1]); + } + + if (isset($pipes[2])) { + $stderr = stream_get_contents($pipes[2]); + + fclose($pipes[2]); + } + } + + if (isset($handles[1])) { + rewind($handles[1]); + + $stdout = stream_get_contents($handles[1]); + + fclose($handles[1]); + } + + if (isset($handles[2])) { + rewind($handles[2]); + + $stderr = stream_get_contents($handles[2]); + + fclose($handles[2]); + } + + proc_close($process); + + $this->cleanup(); + + return ['stdout' => $stdout, 'stderr' => $stderr]; + } + + /** + * @param resource $pipe + */ + protected function process($pipe, string $job): void + { + fwrite($pipe, $job); + } + + protected function cleanup(): void + { + if ($this->tempFile) { + unlink($this->tempFile); + } + } + + protected function useTemporaryFile(): bool + { + return false; + } +} diff --git a/vendor/phpunit/phpunit/src/Util/PHP/Template/PhptTestCase.tpl b/vendor/phpunit/phpunit/src/Util/PHP/Template/PhptTestCase.tpl new file mode 100644 index 00000000..f23a0d1a --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/PHP/Template/PhptTestCase.tpl @@ -0,0 +1,57 @@ +{driverMethod}($filter), + $filter + ); + + if ({codeCoverageCacheDirectory}) { + $coverage->cacheStaticAnalysis({codeCoverageCacheDirectory}); + } + + $coverage->start(__FILE__); +} + +register_shutdown_function( + function() use ($coverage) { + $output = null; + + if ($coverage) { + $output = $coverage->stop(); + } + + file_put_contents('{coverageFile}', serialize($output)); + } +); + +ob_end_clean(); + +require '{job}'; diff --git a/vendor/phpunit/phpunit/src/Util/PHP/Template/TestCaseClass.tpl b/vendor/phpunit/phpunit/src/Util/PHP/Template/TestCaseClass.tpl new file mode 100644 index 00000000..0486d116 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/PHP/Template/TestCaseClass.tpl @@ -0,0 +1,123 @@ +{driverMethod}($filter), + $filter + ); + + if ({cachesStaticAnalysis}) { + $codeCoverage->cacheStaticAnalysis(unserialize('{codeCoverageCacheDirectory}')); + } + + $result->setCodeCoverage($codeCoverage); + } + + $result->beStrictAboutTestsThatDoNotTestAnything({isStrictAboutTestsThatDoNotTestAnything}); + $result->beStrictAboutOutputDuringTests({isStrictAboutOutputDuringTests}); + $result->enforceTimeLimit({enforcesTimeLimit}); + $result->beStrictAboutTodoAnnotatedTests({isStrictAboutTodoAnnotatedTests}); + $result->beStrictAboutResourceUsageDuringSmallTests({isStrictAboutResourceUsageDuringSmallTests}); + + $test = new {className}('{name}', unserialize('{data}'), '{dataName}'); + $test->setDependencyInput(unserialize('{dependencyInput}')); + $test->setInIsolation(true); + + ob_end_clean(); + $test->run($result); + $output = ''; + if (!$test->hasExpectationOnOutput()) { + $output = $test->getActualOutput(); + } + + ini_set('xdebug.scream', '0'); + + @rewind(STDOUT); /* @ as not every STDOUT target stream is rewindable */ + if ($stdout = @stream_get_contents(STDOUT)) { + $output = $stdout . $output; + $streamMetaData = stream_get_meta_data(STDOUT); + if (!empty($streamMetaData['stream_type']) && 'STDIO' === $streamMetaData['stream_type']) { + @ftruncate(STDOUT, 0); + @rewind(STDOUT); + } + } + + file_put_contents( + '{processResultFile}', + serialize( + [ + 'testResult' => $test->getResult(), + 'numAssertions' => $test->getNumAssertions(), + 'result' => $result, + 'output' => $output + ] + ) + ); +} + +$configurationFilePath = '{configurationFilePath}'; + +if ('' !== $configurationFilePath) { + $configuration = (new Loader)->load($configurationFilePath); + + (new PhpHandler)->handle($configuration->php()); + + unset($configuration); +} + +function __phpunit_error_handler($errno, $errstr, $errfile, $errline) +{ + return true; +} + +set_error_handler('__phpunit_error_handler'); + +{constants} +{included_files} +{globals} + +restore_error_handler(); + +if (isset($GLOBALS['__PHPUNIT_BOOTSTRAP'])) { + require_once $GLOBALS['__PHPUNIT_BOOTSTRAP']; + unset($GLOBALS['__PHPUNIT_BOOTSTRAP']); +} + +__phpunit_run_isolated_test(); diff --git a/vendor/phpunit/phpunit/src/Util/PHP/Template/TestCaseMethod.tpl b/vendor/phpunit/phpunit/src/Util/PHP/Template/TestCaseMethod.tpl new file mode 100644 index 00000000..067934db --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/PHP/Template/TestCaseMethod.tpl @@ -0,0 +1,126 @@ +{driverMethod}($filter), + $filter + ); + + if ({cachesStaticAnalysis}) { + $codeCoverage->cacheStaticAnalysis(unserialize('{codeCoverageCacheDirectory}')); + } + + $result->setCodeCoverage($codeCoverage); + } + + $result->beStrictAboutTestsThatDoNotTestAnything({isStrictAboutTestsThatDoNotTestAnything}); + $result->beStrictAboutOutputDuringTests({isStrictAboutOutputDuringTests}); + $result->enforceTimeLimit({enforcesTimeLimit}); + $result->beStrictAboutTodoAnnotatedTests({isStrictAboutTodoAnnotatedTests}); + $result->beStrictAboutResourceUsageDuringSmallTests({isStrictAboutResourceUsageDuringSmallTests}); + + $test = new {className}('{methodName}', unserialize('{data}'), '{dataName}'); + \assert($test instanceof TestCase); + + $test->setDependencyInput(unserialize('{dependencyInput}')); + $test->setInIsolation(true); + + ob_end_clean(); + $test->run($result); + $output = ''; + if (!$test->hasExpectationOnOutput()) { + $output = $test->getActualOutput(); + } + + ini_set('xdebug.scream', '0'); + + @rewind(STDOUT); /* @ as not every STDOUT target stream is rewindable */ + if ($stdout = @stream_get_contents(STDOUT)) { + $output = $stdout . $output; + $streamMetaData = stream_get_meta_data(STDOUT); + if (!empty($streamMetaData['stream_type']) && 'STDIO' === $streamMetaData['stream_type']) { + @ftruncate(STDOUT, 0); + @rewind(STDOUT); + } + } + + file_put_contents( + '{processResultFile}', + serialize( + [ + 'testResult' => $test->getResult(), + 'numAssertions' => $test->getNumAssertions(), + 'result' => $result, + 'output' => $output + ] + ) + ); +} + +$configurationFilePath = '{configurationFilePath}'; + +if ('' !== $configurationFilePath) { + $configuration = (new Loader)->load($configurationFilePath); + + (new PhpHandler)->handle($configuration->php()); + + unset($configuration); +} + +function __phpunit_error_handler($errno, $errstr, $errfile, $errline) +{ + return true; +} + +set_error_handler('__phpunit_error_handler'); + +{constants} +{included_files} +{globals} + +restore_error_handler(); + +if (isset($GLOBALS['__PHPUNIT_BOOTSTRAP'])) { + require_once $GLOBALS['__PHPUNIT_BOOTSTRAP']; + unset($GLOBALS['__PHPUNIT_BOOTSTRAP']); +} + +__phpunit_run_isolated_test(); diff --git a/vendor/phpunit/phpunit/src/Util/PHP/WindowsPhpProcess.php b/vendor/phpunit/phpunit/src/Util/PHP/WindowsPhpProcess.php new file mode 100644 index 00000000..2480bc8c --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/PHP/WindowsPhpProcess.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\PHP; + +use const PHP_MAJOR_VERSION; +use function tmpfile; +use PHPUnit\Framework\Exception; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @see https://bugs.php.net/bug.php?id=51800 + */ +final class WindowsPhpProcess extends DefaultPhpProcess +{ + public function getCommand(array $settings, string $file = null): string + { + if (PHP_MAJOR_VERSION < 8) { + return '"' . parent::getCommand($settings, $file) . '"'; + } + + return parent::getCommand($settings, $file); + } + + /** + * @throws Exception + */ + protected function getHandles(): array + { + if (false === $stdout_handle = tmpfile()) { + throw new Exception( + 'A temporary file could not be created; verify that your TEMP environment variable is writable', + ); + } + + return [ + 1 => $stdout_handle, + ]; + } + + protected function useTemporaryFile(): bool + { + return true; + } +} diff --git a/vendor/phpunit/phpunit/src/Util/Printer.php b/vendor/phpunit/phpunit/src/Util/Printer.php new file mode 100644 index 00000000..311d4943 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/Printer.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use const ENT_COMPAT; +use const ENT_SUBSTITUTE; +use const PHP_SAPI; +use function assert; +use function count; +use function dirname; +use function explode; +use function fclose; +use function fopen; +use function fsockopen; +use function fwrite; +use function htmlspecialchars; +use function is_resource; +use function is_string; +use function sprintf; +use function str_replace; +use function strncmp; +use function strpos; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +class Printer +{ + /** + * @psalm-var closed-resource|resource + */ + private $stream; + + /** + * @var bool + */ + private $isPhpStream; + + /** + * @param null|resource|string $out + * + * @throws Exception + */ + public function __construct($out = null) + { + if (is_resource($out)) { + $this->stream = $out; + + return; + } + + if (!is_string($out)) { + return; + } + + if (strpos($out, 'socket://') === 0) { + $tmp = explode(':', str_replace('socket://', '', $out)); + + if (count($tmp) !== 2) { + throw new Exception( + sprintf( + '"%s" does not match "socket://hostname:port" format', + $out, + ), + ); + } + + $this->stream = fsockopen($tmp[0], (int) $tmp[1]); + + return; + } + + if (strpos($out, 'php://') === false && !Filesystem::createDirectory(dirname($out))) { + throw new Exception( + sprintf( + 'Directory "%s" was not created', + dirname($out), + ), + ); + } + + $this->stream = fopen($out, 'wb'); + $this->isPhpStream = strncmp($out, 'php://', 6) !== 0; + } + + public function write(string $buffer): void + { + if ($this->stream) { + assert(is_resource($this->stream)); + + fwrite($this->stream, $buffer); + } else { + if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg') { + $buffer = htmlspecialchars($buffer, ENT_COMPAT | ENT_SUBSTITUTE); + } + + print $buffer; + } + } + + public function flush(): void + { + if ($this->stream && $this->isPhpStream) { + assert(is_resource($this->stream)); + + fclose($this->stream); + } + } +} diff --git a/vendor/phpunit/phpunit/src/Util/Reflection.php b/vendor/phpunit/phpunit/src/Util/Reflection.php new file mode 100644 index 00000000..3ffc2d5e --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/Reflection.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use PHPUnit\Framework\Assert; +use PHPUnit\Framework\TestCase; +use ReflectionClass; +use ReflectionMethod; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Reflection +{ + /** + * @psalm-return list + */ + public function publicMethodsInTestClass(ReflectionClass $class): array + { + return $this->filterMethods($class, ReflectionMethod::IS_PUBLIC); + } + + /** + * @psalm-return list + */ + public function methodsInTestClass(ReflectionClass $class): array + { + return $this->filterMethods($class, null); + } + + /** + * @psalm-return list + */ + private function filterMethods(ReflectionClass $class, ?int $filter): array + { + $methods = []; + + // PHP <7.3.5 throw error when null is passed + // to ReflectionClass::getMethods() when strict_types is enabled. + $classMethods = $filter === null ? $class->getMethods() : $class->getMethods($filter); + + foreach ($classMethods as $method) { + if ($method->getDeclaringClass()->getName() === TestCase::class) { + continue; + } + + if ($method->getDeclaringClass()->getName() === Assert::class) { + continue; + } + + $methods[] = $method; + } + + return $methods; + } +} diff --git a/vendor/phpunit/phpunit/src/Util/RegularExpression.php b/vendor/phpunit/phpunit/src/Util/RegularExpression.php new file mode 100644 index 00000000..1e97d6c2 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/RegularExpression.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use function preg_match; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class RegularExpression +{ + /** + * @return false|int + */ + public static function safeMatch(string $pattern, string $subject) + { + return ErrorHandler::invokeIgnoringWarnings( + static function () use ($pattern, $subject) + { + return preg_match($pattern, $subject); + }, + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Util/Test.php b/vendor/phpunit/phpunit/src/Util/Test.php new file mode 100644 index 00000000..12c6bf16 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/Test.php @@ -0,0 +1,783 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use const PHP_OS; +use const PHP_VERSION; +use function addcslashes; +use function array_flip; +use function array_key_exists; +use function array_merge; +use function array_unique; +use function array_unshift; +use function class_exists; +use function count; +use function explode; +use function extension_loaded; +use function function_exists; +use function get_class; +use function ini_get; +use function interface_exists; +use function is_array; +use function is_int; +use function method_exists; +use function phpversion; +use function preg_match; +use function preg_replace; +use function sprintf; +use function strncmp; +use function strpos; +use function strtolower; +use function trim; +use function version_compare; +use PHPUnit\Framework\CodeCoverageException; +use PHPUnit\Framework\ExecutionOrderDependency; +use PHPUnit\Framework\InvalidCoversTargetException; +use PHPUnit\Framework\SelfDescribing; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\Warning; +use PHPUnit\Runner\Version; +use PHPUnit\Util\Annotation\Registry; +use ReflectionClass; +use ReflectionException; +use ReflectionMethod; +use SebastianBergmann\CodeUnit\CodeUnitCollection; +use SebastianBergmann\CodeUnit\InvalidCodeUnitException; +use SebastianBergmann\CodeUnit\Mapper; +use SebastianBergmann\Environment\OperatingSystem; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Test +{ + /** + * @var int + */ + public const UNKNOWN = -1; + + /** + * @var int + */ + public const SMALL = 0; + + /** + * @var int + */ + public const MEDIUM = 1; + + /** + * @var int + */ + public const LARGE = 2; + + /** + * @var array + */ + private static $hookMethods = []; + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public static function describe(\PHPUnit\Framework\Test $test): array + { + if ($test instanceof TestCase) { + return [get_class($test), $test->getName()]; + } + + if ($test instanceof SelfDescribing) { + return ['', $test->toString()]; + } + + return ['', get_class($test)]; + } + + public static function describeAsString(\PHPUnit\Framework\Test $test): string + { + if ($test instanceof SelfDescribing) { + return $test->toString(); + } + + return get_class($test); + } + + /** + * @throws CodeCoverageException + * + * @return array|bool + * + * @psalm-param class-string $className + */ + public static function getLinesToBeCovered(string $className, string $methodName) + { + $annotations = self::parseTestMethodAnnotations( + $className, + $methodName, + ); + + if (!self::shouldCoversAnnotationBeUsed($annotations)) { + return false; + } + + return self::getLinesToBeCoveredOrUsed($className, $methodName, 'covers'); + } + + /** + * Returns lines of code specified with the @uses annotation. + * + * @throws CodeCoverageException + * + * @psalm-param class-string $className + */ + public static function getLinesToBeUsed(string $className, string $methodName): array + { + return self::getLinesToBeCoveredOrUsed($className, $methodName, 'uses'); + } + + public static function requiresCodeCoverageDataCollection(TestCase $test): bool + { + $annotations = self::parseTestMethodAnnotations( + get_class($test), + $test->getName(false), + ); + + // If there is no @covers annotation but a @coversNothing annotation on + // the test method then code coverage data does not need to be collected + if (isset($annotations['method']['coversNothing'])) { + // @see https://github.com/sebastianbergmann/phpunit/issues/4947#issuecomment-1084480950 + // return false; + } + + // If there is at least one @covers annotation then + // code coverage data needs to be collected + if (isset($annotations['method']['covers'])) { + return true; + } + + // If there is no @covers annotation but a @coversNothing annotation + // then code coverage data does not need to be collected + if (isset($annotations['class']['coversNothing'])) { + // @see https://github.com/sebastianbergmann/phpunit/issues/4947#issuecomment-1084480950 + // return false; + } + + // If there is no @coversNothing annotation then + // code coverage data may be collected + return true; + } + + /** + * @throws Exception + * + * @psalm-param class-string $className + */ + public static function getRequirements(string $className, string $methodName): array + { + return self::mergeArraysRecursively( + Registry::getInstance()->forClassName($className)->requirements(), + Registry::getInstance()->forMethod($className, $methodName)->requirements(), + ); + } + + /** + * Returns the missing requirements for a test. + * + * @throws Exception + * @throws Warning + * + * @psalm-param class-string $className + */ + public static function getMissingRequirements(string $className, string $methodName): array + { + $required = self::getRequirements($className, $methodName); + $missing = []; + $hint = null; + + if (!empty($required['PHP'])) { + $operator = new VersionComparisonOperator(empty($required['PHP']['operator']) ? '>=' : $required['PHP']['operator']); + + if (!version_compare(PHP_VERSION, $required['PHP']['version'], $operator->asString())) { + $missing[] = sprintf('PHP %s %s is required.', $operator->asString(), $required['PHP']['version']); + $hint = 'PHP'; + } + } elseif (!empty($required['PHP_constraint'])) { + $version = new \PharIo\Version\Version(self::sanitizeVersionNumber(PHP_VERSION)); + + if (!$required['PHP_constraint']['constraint']->complies($version)) { + $missing[] = sprintf( + 'PHP version does not match the required constraint %s.', + $required['PHP_constraint']['constraint']->asString(), + ); + + $hint = 'PHP_constraint'; + } + } + + if (!empty($required['PHPUnit'])) { + $phpunitVersion = Version::id(); + + $operator = new VersionComparisonOperator(empty($required['PHPUnit']['operator']) ? '>=' : $required['PHPUnit']['operator']); + + if (!version_compare($phpunitVersion, $required['PHPUnit']['version'], $operator->asString())) { + $missing[] = sprintf('PHPUnit %s %s is required.', $operator->asString(), $required['PHPUnit']['version']); + $hint = $hint ?? 'PHPUnit'; + } + } elseif (!empty($required['PHPUnit_constraint'])) { + $phpunitVersion = new \PharIo\Version\Version(self::sanitizeVersionNumber(Version::id())); + + if (!$required['PHPUnit_constraint']['constraint']->complies($phpunitVersion)) { + $missing[] = sprintf( + 'PHPUnit version does not match the required constraint %s.', + $required['PHPUnit_constraint']['constraint']->asString(), + ); + + $hint = $hint ?? 'PHPUnit_constraint'; + } + } + + if (!empty($required['OSFAMILY']) && $required['OSFAMILY'] !== (new OperatingSystem)->getFamily()) { + $missing[] = sprintf('Operating system %s is required.', $required['OSFAMILY']); + $hint = $hint ?? 'OSFAMILY'; + } + + if (!empty($required['OS'])) { + $requiredOsPattern = sprintf('/%s/i', addcslashes($required['OS'], '/')); + + if (!preg_match($requiredOsPattern, PHP_OS)) { + $missing[] = sprintf('Operating system matching %s is required.', $requiredOsPattern); + $hint = $hint ?? 'OS'; + } + } + + if (!empty($required['functions'])) { + foreach ($required['functions'] as $function) { + $pieces = explode('::', $function); + + if (count($pieces) === 2 && class_exists($pieces[0]) && method_exists($pieces[0], $pieces[1])) { + continue; + } + + if (function_exists($function)) { + continue; + } + + $missing[] = sprintf('Function %s is required.', $function); + $hint = $hint ?? 'function_' . $function; + } + } + + if (!empty($required['setting'])) { + foreach ($required['setting'] as $setting => $value) { + if (ini_get($setting) !== $value) { + $missing[] = sprintf('Setting "%s" must be "%s".', $setting, $value); + $hint = $hint ?? '__SETTING_' . $setting; + } + } + } + + if (!empty($required['extensions'])) { + foreach ($required['extensions'] as $extension) { + if (isset($required['extension_versions'][$extension])) { + continue; + } + + if (!extension_loaded($extension)) { + $missing[] = sprintf('Extension %s is required.', $extension); + $hint = $hint ?? 'extension_' . $extension; + } + } + } + + if (!empty($required['extension_versions'])) { + foreach ($required['extension_versions'] as $extension => $req) { + $actualVersion = phpversion($extension); + + $operator = new VersionComparisonOperator(empty($req['operator']) ? '>=' : $req['operator']); + + if ($actualVersion === false || !version_compare($actualVersion, $req['version'], $operator->asString())) { + $missing[] = sprintf('Extension %s %s %s is required.', $extension, $operator->asString(), $req['version']); + $hint = $hint ?? 'extension_' . $extension; + } + } + } + + if ($hint && isset($required['__OFFSET'])) { + array_unshift($missing, '__OFFSET_FILE=' . $required['__OFFSET']['__FILE']); + array_unshift($missing, '__OFFSET_LINE=' . ($required['__OFFSET'][$hint] ?? 1)); + } + + return $missing; + } + + /** + * Returns the provided data for a method. + * + * @throws Exception + * + * @psalm-param class-string $className + */ + public static function getProvidedData(string $className, string $methodName): ?array + { + return Registry::getInstance()->forMethod($className, $methodName)->getProvidedData(); + } + + /** + * @psalm-param class-string $className + */ + public static function parseTestMethodAnnotations(string $className, ?string $methodName = null): array + { + $registry = Registry::getInstance(); + + if ($methodName !== null) { + try { + return [ + 'method' => $registry->forMethod($className, $methodName)->symbolAnnotations(), + 'class' => $registry->forClassName($className)->symbolAnnotations(), + ]; + } catch (Exception $methodNotFound) { + // ignored + } + } + + return [ + 'method' => null, + 'class' => $registry->forClassName($className)->symbolAnnotations(), + ]; + } + + /** + * @psalm-param class-string $className + */ + public static function getInlineAnnotations(string $className, string $methodName): array + { + return Registry::getInstance()->forMethod($className, $methodName)->getInlineAnnotations(); + } + + /** @psalm-param class-string $className */ + public static function getBackupSettings(string $className, string $methodName): array + { + return [ + 'backupGlobals' => self::getBooleanAnnotationSetting( + $className, + $methodName, + 'backupGlobals', + ), + 'backupStaticAttributes' => self::getBooleanAnnotationSetting( + $className, + $methodName, + 'backupStaticAttributes', + ), + ]; + } + + /** + * @psalm-param class-string $className + * + * @return ExecutionOrderDependency[] + */ + public static function getDependencies(string $className, string $methodName): array + { + $annotations = self::parseTestMethodAnnotations( + $className, + $methodName, + ); + + $dependsAnnotations = $annotations['class']['depends'] ?? []; + + if (isset($annotations['method']['depends'])) { + $dependsAnnotations = array_merge( + $dependsAnnotations, + $annotations['method']['depends'], + ); + } + + // Normalize dependency name to className::methodName + $dependencies = []; + + foreach ($dependsAnnotations as $value) { + $dependencies[] = ExecutionOrderDependency::createFromDependsAnnotation($className, $value); + } + + return array_unique($dependencies); + } + + /** @psalm-param class-string $className */ + public static function getGroups(string $className, ?string $methodName = ''): array + { + $annotations = self::parseTestMethodAnnotations( + $className, + $methodName, + ); + + $groups = []; + + if (isset($annotations['method']['author'])) { + $groups[] = $annotations['method']['author']; + } elseif (isset($annotations['class']['author'])) { + $groups[] = $annotations['class']['author']; + } + + if (isset($annotations['class']['group'])) { + $groups[] = $annotations['class']['group']; + } + + if (isset($annotations['method']['group'])) { + $groups[] = $annotations['method']['group']; + } + + if (isset($annotations['class']['ticket'])) { + $groups[] = $annotations['class']['ticket']; + } + + if (isset($annotations['method']['ticket'])) { + $groups[] = $annotations['method']['ticket']; + } + + foreach (['method', 'class'] as $element) { + foreach (['small', 'medium', 'large'] as $size) { + if (isset($annotations[$element][$size])) { + $groups[] = [$size]; + + break 2; + } + } + } + + foreach (['method', 'class'] as $element) { + if (isset($annotations[$element]['covers'])) { + foreach ($annotations[$element]['covers'] as $coversTarget) { + $groups[] = ['__phpunit_covers_' . self::canonicalizeName($coversTarget)]; + } + } + + if (isset($annotations[$element]['uses'])) { + foreach ($annotations[$element]['uses'] as $usesTarget) { + $groups[] = ['__phpunit_uses_' . self::canonicalizeName($usesTarget)]; + } + } + } + + return array_unique(array_merge([], ...$groups)); + } + + /** @psalm-param class-string $className */ + public static function getSize(string $className, ?string $methodName): int + { + $groups = array_flip(self::getGroups($className, $methodName)); + + if (isset($groups['large'])) { + return self::LARGE; + } + + if (isset($groups['medium'])) { + return self::MEDIUM; + } + + if (isset($groups['small'])) { + return self::SMALL; + } + + return self::UNKNOWN; + } + + /** @psalm-param class-string $className */ + public static function getProcessIsolationSettings(string $className, string $methodName): bool + { + $annotations = self::parseTestMethodAnnotations( + $className, + $methodName, + ); + + return isset($annotations['class']['runTestsInSeparateProcesses']) || isset($annotations['method']['runInSeparateProcess']); + } + + /** @psalm-param class-string $className */ + public static function getClassProcessIsolationSettings(string $className, string $methodName): bool + { + $annotations = self::parseTestMethodAnnotations( + $className, + $methodName, + ); + + return isset($annotations['class']['runClassInSeparateProcess']); + } + + /** @psalm-param class-string $className */ + public static function getPreserveGlobalStateSettings(string $className, string $methodName): ?bool + { + return self::getBooleanAnnotationSetting( + $className, + $methodName, + 'preserveGlobalState', + ); + } + + /** @psalm-param class-string $className */ + public static function getHookMethods(string $className): array + { + if (!class_exists($className, false)) { + return self::emptyHookMethodsArray(); + } + + if (!isset(self::$hookMethods[$className])) { + self::$hookMethods[$className] = self::emptyHookMethodsArray(); + + try { + foreach ((new Reflection)->methodsInTestClass(new ReflectionClass($className)) as $method) { + $docBlock = Registry::getInstance()->forMethod($className, $method->getName()); + + if ($method->isStatic()) { + if ($docBlock->isHookToBeExecutedBeforeClass()) { + array_unshift( + self::$hookMethods[$className]['beforeClass'], + $method->getName(), + ); + } + + if ($docBlock->isHookToBeExecutedAfterClass()) { + self::$hookMethods[$className]['afterClass'][] = $method->getName(); + } + } + + if ($docBlock->isToBeExecutedBeforeTest()) { + array_unshift( + self::$hookMethods[$className]['before'], + $method->getName(), + ); + } + + if ($docBlock->isToBeExecutedAsPreCondition()) { + array_unshift( + self::$hookMethods[$className]['preCondition'], + $method->getName(), + ); + } + + if ($docBlock->isToBeExecutedAsPostCondition()) { + self::$hookMethods[$className]['postCondition'][] = $method->getName(); + } + + if ($docBlock->isToBeExecutedAfterTest()) { + self::$hookMethods[$className]['after'][] = $method->getName(); + } + } + } catch (ReflectionException $e) { + } + } + + return self::$hookMethods[$className]; + } + + public static function isTestMethod(ReflectionMethod $method): bool + { + if (!$method->isPublic()) { + return false; + } + + if (strpos($method->getName(), 'test') === 0) { + return true; + } + + return array_key_exists( + 'test', + Registry::getInstance()->forMethod( + $method->getDeclaringClass()->getName(), + $method->getName(), + ) + ->symbolAnnotations(), + ); + } + + /** + * @throws CodeCoverageException + * + * @psalm-param class-string $className + */ + private static function getLinesToBeCoveredOrUsed(string $className, string $methodName, string $mode): array + { + $annotations = self::parseTestMethodAnnotations( + $className, + $methodName, + ); + + $classShortcut = null; + + if (!empty($annotations['class'][$mode . 'DefaultClass'])) { + if (count($annotations['class'][$mode . 'DefaultClass']) > 1) { + throw new CodeCoverageException( + sprintf( + 'More than one @%sClass annotation in class or interface "%s".', + $mode, + $className, + ), + ); + } + + $classShortcut = $annotations['class'][$mode . 'DefaultClass'][0]; + } + + $list = $annotations['class'][$mode] ?? []; + + if (isset($annotations['method'][$mode])) { + $list = array_merge($list, $annotations['method'][$mode]); + } + + $codeUnits = CodeUnitCollection::fromArray([]); + $mapper = new Mapper; + + foreach (array_unique($list) as $element) { + if ($classShortcut && strncmp($element, '::', 2) === 0) { + $element = $classShortcut . $element; + } + + $element = preg_replace('/[\s()]+$/', '', $element); + $element = explode(' ', $element); + $element = $element[0]; + + if ($mode === 'covers' && interface_exists($element)) { + throw new InvalidCoversTargetException( + sprintf( + 'Trying to @cover interface "%s".', + $element, + ), + ); + } + + try { + $codeUnits = $codeUnits->mergeWith($mapper->stringToCodeUnits($element)); + } catch (InvalidCodeUnitException $e) { + throw new InvalidCoversTargetException( + sprintf( + '"@%s %s" is invalid', + $mode, + $element, + ), + $e->getCode(), + $e, + ); + } + } + + return $mapper->codeUnitsToSourceLines($codeUnits); + } + + private static function emptyHookMethodsArray(): array + { + return [ + 'beforeClass' => ['setUpBeforeClass'], + 'before' => ['setUp'], + 'preCondition' => ['assertPreConditions'], + 'postCondition' => ['assertPostConditions'], + 'after' => ['tearDown'], + 'afterClass' => ['tearDownAfterClass'], + ]; + } + + /** @psalm-param class-string $className */ + private static function getBooleanAnnotationSetting(string $className, ?string $methodName, string $settingName): ?bool + { + $annotations = self::parseTestMethodAnnotations( + $className, + $methodName, + ); + + if (isset($annotations['method'][$settingName])) { + if ($annotations['method'][$settingName][0] === 'enabled') { + return true; + } + + if ($annotations['method'][$settingName][0] === 'disabled') { + return false; + } + } + + if (isset($annotations['class'][$settingName])) { + if ($annotations['class'][$settingName][0] === 'enabled') { + return true; + } + + if ($annotations['class'][$settingName][0] === 'disabled') { + return false; + } + } + + return null; + } + + /** + * Trims any extensions from version string that follows after + * the .[.] format. + */ + private static function sanitizeVersionNumber(string $version) + { + return preg_replace( + '/^(\d+\.\d+(?:.\d+)?).*$/', + '$1', + $version, + ); + } + + private static function shouldCoversAnnotationBeUsed(array $annotations): bool + { + if (isset($annotations['method']['coversNothing'])) { + return false; + } + + if (isset($annotations['method']['covers'])) { + return true; + } + + if (isset($annotations['class']['coversNothing'])) { + return false; + } + + return true; + } + + /** + * Merge two arrays together. + * + * If an integer key exists in both arrays and preserveNumericKeys is false, the value + * from the second array will be appended to the first array. If both values are arrays, they + * are merged together, else the value of the second array overwrites the one of the first array. + * + * This implementation is copied from https://github.com/zendframework/zend-stdlib/blob/76b653c5e99b40eccf5966e3122c90615134ae46/src/ArrayUtils.php + * + * Zend Framework (http://framework.zend.com/) + * + * @see http://github.com/zendframework/zf2 for the canonical source repository + * + * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ + private static function mergeArraysRecursively(array $a, array $b): array + { + foreach ($b as $key => $value) { + if (array_key_exists($key, $a)) { + if (is_int($key)) { + $a[] = $value; + } elseif (is_array($value) && is_array($a[$key])) { + $a[$key] = self::mergeArraysRecursively($a[$key], $value); + } else { + $a[$key] = $value; + } + } else { + $a[$key] = $value; + } + } + + return $a; + } + + private static function canonicalizeName(string $name): string + { + return strtolower(trim($name, '\\')); + } +} diff --git a/vendor/phpunit/phpunit/src/Util/TestDox/CliTestDoxPrinter.php b/vendor/phpunit/phpunit/src/Util/TestDox/CliTestDoxPrinter.php new file mode 100644 index 00000000..031c7ed6 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/TestDox/CliTestDoxPrinter.php @@ -0,0 +1,380 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\TestDox; + +use const PHP_EOL; +use function array_map; +use function ceil; +use function count; +use function explode; +use function get_class; +use function implode; +use function preg_match; +use function sprintf; +use function strlen; +use function strpos; +use function trim; +use PHPUnit\Framework\Test; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\TestResult; +use PHPUnit\Runner\BaseTestRunner; +use PHPUnit\Runner\PhptTestCase; +use PHPUnit\Util\Color; +use SebastianBergmann\Timer\ResourceUsageFormatter; +use SebastianBergmann\Timer\Timer; +use Throwable; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +class CliTestDoxPrinter extends TestDoxPrinter +{ + /** + * The default Testdox left margin for messages is a vertical line. + */ + private const PREFIX_SIMPLE = [ + 'default' => '│', + 'start' => '│', + 'message' => '│', + 'diff' => '│', + 'trace' => '│', + 'last' => '│', + ]; + + /** + * Colored Testdox use box-drawing for a more textured map of the message. + */ + private const PREFIX_DECORATED = [ + 'default' => '│', + 'start' => '┐', + 'message' => '├', + 'diff' => '┊', + 'trace' => '╵', + 'last' => '┴', + ]; + + private const SPINNER_ICONS = [ + " \e[36m◐\e[0m running tests", + " \e[36m◓\e[0m running tests", + " \e[36m◑\e[0m running tests", + " \e[36m◒\e[0m running tests", + ]; + private const STATUS_STYLES = [ + BaseTestRunner::STATUS_PASSED => [ + 'symbol' => '✔', + 'color' => 'fg-green', + ], + BaseTestRunner::STATUS_ERROR => [ + 'symbol' => '✘', + 'color' => 'fg-yellow', + 'message' => 'bg-yellow,fg-black', + ], + BaseTestRunner::STATUS_FAILURE => [ + 'symbol' => '✘', + 'color' => 'fg-red', + 'message' => 'bg-red,fg-white', + ], + BaseTestRunner::STATUS_SKIPPED => [ + 'symbol' => '↩', + 'color' => 'fg-cyan', + 'message' => 'fg-cyan', + ], + BaseTestRunner::STATUS_RISKY => [ + 'symbol' => '☢', + 'color' => 'fg-yellow', + 'message' => 'fg-yellow', + ], + BaseTestRunner::STATUS_INCOMPLETE => [ + 'symbol' => '∅', + 'color' => 'fg-yellow', + 'message' => 'fg-yellow', + ], + BaseTestRunner::STATUS_WARNING => [ + 'symbol' => '⚠', + 'color' => 'fg-yellow', + 'message' => 'fg-yellow', + ], + BaseTestRunner::STATUS_UNKNOWN => [ + 'symbol' => '?', + 'color' => 'fg-blue', + 'message' => 'fg-white,bg-blue', + ], + ]; + + /** + * @var int[] + */ + private $nonSuccessfulTestResults = []; + + /** + * @var Timer + */ + private $timer; + + /** + * @param null|resource|string $out + * @param int|string $numberOfColumns + * + * @throws \PHPUnit\Framework\Exception + */ + public function __construct($out = null, bool $verbose = false, string $colors = self::COLOR_DEFAULT, bool $debug = false, $numberOfColumns = 80, bool $reverse = false) + { + parent::__construct($out, $verbose, $colors, $debug, $numberOfColumns, $reverse); + + $this->timer = new Timer; + + $this->timer->start(); + } + + public function printResult(TestResult $result): void + { + $this->printHeader($result); + + $this->printNonSuccessfulTestsSummary($result->count()); + + $this->printFooter($result); + } + + protected function printHeader(TestResult $result): void + { + $this->write("\n" . (new ResourceUsageFormatter)->resourceUsage($this->timer->stop()) . "\n\n"); + } + + protected function formatClassName(Test $test): string + { + if ($test instanceof TestCase) { + return $this->prettifier->prettifyTestClass(get_class($test)); + } + + return get_class($test); + } + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + protected function registerTestResult(Test $test, ?Throwable $t, int $status, float $time, bool $verbose): void + { + if ($status !== BaseTestRunner::STATUS_PASSED) { + $this->nonSuccessfulTestResults[] = $this->testIndex; + } + + parent::registerTestResult($test, $t, $status, $time, $verbose); + } + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + protected function formatTestName(Test $test): string + { + if ($test instanceof TestCase) { + return $this->prettifier->prettifyTestCase($test); + } + + return parent::formatTestName($test); + } + + protected function writeTestResult(array $prevResult, array $result): void + { + // spacer line for new suite headers and after verbose messages + if ($prevResult['testName'] !== '' && + (!empty($prevResult['message']) || $prevResult['className'] !== $result['className'])) { + $this->write(PHP_EOL); + } + + // suite header + if ($prevResult['className'] !== $result['className']) { + $this->write($this->colorizeTextBox('underlined', $result['className']) . PHP_EOL); + } + + // test result line + if ($this->colors && $result['className'] === PhptTestCase::class) { + $testName = Color::colorizePath($result['testName'], $prevResult['testName'], true); + } else { + $testName = $result['testMethod']; + } + + $style = self::STATUS_STYLES[$result['status']]; + $line = sprintf( + ' %s %s%s' . PHP_EOL, + $this->colorizeTextBox($style['color'], $style['symbol']), + $testName, + $this->verbose ? ' ' . $this->formatRuntime($result['time'], $style['color']) : '', + ); + + $this->write($line); + + // additional information when verbose + $this->write($result['message']); + } + + protected function formatThrowable(Throwable $t, ?int $status = null): string + { + return trim(\PHPUnit\Framework\TestFailure::exceptionToString($t)); + } + + protected function colorizeMessageAndDiff(string $style, string $buffer): array + { + $lines = $buffer ? array_map('\rtrim', explode(PHP_EOL, $buffer)) : []; + $message = []; + $diff = []; + $insideDiff = false; + + foreach ($lines as $line) { + if ($line === '--- Expected') { + $insideDiff = true; + } + + if (!$insideDiff) { + $message[] = $line; + } else { + if (strpos($line, '-') === 0) { + $line = Color::colorize('fg-red', Color::visualizeWhitespace($line, true)); + } elseif (strpos($line, '+') === 0) { + $line = Color::colorize('fg-green', Color::visualizeWhitespace($line, true)); + } elseif ($line === '@@ @@') { + $line = Color::colorize('fg-cyan', $line); + } + $diff[] = $line; + } + } + $diff = implode(PHP_EOL, $diff); + + if (!empty($message)) { + $message = $this->colorizeTextBox($style, implode(PHP_EOL, $message)); + } + + return [$message, $diff]; + } + + protected function formatStacktrace(Throwable $t): string + { + $trace = \PHPUnit\Util\Filter::getFilteredStacktrace($t); + + if (!$this->colors) { + return $trace; + } + + $lines = []; + $prevPath = ''; + + foreach (explode(PHP_EOL, $trace) as $line) { + if (preg_match('/^(.*):(\d+)$/', $line, $matches)) { + $lines[] = Color::colorizePath($matches[1], $prevPath) . + Color::dim(':') . + Color::colorize('fg-blue', $matches[2]) . + "\n"; + $prevPath = $matches[1]; + } else { + $lines[] = $line; + $prevPath = ''; + } + } + + return implode('', $lines); + } + + protected function formatTestResultMessage(Throwable $t, array $result, ?string $prefix = null): string + { + $message = $this->formatThrowable($t, $result['status']); + $diff = ''; + + if (!($this->verbose || $result['verbose'])) { + return ''; + } + + if ($message && $this->colors) { + $style = self::STATUS_STYLES[$result['status']]['message'] ?? ''; + [$message, $diff] = $this->colorizeMessageAndDiff($style, $message); + } + + if ($prefix === null || !$this->colors) { + $prefix = self::PREFIX_SIMPLE; + } + + if ($this->colors) { + $color = self::STATUS_STYLES[$result['status']]['color'] ?? ''; + $prefix = array_map(static function ($p) use ($color) + { + return Color::colorize($color, $p); + }, self::PREFIX_DECORATED); + } + + $trace = $this->formatStacktrace($t); + $out = $this->prefixLines($prefix['start'], PHP_EOL) . PHP_EOL; + + if ($message) { + $out .= $this->prefixLines($prefix['message'], $message . PHP_EOL) . PHP_EOL; + } + + if ($diff) { + $out .= $this->prefixLines($prefix['diff'], $diff . PHP_EOL) . PHP_EOL; + } + + if ($trace) { + if ($message || $diff) { + $out .= $this->prefixLines($prefix['default'], PHP_EOL) . PHP_EOL; + } + $out .= $this->prefixLines($prefix['trace'], $trace . PHP_EOL) . PHP_EOL; + } + $out .= $this->prefixLines($prefix['last'], PHP_EOL) . PHP_EOL; + + return $out; + } + + protected function drawSpinner(): void + { + if ($this->colors) { + $id = $this->spinState % count(self::SPINNER_ICONS); + $this->write(self::SPINNER_ICONS[$id]); + } + } + + protected function undrawSpinner(): void + { + if ($this->colors) { + $id = $this->spinState % count(self::SPINNER_ICONS); + $this->write("\e[1K\e[" . strlen(self::SPINNER_ICONS[$id]) . 'D'); + } + } + + private function formatRuntime(float $time, string $color = ''): string + { + if (!$this->colors) { + return sprintf('[%.2f ms]', $time * 1000); + } + + if ($time > 1) { + $color = 'fg-magenta'; + } + + return Color::colorize($color, ' ' . (int) ceil($time * 1000) . ' ' . Color::dim('ms')); + } + + private function printNonSuccessfulTestsSummary(int $numberOfExecutedTests): void + { + if (empty($this->nonSuccessfulTestResults)) { + return; + } + + if ((count($this->nonSuccessfulTestResults) / $numberOfExecutedTests) >= 0.7) { + return; + } + + $this->write("Summary of non-successful tests:\n\n"); + + $prevResult = $this->getEmptyTestResult(); + + foreach ($this->nonSuccessfulTestResults as $testIndex) { + $result = $this->testResults[$testIndex]; + $this->writeTestResult($prevResult, $result); + $prevResult = $result; + } + } +} diff --git a/vendor/phpunit/phpunit/src/Util/TestDox/HtmlResultPrinter.php b/vendor/phpunit/phpunit/src/Util/TestDox/HtmlResultPrinter.php new file mode 100644 index 00000000..d08bfad4 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/TestDox/HtmlResultPrinter.php @@ -0,0 +1,159 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\TestDox; + +use function sprintf; +use PHPUnit\Framework\TestResult; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class HtmlResultPrinter extends ResultPrinter +{ + /** + * @var string + */ + private const PAGE_HEADER = <<<'EOT' + + + + + Test Documentation + + + +EOT; + + /** + * @var string + */ + private const CLASS_HEADER = <<<'EOT' + +

      %s

      +
        + +EOT; + + /** + * @var string + */ + private const CLASS_FOOTER = <<<'EOT' +
      +EOT; + + /** + * @var string + */ + private const PAGE_FOOTER = <<<'EOT' + + + +EOT; + + public function printResult(TestResult $result): void + { + } + + /** + * Handler for 'start run' event. + */ + protected function startRun(): void + { + $this->write(self::PAGE_HEADER); + } + + /** + * Handler for 'start class' event. + */ + protected function startClass(string $name): void + { + $this->write( + sprintf( + self::CLASS_HEADER, + $this->currentTestClassPrettified, + ), + ); + } + + /** + * Handler for 'on test' event. + */ + protected function onTest(string $name, bool $success = true): void + { + $this->write( + sprintf( + "
    • %s
    • \n", + $success ? 'success' : 'defect', + $name, + ), + ); + } + + /** + * Handler for 'end class' event. + */ + protected function endClass(string $name): void + { + $this->write(self::CLASS_FOOTER); + } + + /** + * Handler for 'end run' event. + */ + protected function endRun(): void + { + $this->write(self::PAGE_FOOTER); + } +} diff --git a/vendor/phpunit/phpunit/src/Util/TestDox/NamePrettifier.php b/vendor/phpunit/phpunit/src/Util/TestDox/NamePrettifier.php new file mode 100644 index 00000000..ee0a41b1 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/TestDox/NamePrettifier.php @@ -0,0 +1,312 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\TestDox; + +use function array_key_exists; +use function array_keys; +use function array_map; +use function array_pop; +use function array_values; +use function explode; +use function get_class; +use function gettype; +use function implode; +use function in_array; +use function is_bool; +use function is_float; +use function is_int; +use function is_numeric; +use function is_object; +use function is_scalar; +use function is_string; +use function ord; +use function preg_quote; +use function preg_replace; +use function range; +use function sprintf; +use function str_replace; +use function strlen; +use function strpos; +use function strtolower; +use function strtoupper; +use function substr; +use function trim; +use PHPUnit\Framework\TestCase; +use PHPUnit\Util\Color; +use PHPUnit\Util\Exception as UtilException; +use PHPUnit\Util\Test; +use ReflectionException; +use ReflectionMethod; +use ReflectionObject; +use SebastianBergmann\Exporter\Exporter; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class NamePrettifier +{ + /** + * @var string[] + */ + private $strings = []; + + /** + * @var bool + */ + private $useColor; + + public function __construct(bool $useColor = false) + { + $this->useColor = $useColor; + } + + /** + * Prettifies the name of a test class. + * + * @psalm-param class-string $className + */ + public function prettifyTestClass(string $className): string + { + try { + $annotations = Test::parseTestMethodAnnotations($className); + + if (isset($annotations['class']['testdox'][0])) { + return $annotations['class']['testdox'][0]; + } + } catch (UtilException $e) { + // ignore, determine className by parsing the provided name + } + + $parts = explode('\\', $className); + $className = array_pop($parts); + + if (substr($className, -1 * strlen('Test')) === 'Test') { + $className = substr($className, 0, strlen($className) - strlen('Test')); + } + + if (strpos($className, 'Tests') === 0) { + $className = substr($className, strlen('Tests')); + } elseif (strpos($className, 'Test') === 0) { + $className = substr($className, strlen('Test')); + } + + if (empty($className)) { + $className = 'UnnamedTests'; + } + + if (!empty($parts)) { + $parts[] = $className; + $fullyQualifiedName = implode('\\', $parts); + } else { + $fullyQualifiedName = $className; + } + + $result = preg_replace('/(?<=[[:lower:]])(?=[[:upper:]])/u', ' ', $className); + + if ($fullyQualifiedName !== $className) { + return $result . ' (' . $fullyQualifiedName . ')'; + } + + return $result; + } + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function prettifyTestCase(TestCase $test): string + { + $annotations = Test::parseTestMethodAnnotations( + get_class($test), + $test->getName(false), + ); + + $annotationWithPlaceholders = false; + + $callback = static function (string $variable): string + { + return sprintf('/%s(?=\b)/', preg_quote($variable, '/')); + }; + + if (isset($annotations['method']['testdox'][0])) { + $result = $annotations['method']['testdox'][0]; + + if (strpos($result, '$') !== false) { + $annotation = $annotations['method']['testdox'][0]; + $providedData = $this->mapTestMethodParameterNamesToProvidedDataValues($test); + $variables = array_map($callback, array_keys($providedData)); + + $result = trim(preg_replace($variables, $providedData, $annotation)); + + $annotationWithPlaceholders = true; + } + } else { + $result = $this->prettifyTestMethod($test->getName(false)); + } + + if (!$annotationWithPlaceholders && $test->usesDataProvider()) { + $result .= $this->prettifyDataSet($test); + } + + return $result; + } + + public function prettifyDataSet(TestCase $test): string + { + if (!$this->useColor) { + return $test->getDataSetAsString(false); + } + + if (is_int($test->dataName())) { + $data = Color::dim(' with data set ') . Color::colorize('fg-cyan', (string) $test->dataName()); + } else { + $data = Color::dim(' with ') . Color::colorize('fg-cyan', Color::visualizeWhitespace((string) $test->dataName())); + } + + return $data; + } + + /** + * Prettifies the name of a test method. + */ + public function prettifyTestMethod(string $name): string + { + $buffer = ''; + + if ($name === '') { + return $buffer; + } + + $string = (string) preg_replace('#\d+$#', '', $name, -1, $count); + + if (in_array($string, $this->strings, true)) { + $name = $string; + } elseif ($count === 0) { + $this->strings[] = $string; + } + + if (strpos($name, 'test_') === 0) { + $name = substr($name, 5); + } elseif (strpos($name, 'test') === 0) { + $name = substr($name, 4); + } + + if ($name === '') { + return $buffer; + } + + $name[0] = strtoupper($name[0]); + + if (strpos($name, '_') !== false) { + return trim(str_replace('_', ' ', $name)); + } + + $wasNumeric = false; + + foreach (range(0, strlen($name) - 1) as $i) { + if ($i > 0 && ord($name[$i]) >= 65 && ord($name[$i]) <= 90) { + $buffer .= ' ' . strtolower($name[$i]); + } else { + $isNumeric = is_numeric($name[$i]); + + if (!$wasNumeric && $isNumeric) { + $buffer .= ' '; + $wasNumeric = true; + } + + if ($wasNumeric && !$isNumeric) { + $wasNumeric = false; + } + + $buffer .= $name[$i]; + } + } + + return $buffer; + } + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + private function mapTestMethodParameterNamesToProvidedDataValues(TestCase $test): array + { + try { + $reflector = new ReflectionMethod(get_class($test), $test->getName(false)); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new UtilException( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + $providedData = []; + $providedDataValues = array_values($test->getProvidedData()); + $i = 0; + + $providedData['$_dataName'] = $test->dataName(); + + foreach ($reflector->getParameters() as $parameter) { + if (!array_key_exists($i, $providedDataValues) && $parameter->isDefaultValueAvailable()) { + try { + $providedDataValues[$i] = $parameter->getDefaultValue(); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new UtilException( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + } + + $value = $providedDataValues[$i++] ?? null; + + if (is_object($value)) { + $reflector = new ReflectionObject($value); + + if ($reflector->hasMethod('__toString')) { + $value = (string) $value; + } else { + $value = get_class($value); + } + } + + if (!is_scalar($value)) { + $value = gettype($value); + } + + if (is_bool($value) || is_int($value) || is_float($value)) { + $value = (new Exporter)->export($value); + } + + if (is_string($value) && $value === '') { + if ($this->useColor) { + $value = Color::colorize('dim,underlined', 'empty'); + } else { + $value = "''"; + } + } + + $providedData['$' . $parameter->getName()] = $value; + } + + if ($this->useColor) { + $providedData = array_map(static function ($value) + { + return Color::colorize('fg-cyan', Color::visualizeWhitespace((string) $value, true)); + }, $providedData); + } + + return $providedData; + } +} diff --git a/vendor/phpunit/phpunit/src/Util/TestDox/ResultPrinter.php b/vendor/phpunit/phpunit/src/Util/TestDox/ResultPrinter.php new file mode 100644 index 00000000..c4b63644 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/TestDox/ResultPrinter.php @@ -0,0 +1,343 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\TestDox; + +use function get_class; +use function in_array; +use PHPUnit\Framework\AssertionFailedError; +use PHPUnit\Framework\ErrorTestCase; +use PHPUnit\Framework\Test; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Framework\Warning; +use PHPUnit\Framework\WarningTestCase; +use PHPUnit\Runner\BaseTestRunner; +use PHPUnit\TextUI\ResultPrinter as ResultPrinterInterface; +use PHPUnit\Util\Printer; +use Throwable; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract class ResultPrinter extends Printer implements ResultPrinterInterface +{ + /** + * @var NamePrettifier + */ + protected $prettifier; + + /** + * @var string + */ + protected $testClass = ''; + + /** + * @var int + */ + protected $testStatus; + + /** + * @var array + */ + protected $tests = []; + + /** + * @var int + */ + protected $successful = 0; + + /** + * @var int + */ + protected $warned = 0; + + /** + * @var int + */ + protected $failed = 0; + + /** + * @var int + */ + protected $risky = 0; + + /** + * @var int + */ + protected $skipped = 0; + + /** + * @var int + */ + protected $incomplete = 0; + + /** + * @var null|string + */ + protected $currentTestClassPrettified; + + /** + * @var null|string + */ + protected $currentTestMethodPrettified; + + /** + * @var array + */ + private $groups; + + /** + * @var array + */ + private $excludeGroups; + + /** + * @param resource $out + * + * @throws \PHPUnit\Framework\Exception + */ + public function __construct($out = null, array $groups = [], array $excludeGroups = []) + { + parent::__construct($out); + + $this->groups = $groups; + $this->excludeGroups = $excludeGroups; + + $this->prettifier = new NamePrettifier; + $this->startRun(); + } + + /** + * Flush buffer and close output. + */ + public function flush(): void + { + $this->doEndClass(); + $this->endRun(); + + parent::flush(); + } + + /** + * An error occurred. + */ + public function addError(Test $test, Throwable $t, float $time): void + { + if (!$this->isOfInterest($test)) { + return; + } + + $this->testStatus = BaseTestRunner::STATUS_ERROR; + $this->failed++; + } + + /** + * A warning occurred. + */ + public function addWarning(Test $test, Warning $e, float $time): void + { + if (!$this->isOfInterest($test)) { + return; + } + + $this->testStatus = BaseTestRunner::STATUS_WARNING; + $this->warned++; + } + + /** + * A failure occurred. + */ + public function addFailure(Test $test, AssertionFailedError $e, float $time): void + { + if (!$this->isOfInterest($test)) { + return; + } + + $this->testStatus = BaseTestRunner::STATUS_FAILURE; + $this->failed++; + } + + /** + * Incomplete test. + */ + public function addIncompleteTest(Test $test, Throwable $t, float $time): void + { + if (!$this->isOfInterest($test)) { + return; + } + + $this->testStatus = BaseTestRunner::STATUS_INCOMPLETE; + $this->incomplete++; + } + + /** + * Risky test. + */ + public function addRiskyTest(Test $test, Throwable $t, float $time): void + { + if (!$this->isOfInterest($test)) { + return; + } + + $this->testStatus = BaseTestRunner::STATUS_RISKY; + $this->risky++; + } + + /** + * Skipped test. + */ + public function addSkippedTest(Test $test, Throwable $t, float $time): void + { + if (!$this->isOfInterest($test)) { + return; + } + + $this->testStatus = BaseTestRunner::STATUS_SKIPPED; + $this->skipped++; + } + + /** + * A testsuite started. + */ + public function startTestSuite(TestSuite $suite): void + { + } + + /** + * A testsuite ended. + */ + public function endTestSuite(TestSuite $suite): void + { + } + + /** + * A test started. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function startTest(Test $test): void + { + if (!$this->isOfInterest($test)) { + return; + } + + $class = get_class($test); + + if ($this->testClass !== $class) { + if ($this->testClass !== '') { + $this->doEndClass(); + } + + $this->currentTestClassPrettified = $this->prettifier->prettifyTestClass($class); + $this->testClass = $class; + $this->tests = []; + + $this->startClass($class); + } + + if ($test instanceof TestCase) { + $this->currentTestMethodPrettified = $this->prettifier->prettifyTestCase($test); + } + + $this->testStatus = BaseTestRunner::STATUS_PASSED; + } + + /** + * A test ended. + */ + public function endTest(Test $test, float $time): void + { + if (!$this->isOfInterest($test)) { + return; + } + + $this->tests[] = [$this->currentTestMethodPrettified, $this->testStatus]; + + $this->currentTestClassPrettified = null; + $this->currentTestMethodPrettified = null; + } + + protected function doEndClass(): void + { + foreach ($this->tests as $test) { + $this->onTest($test[0], $test[1] === BaseTestRunner::STATUS_PASSED); + } + + $this->endClass($this->testClass); + } + + /** + * Handler for 'start run' event. + */ + protected function startRun(): void + { + } + + /** + * Handler for 'start class' event. + */ + protected function startClass(string $name): void + { + } + + /** + * Handler for 'on test' event. + */ + protected function onTest(string $name, bool $success = true): void + { + } + + /** + * Handler for 'end class' event. + */ + protected function endClass(string $name): void + { + } + + /** + * Handler for 'end run' event. + */ + protected function endRun(): void + { + } + + private function isOfInterest(Test $test): bool + { + if (!$test instanceof TestCase) { + return false; + } + + if ($test instanceof ErrorTestCase || $test instanceof WarningTestCase) { + return false; + } + + if (!empty($this->groups)) { + foreach ($test->getGroups() as $group) { + if (in_array($group, $this->groups, true)) { + return true; + } + } + + return false; + } + + if (!empty($this->excludeGroups)) { + foreach ($test->getGroups() as $group) { + if (in_array($group, $this->excludeGroups, true)) { + return false; + } + } + + return true; + } + + return true; + } +} diff --git a/vendor/phpunit/phpunit/src/Util/TestDox/TestDoxPrinter.php b/vendor/phpunit/phpunit/src/Util/TestDox/TestDoxPrinter.php new file mode 100644 index 00000000..5a418998 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/TestDox/TestDoxPrinter.php @@ -0,0 +1,388 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\TestDox; + +use const PHP_EOL; +use function array_map; +use function get_class; +use function implode; +use function method_exists; +use function preg_split; +use function trim; +use PHPUnit\Framework\AssertionFailedError; +use PHPUnit\Framework\Reorderable; +use PHPUnit\Framework\Test; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\TestResult; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Framework\Warning; +use PHPUnit\Runner\BaseTestRunner; +use PHPUnit\Runner\PhptTestCase; +use PHPUnit\TextUI\DefaultResultPrinter; +use Throwable; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +class TestDoxPrinter extends DefaultResultPrinter +{ + /** + * @var NamePrettifier + */ + protected $prettifier; + + /** + * @var int The number of test results received from the TestRunner + */ + protected $testIndex = 0; + + /** + * @var int The number of test results already sent to the output + */ + protected $testFlushIndex = 0; + + /** + * @var array Buffer for test results + */ + protected $testResults = []; + + /** + * @var array Lookup table for testname to testResults[index] + */ + protected $testNameResultIndex = []; + + /** + * @var bool + */ + protected $enableOutputBuffer = false; + + /** + * @var array array + */ + protected $originalExecutionOrder = []; + + /** + * @var int + */ + protected $spinState = 0; + + /** + * @var bool + */ + protected $showProgress = true; + + /** + * @param null|resource|string $out + * @param int|string $numberOfColumns + * + * @throws \PHPUnit\Framework\Exception + */ + public function __construct($out = null, bool $verbose = false, string $colors = self::COLOR_DEFAULT, bool $debug = false, $numberOfColumns = 80, bool $reverse = false) + { + parent::__construct($out, $verbose, $colors, $debug, $numberOfColumns, $reverse); + + $this->prettifier = new NamePrettifier($this->colors); + } + + public function setOriginalExecutionOrder(array $order): void + { + $this->originalExecutionOrder = $order; + $this->enableOutputBuffer = !empty($order); + } + + public function setShowProgressAnimation(bool $showProgress): void + { + $this->showProgress = $showProgress; + } + + public function printResult(TestResult $result): void + { + } + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function endTest(Test $test, float $time): void + { + if (!$test instanceof TestCase && !$test instanceof PhptTestCase && !$test instanceof TestSuite) { + return; + } + + if ($this->testHasPassed()) { + $this->registerTestResult($test, null, BaseTestRunner::STATUS_PASSED, $time, false); + } + + if ($test instanceof TestCase || $test instanceof PhptTestCase) { + $this->testIndex++; + } + + parent::endTest($test, $time); + } + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function addError(Test $test, Throwable $t, float $time): void + { + $this->registerTestResult($test, $t, BaseTestRunner::STATUS_ERROR, $time, true); + } + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function addWarning(Test $test, Warning $e, float $time): void + { + $this->registerTestResult($test, $e, BaseTestRunner::STATUS_WARNING, $time, true); + } + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function addFailure(Test $test, AssertionFailedError $e, float $time): void + { + $this->registerTestResult($test, $e, BaseTestRunner::STATUS_FAILURE, $time, true); + } + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function addIncompleteTest(Test $test, Throwable $t, float $time): void + { + $this->registerTestResult($test, $t, BaseTestRunner::STATUS_INCOMPLETE, $time, false); + } + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function addRiskyTest(Test $test, Throwable $t, float $time): void + { + $this->registerTestResult($test, $t, BaseTestRunner::STATUS_RISKY, $time, false); + } + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function addSkippedTest(Test $test, Throwable $t, float $time): void + { + $this->registerTestResult($test, $t, BaseTestRunner::STATUS_SKIPPED, $time, false); + } + + public function writeProgress(string $progress): void + { + $this->flushOutputBuffer(); + } + + public function flush(): void + { + $this->flushOutputBuffer(true); + } + + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + protected function registerTestResult(Test $test, ?Throwable $t, int $status, float $time, bool $verbose): void + { + $testName = $test instanceof Reorderable ? $test->sortId() : $test->getName(); + + $result = [ + 'className' => $this->formatClassName($test), + 'testName' => $testName, + 'testMethod' => $this->formatTestName($test), + 'message' => '', + 'status' => $status, + 'time' => $time, + 'verbose' => $verbose, + ]; + + if ($t !== null) { + $result['message'] = $this->formatTestResultMessage($t, $result); + } + + $this->testResults[$this->testIndex] = $result; + $this->testNameResultIndex[$testName] = $this->testIndex; + } + + protected function formatTestName(Test $test): string + { + return method_exists($test, 'getName') ? $test->getName() : ''; + } + + protected function formatClassName(Test $test): string + { + return get_class($test); + } + + protected function testHasPassed(): bool + { + if (!isset($this->testResults[$this->testIndex]['status'])) { + return true; + } + + if ($this->testResults[$this->testIndex]['status'] === BaseTestRunner::STATUS_PASSED) { + return true; + } + + return false; + } + + protected function flushOutputBuffer(bool $forceFlush = false): void + { + if ($this->testFlushIndex === $this->testIndex) { + return; + } + + if ($this->testFlushIndex > 0) { + if ($this->enableOutputBuffer && + isset($this->originalExecutionOrder[$this->testFlushIndex - 1])) { + $prevResult = $this->getTestResultByName($this->originalExecutionOrder[$this->testFlushIndex - 1]); + } else { + $prevResult = $this->testResults[$this->testFlushIndex - 1]; + } + } else { + $prevResult = $this->getEmptyTestResult(); + } + + if (!$this->enableOutputBuffer) { + $this->writeTestResult($prevResult, $this->testResults[$this->testFlushIndex++]); + } else { + do { + $flushed = false; + + if (!$forceFlush && isset($this->originalExecutionOrder[$this->testFlushIndex])) { + $result = $this->getTestResultByName($this->originalExecutionOrder[$this->testFlushIndex]); + } else { + // This test(name) cannot found in original execution order, + // flush result to output stream right away + $result = $this->testResults[$this->testFlushIndex]; + } + + if (!empty($result)) { + $this->hideSpinner(); + $this->writeTestResult($prevResult, $result); + $this->testFlushIndex++; + $prevResult = $result; + $flushed = true; + } else { + $this->showSpinner(); + } + } while ($flushed && $this->testFlushIndex < $this->testIndex); + } + } + + protected function showSpinner(): void + { + if (!$this->showProgress) { + return; + } + + if ($this->spinState) { + $this->undrawSpinner(); + } + + $this->spinState++; + $this->drawSpinner(); + } + + protected function hideSpinner(): void + { + if (!$this->showProgress) { + return; + } + + if ($this->spinState) { + $this->undrawSpinner(); + } + + $this->spinState = 0; + } + + protected function drawSpinner(): void + { + // optional for CLI printers: show the user a 'buffering output' spinner + } + + protected function undrawSpinner(): void + { + // remove the spinner from the current line + } + + protected function writeTestResult(array $prevResult, array $result): void + { + } + + protected function getEmptyTestResult(): array + { + return [ + 'className' => '', + 'testName' => '', + 'message' => '', + 'failed' => '', + 'verbose' => '', + ]; + } + + protected function getTestResultByName(?string $testName): array + { + if (isset($this->testNameResultIndex[$testName])) { + return $this->testResults[$this->testNameResultIndex[$testName]]; + } + + return []; + } + + protected function formatThrowable(Throwable $t, ?int $status = null): string + { + $message = trim(\PHPUnit\Framework\TestFailure::exceptionToString($t)); + + if ($message) { + $message .= PHP_EOL . PHP_EOL . $this->formatStacktrace($t); + } else { + $message = $this->formatStacktrace($t); + } + + return $message; + } + + protected function formatStacktrace(Throwable $t): string + { + return \PHPUnit\Util\Filter::getFilteredStacktrace($t); + } + + protected function formatTestResultMessage(Throwable $t, array $result, string $prefix = '│'): string + { + $message = $this->formatThrowable($t, $result['status']); + + if ($message === '') { + return ''; + } + + if (!($this->verbose || $result['verbose'])) { + return ''; + } + + return $this->prefixLines($prefix, $message); + } + + protected function prefixLines(string $prefix, string $message): string + { + $message = trim($message); + + return implode( + PHP_EOL, + array_map( + static function (string $text) use ($prefix) + { + return ' ' . $prefix . ($text ? ' ' . $text : ''); + }, + preg_split('/\r\n|\r|\n/', $message), + ), + ); + } +} diff --git a/vendor/phpunit/phpunit/src/Util/TestDox/TextResultPrinter.php b/vendor/phpunit/phpunit/src/Util/TestDox/TextResultPrinter.php new file mode 100644 index 00000000..8a1893e5 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/TestDox/TextResultPrinter.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\TestDox; + +use PHPUnit\Framework\TestResult; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TextResultPrinter extends ResultPrinter +{ + public function printResult(TestResult $result): void + { + } + + /** + * Handler for 'start class' event. + */ + protected function startClass(string $name): void + { + $this->write($this->currentTestClassPrettified . "\n"); + } + + /** + * Handler for 'on test' event. + */ + protected function onTest(string $name, bool $success = true): void + { + if ($success) { + $this->write(' [x] '); + } else { + $this->write(' [ ] '); + } + + $this->write($name . "\n"); + } + + /** + * Handler for 'end class' event. + */ + protected function endClass(string $name): void + { + $this->write("\n"); + } +} diff --git a/vendor/phpunit/phpunit/src/Util/TestDox/XmlResultPrinter.php b/vendor/phpunit/phpunit/src/Util/TestDox/XmlResultPrinter.php new file mode 100644 index 00000000..84d4d37c --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/TestDox/XmlResultPrinter.php @@ -0,0 +1,261 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\TestDox; + +use function array_filter; +use function get_class; +use function implode; +use function strpos; +use DOMDocument; +use DOMElement; +use PHPUnit\Framework\AssertionFailedError; +use PHPUnit\Framework\Exception; +use PHPUnit\Framework\Test; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\TestListener; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Framework\Warning; +use PHPUnit\Framework\WarningTestCase; +use PHPUnit\Util\Printer; +use PHPUnit\Util\Test as TestUtil; +use ReflectionClass; +use ReflectionException; +use Throwable; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class XmlResultPrinter extends Printer implements TestListener +{ + /** + * @var DOMDocument + */ + private $document; + + /** + * @var DOMElement + */ + private $root; + + /** + * @var NamePrettifier + */ + private $prettifier; + + /** + * @var null|Throwable + */ + private $exception; + + /** + * @param resource|string $out + * + * @throws Exception + */ + public function __construct($out = null) + { + $this->document = new DOMDocument('1.0', 'UTF-8'); + $this->document->formatOutput = true; + + $this->root = $this->document->createElement('tests'); + $this->document->appendChild($this->root); + + $this->prettifier = new NamePrettifier; + + parent::__construct($out); + } + + /** + * Flush buffer and close output. + */ + public function flush(): void + { + $this->write($this->document->saveXML()); + + parent::flush(); + } + + /** + * An error occurred. + */ + public function addError(Test $test, Throwable $t, float $time): void + { + $this->exception = $t; + } + + /** + * A warning occurred. + */ + public function addWarning(Test $test, Warning $e, float $time): void + { + } + + /** + * A failure occurred. + */ + public function addFailure(Test $test, AssertionFailedError $e, float $time): void + { + $this->exception = $e; + } + + /** + * Incomplete test. + */ + public function addIncompleteTest(Test $test, Throwable $t, float $time): void + { + } + + /** + * Risky test. + */ + public function addRiskyTest(Test $test, Throwable $t, float $time): void + { + } + + /** + * Skipped test. + */ + public function addSkippedTest(Test $test, Throwable $t, float $time): void + { + } + + /** + * A test suite started. + */ + public function startTestSuite(TestSuite $suite): void + { + } + + /** + * A test suite ended. + */ + public function endTestSuite(TestSuite $suite): void + { + } + + /** + * A test started. + */ + public function startTest(Test $test): void + { + $this->exception = null; + } + + /** + * A test ended. + * + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function endTest(Test $test, float $time): void + { + if (!$test instanceof TestCase || $test instanceof WarningTestCase) { + return; + } + + $groups = array_filter( + $test->getGroups(), + static function ($group) + { + return !($group === 'small' || $group === 'medium' || $group === 'large' || strpos($group, '__phpunit_') === 0); + }, + ); + + $testNode = $this->document->createElement('test'); + + $testNode->setAttribute('className', get_class($test)); + $testNode->setAttribute('methodName', $test->getName()); + $testNode->setAttribute('prettifiedClassName', $this->prettifier->prettifyTestClass(get_class($test))); + $testNode->setAttribute('prettifiedMethodName', $this->prettifier->prettifyTestCase($test)); + $testNode->setAttribute('status', (string) $test->getStatus()); + $testNode->setAttribute('time', (string) $time); + $testNode->setAttribute('size', (string) $test->getSize()); + $testNode->setAttribute('groups', implode(',', $groups)); + + foreach ($groups as $group) { + $groupNode = $this->document->createElement('group'); + + $groupNode->setAttribute('name', $group); + + $testNode->appendChild($groupNode); + } + + $annotations = TestUtil::parseTestMethodAnnotations( + get_class($test), + $test->getName(false), + ); + + foreach (['class', 'method'] as $type) { + foreach ($annotations[$type] as $annotation => $values) { + if ($annotation !== 'covers' && $annotation !== 'uses') { + continue; + } + + foreach ($values as $value) { + $coversNode = $this->document->createElement($annotation); + + $coversNode->setAttribute('target', $value); + + $testNode->appendChild($coversNode); + } + } + } + + foreach ($test->doubledTypes() as $doubledType) { + $testDoubleNode = $this->document->createElement('testDouble'); + + $testDoubleNode->setAttribute('type', $doubledType); + + $testNode->appendChild($testDoubleNode); + } + + $inlineAnnotations = TestUtil::getInlineAnnotations(get_class($test), $test->getName(false)); + + if (isset($inlineAnnotations['given'], $inlineAnnotations['when'], $inlineAnnotations['then'])) { + $testNode->setAttribute('given', $inlineAnnotations['given']['value']); + $testNode->setAttribute('givenStartLine', (string) $inlineAnnotations['given']['line']); + $testNode->setAttribute('when', $inlineAnnotations['when']['value']); + $testNode->setAttribute('whenStartLine', (string) $inlineAnnotations['when']['line']); + $testNode->setAttribute('then', $inlineAnnotations['then']['value']); + $testNode->setAttribute('thenStartLine', (string) $inlineAnnotations['then']['line']); + } + + if ($this->exception !== null) { + if ($this->exception instanceof Exception) { + $steps = $this->exception->getSerializableTrace(); + } else { + $steps = $this->exception->getTrace(); + } + + try { + $file = (new ReflectionClass($test))->getFileName(); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + foreach ($steps as $step) { + if (isset($step['file']) && $step['file'] === $file) { + $testNode->setAttribute('exceptionLine', (string) $step['line']); + + break; + } + } + + $testNode->setAttribute('exceptionMessage', $this->exception->getMessage()); + } + + $this->root->appendChild($testNode); + } +} diff --git a/vendor/phpunit/phpunit/src/Util/TextTestListRenderer.php b/vendor/phpunit/phpunit/src/Util/TextTestListRenderer.php new file mode 100644 index 00000000..beb0e4b0 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/TextTestListRenderer.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use const PHP_EOL; +use function get_class; +use function sprintf; +use function str_replace; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Runner\PhptTestCase; +use RecursiveIteratorIterator; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TextTestListRenderer +{ + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function render(TestSuite $suite): string + { + $buffer = 'Available test(s):' . PHP_EOL; + + foreach (new RecursiveIteratorIterator($suite->getIterator()) as $test) { + if ($test instanceof TestCase) { + $name = sprintf( + '%s::%s', + get_class($test), + str_replace(' with data set ', '', $test->getName()), + ); + } elseif ($test instanceof PhptTestCase) { + $name = $test->getName(); + } else { + continue; + } + + $buffer .= sprintf( + ' - %s' . PHP_EOL, + $name, + ); + } + + return $buffer; + } +} diff --git a/vendor/phpunit/phpunit/src/Util/Type.php b/vendor/phpunit/phpunit/src/Util/Type.php new file mode 100644 index 00000000..ec6a1d78 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/Type.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Type +{ + public static function isType(string $type): bool + { + switch ($type) { + case 'numeric': + case 'integer': + case 'int': + case 'iterable': + case 'float': + case 'string': + case 'boolean': + case 'bool': + case 'null': + case 'array': + case 'object': + case 'resource': + case 'scalar': + return true; + + default: + return false; + } + } +} diff --git a/vendor/phpunit/phpunit/src/Util/VersionComparisonOperator.php b/vendor/phpunit/phpunit/src/Util/VersionComparisonOperator.php new file mode 100644 index 00000000..57ca7c32 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/VersionComparisonOperator.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use function in_array; +use function sprintf; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class VersionComparisonOperator +{ + /** + * @psalm-var '<'|'lt'|'<='|'le'|'>'|'gt'|'>='|'ge'|'=='|'='|'eq'|'!='|'<>'|'ne' + */ + private $operator; + + public function __construct(string $operator) + { + $this->ensureOperatorIsValid($operator); + + $this->operator = $operator; + } + + /** + * @return '!='|'<'|'<='|'<>'|'='|'=='|'>'|'>='|'eq'|'ge'|'gt'|'le'|'lt'|'ne' + */ + public function asString(): string + { + return $this->operator; + } + + /** + * @throws Exception + * + * @psalm-assert '<'|'lt'|'<='|'le'|'>'|'gt'|'>='|'ge'|'=='|'='|'eq'|'!='|'<>'|'ne' $operator + */ + private function ensureOperatorIsValid(string $operator): void + { + if (!in_array($operator, ['<', 'lt', '<=', 'le', '>', 'gt', '>=', 'ge', '==', '=', 'eq', '!=', '<>', 'ne'], true)) { + throw new Exception( + sprintf( + '"%s" is not a valid version_compare() operator', + $operator, + ), + ); + } + } +} diff --git a/vendor/phpunit/phpunit/src/Util/XdebugFilterScriptGenerator.php b/vendor/phpunit/phpunit/src/Util/XdebugFilterScriptGenerator.php new file mode 100644 index 00000000..d6366eaa --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/XdebugFilterScriptGenerator.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use const DIRECTORY_SEPARATOR; +use function addslashes; +use function array_map; +use function implode; +use function is_string; +use function realpath; +use function sprintf; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\CodeCoverage as FilterConfiguration; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @deprecated + */ +final class XdebugFilterScriptGenerator +{ + public function generate(FilterConfiguration $filter): string + { + $files = array_map( + static function ($item) + { + return sprintf( + " '%s'", + $item, + ); + }, + $this->getItems($filter), + ); + + $files = implode(",\n", $files); + + return <<directories() as $directory) { + $path = realpath($directory->path()); + + if (is_string($path)) { + $files[] = sprintf( + addslashes('%s' . DIRECTORY_SEPARATOR), + $path, + ); + } + } + + foreach ($filter->files() as $file) { + $files[] = $file->path(); + } + + return $files; + } +} diff --git a/vendor/phpunit/phpunit/src/Util/Xml.php b/vendor/phpunit/phpunit/src/Util/Xml.php new file mode 100644 index 00000000..efdd56ef --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/Xml.php @@ -0,0 +1,193 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use const ENT_QUOTES; +use function assert; +use function class_exists; +use function htmlspecialchars; +use function mb_convert_encoding; +use function ord; +use function preg_replace; +use function settype; +use function strlen; +use DOMCharacterData; +use DOMDocument; +use DOMElement; +use DOMNode; +use DOMText; +use ReflectionClass; +use ReflectionException; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Xml +{ + /** + * @deprecated Only used by assertEqualXMLStructure() + */ + public static function import(DOMElement $element): DOMElement + { + return (new DOMDocument)->importNode($element, true); + } + + /** + * @deprecated Only used by assertEqualXMLStructure() + */ + public static function removeCharacterDataNodes(DOMNode $node): void + { + if ($node->hasChildNodes()) { + for ($i = $node->childNodes->length - 1; $i >= 0; $i--) { + if (($child = $node->childNodes->item($i)) instanceof DOMCharacterData) { + $node->removeChild($child); + } + } + } + } + + /** + * Escapes a string for the use in XML documents. + * + * Any Unicode character is allowed, excluding the surrogate blocks, FFFE, + * and FFFF (not even as character reference). + * + * @see https://www.w3.org/TR/xml/#charsets + */ + public static function prepareString(string $string): string + { + return preg_replace( + '/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]/', + '', + htmlspecialchars( + self::convertToUtf8($string), + ENT_QUOTES, + ), + ); + } + + /** + * "Convert" a DOMElement object into a PHP variable. + */ + public static function xmlToVariable(DOMElement $element) + { + $variable = null; + + switch ($element->tagName) { + case 'array': + $variable = []; + + foreach ($element->childNodes as $entry) { + if (!$entry instanceof DOMElement || $entry->tagName !== 'element') { + continue; + } + $item = $entry->childNodes->item(0); + + if ($item instanceof DOMText) { + $item = $entry->childNodes->item(1); + } + + $value = self::xmlToVariable($item); + + if ($entry->hasAttribute('key')) { + $variable[(string) $entry->getAttribute('key')] = $value; + } else { + $variable[] = $value; + } + } + + break; + + case 'object': + $className = $element->getAttribute('class'); + + if ($element->hasChildNodes()) { + $arguments = $element->childNodes->item(0)->childNodes; + $constructorArgs = []; + + foreach ($arguments as $argument) { + if ($argument instanceof DOMElement) { + $constructorArgs[] = self::xmlToVariable($argument); + } + } + + try { + assert(class_exists($className)); + + $variable = (new ReflectionClass($className))->newInstanceArgs($constructorArgs); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + } else { + $variable = new $className; + } + + break; + + case 'boolean': + $variable = $element->textContent === 'true'; + + break; + + case 'integer': + case 'double': + case 'string': + $variable = $element->textContent; + + settype($variable, $element->tagName); + + break; + } + + return $variable; + } + + private static function convertToUtf8(string $string): string + { + if (!self::isUtf8($string)) { + $string = mb_convert_encoding($string, 'UTF-8'); + } + + return $string; + } + + private static function isUtf8(string $string): bool + { + $length = strlen($string); + + for ($i = 0; $i < $length; $i++) { + if (ord($string[$i]) < 0x80) { + $n = 0; + } elseif ((ord($string[$i]) & 0xE0) === 0xC0) { + $n = 1; + } elseif ((ord($string[$i]) & 0xF0) === 0xE0) { + $n = 2; + } elseif ((ord($string[$i]) & 0xF0) === 0xF0) { + $n = 3; + } else { + return false; + } + + for ($j = 0; $j < $n; $j++) { + if ((++$i === $length) || ((ord($string[$i]) & 0xC0) !== 0x80)) { + return false; + } + } + } + + return true; + } +} diff --git a/vendor/phpunit/phpunit/src/Util/Xml/Exception.php b/vendor/phpunit/phpunit/src/Util/Xml/Exception.php new file mode 100644 index 00000000..09b73d8f --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/Xml/Exception.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\Xml; + +use RuntimeException; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Exception extends RuntimeException implements \PHPUnit\Exception +{ +} diff --git a/vendor/phpunit/phpunit/src/Util/Xml/FailedSchemaDetectionResult.php b/vendor/phpunit/phpunit/src/Util/Xml/FailedSchemaDetectionResult.php new file mode 100644 index 00000000..0949f568 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/Xml/FailedSchemaDetectionResult.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\Xml; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class FailedSchemaDetectionResult extends SchemaDetectionResult +{ +} diff --git a/vendor/phpunit/phpunit/src/Util/Xml/Loader.php b/vendor/phpunit/phpunit/src/Util/Xml/Loader.php new file mode 100644 index 00000000..2ba5ace3 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/Xml/Loader.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\Xml; + +use function chdir; +use function dirname; +use function error_reporting; +use function file_get_contents; +use function getcwd; +use function libxml_get_errors; +use function libxml_use_internal_errors; +use function sprintf; +use DOMDocument; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Loader +{ + /** + * @throws Exception + */ + public function loadFile(string $filename, bool $isHtml = false, bool $xinclude = false, bool $strict = false): DOMDocument + { + $reporting = error_reporting(0); + $contents = file_get_contents($filename); + + error_reporting($reporting); + + if ($contents === false) { + throw new Exception( + sprintf( + 'Could not read "%s".', + $filename, + ), + ); + } + + return $this->load($contents, $isHtml, $filename, $xinclude, $strict); + } + + /** + * @throws Exception + */ + public function load(string $actual, bool $isHtml = false, string $filename = '', bool $xinclude = false, bool $strict = false): DOMDocument + { + if ($actual === '') { + throw new Exception('Could not load XML from empty string'); + } + + // Required for XInclude on Windows. + if ($xinclude) { + $cwd = getcwd(); + @chdir(dirname($filename)); + } + + $document = new DOMDocument; + $document->preserveWhiteSpace = false; + + $internal = libxml_use_internal_errors(true); + $message = ''; + $reporting = error_reporting(0); + + if ($filename !== '') { + // Required for XInclude + $document->documentURI = $filename; + } + + if ($isHtml) { + $loaded = $document->loadHTML($actual); + } else { + $loaded = $document->loadXML($actual); + } + + if (!$isHtml && $xinclude) { + $document->xinclude(); + } + + foreach (libxml_get_errors() as $error) { + $message .= "\n" . $error->message; + } + + libxml_use_internal_errors($internal); + error_reporting($reporting); + + if (isset($cwd)) { + @chdir($cwd); + } + + if ($loaded === false || ($strict && $message !== '')) { + if ($filename !== '') { + throw new Exception( + sprintf( + 'Could not load "%s".%s', + $filename, + $message !== '' ? "\n" . $message : '', + ), + ); + } + + if ($message === '') { + $message = 'Could not load XML for unknown reason'; + } + + throw new Exception($message); + } + + return $document; + } +} diff --git a/vendor/phpunit/phpunit/src/Util/Xml/SchemaDetectionResult.php b/vendor/phpunit/phpunit/src/Util/Xml/SchemaDetectionResult.php new file mode 100644 index 00000000..3ae45723 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/Xml/SchemaDetectionResult.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\Xml; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +abstract class SchemaDetectionResult +{ + /** + * @psalm-assert-if-true SuccessfulSchemaDetectionResult $this + */ + public function detected(): bool + { + return false; + } + + /** + * @throws Exception + */ + public function version(): string + { + throw new Exception('No supported schema was detected'); + } +} diff --git a/vendor/phpunit/phpunit/src/Util/Xml/SchemaDetector.php b/vendor/phpunit/phpunit/src/Util/Xml/SchemaDetector.php new file mode 100644 index 00000000..1877a9a1 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/Xml/SchemaDetector.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\Xml; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class SchemaDetector +{ + /** + * @throws Exception + */ + public function detect(string $filename): SchemaDetectionResult + { + $document = (new Loader)->loadFile( + $filename, + false, + true, + true, + ); + + $schemaFinder = new SchemaFinder; + + foreach ($schemaFinder->available() as $candidate) { + $schema = (new SchemaFinder)->find($candidate); + + if (!(new Validator)->validate($document, $schema)->hasValidationErrors()) { + return new SuccessfulSchemaDetectionResult($candidate); + } + } + + return new FailedSchemaDetectionResult; + } +} diff --git a/vendor/phpunit/phpunit/src/Util/Xml/SchemaFinder.php b/vendor/phpunit/phpunit/src/Util/Xml/SchemaFinder.php new file mode 100644 index 00000000..eb5f4f15 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/Xml/SchemaFinder.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\Xml; + +use function assert; +use function defined; +use function is_file; +use function rsort; +use function sprintf; +use DirectoryIterator; +use PHPUnit\Runner\Version; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class SchemaFinder +{ + /** + * @psalm-return non-empty-list + */ + public function available(): array + { + $result = [Version::series()]; + + foreach ((new DirectoryIterator($this->path() . 'schema')) as $file) { + if ($file->isDot()) { + continue; + } + + $version = $file->getBasename('.xsd'); + + assert(!empty($version)); + + $result[] = $version; + } + + rsort($result); + + return $result; + } + + /** + * @throws Exception + */ + public function find(string $version): string + { + if ($version === Version::series()) { + $filename = $this->path() . 'phpunit.xsd'; + } else { + $filename = $this->path() . 'schema/' . $version . '.xsd'; + } + + if (!is_file($filename)) { + throw new Exception( + sprintf( + 'Schema for PHPUnit %s is not available', + $version, + ), + ); + } + + return $filename; + } + + private function path(): string + { + if (defined('__PHPUNIT_PHAR_ROOT__')) { + return __PHPUNIT_PHAR_ROOT__ . '/'; + } + + return __DIR__ . '/../../../'; + } +} diff --git a/vendor/phpunit/phpunit/src/Util/Xml/SnapshotNodeList.php b/vendor/phpunit/phpunit/src/Util/Xml/SnapshotNodeList.php new file mode 100644 index 00000000..e383678d --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/Xml/SnapshotNodeList.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\Xml; + +use function count; +use ArrayIterator; +use Countable; +use DOMNode; +use DOMNodeList; +use IteratorAggregate; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @template-implements IteratorAggregate + */ +final class SnapshotNodeList implements Countable, IteratorAggregate +{ + /** + * @var DOMNode[] + */ + private $nodes = []; + + public static function fromNodeList(DOMNodeList $list): self + { + $snapshot = new self; + + foreach ($list as $node) { + $snapshot->nodes[] = $node; + } + + return $snapshot; + } + + public function count(): int + { + return count($this->nodes); + } + + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->nodes); + } +} diff --git a/vendor/phpunit/phpunit/src/Util/Xml/SuccessfulSchemaDetectionResult.php b/vendor/phpunit/phpunit/src/Util/Xml/SuccessfulSchemaDetectionResult.php new file mode 100644 index 00000000..77202c35 --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/Xml/SuccessfulSchemaDetectionResult.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\Xml; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class SuccessfulSchemaDetectionResult extends SchemaDetectionResult +{ + /** + * @psalm-var non-empty-string + */ + private $version; + + /** + * @psalm-param non-empty-string $version + */ + public function __construct(string $version) + { + $this->version = $version; + } + + /** + * @psalm-assert-if-true SuccessfulSchemaDetectionResult $this + */ + public function detected(): bool + { + return true; + } + + /** + * @psalm-return non-empty-string + */ + public function version(): string + { + return $this->version; + } +} diff --git a/vendor/phpunit/phpunit/src/Util/Xml/ValidationResult.php b/vendor/phpunit/phpunit/src/Util/Xml/ValidationResult.php new file mode 100644 index 00000000..3292267b --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/Xml/ValidationResult.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\Xml; + +use function sprintf; +use function trim; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @psalm-immutable + */ +final class ValidationResult +{ + /** + * @psalm-var array> + */ + private $validationErrors = []; + + /** + * @psalm-param array $errors + */ + public static function fromArray(array $errors): self + { + $validationErrors = []; + + foreach ($errors as $error) { + if (!isset($validationErrors[$error->line])) { + $validationErrors[$error->line] = []; + } + + $validationErrors[$error->line][] = trim($error->message); + } + + return new self($validationErrors); + } + + private function __construct(array $validationErrors) + { + $this->validationErrors = $validationErrors; + } + + public function hasValidationErrors(): bool + { + return !empty($this->validationErrors); + } + + public function asString(): string + { + $buffer = ''; + + foreach ($this->validationErrors as $line => $validationErrorsOnLine) { + $buffer .= sprintf(PHP_EOL . ' Line %d:' . PHP_EOL, $line); + + foreach ($validationErrorsOnLine as $validationError) { + $buffer .= sprintf(' - %s' . PHP_EOL, $validationError); + } + } + + return $buffer; + } +} diff --git a/vendor/phpunit/phpunit/src/Util/Xml/Validator.php b/vendor/phpunit/phpunit/src/Util/Xml/Validator.php new file mode 100644 index 00000000..b3c4e05b --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/Xml/Validator.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\Xml; + +use function file_get_contents; +use function libxml_clear_errors; +use function libxml_get_errors; +use function libxml_use_internal_errors; +use DOMDocument; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Validator +{ + public function validate(DOMDocument $document, string $xsdFilename): ValidationResult + { + $originalErrorHandling = libxml_use_internal_errors(true); + + $document->schemaValidateSource(file_get_contents($xsdFilename)); + + $errors = libxml_get_errors(); + libxml_clear_errors(); + libxml_use_internal_errors($originalErrorHandling); + + return ValidationResult::fromArray($errors); + } +} diff --git a/vendor/phpunit/phpunit/src/Util/XmlTestListRenderer.php b/vendor/phpunit/phpunit/src/Util/XmlTestListRenderer.php new file mode 100644 index 00000000..7a63b10d --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/XmlTestListRenderer.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use function get_class; +use function implode; +use function str_replace; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Runner\PhptTestCase; +use RecursiveIteratorIterator; +use XMLWriter; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class XmlTestListRenderer +{ + /** + * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + public function render(TestSuite $suite): string + { + $writer = new XMLWriter; + + $writer->openMemory(); + $writer->setIndent(true); + $writer->startDocument('1.0', 'UTF-8'); + $writer->startElement('tests'); + + $currentTestCase = null; + + foreach (new RecursiveIteratorIterator($suite->getIterator()) as $test) { + if ($test instanceof TestCase) { + if (get_class($test) !== $currentTestCase) { + if ($currentTestCase !== null) { + $writer->endElement(); + } + + $writer->startElement('testCaseClass'); + $writer->writeAttribute('name', get_class($test)); + + $currentTestCase = get_class($test); + } + + $writer->startElement('testCaseMethod'); + $writer->writeAttribute('name', $test->getName(false)); + $writer->writeAttribute('groups', implode(',', $test->getGroups())); + + if (!empty($test->getDataSetAsString(false))) { + $writer->writeAttribute( + 'dataSet', + str_replace( + ' with data set ', + '', + $test->getDataSetAsString(false), + ), + ); + } + + $writer->endElement(); + } elseif ($test instanceof PhptTestCase) { + if ($currentTestCase !== null) { + $writer->endElement(); + + $currentTestCase = null; + } + + $writer->startElement('phptFile'); + $writer->writeAttribute('path', $test->getName()); + $writer->endElement(); + } + } + + if ($currentTestCase !== null) { + $writer->endElement(); + } + + $writer->endElement(); + $writer->endDocument(); + + return $writer->outputMemory(); + } +} diff --git a/vendor/psr/event-dispatcher/.editorconfig b/vendor/psr/event-dispatcher/.editorconfig new file mode 100644 index 00000000..164f092d --- /dev/null +++ b/vendor/psr/event-dispatcher/.editorconfig @@ -0,0 +1,15 @@ +; This file is for unifying the coding style for different editors and IDEs. +; More information at http://editorconfig.org + +root = true + +[*] +charset = utf-8 +indent_size = 4 +indent_style = space +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[Makefile] +indent_style = tab diff --git a/vendor/psr/event-dispatcher/.gitignore b/vendor/psr/event-dispatcher/.gitignore new file mode 100644 index 00000000..3a9875b4 --- /dev/null +++ b/vendor/psr/event-dispatcher/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +composer.lock diff --git a/vendor/psr/event-dispatcher/LICENSE b/vendor/psr/event-dispatcher/LICENSE new file mode 100644 index 00000000..3f1559b2 --- /dev/null +++ b/vendor/psr/event-dispatcher/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 PHP-FIG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/psr/event-dispatcher/README.md b/vendor/psr/event-dispatcher/README.md new file mode 100644 index 00000000..294214af --- /dev/null +++ b/vendor/psr/event-dispatcher/README.md @@ -0,0 +1,6 @@ +PSR Event Dispatcher +==================== + +This repository holds the interfaces related to [PSR-14](http://www.php-fig.org/psr/psr-14/). + +Note that this is not an Event Dispatcher implementation of its own. It is merely interfaces that describe the components of an Event Dispatcher. See the specification for more details. diff --git a/vendor/psr/event-dispatcher/composer.json b/vendor/psr/event-dispatcher/composer.json new file mode 100644 index 00000000..667a7144 --- /dev/null +++ b/vendor/psr/event-dispatcher/composer.json @@ -0,0 +1,26 @@ +{ + "name": "psr/event-dispatcher", + "description": "Standard interfaces for event handling.", + "type": "library", + "keywords": ["psr", "psr-14", "events"], + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "require": { + "php": ">=7.2.0" + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/vendor/psr/event-dispatcher/src/EventDispatcherInterface.php b/vendor/psr/event-dispatcher/src/EventDispatcherInterface.php new file mode 100644 index 00000000..4306fa91 --- /dev/null +++ b/vendor/psr/event-dispatcher/src/EventDispatcherInterface.php @@ -0,0 +1,21 @@ +=7.0.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/vendor/psr/http-factory/src/RequestFactoryInterface.php b/vendor/psr/http-factory/src/RequestFactoryInterface.php new file mode 100644 index 00000000..cb39a08b --- /dev/null +++ b/vendor/psr/http-factory/src/RequestFactoryInterface.php @@ -0,0 +1,18 @@ += 5.3. + +[![Build Status](https://travis-ci.org/ralouphie/getallheaders.svg?branch=master)](https://travis-ci.org/ralouphie/getallheaders) +[![Coverage Status](https://coveralls.io/repos/ralouphie/getallheaders/badge.png?branch=master)](https://coveralls.io/r/ralouphie/getallheaders?branch=master) +[![Latest Stable Version](https://poser.pugx.org/ralouphie/getallheaders/v/stable.png)](https://packagist.org/packages/ralouphie/getallheaders) +[![Latest Unstable Version](https://poser.pugx.org/ralouphie/getallheaders/v/unstable.png)](https://packagist.org/packages/ralouphie/getallheaders) +[![License](https://poser.pugx.org/ralouphie/getallheaders/license.png)](https://packagist.org/packages/ralouphie/getallheaders) + + +This is a simple polyfill for [`getallheaders()`](http://www.php.net/manual/en/function.getallheaders.php). + +## Install + +For PHP version **`>= 5.6`**: + +``` +composer require ralouphie/getallheaders +``` + +For PHP version **`< 5.6`**: + +``` +composer require ralouphie/getallheaders "^2" +``` diff --git a/vendor/ralouphie/getallheaders/composer.json b/vendor/ralouphie/getallheaders/composer.json new file mode 100644 index 00000000..de8ce62e --- /dev/null +++ b/vendor/ralouphie/getallheaders/composer.json @@ -0,0 +1,26 @@ +{ + "name": "ralouphie/getallheaders", + "description": "A polyfill for getallheaders.", + "license": "MIT", + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "require": { + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "^5 || ^6.5", + "php-coveralls/php-coveralls": "^2.1" + }, + "autoload": { + "files": ["src/getallheaders.php"] + }, + "autoload-dev": { + "psr-4": { + "getallheaders\\Tests\\": "tests/" + } + } +} diff --git a/vendor/ralouphie/getallheaders/src/getallheaders.php b/vendor/ralouphie/getallheaders/src/getallheaders.php new file mode 100644 index 00000000..c7285a5b --- /dev/null +++ b/vendor/ralouphie/getallheaders/src/getallheaders.php @@ -0,0 +1,46 @@ + 'Content-Type', + 'CONTENT_LENGTH' => 'Content-Length', + 'CONTENT_MD5' => 'Content-Md5', + ); + + foreach ($_SERVER as $key => $value) { + if (substr($key, 0, 5) === 'HTTP_') { + $key = substr($key, 5); + if (!isset($copy_server[$key]) || !isset($_SERVER[$key])) { + $key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key)))); + $headers[$key] = $value; + } + } elseif (isset($copy_server[$key])) { + $headers[$copy_server[$key]] = $value; + } + } + + if (!isset($headers['Authorization'])) { + if (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) { + $headers['Authorization'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION']; + } elseif (isset($_SERVER['PHP_AUTH_USER'])) { + $basic_pass = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : ''; + $headers['Authorization'] = 'Basic ' . base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . $basic_pass); + } elseif (isset($_SERVER['PHP_AUTH_DIGEST'])) { + $headers['Authorization'] = $_SERVER['PHP_AUTH_DIGEST']; + } + } + + return $headers; + } + +} diff --git a/vendor/redaxo/php-cs-fixer-config/.github/workflows/upstream-check.yml b/vendor/redaxo/php-cs-fixer-config/.github/workflows/upstream-check.yml new file mode 100644 index 00000000..afeb99a5 --- /dev/null +++ b/vendor/redaxo/php-cs-fixer-config/.github/workflows/upstream-check.yml @@ -0,0 +1,63 @@ +name: Upstream + +on: + pull_request: + types: [ opened, synchronize, reopened, ready_for_review ] + +jobs: + upstream_run: + name: ${{ matrix.repo }}@${{ matrix.branch }} + runs-on: ubuntu-latest + timeout-minutes: 10 + + concurrency: + group: upstream-check-${{github.event_name}}-${{ matrix.repo }}-${{ matrix.branch }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + + strategy: + fail-fast: false + matrix: + include: + - repo: redaxo/redaxo + branch: main + php: '8.1' + - repo: redaxo/redaxo + branch: 6.x + php: '8.3' + - repo: yakamara/yform + branch: master + php: '8.1' + + if: github.event.pull_request.draft == false + steps: + - uses: actions/checkout@v4 + with: + repository: ${{ matrix.repo }} + ref: ${{ matrix.branch }} + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: none + + - name: set redaxo/php-cs-fixer-config to current branch + run: composer require redaxo/php-cs-fixer-config:dev-${{ github.head_ref }} --no-scripts --no-update --dev + + - name: allow php-cs-fixer version required by redaxo/php-cs-fixer-config + run: composer require friendsofphp/php-cs-fixer:\* --no-scripts --no-update --dev + + - uses: ramsey/composer-install@v3 + if: hashFiles('composer.lock') != '' + with: + dependency-versions: 'highest' # to trigger 'composer update' instead of 'composer install' but limited to redaxo/php-cs-fixer-config updates only + composer-options: 'redaxo/php-cs-fixer-config --prefer-dist --no-progress --with-all-dependencies' + + - uses: ramsey/composer-install@v3 + if: hashFiles('composer.lock') == '' + with: + dependency-versions: 'highest' # to trigger 'composer update' + composer-options: '--prefer-dist --no-progress --with-all-dependencies' + + - name: run php-cs-fixer + run: vendor/bin/php-cs-fixer check -v --ansi --diff || exit 0 diff --git a/vendor/redaxo/php-cs-fixer-config/.gitignore b/vendor/redaxo/php-cs-fixer-config/.gitignore new file mode 100644 index 00000000..df910bc0 --- /dev/null +++ b/vendor/redaxo/php-cs-fixer-config/.gitignore @@ -0,0 +1,3 @@ +/vendor/ +/.php-cs-fixer.cache +/composer.lock diff --git a/vendor/redaxo/php-cs-fixer-config/.php-cs-fixer.dist.php b/vendor/redaxo/php-cs-fixer-config/.php-cs-fixer.dist.php new file mode 100644 index 00000000..8ced3354 --- /dev/null +++ b/vendor/redaxo/php-cs-fixer-config/.php-cs-fixer.dist.php @@ -0,0 +1,11 @@ +in(__DIR__) +; + +return (new Redaxo\PhpCsFixerConfig\Config()) + ->setFinder($finder) +; diff --git a/vendor/redaxo/php-cs-fixer-config/LICENSE b/vendor/redaxo/php-cs-fixer-config/LICENSE new file mode 100644 index 00000000..dce304ce --- /dev/null +++ b/vendor/redaxo/php-cs-fixer-config/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Yakamara Media GmbH & Co. KG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/redaxo/php-cs-fixer-config/README.md b/vendor/redaxo/php-cs-fixer-config/README.md new file mode 100644 index 00000000..fe7f8bca --- /dev/null +++ b/vendor/redaxo/php-cs-fixer-config/README.md @@ -0,0 +1,22 @@ +# [PHP-CS-Fixer](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer) config for [REDAXO](https://github.com/redaxo/redaxo) + +### Installation + +``` +composer require --dev redaxo/php-cs-fixer-config +``` + +Example `.php-cs-fixer.dist.php`: + +```php +in(__DIR__) +; + +return (Redaxo\PhpCsFixerConfig\Config::redaxo5()) // or `::redaxo6()` + ->setFinder($finder) +; + +``` diff --git a/vendor/redaxo/php-cs-fixer-config/composer.json b/vendor/redaxo/php-cs-fixer-config/composer.json new file mode 100644 index 00000000..8e34b798 --- /dev/null +++ b/vendor/redaxo/php-cs-fixer-config/composer.json @@ -0,0 +1,34 @@ +{ + "name": "redaxo/php-cs-fixer-config", + "description": "php-cs-fixer config for REDAXO", + "type": "library", + "keywords": [ + "fixer", + "standards", + "static analysis", + "static code analysis" + ], + "license": "MIT", + "authors": [ + { + "name": "REDAXO Team", + "email": "info@redaxo.org" + } + ], + "require": { + "php": "^8.1", + "friendsofphp/php-cs-fixer": "^3.51.0", + "kubawerlos/php-cs-fixer-custom-fixers": "^3.21.0" + }, + "autoload": { + "psr-4": { + "Redaxo\\PhpCsFixerConfig\\": "src" + } + }, + "config": { + "preferred-install": { + "*": "dist" + }, + "sort-packages": true + } +} diff --git a/vendor/redaxo/php-cs-fixer-config/src/Config.php b/vendor/redaxo/php-cs-fixer-config/src/Config.php new file mode 100644 index 00000000..5bc30e9d --- /dev/null +++ b/vendor/redaxo/php-cs-fixer-config/src/Config.php @@ -0,0 +1,137 @@ +> */ + private array $defaultRules; + + /** + * @deprecated use `Config::redaxo5()` or `Config::redaxo6()` instead + */ + public function __construct(string $name = 'REDAXO 5') + { + parent::__construct($name); + + $this->setUsingCache(true); + $this->setRiskyAllowed(true); + $this->registerCustomFixers(new Fixers()); + $this->registerCustomFixers([ + new NoSemicolonBeforeClosingTagFixer(), + new StatementIndentationFixer(), + ]); + + $this->defaultRules = [ + '@PER-CS2.0' => true, + '@PER-CS2.0:risky' => true, + '@Symfony' => true, + '@Symfony:risky' => true, + '@PHP81Migration' => true, + '@PHP80Migration:risky' => true, + '@PHPUnit100Migration:risky' => true, + + 'array_indentation' => true, + 'blank_line_before_statement' => false, + 'comment_to_phpdoc' => true, + 'concat_space' => ['spacing' => 'one'], + 'declare_strict_types' => false, + 'echo_tag_syntax' => ['format' => 'short'], + 'fully_qualified_strict_types' => ['import_symbols' => true], + 'global_namespace_import' => [ + 'import_constants' => true, + 'import_functions' => true, + 'import_classes' => true, + ], + 'heredoc_to_nowdoc' => true, + 'method_argument_space' => ['on_multiline' => 'ignore'], + 'multiline_comment_opening_closing' => true, + 'native_constant_invocation' => [ + 'scope' => 'namespaced', + 'strict' => false, + ], + 'no_alternative_syntax' => false, + 'no_blank_lines_after_phpdoc' => false, + 'no_superfluous_elseif' => true, + 'no_superfluous_phpdoc_tags' => [ + 'allow_mixed' => true, + 'remove_inheritdoc' => true, + ], + 'no_unreachable_default_argument_value' => true, + 'no_useless_else' => true, + 'no_useless_return' => true, + 'ordered_class_elements' => ['order' => [ + 'use_trait', + 'case', + 'constant_public', + 'constant_protected', + 'constant_private', + 'property', + 'construct', + 'phpunit', + 'method', + ]], + 'php_unit_internal_class' => true, + 'php_unit_test_case_static_method_calls' => ['call_type' => 'self'], + 'phpdoc_align' => false, + 'phpdoc_array_type' => true, + 'phpdoc_no_package' => false, + 'phpdoc_order' => true, + 'phpdoc_separation' => false, + 'phpdoc_to_comment' => false, + 'phpdoc_var_annotation_correct_order' => true, + 'psr_autoloading' => false, + 'semicolon_after_instruction' => false, + 'single_line_empty_body' => true, + 'statement_indentation' => false, + 'static_lambda' => true, + 'string_implicit_backslashes' => ['single_quoted' => 'ignore'], + 'trailing_comma_in_multiline' => [ + 'after_heredoc' => true, + 'elements' => ['arguments', 'arrays', 'match', 'parameters'], + ], + 'use_arrow_functions' => false, + 'void_return' => false, + + MultilinePromotedPropertiesFixer::name() => ['keep_blank_lines' => true], + PhpdocSingleLineVarFixer::name() => true, + + 'Redaxo/no_semicolon_before_closing_tag' => true, + 'Redaxo/statement_indentation' => true, + ]; + + $this->setRules([]); + } + + public static function redaxo5(): self + { + return new self(); + } + + public static function redaxo6(): self + { + $config = new self('REDAXO 6'); + + $config->defaultRules['general_phpdoc_annotation_remove'] = [ + 'annotations' => ['author', 'package'], + ]; + + $config->setRules([]); + + return $config; + } + + public function setRules(array $rules): ConfigInterface + { + return parent::setRules(array_merge($this->defaultRules, $rules)); + } +} diff --git a/vendor/redaxo/php-cs-fixer-config/src/Fixer/NoSemicolonBeforeClosingTagFixer.php b/vendor/redaxo/php-cs-fixer-config/src/Fixer/NoSemicolonBeforeClosingTagFixer.php new file mode 100644 index 00000000..251078bd --- /dev/null +++ b/vendor/redaxo/php-cs-fixer-config/src/Fixer/NoSemicolonBeforeClosingTagFixer.php @@ -0,0 +1,54 @@ +\n")], + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return !$tokens->isMonolithicPhp(); + } + + protected function applyFix(SplFileInfo $file, Tokens $tokens): void + { + for ($index = count($tokens) - 1; $index > 1; --$index) { + if (!$tokens[$index]->isGivenKind(T_CLOSE_TAG)) { + continue; + } + + $prev = $index - 1; + if ($tokens[$prev]->isWhitespace(' ')) { + --$prev; + } + if (!$tokens[$prev]->equals(';')) { + continue; + } + + $tokens->clearAt($prev); + } + } +} diff --git a/vendor/redaxo/php-cs-fixer-config/src/Fixer/StatementIndentationFixer.php b/vendor/redaxo/php-cs-fixer-config/src/Fixer/StatementIndentationFixer.php new file mode 100644 index 00000000..f017bb86 --- /dev/null +++ b/vendor/redaxo/php-cs-fixer-config/src/Fixer/StatementIndentationFixer.php @@ -0,0 +1,48 @@ +isMonolithicPhp(); + } + + protected function createProxyFixers(): array + { + return [new \PhpCsFixer\Fixer\Whitespace\StatementIndentationFixer()]; + } +} diff --git a/vendor/robrichards/xmlseclibs/CHANGELOG.txt b/vendor/robrichards/xmlseclibs/CHANGELOG.txt new file mode 100644 index 00000000..351b1042 --- /dev/null +++ b/vendor/robrichards/xmlseclibs/CHANGELOG.txt @@ -0,0 +1,228 @@ +xmlseclibs.php +||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| +05, Sep 2020, 3.1.1 +Features: +- Support OAEP (iggyvolz) + +Bug Fixes: +- Fix AES128 (iggyvolz) + +Improvements: +- Fix tests for older PHP + +22, Apr 2020, 3.1.0 +Features: +- Support AES-GCM. Requires PHP 7.1. (François Kooman) + +Improvements: +- Fix Travis tests for older PHP versions. +- Use DOMElement interface to fix some IDEs reporting documentation errors + +Bug Fixes: +- FIX missing InclusiveNamespaces PrefixList from Java + Apache WSS4J. (njake) + +06, Nov 2019, 3.0.4 +Security Improvements: +- Insure only a single SignedInfo element exists within a signature during + verification. Refs CVE-2019-3465. +Bug Fixes: +- Fix variable casing. + +15, Nov 2018, 3.0.3 +Bug Fixes: +- Fix casing of class name. (Willem Stuursma-Ruwen) +- Fix Xpath casing. (Tim van Dijen) + +Improvements: +- Make PCRE2 compliant. (Stefan Winter) +- Add PHP 7.3 support. (Stefan Winter) + +27, Sep 2018, 3.0.2 +Security Improvements: +- OpenSSL is now a requirement rather than suggestion. (Slaven Bacelic) +- Filter input to avoid XPath injection. (Jaime Pérez) + +Bug Fixes: +- Fix missing parentheses (Tim van Dijen) + +Improvements: +- Use strict comparison operator to compare digest values. (Jaime Pérez) +- Remove call to file_get_contents that doesn't even work. (Jaime Pérez) +- Document potentially dangerous return value behaviour. (Thijs Kinkhorst) + +31, Aug 2017, 3.0.1 +Bug Fixes: +- Fixed missing () in function call. (Dennis Væversted) + +Improvements: +- Add OneLogin to supported software. +- Add .gitattributes to remove unneeded files. (Filippo Tessarotto) +- Fix bug in example code. (Dan Church) +- Travis: add PHP 7.1, move hhvm to allowed failures. (Thijs Kinkhorst) +- Drop failing extract-win-cert test (Thijs Kinkhorst). (Thijs Kinkhorst) +- Add comments to warn about return values of verify(). (Thijs Kinkhorst) +- Fix tests to properly check return code of verify(). (Thijs Kinkhorst) +- Restore support for PHP >= 5.4. (Jaime Pérez) + +25, May 2017, 3.0.0 +Improvements: +- Remove use of mcrypt (skymeyer) + +08, Sep 2016, 2.0.1 +Bug Fixes: +- Strip whitespace characters when parsing X509Certificate. fixes #84 + (klemen.bratec) +- Certificate 'subject' values can be arrays. fixes #80 (Andreas Stangl) +- HHVM signing node with ID attribute w/out namespace regenerates ID value. + fixes #88 (Milos Tomic) + +Improvements: +- Fix typos and add some PHPDoc Blocks. (gfaust-qb) +- Update lightSAML link. (Milos Tomic) +- Update copyright dates. + +31, Jul 2015, 2.0.0 +Features: +- Namespace support. Classes now in the RobRichards\XMLSecLibs\ namespace. + +Improvements: +- Dropped support for PHP 5.2 + +31, Jul 2015, 1.4.1 +Bug Fixes: +- Allow for large digest values that may have line breaks. fixes #62 + +Features: +- Support for locating specific signature when multiple exist in + document. (griga3k) + +Improvements: +- Add optional argument to XMLSecurityDSig to define the prefix to be used, + also allowing for null to use no prefix, for the dsig namespace. fixes #13 +- Code cleanup +- Depreciated XMLSecurityDSig::generate_GUID for XMLSecurityDSig::generateGUID + +23, Jun 2015, 1.4.0 +Features: +- Support for PSR-0 standard. +- Support for X509SubjectName. (Milos Tomic) +- Add HMAC-SHA1 support. + +Improvements: +- Add how to install to README. (Bernardo Vieira da Silva) +- Code cleanup. (Jaime Pérez) +- Normalilze tests. (Hidde Wieringa) +- Add basic usage to README. (Hidde Wieringa) + +21, May 2015, 1.3.2 +Bug Fixes: +- Fix Undefined variable notice. (dpieper85) +- Fix typo when setting MimeType attribute. (Eugene OZ) +- Fix validateReference() with enveloping signatures + +Features: +- canonicalizeData performance optimization. (Jaime Pérez) +- Add composer support (Maks3w) + +19, Jun 2013, 1.3.1 +Features: +- return encrypted node from XMLSecEnc::encryptNode() when replace is set to + false. (Olav) +- Add support for RSA SHA384 and RSA_SHA512 and SHA384 digest. (Jaime PŽrez) +- Add options parameter to the add cert methods. +- Add optional issuerSerial creation with cert + +Bug Fixes: +- Fix persisted Id when namespaced. (Koen Thomeer) + +Improvements: +- Add LICENSE file +- Convert CHANGELOG.txt to UTF-8 + +26, Sep 2011, 1.3.0 +Features: +- Add param to append sig to node when signing. Fixes a problem when using + inclusive canonicalization to append a signature within a namespaced subtree. + ex. $objDSig->sign($objKey, $appendToNode); +- Add ability to encrypt by reference +- Add support for refences within an encrypted key +- Add thumbprint generation capability (XMLSecurityKey->getX509Thumbprint() and + XMLSecurityKey::getRawThumbprint($cert)) +- Return signature element node from XMLSecurityDSig::insertSignature() and + XMLSecurityDSig::appendSignature() methods +- Support for with simple URI Id reference. +- Add XMLSecurityKey::getSymmetricKeySize() method (Olav) +- Add XMLSecEnc::getCipherValue() method (Olav) +- Improve XMLSecurityKey:generateSessionKey() logic (Olav) + +Bug Fixes: +- Change split() to explode() as split is now depreciated +- ds:References using empty or simple URI Id reference should never include + comments in canonicalized data. +- Make sure that the elements in EncryptedData are emitted in the correct + sequence. + +11 Jan 2010, 1.2.2 +Features: +- Add support XPath support when creating signature. Provides support for + working with EBXML documents. +- Add reference option to force creation of URI attribute. For use + when adding a DOM Document where by default no URI attribute is added. +- Add support for RSA-SHA256 + +Bug Fixes: +- fix bug #5: createDOMDocumentFragment() in decryptNode when data is node + content (patch by Francois Wang) + + +08 Jul 2008, 1.2.1 +Features: +- Attempt to use mhash when hash extension is not present. (Alfredo Cubitos). +- Add fallback to built-in sha1 if both hash and mhash are not available and + throw error for other for other missing hashes. (patch by Olav Morken). +- Add getX509Certificate method to retrieve the x509 cert used for Key. + (patch by Olav Morken). +- Add getValidatedNodes method to retrieve the elements signed by the + signature. (patch by Olav Morken). +- Add insertSignature method for precision signature insertion. Merge + functionality from appendSignature in the process. (Olav Morken, Rob). +- Finally add some tests + +Bug Fixes: +- Fix canonicalization for Document node when using PHP < 5.2. +- Add padding for RSA_SHA1. (patch by Olav Morken). + + +27 Nov 2007, 1.2.0 +Features: +- New addReference/List option (overwrite). Boolean flag indicating if URI + value should be overwritten if already existing within document. + Default is TRUE to maintain BC. + +18 Nov 2007, 1.1.2 +Bug Fixes: +- Remove closing PHP tag to fix extra whitespace characters from being output + +11 Nov 2007, 1.1.1 +Features: +- Add getRefNodeID() and getRefIDs() methods missed in previous release. + Provide functionality to find URIs of existing reference nodes. + Required by simpleSAMLphp project + +Bug Fixes: +- Remove erroneous whitespace causing issues under certain circumastances. + +18 Oct 2007, 1.1.0 +Features: +- Enable creation of enveloping signature. This allows the creation of + managed information cards. +- Add addObject method for enveloping signatures. +- Add staticGet509XCerts method. Chained certificates within a PEM file can + now be added within the X509Data node. +- Add xpath support within transformations +- Add InclusiveNamespaces prefix list support within exclusive transformations. + +Bug Fixes: +- Initialize random number generator for mcrypt_create_iv. (Joan Cornadó). +- Fix an interoperability issue with .NET when encrypting data in CBC mode. + (Joan Cornadó). diff --git a/vendor/robrichards/xmlseclibs/LICENSE b/vendor/robrichards/xmlseclibs/LICENSE new file mode 100644 index 00000000..4fe5e5ff --- /dev/null +++ b/vendor/robrichards/xmlseclibs/LICENSE @@ -0,0 +1,31 @@ +Copyright (c) 2007-2019, Robert Richards . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Robert Richards nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/vendor/robrichards/xmlseclibs/README.md b/vendor/robrichards/xmlseclibs/README.md new file mode 100644 index 00000000..a576080a --- /dev/null +++ b/vendor/robrichards/xmlseclibs/README.md @@ -0,0 +1,85 @@ +#xmlseclibs + +xmlseclibs is a library written in PHP for working with XML Encryption and Signatures. + +The author of xmlseclibs is Rob Richards. + +# Branches +Master is currently the only actively maintained branch. +* master/3.1: Added AES-GCM support requiring 7.1+ +* 3.0: Removes mcrypt usage requiring 5.4+ (5.6.24+ recommended for security reasons) +* 2.0: Contains namespace support requiring 5.3+ +* 1.4: Contains auto-loader support while also maintaining backwards compatiblity with the older 1.3 version using the xmlseclibs.php file. Supports PHP 5.2+ + +# Requirements + +xmlseclibs requires PHP version 5.4 or greater. **5.6.24+ recommended for security reasons** + + +## How to Install + +Install with [`composer.phar`](http://getcomposer.org). + +```sh +php composer.phar require "robrichards/xmlseclibs" +``` + + +## Use cases + +xmlseclibs is being used in many different software. + +* [SimpleSAMLPHP](https://github.com/simplesamlphp/simplesamlphp) +* [LightSAML](https://github.com/lightsaml/lightsaml) +* [OneLogin](https://github.com/onelogin/php-saml) + +## Basic usage + +The example below shows basic usage of xmlseclibs, with a SHA-256 signature. + +```php +use RobRichards\XMLSecLibs\XMLSecurityDSig; +use RobRichards\XMLSecLibs\XMLSecurityKey; + +// Load the XML to be signed +$doc = new DOMDocument(); +$doc->load('./path/to/file/tobesigned.xml'); + +// Create a new Security object +$objDSig = new XMLSecurityDSig(); +// Use the c14n exclusive canonicalization +$objDSig->setCanonicalMethod(XMLSecurityDSig::EXC_C14N); +// Sign using SHA-256 +$objDSig->addReference( + $doc, + XMLSecurityDSig::SHA256, + array('http://www.w3.org/2000/09/xmldsig#enveloped-signature') +); + +// Create a new (private) Security key +$objKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA256, array('type'=>'private')); +/* +If key has a passphrase, set it using +$objKey->passphrase = ''; +*/ +// Load the private key +$objKey->loadKey('./path/to/privatekey.pem', TRUE); + +// Sign the XML file +$objDSig->sign($objKey); + +// Add the associated public key to the signature +$objDSig->add509Cert(file_get_contents('./path/to/file/mycert.pem')); + +// Append the signature to the XML +$objDSig->appendSignature($doc->documentElement); +// Save the signed XML +$doc->save('./path/to/signed.xml'); +``` + +## How to Contribute + +* [Open Issues](https://github.com/robrichards/xmlseclibs/issues) +* [Open Pull Requests](https://github.com/robrichards/xmlseclibs/pulls) + +Mailing List: https://groups.google.com/forum/#!forum/xmlseclibs diff --git a/vendor/robrichards/xmlseclibs/composer.json b/vendor/robrichards/xmlseclibs/composer.json new file mode 100644 index 00000000..22ce7a3e --- /dev/null +++ b/vendor/robrichards/xmlseclibs/composer.json @@ -0,0 +1,21 @@ +{ + "name": "robrichards/xmlseclibs", + "description": "A PHP library for XML Security", + "license": "BSD-3-Clause", + "keywords": [ + "xml", + "xmldsig", + "signature", + "security" + ], + "homepage": "https://github.com/robrichards/xmlseclibs", + "autoload": { + "psr-4": { + "RobRichards\\XMLSecLibs\\": "src" + } + }, + "require": { + "php": ">= 5.4", + "ext-openssl": "*" + } +} diff --git a/vendor/robrichards/xmlseclibs/src/Utils/XPath.php b/vendor/robrichards/xmlseclibs/src/Utils/XPath.php new file mode 100644 index 00000000..8cdc48e1 --- /dev/null +++ b/vendor/robrichards/xmlseclibs/src/Utils/XPath.php @@ -0,0 +1,44 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Robert Richards nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @author Robert Richards + * @copyright 2007-2020 Robert Richards + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + */ + +class XMLSecEnc +{ + const template = " + + + +"; + + const Element = 'http://www.w3.org/2001/04/xmlenc#Element'; + const Content = 'http://www.w3.org/2001/04/xmlenc#Content'; + const URI = 3; + const XMLENCNS = 'http://www.w3.org/2001/04/xmlenc#'; + + /** @var null|DOMDocument */ + private $encdoc = null; + + /** @var null|DOMNode */ + private $rawNode = null; + + /** @var null|string */ + public $type = null; + + /** @var null|DOMElement */ + public $encKey = null; + + /** @var array */ + private $references = array(); + + public function __construct() + { + $this->_resetTemplate(); + } + + private function _resetTemplate() + { + $this->encdoc = new DOMDocument(); + $this->encdoc->loadXML(self::template); + } + + /** + * @param string $name + * @param DOMNode $node + * @param string $type + * @throws Exception + */ + public function addReference($name, $node, $type) + { + if (! $node instanceOf DOMNode) { + throw new Exception('$node is not of type DOMNode'); + } + $curencdoc = $this->encdoc; + $this->_resetTemplate(); + $encdoc = $this->encdoc; + $this->encdoc = $curencdoc; + $refuri = XMLSecurityDSig::generateGUID(); + $element = $encdoc->documentElement; + $element->setAttribute("Id", $refuri); + $this->references[$name] = array("node" => $node, "type" => $type, "encnode" => $encdoc, "refuri" => $refuri); + } + + /** + * @param DOMNode $node + */ + public function setNode($node) + { + $this->rawNode = $node; + } + + /** + * Encrypt the selected node with the given key. + * + * @param XMLSecurityKey $objKey The encryption key and algorithm. + * @param bool $replace Whether the encrypted node should be replaced in the original tree. Default is true. + * @throws Exception + * + * @return DOMElement The -element. + */ + public function encryptNode($objKey, $replace = true) + { + $data = ''; + if (empty($this->rawNode)) { + throw new Exception('Node to encrypt has not been set'); + } + if (! $objKey instanceof XMLSecurityKey) { + throw new Exception('Invalid Key'); + } + $doc = $this->rawNode->ownerDocument; + $xPath = new DOMXPath($this->encdoc); + $objList = $xPath->query('/xenc:EncryptedData/xenc:CipherData/xenc:CipherValue'); + $cipherValue = $objList->item(0); + if ($cipherValue == null) { + throw new Exception('Error locating CipherValue element within template'); + } + switch ($this->type) { + case (self::Element): + $data = $doc->saveXML($this->rawNode); + $this->encdoc->documentElement->setAttribute('Type', self::Element); + break; + case (self::Content): + $children = $this->rawNode->childNodes; + foreach ($children AS $child) { + $data .= $doc->saveXML($child); + } + $this->encdoc->documentElement->setAttribute('Type', self::Content); + break; + default: + throw new Exception('Type is currently not supported'); + } + + $encMethod = $this->encdoc->documentElement->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:EncryptionMethod')); + $encMethod->setAttribute('Algorithm', $objKey->getAlgorithm()); + $cipherValue->parentNode->parentNode->insertBefore($encMethod, $cipherValue->parentNode->parentNode->firstChild); + + $strEncrypt = base64_encode($objKey->encryptData($data)); + $value = $this->encdoc->createTextNode($strEncrypt); + $cipherValue->appendChild($value); + + if ($replace) { + switch ($this->type) { + case (self::Element): + if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) { + return $this->encdoc; + } + $importEnc = $this->rawNode->ownerDocument->importNode($this->encdoc->documentElement, true); + $this->rawNode->parentNode->replaceChild($importEnc, $this->rawNode); + return $importEnc; + case (self::Content): + $importEnc = $this->rawNode->ownerDocument->importNode($this->encdoc->documentElement, true); + while ($this->rawNode->firstChild) { + $this->rawNode->removeChild($this->rawNode->firstChild); + } + $this->rawNode->appendChild($importEnc); + return $importEnc; + } + } else { + return $this->encdoc->documentElement; + } + } + + /** + * @param XMLSecurityKey $objKey + * @throws Exception + */ + public function encryptReferences($objKey) + { + $curRawNode = $this->rawNode; + $curType = $this->type; + foreach ($this->references AS $name => $reference) { + $this->encdoc = $reference["encnode"]; + $this->rawNode = $reference["node"]; + $this->type = $reference["type"]; + try { + $encNode = $this->encryptNode($objKey); + $this->references[$name]["encnode"] = $encNode; + } catch (Exception $e) { + $this->rawNode = $curRawNode; + $this->type = $curType; + throw $e; + } + } + $this->rawNode = $curRawNode; + $this->type = $curType; + } + + /** + * Retrieve the CipherValue text from this encrypted node. + * + * @throws Exception + * @return string|null The Ciphervalue text, or null if no CipherValue is found. + */ + public function getCipherValue() + { + if (empty($this->rawNode)) { + throw new Exception('Node to decrypt has not been set'); + } + + $doc = $this->rawNode->ownerDocument; + $xPath = new DOMXPath($doc); + $xPath->registerNamespace('xmlencr', self::XMLENCNS); + /* Only handles embedded content right now and not a reference */ + $query = "./xmlencr:CipherData/xmlencr:CipherValue"; + $nodeset = $xPath->query($query, $this->rawNode); + $node = $nodeset->item(0); + + if (!$node) { + return null; + } + + return base64_decode($node->nodeValue); + } + + /** + * Decrypt this encrypted node. + * + * The behaviour of this function depends on the value of $replace. + * If $replace is false, we will return the decrypted data as a string. + * If $replace is true, we will insert the decrypted element(s) into the + * document, and return the decrypted element(s). + * + * @param XMLSecurityKey $objKey The decryption key that should be used when decrypting the node. + * @param boolean $replace Whether we should replace the encrypted node in the XML document with the decrypted data. The default is true. + * + * @return string|DOMElement The decrypted data. + */ + public function decryptNode($objKey, $replace=true) + { + if (! $objKey instanceof XMLSecurityKey) { + throw new Exception('Invalid Key'); + } + + $encryptedData = $this->getCipherValue(); + if ($encryptedData) { + $decrypted = $objKey->decryptData($encryptedData); + if ($replace) { + switch ($this->type) { + case (self::Element): + $newdoc = new DOMDocument(); + $newdoc->loadXML($decrypted); + if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) { + return $newdoc; + } + $importEnc = $this->rawNode->ownerDocument->importNode($newdoc->documentElement, true); + $this->rawNode->parentNode->replaceChild($importEnc, $this->rawNode); + return $importEnc; + case (self::Content): + if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) { + $doc = $this->rawNode; + } else { + $doc = $this->rawNode->ownerDocument; + } + $newFrag = $doc->createDocumentFragment(); + $newFrag->appendXML($decrypted); + $parent = $this->rawNode->parentNode; + $parent->replaceChild($newFrag, $this->rawNode); + return $parent; + default: + return $decrypted; + } + } else { + return $decrypted; + } + } else { + throw new Exception("Cannot locate encrypted data"); + } + } + + /** + * Encrypt the XMLSecurityKey + * + * @param XMLSecurityKey $srcKey + * @param XMLSecurityKey $rawKey + * @param bool $append + * @throws Exception + */ + public function encryptKey($srcKey, $rawKey, $append=true) + { + if ((! $srcKey instanceof XMLSecurityKey) || (! $rawKey instanceof XMLSecurityKey)) { + throw new Exception('Invalid Key'); + } + $strEncKey = base64_encode($srcKey->encryptData($rawKey->key)); + $root = $this->encdoc->documentElement; + $encKey = $this->encdoc->createElementNS(self::XMLENCNS, 'xenc:EncryptedKey'); + if ($append) { + $keyInfo = $root->insertBefore($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyInfo'), $root->firstChild); + $keyInfo->appendChild($encKey); + } else { + $this->encKey = $encKey; + } + $encMethod = $encKey->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:EncryptionMethod')); + $encMethod->setAttribute('Algorithm', $srcKey->getAlgorith()); + if (! empty($srcKey->name)) { + $keyInfo = $encKey->appendChild($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyInfo')); + $keyInfo->appendChild($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyName', $srcKey->name)); + } + $cipherData = $encKey->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:CipherData')); + $cipherData->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:CipherValue', $strEncKey)); + if (is_array($this->references) && count($this->references) > 0) { + $refList = $encKey->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:ReferenceList')); + foreach ($this->references AS $name => $reference) { + $refuri = $reference["refuri"]; + $dataRef = $refList->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:DataReference')); + $dataRef->setAttribute("URI", '#' . $refuri); + } + } + return; + } + + /** + * @param XMLSecurityKey $encKey + * @return DOMElement|string + * @throws Exception + */ + public function decryptKey($encKey) + { + if (! $encKey->isEncrypted) { + throw new Exception("Key is not Encrypted"); + } + if (empty($encKey->key)) { + throw new Exception("Key is missing data to perform the decryption"); + } + return $this->decryptNode($encKey, false); + } + + /** + * @param DOMDocument $element + * @return DOMNode|null + */ + public function locateEncryptedData($element) + { + if ($element instanceof DOMDocument) { + $doc = $element; + } else { + $doc = $element->ownerDocument; + } + if ($doc) { + $xpath = new DOMXPath($doc); + $query = "//*[local-name()='EncryptedData' and namespace-uri()='".self::XMLENCNS."']"; + $nodeset = $xpath->query($query); + return $nodeset->item(0); + } + return null; + } + + /** + * Returns the key from the DOM + * @param null|DOMNode $node + * @return null|XMLSecurityKey + */ + public function locateKey($node=null) + { + if (empty($node)) { + $node = $this->rawNode; + } + if (! $node instanceof DOMNode) { + return null; + } + if ($doc = $node->ownerDocument) { + $xpath = new DOMXPath($doc); + $xpath->registerNamespace('xmlsecenc', self::XMLENCNS); + $query = ".//xmlsecenc:EncryptionMethod"; + $nodeset = $xpath->query($query, $node); + if ($encmeth = $nodeset->item(0)) { + $attrAlgorithm = $encmeth->getAttribute("Algorithm"); + try { + $objKey = new XMLSecurityKey($attrAlgorithm, array('type' => 'private')); + } catch (Exception $e) { + return null; + } + return $objKey; + } + } + return null; + } + + /** + * @param null|XMLSecurityKey $objBaseKey + * @param null|DOMNode $node + * @return null|XMLSecurityKey + * @throws Exception + */ + public static function staticLocateKeyInfo($objBaseKey=null, $node=null) + { + if (empty($node) || (! $node instanceof DOMNode)) { + return null; + } + $doc = $node->ownerDocument; + if (!$doc) { + return null; + } + + $xpath = new DOMXPath($doc); + $xpath->registerNamespace('xmlsecenc', self::XMLENCNS); + $xpath->registerNamespace('xmlsecdsig', XMLSecurityDSig::XMLDSIGNS); + $query = "./xmlsecdsig:KeyInfo"; + $nodeset = $xpath->query($query, $node); + $encmeth = $nodeset->item(0); + if (!$encmeth) { + /* No KeyInfo in EncryptedData / EncryptedKey. */ + return $objBaseKey; + } + + foreach ($encmeth->childNodes AS $child) { + switch ($child->localName) { + case 'KeyName': + if (! empty($objBaseKey)) { + $objBaseKey->name = $child->nodeValue; + } + break; + case 'KeyValue': + foreach ($child->childNodes AS $keyval) { + switch ($keyval->localName) { + case 'DSAKeyValue': + throw new Exception("DSAKeyValue currently not supported"); + case 'RSAKeyValue': + $modulus = null; + $exponent = null; + if ($modulusNode = $keyval->getElementsByTagName('Modulus')->item(0)) { + $modulus = base64_decode($modulusNode->nodeValue); + } + if ($exponentNode = $keyval->getElementsByTagName('Exponent')->item(0)) { + $exponent = base64_decode($exponentNode->nodeValue); + } + if (empty($modulus) || empty($exponent)) { + throw new Exception("Missing Modulus or Exponent"); + } + $publicKey = XMLSecurityKey::convertRSA($modulus, $exponent); + $objBaseKey->loadKey($publicKey); + break; + } + } + break; + case 'RetrievalMethod': + $type = $child->getAttribute('Type'); + if ($type !== 'http://www.w3.org/2001/04/xmlenc#EncryptedKey') { + /* Unsupported key type. */ + break; + } + $uri = $child->getAttribute('URI'); + if ($uri[0] !== '#') { + /* URI not a reference - unsupported. */ + break; + } + $id = substr($uri, 1); + + $query = '//xmlsecenc:EncryptedKey[@Id="'.XPath::filterAttrValue($id, XPath::DOUBLE_QUOTE).'"]'; + $keyElement = $xpath->query($query)->item(0); + if (!$keyElement) { + throw new Exception("Unable to locate EncryptedKey with @Id='$id'."); + } + + return XMLSecurityKey::fromEncryptedKeyElement($keyElement); + case 'EncryptedKey': + return XMLSecurityKey::fromEncryptedKeyElement($child); + case 'X509Data': + if ($x509certNodes = $child->getElementsByTagName('X509Certificate')) { + if ($x509certNodes->length > 0) { + $x509cert = $x509certNodes->item(0)->textContent; + $x509cert = str_replace(array("\r", "\n", " "), "", $x509cert); + $x509cert = "-----BEGIN CERTIFICATE-----\n".chunk_split($x509cert, 64, "\n")."-----END CERTIFICATE-----\n"; + $objBaseKey->loadKey($x509cert, false, true); + } + } + break; + } + } + return $objBaseKey; + } + + /** + * @param null|XMLSecurityKey $objBaseKey + * @param null|DOMNode $node + * @return null|XMLSecurityKey + */ + public function locateKeyInfo($objBaseKey=null, $node=null) + { + if (empty($node)) { + $node = $this->rawNode; + } + return self::staticLocateKeyInfo($objBaseKey, $node); + } +} diff --git a/vendor/robrichards/xmlseclibs/src/XMLSecurityDSig.php b/vendor/robrichards/xmlseclibs/src/XMLSecurityDSig.php new file mode 100644 index 00000000..9986123e --- /dev/null +++ b/vendor/robrichards/xmlseclibs/src/XMLSecurityDSig.php @@ -0,0 +1,1162 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Robert Richards nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @author Robert Richards + * @copyright 2007-2020 Robert Richards + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + */ + +class XMLSecurityDSig +{ + const XMLDSIGNS = 'http://www.w3.org/2000/09/xmldsig#'; + const SHA1 = 'http://www.w3.org/2000/09/xmldsig#sha1'; + const SHA256 = 'http://www.w3.org/2001/04/xmlenc#sha256'; + const SHA384 = 'http://www.w3.org/2001/04/xmldsig-more#sha384'; + const SHA512 = 'http://www.w3.org/2001/04/xmlenc#sha512'; + const RIPEMD160 = 'http://www.w3.org/2001/04/xmlenc#ripemd160'; + + const C14N = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315'; + const C14N_COMMENTS = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments'; + const EXC_C14N = 'http://www.w3.org/2001/10/xml-exc-c14n#'; + const EXC_C14N_COMMENTS = 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments'; + + const template = ' + + + +'; + + const BASE_TEMPLATE = ' + + + +'; + + /** @var DOMElement|null */ + public $sigNode = null; + + /** @var array */ + public $idKeys = array(); + + /** @var array */ + public $idNS = array(); + + /** @var string|null */ + private $signedInfo = null; + + /** @var DomXPath|null */ + private $xPathCtx = null; + + /** @var string|null */ + private $canonicalMethod = null; + + /** @var string */ + private $prefix = ''; + + /** @var string */ + private $searchpfx = 'secdsig'; + + /** + * This variable contains an associative array of validated nodes. + * @var array|null + */ + private $validatedNodes = null; + + /** + * @param string $prefix + */ + public function __construct($prefix='ds') + { + $template = self::BASE_TEMPLATE; + if (! empty($prefix)) { + $this->prefix = $prefix.':'; + $search = array("ownerDocument; + } + if ($doc) { + $xpath = new DOMXPath($doc); + $xpath->registerNamespace('secdsig', self::XMLDSIGNS); + $query = ".//secdsig:Signature"; + $nodeset = $xpath->query($query, $objDoc); + $this->sigNode = $nodeset->item($pos); + $query = "./secdsig:SignedInfo"; + $nodeset = $xpath->query($query, $this->sigNode); + if ($nodeset->length > 1) { + throw new Exception("Invalid structure - Too many SignedInfo elements found"); + } + return $this->sigNode; + } + return null; + } + + /** + * @param string $name + * @param null|string $value + * @return DOMElement + */ + public function createNewSignNode($name, $value=null) + { + $doc = $this->sigNode->ownerDocument; + if (! is_null($value)) { + $node = $doc->createElementNS(self::XMLDSIGNS, $this->prefix.$name, $value); + } else { + $node = $doc->createElementNS(self::XMLDSIGNS, $this->prefix.$name); + } + return $node; + } + + /** + * @param string $method + * @throws Exception + */ + public function setCanonicalMethod($method) + { + switch ($method) { + case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315': + case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments': + case 'http://www.w3.org/2001/10/xml-exc-c14n#': + case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments': + $this->canonicalMethod = $method; + break; + default: + throw new Exception('Invalid Canonical Method'); + } + if ($xpath = $this->getXPathObj()) { + $query = './'.$this->searchpfx.':SignedInfo'; + $nodeset = $xpath->query($query, $this->sigNode); + if ($sinfo = $nodeset->item(0)) { + $query = './'.$this->searchpfx.'CanonicalizationMethod'; + $nodeset = $xpath->query($query, $sinfo); + if (! ($canonNode = $nodeset->item(0))) { + $canonNode = $this->createNewSignNode('CanonicalizationMethod'); + $sinfo->insertBefore($canonNode, $sinfo->firstChild); + } + $canonNode->setAttribute('Algorithm', $this->canonicalMethod); + } + } + } + + /** + * @param DOMNode $node + * @param string $canonicalmethod + * @param null|array $arXPath + * @param null|array $prefixList + * @return string + */ + private function canonicalizeData($node, $canonicalmethod, $arXPath=null, $prefixList=null) + { + $exclusive = false; + $withComments = false; + switch ($canonicalmethod) { + case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315': + $exclusive = false; + $withComments = false; + break; + case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments': + $withComments = true; + break; + case 'http://www.w3.org/2001/10/xml-exc-c14n#': + $exclusive = true; + break; + case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments': + $exclusive = true; + $withComments = true; + break; + } + + if (is_null($arXPath) && ($node instanceof DOMNode) && ($node->ownerDocument !== null) && $node->isSameNode($node->ownerDocument->documentElement)) { + /* Check for any PI or comments as they would have been excluded */ + $element = $node; + while ($refnode = $element->previousSibling) { + if ($refnode->nodeType == XML_PI_NODE || (($refnode->nodeType == XML_COMMENT_NODE) && $withComments)) { + break; + } + $element = $refnode; + } + if ($refnode == null) { + $node = $node->ownerDocument; + } + } + + return $node->C14N($exclusive, $withComments, $arXPath, $prefixList); + } + + /** + * @return null|string + */ + public function canonicalizeSignedInfo() + { + + $doc = $this->sigNode->ownerDocument; + $canonicalmethod = null; + if ($doc) { + $xpath = $this->getXPathObj(); + $query = "./secdsig:SignedInfo"; + $nodeset = $xpath->query($query, $this->sigNode); + if ($nodeset->length > 1) { + throw new Exception("Invalid structure - Too many SignedInfo elements found"); + } + if ($signInfoNode = $nodeset->item(0)) { + $query = "./secdsig:CanonicalizationMethod"; + $nodeset = $xpath->query($query, $signInfoNode); + $prefixList = null; + if ($canonNode = $nodeset->item(0)) { + $canonicalmethod = $canonNode->getAttribute('Algorithm'); + foreach ($canonNode->childNodes as $node) + { + if ($node->localName == 'InclusiveNamespaces') { + if ($pfx = $node->getAttribute('PrefixList')) { + $arpfx = array_filter(explode(' ', $pfx)); + if (count($arpfx) > 0) { + $prefixList = array_merge($prefixList ? $prefixList : array(), $arpfx); + } + } + } + } + } + $this->signedInfo = $this->canonicalizeData($signInfoNode, $canonicalmethod, null, $prefixList); + return $this->signedInfo; + } + } + return null; + } + + /** + * @param string $digestAlgorithm + * @param string $data + * @param bool $encode + * @return string + * @throws Exception + */ + public function calculateDigest($digestAlgorithm, $data, $encode = true) + { + switch ($digestAlgorithm) { + case self::SHA1: + $alg = 'sha1'; + break; + case self::SHA256: + $alg = 'sha256'; + break; + case self::SHA384: + $alg = 'sha384'; + break; + case self::SHA512: + $alg = 'sha512'; + break; + case self::RIPEMD160: + $alg = 'ripemd160'; + break; + default: + throw new Exception("Cannot validate digest: Unsupported Algorithm <$digestAlgorithm>"); + } + + $digest = hash($alg, $data, true); + if ($encode) { + $digest = base64_encode($digest); + } + return $digest; + + } + + /** + * @param $refNode + * @param string $data + * @return bool + */ + public function validateDigest($refNode, $data) + { + $xpath = new DOMXPath($refNode->ownerDocument); + $xpath->registerNamespace('secdsig', self::XMLDSIGNS); + $query = 'string(./secdsig:DigestMethod/@Algorithm)'; + $digestAlgorithm = $xpath->evaluate($query, $refNode); + $digValue = $this->calculateDigest($digestAlgorithm, $data, false); + $query = 'string(./secdsig:DigestValue)'; + $digestValue = $xpath->evaluate($query, $refNode); + return ($digValue === base64_decode($digestValue)); + } + + /** + * @param $refNode + * @param DOMNode $objData + * @param bool $includeCommentNodes + * @return string + */ + public function processTransforms($refNode, $objData, $includeCommentNodes = true) + { + $data = $objData; + $xpath = new DOMXPath($refNode->ownerDocument); + $xpath->registerNamespace('secdsig', self::XMLDSIGNS); + $query = './secdsig:Transforms/secdsig:Transform'; + $nodelist = $xpath->query($query, $refNode); + $canonicalMethod = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315'; + $arXPath = null; + $prefixList = null; + foreach ($nodelist AS $transform) { + $algorithm = $transform->getAttribute("Algorithm"); + switch ($algorithm) { + case 'http://www.w3.org/2001/10/xml-exc-c14n#': + case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments': + + if (!$includeCommentNodes) { + /* We remove comment nodes by forcing it to use a canonicalization + * without comments. + */ + $canonicalMethod = 'http://www.w3.org/2001/10/xml-exc-c14n#'; + } else { + $canonicalMethod = $algorithm; + } + + $node = $transform->firstChild; + while ($node) { + if ($node->localName == 'InclusiveNamespaces') { + if ($pfx = $node->getAttribute('PrefixList')) { + $arpfx = array(); + $pfxlist = explode(" ", $pfx); + foreach ($pfxlist AS $pfx) { + $val = trim($pfx); + if (! empty($val)) { + $arpfx[] = $val; + } + } + if (count($arpfx) > 0) { + $prefixList = $arpfx; + } + } + break; + } + $node = $node->nextSibling; + } + break; + case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315': + case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments': + if (!$includeCommentNodes) { + /* We remove comment nodes by forcing it to use a canonicalization + * without comments. + */ + $canonicalMethod = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315'; + } else { + $canonicalMethod = $algorithm; + } + + break; + case 'http://www.w3.org/TR/1999/REC-xpath-19991116': + $node = $transform->firstChild; + while ($node) { + if ($node->localName == 'XPath') { + $arXPath = array(); + $arXPath['query'] = '(.//. | .//@* | .//namespace::*)['.$node->nodeValue.']'; + $arXPath['namespaces'] = array(); + $nslist = $xpath->query('./namespace::*', $node); + foreach ($nslist AS $nsnode) { + if ($nsnode->localName != "xml") { + $arXPath['namespaces'][$nsnode->localName] = $nsnode->nodeValue; + } + } + break; + } + $node = $node->nextSibling; + } + break; + } + } + if ($data instanceof DOMNode) { + $data = $this->canonicalizeData($objData, $canonicalMethod, $arXPath, $prefixList); + } + return $data; + } + + /** + * @param DOMNode $refNode + * @return bool + */ + public function processRefNode($refNode) + { + $dataObject = null; + + /* + * Depending on the URI, we may not want to include comments in the result + * See: http://www.w3.org/TR/xmldsig-core/#sec-ReferenceProcessingModel + */ + $includeCommentNodes = true; + + if ($uri = $refNode->getAttribute("URI")) { + $arUrl = parse_url($uri); + if (empty($arUrl['path'])) { + if ($identifier = $arUrl['fragment']) { + + /* This reference identifies a node with the given id by using + * a URI on the form "#identifier". This should not include comments. + */ + $includeCommentNodes = false; + + $xPath = new DOMXPath($refNode->ownerDocument); + if ($this->idNS && is_array($this->idNS)) { + foreach ($this->idNS as $nspf => $ns) { + $xPath->registerNamespace($nspf, $ns); + } + } + $iDlist = '@Id="'.XPath::filterAttrValue($identifier, XPath::DOUBLE_QUOTE).'"'; + if (is_array($this->idKeys)) { + foreach ($this->idKeys as $idKey) { + $iDlist .= " or @".XPath::filterAttrName($idKey).'="'. + XPath::filterAttrValue($identifier, XPath::DOUBLE_QUOTE).'"'; + } + } + $query = '//*['.$iDlist.']'; + $dataObject = $xPath->query($query)->item(0); + } else { + $dataObject = $refNode->ownerDocument; + } + } + } else { + /* This reference identifies the root node with an empty URI. This should + * not include comments. + */ + $includeCommentNodes = false; + + $dataObject = $refNode->ownerDocument; + } + $data = $this->processTransforms($refNode, $dataObject, $includeCommentNodes); + if (!$this->validateDigest($refNode, $data)) { + return false; + } + + if ($dataObject instanceof DOMNode) { + /* Add this node to the list of validated nodes. */ + if (! empty($identifier)) { + $this->validatedNodes[$identifier] = $dataObject; + } else { + $this->validatedNodes[] = $dataObject; + } + } + + return true; + } + + /** + * @param DOMNode $refNode + * @return null + */ + public function getRefNodeID($refNode) + { + if ($uri = $refNode->getAttribute("URI")) { + $arUrl = parse_url($uri); + if (empty($arUrl['path'])) { + if ($identifier = $arUrl['fragment']) { + return $identifier; + } + } + } + return null; + } + + /** + * @return array + * @throws Exception + */ + public function getRefIDs() + { + $refids = array(); + + $xpath = $this->getXPathObj(); + $query = "./secdsig:SignedInfo[1]/secdsig:Reference"; + $nodeset = $xpath->query($query, $this->sigNode); + if ($nodeset->length == 0) { + throw new Exception("Reference nodes not found"); + } + foreach ($nodeset AS $refNode) { + $refids[] = $this->getRefNodeID($refNode); + } + return $refids; + } + + /** + * @return bool + * @throws Exception + */ + public function validateReference() + { + $docElem = $this->sigNode->ownerDocument->documentElement; + if (! $docElem->isSameNode($this->sigNode)) { + if ($this->sigNode->parentNode != null) { + $this->sigNode->parentNode->removeChild($this->sigNode); + } + } + $xpath = $this->getXPathObj(); + $query = "./secdsig:SignedInfo[1]/secdsig:Reference"; + $nodeset = $xpath->query($query, $this->sigNode); + if ($nodeset->length == 0) { + throw new Exception("Reference nodes not found"); + } + + /* Initialize/reset the list of validated nodes. */ + $this->validatedNodes = array(); + + foreach ($nodeset AS $refNode) { + if (! $this->processRefNode($refNode)) { + /* Clear the list of validated nodes. */ + $this->validatedNodes = null; + throw new Exception("Reference validation failed"); + } + } + return true; + } + + /** + * @param DOMNode $sinfoNode + * @param DOMDocument $node + * @param string $algorithm + * @param null|array $arTransforms + * @param null|array $options + */ + private function addRefInternal($sinfoNode, $node, $algorithm, $arTransforms=null, $options=null) + { + $prefix = null; + $prefix_ns = null; + $id_name = 'Id'; + $overwrite_id = true; + $force_uri = false; + + if (is_array($options)) { + $prefix = empty($options['prefix']) ? null : $options['prefix']; + $prefix_ns = empty($options['prefix_ns']) ? null : $options['prefix_ns']; + $id_name = empty($options['id_name']) ? 'Id' : $options['id_name']; + $overwrite_id = !isset($options['overwrite']) ? true : (bool) $options['overwrite']; + $force_uri = !isset($options['force_uri']) ? false : (bool) $options['force_uri']; + } + + $attname = $id_name; + if (! empty($prefix)) { + $attname = $prefix.':'.$attname; + } + + $refNode = $this->createNewSignNode('Reference'); + $sinfoNode->appendChild($refNode); + + if (! $node instanceof DOMDocument) { + $uri = null; + if (! $overwrite_id) { + $uri = $prefix_ns ? $node->getAttributeNS($prefix_ns, $id_name) : $node->getAttribute($id_name); + } + if (empty($uri)) { + $uri = self::generateGUID(); + $node->setAttributeNS($prefix_ns, $attname, $uri); + } + $refNode->setAttribute("URI", '#'.$uri); + } elseif ($force_uri) { + $refNode->setAttribute("URI", ''); + } + + $transNodes = $this->createNewSignNode('Transforms'); + $refNode->appendChild($transNodes); + + if (is_array($arTransforms)) { + foreach ($arTransforms AS $transform) { + $transNode = $this->createNewSignNode('Transform'); + $transNodes->appendChild($transNode); + if (is_array($transform) && + (! empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116'])) && + (! empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['query']))) { + $transNode->setAttribute('Algorithm', 'http://www.w3.org/TR/1999/REC-xpath-19991116'); + $XPathNode = $this->createNewSignNode('XPath', $transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['query']); + $transNode->appendChild($XPathNode); + if (! empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['namespaces'])) { + foreach ($transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['namespaces'] AS $prefix => $namespace) { + $XPathNode->setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:$prefix", $namespace); + } + } + } else { + $transNode->setAttribute('Algorithm', $transform); + } + } + } elseif (! empty($this->canonicalMethod)) { + $transNode = $this->createNewSignNode('Transform'); + $transNodes->appendChild($transNode); + $transNode->setAttribute('Algorithm', $this->canonicalMethod); + } + + $canonicalData = $this->processTransforms($refNode, $node); + $digValue = $this->calculateDigest($algorithm, $canonicalData); + + $digestMethod = $this->createNewSignNode('DigestMethod'); + $refNode->appendChild($digestMethod); + $digestMethod->setAttribute('Algorithm', $algorithm); + + $digestValue = $this->createNewSignNode('DigestValue', $digValue); + $refNode->appendChild($digestValue); + } + + /** + * @param DOMDocument $node + * @param string $algorithm + * @param null|array $arTransforms + * @param null|array $options + */ + public function addReference($node, $algorithm, $arTransforms=null, $options=null) + { + if ($xpath = $this->getXPathObj()) { + $query = "./secdsig:SignedInfo"; + $nodeset = $xpath->query($query, $this->sigNode); + if ($sInfo = $nodeset->item(0)) { + $this->addRefInternal($sInfo, $node, $algorithm, $arTransforms, $options); + } + } + } + + /** + * @param array $arNodes + * @param string $algorithm + * @param null|array $arTransforms + * @param null|array $options + */ + public function addReferenceList($arNodes, $algorithm, $arTransforms=null, $options=null) + { + if ($xpath = $this->getXPathObj()) { + $query = "./secdsig:SignedInfo"; + $nodeset = $xpath->query($query, $this->sigNode); + if ($sInfo = $nodeset->item(0)) { + foreach ($arNodes AS $node) { + $this->addRefInternal($sInfo, $node, $algorithm, $arTransforms, $options); + } + } + } + } + + /** + * @param DOMElement|string $data + * @param null|string $mimetype + * @param null|string $encoding + * @return DOMElement + */ + public function addObject($data, $mimetype=null, $encoding=null) + { + $objNode = $this->createNewSignNode('Object'); + $this->sigNode->appendChild($objNode); + if (! empty($mimetype)) { + $objNode->setAttribute('MimeType', $mimetype); + } + if (! empty($encoding)) { + $objNode->setAttribute('Encoding', $encoding); + } + + if ($data instanceof DOMElement) { + $newData = $this->sigNode->ownerDocument->importNode($data, true); + } else { + $newData = $this->sigNode->ownerDocument->createTextNode($data); + } + $objNode->appendChild($newData); + + return $objNode; + } + + /** + * @param null|DOMNode $node + * @return null|XMLSecurityKey + */ + public function locateKey($node=null) + { + if (empty($node)) { + $node = $this->sigNode; + } + if (! $node instanceof DOMNode) { + return null; + } + if ($doc = $node->ownerDocument) { + $xpath = new DOMXPath($doc); + $xpath->registerNamespace('secdsig', self::XMLDSIGNS); + $query = "string(./secdsig:SignedInfo/secdsig:SignatureMethod/@Algorithm)"; + $algorithm = $xpath->evaluate($query, $node); + if ($algorithm) { + try { + $objKey = new XMLSecurityKey($algorithm, array('type' => 'public')); + } catch (Exception $e) { + return null; + } + return $objKey; + } + } + return null; + } + + /** + * Returns: + * Bool when verifying HMAC_SHA1; + * Int otherwise, with following meanings: + * 1 on succesful signature verification, + * 0 when signature verification failed, + * -1 if an error occurred during processing. + * + * NOTE: be very careful when checking the int return value, because in + * PHP, -1 will be cast to True when in boolean context. Always check the + * return value in a strictly typed way, e.g. "$obj->verify(...) === 1". + * + * @param XMLSecurityKey $objKey + * @return bool|int + * @throws Exception + */ + public function verify($objKey) + { + $doc = $this->sigNode->ownerDocument; + $xpath = new DOMXPath($doc); + $xpath->registerNamespace('secdsig', self::XMLDSIGNS); + $query = "string(./secdsig:SignatureValue)"; + $sigValue = $xpath->evaluate($query, $this->sigNode); + if (empty($sigValue)) { + throw new Exception("Unable to locate SignatureValue"); + } + return $objKey->verifySignature($this->signedInfo, base64_decode($sigValue)); + } + + /** + * @param XMLSecurityKey $objKey + * @param string $data + * @return mixed|string + */ + public function signData($objKey, $data) + { + return $objKey->signData($data); + } + + /** + * @param XMLSecurityKey $objKey + * @param null|DOMNode $appendToNode + */ + public function sign($objKey, $appendToNode = null) + { + // If we have a parent node append it now so C14N properly works + if ($appendToNode != null) { + $this->resetXPathObj(); + $this->appendSignature($appendToNode); + $this->sigNode = $appendToNode->lastChild; + } + if ($xpath = $this->getXPathObj()) { + $query = "./secdsig:SignedInfo"; + $nodeset = $xpath->query($query, $this->sigNode); + if ($sInfo = $nodeset->item(0)) { + $query = "./secdsig:SignatureMethod"; + $nodeset = $xpath->query($query, $sInfo); + $sMethod = $nodeset->item(0); + $sMethod->setAttribute('Algorithm', $objKey->type); + $data = $this->canonicalizeData($sInfo, $this->canonicalMethod); + $sigValue = base64_encode($this->signData($objKey, $data)); + $sigValueNode = $this->createNewSignNode('SignatureValue', $sigValue); + if ($infoSibling = $sInfo->nextSibling) { + $infoSibling->parentNode->insertBefore($sigValueNode, $infoSibling); + } else { + $this->sigNode->appendChild($sigValueNode); + } + } + } + } + + public function appendCert() + { + + } + + /** + * @param XMLSecurityKey $objKey + * @param null|DOMNode $parent + */ + public function appendKey($objKey, $parent=null) + { + $objKey->serializeKey($parent); + } + + + /** + * This function inserts the signature element. + * + * The signature element will be appended to the element, unless $beforeNode is specified. If $beforeNode + * is specified, the signature element will be inserted as the last element before $beforeNode. + * + * @param DOMNode $node The node the signature element should be inserted into. + * @param DOMNode $beforeNode The node the signature element should be located before. + * + * @return DOMNode The signature element node + */ + public function insertSignature($node, $beforeNode = null) + { + + $document = $node->ownerDocument; + $signatureElement = $document->importNode($this->sigNode, true); + + if ($beforeNode == null) { + return $node->insertBefore($signatureElement); + } else { + return $node->insertBefore($signatureElement, $beforeNode); + } + } + + /** + * @param DOMNode $parentNode + * @param bool $insertBefore + * @return DOMNode + */ + public function appendSignature($parentNode, $insertBefore = false) + { + $beforeNode = $insertBefore ? $parentNode->firstChild : null; + return $this->insertSignature($parentNode, $beforeNode); + } + + /** + * @param string $cert + * @param bool $isPEMFormat + * @return string + */ + public static function get509XCert($cert, $isPEMFormat=true) + { + $certs = self::staticGet509XCerts($cert, $isPEMFormat); + if (! empty($certs)) { + return $certs[0]; + } + return ''; + } + + /** + * @param string $certs + * @param bool $isPEMFormat + * @return array + */ + public static function staticGet509XCerts($certs, $isPEMFormat=true) + { + if ($isPEMFormat) { + $data = ''; + $certlist = array(); + $arCert = explode("\n", $certs); + $inData = false; + foreach ($arCert AS $curData) { + if (! $inData) { + if (strncmp($curData, '-----BEGIN CERTIFICATE', 22) == 0) { + $inData = true; + } + } else { + if (strncmp($curData, '-----END CERTIFICATE', 20) == 0) { + $inData = false; + $certlist[] = $data; + $data = ''; + continue; + } + $data .= trim($curData); + } + } + return $certlist; + } else { + return array($certs); + } + } + + /** + * @param DOMElement $parentRef + * @param string $cert + * @param bool $isPEMFormat + * @param bool $isURL + * @param null|DOMXPath $xpath + * @param null|array $options + * @throws Exception + */ + public static function staticAdd509Cert($parentRef, $cert, $isPEMFormat=true, $isURL=false, $xpath=null, $options=null) + { + if ($isURL) { + $cert = file_get_contents($cert); + } + if (! $parentRef instanceof DOMElement) { + throw new Exception('Invalid parent Node parameter'); + } + $baseDoc = $parentRef->ownerDocument; + + if (empty($xpath)) { + $xpath = new DOMXPath($parentRef->ownerDocument); + $xpath->registerNamespace('secdsig', self::XMLDSIGNS); + } + + $query = "./secdsig:KeyInfo"; + $nodeset = $xpath->query($query, $parentRef); + $keyInfo = $nodeset->item(0); + $dsig_pfx = ''; + if (! $keyInfo) { + $pfx = $parentRef->lookupPrefix(self::XMLDSIGNS); + if (! empty($pfx)) { + $dsig_pfx = $pfx.":"; + } + $inserted = false; + $keyInfo = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'KeyInfo'); + + $query = "./secdsig:Object"; + $nodeset = $xpath->query($query, $parentRef); + if ($sObject = $nodeset->item(0)) { + $sObject->parentNode->insertBefore($keyInfo, $sObject); + $inserted = true; + } + + if (! $inserted) { + $parentRef->appendChild($keyInfo); + } + } else { + $pfx = $keyInfo->lookupPrefix(self::XMLDSIGNS); + if (! empty($pfx)) { + $dsig_pfx = $pfx.":"; + } + } + + // Add all certs if there are more than one + $certs = self::staticGet509XCerts($cert, $isPEMFormat); + + // Attach X509 data node + $x509DataNode = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'X509Data'); + $keyInfo->appendChild($x509DataNode); + + $issuerSerial = false; + $subjectName = false; + if (is_array($options)) { + if (! empty($options['issuerSerial'])) { + $issuerSerial = true; + } + if (! empty($options['subjectName'])) { + $subjectName = true; + } + } + + // Attach all certificate nodes and any additional data + foreach ($certs as $X509Cert) { + if ($issuerSerial || $subjectName) { + if ($certData = openssl_x509_parse("-----BEGIN CERTIFICATE-----\n".chunk_split($X509Cert, 64, "\n")."-----END CERTIFICATE-----\n")) { + if ($subjectName && ! empty($certData['subject'])) { + if (is_array($certData['subject'])) { + $parts = array(); + foreach ($certData['subject'] AS $key => $value) { + if (is_array($value)) { + foreach ($value as $valueElement) { + array_unshift($parts, "$key=$valueElement"); + } + } else { + array_unshift($parts, "$key=$value"); + } + } + $subjectNameValue = implode(',', $parts); + } else { + $subjectNameValue = $certData['issuer']; + } + $x509SubjectNode = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'X509SubjectName', $subjectNameValue); + $x509DataNode->appendChild($x509SubjectNode); + } + if ($issuerSerial && ! empty($certData['issuer']) && ! empty($certData['serialNumber'])) { + if (is_array($certData['issuer'])) { + $parts = array(); + foreach ($certData['issuer'] AS $key => $value) { + array_unshift($parts, "$key=$value"); + } + $issuerName = implode(',', $parts); + } else { + $issuerName = $certData['issuer']; + } + + $x509IssuerNode = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'X509IssuerSerial'); + $x509DataNode->appendChild($x509IssuerNode); + + $x509Node = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'X509IssuerName', $issuerName); + $x509IssuerNode->appendChild($x509Node); + $x509Node = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'X509SerialNumber', $certData['serialNumber']); + $x509IssuerNode->appendChild($x509Node); + } + } + + } + $x509CertNode = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'X509Certificate', $X509Cert); + $x509DataNode->appendChild($x509CertNode); + } + } + + /** + * @param string $cert + * @param bool $isPEMFormat + * @param bool $isURL + * @param null|array $options + */ + public function add509Cert($cert, $isPEMFormat=true, $isURL=false, $options=null) + { + if ($xpath = $this->getXPathObj()) { + self::staticAdd509Cert($this->sigNode, $cert, $isPEMFormat, $isURL, $xpath, $options); + } + } + + /** + * This function appends a node to the KeyInfo. + * + * The KeyInfo element will be created if one does not exist in the document. + * + * @param DOMNode $node The node to append to the KeyInfo. + * + * @return DOMNode The KeyInfo element node + */ + public function appendToKeyInfo($node) + { + $parentRef = $this->sigNode; + $baseDoc = $parentRef->ownerDocument; + + $xpath = $this->getXPathObj(); + if (empty($xpath)) { + $xpath = new DOMXPath($parentRef->ownerDocument); + $xpath->registerNamespace('secdsig', self::XMLDSIGNS); + } + + $query = "./secdsig:KeyInfo"; + $nodeset = $xpath->query($query, $parentRef); + $keyInfo = $nodeset->item(0); + if (! $keyInfo) { + $dsig_pfx = ''; + $pfx = $parentRef->lookupPrefix(self::XMLDSIGNS); + if (! empty($pfx)) { + $dsig_pfx = $pfx.":"; + } + $inserted = false; + $keyInfo = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'KeyInfo'); + + $query = "./secdsig:Object"; + $nodeset = $xpath->query($query, $parentRef); + if ($sObject = $nodeset->item(0)) { + $sObject->parentNode->insertBefore($keyInfo, $sObject); + $inserted = true; + } + + if (! $inserted) { + $parentRef->appendChild($keyInfo); + } + } + + $keyInfo->appendChild($node); + + return $keyInfo; + } + + /** + * This function retrieves an associative array of the validated nodes. + * + * The array will contain the id of the referenced node as the key and the node itself + * as the value. + * + * Returns: + * An associative array of validated nodes or null if no nodes have been validated. + * + * @return array Associative array of validated nodes + */ + public function getValidatedNodes() + { + return $this->validatedNodes; + } +} diff --git a/vendor/robrichards/xmlseclibs/src/XMLSecurityKey.php b/vendor/robrichards/xmlseclibs/src/XMLSecurityKey.php new file mode 100644 index 00000000..7eed04d2 --- /dev/null +++ b/vendor/robrichards/xmlseclibs/src/XMLSecurityKey.php @@ -0,0 +1,813 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Robert Richards nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @author Robert Richards + * @copyright 2007-2020 Robert Richards + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + */ + +class XMLSecurityKey +{ + const TRIPLEDES_CBC = 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc'; + const AES128_CBC = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc'; + const AES192_CBC = 'http://www.w3.org/2001/04/xmlenc#aes192-cbc'; + const AES256_CBC = 'http://www.w3.org/2001/04/xmlenc#aes256-cbc'; + const AES128_GCM = 'http://www.w3.org/2009/xmlenc11#aes128-gcm'; + const AES192_GCM = 'http://www.w3.org/2009/xmlenc11#aes192-gcm'; + const AES256_GCM = 'http://www.w3.org/2009/xmlenc11#aes256-gcm'; + const RSA_1_5 = 'http://www.w3.org/2001/04/xmlenc#rsa-1_5'; + const RSA_OAEP_MGF1P = 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p'; + const RSA_OAEP = 'http://www.w3.org/2009/xmlenc11#rsa-oaep'; + const DSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#dsa-sha1'; + const RSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'; + const RSA_SHA256 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'; + const RSA_SHA384 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384'; + const RSA_SHA512 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512'; + const HMAC_SHA1 = 'http://www.w3.org/2000/09/xmldsig#hmac-sha1'; + const AUTHTAG_LENGTH = 16; + + /** @var array */ + private $cryptParams = array(); + + /** @var int|string */ + public $type = 0; + + /** @var mixed|null */ + public $key = null; + + /** @var string */ + public $passphrase = ""; + + /** @var string|null */ + public $iv = null; + + /** @var string|null */ + public $name = null; + + /** @var mixed|null */ + public $keyChain = null; + + /** @var bool */ + public $isEncrypted = false; + + /** @var XMLSecEnc|null */ + public $encryptedCtx = null; + + /** @var mixed|null */ + public $guid = null; + + /** + * This variable contains the certificate as a string if this key represents an X509-certificate. + * If this key doesn't represent a certificate, this will be null. + * @var string|null + */ + private $x509Certificate = null; + + /** + * This variable contains the certificate thumbprint if we have loaded an X509-certificate. + * @var string|null + */ + private $X509Thumbprint = null; + + /** + * @param string $type + * @param null|array $params + * @throws Exception + */ + public function __construct($type, $params=null) + { + switch ($type) { + case (self::TRIPLEDES_CBC): + $this->cryptParams['library'] = 'openssl'; + $this->cryptParams['cipher'] = 'des-ede3-cbc'; + $this->cryptParams['type'] = 'symmetric'; + $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc'; + $this->cryptParams['keysize'] = 24; + $this->cryptParams['blocksize'] = 8; + break; + case (self::AES128_CBC): + $this->cryptParams['library'] = 'openssl'; + $this->cryptParams['cipher'] = 'aes-128-cbc'; + $this->cryptParams['type'] = 'symmetric'; + $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc'; + $this->cryptParams['keysize'] = 16; + $this->cryptParams['blocksize'] = 16; + break; + case (self::AES192_CBC): + $this->cryptParams['library'] = 'openssl'; + $this->cryptParams['cipher'] = 'aes-192-cbc'; + $this->cryptParams['type'] = 'symmetric'; + $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes192-cbc'; + $this->cryptParams['keysize'] = 24; + $this->cryptParams['blocksize'] = 16; + break; + case (self::AES256_CBC): + $this->cryptParams['library'] = 'openssl'; + $this->cryptParams['cipher'] = 'aes-256-cbc'; + $this->cryptParams['type'] = 'symmetric'; + $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes256-cbc'; + $this->cryptParams['keysize'] = 32; + $this->cryptParams['blocksize'] = 16; + break; + case (self::AES128_GCM): + $this->cryptParams['library'] = 'openssl'; + $this->cryptParams['cipher'] = 'aes-128-gcm'; + $this->cryptParams['type'] = 'symmetric'; + $this->cryptParams['method'] = 'http://www.w3.org/2009/xmlenc11#aes128-gcm'; + $this->cryptParams['keysize'] = 16; + $this->cryptParams['blocksize'] = 16; + break; + case (self::AES192_GCM): + $this->cryptParams['library'] = 'openssl'; + $this->cryptParams['cipher'] = 'aes-192-gcm'; + $this->cryptParams['type'] = 'symmetric'; + $this->cryptParams['method'] = 'http://www.w3.org/2009/xmlenc11#aes192-gcm'; + $this->cryptParams['keysize'] = 24; + $this->cryptParams['blocksize'] = 16; + break; + case (self::AES256_GCM): + $this->cryptParams['library'] = 'openssl'; + $this->cryptParams['cipher'] = 'aes-256-gcm'; + $this->cryptParams['type'] = 'symmetric'; + $this->cryptParams['method'] = 'http://www.w3.org/2009/xmlenc11#aes256-gcm'; + $this->cryptParams['keysize'] = 32; + $this->cryptParams['blocksize'] = 16; + break; + case (self::RSA_1_5): + $this->cryptParams['library'] = 'openssl'; + $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING; + $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#rsa-1_5'; + if (is_array($params) && ! empty($params['type'])) { + if ($params['type'] == 'public' || $params['type'] == 'private') { + $this->cryptParams['type'] = $params['type']; + break; + } + } + throw new Exception('Certificate "type" (private/public) must be passed via parameters'); + case (self::RSA_OAEP_MGF1P): + $this->cryptParams['library'] = 'openssl'; + $this->cryptParams['padding'] = OPENSSL_PKCS1_OAEP_PADDING; + $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p'; + $this->cryptParams['hash'] = null; + if (is_array($params) && ! empty($params['type'])) { + if ($params['type'] == 'public' || $params['type'] == 'private') { + $this->cryptParams['type'] = $params['type']; + break; + } + } + throw new Exception('Certificate "type" (private/public) must be passed via parameters'); + case (self::RSA_OAEP): + $this->cryptParams['library'] = 'openssl'; + $this->cryptParams['padding'] = OPENSSL_PKCS1_OAEP_PADDING; + $this->cryptParams['method'] = 'http://www.w3.org/2009/xmlenc11#rsa-oaep'; + $this->cryptParams['hash'] = 'http://www.w3.org/2009/xmlenc11#mgf1sha1'; + if (is_array($params) && ! empty($params['type'])) { + if ($params['type'] == 'public' || $params['type'] == 'private') { + $this->cryptParams['type'] = $params['type']; + break; + } + } + throw new Exception('Certificate "type" (private/public) must be passed via parameters'); + case (self::RSA_SHA1): + $this->cryptParams['library'] = 'openssl'; + $this->cryptParams['method'] = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'; + $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING; + if (is_array($params) && ! empty($params['type'])) { + if ($params['type'] == 'public' || $params['type'] == 'private') { + $this->cryptParams['type'] = $params['type']; + break; + } + } + throw new Exception('Certificate "type" (private/public) must be passed via parameters'); + case (self::RSA_SHA256): + $this->cryptParams['library'] = 'openssl'; + $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'; + $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING; + $this->cryptParams['digest'] = 'SHA256'; + if (is_array($params) && ! empty($params['type'])) { + if ($params['type'] == 'public' || $params['type'] == 'private') { + $this->cryptParams['type'] = $params['type']; + break; + } + } + throw new Exception('Certificate "type" (private/public) must be passed via parameters'); + case (self::RSA_SHA384): + $this->cryptParams['library'] = 'openssl'; + $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384'; + $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING; + $this->cryptParams['digest'] = 'SHA384'; + if (is_array($params) && ! empty($params['type'])) { + if ($params['type'] == 'public' || $params['type'] == 'private') { + $this->cryptParams['type'] = $params['type']; + break; + } + } + throw new Exception('Certificate "type" (private/public) must be passed via parameters'); + case (self::RSA_SHA512): + $this->cryptParams['library'] = 'openssl'; + $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512'; + $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING; + $this->cryptParams['digest'] = 'SHA512'; + if (is_array($params) && ! empty($params['type'])) { + if ($params['type'] == 'public' || $params['type'] == 'private') { + $this->cryptParams['type'] = $params['type']; + break; + } + } + throw new Exception('Certificate "type" (private/public) must be passed via parameters'); + case (self::HMAC_SHA1): + $this->cryptParams['library'] = $type; + $this->cryptParams['method'] = 'http://www.w3.org/2000/09/xmldsig#hmac-sha1'; + break; + default: + throw new Exception('Invalid Key Type'); + } + $this->type = $type; + } + + /** + * Retrieve the key size for the symmetric encryption algorithm.. + * + * If the key size is unknown, or this isn't a symmetric encryption algorithm, + * null is returned. + * + * @return int|null The number of bytes in the key. + */ + public function getSymmetricKeySize() + { + if (! isset($this->cryptParams['keysize'])) { + return null; + } + return $this->cryptParams['keysize']; + } + + /** + * Generates a session key using the openssl-extension. + * In case of using DES3-CBC the key is checked for a proper parity bits set. + * @return string + * @throws Exception + */ + public function generateSessionKey() + { + if (!isset($this->cryptParams['keysize'])) { + throw new Exception('Unknown key size for type "' . $this->type . '".'); + } + $keysize = $this->cryptParams['keysize']; + + $key = openssl_random_pseudo_bytes($keysize); + + if ($this->type === self::TRIPLEDES_CBC) { + /* Make sure that the generated key has the proper parity bits set. + * Mcrypt doesn't care about the parity bits, but others may care. + */ + for ($i = 0; $i < strlen($key); $i++) { + $byte = ord($key[$i]) & 0xfe; + $parity = 1; + for ($j = 1; $j < 8; $j++) { + $parity ^= ($byte >> $j) & 1; + } + $byte |= $parity; + $key[$i] = chr($byte); + } + } + + $this->key = $key; + return $key; + } + + /** + * Get the raw thumbprint of a certificate + * + * @param string $cert + * @return null|string + */ + public static function getRawThumbprint($cert) + { + + $arCert = explode("\n", $cert); + $data = ''; + $inData = false; + + foreach ($arCert AS $curData) { + if (! $inData) { + if (strncmp($curData, '-----BEGIN CERTIFICATE', 22) == 0) { + $inData = true; + } + } else { + if (strncmp($curData, '-----END CERTIFICATE', 20) == 0) { + break; + } + $data .= trim($curData); + } + } + + if (! empty($data)) { + return strtolower(sha1(base64_decode($data))); + } + + return null; + } + + /** + * Loads the given key, or - with isFile set true - the key from the keyfile. + * + * @param string $key + * @param bool $isFile + * @param bool $isCert + * @throws Exception + */ + public function loadKey($key, $isFile=false, $isCert = false) + { + if ($isFile) { + $this->key = file_get_contents($key); + } else { + $this->key = $key; + } + if ($isCert) { + $this->key = openssl_x509_read($this->key); + openssl_x509_export($this->key, $str_cert); + $this->x509Certificate = $str_cert; + $this->key = $str_cert; + } else { + $this->x509Certificate = null; + } + if ($this->cryptParams['library'] == 'openssl') { + switch ($this->cryptParams['type']) { + case 'public': + if ($isCert) { + /* Load the thumbprint if this is an X509 certificate. */ + $this->X509Thumbprint = self::getRawThumbprint($this->key); + } + $this->key = openssl_get_publickey($this->key); + if (! $this->key) { + throw new Exception('Unable to extract public key'); + } + break; + + case 'private': + $this->key = openssl_get_privatekey($this->key, $this->passphrase); + break; + + case'symmetric': + if (strlen($this->key) < $this->cryptParams['keysize']) { + throw new Exception('Key must contain at least '.$this->cryptParams['keysize'].' characters for this cipher, contains '.strlen($this->key)); + } + break; + + default: + throw new Exception('Unknown type'); + } + } + } + + /** + * ISO 10126 Padding + * + * @param string $data + * @param integer $blockSize + * @throws Exception + * @return string + */ + private function padISO10126($data, $blockSize) + { + if ($blockSize > 256) { + throw new Exception('Block size higher than 256 not allowed'); + } + $padChr = $blockSize - (strlen($data) % $blockSize); + $pattern = chr($padChr); + return $data . str_repeat($pattern, $padChr); + } + + /** + * Remove ISO 10126 Padding + * + * @param string $data + * @return string + */ + private function unpadISO10126($data) + { + $padChr = substr($data, -1); + $padLen = ord($padChr); + return substr($data, 0, -$padLen); + } + + /** + * Encrypts the given data (string) using the openssl-extension + * + * @param string $data + * @return string + */ + private function encryptSymmetric($data) + { + $this->iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($this->cryptParams['cipher'])); + $authTag = null; + if(in_array($this->cryptParams['cipher'], ['aes-128-gcm', 'aes-192-gcm', 'aes-256-gcm'])) { + if (version_compare(PHP_VERSION, '7.1.0') < 0) { + throw new Exception('PHP 7.1.0 is required to use AES GCM algorithms'); + } + $authTag = openssl_random_pseudo_bytes(self::AUTHTAG_LENGTH); + $encrypted = openssl_encrypt($data, $this->cryptParams['cipher'], $this->key, OPENSSL_RAW_DATA, $this->iv, $authTag); + } else { + $data = $this->padISO10126($data, $this->cryptParams['blocksize']); + $encrypted = openssl_encrypt($data, $this->cryptParams['cipher'], $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $this->iv); + } + + if (false === $encrypted) { + throw new Exception('Failure encrypting Data (openssl symmetric) - ' . openssl_error_string()); + } + return $this->iv . $encrypted . $authTag; + } + + /** + * Decrypts the given data (string) using the openssl-extension + * + * @param string $data + * @return string + */ + private function decryptSymmetric($data) + { + $iv_length = openssl_cipher_iv_length($this->cryptParams['cipher']); + $this->iv = substr($data, 0, $iv_length); + $data = substr($data, $iv_length); + $authTag = null; + if(in_array($this->cryptParams['cipher'], ['aes-128-gcm', 'aes-192-gcm', 'aes-256-gcm'])) { + if (version_compare(PHP_VERSION, '7.1.0') < 0) { + throw new Exception('PHP 7.1.0 is required to use AES GCM algorithms'); + } + // obtain and remove the authentication tag + $offset = 0 - self::AUTHTAG_LENGTH; + $authTag = substr($data, $offset); + $data = substr($data, 0, $offset); + $decrypted = openssl_decrypt($data, $this->cryptParams['cipher'], $this->key, OPENSSL_RAW_DATA, $this->iv, $authTag); + } else { + $decrypted = openssl_decrypt($data, $this->cryptParams['cipher'], $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $this->iv); + } + + if (false === $decrypted) { + throw new Exception('Failure decrypting Data (openssl symmetric) - ' . openssl_error_string()); + } + return null !== $authTag ? $decrypted : $this->unpadISO10126($decrypted); + } + + /** + * Encrypts the given public data (string) using the openssl-extension + * + * @param string $data + * @return string + * @throws Exception + */ + private function encryptPublic($data) + { + if (! openssl_public_encrypt($data, $encrypted, $this->key, $this->cryptParams['padding'])) { + throw new Exception('Failure encrypting Data (openssl public) - ' . openssl_error_string()); + } + return $encrypted; + } + + /** + * Decrypts the given public data (string) using the openssl-extension + * + * @param string $data + * @return string + * @throws Exception + */ + private function decryptPublic($data) + { + if (! openssl_public_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) { + throw new Exception('Failure decrypting Data (openssl public) - ' . openssl_error_string()); + } + return $decrypted; + } + + /** + * Encrypts the given private data (string) using the openssl-extension + * + * @param string $data + * @return string + * @throws Exception + */ + private function encryptPrivate($data) + { + if (! openssl_private_encrypt($data, $encrypted, $this->key, $this->cryptParams['padding'])) { + throw new Exception('Failure encrypting Data (openssl private) - ' . openssl_error_string()); + } + return $encrypted; + } + + /** + * Decrypts the given private data (string) using the openssl-extension + * + * @param string $data + * @return string + * @throws Exception + */ + private function decryptPrivate($data) + { + if (! openssl_private_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) { + throw new Exception('Failure decrypting Data (openssl private) - ' . openssl_error_string()); + } + return $decrypted; + } + + /** + * Signs the given data (string) using the openssl-extension + * + * @param string $data + * @return string + * @throws Exception + */ + private function signOpenSSL($data) + { + $algo = OPENSSL_ALGO_SHA1; + if (! empty($this->cryptParams['digest'])) { + $algo = $this->cryptParams['digest']; + } + if (! openssl_sign($data, $signature, $this->key, $algo)) { + throw new Exception('Failure Signing Data: ' . openssl_error_string() . ' - ' . $algo); + } + return $signature; + } + + /** + * Verifies the given data (string) belonging to the given signature using the openssl-extension + * + * Returns: + * 1 on succesful signature verification, + * 0 when signature verification failed, + * -1 if an error occurred during processing. + * + * NOTE: be very careful when checking the return value, because in PHP, + * -1 will be cast to True when in boolean context. So always check the + * return value in a strictly typed way, e.g. "$obj->verify(...) === 1". + * + * @param string $data + * @param string $signature + * @return int + */ + private function verifyOpenSSL($data, $signature) + { + $algo = OPENSSL_ALGO_SHA1; + if (! empty($this->cryptParams['digest'])) { + $algo = $this->cryptParams['digest']; + } + return openssl_verify($data, $signature, $this->key, $algo); + } + + /** + * Encrypts the given data (string) using the regarding php-extension, depending on the library assigned to algorithm in the contructor. + * + * @param string $data + * @return mixed|string + */ + public function encryptData($data) + { + if ($this->cryptParams['library'] === 'openssl') { + switch ($this->cryptParams['type']) { + case 'symmetric': + return $this->encryptSymmetric($data); + case 'public': + return $this->encryptPublic($data); + case 'private': + return $this->encryptPrivate($data); + } + } + } + + /** + * Decrypts the given data (string) using the regarding php-extension, depending on the library assigned to algorithm in the contructor. + * + * @param string $data + * @return mixed|string + */ + public function decryptData($data) + { + if ($this->cryptParams['library'] === 'openssl') { + switch ($this->cryptParams['type']) { + case 'symmetric': + return $this->decryptSymmetric($data); + case 'public': + return $this->decryptPublic($data); + case 'private': + return $this->decryptPrivate($data); + } + } + } + + /** + * Signs the data (string) using the extension assigned to the type in the constructor. + * + * @param string $data + * @return mixed|string + */ + public function signData($data) + { + switch ($this->cryptParams['library']) { + case 'openssl': + return $this->signOpenSSL($data); + case (self::HMAC_SHA1): + return hash_hmac("sha1", $data, $this->key, true); + } + } + + /** + * Verifies the data (string) against the given signature using the extension assigned to the type in the constructor. + * + * Returns in case of openSSL: + * 1 on succesful signature verification, + * 0 when signature verification failed, + * -1 if an error occurred during processing. + * + * NOTE: be very careful when checking the return value, because in PHP, + * -1 will be cast to True when in boolean context. So always check the + * return value in a strictly typed way, e.g. "$obj->verify(...) === 1". + * + * @param string $data + * @param string $signature + * @return bool|int + */ + public function verifySignature($data, $signature) + { + switch ($this->cryptParams['library']) { + case 'openssl': + return $this->verifyOpenSSL($data, $signature); + case (self::HMAC_SHA1): + $expectedSignature = hash_hmac("sha1", $data, $this->key, true); + return strcmp($signature, $expectedSignature) == 0; + } + } + + /** + * @deprecated + * @see getAlgorithm() + * @return mixed + */ + public function getAlgorith() + { + return $this->getAlgorithm(); + } + + /** + * @return mixed + */ + public function getAlgorithm() + { + return $this->cryptParams['method']; + } + + /** + * + * @param int $type + * @param string $string + * @return null|string + */ + public static function makeAsnSegment($type, $string) + { + switch ($type) { + case 0x02: + if (ord($string) > 0x7f) + $string = chr(0).$string; + break; + case 0x03: + $string = chr(0).$string; + break; + } + + $length = strlen($string); + + if ($length < 128) { + $output = sprintf("%c%c%s", $type, $length, $string); + } else if ($length < 0x0100) { + $output = sprintf("%c%c%c%s", $type, 0x81, $length, $string); + } else if ($length < 0x010000) { + $output = sprintf("%c%c%c%c%s", $type, 0x82, $length / 0x0100, $length % 0x0100, $string); + } else { + $output = null; + } + return $output; + } + + /** + * + * Hint: Modulus and Exponent must already be base64 decoded + * @param string $modulus + * @param string $exponent + * @return string + */ + public static function convertRSA($modulus, $exponent) + { + /* make an ASN publicKeyInfo */ + $exponentEncoding = self::makeAsnSegment(0x02, $exponent); + $modulusEncoding = self::makeAsnSegment(0x02, $modulus); + $sequenceEncoding = self::makeAsnSegment(0x30, $modulusEncoding.$exponentEncoding); + $bitstringEncoding = self::makeAsnSegment(0x03, $sequenceEncoding); + $rsaAlgorithmIdentifier = pack("H*", "300D06092A864886F70D0101010500"); + $publicKeyInfo = self::makeAsnSegment(0x30, $rsaAlgorithmIdentifier.$bitstringEncoding); + + /* encode the publicKeyInfo in base64 and add PEM brackets */ + $publicKeyInfoBase64 = base64_encode($publicKeyInfo); + $encoding = "-----BEGIN PUBLIC KEY-----\n"; + $offset = 0; + while ($segment = substr($publicKeyInfoBase64, $offset, 64)) { + $encoding = $encoding.$segment."\n"; + $offset += 64; + } + return $encoding."-----END PUBLIC KEY-----\n"; + } + + /** + * @param mixed $parent + */ + public function serializeKey($parent) + { + + } + + /** + * Retrieve the X509 certificate this key represents. + * + * Will return the X509 certificate in PEM-format if this key represents + * an X509 certificate. + * + * @return string The X509 certificate or null if this key doesn't represent an X509-certificate. + */ + public function getX509Certificate() + { + return $this->x509Certificate; + } + + /** + * Get the thumbprint of this X509 certificate. + * + * Returns: + * The thumbprint as a lowercase 40-character hexadecimal number, or null + * if this isn't a X509 certificate. + * + * @return string Lowercase 40-character hexadecimal number of thumbprint + */ + public function getX509Thumbprint() + { + return $this->X509Thumbprint; + } + + + /** + * Create key from an EncryptedKey-element. + * + * @param DOMElement $element The EncryptedKey-element. + * @throws Exception + * + * @return XMLSecurityKey The new key. + */ + public static function fromEncryptedKeyElement(DOMElement $element) + { + + $objenc = new XMLSecEnc(); + $objenc->setNode($element); + if (! $objKey = $objenc->locateKey()) { + throw new Exception("Unable to locate algorithm for this Encrypted Key"); + } + $objKey->isEncrypted = true; + $objKey->encryptedCtx = $objenc; + XMLSecEnc::staticLocateKeyInfo($objKey, $element); + return $objKey; + } + +} diff --git a/vendor/robrichards/xmlseclibs/xmlseclibs.php b/vendor/robrichards/xmlseclibs/xmlseclibs.php new file mode 100644 index 00000000..1c10acc7 --- /dev/null +++ b/vendor/robrichards/xmlseclibs/xmlseclibs.php @@ -0,0 +1,47 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Robert Richards nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @author Robert Richards + * @copyright 2007-2020 Robert Richards + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version 3.1.1 + */ + +$xmlseclibs_srcdir = dirname(__FILE__) . '/src/'; +require $xmlseclibs_srcdir . '/XMLSecurityKey.php'; +require $xmlseclibs_srcdir . '/XMLSecurityDSig.php'; +require $xmlseclibs_srcdir . '/XMLSecEnc.php'; +require $xmlseclibs_srcdir . '/Utils/XPath.php'; diff --git a/vendor/sebastian/cli-parser/ChangeLog.md b/vendor/sebastian/cli-parser/ChangeLog.md new file mode 100644 index 00000000..9ecd241d --- /dev/null +++ b/vendor/sebastian/cli-parser/ChangeLog.md @@ -0,0 +1,23 @@ +# ChangeLog + +All notable changes are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles. + +## [1.0.2] - 2024-03-02 + +### Changed + +* Do not use implicitly nullable parameters + +## [1.0.1] - 2020-09-28 + +### Changed + +* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3` + +## [1.0.0] - 2020-08-12 + +* Initial release + +[1.0.2]: https://github.com/sebastianbergmann/cli-parser/compare/1.0.1...1.0.2 +[1.0.1]: https://github.com/sebastianbergmann/cli-parser/compare/1.0.0...1.0.1 +[1.0.0]: https://github.com/sebastianbergmann/cli-parser/compare/bb7bb3297957927962b0a3335befe7b66f7462e9...1.0.0 diff --git a/vendor/sebastian/cli-parser/LICENSE b/vendor/sebastian/cli-parser/LICENSE new file mode 100644 index 00000000..0e33c059 --- /dev/null +++ b/vendor/sebastian/cli-parser/LICENSE @@ -0,0 +1,33 @@ +sebastian/cli-parser + +Copyright (c) 2020, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/sebastian/cli-parser/README.md b/vendor/sebastian/cli-parser/README.md new file mode 100644 index 00000000..39c17a72 --- /dev/null +++ b/vendor/sebastian/cli-parser/README.md @@ -0,0 +1,17 @@ +# sebastian/cli-parser + +Library for parsing `$_SERVER['argv']`, extracted from `phpunit/phpunit`. + +## Installation + +You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): + +``` +composer require sebastian/cli-parser +``` + +If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency: + +``` +composer require --dev sebastian/cli-parser +``` diff --git a/vendor/sebastian/cli-parser/composer.json b/vendor/sebastian/cli-parser/composer.json new file mode 100644 index 00000000..34c376f9 --- /dev/null +++ b/vendor/sebastian/cli-parser/composer.json @@ -0,0 +1,41 @@ +{ + "name": "sebastian/cli-parser", + "description": "Library for parsing CLI options", + "type": "library", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues" + }, + "prefer-stable": true, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "config": { + "platform": { + "php": "7.3.0" + }, + "optimize-autoloader": true, + "sort-packages": true + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + } +} diff --git a/vendor/sebastian/cli-parser/infection.json b/vendor/sebastian/cli-parser/infection.json new file mode 100644 index 00000000..09546514 --- /dev/null +++ b/vendor/sebastian/cli-parser/infection.json @@ -0,0 +1,12 @@ +{ + "source": { + "directories": [ + "src" + ] + }, + "mutators": { + "@default": true + }, + "minMsi": 100, + "minCoveredMsi": 100 +} diff --git a/vendor/sebastian/cli-parser/src/Parser.php b/vendor/sebastian/cli-parser/src/Parser.php new file mode 100644 index 00000000..67d8909f --- /dev/null +++ b/vendor/sebastian/cli-parser/src/Parser.php @@ -0,0 +1,204 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CliParser; + +use function array_map; +use function array_merge; +use function array_shift; +use function array_slice; +use function assert; +use function count; +use function current; +use function explode; +use function is_array; +use function is_int; +use function is_string; +use function key; +use function next; +use function preg_replace; +use function reset; +use function sort; +use function strlen; +use function strpos; +use function strstr; +use function substr; + +final class Parser +{ + /** + * @psalm-param list $argv + * @psalm-param list $longOptions + * + * @throws AmbiguousOptionException + * @throws RequiredOptionArgumentMissingException + * @throws OptionDoesNotAllowArgumentException + * @throws UnknownOptionException + */ + public function parse(array $argv, string $shortOptions, ?array $longOptions = null): array + { + if (empty($argv)) { + return [[], []]; + } + + $options = []; + $nonOptions = []; + + if ($longOptions) { + sort($longOptions); + } + + if (isset($argv[0][0]) && $argv[0][0] !== '-') { + array_shift($argv); + } + + reset($argv); + + $argv = array_map('trim', $argv); + + while (false !== $arg = current($argv)) { + $i = key($argv); + + assert(is_int($i)); + + next($argv); + + if ($arg === '') { + continue; + } + + if ($arg === '--') { + $nonOptions = array_merge($nonOptions, array_slice($argv, $i + 1)); + + break; + } + + if ($arg[0] !== '-' || (strlen($arg) > 1 && $arg[1] === '-' && !$longOptions)) { + $nonOptions[] = $arg; + + continue; + } + + if (strlen($arg) > 1 && $arg[1] === '-' && is_array($longOptions)) { + $this->parseLongOption( + substr($arg, 2), + $longOptions, + $options, + $argv + ); + } else { + $this->parseShortOption( + substr($arg, 1), + $shortOptions, + $options, + $argv + ); + } + } + + return [$options, $nonOptions]; + } + + /** + * @throws RequiredOptionArgumentMissingException + */ + private function parseShortOption(string $arg, string $shortOptions, array &$opts, array &$args): void + { + $argLength = strlen($arg); + + for ($i = 0; $i < $argLength; $i++) { + $option = $arg[$i]; + $optionArgument = null; + + if ($arg[$i] === ':' || ($spec = strstr($shortOptions, $option)) === false) { + throw new UnknownOptionException('-' . $option); + } + + assert(is_string($spec)); + + if (strlen($spec) > 1 && $spec[1] === ':') { + if ($i + 1 < $argLength) { + $opts[] = [$option, substr($arg, $i + 1)]; + + break; + } + + if (!(strlen($spec) > 2 && $spec[2] === ':')) { + $optionArgument = current($args); + + if (!$optionArgument) { + throw new RequiredOptionArgumentMissingException('-' . $option); + } + + assert(is_string($optionArgument)); + + next($args); + } + } + + $opts[] = [$option, $optionArgument]; + } + } + + /** + * @psalm-param list $longOptions + * + * @throws AmbiguousOptionException + * @throws RequiredOptionArgumentMissingException + * @throws OptionDoesNotAllowArgumentException + * @throws UnknownOptionException + */ + private function parseLongOption(string $arg, array $longOptions, array &$opts, array &$args): void + { + $count = count($longOptions); + $list = explode('=', $arg); + $option = $list[0]; + $optionArgument = null; + + if (count($list) > 1) { + $optionArgument = $list[1]; + } + + $optionLength = strlen($option); + + foreach ($longOptions as $i => $longOption) { + $opt_start = substr($longOption, 0, $optionLength); + + if ($opt_start !== $option) { + continue; + } + + $opt_rest = substr($longOption, $optionLength); + + if ($opt_rest !== '' && $i + 1 < $count && $option[0] !== '=' && strpos($longOptions[$i + 1], $option) === 0) { + throw new AmbiguousOptionException('--' . $option); + } + + if (substr($longOption, -1) === '=') { + /* @noinspection StrlenInEmptyStringCheckContextInspection */ + if (substr($longOption, -2) !== '==' && !strlen((string) $optionArgument)) { + if (false === $optionArgument = current($args)) { + throw new RequiredOptionArgumentMissingException('--' . $option); + } + + next($args); + } + } elseif ($optionArgument) { + throw new OptionDoesNotAllowArgumentException('--' . $option); + } + + $fullOption = '--' . preg_replace('/={1,2}$/', '', $longOption); + $opts[] = [$fullOption, $optionArgument]; + + return; + } + + throw new UnknownOptionException('--' . $option); + } +} diff --git a/vendor/sebastian/cli-parser/src/exceptions/AmbiguousOptionException.php b/vendor/sebastian/cli-parser/src/exceptions/AmbiguousOptionException.php new file mode 100644 index 00000000..a99f6369 --- /dev/null +++ b/vendor/sebastian/cli-parser/src/exceptions/AmbiguousOptionException.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CliParser; + +use function sprintf; +use RuntimeException; + +final class AmbiguousOptionException extends RuntimeException implements Exception +{ + public function __construct(string $option) + { + parent::__construct( + sprintf( + 'Option "%s" is ambiguous', + $option + ) + ); + } +} diff --git a/vendor/sebastian/cli-parser/src/exceptions/Exception.php b/vendor/sebastian/cli-parser/src/exceptions/Exception.php new file mode 100644 index 00000000..f35ad245 --- /dev/null +++ b/vendor/sebastian/cli-parser/src/exceptions/Exception.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CliParser; + +use Throwable; + +interface Exception extends Throwable +{ +} diff --git a/vendor/sebastian/cli-parser/src/exceptions/OptionDoesNotAllowArgumentException.php b/vendor/sebastian/cli-parser/src/exceptions/OptionDoesNotAllowArgumentException.php new file mode 100644 index 00000000..0aad29ac --- /dev/null +++ b/vendor/sebastian/cli-parser/src/exceptions/OptionDoesNotAllowArgumentException.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CliParser; + +use function sprintf; +use RuntimeException; + +final class OptionDoesNotAllowArgumentException extends RuntimeException implements Exception +{ + public function __construct(string $option) + { + parent::__construct( + sprintf( + 'Option "%s" does not allow an argument', + $option + ) + ); + } +} diff --git a/vendor/sebastian/cli-parser/src/exceptions/RequiredOptionArgumentMissingException.php b/vendor/sebastian/cli-parser/src/exceptions/RequiredOptionArgumentMissingException.php new file mode 100644 index 00000000..d2a930b6 --- /dev/null +++ b/vendor/sebastian/cli-parser/src/exceptions/RequiredOptionArgumentMissingException.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CliParser; + +use function sprintf; +use RuntimeException; + +final class RequiredOptionArgumentMissingException extends RuntimeException implements Exception +{ + public function __construct(string $option) + { + parent::__construct( + sprintf( + 'Required argument for option "%s" is missing', + $option + ) + ); + } +} diff --git a/vendor/sebastian/cli-parser/src/exceptions/UnknownOptionException.php b/vendor/sebastian/cli-parser/src/exceptions/UnknownOptionException.php new file mode 100644 index 00000000..e98d9fd0 --- /dev/null +++ b/vendor/sebastian/cli-parser/src/exceptions/UnknownOptionException.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CliParser; + +use function sprintf; +use RuntimeException; + +final class UnknownOptionException extends RuntimeException implements Exception +{ + public function __construct(string $option) + { + parent::__construct( + sprintf( + 'Unknown option "%s"', + $option + ) + ); + } +} diff --git a/vendor/sebastian/code-unit-reverse-lookup/ChangeLog.md b/vendor/sebastian/code-unit-reverse-lookup/ChangeLog.md new file mode 100644 index 00000000..43a5db90 --- /dev/null +++ b/vendor/sebastian/code-unit-reverse-lookup/ChangeLog.md @@ -0,0 +1,38 @@ +# Change Log + +All notable changes to `sebastianbergmann/code-unit-reverse-lookup` are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. + +## [2.0.3] - 2020-09-28 + +### Changed + +* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3` + +## [2.0.2] - 2020-06-26 + +### Added + +* This component is now supported on PHP 8 + +## [2.0.1] - 2020-06-15 + +### Changed + +* Tests etc. are now ignored for archive exports + +## 2.0.0 - 2020-02-07 + +### Removed + +* This component is no longer supported on PHP 5.6, PHP 7.0, PHP 7.1, and PHP 7.2 + +## 1.0.0 - 2016-02-13 + +### Added + +* Initial release + +[2.0.3]: https://github.com/sebastianbergmann/code-unit-reverse-lookup/compare/2.0.2...2.0.3 +[2.0.2]: https://github.com/sebastianbergmann/code-unit-reverse-lookup/compare/2.0.1...2.0.2 +[2.0.1]: https://github.com/sebastianbergmann/code-unit-reverse-lookup/compare/2.0.0...2.0.1 +[2.0.0]: https://github.com/sebastianbergmann/code-unit-reverse-lookup/compare/1.0.0...2.0.0 diff --git a/vendor/sebastian/code-unit-reverse-lookup/LICENSE b/vendor/sebastian/code-unit-reverse-lookup/LICENSE new file mode 100644 index 00000000..dc4bf701 --- /dev/null +++ b/vendor/sebastian/code-unit-reverse-lookup/LICENSE @@ -0,0 +1,33 @@ +code-unit-reverse-lookup + +Copyright (c) 2016-2020, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/sebastian/code-unit-reverse-lookup/README.md b/vendor/sebastian/code-unit-reverse-lookup/README.md new file mode 100644 index 00000000..1c0ca235 --- /dev/null +++ b/vendor/sebastian/code-unit-reverse-lookup/README.md @@ -0,0 +1,20 @@ +# sebastian/code-unit-reverse-lookup + +[![CI Status](https://github.com/sebastianbergmann/code-unit-reverse-lookup/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/code-unit-reverse-lookup/actions) +[![Type Coverage](https://shepherd.dev/github/sebastianbergmann/code-unit-reverse-lookup/coverage.svg)](https://shepherd.dev/github/sebastianbergmann/code-unit-reverse-lookup) + +Looks up which function or method a line of code belongs to. + +## Installation + +You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): + +``` +composer require sebastian/code-unit-reverse-lookup +``` + +If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency: + +``` +composer require --dev sebastian/code-unit-reverse-lookup +``` diff --git a/vendor/sebastian/code-unit-reverse-lookup/composer.json b/vendor/sebastian/code-unit-reverse-lookup/composer.json new file mode 100644 index 00000000..cff96167 --- /dev/null +++ b/vendor/sebastian/code-unit-reverse-lookup/composer.json @@ -0,0 +1,36 @@ +{ + "name": "sebastian/code-unit-reverse-lookup", + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "prefer-stable": true, + "config": { + "platform": { + "php": "7.3.0" + }, + "optimize-autoloader": true, + "sort-packages": true + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + } +} diff --git a/vendor/sebastian/code-unit-reverse-lookup/src/Wizard.php b/vendor/sebastian/code-unit-reverse-lookup/src/Wizard.php new file mode 100644 index 00000000..35de5398 --- /dev/null +++ b/vendor/sebastian/code-unit-reverse-lookup/src/Wizard.php @@ -0,0 +1,125 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeUnitReverseLookup; + +use function array_merge; +use function assert; +use function get_declared_classes; +use function get_declared_traits; +use function get_defined_functions; +use function is_array; +use function range; +use ReflectionClass; +use ReflectionFunction; +use ReflectionFunctionAbstract; +use ReflectionMethod; + +/** + * @since Class available since Release 1.0.0 + */ +class Wizard +{ + /** + * @var array + */ + private $lookupTable = []; + + /** + * @var array + */ + private $processedClasses = []; + + /** + * @var array + */ + private $processedFunctions = []; + + /** + * @param string $filename + * @param int $lineNumber + * + * @return string + */ + public function lookup($filename, $lineNumber) + { + if (!isset($this->lookupTable[$filename][$lineNumber])) { + $this->updateLookupTable(); + } + + if (isset($this->lookupTable[$filename][$lineNumber])) { + return $this->lookupTable[$filename][$lineNumber]; + } + + return $filename . ':' . $lineNumber; + } + + private function updateLookupTable(): void + { + $this->processClassesAndTraits(); + $this->processFunctions(); + } + + private function processClassesAndTraits(): void + { + $classes = get_declared_classes(); + $traits = get_declared_traits(); + + assert(is_array($classes)); + assert(is_array($traits)); + + foreach (array_merge($classes, $traits) as $classOrTrait) { + if (isset($this->processedClasses[$classOrTrait])) { + continue; + } + + $reflector = new ReflectionClass($classOrTrait); + + foreach ($reflector->getMethods() as $method) { + $this->processFunctionOrMethod($method); + } + + $this->processedClasses[$classOrTrait] = true; + } + } + + private function processFunctions(): void + { + foreach (get_defined_functions()['user'] as $function) { + if (isset($this->processedFunctions[$function])) { + continue; + } + + $this->processFunctionOrMethod(new ReflectionFunction($function)); + + $this->processedFunctions[$function] = true; + } + } + + private function processFunctionOrMethod(ReflectionFunctionAbstract $functionOrMethod): void + { + if ($functionOrMethod->isInternal()) { + return; + } + + $name = $functionOrMethod->getName(); + + if ($functionOrMethod instanceof ReflectionMethod) { + $name = $functionOrMethod->getDeclaringClass()->getName() . '::' . $name; + } + + if (!isset($this->lookupTable[$functionOrMethod->getFileName()])) { + $this->lookupTable[$functionOrMethod->getFileName()] = []; + } + + foreach (range($functionOrMethod->getStartLine(), $functionOrMethod->getEndLine()) as $line) { + $this->lookupTable[$functionOrMethod->getFileName()][$line] = $name; + } + } +} diff --git a/vendor/sebastian/code-unit/.psalm/baseline.xml b/vendor/sebastian/code-unit/.psalm/baseline.xml new file mode 100644 index 00000000..e4488919 --- /dev/null +++ b/vendor/sebastian/code-unit/.psalm/baseline.xml @@ -0,0 +1,23 @@ + + + + + $firstPart + $firstPart + $firstPart + $firstPart + $firstPart + $firstPart + $firstPart + $firstPart + $firstPart + $secondPart + $unit + $unit + $unit + $unit + $unit + $unit + + + diff --git a/vendor/sebastian/code-unit/.psalm/config.xml b/vendor/sebastian/code-unit/.psalm/config.xml new file mode 100644 index 00000000..a39e9a4c --- /dev/null +++ b/vendor/sebastian/code-unit/.psalm/config.xml @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/vendor/sebastian/code-unit/ChangeLog.md b/vendor/sebastian/code-unit/ChangeLog.md new file mode 100644 index 00000000..0978e651 --- /dev/null +++ b/vendor/sebastian/code-unit/ChangeLog.md @@ -0,0 +1,65 @@ +# ChangeLog + +All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. + +## [1.0.8] - 2020-10-26 + +### Fixed + +* `SebastianBergmann\CodeUnit\Exception` now correctly extends `\Throwable` + +## [1.0.7] - 2020-10-02 + +### Fixed + +* `SebastianBergmann\CodeUnit\Mapper::stringToCodeUnits()` no longer attempts to create `CodeUnit` objects for code units that are not declared in userland + +## [1.0.6] - 2020-09-28 + +### Changed + +* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3` + +## [1.0.5] - 2020-06-26 + +### Fixed + +* [#3](https://github.com/sebastianbergmann/code-unit/issues/3): Regression in 1.0.4 + +## [1.0.4] - 2020-06-26 + +### Added + +* This component is now supported on PHP 8 + +## [1.0.3] - 2020-06-15 + +### Changed + +* Tests etc. are now ignored for archive exports + +## [1.0.2] - 2020-04-30 + +### Fixed + +* `Mapper::stringToCodeUnits()` raised the wrong exception for `Class::method` when a class named `Class` exists but does not have a method named `method` + +## [1.0.1] - 2020-04-27 + +### Fixed + +* [#2](https://github.com/sebastianbergmann/code-unit/issues/2): `Mapper::stringToCodeUnits()` breaks when `ClassName` is used for class that extends built-in class + +## [1.0.0] - 2020-03-30 + +* Initial release + +[1.0.8]: https://github.com/sebastianbergmann/code-unit/compare/1.0.7...1.0.8 +[1.0.7]: https://github.com/sebastianbergmann/code-unit/compare/1.0.6...1.0.7 +[1.0.6]: https://github.com/sebastianbergmann/code-unit/compare/1.0.5...1.0.6 +[1.0.5]: https://github.com/sebastianbergmann/code-unit/compare/1.0.4...1.0.5 +[1.0.4]: https://github.com/sebastianbergmann/code-unit/compare/1.0.3...1.0.4 +[1.0.3]: https://github.com/sebastianbergmann/code-unit/compare/1.0.2...1.0.3 +[1.0.2]: https://github.com/sebastianbergmann/code-unit/compare/1.0.1...1.0.2 +[1.0.1]: https://github.com/sebastianbergmann/code-unit/compare/1.0.0...1.0.1 +[1.0.0]: https://github.com/sebastianbergmann/code-unit/compare/530c3900e5db9bcb8516da545bef0d62536cedaa...1.0.0 diff --git a/vendor/sebastian/code-unit/LICENSE b/vendor/sebastian/code-unit/LICENSE new file mode 100644 index 00000000..b99bc8ac --- /dev/null +++ b/vendor/sebastian/code-unit/LICENSE @@ -0,0 +1,33 @@ +sebastian/code-unit + +Copyright (c) 2020, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/sebastian/code-unit/README.md b/vendor/sebastian/code-unit/README.md new file mode 100644 index 00000000..d20227a9 --- /dev/null +++ b/vendor/sebastian/code-unit/README.md @@ -0,0 +1,17 @@ +# sebastian/code-unit + +Collection of value objects that represent the PHP code units. + +## Installation + +You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): + +``` +composer require sebastian/code-unit +``` + +If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency: + +``` +composer require --dev sebastian/code-unit +``` diff --git a/vendor/sebastian/code-unit/composer.json b/vendor/sebastian/code-unit/composer.json new file mode 100644 index 00000000..5b86ec58 --- /dev/null +++ b/vendor/sebastian/code-unit/composer.json @@ -0,0 +1,50 @@ +{ + "name": "sebastian/code-unit", + "description": "Collection of value objects that represent the PHP code units", + "type": "library", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues" + }, + "prefer-stable": true, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "config": { + "platform": { + "php": "7.3.0" + }, + "optimize-autoloader": true, + "sort-packages": true + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "autoload-dev": { + "classmap": [ + "tests/_fixture" + ], + "files": [ + "tests/_fixture/file_with_multiple_code_units.php", + "tests/_fixture/function.php" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + } +} diff --git a/vendor/sebastian/code-unit/src/ClassMethodUnit.php b/vendor/sebastian/code-unit/src/ClassMethodUnit.php new file mode 100644 index 00000000..f9ddac29 --- /dev/null +++ b/vendor/sebastian/code-unit/src/ClassMethodUnit.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeUnit; + +/** + * @psalm-immutable + */ +final class ClassMethodUnit extends CodeUnit +{ + /** + * @psalm-assert-if-true ClassMethodUnit $this + */ + public function isClassMethod(): bool + { + return true; + } +} diff --git a/vendor/sebastian/code-unit/src/ClassUnit.php b/vendor/sebastian/code-unit/src/ClassUnit.php new file mode 100644 index 00000000..3ba0ee66 --- /dev/null +++ b/vendor/sebastian/code-unit/src/ClassUnit.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeUnit; + +/** + * @psalm-immutable + */ +final class ClassUnit extends CodeUnit +{ + /** + * @psalm-assert-if-true ClassUnit $this + */ + public function isClass(): bool + { + return true; + } +} diff --git a/vendor/sebastian/code-unit/src/CodeUnit.php b/vendor/sebastian/code-unit/src/CodeUnit.php new file mode 100644 index 00000000..9e5cceb3 --- /dev/null +++ b/vendor/sebastian/code-unit/src/CodeUnit.php @@ -0,0 +1,445 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeUnit; + +use function range; +use function sprintf; +use ReflectionClass; +use ReflectionFunction; +use ReflectionMethod; + +/** + * @psalm-immutable + */ +abstract class CodeUnit +{ + /** + * @var string + */ + private $name; + + /** + * @var string + */ + private $sourceFileName; + + /** + * @var array + * @psalm-var list + */ + private $sourceLines; + + /** + * @psalm-param class-string $className + * + * @throws InvalidCodeUnitException + * @throws ReflectionException + */ + public static function forClass(string $className): ClassUnit + { + self::ensureUserDefinedClass($className); + + $reflector = self::reflectorForClass($className); + + return new ClassUnit( + $className, + $reflector->getFileName(), + range( + $reflector->getStartLine(), + $reflector->getEndLine() + ) + ); + } + + /** + * @psalm-param class-string $className + * + * @throws InvalidCodeUnitException + * @throws ReflectionException + */ + public static function forClassMethod(string $className, string $methodName): ClassMethodUnit + { + self::ensureUserDefinedClass($className); + + $reflector = self::reflectorForClassMethod($className, $methodName); + + return new ClassMethodUnit( + $className . '::' . $methodName, + $reflector->getFileName(), + range( + $reflector->getStartLine(), + $reflector->getEndLine() + ) + ); + } + + /** + * @psalm-param class-string $interfaceName + * + * @throws InvalidCodeUnitException + * @throws ReflectionException + */ + public static function forInterface(string $interfaceName): InterfaceUnit + { + self::ensureUserDefinedInterface($interfaceName); + + $reflector = self::reflectorForClass($interfaceName); + + return new InterfaceUnit( + $interfaceName, + $reflector->getFileName(), + range( + $reflector->getStartLine(), + $reflector->getEndLine() + ) + ); + } + + /** + * @psalm-param class-string $interfaceName + * + * @throws InvalidCodeUnitException + * @throws ReflectionException + */ + public static function forInterfaceMethod(string $interfaceName, string $methodName): InterfaceMethodUnit + { + self::ensureUserDefinedInterface($interfaceName); + + $reflector = self::reflectorForClassMethod($interfaceName, $methodName); + + return new InterfaceMethodUnit( + $interfaceName . '::' . $methodName, + $reflector->getFileName(), + range( + $reflector->getStartLine(), + $reflector->getEndLine() + ) + ); + } + + /** + * @psalm-param class-string $traitName + * + * @throws InvalidCodeUnitException + * @throws ReflectionException + */ + public static function forTrait(string $traitName): TraitUnit + { + self::ensureUserDefinedTrait($traitName); + + $reflector = self::reflectorForClass($traitName); + + return new TraitUnit( + $traitName, + $reflector->getFileName(), + range( + $reflector->getStartLine(), + $reflector->getEndLine() + ) + ); + } + + /** + * @psalm-param class-string $traitName + * + * @throws InvalidCodeUnitException + * @throws ReflectionException + */ + public static function forTraitMethod(string $traitName, string $methodName): TraitMethodUnit + { + self::ensureUserDefinedTrait($traitName); + + $reflector = self::reflectorForClassMethod($traitName, $methodName); + + return new TraitMethodUnit( + $traitName . '::' . $methodName, + $reflector->getFileName(), + range( + $reflector->getStartLine(), + $reflector->getEndLine() + ) + ); + } + + /** + * @psalm-param callable-string $functionName + * + * @throws InvalidCodeUnitException + * @throws ReflectionException + */ + public static function forFunction(string $functionName): FunctionUnit + { + $reflector = self::reflectorForFunction($functionName); + + if (!$reflector->isUserDefined()) { + throw new InvalidCodeUnitException( + sprintf( + '"%s" is not a user-defined function', + $functionName + ) + ); + } + + return new FunctionUnit( + $functionName, + $reflector->getFileName(), + range( + $reflector->getStartLine(), + $reflector->getEndLine() + ) + ); + } + + /** + * @psalm-param list $sourceLines + */ + private function __construct(string $name, string $sourceFileName, array $sourceLines) + { + $this->name = $name; + $this->sourceFileName = $sourceFileName; + $this->sourceLines = $sourceLines; + } + + public function name(): string + { + return $this->name; + } + + public function sourceFileName(): string + { + return $this->sourceFileName; + } + + /** + * @psalm-return list + */ + public function sourceLines(): array + { + return $this->sourceLines; + } + + public function isClass(): bool + { + return false; + } + + public function isClassMethod(): bool + { + return false; + } + + public function isInterface(): bool + { + return false; + } + + public function isInterfaceMethod(): bool + { + return false; + } + + public function isTrait(): bool + { + return false; + } + + public function isTraitMethod(): bool + { + return false; + } + + public function isFunction(): bool + { + return false; + } + + /** + * @psalm-param class-string $className + * + * @throws InvalidCodeUnitException + */ + private static function ensureUserDefinedClass(string $className): void + { + try { + $reflector = new ReflectionClass($className); + + if ($reflector->isInterface()) { + throw new InvalidCodeUnitException( + sprintf( + '"%s" is an interface and not a class', + $className + ) + ); + } + + if ($reflector->isTrait()) { + throw new InvalidCodeUnitException( + sprintf( + '"%s" is a trait and not a class', + $className + ) + ); + } + + if (!$reflector->isUserDefined()) { + throw new InvalidCodeUnitException( + sprintf( + '"%s" is not a user-defined class', + $className + ) + ); + } + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + (int) $e->getCode(), + $e + ); + } + // @codeCoverageIgnoreEnd + } + + /** + * @psalm-param class-string $interfaceName + * + * @throws InvalidCodeUnitException + */ + private static function ensureUserDefinedInterface(string $interfaceName): void + { + try { + $reflector = new ReflectionClass($interfaceName); + + if (!$reflector->isInterface()) { + throw new InvalidCodeUnitException( + sprintf( + '"%s" is not an interface', + $interfaceName + ) + ); + } + + if (!$reflector->isUserDefined()) { + throw new InvalidCodeUnitException( + sprintf( + '"%s" is not a user-defined interface', + $interfaceName + ) + ); + } + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + (int) $e->getCode(), + $e + ); + } + // @codeCoverageIgnoreEnd + } + + /** + * @psalm-param class-string $traitName + * + * @throws InvalidCodeUnitException + */ + private static function ensureUserDefinedTrait(string $traitName): void + { + try { + $reflector = new ReflectionClass($traitName); + + if (!$reflector->isTrait()) { + throw new InvalidCodeUnitException( + sprintf( + '"%s" is not a trait', + $traitName + ) + ); + } + + // @codeCoverageIgnoreStart + if (!$reflector->isUserDefined()) { + throw new InvalidCodeUnitException( + sprintf( + '"%s" is not a user-defined trait', + $traitName + ) + ); + } + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + (int) $e->getCode(), + $e + ); + } + // @codeCoverageIgnoreEnd + } + + /** + * @psalm-param class-string $className + * + * @throws ReflectionException + */ + private static function reflectorForClass(string $className): ReflectionClass + { + try { + return new ReflectionClass($className); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + (int) $e->getCode(), + $e + ); + } + // @codeCoverageIgnoreEnd + } + + /** + * @psalm-param class-string $className + * + * @throws ReflectionException + */ + private static function reflectorForClassMethod(string $className, string $methodName): ReflectionMethod + { + try { + return new ReflectionMethod($className, $methodName); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + (int) $e->getCode(), + $e + ); + } + // @codeCoverageIgnoreEnd + } + + /** + * @psalm-param callable-string $functionName + * + * @throws ReflectionException + */ + private static function reflectorForFunction(string $functionName): ReflectionFunction + { + try { + return new ReflectionFunction($functionName); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + (int) $e->getCode(), + $e + ); + } + // @codeCoverageIgnoreEnd + } +} diff --git a/vendor/sebastian/code-unit/src/CodeUnitCollection.php b/vendor/sebastian/code-unit/src/CodeUnitCollection.php new file mode 100644 index 00000000..f53db8a1 --- /dev/null +++ b/vendor/sebastian/code-unit/src/CodeUnitCollection.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeUnit; + +use function array_merge; +use function count; +use Countable; +use IteratorAggregate; + +final class CodeUnitCollection implements Countable, IteratorAggregate +{ + /** + * @psalm-var list + */ + private $codeUnits = []; + + /** + * @psalm-param list $items + */ + public static function fromArray(array $items): self + { + $collection = new self; + + foreach ($items as $item) { + $collection->add($item); + } + + return $collection; + } + + public static function fromList(CodeUnit ...$items): self + { + return self::fromArray($items); + } + + private function __construct() + { + } + + /** + * @psalm-return list + */ + public function asArray(): array + { + return $this->codeUnits; + } + + public function getIterator(): CodeUnitCollectionIterator + { + return new CodeUnitCollectionIterator($this); + } + + public function count(): int + { + return count($this->codeUnits); + } + + public function isEmpty(): bool + { + return empty($this->codeUnits); + } + + public function mergeWith(self $other): self + { + return self::fromArray( + array_merge( + $this->asArray(), + $other->asArray() + ) + ); + } + + private function add(CodeUnit $item): void + { + $this->codeUnits[] = $item; + } +} diff --git a/vendor/sebastian/code-unit/src/CodeUnitCollectionIterator.php b/vendor/sebastian/code-unit/src/CodeUnitCollectionIterator.php new file mode 100644 index 00000000..bdc86d88 --- /dev/null +++ b/vendor/sebastian/code-unit/src/CodeUnitCollectionIterator.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeUnit; + +use Iterator; + +final class CodeUnitCollectionIterator implements Iterator +{ + /** + * @psalm-var list + */ + private $codeUnits; + + /** + * @var int + */ + private $position = 0; + + public function __construct(CodeUnitCollection $collection) + { + $this->codeUnits = $collection->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return isset($this->codeUnits[$this->position]); + } + + public function key(): int + { + return $this->position; + } + + public function current(): CodeUnit + { + return $this->codeUnits[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/vendor/sebastian/code-unit/src/FunctionUnit.php b/vendor/sebastian/code-unit/src/FunctionUnit.php new file mode 100644 index 00000000..df76cf19 --- /dev/null +++ b/vendor/sebastian/code-unit/src/FunctionUnit.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeUnit; + +/** + * @psalm-immutable + */ +final class FunctionUnit extends CodeUnit +{ + /** + * @psalm-assert-if-true FunctionUnit $this + */ + public function isFunction(): bool + { + return true; + } +} diff --git a/vendor/sebastian/code-unit/src/InterfaceMethodUnit.php b/vendor/sebastian/code-unit/src/InterfaceMethodUnit.php new file mode 100644 index 00000000..fcd44f41 --- /dev/null +++ b/vendor/sebastian/code-unit/src/InterfaceMethodUnit.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeUnit; + +/** + * @psalm-immutable + */ +final class InterfaceMethodUnit extends CodeUnit +{ + /** + * @psalm-assert-if-true InterfaceMethod $this + */ + public function isInterfaceMethod(): bool + { + return true; + } +} diff --git a/vendor/sebastian/code-unit/src/InterfaceUnit.php b/vendor/sebastian/code-unit/src/InterfaceUnit.php new file mode 100644 index 00000000..5cf585bf --- /dev/null +++ b/vendor/sebastian/code-unit/src/InterfaceUnit.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeUnit; + +/** + * @psalm-immutable + */ +final class InterfaceUnit extends CodeUnit +{ + /** + * @psalm-assert-if-true InterfaceUnit $this + */ + public function isInterface(): bool + { + return true; + } +} diff --git a/vendor/sebastian/code-unit/src/Mapper.php b/vendor/sebastian/code-unit/src/Mapper.php new file mode 100644 index 00000000..a72b3b0d --- /dev/null +++ b/vendor/sebastian/code-unit/src/Mapper.php @@ -0,0 +1,414 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeUnit; + +use function array_keys; +use function array_merge; +use function array_unique; +use function array_values; +use function class_exists; +use function explode; +use function function_exists; +use function interface_exists; +use function ksort; +use function method_exists; +use function sort; +use function sprintf; +use function str_replace; +use function strpos; +use function trait_exists; +use ReflectionClass; +use ReflectionFunction; +use ReflectionMethod; + +final class Mapper +{ + /** + * @psalm-return array> + */ + public function codeUnitsToSourceLines(CodeUnitCollection $codeUnits): array + { + $result = []; + + foreach ($codeUnits as $codeUnit) { + $sourceFileName = $codeUnit->sourceFileName(); + + if (!isset($result[$sourceFileName])) { + $result[$sourceFileName] = []; + } + + $result[$sourceFileName] = array_merge($result[$sourceFileName], $codeUnit->sourceLines()); + } + + foreach (array_keys($result) as $sourceFileName) { + $result[$sourceFileName] = array_values(array_unique($result[$sourceFileName])); + + sort($result[$sourceFileName]); + } + + ksort($result); + + return $result; + } + + /** + * @throws InvalidCodeUnitException + * @throws ReflectionException + */ + public function stringToCodeUnits(string $unit): CodeUnitCollection + { + if (strpos($unit, '::') !== false) { + [$firstPart, $secondPart] = explode('::', $unit); + + if (empty($firstPart) && $this->isUserDefinedFunction($secondPart)) { + return CodeUnitCollection::fromList(CodeUnit::forFunction($secondPart)); + } + + if ($this->isUserDefinedClass($firstPart)) { + if ($secondPart === '') { + return $this->publicMethodsOfClass($firstPart); + } + + if ($secondPart === '') { + return $this->protectedAndPrivateMethodsOfClass($firstPart); + } + + if ($secondPart === '') { + return $this->protectedMethodsOfClass($firstPart); + } + + if ($secondPart === '') { + return $this->publicAndPrivateMethodsOfClass($firstPart); + } + + if ($secondPart === '') { + return $this->privateMethodsOfClass($firstPart); + } + + if ($secondPart === '') { + return $this->publicAndProtectedMethodsOfClass($firstPart); + } + + if ($this->isUserDefinedMethod($firstPart, $secondPart)) { + return CodeUnitCollection::fromList(CodeUnit::forClassMethod($firstPart, $secondPart)); + } + } + + if ($this->isUserDefinedInterface($firstPart)) { + return CodeUnitCollection::fromList(CodeUnit::forInterfaceMethod($firstPart, $secondPart)); + } + + if ($this->isUserDefinedTrait($firstPart)) { + return CodeUnitCollection::fromList(CodeUnit::forTraitMethod($firstPart, $secondPart)); + } + } else { + if ($this->isUserDefinedClass($unit)) { + $units = [CodeUnit::forClass($unit)]; + + foreach ($this->reflectorForClass($unit)->getTraits() as $trait) { + if (!$trait->isUserDefined()) { + // @codeCoverageIgnoreStart + continue; + // @codeCoverageIgnoreEnd + } + + $units[] = CodeUnit::forTrait($trait->getName()); + } + + return CodeUnitCollection::fromArray($units); + } + + if ($this->isUserDefinedInterface($unit)) { + return CodeUnitCollection::fromList(CodeUnit::forInterface($unit)); + } + + if ($this->isUserDefinedTrait($unit)) { + return CodeUnitCollection::fromList(CodeUnit::forTrait($unit)); + } + + if ($this->isUserDefinedFunction($unit)) { + return CodeUnitCollection::fromList(CodeUnit::forFunction($unit)); + } + + $unit = str_replace('', '', $unit); + + if ($this->isUserDefinedClass($unit)) { + return $this->classAndParentClassesAndTraits($unit); + } + } + + throw new InvalidCodeUnitException( + sprintf( + '"%s" is not a valid code unit', + $unit + ) + ); + } + + /** + * @psalm-param class-string $className + * + * @throws ReflectionException + */ + private function publicMethodsOfClass(string $className): CodeUnitCollection + { + return $this->methodsOfClass($className, ReflectionMethod::IS_PUBLIC); + } + + /** + * @psalm-param class-string $className + * + * @throws ReflectionException + */ + private function publicAndProtectedMethodsOfClass(string $className): CodeUnitCollection + { + return $this->methodsOfClass($className, ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED); + } + + /** + * @psalm-param class-string $className + * + * @throws ReflectionException + */ + private function publicAndPrivateMethodsOfClass(string $className): CodeUnitCollection + { + return $this->methodsOfClass($className, ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PRIVATE); + } + + /** + * @psalm-param class-string $className + * + * @throws ReflectionException + */ + private function protectedMethodsOfClass(string $className): CodeUnitCollection + { + return $this->methodsOfClass($className, ReflectionMethod::IS_PROTECTED); + } + + /** + * @psalm-param class-string $className + * + * @throws ReflectionException + */ + private function protectedAndPrivateMethodsOfClass(string $className): CodeUnitCollection + { + return $this->methodsOfClass($className, ReflectionMethod::IS_PROTECTED | ReflectionMethod::IS_PRIVATE); + } + + /** + * @psalm-param class-string $className + * + * @throws ReflectionException + */ + private function privateMethodsOfClass(string $className): CodeUnitCollection + { + return $this->methodsOfClass($className, ReflectionMethod::IS_PRIVATE); + } + + /** + * @psalm-param class-string $className + * + * @throws ReflectionException + */ + private function methodsOfClass(string $className, int $filter): CodeUnitCollection + { + $units = []; + + foreach ($this->reflectorForClass($className)->getMethods($filter) as $method) { + if (!$method->isUserDefined()) { + continue; + } + + $units[] = CodeUnit::forClassMethod($className, $method->getName()); + } + + return CodeUnitCollection::fromArray($units); + } + + /** + * @psalm-param class-string $className + * + * @throws ReflectionException + */ + private function classAndParentClassesAndTraits(string $className): CodeUnitCollection + { + $units = [CodeUnit::forClass($className)]; + + $reflector = $this->reflectorForClass($className); + + foreach ($this->reflectorForClass($className)->getTraits() as $trait) { + if (!$trait->isUserDefined()) { + // @codeCoverageIgnoreStart + continue; + // @codeCoverageIgnoreEnd + } + + $units[] = CodeUnit::forTrait($trait->getName()); + } + + while ($reflector = $reflector->getParentClass()) { + if (!$reflector->isUserDefined()) { + break; + } + + $units[] = CodeUnit::forClass($reflector->getName()); + + foreach ($reflector->getTraits() as $trait) { + if (!$trait->isUserDefined()) { + // @codeCoverageIgnoreStart + continue; + // @codeCoverageIgnoreEnd + } + + $units[] = CodeUnit::forTrait($trait->getName()); + } + } + + return CodeUnitCollection::fromArray($units); + } + + /** + * @psalm-param class-string $className + * + * @throws ReflectionException + */ + private function reflectorForClass(string $className): ReflectionClass + { + try { + return new ReflectionClass($className); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + (int) $e->getCode(), + $e + ); + } + // @codeCoverageIgnoreEnd + } + + /** + * @throws ReflectionException + */ + private function isUserDefinedFunction(string $functionName): bool + { + if (!function_exists($functionName)) { + return false; + } + + try { + return (new ReflectionFunction($functionName))->isUserDefined(); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + (int) $e->getCode(), + $e + ); + } + // @codeCoverageIgnoreEnd + } + + /** + * @throws ReflectionException + */ + private function isUserDefinedClass(string $className): bool + { + if (!class_exists($className)) { + return false; + } + + try { + return (new ReflectionClass($className))->isUserDefined(); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + (int) $e->getCode(), + $e + ); + } + // @codeCoverageIgnoreEnd + } + + /** + * @throws ReflectionException + */ + private function isUserDefinedInterface(string $interfaceName): bool + { + if (!interface_exists($interfaceName)) { + return false; + } + + try { + return (new ReflectionClass($interfaceName))->isUserDefined(); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + (int) $e->getCode(), + $e + ); + } + // @codeCoverageIgnoreEnd + } + + /** + * @throws ReflectionException + */ + private function isUserDefinedTrait(string $traitName): bool + { + if (!trait_exists($traitName)) { + return false; + } + + try { + return (new ReflectionClass($traitName))->isUserDefined(); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + (int) $e->getCode(), + $e + ); + } + // @codeCoverageIgnoreEnd + } + + /** + * @throws ReflectionException + */ + private function isUserDefinedMethod(string $className, string $methodName): bool + { + if (!class_exists($className)) { + // @codeCoverageIgnoreStart + return false; + // @codeCoverageIgnoreEnd + } + + if (!method_exists($className, $methodName)) { + // @codeCoverageIgnoreStart + return false; + // @codeCoverageIgnoreEnd + } + + try { + return (new ReflectionMethod($className, $methodName))->isUserDefined(); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + (int) $e->getCode(), + $e + ); + } + // @codeCoverageIgnoreEnd + } +} diff --git a/vendor/sebastian/code-unit/src/TraitMethodUnit.php b/vendor/sebastian/code-unit/src/TraitMethodUnit.php new file mode 100644 index 00000000..a58f7249 --- /dev/null +++ b/vendor/sebastian/code-unit/src/TraitMethodUnit.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeUnit; + +/** + * @psalm-immutable + */ +final class TraitMethodUnit extends CodeUnit +{ + /** + * @psalm-assert-if-true TraitMethodUnit $this + */ + public function isTraitMethod(): bool + { + return true; + } +} diff --git a/vendor/sebastian/code-unit/src/TraitUnit.php b/vendor/sebastian/code-unit/src/TraitUnit.php new file mode 100644 index 00000000..abddfc11 --- /dev/null +++ b/vendor/sebastian/code-unit/src/TraitUnit.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeUnit; + +/** + * @psalm-immutable + */ +final class TraitUnit extends CodeUnit +{ + /** + * @psalm-assert-if-true TraitUnit $this + */ + public function isTrait(): bool + { + return true; + } +} diff --git a/vendor/sebastian/code-unit/src/exceptions/Exception.php b/vendor/sebastian/code-unit/src/exceptions/Exception.php new file mode 100644 index 00000000..74d0eeef --- /dev/null +++ b/vendor/sebastian/code-unit/src/exceptions/Exception.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeUnit; + +use Throwable; + +interface Exception extends Throwable +{ +} diff --git a/vendor/sebastian/code-unit/src/exceptions/InvalidCodeUnitException.php b/vendor/sebastian/code-unit/src/exceptions/InvalidCodeUnitException.php new file mode 100644 index 00000000..60a3da82 --- /dev/null +++ b/vendor/sebastian/code-unit/src/exceptions/InvalidCodeUnitException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeUnit; + +use RuntimeException; + +final class InvalidCodeUnitException extends RuntimeException implements Exception +{ +} diff --git a/vendor/sebastian/code-unit/src/exceptions/NoTraitException.php b/vendor/sebastian/code-unit/src/exceptions/NoTraitException.php new file mode 100644 index 00000000..e9b9b9c7 --- /dev/null +++ b/vendor/sebastian/code-unit/src/exceptions/NoTraitException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeUnit; + +use RuntimeException; + +final class NoTraitException extends RuntimeException implements Exception +{ +} diff --git a/vendor/sebastian/code-unit/src/exceptions/ReflectionException.php b/vendor/sebastian/code-unit/src/exceptions/ReflectionException.php new file mode 100644 index 00000000..23201278 --- /dev/null +++ b/vendor/sebastian/code-unit/src/exceptions/ReflectionException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeUnit; + +use RuntimeException; + +final class ReflectionException extends RuntimeException implements Exception +{ +} diff --git a/vendor/sebastian/comparator/ChangeLog.md b/vendor/sebastian/comparator/ChangeLog.md new file mode 100644 index 00000000..17557536 --- /dev/null +++ b/vendor/sebastian/comparator/ChangeLog.md @@ -0,0 +1,143 @@ +# ChangeLog + +All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. + +## [4.0.8] - 2022-09-14 + +### Fixed + +* [#102](https://github.com/sebastianbergmann/comparator/pull/102): Fix `float` comparison precision + +## [4.0.7] - 2022-09-14 + +### Fixed + +* [#99](https://github.com/sebastianbergmann/comparator/pull/99): Fix weak comparison between `'0'` and `false` + +## [4.0.6] - 2020-10-26 + +### Fixed + +* `SebastianBergmann\Comparator\Exception` now correctly extends `\Throwable` + +## [4.0.5] - 2020-09-30 + +### Fixed + +* [#89](https://github.com/sebastianbergmann/comparator/pull/89): Handle PHP 8 `ValueError` + +## [4.0.4] - 2020-09-28 + +### Changed + +* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3` + +## [4.0.3] - 2020-06-26 + +### Added + +* This component is now supported on PHP 8 + +## [4.0.2] - 2020-06-15 + +### Fixed + +* [#85](https://github.com/sebastianbergmann/comparator/issues/85): Version 4.0.1 breaks backward compatibility + +## [4.0.1] - 2020-06-15 + +### Changed + +* Tests etc. are now ignored for archive exports + +## [4.0.0] - 2020-02-07 + +### Removed + +* Removed support for PHP 7.1 and PHP 7.2 + +## [3.0.5] - 2022-09-14 + +### Fixed + +* [#102](https://github.com/sebastianbergmann/comparator/pull/102): Fix `float` comparison precision + +## [3.0.4] - 2022-09-14 + +### Fixed + +* [#99](https://github.com/sebastianbergmann/comparator/pull/99): Fix weak comparison between `'0'` and `false` + +## [3.0.3] - 2020-11-30 + +### Changed + +* Changed PHP version constraint in `composer.json` from `^7.1` to `>=7.1` + +## [3.0.2] - 2018-07-12 + +### Changed + +* By default, `MockObjectComparator` is now tried before all other (default) comparators + +## [3.0.1] - 2018-06-14 + +### Fixed + +* [#53](https://github.com/sebastianbergmann/comparator/pull/53): `DOMNodeComparator` ignores `$ignoreCase` parameter +* [#58](https://github.com/sebastianbergmann/comparator/pull/58): `ScalarComparator` does not handle extremely ugly string comparison edge cases + +## [3.0.0] - 2018-04-18 + +### Fixed + +* [#48](https://github.com/sebastianbergmann/comparator/issues/48): `DateTimeComparator` does not support fractional second deltas + +### Removed + +* Removed support for PHP 7.0 + +## [2.1.3] - 2018-02-01 + +### Changed + +* This component is now compatible with version 3 of `sebastian/diff` + +## [2.1.2] - 2018-01-12 + +### Fixed + +* Fix comparison of `DateTimeImmutable` objects + +## [2.1.1] - 2017-12-22 + +### Fixed + +* [phpunit/#2923](https://github.com/sebastianbergmann/phpunit/issues/2923): Unexpected failed date matching + +## [2.1.0] - 2017-11-03 + +### Added + +* Added `SebastianBergmann\Comparator\Factory::reset()` to unregister all non-default comparators +* Added support for `phpunit/phpunit-mock-objects` version `^5.0` + +[4.0.8]: https://github.com/sebastianbergmann/comparator/compare/4.0.7...4.0.8 +[4.0.7]: https://github.com/sebastianbergmann/comparator/compare/4.0.6...4.0.7 +[4.0.6]: https://github.com/sebastianbergmann/comparator/compare/4.0.5...4.0.6 +[4.0.5]: https://github.com/sebastianbergmann/comparator/compare/4.0.4...4.0.5 +[4.0.4]: https://github.com/sebastianbergmann/comparator/compare/4.0.3...4.0.4 +[4.0.3]: https://github.com/sebastianbergmann/comparator/compare/4.0.2...4.0.3 +[4.0.2]: https://github.com/sebastianbergmann/comparator/compare/4.0.1...4.0.2 +[4.0.1]: https://github.com/sebastianbergmann/comparator/compare/4.0.0...4.0.1 +[4.0.0]: https://github.com/sebastianbergmann/comparator/compare/3.0.5...4.0.0 +[3.0.5]: https://github.com/sebastianbergmann/comparator/compare/3.0.4...3.0.5 +[3.0.4]: https://github.com/sebastianbergmann/comparator/compare/3.0.3...3.0.4 +[3.0.3]: https://github.com/sebastianbergmann/comparator/compare/3.0.2...3.0.3 +[3.0.2]: https://github.com/sebastianbergmann/comparator/compare/3.0.1...3.0.2 +[3.0.1]: https://github.com/sebastianbergmann/comparator/compare/3.0.0...3.0.1 +[3.0.0]: https://github.com/sebastianbergmann/comparator/compare/2.1.3...3.0.0 +[2.1.3]: https://github.com/sebastianbergmann/comparator/compare/2.1.2...2.1.3 +[2.1.2]: https://github.com/sebastianbergmann/comparator/compare/2.1.1...2.1.2 +[2.1.1]: https://github.com/sebastianbergmann/comparator/compare/2.1.0...2.1.1 +[2.1.0]: https://github.com/sebastianbergmann/comparator/compare/2.0.2...2.1.0 diff --git a/vendor/sebastian/comparator/LICENSE b/vendor/sebastian/comparator/LICENSE new file mode 100644 index 00000000..6ad70cba --- /dev/null +++ b/vendor/sebastian/comparator/LICENSE @@ -0,0 +1,33 @@ +Comparator + +Copyright (c) 2002-2020, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/sebastian/comparator/README.md b/vendor/sebastian/comparator/README.md new file mode 100644 index 00000000..f6002db6 --- /dev/null +++ b/vendor/sebastian/comparator/README.md @@ -0,0 +1,41 @@ +# sebastian/comparator + +[![CI Status](https://github.com/sebastianbergmann/comparator/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/comparator/actions) +[![Type Coverage](https://shepherd.dev/github/sebastianbergmann/comparator/coverage.svg)](https://shepherd.dev/github/sebastianbergmann/comparator) + +This component provides the functionality to compare PHP values for equality. + +## Installation + +You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): + +``` +composer require sebastian/comparator +``` + +If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency: + +``` +composer require --dev sebastian/comparator +``` + +## Usage + +```php +getComparatorFor($date1, $date2); + +try { + $comparator->assertEquals($date1, $date2); + print "Dates match"; +} catch (ComparisonFailure $failure) { + print "Dates don't match"; +} +``` diff --git a/vendor/sebastian/comparator/composer.json b/vendor/sebastian/comparator/composer.json new file mode 100644 index 00000000..b758e03c --- /dev/null +++ b/vendor/sebastian/comparator/composer.json @@ -0,0 +1,57 @@ +{ + "name": "sebastian/comparator", + "description": "Provides the functionality to compare PHP values for equality", + "keywords": ["comparator","compare","equality"], + "homepage": "https://github.com/sebastianbergmann/comparator", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "prefer-stable": true, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "config": { + "platform": { + "php": "7.3.0" + }, + "optimize-autoloader": true, + "sort-packages": true + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "autoload-dev": { + "classmap": [ + "tests/_fixture" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + } +} + diff --git a/vendor/sebastian/comparator/src/ArrayComparator.php b/vendor/sebastian/comparator/src/ArrayComparator.php new file mode 100644 index 00000000..5d9fbce6 --- /dev/null +++ b/vendor/sebastian/comparator/src/ArrayComparator.php @@ -0,0 +1,141 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Comparator; + +use function array_key_exists; +use function is_array; +use function sort; +use function sprintf; +use function str_replace; +use function trim; + +/** + * Compares arrays for equality. + * + * Arrays are equal if they contain the same key-value pairs. + * The order of the keys does not matter. + * The types of key-value pairs do not matter. + */ +class ArrayComparator extends Comparator +{ + /** + * Returns whether the comparator can compare two values. + * + * @param mixed $expected The first value to compare + * @param mixed $actual The second value to compare + * + * @return bool + */ + public function accepts($expected, $actual) + { + return is_array($expected) && is_array($actual); + } + + /** + * Asserts that two arrays are equal. + * + * @param mixed $expected First value to compare + * @param mixed $actual Second value to compare + * @param float $delta Allowed numerical distance between two values to consider them equal + * @param bool $canonicalize Arrays are sorted before comparison when set to true + * @param bool $ignoreCase Case is ignored when set to true + * @param array $processed List of already processed elements (used to prevent infinite recursion) + * + * @throws ComparisonFailure + */ + public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false, array &$processed = [])/*: void*/ + { + if ($canonicalize) { + sort($expected); + sort($actual); + } + + $remaining = $actual; + $actualAsString = "Array (\n"; + $expectedAsString = "Array (\n"; + $equal = true; + + foreach ($expected as $key => $value) { + unset($remaining[$key]); + + if (!array_key_exists($key, $actual)) { + $expectedAsString .= sprintf( + " %s => %s\n", + $this->exporter->export($key), + $this->exporter->shortenedExport($value) + ); + + $equal = false; + + continue; + } + + try { + $comparator = $this->factory->getComparatorFor($value, $actual[$key]); + $comparator->assertEquals($value, $actual[$key], $delta, $canonicalize, $ignoreCase, $processed); + + $expectedAsString .= sprintf( + " %s => %s\n", + $this->exporter->export($key), + $this->exporter->shortenedExport($value) + ); + + $actualAsString .= sprintf( + " %s => %s\n", + $this->exporter->export($key), + $this->exporter->shortenedExport($actual[$key]) + ); + } catch (ComparisonFailure $e) { + $expectedAsString .= sprintf( + " %s => %s\n", + $this->exporter->export($key), + $e->getExpectedAsString() ? $this->indent($e->getExpectedAsString()) : $this->exporter->shortenedExport($e->getExpected()) + ); + + $actualAsString .= sprintf( + " %s => %s\n", + $this->exporter->export($key), + $e->getActualAsString() ? $this->indent($e->getActualAsString()) : $this->exporter->shortenedExport($e->getActual()) + ); + + $equal = false; + } + } + + foreach ($remaining as $key => $value) { + $actualAsString .= sprintf( + " %s => %s\n", + $this->exporter->export($key), + $this->exporter->shortenedExport($value) + ); + + $equal = false; + } + + $expectedAsString .= ')'; + $actualAsString .= ')'; + + if (!$equal) { + throw new ComparisonFailure( + $expected, + $actual, + $expectedAsString, + $actualAsString, + false, + 'Failed asserting that two arrays are equal.' + ); + } + } + + protected function indent($lines) + { + return trim(str_replace("\n", "\n ", $lines)); + } +} diff --git a/vendor/sebastian/comparator/src/Comparator.php b/vendor/sebastian/comparator/src/Comparator.php new file mode 100644 index 00000000..e1906c16 --- /dev/null +++ b/vendor/sebastian/comparator/src/Comparator.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Comparator; + +use SebastianBergmann\Exporter\Exporter; + +/** + * Abstract base class for comparators which compare values for equality. + */ +abstract class Comparator +{ + /** + * @var Factory + */ + protected $factory; + + /** + * @var Exporter + */ + protected $exporter; + + public function __construct() + { + $this->exporter = new Exporter; + } + + public function setFactory(Factory $factory)/*: void*/ + { + $this->factory = $factory; + } + + /** + * Returns whether the comparator can compare two values. + * + * @param mixed $expected The first value to compare + * @param mixed $actual The second value to compare + * + * @return bool + */ + abstract public function accepts($expected, $actual); + + /** + * Asserts that two values are equal. + * + * @param mixed $expected First value to compare + * @param mixed $actual Second value to compare + * @param float $delta Allowed numerical distance between two values to consider them equal + * @param bool $canonicalize Arrays are sorted before comparison when set to true + * @param bool $ignoreCase Case is ignored when set to true + * + * @throws ComparisonFailure + */ + abstract public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false); +} diff --git a/vendor/sebastian/comparator/src/ComparisonFailure.php b/vendor/sebastian/comparator/src/ComparisonFailure.php new file mode 100644 index 00000000..857314da --- /dev/null +++ b/vendor/sebastian/comparator/src/ComparisonFailure.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Comparator; + +use RuntimeException; +use SebastianBergmann\Diff\Differ; +use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder; + +/** + * Thrown when an assertion for string equality failed. + */ +class ComparisonFailure extends RuntimeException +{ + /** + * Expected value of the retrieval which does not match $actual. + * + * @var mixed + */ + protected $expected; + + /** + * Actually retrieved value which does not match $expected. + * + * @var mixed + */ + protected $actual; + + /** + * The string representation of the expected value. + * + * @var string + */ + protected $expectedAsString; + + /** + * The string representation of the actual value. + * + * @var string + */ + protected $actualAsString; + + /** + * @var bool + */ + protected $identical; + + /** + * Optional message which is placed in front of the first line + * returned by toString(). + * + * @var string + */ + protected $message; + + /** + * Initialises with the expected value and the actual value. + * + * @param mixed $expected expected value retrieved + * @param mixed $actual actual value retrieved + * @param string $expectedAsString + * @param string $actualAsString + * @param bool $identical + * @param string $message a string which is prefixed on all returned lines + * in the difference output + */ + public function __construct($expected, $actual, $expectedAsString, $actualAsString, $identical = false, $message = '') + { + $this->expected = $expected; + $this->actual = $actual; + $this->expectedAsString = $expectedAsString; + $this->actualAsString = $actualAsString; + $this->message = $message; + } + + public function getActual() + { + return $this->actual; + } + + public function getExpected() + { + return $this->expected; + } + + /** + * @return string + */ + public function getActualAsString() + { + return $this->actualAsString; + } + + /** + * @return string + */ + public function getExpectedAsString() + { + return $this->expectedAsString; + } + + /** + * @return string + */ + public function getDiff() + { + if (!$this->actualAsString && !$this->expectedAsString) { + return ''; + } + + $differ = new Differ(new UnifiedDiffOutputBuilder("\n--- Expected\n+++ Actual\n")); + + return $differ->diff($this->expectedAsString, $this->actualAsString); + } + + /** + * @return string + */ + public function toString() + { + return $this->message . $this->getDiff(); + } +} diff --git a/vendor/sebastian/comparator/src/DOMNodeComparator.php b/vendor/sebastian/comparator/src/DOMNodeComparator.php new file mode 100644 index 00000000..5bf854ea --- /dev/null +++ b/vendor/sebastian/comparator/src/DOMNodeComparator.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Comparator; + +use function sprintf; +use function strtolower; +use DOMDocument; +use DOMNode; +use ValueError; + +/** + * Compares DOMNode instances for equality. + */ +class DOMNodeComparator extends ObjectComparator +{ + /** + * Returns whether the comparator can compare two values. + * + * @param mixed $expected The first value to compare + * @param mixed $actual The second value to compare + * + * @return bool + */ + public function accepts($expected, $actual) + { + return $expected instanceof DOMNode && $actual instanceof DOMNode; + } + + /** + * Asserts that two values are equal. + * + * @param mixed $expected First value to compare + * @param mixed $actual Second value to compare + * @param float $delta Allowed numerical distance between two values to consider them equal + * @param bool $canonicalize Arrays are sorted before comparison when set to true + * @param bool $ignoreCase Case is ignored when set to true + * @param array $processed List of already processed elements (used to prevent infinite recursion) + * + * @throws ComparisonFailure + */ + public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false, array &$processed = [])/*: void*/ + { + $expectedAsString = $this->nodeToText($expected, true, $ignoreCase); + $actualAsString = $this->nodeToText($actual, true, $ignoreCase); + + if ($expectedAsString !== $actualAsString) { + $type = $expected instanceof DOMDocument ? 'documents' : 'nodes'; + + throw new ComparisonFailure( + $expected, + $actual, + $expectedAsString, + $actualAsString, + false, + sprintf("Failed asserting that two DOM %s are equal.\n", $type) + ); + } + } + + /** + * Returns the normalized, whitespace-cleaned, and indented textual + * representation of a DOMNode. + */ + private function nodeToText(DOMNode $node, bool $canonicalize, bool $ignoreCase): string + { + if ($canonicalize) { + $document = new DOMDocument; + + try { + @$document->loadXML($node->C14N()); + } catch (ValueError $e) { + } + + $node = $document; + } + + $document = $node instanceof DOMDocument ? $node : $node->ownerDocument; + + $document->formatOutput = true; + $document->normalizeDocument(); + + $text = $node instanceof DOMDocument ? $node->saveXML() : $document->saveXML($node); + + return $ignoreCase ? strtolower($text) : $text; + } +} diff --git a/vendor/sebastian/comparator/src/DateTimeComparator.php b/vendor/sebastian/comparator/src/DateTimeComparator.php new file mode 100644 index 00000000..0a303b62 --- /dev/null +++ b/vendor/sebastian/comparator/src/DateTimeComparator.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Comparator; + +use function abs; +use function floor; +use function sprintf; +use DateInterval; +use DateTime; +use DateTimeInterface; +use DateTimeZone; +use Exception; + +/** + * Compares DateTimeInterface instances for equality. + */ +class DateTimeComparator extends ObjectComparator +{ + /** + * Returns whether the comparator can compare two values. + * + * @param mixed $expected The first value to compare + * @param mixed $actual The second value to compare + * + * @return bool + */ + public function accepts($expected, $actual) + { + return ($expected instanceof DateTime || $expected instanceof DateTimeInterface) && + ($actual instanceof DateTime || $actual instanceof DateTimeInterface); + } + + /** + * Asserts that two values are equal. + * + * @param mixed $expected First value to compare + * @param mixed $actual Second value to compare + * @param float $delta Allowed numerical distance between two values to consider them equal + * @param bool $canonicalize Arrays are sorted before comparison when set to true + * @param bool $ignoreCase Case is ignored when set to true + * @param array $processed List of already processed elements (used to prevent infinite recursion) + * + * @throws Exception + * @throws ComparisonFailure + */ + public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false, array &$processed = [])/*: void*/ + { + /** @var DateTimeInterface $expected */ + /** @var DateTimeInterface $actual */ + $absDelta = abs($delta); + $delta = new DateInterval(sprintf('PT%dS', $absDelta)); + $delta->f = $absDelta - floor($absDelta); + + $actualClone = (clone $actual) + ->setTimezone(new DateTimeZone('UTC')); + + $expectedLower = (clone $expected) + ->setTimezone(new DateTimeZone('UTC')) + ->sub($delta); + + $expectedUpper = (clone $expected) + ->setTimezone(new DateTimeZone('UTC')) + ->add($delta); + + if ($actualClone < $expectedLower || $actualClone > $expectedUpper) { + throw new ComparisonFailure( + $expected, + $actual, + $this->dateTimeToString($expected), + $this->dateTimeToString($actual), + false, + 'Failed asserting that two DateTime objects are equal.' + ); + } + } + + /** + * Returns an ISO 8601 formatted string representation of a datetime or + * 'Invalid DateTimeInterface object' if the provided DateTimeInterface was not properly + * initialized. + */ + private function dateTimeToString(DateTimeInterface $datetime): string + { + $string = $datetime->format('Y-m-d\TH:i:s.uO'); + + return $string ?: 'Invalid DateTimeInterface object'; + } +} diff --git a/vendor/sebastian/comparator/src/DoubleComparator.php b/vendor/sebastian/comparator/src/DoubleComparator.php new file mode 100644 index 00000000..42352219 --- /dev/null +++ b/vendor/sebastian/comparator/src/DoubleComparator.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Comparator; + +use function is_float; +use function is_numeric; + +/** + * Compares doubles for equality. + * + * @deprecated since v3.0.5 and v4.0.8 + */ +class DoubleComparator extends NumericComparator +{ + /** + * Smallest value available in PHP. + * + * @var float + */ + public const EPSILON = 0.0000000001; + + /** + * Returns whether the comparator can compare two values. + * + * @param mixed $expected The first value to compare + * @param mixed $actual The second value to compare + * + * @return bool + */ + public function accepts($expected, $actual) + { + return (is_float($expected) || is_float($actual)) && is_numeric($expected) && is_numeric($actual); + } + + /** + * Asserts that two values are equal. + * + * @param mixed $expected First value to compare + * @param mixed $actual Second value to compare + * @param float $delta Allowed numerical distance between two values to consider them equal + * @param bool $canonicalize Arrays are sorted before comparison when set to true + * @param bool $ignoreCase Case is ignored when set to true + * + * @throws ComparisonFailure + */ + public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false)/*: void*/ + { + if ($delta == 0) { + $delta = self::EPSILON; + } + + parent::assertEquals($expected, $actual, $delta, $canonicalize, $ignoreCase); + } +} diff --git a/vendor/sebastian/comparator/src/ExceptionComparator.php b/vendor/sebastian/comparator/src/ExceptionComparator.php new file mode 100644 index 00000000..1fc0174e --- /dev/null +++ b/vendor/sebastian/comparator/src/ExceptionComparator.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Comparator; + +use Exception; + +/** + * Compares Exception instances for equality. + */ +class ExceptionComparator extends ObjectComparator +{ + /** + * Returns whether the comparator can compare two values. + * + * @param mixed $expected The first value to compare + * @param mixed $actual The second value to compare + * + * @return bool + */ + public function accepts($expected, $actual) + { + return $expected instanceof Exception && $actual instanceof Exception; + } + + /** + * Converts an object to an array containing all of its private, protected + * and public properties. + * + * @param object $object + * + * @return array + */ + protected function toArray($object) + { + $array = parent::toArray($object); + + unset( + $array['file'], + $array['line'], + $array['trace'], + $array['string'], + $array['xdebug_message'] + ); + + return $array; + } +} diff --git a/vendor/sebastian/comparator/src/Factory.php b/vendor/sebastian/comparator/src/Factory.php new file mode 100644 index 00000000..6a8b5b44 --- /dev/null +++ b/vendor/sebastian/comparator/src/Factory.php @@ -0,0 +1,141 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Comparator; + +use function array_unshift; + +/** + * Factory for comparators which compare values for equality. + */ +class Factory +{ + /** + * @var Factory + */ + private static $instance; + + /** + * @var Comparator[] + */ + private $customComparators = []; + + /** + * @var Comparator[] + */ + private $defaultComparators = []; + + /** + * @return Factory + */ + public static function getInstance() + { + if (self::$instance === null) { + self::$instance = new self; // @codeCoverageIgnore + } + + return self::$instance; + } + + /** + * Constructs a new factory. + */ + public function __construct() + { + $this->registerDefaultComparators(); + } + + /** + * Returns the correct comparator for comparing two values. + * + * @param mixed $expected The first value to compare + * @param mixed $actual The second value to compare + * + * @return Comparator + */ + public function getComparatorFor($expected, $actual) + { + foreach ($this->customComparators as $comparator) { + if ($comparator->accepts($expected, $actual)) { + return $comparator; + } + } + + foreach ($this->defaultComparators as $comparator) { + if ($comparator->accepts($expected, $actual)) { + return $comparator; + } + } + + throw new RuntimeException('No suitable Comparator implementation found'); + } + + /** + * Registers a new comparator. + * + * This comparator will be returned by getComparatorFor() if its accept() method + * returns TRUE for the compared values. It has higher priority than the + * existing comparators, meaning that its accept() method will be invoked + * before those of the other comparators. + * + * @param Comparator $comparator The comparator to be registered + */ + public function register(Comparator $comparator)/*: void*/ + { + array_unshift($this->customComparators, $comparator); + + $comparator->setFactory($this); + } + + /** + * Unregisters a comparator. + * + * This comparator will no longer be considered by getComparatorFor(). + * + * @param Comparator $comparator The comparator to be unregistered + */ + public function unregister(Comparator $comparator)/*: void*/ + { + foreach ($this->customComparators as $key => $_comparator) { + if ($comparator === $_comparator) { + unset($this->customComparators[$key]); + } + } + } + + /** + * Unregisters all non-default comparators. + */ + public function reset()/*: void*/ + { + $this->customComparators = []; + } + + private function registerDefaultComparators(): void + { + $this->registerDefaultComparator(new MockObjectComparator); + $this->registerDefaultComparator(new DateTimeComparator); + $this->registerDefaultComparator(new DOMNodeComparator); + $this->registerDefaultComparator(new SplObjectStorageComparator); + $this->registerDefaultComparator(new ExceptionComparator); + $this->registerDefaultComparator(new ObjectComparator); + $this->registerDefaultComparator(new ResourceComparator); + $this->registerDefaultComparator(new ArrayComparator); + $this->registerDefaultComparator(new NumericComparator); + $this->registerDefaultComparator(new ScalarComparator); + $this->registerDefaultComparator(new TypeComparator); + } + + private function registerDefaultComparator(Comparator $comparator): void + { + $this->defaultComparators[] = $comparator; + + $comparator->setFactory($this); + } +} diff --git a/vendor/sebastian/comparator/src/MockObjectComparator.php b/vendor/sebastian/comparator/src/MockObjectComparator.php new file mode 100644 index 00000000..cb670316 --- /dev/null +++ b/vendor/sebastian/comparator/src/MockObjectComparator.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Comparator; + +use PHPUnit\Framework\MockObject\MockObject; + +/** + * Compares PHPUnit\Framework\MockObject\MockObject instances for equality. + */ +class MockObjectComparator extends ObjectComparator +{ + /** + * Returns whether the comparator can compare two values. + * + * @param mixed $expected The first value to compare + * @param mixed $actual The second value to compare + * + * @return bool + */ + public function accepts($expected, $actual) + { + return $expected instanceof MockObject && $actual instanceof MockObject; + } + + /** + * Converts an object to an array containing all of its private, protected + * and public properties. + * + * @param object $object + * + * @return array + */ + protected function toArray($object) + { + $array = parent::toArray($object); + + unset($array['__phpunit_invocationMocker']); + + return $array; + } +} diff --git a/vendor/sebastian/comparator/src/NumericComparator.php b/vendor/sebastian/comparator/src/NumericComparator.php new file mode 100644 index 00000000..841881c9 --- /dev/null +++ b/vendor/sebastian/comparator/src/NumericComparator.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Comparator; + +use function abs; +use function is_float; +use function is_infinite; +use function is_nan; +use function is_numeric; +use function is_string; +use function sprintf; + +/** + * Compares numerical values for equality. + */ +class NumericComparator extends ScalarComparator +{ + /** + * Returns whether the comparator can compare two values. + * + * @param mixed $expected The first value to compare + * @param mixed $actual The second value to compare + * + * @return bool + */ + public function accepts($expected, $actual) + { + // all numerical values, but not if both of them are strings + return is_numeric($expected) && is_numeric($actual) && + !(is_string($expected) && is_string($actual)); + } + + /** + * Asserts that two values are equal. + * + * @param mixed $expected First value to compare + * @param mixed $actual Second value to compare + * @param float $delta Allowed numerical distance between two values to consider them equal + * @param bool $canonicalize Arrays are sorted before comparison when set to true + * @param bool $ignoreCase Case is ignored when set to true + * + * @throws ComparisonFailure + */ + public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false)/*: void*/ + { + if ($this->isInfinite($actual) && $this->isInfinite($expected)) { + return; + } + + if (($this->isInfinite($actual) xor $this->isInfinite($expected)) || + ($this->isNan($actual) || $this->isNan($expected)) || + abs($actual - $expected) > $delta) { + throw new ComparisonFailure( + $expected, + $actual, + '', + '', + false, + sprintf( + 'Failed asserting that %s matches expected %s.', + $this->exporter->export($actual), + $this->exporter->export($expected) + ) + ); + } + } + + private function isInfinite($value): bool + { + return is_float($value) && is_infinite($value); + } + + private function isNan($value): bool + { + return is_float($value) && is_nan($value); + } +} diff --git a/vendor/sebastian/comparator/src/ObjectComparator.php b/vendor/sebastian/comparator/src/ObjectComparator.php new file mode 100644 index 00000000..9380ba15 --- /dev/null +++ b/vendor/sebastian/comparator/src/ObjectComparator.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Comparator; + +use function get_class; +use function in_array; +use function is_object; +use function sprintf; +use function substr_replace; + +/** + * Compares objects for equality. + */ +class ObjectComparator extends ArrayComparator +{ + /** + * Returns whether the comparator can compare two values. + * + * @param mixed $expected The first value to compare + * @param mixed $actual The second value to compare + * + * @return bool + */ + public function accepts($expected, $actual) + { + return is_object($expected) && is_object($actual); + } + + /** + * Asserts that two values are equal. + * + * @param mixed $expected First value to compare + * @param mixed $actual Second value to compare + * @param float $delta Allowed numerical distance between two values to consider them equal + * @param bool $canonicalize Arrays are sorted before comparison when set to true + * @param bool $ignoreCase Case is ignored when set to true + * @param array $processed List of already processed elements (used to prevent infinite recursion) + * + * @throws ComparisonFailure + */ + public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false, array &$processed = [])/*: void*/ + { + if (get_class($actual) !== get_class($expected)) { + throw new ComparisonFailure( + $expected, + $actual, + $this->exporter->export($expected), + $this->exporter->export($actual), + false, + sprintf( + '%s is not instance of expected class "%s".', + $this->exporter->export($actual), + get_class($expected) + ) + ); + } + + // don't compare twice to allow for cyclic dependencies + if (in_array([$actual, $expected], $processed, true) || + in_array([$expected, $actual], $processed, true)) { + return; + } + + $processed[] = [$actual, $expected]; + + // don't compare objects if they are identical + // this helps to avoid the error "maximum function nesting level reached" + // CAUTION: this conditional clause is not tested + if ($actual !== $expected) { + try { + parent::assertEquals( + $this->toArray($expected), + $this->toArray($actual), + $delta, + $canonicalize, + $ignoreCase, + $processed + ); + } catch (ComparisonFailure $e) { + throw new ComparisonFailure( + $expected, + $actual, + // replace "Array" with "MyClass object" + substr_replace($e->getExpectedAsString(), get_class($expected) . ' Object', 0, 5), + substr_replace($e->getActualAsString(), get_class($actual) . ' Object', 0, 5), + false, + 'Failed asserting that two objects are equal.' + ); + } + } + } + + /** + * Converts an object to an array containing all of its private, protected + * and public properties. + * + * @param object $object + * + * @return array + */ + protected function toArray($object) + { + return $this->exporter->toArray($object); + } +} diff --git a/vendor/sebastian/comparator/src/ResourceComparator.php b/vendor/sebastian/comparator/src/ResourceComparator.php new file mode 100644 index 00000000..7822598b --- /dev/null +++ b/vendor/sebastian/comparator/src/ResourceComparator.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Comparator; + +use function is_resource; + +/** + * Compares resources for equality. + */ +class ResourceComparator extends Comparator +{ + /** + * Returns whether the comparator can compare two values. + * + * @param mixed $expected The first value to compare + * @param mixed $actual The second value to compare + * + * @return bool + */ + public function accepts($expected, $actual) + { + return is_resource($expected) && is_resource($actual); + } + + /** + * Asserts that two values are equal. + * + * @param mixed $expected First value to compare + * @param mixed $actual Second value to compare + * @param float $delta Allowed numerical distance between two values to consider them equal + * @param bool $canonicalize Arrays are sorted before comparison when set to true + * @param bool $ignoreCase Case is ignored when set to true + * + * @throws ComparisonFailure + */ + public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false)/*: void*/ + { + if ($actual != $expected) { + throw new ComparisonFailure( + $expected, + $actual, + $this->exporter->export($expected), + $this->exporter->export($actual) + ); + } + } +} diff --git a/vendor/sebastian/comparator/src/ScalarComparator.php b/vendor/sebastian/comparator/src/ScalarComparator.php new file mode 100644 index 00000000..7f131229 --- /dev/null +++ b/vendor/sebastian/comparator/src/ScalarComparator.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Comparator; + +use function is_bool; +use function is_object; +use function is_scalar; +use function is_string; +use function method_exists; +use function sprintf; +use function strtolower; + +/** + * Compares scalar or NULL values for equality. + */ +class ScalarComparator extends Comparator +{ + /** + * Returns whether the comparator can compare two values. + * + * @param mixed $expected The first value to compare + * @param mixed $actual The second value to compare + * + * @return bool + * + * @since Method available since Release 3.6.0 + */ + public function accepts($expected, $actual) + { + return ((is_scalar($expected) xor null === $expected) && + (is_scalar($actual) xor null === $actual)) + // allow comparison between strings and objects featuring __toString() + || (is_string($expected) && is_object($actual) && method_exists($actual, '__toString')) + || (is_object($expected) && method_exists($expected, '__toString') && is_string($actual)); + } + + /** + * Asserts that two values are equal. + * + * @param mixed $expected First value to compare + * @param mixed $actual Second value to compare + * @param float $delta Allowed numerical distance between two values to consider them equal + * @param bool $canonicalize Arrays are sorted before comparison when set to true + * @param bool $ignoreCase Case is ignored when set to true + * + * @throws ComparisonFailure + */ + public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false)/*: void*/ + { + $expectedToCompare = $expected; + $actualToCompare = $actual; + + // always compare as strings to avoid strange behaviour + // otherwise 0 == 'Foobar' + if ((is_string($expected) && !is_bool($actual)) || (is_string($actual) && !is_bool($expected))) { + $expectedToCompare = (string) $expectedToCompare; + $actualToCompare = (string) $actualToCompare; + + if ($ignoreCase) { + $expectedToCompare = strtolower($expectedToCompare); + $actualToCompare = strtolower($actualToCompare); + } + } + + if ($expectedToCompare !== $actualToCompare && is_string($expected) && is_string($actual)) { + throw new ComparisonFailure( + $expected, + $actual, + $this->exporter->export($expected), + $this->exporter->export($actual), + false, + 'Failed asserting that two strings are equal.' + ); + } + + if ($expectedToCompare != $actualToCompare) { + throw new ComparisonFailure( + $expected, + $actual, + // no diff is required + '', + '', + false, + sprintf( + 'Failed asserting that %s matches expected %s.', + $this->exporter->export($actual), + $this->exporter->export($expected) + ) + ); + } + } +} diff --git a/vendor/sebastian/comparator/src/SplObjectStorageComparator.php b/vendor/sebastian/comparator/src/SplObjectStorageComparator.php new file mode 100644 index 00000000..d9b6f541 --- /dev/null +++ b/vendor/sebastian/comparator/src/SplObjectStorageComparator.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Comparator; + +use SplObjectStorage; + +/** + * Compares \SplObjectStorage instances for equality. + */ +class SplObjectStorageComparator extends Comparator +{ + /** + * Returns whether the comparator can compare two values. + * + * @param mixed $expected The first value to compare + * @param mixed $actual The second value to compare + * + * @return bool + */ + public function accepts($expected, $actual) + { + return $expected instanceof SplObjectStorage && $actual instanceof SplObjectStorage; + } + + /** + * Asserts that two values are equal. + * + * @param mixed $expected First value to compare + * @param mixed $actual Second value to compare + * @param float $delta Allowed numerical distance between two values to consider them equal + * @param bool $canonicalize Arrays are sorted before comparison when set to true + * @param bool $ignoreCase Case is ignored when set to true + * + * @throws ComparisonFailure + */ + public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false)/*: void*/ + { + foreach ($actual as $object) { + if (!$expected->contains($object)) { + throw new ComparisonFailure( + $expected, + $actual, + $this->exporter->export($expected), + $this->exporter->export($actual), + false, + 'Failed asserting that two objects are equal.' + ); + } + } + + foreach ($expected as $object) { + if (!$actual->contains($object)) { + throw new ComparisonFailure( + $expected, + $actual, + $this->exporter->export($expected), + $this->exporter->export($actual), + false, + 'Failed asserting that two objects are equal.' + ); + } + } + } +} diff --git a/vendor/sebastian/comparator/src/TypeComparator.php b/vendor/sebastian/comparator/src/TypeComparator.php new file mode 100644 index 00000000..b0d38d72 --- /dev/null +++ b/vendor/sebastian/comparator/src/TypeComparator.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Comparator; + +use function gettype; +use function sprintf; + +/** + * Compares values for type equality. + */ +class TypeComparator extends Comparator +{ + /** + * Returns whether the comparator can compare two values. + * + * @param mixed $expected The first value to compare + * @param mixed $actual The second value to compare + * + * @return bool + */ + public function accepts($expected, $actual) + { + return true; + } + + /** + * Asserts that two values are equal. + * + * @param mixed $expected First value to compare + * @param mixed $actual Second value to compare + * @param float $delta Allowed numerical distance between two values to consider them equal + * @param bool $canonicalize Arrays are sorted before comparison when set to true + * @param bool $ignoreCase Case is ignored when set to true + * + * @throws ComparisonFailure + */ + public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false)/*: void*/ + { + if (gettype($expected) != gettype($actual)) { + throw new ComparisonFailure( + $expected, + $actual, + // we don't need a diff + '', + '', + false, + sprintf( + '%s does not match expected type "%s".', + $this->exporter->shortenedExport($actual), + gettype($expected) + ) + ); + } + } +} diff --git a/vendor/sebastian/comparator/src/exceptions/Exception.php b/vendor/sebastian/comparator/src/exceptions/Exception.php new file mode 100644 index 00000000..8975aaf1 --- /dev/null +++ b/vendor/sebastian/comparator/src/exceptions/Exception.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Comparator; + +use Throwable; + +interface Exception extends Throwable +{ +} diff --git a/vendor/sebastian/comparator/src/exceptions/RuntimeException.php b/vendor/sebastian/comparator/src/exceptions/RuntimeException.php new file mode 100644 index 00000000..ca726084 --- /dev/null +++ b/vendor/sebastian/comparator/src/exceptions/RuntimeException.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Comparator; + +final class RuntimeException extends \RuntimeException implements Exception +{ +} diff --git a/vendor/sebastian/complexity/.psalm/baseline.xml b/vendor/sebastian/complexity/.psalm/baseline.xml new file mode 100644 index 00000000..77e688e0 --- /dev/null +++ b/vendor/sebastian/complexity/.psalm/baseline.xml @@ -0,0 +1,2 @@ + + diff --git a/vendor/sebastian/complexity/.psalm/config.xml b/vendor/sebastian/complexity/.psalm/config.xml new file mode 100644 index 00000000..8172fe15 --- /dev/null +++ b/vendor/sebastian/complexity/.psalm/config.xml @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/vendor/sebastian/complexity/ChangeLog.md b/vendor/sebastian/complexity/ChangeLog.md new file mode 100644 index 00000000..51deba71 --- /dev/null +++ b/vendor/sebastian/complexity/ChangeLog.md @@ -0,0 +1,37 @@ +# ChangeLog + +All notable changes are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles. + +## [2.0.3] - 2023-12-22 + +### Changed + +* This component is now compatible with `nikic/php-parser` 5.0 + +## [2.0.2] - 2020-10-26 + +### Fixed + +* `SebastianBergmann\Complexity\Exception` now correctly extends `\Throwable` + +## [2.0.1] - 2020-09-28 + +### Changed + +* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3` + +## [2.0.0] - 2020-07-25 + +### Removed + +* The `ParentConnectingVisitor` has been removed (it should have been marked as `@internal`) + +## [1.0.0] - 2020-07-22 + +* Initial release + +[2.0.3]: https://github.com/sebastianbergmann/complexity/compare/2.0.2...2.0.3 +[2.0.2]: https://github.com/sebastianbergmann/complexity/compare/2.0.1...2.0.2 +[2.0.1]: https://github.com/sebastianbergmann/complexity/compare/2.0.0...2.0.1 +[2.0.0]: https://github.com/sebastianbergmann/complexity/compare/1.0.0...2.0.0 +[1.0.0]: https://github.com/sebastianbergmann/complexity/compare/70ee0ad32d9e2be3f85beffa3e2eb474193f2487...1.0.0 diff --git a/vendor/sebastian/complexity/LICENSE b/vendor/sebastian/complexity/LICENSE new file mode 100644 index 00000000..5f818df6 --- /dev/null +++ b/vendor/sebastian/complexity/LICENSE @@ -0,0 +1,33 @@ +sebastian/complexity + +Copyright (c) 2020, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/sebastian/complexity/README.md b/vendor/sebastian/complexity/README.md new file mode 100644 index 00000000..5f53b0b5 --- /dev/null +++ b/vendor/sebastian/complexity/README.md @@ -0,0 +1,22 @@ +# sebastian/complexity + +Library for calculating the complexity of PHP code units. + +[![Latest Stable Version](https://img.shields.io/packagist/v/sebastian/complexity.svg?style=flat-square)](https://packagist.org/packages/sebastian/complexity) +[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.3-8892BF.svg?style=flat-square)](https://php.net/) +[![CI Status](https://github.com/sebastianbergmann/complexity/workflows/CI/badge.svg?branch=master&event=push)](https://phpunit.de/build-status.html) +[![Type Coverage](https://shepherd.dev/github/sebastianbergmann/complexity/coverage.svg)](https://shepherd.dev/github/sebastianbergmann/complexity) + +## Installation + +You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): + +``` +composer require sebastian/complexity +``` + +If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency: + +``` +composer require --dev sebastian/complexity +``` diff --git a/vendor/sebastian/complexity/composer.json b/vendor/sebastian/complexity/composer.json new file mode 100644 index 00000000..592e4185 --- /dev/null +++ b/vendor/sebastian/complexity/composer.json @@ -0,0 +1,42 @@ +{ + "name": "sebastian/complexity", + "description": "Library for calculating the complexity of PHP code units", + "type": "library", + "homepage": "https://github.com/sebastianbergmann/complexity", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues" + }, + "prefer-stable": true, + "require": { + "php": ">=7.3", + "nikic/php-parser": "^4.18 || ^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "config": { + "platform": { + "php": "7.3.0" + }, + "optimize-autoloader": true, + "sort-packages": true + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + } +} diff --git a/vendor/sebastian/complexity/src/Calculator.php b/vendor/sebastian/complexity/src/Calculator.php new file mode 100644 index 00000000..f5381903 --- /dev/null +++ b/vendor/sebastian/complexity/src/Calculator.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Complexity; + +use PhpParser\Error; +use PhpParser\Node; +use PhpParser\NodeTraverser; +use PhpParser\NodeVisitor\NameResolver; +use PhpParser\NodeVisitor\ParentConnectingVisitor; +use PhpParser\ParserFactory; + +final class Calculator +{ + /** + * @throws RuntimeException + */ + public function calculateForSourceFile(string $sourceFile): ComplexityCollection + { + return $this->calculateForSourceString(file_get_contents($sourceFile)); + } + + /** + * @throws RuntimeException + */ + public function calculateForSourceString(string $source): ComplexityCollection + { + try { + $nodes = (new ParserFactory)->createForHostVersion()->parse($source); + + assert($nodes !== null); + + return $this->calculateForAbstractSyntaxTree($nodes); + + // @codeCoverageIgnoreStart + } catch (Error $error) { + throw new RuntimeException( + $error->getMessage(), + (int) $error->getCode(), + $error + ); + } + // @codeCoverageIgnoreEnd + } + + /** + * @param Node[] $nodes + * + * @throws RuntimeException + */ + public function calculateForAbstractSyntaxTree(array $nodes): ComplexityCollection + { + $traverser = new NodeTraverser; + $complexityCalculatingVisitor = new ComplexityCalculatingVisitor(true); + + $traverser->addVisitor(new NameResolver); + $traverser->addVisitor(new ParentConnectingVisitor); + $traverser->addVisitor($complexityCalculatingVisitor); + + try { + /* @noinspection UnusedFunctionResultInspection */ + $traverser->traverse($nodes); + // @codeCoverageIgnoreStart + } catch (Error $error) { + throw new RuntimeException( + $error->getMessage(), + (int) $error->getCode(), + $error + ); + } + // @codeCoverageIgnoreEnd + + return $complexityCalculatingVisitor->result(); + } +} diff --git a/vendor/sebastian/complexity/src/Complexity/Complexity.php b/vendor/sebastian/complexity/src/Complexity/Complexity.php new file mode 100644 index 00000000..dc6708dd --- /dev/null +++ b/vendor/sebastian/complexity/src/Complexity/Complexity.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Complexity; + +/** + * @psalm-immutable + */ +final class Complexity +{ + /** + * @var string + */ + private $name; + + /** + * @var int + */ + private $cyclomaticComplexity; + + public function __construct(string $name, int $cyclomaticComplexity) + { + $this->name = $name; + $this->cyclomaticComplexity = $cyclomaticComplexity; + } + + public function name(): string + { + return $this->name; + } + + public function cyclomaticComplexity(): int + { + return $this->cyclomaticComplexity; + } +} diff --git a/vendor/sebastian/complexity/src/Complexity/ComplexityCollection.php b/vendor/sebastian/complexity/src/Complexity/ComplexityCollection.php new file mode 100644 index 00000000..ccbddbf7 --- /dev/null +++ b/vendor/sebastian/complexity/src/Complexity/ComplexityCollection.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Complexity; + +use function count; +use Countable; +use IteratorAggregate; + +/** + * @psalm-immutable + */ +final class ComplexityCollection implements Countable, IteratorAggregate +{ + /** + * @psalm-var list + */ + private $items = []; + + public static function fromList(Complexity ...$items): self + { + return new self($items); + } + + /** + * @psalm-param list $items + */ + private function __construct(array $items) + { + $this->items = $items; + } + + /** + * @psalm-return list + */ + public function asArray(): array + { + return $this->items; + } + + public function getIterator(): ComplexityCollectionIterator + { + return new ComplexityCollectionIterator($this); + } + + public function count(): int + { + return count($this->items); + } + + public function isEmpty(): bool + { + return empty($this->items); + } + + public function cyclomaticComplexity(): int + { + $cyclomaticComplexity = 0; + + foreach ($this as $item) { + $cyclomaticComplexity += $item->cyclomaticComplexity(); + } + + return $cyclomaticComplexity; + } +} diff --git a/vendor/sebastian/complexity/src/Complexity/ComplexityCollectionIterator.php b/vendor/sebastian/complexity/src/Complexity/ComplexityCollectionIterator.php new file mode 100644 index 00000000..ec39e199 --- /dev/null +++ b/vendor/sebastian/complexity/src/Complexity/ComplexityCollectionIterator.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Complexity; + +use Iterator; + +final class ComplexityCollectionIterator implements Iterator +{ + /** + * @psalm-var list + */ + private $items; + + /** + * @var int + */ + private $position = 0; + + public function __construct(ComplexityCollection $items) + { + $this->items = $items->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return isset($this->items[$this->position]); + } + + public function key(): int + { + return $this->position; + } + + public function current(): Complexity + { + return $this->items[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/vendor/sebastian/complexity/src/Exception/Exception.php b/vendor/sebastian/complexity/src/Exception/Exception.php new file mode 100644 index 00000000..897ecdcf --- /dev/null +++ b/vendor/sebastian/complexity/src/Exception/Exception.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Complexity; + +use Throwable; + +interface Exception extends Throwable +{ +} diff --git a/vendor/sebastian/complexity/src/Exception/RuntimeException.php b/vendor/sebastian/complexity/src/Exception/RuntimeException.php new file mode 100644 index 00000000..6c68a6f0 --- /dev/null +++ b/vendor/sebastian/complexity/src/Exception/RuntimeException.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Complexity; + +final class RuntimeException extends \RuntimeException implements Exception +{ +} diff --git a/vendor/sebastian/complexity/src/Visitor/ComplexityCalculatingVisitor.php b/vendor/sebastian/complexity/src/Visitor/ComplexityCalculatingVisitor.php new file mode 100644 index 00000000..b69f2b09 --- /dev/null +++ b/vendor/sebastian/complexity/src/Visitor/ComplexityCalculatingVisitor.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Complexity; + +use function assert; +use function is_array; +use PhpParser\Node; +use PhpParser\Node\Name; +use PhpParser\Node\Stmt; +use PhpParser\Node\Stmt\Class_; +use PhpParser\Node\Stmt\ClassMethod; +use PhpParser\Node\Stmt\Function_; +use PhpParser\Node\Stmt\Trait_; +use PhpParser\NodeTraverser; +use PhpParser\NodeVisitorAbstract; + +final class ComplexityCalculatingVisitor extends NodeVisitorAbstract +{ + /** + * @psalm-var list + */ + private $result = []; + + /** + * @var bool + */ + private $shortCircuitTraversal; + + public function __construct(bool $shortCircuitTraversal) + { + $this->shortCircuitTraversal = $shortCircuitTraversal; + } + + public function enterNode(Node $node): ?int + { + if (!$node instanceof ClassMethod && !$node instanceof Function_) { + return null; + } + + if ($node instanceof ClassMethod) { + $name = $this->classMethodName($node); + } else { + $name = $this->functionName($node); + } + + $statements = $node->getStmts(); + + assert(is_array($statements)); + + $this->result[] = new Complexity( + $name, + $this->cyclomaticComplexity($statements) + ); + + if ($this->shortCircuitTraversal) { + return NodeTraverser::DONT_TRAVERSE_CHILDREN; + } + + return null; + } + + public function result(): ComplexityCollection + { + return ComplexityCollection::fromList(...$this->result); + } + + /** + * @param Stmt[] $statements + */ + private function cyclomaticComplexity(array $statements): int + { + $traverser = new NodeTraverser; + + $cyclomaticComplexityCalculatingVisitor = new CyclomaticComplexityCalculatingVisitor; + + $traverser->addVisitor($cyclomaticComplexityCalculatingVisitor); + + /* @noinspection UnusedFunctionResultInspection */ + $traverser->traverse($statements); + + return $cyclomaticComplexityCalculatingVisitor->cyclomaticComplexity(); + } + + private function classMethodName(ClassMethod $node): string + { + $parent = $node->getAttribute('parent'); + + assert($parent instanceof Class_ || $parent instanceof Trait_); + assert(isset($parent->namespacedName)); + assert($parent->namespacedName instanceof Name); + + return $parent->namespacedName->toString() . '::' . $node->name->toString(); + } + + private function functionName(Function_ $node): string + { + assert(isset($node->namespacedName)); + assert($node->namespacedName instanceof Name); + + return $node->namespacedName->toString(); + } +} diff --git a/vendor/sebastian/complexity/src/Visitor/CyclomaticComplexityCalculatingVisitor.php b/vendor/sebastian/complexity/src/Visitor/CyclomaticComplexityCalculatingVisitor.php new file mode 100644 index 00000000..d4430876 --- /dev/null +++ b/vendor/sebastian/complexity/src/Visitor/CyclomaticComplexityCalculatingVisitor.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Complexity; + +use function get_class; +use PhpParser\Node; +use PhpParser\Node\Expr\BinaryOp\BooleanAnd; +use PhpParser\Node\Expr\BinaryOp\BooleanOr; +use PhpParser\Node\Expr\BinaryOp\LogicalAnd; +use PhpParser\Node\Expr\BinaryOp\LogicalOr; +use PhpParser\Node\Expr\Ternary; +use PhpParser\Node\Stmt\Case_; +use PhpParser\Node\Stmt\Catch_; +use PhpParser\Node\Stmt\ElseIf_; +use PhpParser\Node\Stmt\For_; +use PhpParser\Node\Stmt\Foreach_; +use PhpParser\Node\Stmt\If_; +use PhpParser\Node\Stmt\While_; +use PhpParser\NodeVisitorAbstract; + +final class CyclomaticComplexityCalculatingVisitor extends NodeVisitorAbstract +{ + /** + * @var int + */ + private $cyclomaticComplexity = 1; + + public function enterNode(Node $node): void + { + /* @noinspection GetClassMissUseInspection */ + switch (get_class($node)) { + case BooleanAnd::class: + case BooleanOr::class: + case Case_::class: + case Catch_::class: + case ElseIf_::class: + case For_::class: + case Foreach_::class: + case If_::class: + case LogicalAnd::class: + case LogicalOr::class: + case Ternary::class: + case While_::class: + $this->cyclomaticComplexity++; + } + } + + public function cyclomaticComplexity(): int + { + return $this->cyclomaticComplexity; + } +} diff --git a/vendor/sebastian/diff/ChangeLog.md b/vendor/sebastian/diff/ChangeLog.md new file mode 100644 index 00000000..e62d8f95 --- /dev/null +++ b/vendor/sebastian/diff/ChangeLog.md @@ -0,0 +1,103 @@ +# ChangeLog + +All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. + +## [4.0.6] - 2024-03-02 + +### Changed + +* Do not use implicitly nullable parameters + +## [4.0.5] - 2023-05-07 + +### Changed + +* [#118](https://github.com/sebastianbergmann/diff/pull/118): Improve performance of `MemoryEfficientLongestCommonSubsequenceCalculator` +* [#119](https://github.com/sebastianbergmann/diff/pull/119): Improve performance of `TimeEfficientLongestCommonSubsequenceCalculator` + +## [4.0.4] - 2020-10-26 + +### Fixed + +* `SebastianBergmann\Diff\Exception` now correctly extends `\Throwable` + +## [4.0.3] - 2020-09-28 + +### Changed + +* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3` + +## [4.0.2] - 2020-06-30 + +### Added + +* This component is now supported on PHP 8 + +## [4.0.1] - 2020-05-08 + +### Fixed + +* [#99](https://github.com/sebastianbergmann/diff/pull/99): Regression in unified diff output of identical strings + +## [4.0.0] - 2020-02-07 + +### Removed + +* Removed support for PHP 7.1 and PHP 7.2 + +## [3.0.2] - 2019-02-04 + +### Changed + +* `Chunk::setLines()` now ensures that the `$lines` array only contains `Line` objects + +## [3.0.1] - 2018-06-10 + +### Fixed + +* Removed `"minimum-stability": "dev",` from `composer.json` + +## [3.0.0] - 2018-02-01 + +* The `StrictUnifiedDiffOutputBuilder` implementation of the `DiffOutputBuilderInterface` was added + +### Changed + +* The default `DiffOutputBuilderInterface` implementation now generates context lines (unchanged lines) + +### Removed + +* Removed support for PHP 7.0 + +### Fixed + +* [#70](https://github.com/sebastianbergmann/diff/issues/70): Diffing of arrays no longer works + +## [2.0.1] - 2017-08-03 + +### Fixed + +* [#66](https://github.com/sebastianbergmann/diff/pull/66): Restored backwards compatibility for PHPUnit 6.1.4, 6.2.0, 6.2.1, 6.2.2, and 6.2.3 + +## [2.0.0] - 2017-07-11 [YANKED] + +### Added + +* [#64](https://github.com/sebastianbergmann/diff/pull/64): Show line numbers for chunks of a diff + +### Removed + +* This component is no longer supported on PHP 5.6 + +[4.0.6]: https://github.com/sebastianbergmann/diff/compare/4.0.5...4.0.6 +[4.0.5]: https://github.com/sebastianbergmann/diff/compare/4.0.4...4.0.5 +[4.0.4]: https://github.com/sebastianbergmann/diff/compare/4.0.3...4.0.4 +[4.0.3]: https://github.com/sebastianbergmann/diff/compare/4.0.2...4.0.3 +[4.0.2]: https://github.com/sebastianbergmann/diff/compare/4.0.1...4.0.2 +[4.0.1]: https://github.com/sebastianbergmann/diff/compare/4.0.0...4.0.1 +[4.0.0]: https://github.com/sebastianbergmann/diff/compare/3.0.2...4.0.0 +[3.0.2]: https://github.com/sebastianbergmann/diff/compare/3.0.1...3.0.2 +[3.0.1]: https://github.com/sebastianbergmann/diff/compare/3.0.0...3.0.1 +[3.0.0]: https://github.com/sebastianbergmann/diff/compare/2.0...3.0.0 +[2.0.1]: https://github.com/sebastianbergmann/diff/compare/c341c98ce083db77f896a0aa64f5ee7652915970...2.0.1 +[2.0.0]: https://github.com/sebastianbergmann/diff/compare/1.4...c341c98ce083db77f896a0aa64f5ee7652915970 diff --git a/vendor/sebastian/diff/LICENSE b/vendor/sebastian/diff/LICENSE new file mode 100644 index 00000000..f22f31cf --- /dev/null +++ b/vendor/sebastian/diff/LICENSE @@ -0,0 +1,33 @@ +sebastian/diff + +Copyright (c) 2002-2020, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/sebastian/diff/README.md b/vendor/sebastian/diff/README.md new file mode 100644 index 00000000..734b852d --- /dev/null +++ b/vendor/sebastian/diff/README.md @@ -0,0 +1,202 @@ +# sebastian/diff + +[![CI Status](https://github.com/sebastianbergmann/diff/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/diff/actions) +[![Type Coverage](https://shepherd.dev/github/sebastianbergmann/diff/coverage.svg)](https://shepherd.dev/github/sebastianbergmann/diff) + +Diff implementation for PHP, factored out of PHPUnit into a stand-alone component. + +## Installation + +You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): + +``` +composer require sebastian/diff +``` + +If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency: + +``` +composer require --dev sebastian/diff +``` + +### Usage + +#### Generating diff + +The `Differ` class can be used to generate a textual representation of the difference between two strings: + +```php +diff('foo', 'bar'); +``` + +The code above yields the output below: +```diff +--- Original ++++ New +@@ @@ +-foo ++bar +``` + +There are three output builders available in this package: + +#### UnifiedDiffOutputBuilder + +This is default builder, which generates the output close to udiff and is used by PHPUnit. + +```php +diff('foo', 'bar'); +``` + +#### StrictUnifiedDiffOutputBuilder + +Generates (strict) Unified diff's (unidiffs) with hunks, +similar to `diff -u` and compatible with `patch` and `git apply`. + +```php + true, // ranges of length one are rendered with the trailing `,1` + 'commonLineThreshold' => 6, // number of same lines before ending a new hunk and creating a new one (if needed) + 'contextLines' => 3, // like `diff: -u, -U NUM, --unified[=NUM]`, for patch/git apply compatibility best to keep at least @ 3 + 'fromFile' => null, + 'fromFileDate' => null, + 'toFile' => null, + 'toFileDate' => null, +]); + +$differ = new Differ($builder); +print $differ->diff('foo', 'bar'); +``` + +#### DiffOnlyOutputBuilder + +Output only the lines that differ. + +```php +diff('foo', 'bar'); +``` + +#### DiffOutputBuilderInterface + +You can pass any output builder to the `Differ` class as longs as it implements the `DiffOutputBuilderInterface`. + +#### Parsing diff + +The `Parser` class can be used to parse a unified diff into an object graph: + +```php +use SebastianBergmann\Diff\Parser; +use SebastianBergmann\Git; + +$git = new Git('/usr/local/src/money'); + +$diff = $git->getDiff( + '948a1a07768d8edd10dcefa8315c1cbeffb31833', + 'c07a373d2399f3e686234c4f7f088d635eb9641b' +); + +$parser = new Parser; + +print_r($parser->parse($diff)); +``` + +The code above yields the output below: + + Array + ( + [0] => SebastianBergmann\Diff\Diff Object + ( + [from:SebastianBergmann\Diff\Diff:private] => a/tests/MoneyTest.php + [to:SebastianBergmann\Diff\Diff:private] => b/tests/MoneyTest.php + [chunks:SebastianBergmann\Diff\Diff:private] => Array + ( + [0] => SebastianBergmann\Diff\Chunk Object + ( + [start:SebastianBergmann\Diff\Chunk:private] => 87 + [startRange:SebastianBergmann\Diff\Chunk:private] => 7 + [end:SebastianBergmann\Diff\Chunk:private] => 87 + [endRange:SebastianBergmann\Diff\Chunk:private] => 7 + [lines:SebastianBergmann\Diff\Chunk:private] => Array + ( + [0] => SebastianBergmann\Diff\Line Object + ( + [type:SebastianBergmann\Diff\Line:private] => 3 + [content:SebastianBergmann\Diff\Line:private] => * @covers SebastianBergmann\Money\Money::add + ) + + [1] => SebastianBergmann\Diff\Line Object + ( + [type:SebastianBergmann\Diff\Line:private] => 3 + [content:SebastianBergmann\Diff\Line:private] => * @covers SebastianBergmann\Money\Money::newMoney + ) + + [2] => SebastianBergmann\Diff\Line Object + ( + [type:SebastianBergmann\Diff\Line:private] => 3 + [content:SebastianBergmann\Diff\Line:private] => */ + ) + + [3] => SebastianBergmann\Diff\Line Object + ( + [type:SebastianBergmann\Diff\Line:private] => 2 + [content:SebastianBergmann\Diff\Line:private] => public function testAnotherMoneyWithSameCurrencyObjectCanBeAdded() + ) + + [4] => SebastianBergmann\Diff\Line Object + ( + [type:SebastianBergmann\Diff\Line:private] => 1 + [content:SebastianBergmann\Diff\Line:private] => public function testAnotherMoneyObjectWithSameCurrencyCanBeAdded() + ) + + [5] => SebastianBergmann\Diff\Line Object + ( + [type:SebastianBergmann\Diff\Line:private] => 3 + [content:SebastianBergmann\Diff\Line:private] => { + ) + + [6] => SebastianBergmann\Diff\Line Object + ( + [type:SebastianBergmann\Diff\Line:private] => 3 + [content:SebastianBergmann\Diff\Line:private] => $a = new Money(1, new Currency('EUR')); + ) + + [7] => SebastianBergmann\Diff\Line Object + ( + [type:SebastianBergmann\Diff\Line:private] => 3 + [content:SebastianBergmann\Diff\Line:private] => $b = new Money(2, new Currency('EUR')); + ) + ) + ) + ) + ) + ) diff --git a/vendor/sebastian/diff/composer.json b/vendor/sebastian/diff/composer.json new file mode 100644 index 00000000..cf92202b --- /dev/null +++ b/vendor/sebastian/diff/composer.json @@ -0,0 +1,47 @@ +{ + "name": "sebastian/diff", + "description": "Diff implementation", + "keywords": ["diff", "udiff", "unidiff", "unified diff"], + "homepage": "https://github.com/sebastianbergmann/diff", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "prefer-stable": true, + "config": { + "platform": { + "php": "7.3.0" + }, + "optimize-autoloader": true, + "sort-packages": true + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "autoload-dev": { + "classmap": [ + "tests/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + } +} diff --git a/vendor/sebastian/diff/src/Chunk.php b/vendor/sebastian/diff/src/Chunk.php new file mode 100644 index 00000000..16ae34f4 --- /dev/null +++ b/vendor/sebastian/diff/src/Chunk.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Diff; + +final class Chunk +{ + /** + * @var int + */ + private $start; + + /** + * @var int + */ + private $startRange; + + /** + * @var int + */ + private $end; + + /** + * @var int + */ + private $endRange; + + /** + * @var Line[] + */ + private $lines; + + public function __construct(int $start = 0, int $startRange = 1, int $end = 0, int $endRange = 1, array $lines = []) + { + $this->start = $start; + $this->startRange = $startRange; + $this->end = $end; + $this->endRange = $endRange; + $this->lines = $lines; + } + + public function getStart(): int + { + return $this->start; + } + + public function getStartRange(): int + { + return $this->startRange; + } + + public function getEnd(): int + { + return $this->end; + } + + public function getEndRange(): int + { + return $this->endRange; + } + + /** + * @return Line[] + */ + public function getLines(): array + { + return $this->lines; + } + + /** + * @param Line[] $lines + */ + public function setLines(array $lines): void + { + foreach ($lines as $line) { + if (!$line instanceof Line) { + throw new InvalidArgumentException; + } + } + + $this->lines = $lines; + } +} diff --git a/vendor/sebastian/diff/src/Diff.php b/vendor/sebastian/diff/src/Diff.php new file mode 100644 index 00000000..17b2084f --- /dev/null +++ b/vendor/sebastian/diff/src/Diff.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Diff; + +final class Diff +{ + /** + * @var string + */ + private $from; + + /** + * @var string + */ + private $to; + + /** + * @var Chunk[] + */ + private $chunks; + + /** + * @param Chunk[] $chunks + */ + public function __construct(string $from, string $to, array $chunks = []) + { + $this->from = $from; + $this->to = $to; + $this->chunks = $chunks; + } + + public function getFrom(): string + { + return $this->from; + } + + public function getTo(): string + { + return $this->to; + } + + /** + * @return Chunk[] + */ + public function getChunks(): array + { + return $this->chunks; + } + + /** + * @param Chunk[] $chunks + */ + public function setChunks(array $chunks): void + { + $this->chunks = $chunks; + } +} diff --git a/vendor/sebastian/diff/src/Differ.php b/vendor/sebastian/diff/src/Differ.php new file mode 100644 index 00000000..98c7a9b2 --- /dev/null +++ b/vendor/sebastian/diff/src/Differ.php @@ -0,0 +1,327 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Diff; + +use const PHP_INT_SIZE; +use const PREG_SPLIT_DELIM_CAPTURE; +use const PREG_SPLIT_NO_EMPTY; +use function array_shift; +use function array_unshift; +use function array_values; +use function count; +use function current; +use function end; +use function get_class; +use function gettype; +use function is_array; +use function is_object; +use function is_string; +use function key; +use function min; +use function preg_split; +use function prev; +use function reset; +use function sprintf; +use function substr; +use SebastianBergmann\Diff\Output\DiffOutputBuilderInterface; +use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder; + +final class Differ +{ + public const OLD = 0; + + public const ADDED = 1; + + public const REMOVED = 2; + + public const DIFF_LINE_END_WARNING = 3; + + public const NO_LINE_END_EOF_WARNING = 4; + + /** + * @var DiffOutputBuilderInterface + */ + private $outputBuilder; + + /** + * @param DiffOutputBuilderInterface $outputBuilder + * + * @throws InvalidArgumentException + */ + public function __construct($outputBuilder = null) + { + if ($outputBuilder instanceof DiffOutputBuilderInterface) { + $this->outputBuilder = $outputBuilder; + } elseif (null === $outputBuilder) { + $this->outputBuilder = new UnifiedDiffOutputBuilder; + } elseif (is_string($outputBuilder)) { + // PHPUnit 6.1.4, 6.2.0, 6.2.1, 6.2.2, and 6.2.3 support + // @see https://github.com/sebastianbergmann/phpunit/issues/2734#issuecomment-314514056 + // @deprecated + $this->outputBuilder = new UnifiedDiffOutputBuilder($outputBuilder); + } else { + throw new InvalidArgumentException( + sprintf( + 'Expected builder to be an instance of DiffOutputBuilderInterface, or a string, got %s.', + is_object($outputBuilder) ? 'instance of "' . get_class($outputBuilder) . '"' : gettype($outputBuilder) . ' "' . $outputBuilder . '"' + ) + ); + } + } + + /** + * Returns the diff between two arrays or strings as string. + * + * @param array|string $from + * @param array|string $to + */ + public function diff($from, $to, ?LongestCommonSubsequenceCalculator $lcs = null): string + { + $diff = $this->diffToArray( + $this->normalizeDiffInput($from), + $this->normalizeDiffInput($to), + $lcs + ); + + return $this->outputBuilder->getDiff($diff); + } + + /** + * Returns the diff between two arrays or strings as array. + * + * Each array element contains two elements: + * - [0] => mixed $token + * - [1] => 2|1|0 + * + * - 2: REMOVED: $token was removed from $from + * - 1: ADDED: $token was added to $from + * - 0: OLD: $token is not changed in $to + * + * @param array|string $from + * @param array|string $to + * @param LongestCommonSubsequenceCalculator $lcs + */ + public function diffToArray($from, $to, ?LongestCommonSubsequenceCalculator $lcs = null): array + { + if (is_string($from)) { + $from = $this->splitStringByLines($from); + } elseif (!is_array($from)) { + throw new InvalidArgumentException('"from" must be an array or string.'); + } + + if (is_string($to)) { + $to = $this->splitStringByLines($to); + } elseif (!is_array($to)) { + throw new InvalidArgumentException('"to" must be an array or string.'); + } + + [$from, $to, $start, $end] = self::getArrayDiffParted($from, $to); + + if ($lcs === null) { + $lcs = $this->selectLcsImplementation($from, $to); + } + + $common = $lcs->calculate(array_values($from), array_values($to)); + $diff = []; + + foreach ($start as $token) { + $diff[] = [$token, self::OLD]; + } + + reset($from); + reset($to); + + foreach ($common as $token) { + while (($fromToken = reset($from)) !== $token) { + $diff[] = [array_shift($from), self::REMOVED]; + } + + while (($toToken = reset($to)) !== $token) { + $diff[] = [array_shift($to), self::ADDED]; + } + + $diff[] = [$token, self::OLD]; + + array_shift($from); + array_shift($to); + } + + while (($token = array_shift($from)) !== null) { + $diff[] = [$token, self::REMOVED]; + } + + while (($token = array_shift($to)) !== null) { + $diff[] = [$token, self::ADDED]; + } + + foreach ($end as $token) { + $diff[] = [$token, self::OLD]; + } + + if ($this->detectUnmatchedLineEndings($diff)) { + array_unshift($diff, ["#Warning: Strings contain different line endings!\n", self::DIFF_LINE_END_WARNING]); + } + + return $diff; + } + + /** + * Casts variable to string if it is not a string or array. + * + * @return array|string + */ + private function normalizeDiffInput($input) + { + if (!is_array($input) && !is_string($input)) { + return (string) $input; + } + + return $input; + } + + /** + * Checks if input is string, if so it will split it line-by-line. + */ + private function splitStringByLines(string $input): array + { + return preg_split('/(.*\R)/', $input, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + } + + private function selectLcsImplementation(array $from, array $to): LongestCommonSubsequenceCalculator + { + // We do not want to use the time-efficient implementation if its memory + // footprint will probably exceed this value. Note that the footprint + // calculation is only an estimation for the matrix and the LCS method + // will typically allocate a bit more memory than this. + $memoryLimit = 100 * 1024 * 1024; + + if ($this->calculateEstimatedFootprint($from, $to) > $memoryLimit) { + return new MemoryEfficientLongestCommonSubsequenceCalculator; + } + + return new TimeEfficientLongestCommonSubsequenceCalculator; + } + + /** + * Calculates the estimated memory footprint for the DP-based method. + * + * @return float|int + */ + private function calculateEstimatedFootprint(array $from, array $to) + { + $itemSize = PHP_INT_SIZE === 4 ? 76 : 144; + + return $itemSize * min(count($from), count($to)) ** 2; + } + + /** + * Returns true if line ends don't match in a diff. + */ + private function detectUnmatchedLineEndings(array $diff): bool + { + $newLineBreaks = ['' => true]; + $oldLineBreaks = ['' => true]; + + foreach ($diff as $entry) { + if (self::OLD === $entry[1]) { + $ln = $this->getLinebreak($entry[0]); + $oldLineBreaks[$ln] = true; + $newLineBreaks[$ln] = true; + } elseif (self::ADDED === $entry[1]) { + $newLineBreaks[$this->getLinebreak($entry[0])] = true; + } elseif (self::REMOVED === $entry[1]) { + $oldLineBreaks[$this->getLinebreak($entry[0])] = true; + } + } + + // if either input or output is a single line without breaks than no warning should be raised + if (['' => true] === $newLineBreaks || ['' => true] === $oldLineBreaks) { + return false; + } + + // two way compare + foreach ($newLineBreaks as $break => $set) { + if (!isset($oldLineBreaks[$break])) { + return true; + } + } + + foreach ($oldLineBreaks as $break => $set) { + if (!isset($newLineBreaks[$break])) { + return true; + } + } + + return false; + } + + private function getLinebreak($line): string + { + if (!is_string($line)) { + return ''; + } + + $lc = substr($line, -1); + + if ("\r" === $lc) { + return "\r"; + } + + if ("\n" !== $lc) { + return ''; + } + + if ("\r\n" === substr($line, -2)) { + return "\r\n"; + } + + return "\n"; + } + + private static function getArrayDiffParted(array &$from, array &$to): array + { + $start = []; + $end = []; + + reset($to); + + foreach ($from as $k => $v) { + $toK = key($to); + + if ($toK === $k && $v === $to[$k]) { + $start[$k] = $v; + + unset($from[$k], $to[$k]); + } else { + break; + } + } + + end($from); + end($to); + + do { + $fromK = key($from); + $toK = key($to); + + if (null === $fromK || null === $toK || current($from) !== current($to)) { + break; + } + + prev($from); + prev($to); + + $end = [$fromK => $from[$fromK]] + $end; + unset($from[$fromK], $to[$toK]); + } while (true); + + return [$from, $to, $start, $end]; + } +} diff --git a/vendor/sebastian/diff/src/Exception/ConfigurationException.php b/vendor/sebastian/diff/src/Exception/ConfigurationException.php new file mode 100644 index 00000000..8847a2e5 --- /dev/null +++ b/vendor/sebastian/diff/src/Exception/ConfigurationException.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Diff; + +use function get_class; +use function gettype; +use function is_object; +use function sprintf; +use Exception; + +final class ConfigurationException extends InvalidArgumentException +{ + public function __construct( + string $option, + string $expected, + $value, + int $code = 0, + ?Exception $previous = null + ) { + parent::__construct( + sprintf( + 'Option "%s" must be %s, got "%s".', + $option, + $expected, + is_object($value) ? get_class($value) : (null === $value ? '' : gettype($value) . '#' . $value) + ), + $code, + $previous + ); + } +} diff --git a/vendor/sebastian/diff/src/Exception/Exception.php b/vendor/sebastian/diff/src/Exception/Exception.php new file mode 100644 index 00000000..e20d3203 --- /dev/null +++ b/vendor/sebastian/diff/src/Exception/Exception.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Diff; + +use Throwable; + +interface Exception extends Throwable +{ +} diff --git a/vendor/sebastian/diff/src/Exception/InvalidArgumentException.php b/vendor/sebastian/diff/src/Exception/InvalidArgumentException.php new file mode 100644 index 00000000..846ac3fb --- /dev/null +++ b/vendor/sebastian/diff/src/Exception/InvalidArgumentException.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Diff; + +class InvalidArgumentException extends \InvalidArgumentException implements Exception +{ +} diff --git a/vendor/sebastian/diff/src/Line.php b/vendor/sebastian/diff/src/Line.php new file mode 100644 index 00000000..3596ed26 --- /dev/null +++ b/vendor/sebastian/diff/src/Line.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Diff; + +final class Line +{ + public const ADDED = 1; + + public const REMOVED = 2; + + public const UNCHANGED = 3; + + /** + * @var int + */ + private $type; + + /** + * @var string + */ + private $content; + + public function __construct(int $type = self::UNCHANGED, string $content = '') + { + $this->type = $type; + $this->content = $content; + } + + public function getContent(): string + { + return $this->content; + } + + public function getType(): int + { + return $this->type; + } +} diff --git a/vendor/sebastian/diff/src/LongestCommonSubsequenceCalculator.php b/vendor/sebastian/diff/src/LongestCommonSubsequenceCalculator.php new file mode 100644 index 00000000..dea8fe1c --- /dev/null +++ b/vendor/sebastian/diff/src/LongestCommonSubsequenceCalculator.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Diff; + +interface LongestCommonSubsequenceCalculator +{ + /** + * Calculates the longest common subsequence of two arrays. + */ + public function calculate(array $from, array $to): array; +} diff --git a/vendor/sebastian/diff/src/MemoryEfficientLongestCommonSubsequenceCalculator.php b/vendor/sebastian/diff/src/MemoryEfficientLongestCommonSubsequenceCalculator.php new file mode 100644 index 00000000..489113b6 --- /dev/null +++ b/vendor/sebastian/diff/src/MemoryEfficientLongestCommonSubsequenceCalculator.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Diff; + +use function array_fill; +use function array_merge; +use function array_reverse; +use function array_slice; +use function count; +use function in_array; +use function max; + +final class MemoryEfficientLongestCommonSubsequenceCalculator implements LongestCommonSubsequenceCalculator +{ + /** + * {@inheritdoc} + */ + public function calculate(array $from, array $to): array + { + $cFrom = count($from); + $cTo = count($to); + + if ($cFrom === 0) { + return []; + } + + if ($cFrom === 1) { + if (in_array($from[0], $to, true)) { + return [$from[0]]; + } + + return []; + } + + $i = (int) ($cFrom / 2); + $fromStart = array_slice($from, 0, $i); + $fromEnd = array_slice($from, $i); + $llB = $this->length($fromStart, $to); + $llE = $this->length(array_reverse($fromEnd), array_reverse($to)); + $jMax = 0; + $max = 0; + + for ($j = 0; $j <= $cTo; $j++) { + $m = $llB[$j] + $llE[$cTo - $j]; + + if ($m >= $max) { + $max = $m; + $jMax = $j; + } + } + + $toStart = array_slice($to, 0, $jMax); + $toEnd = array_slice($to, $jMax); + + return array_merge( + $this->calculate($fromStart, $toStart), + $this->calculate($fromEnd, $toEnd) + ); + } + + private function length(array $from, array $to): array + { + $current = array_fill(0, count($to) + 1, 0); + $cFrom = count($from); + $cTo = count($to); + + for ($i = 0; $i < $cFrom; $i++) { + $prev = $current; + + for ($j = 0; $j < $cTo; $j++) { + if ($from[$i] === $to[$j]) { + $current[$j + 1] = $prev[$j] + 1; + } else { + // don't use max() to avoid function call overhead + if ($current[$j] > $prev[$j + 1]) { + $current[$j + 1] = $current[$j]; + } else { + $current[$j + 1] = $prev[$j + 1]; + } + } + } + } + + return $current; + } +} diff --git a/vendor/sebastian/diff/src/Output/AbstractChunkOutputBuilder.php b/vendor/sebastian/diff/src/Output/AbstractChunkOutputBuilder.php new file mode 100644 index 00000000..e55757c3 --- /dev/null +++ b/vendor/sebastian/diff/src/Output/AbstractChunkOutputBuilder.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Diff\Output; + +use function count; + +abstract class AbstractChunkOutputBuilder implements DiffOutputBuilderInterface +{ + /** + * Takes input of the diff array and returns the common parts. + * Iterates through diff line by line. + */ + protected function getCommonChunks(array $diff, int $lineThreshold = 5): array + { + $diffSize = count($diff); + $capturing = false; + $chunkStart = 0; + $chunkSize = 0; + $commonChunks = []; + + for ($i = 0; $i < $diffSize; ++$i) { + if ($diff[$i][1] === 0 /* OLD */) { + if ($capturing === false) { + $capturing = true; + $chunkStart = $i; + $chunkSize = 0; + } else { + ++$chunkSize; + } + } elseif ($capturing !== false) { + if ($chunkSize >= $lineThreshold) { + $commonChunks[$chunkStart] = $chunkStart + $chunkSize; + } + + $capturing = false; + } + } + + if ($capturing !== false && $chunkSize >= $lineThreshold) { + $commonChunks[$chunkStart] = $chunkStart + $chunkSize; + } + + return $commonChunks; + } +} diff --git a/vendor/sebastian/diff/src/Output/DiffOnlyOutputBuilder.php b/vendor/sebastian/diff/src/Output/DiffOnlyOutputBuilder.php new file mode 100644 index 00000000..f79a935c --- /dev/null +++ b/vendor/sebastian/diff/src/Output/DiffOnlyOutputBuilder.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Diff\Output; + +use function fclose; +use function fopen; +use function fwrite; +use function stream_get_contents; +use function substr; +use SebastianBergmann\Diff\Differ; + +/** + * Builds a diff string representation in a loose unified diff format + * listing only changes lines. Does not include line numbers. + */ +final class DiffOnlyOutputBuilder implements DiffOutputBuilderInterface +{ + /** + * @var string + */ + private $header; + + public function __construct(string $header = "--- Original\n+++ New\n") + { + $this->header = $header; + } + + public function getDiff(array $diff): string + { + $buffer = fopen('php://memory', 'r+b'); + + if ('' !== $this->header) { + fwrite($buffer, $this->header); + + if ("\n" !== substr($this->header, -1, 1)) { + fwrite($buffer, "\n"); + } + } + + foreach ($diff as $diffEntry) { + if ($diffEntry[1] === Differ::ADDED) { + fwrite($buffer, '+' . $diffEntry[0]); + } elseif ($diffEntry[1] === Differ::REMOVED) { + fwrite($buffer, '-' . $diffEntry[0]); + } elseif ($diffEntry[1] === Differ::DIFF_LINE_END_WARNING) { + fwrite($buffer, ' ' . $diffEntry[0]); + + continue; // Warnings should not be tested for line break, it will always be there + } else { /* Not changed (old) 0 */ + continue; // we didn't write the non changs line, so do not add a line break either + } + + $lc = substr($diffEntry[0], -1); + + if ($lc !== "\n" && $lc !== "\r") { + fwrite($buffer, "\n"); // \No newline at end of file + } + } + + $diff = stream_get_contents($buffer, -1, 0); + fclose($buffer); + + return $diff; + } +} diff --git a/vendor/sebastian/diff/src/Output/DiffOutputBuilderInterface.php b/vendor/sebastian/diff/src/Output/DiffOutputBuilderInterface.php new file mode 100644 index 00000000..0e18f9f2 --- /dev/null +++ b/vendor/sebastian/diff/src/Output/DiffOutputBuilderInterface.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Diff\Output; + +/** + * Defines how an output builder should take a generated + * diff array and return a string representation of that diff. + */ +interface DiffOutputBuilderInterface +{ + public function getDiff(array $diff): string; +} diff --git a/vendor/sebastian/diff/src/Output/StrictUnifiedDiffOutputBuilder.php b/vendor/sebastian/diff/src/Output/StrictUnifiedDiffOutputBuilder.php new file mode 100644 index 00000000..9c55ab2a --- /dev/null +++ b/vendor/sebastian/diff/src/Output/StrictUnifiedDiffOutputBuilder.php @@ -0,0 +1,338 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Diff\Output; + +use function array_merge; +use function array_splice; +use function count; +use function fclose; +use function fopen; +use function fwrite; +use function is_bool; +use function is_int; +use function is_string; +use function max; +use function min; +use function sprintf; +use function stream_get_contents; +use function substr; +use SebastianBergmann\Diff\ConfigurationException; +use SebastianBergmann\Diff\Differ; + +/** + * Strict Unified diff output builder. + * + * Generates (strict) Unified diff's (unidiffs) with hunks. + */ +final class StrictUnifiedDiffOutputBuilder implements DiffOutputBuilderInterface +{ + private static $default = [ + 'collapseRanges' => true, // ranges of length one are rendered with the trailing `,1` + 'commonLineThreshold' => 6, // number of same lines before ending a new hunk and creating a new one (if needed) + 'contextLines' => 3, // like `diff: -u, -U NUM, --unified[=NUM]`, for patch/git apply compatibility best to keep at least @ 3 + 'fromFile' => null, + 'fromFileDate' => null, + 'toFile' => null, + 'toFileDate' => null, + ]; + + /** + * @var bool + */ + private $changed; + + /** + * @var bool + */ + private $collapseRanges; + + /** + * @var int >= 0 + */ + private $commonLineThreshold; + + /** + * @var string + */ + private $header; + + /** + * @var int >= 0 + */ + private $contextLines; + + public function __construct(array $options = []) + { + $options = array_merge(self::$default, $options); + + if (!is_bool($options['collapseRanges'])) { + throw new ConfigurationException('collapseRanges', 'a bool', $options['collapseRanges']); + } + + if (!is_int($options['contextLines']) || $options['contextLines'] < 0) { + throw new ConfigurationException('contextLines', 'an int >= 0', $options['contextLines']); + } + + if (!is_int($options['commonLineThreshold']) || $options['commonLineThreshold'] <= 0) { + throw new ConfigurationException('commonLineThreshold', 'an int > 0', $options['commonLineThreshold']); + } + + $this->assertString($options, 'fromFile'); + $this->assertString($options, 'toFile'); + $this->assertStringOrNull($options, 'fromFileDate'); + $this->assertStringOrNull($options, 'toFileDate'); + + $this->header = sprintf( + "--- %s%s\n+++ %s%s\n", + $options['fromFile'], + null === $options['fromFileDate'] ? '' : "\t" . $options['fromFileDate'], + $options['toFile'], + null === $options['toFileDate'] ? '' : "\t" . $options['toFileDate'] + ); + + $this->collapseRanges = $options['collapseRanges']; + $this->commonLineThreshold = $options['commonLineThreshold']; + $this->contextLines = $options['contextLines']; + } + + public function getDiff(array $diff): string + { + if (0 === count($diff)) { + return ''; + } + + $this->changed = false; + + $buffer = fopen('php://memory', 'r+b'); + fwrite($buffer, $this->header); + + $this->writeDiffHunks($buffer, $diff); + + if (!$this->changed) { + fclose($buffer); + + return ''; + } + + $diff = stream_get_contents($buffer, -1, 0); + + fclose($buffer); + + // If the last char is not a linebreak: add it. + // This might happen when both the `from` and `to` do not have a trailing linebreak + $last = substr($diff, -1); + + return "\n" !== $last && "\r" !== $last + ? $diff . "\n" + : $diff; + } + + private function writeDiffHunks($output, array $diff): void + { + // detect "No newline at end of file" and insert into `$diff` if needed + + $upperLimit = count($diff); + + if (0 === $diff[$upperLimit - 1][1]) { + $lc = substr($diff[$upperLimit - 1][0], -1); + + if ("\n" !== $lc) { + array_splice($diff, $upperLimit, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]); + } + } else { + // search back for the last `+` and `-` line, + // check if has trailing linebreak, else add under it warning under it + $toFind = [1 => true, 2 => true]; + + for ($i = $upperLimit - 1; $i >= 0; --$i) { + if (isset($toFind[$diff[$i][1]])) { + unset($toFind[$diff[$i][1]]); + $lc = substr($diff[$i][0], -1); + + if ("\n" !== $lc) { + array_splice($diff, $i + 1, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]); + } + + if (!count($toFind)) { + break; + } + } + } + } + + // write hunks to output buffer + + $cutOff = max($this->commonLineThreshold, $this->contextLines); + $hunkCapture = false; + $sameCount = $toRange = $fromRange = 0; + $toStart = $fromStart = 1; + $i = 0; + + /** @var int $i */ + foreach ($diff as $i => $entry) { + if (0 === $entry[1]) { // same + if (false === $hunkCapture) { + ++$fromStart; + ++$toStart; + + continue; + } + + ++$sameCount; + ++$toRange; + ++$fromRange; + + if ($sameCount === $cutOff) { + $contextStartOffset = ($hunkCapture - $this->contextLines) < 0 + ? $hunkCapture + : $this->contextLines; + + // note: $contextEndOffset = $this->contextLines; + // + // because we never go beyond the end of the diff. + // with the cutoff/contextlines here the follow is never true; + // + // if ($i - $cutOff + $this->contextLines + 1 > \count($diff)) { + // $contextEndOffset = count($diff) - 1; + // } + // + // ; that would be true for a trailing incomplete hunk case which is dealt with after this loop + + $this->writeHunk( + $diff, + $hunkCapture - $contextStartOffset, + $i - $cutOff + $this->contextLines + 1, + $fromStart - $contextStartOffset, + $fromRange - $cutOff + $contextStartOffset + $this->contextLines, + $toStart - $contextStartOffset, + $toRange - $cutOff + $contextStartOffset + $this->contextLines, + $output + ); + + $fromStart += $fromRange; + $toStart += $toRange; + + $hunkCapture = false; + $sameCount = $toRange = $fromRange = 0; + } + + continue; + } + + $sameCount = 0; + + if ($entry[1] === Differ::NO_LINE_END_EOF_WARNING) { + continue; + } + + $this->changed = true; + + if (false === $hunkCapture) { + $hunkCapture = $i; + } + + if (Differ::ADDED === $entry[1]) { // added + ++$toRange; + } + + if (Differ::REMOVED === $entry[1]) { // removed + ++$fromRange; + } + } + + if (false === $hunkCapture) { + return; + } + + // we end here when cutoff (commonLineThreshold) was not reached, but we where capturing a hunk, + // do not render hunk till end automatically because the number of context lines might be less than the commonLineThreshold + + $contextStartOffset = $hunkCapture - $this->contextLines < 0 + ? $hunkCapture + : $this->contextLines; + + // prevent trying to write out more common lines than there are in the diff _and_ + // do not write more than configured through the context lines + $contextEndOffset = min($sameCount, $this->contextLines); + + $fromRange -= $sameCount; + $toRange -= $sameCount; + + $this->writeHunk( + $diff, + $hunkCapture - $contextStartOffset, + $i - $sameCount + $contextEndOffset + 1, + $fromStart - $contextStartOffset, + $fromRange + $contextStartOffset + $contextEndOffset, + $toStart - $contextStartOffset, + $toRange + $contextStartOffset + $contextEndOffset, + $output + ); + } + + private function writeHunk( + array $diff, + int $diffStartIndex, + int $diffEndIndex, + int $fromStart, + int $fromRange, + int $toStart, + int $toRange, + $output + ): void { + fwrite($output, '@@ -' . $fromStart); + + if (!$this->collapseRanges || 1 !== $fromRange) { + fwrite($output, ',' . $fromRange); + } + + fwrite($output, ' +' . $toStart); + + if (!$this->collapseRanges || 1 !== $toRange) { + fwrite($output, ',' . $toRange); + } + + fwrite($output, " @@\n"); + + for ($i = $diffStartIndex; $i < $diffEndIndex; ++$i) { + if ($diff[$i][1] === Differ::ADDED) { + $this->changed = true; + fwrite($output, '+' . $diff[$i][0]); + } elseif ($diff[$i][1] === Differ::REMOVED) { + $this->changed = true; + fwrite($output, '-' . $diff[$i][0]); + } elseif ($diff[$i][1] === Differ::OLD) { + fwrite($output, ' ' . $diff[$i][0]); + } elseif ($diff[$i][1] === Differ::NO_LINE_END_EOF_WARNING) { + $this->changed = true; + fwrite($output, $diff[$i][0]); + } + //} elseif ($diff[$i][1] === Differ::DIFF_LINE_END_WARNING) { // custom comment inserted by PHPUnit/diff package + // skip + //} else { + // unknown/invalid + //} + } + } + + private function assertString(array $options, string $option): void + { + if (!is_string($options[$option])) { + throw new ConfigurationException($option, 'a string', $options[$option]); + } + } + + private function assertStringOrNull(array $options, string $option): void + { + if (null !== $options[$option] && !is_string($options[$option])) { + throw new ConfigurationException($option, 'a string or ', $options[$option]); + } + } +} diff --git a/vendor/sebastian/diff/src/Output/UnifiedDiffOutputBuilder.php b/vendor/sebastian/diff/src/Output/UnifiedDiffOutputBuilder.php new file mode 100644 index 00000000..8aae6450 --- /dev/null +++ b/vendor/sebastian/diff/src/Output/UnifiedDiffOutputBuilder.php @@ -0,0 +1,272 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Diff\Output; + +use function array_splice; +use function count; +use function fclose; +use function fopen; +use function fwrite; +use function max; +use function min; +use function stream_get_contents; +use function strlen; +use function substr; +use SebastianBergmann\Diff\Differ; + +/** + * Builds a diff string representation in unified diff format in chunks. + */ +final class UnifiedDiffOutputBuilder extends AbstractChunkOutputBuilder +{ + /** + * @var bool + */ + private $collapseRanges = true; + + /** + * @var int >= 0 + */ + private $commonLineThreshold = 6; + + /** + * @var int >= 0 + */ + private $contextLines = 3; + + /** + * @var string + */ + private $header; + + /** + * @var bool + */ + private $addLineNumbers; + + public function __construct(string $header = "--- Original\n+++ New\n", bool $addLineNumbers = false) + { + $this->header = $header; + $this->addLineNumbers = $addLineNumbers; + } + + public function getDiff(array $diff): string + { + $buffer = fopen('php://memory', 'r+b'); + + if ('' !== $this->header) { + fwrite($buffer, $this->header); + + if ("\n" !== substr($this->header, -1, 1)) { + fwrite($buffer, "\n"); + } + } + + if (0 !== count($diff)) { + $this->writeDiffHunks($buffer, $diff); + } + + $diff = stream_get_contents($buffer, -1, 0); + + fclose($buffer); + + // If the diff is non-empty and last char is not a linebreak: add it. + // This might happen when both the `from` and `to` do not have a trailing linebreak + $last = substr($diff, -1); + + return 0 !== strlen($diff) && "\n" !== $last && "\r" !== $last + ? $diff . "\n" + : $diff; + } + + private function writeDiffHunks($output, array $diff): void + { + // detect "No newline at end of file" and insert into `$diff` if needed + + $upperLimit = count($diff); + + if (0 === $diff[$upperLimit - 1][1]) { + $lc = substr($diff[$upperLimit - 1][0], -1); + + if ("\n" !== $lc) { + array_splice($diff, $upperLimit, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]); + } + } else { + // search back for the last `+` and `-` line, + // check if has trailing linebreak, else add under it warning under it + $toFind = [1 => true, 2 => true]; + + for ($i = $upperLimit - 1; $i >= 0; --$i) { + if (isset($toFind[$diff[$i][1]])) { + unset($toFind[$diff[$i][1]]); + $lc = substr($diff[$i][0], -1); + + if ("\n" !== $lc) { + array_splice($diff, $i + 1, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]); + } + + if (!count($toFind)) { + break; + } + } + } + } + + // write hunks to output buffer + + $cutOff = max($this->commonLineThreshold, $this->contextLines); + $hunkCapture = false; + $sameCount = $toRange = $fromRange = 0; + $toStart = $fromStart = 1; + $i = 0; + + /** @var int $i */ + foreach ($diff as $i => $entry) { + if (0 === $entry[1]) { // same + if (false === $hunkCapture) { + ++$fromStart; + ++$toStart; + + continue; + } + + ++$sameCount; + ++$toRange; + ++$fromRange; + + if ($sameCount === $cutOff) { + $contextStartOffset = ($hunkCapture - $this->contextLines) < 0 + ? $hunkCapture + : $this->contextLines; + + // note: $contextEndOffset = $this->contextLines; + // + // because we never go beyond the end of the diff. + // with the cutoff/contextlines here the follow is never true; + // + // if ($i - $cutOff + $this->contextLines + 1 > \count($diff)) { + // $contextEndOffset = count($diff) - 1; + // } + // + // ; that would be true for a trailing incomplete hunk case which is dealt with after this loop + + $this->writeHunk( + $diff, + $hunkCapture - $contextStartOffset, + $i - $cutOff + $this->contextLines + 1, + $fromStart - $contextStartOffset, + $fromRange - $cutOff + $contextStartOffset + $this->contextLines, + $toStart - $contextStartOffset, + $toRange - $cutOff + $contextStartOffset + $this->contextLines, + $output + ); + + $fromStart += $fromRange; + $toStart += $toRange; + + $hunkCapture = false; + $sameCount = $toRange = $fromRange = 0; + } + + continue; + } + + $sameCount = 0; + + if ($entry[1] === Differ::NO_LINE_END_EOF_WARNING) { + continue; + } + + if (false === $hunkCapture) { + $hunkCapture = $i; + } + + if (Differ::ADDED === $entry[1]) { + ++$toRange; + } + + if (Differ::REMOVED === $entry[1]) { + ++$fromRange; + } + } + + if (false === $hunkCapture) { + return; + } + + // we end here when cutoff (commonLineThreshold) was not reached, but we where capturing a hunk, + // do not render hunk till end automatically because the number of context lines might be less than the commonLineThreshold + + $contextStartOffset = $hunkCapture - $this->contextLines < 0 + ? $hunkCapture + : $this->contextLines; + + // prevent trying to write out more common lines than there are in the diff _and_ + // do not write more than configured through the context lines + $contextEndOffset = min($sameCount, $this->contextLines); + + $fromRange -= $sameCount; + $toRange -= $sameCount; + + $this->writeHunk( + $diff, + $hunkCapture - $contextStartOffset, + $i - $sameCount + $contextEndOffset + 1, + $fromStart - $contextStartOffset, + $fromRange + $contextStartOffset + $contextEndOffset, + $toStart - $contextStartOffset, + $toRange + $contextStartOffset + $contextEndOffset, + $output + ); + } + + private function writeHunk( + array $diff, + int $diffStartIndex, + int $diffEndIndex, + int $fromStart, + int $fromRange, + int $toStart, + int $toRange, + $output + ): void { + if ($this->addLineNumbers) { + fwrite($output, '@@ -' . $fromStart); + + if (!$this->collapseRanges || 1 !== $fromRange) { + fwrite($output, ',' . $fromRange); + } + + fwrite($output, ' +' . $toStart); + + if (!$this->collapseRanges || 1 !== $toRange) { + fwrite($output, ',' . $toRange); + } + + fwrite($output, " @@\n"); + } else { + fwrite($output, "@@ @@\n"); + } + + for ($i = $diffStartIndex; $i < $diffEndIndex; ++$i) { + if ($diff[$i][1] === Differ::ADDED) { + fwrite($output, '+' . $diff[$i][0]); + } elseif ($diff[$i][1] === Differ::REMOVED) { + fwrite($output, '-' . $diff[$i][0]); + } elseif ($diff[$i][1] === Differ::OLD) { + fwrite($output, ' ' . $diff[$i][0]); + } elseif ($diff[$i][1] === Differ::NO_LINE_END_EOF_WARNING) { + fwrite($output, "\n"); // $diff[$i][0] + } else { /* Not changed (old) Differ::OLD or Warning Differ::DIFF_LINE_END_WARNING */ + fwrite($output, ' ' . $diff[$i][0]); + } + } + } +} diff --git a/vendor/sebastian/diff/src/Parser.php b/vendor/sebastian/diff/src/Parser.php new file mode 100644 index 00000000..cc9e3887 --- /dev/null +++ b/vendor/sebastian/diff/src/Parser.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Diff; + +use function array_pop; +use function count; +use function max; +use function preg_match; +use function preg_split; + +/** + * Unified diff parser. + */ +final class Parser +{ + /** + * @return Diff[] + */ + public function parse(string $string): array + { + $lines = preg_split('(\r\n|\r|\n)', $string); + + if (!empty($lines) && $lines[count($lines) - 1] === '') { + array_pop($lines); + } + + $lineCount = count($lines); + $diffs = []; + $diff = null; + $collected = []; + + for ($i = 0; $i < $lineCount; ++$i) { + if (preg_match('#^---\h+"?(?P[^\\v\\t"]+)#', $lines[$i], $fromMatch) && + preg_match('#^\\+\\+\\+\\h+"?(?P[^\\v\\t"]+)#', $lines[$i + 1], $toMatch)) { + if ($diff !== null) { + $this->parseFileDiff($diff, $collected); + + $diffs[] = $diff; + $collected = []; + } + + $diff = new Diff($fromMatch['file'], $toMatch['file']); + + ++$i; + } else { + if (preg_match('/^(?:diff --git |index [\da-f\.]+|[+-]{3} [ab])/', $lines[$i])) { + continue; + } + + $collected[] = $lines[$i]; + } + } + + if ($diff !== null && count($collected)) { + $this->parseFileDiff($diff, $collected); + + $diffs[] = $diff; + } + + return $diffs; + } + + private function parseFileDiff(Diff $diff, array $lines): void + { + $chunks = []; + $chunk = null; + $diffLines = []; + + foreach ($lines as $line) { + if (preg_match('/^@@\s+-(?P\d+)(?:,\s*(?P\d+))?\s+\+(?P\d+)(?:,\s*(?P\d+))?\s+@@/', $line, $match)) { + $chunk = new Chunk( + (int) $match['start'], + isset($match['startrange']) ? max(1, (int) $match['startrange']) : 1, + (int) $match['end'], + isset($match['endrange']) ? max(1, (int) $match['endrange']) : 1 + ); + + $chunks[] = $chunk; + $diffLines = []; + + continue; + } + + if (preg_match('/^(?P[+ -])?(?P.*)/', $line, $match)) { + $type = Line::UNCHANGED; + + if ($match['type'] === '+') { + $type = Line::ADDED; + } elseif ($match['type'] === '-') { + $type = Line::REMOVED; + } + + $diffLines[] = new Line($type, $match['line']); + + if (null !== $chunk) { + $chunk->setLines($diffLines); + } + } + } + + $diff->setChunks($chunks); + } +} diff --git a/vendor/sebastian/diff/src/TimeEfficientLongestCommonSubsequenceCalculator.php b/vendor/sebastian/diff/src/TimeEfficientLongestCommonSubsequenceCalculator.php new file mode 100644 index 00000000..4e8d951d --- /dev/null +++ b/vendor/sebastian/diff/src/TimeEfficientLongestCommonSubsequenceCalculator.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Diff; + +use function array_reverse; +use function count; +use function max; +use SplFixedArray; + +final class TimeEfficientLongestCommonSubsequenceCalculator implements LongestCommonSubsequenceCalculator +{ + /** + * {@inheritdoc} + */ + public function calculate(array $from, array $to): array + { + $common = []; + $fromLength = count($from); + $toLength = count($to); + $width = $fromLength + 1; + $matrix = new SplFixedArray($width * ($toLength + 1)); + + for ($i = 0; $i <= $fromLength; ++$i) { + $matrix[$i] = 0; + } + + for ($j = 0; $j <= $toLength; ++$j) { + $matrix[$j * $width] = 0; + } + + for ($i = 1; $i <= $fromLength; ++$i) { + for ($j = 1; $j <= $toLength; ++$j) { + $o = ($j * $width) + $i; + + // don't use max() to avoid function call overhead + $firstOrLast = $from[$i - 1] === $to[$j - 1] ? $matrix[$o - $width - 1] + 1 : 0; + + if ($matrix[$o - 1] > $matrix[$o - $width]) { + if ($firstOrLast > $matrix[$o - 1]) { + $matrix[$o] = $firstOrLast; + } else { + $matrix[$o] = $matrix[$o - 1]; + } + } else { + if ($firstOrLast > $matrix[$o - $width]) { + $matrix[$o] = $firstOrLast; + } else { + $matrix[$o] = $matrix[$o - $width]; + } + } + } + } + + $i = $fromLength; + $j = $toLength; + + while ($i > 0 && $j > 0) { + if ($from[$i - 1] === $to[$j - 1]) { + $common[] = $from[$i - 1]; + --$i; + --$j; + } else { + $o = ($j * $width) + $i; + + if ($matrix[$o - $width] > $matrix[$o - 1]) { + --$j; + } else { + --$i; + } + } + } + + return array_reverse($common); + } +} diff --git a/vendor/sebastian/environment/ChangeLog.md b/vendor/sebastian/environment/ChangeLog.md new file mode 100644 index 00000000..07365951 --- /dev/null +++ b/vendor/sebastian/environment/ChangeLog.md @@ -0,0 +1,183 @@ +# Changes in sebastianbergmann/environment + +All notable changes in `sebastianbergmann/environment` are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. + +## [5.1.5] - 2023-02-03 + +### Fixed + +* [#59](https://github.com/sebastianbergmann/environment/issues/59): Wrong usage of `stream_isatty()`, `fstat()` used without checking whether the function is available + +## [5.1.4] - 2022-04-03 + +### Fixed + +* [#63](https://github.com/sebastianbergmann/environment/pull/63): `Runtime::getCurrentSettings()` does not correctly process INI settings + +## [5.1.3] - 2020-09-28 + +### Changed + +* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3` + +## [5.1.2] - 2020-06-26 + +### Added + +* This component is now supported on PHP 8 + +## [5.1.1] - 2020-06-15 + +### Changed + +* Tests etc. are now ignored for archive exports + +## [5.1.0] - 2020-04-14 + +### Added + +* `Runtime::performsJustInTimeCompilation()` returns `true` if PHP 8's JIT is active, `false` otherwise + +## [5.0.2] - 2020-03-31 + +### Fixed + +* [#55](https://github.com/sebastianbergmann/environment/issues/55): `stty` command is executed even if no tty is available + +## [5.0.1] - 2020-02-19 + +### Changed + +* `Runtime::getNameWithVersionAndCodeCoverageDriver()` now prioritizes PCOV over Xdebug when both extensions are loaded (just like php-code-coverage does) + +## [5.0.0] - 2020-02-07 + +### Removed + +* This component is no longer supported on PHP 7.1 and PHP 7.2 + +## [4.2.3] - 2019-11-20 + +### Changed + +* [#50](https://github.com/sebastianbergmann/environment/pull/50): Windows improvements to console capabilities + +### Fixed + +* [#49](https://github.com/sebastianbergmann/environment/issues/49): Detection how OpCache handles docblocks does not work correctly when PHPDBG is used + +## [4.2.2] - 2019-05-05 + +### Fixed + +* [#44](https://github.com/sebastianbergmann/environment/pull/44): `TypeError` in `Console::getNumberOfColumnsInteractive()` + +## [4.2.1] - 2019-04-25 + +### Fixed + +* Fixed an issue in `Runtime::getCurrentSettings()` + +## [4.2.0] - 2019-04-25 + +### Added + +* [#36](https://github.com/sebastianbergmann/environment/pull/36): `Runtime::getCurrentSettings()` + +## [4.1.0] - 2019-02-01 + +### Added + +* Implemented `Runtime::getNameWithVersionAndCodeCoverageDriver()` method +* [#34](https://github.com/sebastianbergmann/environment/pull/34): Support for PCOV extension + +## [4.0.2] - 2019-01-28 + +### Fixed + +* [#33](https://github.com/sebastianbergmann/environment/issues/33): `Runtime::discardsComments()` returns true too eagerly + +### Removed + +* Removed support for Zend Optimizer+ in `Runtime::discardsComments()` + +## [4.0.1] - 2018-11-25 + +### Fixed + +* [#31](https://github.com/sebastianbergmann/environment/issues/31): Regressions in `Console` class + +## [4.0.0] - 2018-10-23 [YANKED] + +### Fixed + +* [#25](https://github.com/sebastianbergmann/environment/pull/25): `Console::hasColorSupport()` does not work on Windows + +### Removed + +* This component is no longer supported on PHP 7.0 + +## [3.1.0] - 2017-07-01 + +### Added + +* [#21](https://github.com/sebastianbergmann/environment/issues/21): Equivalent of `PHP_OS_FAMILY` (for PHP < 7.2) + +## [3.0.4] - 2017-06-20 + +### Fixed + +* [#20](https://github.com/sebastianbergmann/environment/pull/20): PHP 7 mode of HHVM not forced + +## [3.0.3] - 2017-05-18 + +### Fixed + +* [#18](https://github.com/sebastianbergmann/environment/issues/18): `Uncaught TypeError: preg_match() expects parameter 2 to be string, null given` + +## [3.0.2] - 2017-04-21 + +### Fixed + +* [#17](https://github.com/sebastianbergmann/environment/issues/17): `Uncaught TypeError: trim() expects parameter 1 to be string, boolean given` + +## [3.0.1] - 2017-04-21 + +### Fixed + +* Fixed inverted logic in `Runtime::discardsComments()` + +## [3.0.0] - 2017-04-21 + +### Added + +* Implemented `Runtime::discardsComments()` for querying whether the PHP runtime discards annotations + +### Removed + +* This component is no longer supported on PHP 5.6 + +[5.1.5]: https://github.com/sebastianbergmann/environment/compare/5.1.4...5.1.5 +[5.1.4]: https://github.com/sebastianbergmann/environment/compare/5.1.3...5.1.4 +[5.1.3]: https://github.com/sebastianbergmann/environment/compare/5.1.2...5.1.3 +[5.1.2]: https://github.com/sebastianbergmann/environment/compare/5.1.1...5.1.2 +[5.1.1]: https://github.com/sebastianbergmann/environment/compare/5.1.0...5.1.1 +[5.1.0]: https://github.com/sebastianbergmann/environment/compare/5.0.2...5.1.0 +[5.0.2]: https://github.com/sebastianbergmann/environment/compare/5.0.1...5.0.2 +[5.0.1]: https://github.com/sebastianbergmann/environment/compare/5.0.0...5.0.1 +[5.0.0]: https://github.com/sebastianbergmann/environment/compare/4.2.3...5.0.0 +[4.2.3]: https://github.com/sebastianbergmann/environment/compare/4.2.2...4.2.3 +[4.2.2]: https://github.com/sebastianbergmann/environment/compare/4.2.1...4.2.2 +[4.2.1]: https://github.com/sebastianbergmann/environment/compare/4.2.0...4.2.1 +[4.2.0]: https://github.com/sebastianbergmann/environment/compare/4.1.0...4.2.0 +[4.1.0]: https://github.com/sebastianbergmann/environment/compare/4.0.2...4.1.0 +[4.0.2]: https://github.com/sebastianbergmann/environment/compare/4.0.1...4.0.2 +[4.0.1]: https://github.com/sebastianbergmann/environment/compare/66691f8e2dc4641909166b275a9a4f45c0e89092...4.0.1 +[4.0.0]: https://github.com/sebastianbergmann/environment/compare/3.1.0...66691f8e2dc4641909166b275a9a4f45c0e89092 +[3.1.0]: https://github.com/sebastianbergmann/environment/compare/3.0...3.1.0 +[3.0.4]: https://github.com/sebastianbergmann/environment/compare/3.0.3...3.0.4 +[3.0.3]: https://github.com/sebastianbergmann/environment/compare/3.0.2...3.0.3 +[3.0.2]: https://github.com/sebastianbergmann/environment/compare/3.0.1...3.0.2 +[3.0.1]: https://github.com/sebastianbergmann/environment/compare/3.0.0...3.0.1 +[3.0.0]: https://github.com/sebastianbergmann/environment/compare/2.0...3.0.0 + diff --git a/vendor/sebastian/environment/LICENSE b/vendor/sebastian/environment/LICENSE new file mode 100644 index 00000000..42546339 --- /dev/null +++ b/vendor/sebastian/environment/LICENSE @@ -0,0 +1,33 @@ +sebastian/environment + +Copyright (c) 2014-2022, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/sebastian/environment/README.md b/vendor/sebastian/environment/README.md new file mode 100644 index 00000000..1fead13e --- /dev/null +++ b/vendor/sebastian/environment/README.md @@ -0,0 +1,21 @@ +# sebastian/environment + +[![Latest Stable Version](https://img.shields.io/packagist/v/sebastian/environment.svg?style=flat-square)](https://packagist.org/packages/sebastian/environment) +[![CI Status](https://github.com/sebastianbergmann/environment/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/environment/actions) +[![Type Coverage](https://shepherd.dev/github/sebastianbergmann/environment/coverage.svg)](https://shepherd.dev/github/sebastianbergmann/environment) + +This component provides functionality that helps writing PHP code that has runtime-specific (PHP / HHVM) execution paths. + +## Installation + +You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): + +``` +composer require sebastian/environment +``` + +If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency: + +``` +composer require --dev sebastian/environment +``` diff --git a/vendor/sebastian/environment/composer.json b/vendor/sebastian/environment/composer.json new file mode 100644 index 00000000..d50dcfd7 --- /dev/null +++ b/vendor/sebastian/environment/composer.json @@ -0,0 +1,40 @@ +{ + "name": "sebastian/environment", + "description": "Provides functionality to handle HHVM/PHP environments", + "keywords": ["environment","hhvm","xdebug"], + "homepage": "http://www.github.com/sebastianbergmann/environment", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "config": { + "platform": { + "php": "7.3.0" + }, + "optimize-autoloader": true, + "sort-packages": true + }, + "prefer-stable": true, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + } +} diff --git a/vendor/sebastian/environment/src/Console.php b/vendor/sebastian/environment/src/Console.php new file mode 100644 index 00000000..180eb60b --- /dev/null +++ b/vendor/sebastian/environment/src/Console.php @@ -0,0 +1,187 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Environment; + +use const DIRECTORY_SEPARATOR; +use const STDIN; +use const STDOUT; +use function defined; +use function fclose; +use function fstat; +use function function_exists; +use function getenv; +use function is_resource; +use function is_string; +use function posix_isatty; +use function preg_match; +use function proc_close; +use function proc_open; +use function sapi_windows_vt100_support; +use function shell_exec; +use function stream_get_contents; +use function stream_isatty; +use function trim; + +final class Console +{ + /** + * @var int + */ + public const STDIN = 0; + + /** + * @var int + */ + public const STDOUT = 1; + + /** + * @var int + */ + public const STDERR = 2; + + /** + * Returns true if STDOUT supports colorization. + * + * This code has been copied and adapted from + * Symfony\Component\Console\Output\StreamOutput. + */ + public function hasColorSupport(): bool + { + if ('Hyper' === getenv('TERM_PROGRAM')) { + return true; + } + + if ($this->isWindows()) { + // @codeCoverageIgnoreStart + return (defined('STDOUT') && function_exists('sapi_windows_vt100_support') && @sapi_windows_vt100_support(STDOUT)) || + false !== getenv('ANSICON') || + 'ON' === getenv('ConEmuANSI') || + 'xterm' === getenv('TERM'); + // @codeCoverageIgnoreEnd + } + + if (!defined('STDOUT')) { + // @codeCoverageIgnoreStart + return false; + // @codeCoverageIgnoreEnd + } + + return $this->isInteractive(STDOUT); + } + + /** + * Returns the number of columns of the terminal. + * + * @codeCoverageIgnore + */ + public function getNumberOfColumns(): int + { + if (!$this->isInteractive(defined('STDIN') ? STDIN : self::STDIN)) { + return 80; + } + + if ($this->isWindows()) { + return $this->getNumberOfColumnsWindows(); + } + + return $this->getNumberOfColumnsInteractive(); + } + + /** + * Returns if the file descriptor is an interactive terminal or not. + * + * Normally, we want to use a resource as a parameter, yet sadly it's not always awailable, + * eg when running code in interactive console (`php -a`), STDIN/STDOUT/STDERR constants are not defined. + * + * @param int|resource $fileDescriptor + */ + public function isInteractive($fileDescriptor = self::STDOUT): bool + { + if (is_resource($fileDescriptor)) { + if (function_exists('stream_isatty') && @stream_isatty($fileDescriptor)) { + return true; + } + + if (function_exists('fstat')) { + $stat = @fstat(STDOUT); + + return $stat && 0020000 === ($stat['mode'] & 0170000); + } + + return false; + } + + return function_exists('posix_isatty') && @posix_isatty($fileDescriptor); + } + + private function isWindows(): bool + { + return DIRECTORY_SEPARATOR === '\\'; + } + + /** + * @codeCoverageIgnore + */ + private function getNumberOfColumnsInteractive(): int + { + if (function_exists('shell_exec') && preg_match('#\d+ (\d+)#', shell_exec('stty size') ?: '', $match) === 1) { + if ((int) $match[1] > 0) { + return (int) $match[1]; + } + } + + if (function_exists('shell_exec') && preg_match('#columns = (\d+);#', shell_exec('stty') ?: '', $match) === 1) { + if ((int) $match[1] > 0) { + return (int) $match[1]; + } + } + + return 80; + } + + /** + * @codeCoverageIgnore + */ + private function getNumberOfColumnsWindows(): int + { + $ansicon = getenv('ANSICON'); + $columns = 80; + + if (is_string($ansicon) && preg_match('/^(\d+)x\d+ \(\d+x(\d+)\)$/', trim($ansicon), $matches)) { + $columns = (int) $matches[1]; + } elseif (function_exists('proc_open')) { + $process = proc_open( + 'mode CON', + [ + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], + ], + $pipes, + null, + null, + ['suppress_errors' => true] + ); + + if (is_resource($process)) { + $info = stream_get_contents($pipes[1]); + + fclose($pipes[1]); + fclose($pipes[2]); + proc_close($process); + + if (preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) { + $columns = (int) $matches[2]; + } + } + } + + return $columns - 1; + } +} diff --git a/vendor/sebastian/environment/src/OperatingSystem.php b/vendor/sebastian/environment/src/OperatingSystem.php new file mode 100644 index 00000000..1f3ebca7 --- /dev/null +++ b/vendor/sebastian/environment/src/OperatingSystem.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Environment; + +use const DIRECTORY_SEPARATOR; +use const PHP_OS; +use const PHP_OS_FAMILY; +use function defined; + +final class OperatingSystem +{ + /** + * Returns PHP_OS_FAMILY (if defined (which it is on PHP >= 7.2)). + * Returns a string (compatible with PHP_OS_FAMILY) derived from PHP_OS otherwise. + */ + public function getFamily(): string + { + if (defined('PHP_OS_FAMILY')) { + return PHP_OS_FAMILY; + } + + if (DIRECTORY_SEPARATOR === '\\') { + return 'Windows'; + } + + switch (PHP_OS) { + case 'Darwin': + return 'Darwin'; + + case 'DragonFly': + case 'FreeBSD': + case 'NetBSD': + case 'OpenBSD': + return 'BSD'; + + case 'Linux': + return 'Linux'; + + case 'SunOS': + return 'Solaris'; + + default: + return 'Unknown'; + } + } +} diff --git a/vendor/sebastian/environment/src/Runtime.php b/vendor/sebastian/environment/src/Runtime.php new file mode 100644 index 00000000..d1b92d62 --- /dev/null +++ b/vendor/sebastian/environment/src/Runtime.php @@ -0,0 +1,321 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Environment; + +use const PHP_BINARY; +use const PHP_BINDIR; +use const PHP_MAJOR_VERSION; +use const PHP_SAPI; +use const PHP_VERSION; +use function array_map; +use function array_merge; +use function defined; +use function escapeshellarg; +use function explode; +use function extension_loaded; +use function getenv; +use function ini_get; +use function is_readable; +use function parse_ini_file; +use function php_ini_loaded_file; +use function php_ini_scanned_files; +use function phpversion; +use function sprintf; +use function strpos; + +/** + * Utility class for HHVM/PHP environment handling. + */ +final class Runtime +{ + /** + * @var string + */ + private static $binary; + + /** + * Returns true when Xdebug or PCOV is available or + * the runtime used is PHPDBG. + */ + public function canCollectCodeCoverage(): bool + { + return $this->hasXdebug() || $this->hasPCOV() || $this->hasPHPDBGCodeCoverage(); + } + + /** + * Returns true when Zend OPcache is loaded, enabled, + * and is configured to discard comments. + */ + public function discardsComments(): bool + { + if (!$this->isOpcacheActive()) { + return false; + } + + if (ini_get('opcache.save_comments') !== '0') { + return false; + } + + return true; + } + + /** + * Returns true when Zend OPcache is loaded, enabled, + * and is configured to perform just-in-time compilation. + */ + public function performsJustInTimeCompilation(): bool + { + if (PHP_MAJOR_VERSION < 8) { + return false; + } + + if (!$this->isOpcacheActive()) { + return false; + } + + if (strpos(ini_get('opcache.jit'), '0') === 0) { + return false; + } + + return true; + } + + /** + * Returns the path to the binary of the current runtime. + * Appends ' --php' to the path when the runtime is HHVM. + */ + public function getBinary(): string + { + // HHVM + if (self::$binary === null && $this->isHHVM()) { + // @codeCoverageIgnoreStart + if ((self::$binary = getenv('PHP_BINARY')) === false) { + self::$binary = PHP_BINARY; + } + + self::$binary = escapeshellarg(self::$binary) . ' --php' . + ' -d hhvm.php7.all=1'; + // @codeCoverageIgnoreEnd + } + + if (self::$binary === null && PHP_BINARY !== '') { + self::$binary = escapeshellarg(PHP_BINARY); + } + + if (self::$binary === null) { + // @codeCoverageIgnoreStart + $possibleBinaryLocations = [ + PHP_BINDIR . '/php', + PHP_BINDIR . '/php-cli.exe', + PHP_BINDIR . '/php.exe', + ]; + + foreach ($possibleBinaryLocations as $binary) { + if (is_readable($binary)) { + self::$binary = escapeshellarg($binary); + + break; + } + } + // @codeCoverageIgnoreEnd + } + + if (self::$binary === null) { + // @codeCoverageIgnoreStart + self::$binary = 'php'; + // @codeCoverageIgnoreEnd + } + + return self::$binary; + } + + public function getNameWithVersion(): string + { + return $this->getName() . ' ' . $this->getVersion(); + } + + public function getNameWithVersionAndCodeCoverageDriver(): string + { + if (!$this->canCollectCodeCoverage() || $this->hasPHPDBGCodeCoverage()) { + return $this->getNameWithVersion(); + } + + if ($this->hasPCOV()) { + return sprintf( + '%s with PCOV %s', + $this->getNameWithVersion(), + phpversion('pcov') + ); + } + + if ($this->hasXdebug()) { + return sprintf( + '%s with Xdebug %s', + $this->getNameWithVersion(), + phpversion('xdebug') + ); + } + } + + public function getName(): string + { + if ($this->isHHVM()) { + // @codeCoverageIgnoreStart + return 'HHVM'; + // @codeCoverageIgnoreEnd + } + + if ($this->isPHPDBG()) { + // @codeCoverageIgnoreStart + return 'PHPDBG'; + // @codeCoverageIgnoreEnd + } + + return 'PHP'; + } + + public function getVendorUrl(): string + { + if ($this->isHHVM()) { + // @codeCoverageIgnoreStart + return 'http://hhvm.com/'; + // @codeCoverageIgnoreEnd + } + + return 'https://secure.php.net/'; + } + + public function getVersion(): string + { + if ($this->isHHVM()) { + // @codeCoverageIgnoreStart + return HHVM_VERSION; + // @codeCoverageIgnoreEnd + } + + return PHP_VERSION; + } + + /** + * Returns true when the runtime used is PHP and Xdebug is loaded. + */ + public function hasXdebug(): bool + { + return ($this->isPHP() || $this->isHHVM()) && extension_loaded('xdebug'); + } + + /** + * Returns true when the runtime used is HHVM. + */ + public function isHHVM(): bool + { + return defined('HHVM_VERSION'); + } + + /** + * Returns true when the runtime used is PHP without the PHPDBG SAPI. + */ + public function isPHP(): bool + { + return !$this->isHHVM() && !$this->isPHPDBG(); + } + + /** + * Returns true when the runtime used is PHP with the PHPDBG SAPI. + */ + public function isPHPDBG(): bool + { + return PHP_SAPI === 'phpdbg' && !$this->isHHVM(); + } + + /** + * Returns true when the runtime used is PHP with the PHPDBG SAPI + * and the phpdbg_*_oplog() functions are available (PHP >= 7.0). + */ + public function hasPHPDBGCodeCoverage(): bool + { + return $this->isPHPDBG(); + } + + /** + * Returns true when the runtime used is PHP with PCOV loaded and enabled. + */ + public function hasPCOV(): bool + { + return $this->isPHP() && extension_loaded('pcov') && ini_get('pcov.enabled'); + } + + /** + * Parses the loaded php.ini file (if any) as well as all + * additional php.ini files from the additional ini dir for + * a list of all configuration settings loaded from files + * at startup. Then checks for each php.ini setting passed + * via the `$values` parameter whether this setting has + * been changed at runtime. Returns an array of strings + * where each string has the format `key=value` denoting + * the name of a changed php.ini setting with its new value. + * + * @return string[] + */ + public function getCurrentSettings(array $values): array + { + $diff = []; + $files = []; + + if ($file = php_ini_loaded_file()) { + $files[] = $file; + } + + if ($scanned = php_ini_scanned_files()) { + $files = array_merge( + $files, + array_map( + 'trim', + explode(",\n", $scanned) + ) + ); + } + + foreach ($files as $ini) { + $config = parse_ini_file($ini, true); + + foreach ($values as $value) { + $set = ini_get($value); + + if (empty($set)) { + continue; + } + + if ((!isset($config[$value]) || ($set !== $config[$value]))) { + $diff[$value] = sprintf('%s=%s', $value, $set); + } + } + } + + return $diff; + } + + private function isOpcacheActive(): bool + { + if (!extension_loaded('Zend OPcache')) { + return false; + } + + if ((PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') && ini_get('opcache.enable_cli') === '1') { + return true; + } + + if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg' && ini_get('opcache.enable') === '1') { + return true; + } + + return false; + } +} diff --git a/vendor/sebastian/exporter/ChangeLog.md b/vendor/sebastian/exporter/ChangeLog.md new file mode 100644 index 00000000..32ab7051 --- /dev/null +++ b/vendor/sebastian/exporter/ChangeLog.md @@ -0,0 +1,85 @@ +# ChangeLog + +All notable changes are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles. + +## [4.0.6] - 2024-03-02 + +### Changed + +* Do not use implicitly nullable parameters + +## [4.0.5] - 2022-09-14 + +### Fixed + +* [#47](https://github.com/sebastianbergmann/exporter/pull/47): Fix `float` export precision + +## [4.0.4] - 2021-11-11 + +### Changed + +* [#37](https://github.com/sebastianbergmann/exporter/pull/37): Improve export of closed resources + +## [4.0.3] - 2020-09-28 + +### Changed + +* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3` + +## [4.0.2] - 2020-06-26 + +### Added + +* This component is now supported on PHP 8 + +## [4.0.1] - 2020-06-15 + +### Changed + +* Tests etc. are now ignored for archive exports + +## [4.0.0] - 2020-02-07 + +### Removed + +* This component is no longer supported on PHP 7.0, PHP 7.1, and PHP 7.2 + +## [3.1.5] - 2022-09-14 + +### Fixed + +* [#47](https://github.com/sebastianbergmann/exporter/pull/47): Fix `float` export precision + +## [3.1.4] - 2021-11-11 + +### Changed + +* [#38](https://github.com/sebastianbergmann/exporter/pull/38): Improve export of closed resources + +## [3.1.3] - 2020-11-30 + +### Changed + +* Changed PHP version constraint in `composer.json` from `^7.0` to `>=7.0` + +## [3.1.2] - 2019-09-14 + +### Fixed + +* [#29](https://github.com/sebastianbergmann/exporter/pull/29): Second parameter for `str_repeat()` must be an integer + +### Removed + +* Remove HHVM-specific code that is no longer needed + +[4.0.6]: https://github.com/sebastianbergmann/exporter/compare/4.0.5...4.0.6 +[4.0.5]: https://github.com/sebastianbergmann/exporter/compare/4.0.4...4.0.5 +[4.0.4]: https://github.com/sebastianbergmann/exporter/compare/4.0.3...4.0.4 +[4.0.3]: https://github.com/sebastianbergmann/exporter/compare/4.0.2...4.0.3 +[4.0.2]: https://github.com/sebastianbergmann/exporter/compare/4.0.1...4.0.2 +[4.0.1]: https://github.com/sebastianbergmann/exporter/compare/4.0.0...4.0.1 +[4.0.0]: https://github.com/sebastianbergmann/exporter/compare/3.1.2...4.0.0 +[3.1.5]: https://github.com/sebastianbergmann/exporter/compare/3.1.4...3.1.5 +[3.1.4]: https://github.com/sebastianbergmann/exporter/compare/3.1.3...3.1.4 +[3.1.3]: https://github.com/sebastianbergmann/exporter/compare/3.1.2...3.1.3 +[3.1.2]: https://github.com/sebastianbergmann/exporter/compare/3.1.1...3.1.2 diff --git a/vendor/sebastian/exporter/LICENSE b/vendor/sebastian/exporter/LICENSE new file mode 100644 index 00000000..26dc7fec --- /dev/null +++ b/vendor/sebastian/exporter/LICENSE @@ -0,0 +1,33 @@ +Exporter + +Copyright (c) 2002-2021, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/sebastian/exporter/README.md b/vendor/sebastian/exporter/README.md new file mode 100644 index 00000000..ed8719f5 --- /dev/null +++ b/vendor/sebastian/exporter/README.md @@ -0,0 +1,174 @@ +# sebastian/exporter + +[![CI Status](https://github.com/sebastianbergmann/exporter/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/exporter/actions) +[![Type Coverage](https://shepherd.dev/github/sebastianbergmann/exporter/coverage.svg)](https://shepherd.dev/github/sebastianbergmann/exporter) + +This component provides the functionality to export PHP variables for visualization. + +## Installation + +You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): + +``` +composer require sebastian/exporter +``` + +If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency: + +``` +composer require --dev sebastian/exporter +``` + +## Usage + +Exporting: + +```php + '' + 'string' => '' + 'code' => 0 + 'file' => '/home/sebastianbergmann/test.php' + 'line' => 34 + 'previous' => null +) +*/ + +print $exporter->export(new Exception); +``` + +## Data Types + +Exporting simple types: + +```php +export(46); + +// 4.0 +print $exporter->export(4.0); + +// 'hello, world!' +print $exporter->export('hello, world!'); + +// false +print $exporter->export(false); + +// NAN +print $exporter->export(acos(8)); + +// -INF +print $exporter->export(log(0)); + +// null +print $exporter->export(null); + +// resource(13) of type (stream) +print $exporter->export(fopen('php://stderr', 'w')); + +// Binary String: 0x000102030405 +print $exporter->export(chr(0) . chr(1) . chr(2) . chr(3) . chr(4) . chr(5)); +``` + +Exporting complex types: + +```php + Array &1 ( + 0 => 1 + 1 => 2 + 2 => 3 + ) + 1 => Array &2 ( + 0 => '' + 1 => 0 + 2 => false + ) +) +*/ + +print $exporter->export(array(array(1,2,3), array("",0,FALSE))); + +/* +Array &0 ( + 'self' => Array &1 ( + 'self' => Array &1 + ) +) +*/ + +$array = array(); +$array['self'] = &$array; +print $exporter->export($array); + +/* +stdClass Object &0000000003a66dcc0000000025e723e2 ( + 'self' => stdClass Object &0000000003a66dcc0000000025e723e2 +) +*/ + +$obj = new stdClass(); +$obj->self = $obj; +print $exporter->export($obj); +``` + +Compact exports: + +```php +shortenedExport(array()); + +// Array (...) +print $exporter->shortenedExport(array(1,2,3,4,5)); + +// stdClass Object () +print $exporter->shortenedExport(new stdClass); + +// Exception Object (...) +print $exporter->shortenedExport(new Exception); + +// this\nis\na\nsuper\nlong\nstring\nt...\nspace +print $exporter->shortenedExport( +<<=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "ext-mbstring": "*" + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + } +} + diff --git a/vendor/sebastian/exporter/src/Exporter.php b/vendor/sebastian/exporter/src/Exporter.php new file mode 100644 index 00000000..5d85b13a --- /dev/null +++ b/vendor/sebastian/exporter/src/Exporter.php @@ -0,0 +1,346 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Exporter; + +use function bin2hex; +use function count; +use function function_exists; +use function get_class; +use function get_resource_type; +use function gettype; +use function implode; +use function ini_get; +use function ini_set; +use function is_array; +use function is_float; +use function is_object; +use function is_resource; +use function is_string; +use function mb_strlen; +use function mb_substr; +use function preg_match; +use function spl_object_hash; +use function sprintf; +use function str_repeat; +use function str_replace; +use function strlen; +use function substr; +use function var_export; +use SebastianBergmann\RecursionContext\Context; +use SplObjectStorage; + +/** + * A nifty utility for visualizing PHP variables. + * + * + * export(new Exception); + * + */ +class Exporter +{ + /** + * Exports a value as a string. + * + * The output of this method is similar to the output of print_r(), but + * improved in various aspects: + * + * - NULL is rendered as "null" (instead of "") + * - TRUE is rendered as "true" (instead of "1") + * - FALSE is rendered as "false" (instead of "") + * - Strings are always quoted with single quotes + * - Carriage returns and newlines are normalized to \n + * - Recursion and repeated rendering is treated properly + * + * @param int $indentation The indentation level of the 2nd+ line + * + * @return string + */ + public function export($value, $indentation = 0) + { + return $this->recursiveExport($value, $indentation); + } + + /** + * @param array $data + * @param Context $context + * + * @return string + */ + public function shortenedRecursiveExport(&$data, ?Context $context = null) + { + $result = []; + $exporter = new self(); + + if (!$context) { + $context = new Context; + } + + $array = $data; + $context->add($data); + + foreach ($array as $key => $value) { + if (is_array($value)) { + if ($context->contains($data[$key]) !== false) { + $result[] = '*RECURSION*'; + } else { + $result[] = sprintf( + 'array(%s)', + $this->shortenedRecursiveExport($data[$key], $context) + ); + } + } else { + $result[] = $exporter->shortenedExport($value); + } + } + + return implode(', ', $result); + } + + /** + * Exports a value into a single-line string. + * + * The output of this method is similar to the output of + * SebastianBergmann\Exporter\Exporter::export(). + * + * Newlines are replaced by the visible string '\n'. + * Contents of arrays and objects (if any) are replaced by '...'. + * + * @return string + * + * @see SebastianBergmann\Exporter\Exporter::export + */ + public function shortenedExport($value) + { + if (is_string($value)) { + $string = str_replace("\n", '', $this->export($value)); + + if (function_exists('mb_strlen')) { + if (mb_strlen($string) > 40) { + $string = mb_substr($string, 0, 30) . '...' . mb_substr($string, -7); + } + } else { + if (strlen($string) > 40) { + $string = substr($string, 0, 30) . '...' . substr($string, -7); + } + } + + return $string; + } + + if (is_object($value)) { + return sprintf( + '%s Object (%s)', + get_class($value), + count($this->toArray($value)) > 0 ? '...' : '' + ); + } + + if (is_array($value)) { + return sprintf( + 'Array (%s)', + count($value) > 0 ? '...' : '' + ); + } + + return $this->export($value); + } + + /** + * Converts an object to an array containing all of its private, protected + * and public properties. + * + * @return array + */ + public function toArray($value) + { + if (!is_object($value)) { + return (array) $value; + } + + $array = []; + + foreach ((array) $value as $key => $val) { + // Exception traces commonly reference hundreds to thousands of + // objects currently loaded in memory. Including them in the result + // has a severe negative performance impact. + if ("\0Error\0trace" === $key || "\0Exception\0trace" === $key) { + continue; + } + + // properties are transformed to keys in the following way: + // private $property => "\0Classname\0property" + // protected $property => "\0*\0property" + // public $property => "property" + if (preg_match('/^\0.+\0(.+)$/', (string) $key, $matches)) { + $key = $matches[1]; + } + + // See https://github.com/php/php-src/commit/5721132 + if ($key === "\0gcdata") { + continue; + } + + $array[$key] = $val; + } + + // Some internal classes like SplObjectStorage don't work with the + // above (fast) mechanism nor with reflection in Zend. + // Format the output similarly to print_r() in this case + if ($value instanceof SplObjectStorage) { + foreach ($value as $key => $val) { + $array[spl_object_hash($val)] = [ + 'obj' => $val, + 'inf' => $value->getInfo(), + ]; + } + } + + return $array; + } + + /** + * Recursive implementation of export. + * + * @param mixed $value The value to export + * @param int $indentation The indentation level of the 2nd+ line + * @param \SebastianBergmann\RecursionContext\Context $processed Previously processed objects + * + * @return string + * + * @see SebastianBergmann\Exporter\Exporter::export + */ + protected function recursiveExport(&$value, $indentation, $processed = null) + { + if ($value === null) { + return 'null'; + } + + if ($value === true) { + return 'true'; + } + + if ($value === false) { + return 'false'; + } + + if (is_float($value)) { + $precisionBackup = ini_get('precision'); + + ini_set('precision', '-1'); + + try { + $valueStr = (string) $value; + + if ((string) (int) $value === $valueStr) { + return $valueStr . '.0'; + } + + return $valueStr; + } finally { + ini_set('precision', $precisionBackup); + } + } + + if (gettype($value) === 'resource (closed)') { + return 'resource (closed)'; + } + + if (is_resource($value)) { + return sprintf( + 'resource(%d) of type (%s)', + $value, + get_resource_type($value) + ); + } + + if (is_string($value)) { + // Match for most non printable chars somewhat taking multibyte chars into account + if (preg_match('/[^\x09-\x0d\x1b\x20-\xff]/', $value)) { + return 'Binary String: 0x' . bin2hex($value); + } + + return "'" . + str_replace( + '', + "\n", + str_replace( + ["\r\n", "\n\r", "\r", "\n"], + ['\r\n', '\n\r', '\r', '\n'], + $value + ) + ) . + "'"; + } + + $whitespace = str_repeat(' ', 4 * $indentation); + + if (!$processed) { + $processed = new Context; + } + + if (is_array($value)) { + if (($key = $processed->contains($value)) !== false) { + return 'Array &' . $key; + } + + $array = $value; + $key = $processed->add($value); + $values = ''; + + if (count($array) > 0) { + foreach ($array as $k => $v) { + $values .= sprintf( + '%s %s => %s' . "\n", + $whitespace, + $this->recursiveExport($k, $indentation), + $this->recursiveExport($value[$k], $indentation + 1, $processed) + ); + } + + $values = "\n" . $values . $whitespace; + } + + return sprintf('Array &%s (%s)', $key, $values); + } + + if (is_object($value)) { + $class = get_class($value); + + if ($hash = $processed->contains($value)) { + return sprintf('%s Object &%s', $class, $hash); + } + + $hash = $processed->add($value); + $values = ''; + $array = $this->toArray($value); + + if (count($array) > 0) { + foreach ($array as $k => $v) { + $values .= sprintf( + '%s %s => %s' . "\n", + $whitespace, + $this->recursiveExport($k, $indentation), + $this->recursiveExport($v, $indentation + 1, $processed) + ); + } + + $values = "\n" . $values . $whitespace; + } + + return sprintf('%s Object &%s (%s)', $class, $hash, $values); + } + + return var_export($value, true); + } +} diff --git a/vendor/sebastian/global-state/ChangeLog.md b/vendor/sebastian/global-state/ChangeLog.md new file mode 100644 index 00000000..e76f84b8 --- /dev/null +++ b/vendor/sebastian/global-state/ChangeLog.md @@ -0,0 +1,93 @@ +# Changes in sebastian/global-state + +All notable changes in `sebastian/global-state` are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles. + +## [5.0.7] - 2024-03-02 + +### Changed + +* Do not use implicitly nullable parameters + +## [5.0.6] - 2023-08-02 + +### Changed + +* Changed usage of `ReflectionProperty::setValue()` to be compatible with PHP 8.3 + +## [5.0.5] - 2022-02-14 + +### Fixed + +* [#34](https://github.com/sebastianbergmann/global-state/pull/34): Uninitialised typed static properties are not handled correctly + +## [5.0.4] - 2022-02-10 + +### Fixed + +* The `$includeTraits` parameter of `SebastianBergmann\GlobalState\Snapshot::__construct()` is not respected + +## [5.0.3] - 2021-06-11 + +### Changed + +* `SebastianBergmann\GlobalState\CodeExporter::globalVariables()` now generates code that is compatible with PHP 8.1 + +## [5.0.2] - 2020-10-26 + +### Fixed + +* `SebastianBergmann\GlobalState\Exception` now correctly extends `\Throwable` + +## [5.0.1] - 2020-09-28 + +### Changed + +* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3` + +## [5.0.0] - 2020-08-07 + +### Changed + +* The `SebastianBergmann\GlobalState\Blacklist` class has been renamed to `SebastianBergmann\GlobalState\ExcludeList` + +## [4.0.0] - 2020-02-07 + +### Removed + +* This component is no longer supported on PHP 7.2 + +## [3.0.2] - 2022-02-10 + +### Fixed + +* The `$includeTraits` parameter of `SebastianBergmann\GlobalState\Snapshot::__construct()` is not respected + +## [3.0.1] - 2020-11-30 + +### Changed + +* Changed PHP version constraint in `composer.json` from `^7.2` to `>=7.2` + +## [3.0.0] - 2019-02-01 + +### Changed + +* `Snapshot::canBeSerialized()` now recursively checks arrays and object graphs for variables that cannot be serialized + +### Removed + +* This component is no longer supported on PHP 7.0 and PHP 7.1 + +[5.0.7]: https://github.com/sebastianbergmann/global-state/compare/5.0.6...5.0.7 +[5.0.6]: https://github.com/sebastianbergmann/global-state/compare/5.0.5...5.0.6 +[5.0.5]: https://github.com/sebastianbergmann/global-state/compare/5.0.4...5.0.5 +[5.0.4]: https://github.com/sebastianbergmann/global-state/compare/5.0.3...5.0.4 +[5.0.3]: https://github.com/sebastianbergmann/global-state/compare/5.0.2...5.0.3 +[5.0.2]: https://github.com/sebastianbergmann/global-state/compare/5.0.1...5.0.2 +[5.0.1]: https://github.com/sebastianbergmann/global-state/compare/5.0.0...5.0.1 +[5.0.0]: https://github.com/sebastianbergmann/global-state/compare/4.0.0...5.0.0 +[4.0.0]: https://github.com/sebastianbergmann/global-state/compare/3.0.2...4.0.0 +[3.0.2]: https://github.com/sebastianbergmann/phpunit/compare/3.0.1...3.0.2 +[3.0.1]: https://github.com/sebastianbergmann/phpunit/compare/3.0.0...3.0.1 +[3.0.0]: https://github.com/sebastianbergmann/phpunit/compare/2.0.0...3.0.0 + diff --git a/vendor/sebastian/global-state/LICENSE b/vendor/sebastian/global-state/LICENSE new file mode 100644 index 00000000..240190bd --- /dev/null +++ b/vendor/sebastian/global-state/LICENSE @@ -0,0 +1,33 @@ +sebastian/global-state + +Copyright (c) 2001-2022, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/sebastian/global-state/README.md b/vendor/sebastian/global-state/README.md new file mode 100644 index 00000000..af15bedd --- /dev/null +++ b/vendor/sebastian/global-state/README.md @@ -0,0 +1,20 @@ +# sebastian/global-state + +[![CI Status](https://github.com/sebastianbergmann/global-state/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/global-state/actions) +[![Type Coverage](https://shepherd.dev/github/sebastianbergmann/global-state/coverage.svg)](https://shepherd.dev/github/sebastianbergmann/global-state) + +Snapshotting of global state, factored out of PHPUnit into a stand-alone component. + +## Installation + +You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): + +``` +composer require sebastian/global-state +``` + +If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency: + +``` +composer require --dev sebastian/global-state +``` diff --git a/vendor/sebastian/global-state/composer.json b/vendor/sebastian/global-state/composer.json new file mode 100644 index 00000000..0fef446a --- /dev/null +++ b/vendor/sebastian/global-state/composer.json @@ -0,0 +1,51 @@ +{ + "name": "sebastian/global-state", + "description": "Snapshotting of global state", + "keywords": ["global state"], + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "prefer-stable": true, + "config": { + "platform": { + "php": "7.3.0" + }, + "optimize-autoloader": true, + "sort-packages": true + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "autoload-dev": { + "classmap": [ + "tests/_fixture/" + ], + "files": [ + "tests/_fixture/SnapshotFunctions.php" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + } +} diff --git a/vendor/sebastian/global-state/src/CodeExporter.php b/vendor/sebastian/global-state/src/CodeExporter.php new file mode 100644 index 00000000..71cdbf50 --- /dev/null +++ b/vendor/sebastian/global-state/src/CodeExporter.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\GlobalState; + +use const PHP_EOL; +use function is_array; +use function is_scalar; +use function serialize; +use function sprintf; +use function var_export; + +/** + * Exports parts of a Snapshot as PHP code. + */ +final class CodeExporter +{ + public function constants(Snapshot $snapshot): string + { + $result = ''; + + foreach ($snapshot->constants() as $name => $value) { + $result .= sprintf( + 'if (!defined(\'%s\')) define(\'%s\', %s);' . "\n", + $name, + $name, + $this->exportVariable($value) + ); + } + + return $result; + } + + public function globalVariables(Snapshot $snapshot): string + { + $result = <<<'EOT' +call_user_func( + function () + { + foreach (array_keys($GLOBALS) as $key) { + unset($GLOBALS[$key]); + } + } +); + + +EOT; + + foreach ($snapshot->globalVariables() as $name => $value) { + $result .= sprintf( + '$GLOBALS[%s] = %s;' . PHP_EOL, + $this->exportVariable($name), + $this->exportVariable($value) + ); + } + + return $result; + } + + public function iniSettings(Snapshot $snapshot): string + { + $result = ''; + + foreach ($snapshot->iniSettings() as $key => $value) { + $result .= sprintf( + '@ini_set(%s, %s);' . "\n", + $this->exportVariable($key), + $this->exportVariable($value) + ); + } + + return $result; + } + + private function exportVariable($variable): string + { + if (is_scalar($variable) || null === $variable || + (is_array($variable) && $this->arrayOnlyContainsScalars($variable))) { + return var_export($variable, true); + } + + return 'unserialize(' . var_export(serialize($variable), true) . ')'; + } + + private function arrayOnlyContainsScalars(array $array): bool + { + $result = true; + + foreach ($array as $element) { + if (is_array($element)) { + $result = $this->arrayOnlyContainsScalars($element); + } elseif (!is_scalar($element) && null !== $element) { + $result = false; + } + + if ($result === false) { + break; + } + } + + return $result; + } +} diff --git a/vendor/sebastian/global-state/src/ExcludeList.php b/vendor/sebastian/global-state/src/ExcludeList.php new file mode 100644 index 00000000..5631f118 --- /dev/null +++ b/vendor/sebastian/global-state/src/ExcludeList.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\GlobalState; + +use function in_array; +use function strpos; +use ReflectionClass; + +final class ExcludeList +{ + /** + * @var array + */ + private $globalVariables = []; + + /** + * @var string[] + */ + private $classes = []; + + /** + * @var string[] + */ + private $classNamePrefixes = []; + + /** + * @var string[] + */ + private $parentClasses = []; + + /** + * @var string[] + */ + private $interfaces = []; + + /** + * @var array + */ + private $staticAttributes = []; + + public function addGlobalVariable(string $variableName): void + { + $this->globalVariables[$variableName] = true; + } + + public function addClass(string $className): void + { + $this->classes[] = $className; + } + + public function addSubclassesOf(string $className): void + { + $this->parentClasses[] = $className; + } + + public function addImplementorsOf(string $interfaceName): void + { + $this->interfaces[] = $interfaceName; + } + + public function addClassNamePrefix(string $classNamePrefix): void + { + $this->classNamePrefixes[] = $classNamePrefix; + } + + public function addStaticAttribute(string $className, string $attributeName): void + { + if (!isset($this->staticAttributes[$className])) { + $this->staticAttributes[$className] = []; + } + + $this->staticAttributes[$className][$attributeName] = true; + } + + public function isGlobalVariableExcluded(string $variableName): bool + { + return isset($this->globalVariables[$variableName]); + } + + public function isStaticAttributeExcluded(string $className, string $attributeName): bool + { + if (in_array($className, $this->classes, true)) { + return true; + } + + foreach ($this->classNamePrefixes as $prefix) { + if (strpos($className, $prefix) === 0) { + return true; + } + } + + $class = new ReflectionClass($className); + + foreach ($this->parentClasses as $type) { + if ($class->isSubclassOf($type)) { + return true; + } + } + + foreach ($this->interfaces as $type) { + if ($class->implementsInterface($type)) { + return true; + } + } + + if (isset($this->staticAttributes[$className][$attributeName])) { + return true; + } + + return false; + } +} diff --git a/vendor/sebastian/global-state/src/Restorer.php b/vendor/sebastian/global-state/src/Restorer.php new file mode 100644 index 00000000..ab145ce2 --- /dev/null +++ b/vendor/sebastian/global-state/src/Restorer.php @@ -0,0 +1,143 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\GlobalState; + +use function array_diff; +use function array_key_exists; +use function array_keys; +use function array_merge; +use function function_exists; +use function get_defined_functions; +use function in_array; +use function is_array; +use ReflectionClass; +use ReflectionProperty; + +/** + * Restorer of snapshots of global state. + */ +class Restorer +{ + /** + * Deletes function definitions that are not defined in a snapshot. + * + * @throws RuntimeException when the uopz_delete() function is not available + * + * @see https://github.com/krakjoe/uopz + */ + public function restoreFunctions(Snapshot $snapshot): void + { + if (!function_exists('uopz_delete')) { + throw new RuntimeException('The uopz_delete() function is required for this operation'); + } + + $functions = get_defined_functions(); + + foreach (array_diff($functions['user'], $snapshot->functions()) as $function) { + uopz_delete($function); + } + } + + /** + * Restores all global and super-global variables from a snapshot. + */ + public function restoreGlobalVariables(Snapshot $snapshot): void + { + $superGlobalArrays = $snapshot->superGlobalArrays(); + + foreach ($superGlobalArrays as $superGlobalArray) { + $this->restoreSuperGlobalArray($snapshot, $superGlobalArray); + } + + $globalVariables = $snapshot->globalVariables(); + + foreach (array_keys($GLOBALS) as $key) { + if ($key !== 'GLOBALS' && + !in_array($key, $superGlobalArrays, true) && + !$snapshot->excludeList()->isGlobalVariableExcluded($key)) { + if (array_key_exists($key, $globalVariables)) { + $GLOBALS[$key] = $globalVariables[$key]; + } else { + unset($GLOBALS[$key]); + } + } + } + } + + /** + * Restores all static attributes in user-defined classes from this snapshot. + */ + public function restoreStaticAttributes(Snapshot $snapshot): void + { + $current = new Snapshot($snapshot->excludeList(), false, false, false, false, true, false, false, false, false); + $newClasses = array_diff($current->classes(), $snapshot->classes()); + + unset($current); + + foreach ($snapshot->staticAttributes() as $className => $staticAttributes) { + foreach ($staticAttributes as $name => $value) { + $reflector = new ReflectionProperty($className, $name); + $reflector->setAccessible(true); + $reflector->setValue(null, $value); + } + } + + foreach ($newClasses as $className) { + $class = new ReflectionClass($className); + $defaults = $class->getDefaultProperties(); + + foreach ($class->getProperties() as $attribute) { + if (!$attribute->isStatic()) { + continue; + } + + $name = $attribute->getName(); + + if ($snapshot->excludeList()->isStaticAttributeExcluded($className, $name)) { + continue; + } + + if (!isset($defaults[$name])) { + continue; + } + + $attribute->setAccessible(true); + $attribute->setValue(null, $defaults[$name]); + } + } + } + + /** + * Restores a super-global variable array from this snapshot. + */ + private function restoreSuperGlobalArray(Snapshot $snapshot, string $superGlobalArray): void + { + $superGlobalVariables = $snapshot->superGlobalVariables(); + + if (isset($GLOBALS[$superGlobalArray]) && + is_array($GLOBALS[$superGlobalArray]) && + isset($superGlobalVariables[$superGlobalArray])) { + $keys = array_keys( + array_merge( + $GLOBALS[$superGlobalArray], + $superGlobalVariables[$superGlobalArray] + ) + ); + + foreach ($keys as $key) { + if (isset($superGlobalVariables[$superGlobalArray][$key])) { + $GLOBALS[$superGlobalArray][$key] = $superGlobalVariables[$superGlobalArray][$key]; + } else { + unset($GLOBALS[$superGlobalArray][$key]); + } + } + } + } +} diff --git a/vendor/sebastian/global-state/src/Snapshot.php b/vendor/sebastian/global-state/src/Snapshot.php new file mode 100644 index 00000000..61219cac --- /dev/null +++ b/vendor/sebastian/global-state/src/Snapshot.php @@ -0,0 +1,443 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\GlobalState; + +use const PHP_VERSION_ID; +use function array_keys; +use function array_merge; +use function array_reverse; +use function func_get_args; +use function get_declared_classes; +use function get_declared_interfaces; +use function get_declared_traits; +use function get_defined_constants; +use function get_defined_functions; +use function get_included_files; +use function in_array; +use function ini_get_all; +use function is_array; +use function is_object; +use function is_resource; +use function is_scalar; +use function serialize; +use function unserialize; +use ReflectionClass; +use SebastianBergmann\ObjectReflector\ObjectReflector; +use SebastianBergmann\RecursionContext\Context; +use Throwable; + +/** + * A snapshot of global state. + */ +class Snapshot +{ + /** + * @var ExcludeList + */ + private $excludeList; + + /** + * @var array + */ + private $globalVariables = []; + + /** + * @var array + */ + private $superGlobalArrays = []; + + /** + * @var array + */ + private $superGlobalVariables = []; + + /** + * @var array + */ + private $staticAttributes = []; + + /** + * @var array + */ + private $iniSettings = []; + + /** + * @var array + */ + private $includedFiles = []; + + /** + * @var array + */ + private $constants = []; + + /** + * @var array + */ + private $functions = []; + + /** + * @var array + */ + private $interfaces = []; + + /** + * @var array + */ + private $classes = []; + + /** + * @var array + */ + private $traits = []; + + /** + * Creates a snapshot of the current global state. + */ + public function __construct(?ExcludeList $excludeList = null, bool $includeGlobalVariables = true, bool $includeStaticAttributes = true, bool $includeConstants = true, bool $includeFunctions = true, bool $includeClasses = true, bool $includeInterfaces = true, bool $includeTraits = true, bool $includeIniSettings = true, bool $includeIncludedFiles = true) + { + $this->excludeList = $excludeList ?: new ExcludeList; + + if ($includeConstants) { + $this->snapshotConstants(); + } + + if ($includeFunctions) { + $this->snapshotFunctions(); + } + + if ($includeClasses || $includeStaticAttributes) { + $this->snapshotClasses(); + } + + if ($includeInterfaces) { + $this->snapshotInterfaces(); + } + + if ($includeGlobalVariables) { + $this->setupSuperGlobalArrays(); + $this->snapshotGlobals(); + } + + if ($includeStaticAttributes) { + $this->snapshotStaticAttributes(); + } + + if ($includeIniSettings) { + $this->iniSettings = ini_get_all(null, false); + } + + if ($includeIncludedFiles) { + $this->includedFiles = get_included_files(); + } + + if ($includeTraits) { + $this->traits = get_declared_traits(); + } + } + + public function excludeList(): ExcludeList + { + return $this->excludeList; + } + + public function globalVariables(): array + { + return $this->globalVariables; + } + + public function superGlobalVariables(): array + { + return $this->superGlobalVariables; + } + + public function superGlobalArrays(): array + { + return $this->superGlobalArrays; + } + + public function staticAttributes(): array + { + return $this->staticAttributes; + } + + public function iniSettings(): array + { + return $this->iniSettings; + } + + public function includedFiles(): array + { + return $this->includedFiles; + } + + public function constants(): array + { + return $this->constants; + } + + public function functions(): array + { + return $this->functions; + } + + public function interfaces(): array + { + return $this->interfaces; + } + + public function classes(): array + { + return $this->classes; + } + + public function traits(): array + { + return $this->traits; + } + + /** + * Creates a snapshot user-defined constants. + */ + private function snapshotConstants(): void + { + $constants = get_defined_constants(true); + + if (isset($constants['user'])) { + $this->constants = $constants['user']; + } + } + + /** + * Creates a snapshot user-defined functions. + */ + private function snapshotFunctions(): void + { + $functions = get_defined_functions(); + + $this->functions = $functions['user']; + } + + /** + * Creates a snapshot user-defined classes. + */ + private function snapshotClasses(): void + { + foreach (array_reverse(get_declared_classes()) as $className) { + $class = new ReflectionClass($className); + + if (!$class->isUserDefined()) { + break; + } + + $this->classes[] = $className; + } + + $this->classes = array_reverse($this->classes); + } + + /** + * Creates a snapshot user-defined interfaces. + */ + private function snapshotInterfaces(): void + { + foreach (array_reverse(get_declared_interfaces()) as $interfaceName) { + $class = new ReflectionClass($interfaceName); + + if (!$class->isUserDefined()) { + break; + } + + $this->interfaces[] = $interfaceName; + } + + $this->interfaces = array_reverse($this->interfaces); + } + + /** + * Creates a snapshot of all global and super-global variables. + */ + private function snapshotGlobals(): void + { + $superGlobalArrays = $this->superGlobalArrays(); + + foreach ($superGlobalArrays as $superGlobalArray) { + $this->snapshotSuperGlobalArray($superGlobalArray); + } + + foreach (array_keys($GLOBALS) as $key) { + if ($key !== 'GLOBALS' && + !in_array($key, $superGlobalArrays, true) && + $this->canBeSerialized($GLOBALS[$key]) && + !$this->excludeList->isGlobalVariableExcluded($key)) { + /* @noinspection UnserializeExploitsInspection */ + $this->globalVariables[$key] = unserialize(serialize($GLOBALS[$key])); + } + } + } + + /** + * Creates a snapshot a super-global variable array. + */ + private function snapshotSuperGlobalArray(string $superGlobalArray): void + { + $this->superGlobalVariables[$superGlobalArray] = []; + + if (isset($GLOBALS[$superGlobalArray]) && is_array($GLOBALS[$superGlobalArray])) { + foreach ($GLOBALS[$superGlobalArray] as $key => $value) { + /* @noinspection UnserializeExploitsInspection */ + $this->superGlobalVariables[$superGlobalArray][$key] = unserialize(serialize($value)); + } + } + } + + /** + * Creates a snapshot of all static attributes in user-defined classes. + */ + private function snapshotStaticAttributes(): void + { + foreach ($this->classes as $className) { + $class = new ReflectionClass($className); + $snapshot = []; + + foreach ($class->getProperties() as $attribute) { + if ($attribute->isStatic()) { + $name = $attribute->getName(); + + if ($this->excludeList->isStaticAttributeExcluded($className, $name)) { + continue; + } + + $attribute->setAccessible(true); + + if (PHP_VERSION_ID >= 70400 && !$attribute->isInitialized()) { + continue; + } + + $value = $attribute->getValue(); + + if ($this->canBeSerialized($value)) { + /* @noinspection UnserializeExploitsInspection */ + $snapshot[$name] = unserialize(serialize($value)); + } + } + } + + if (!empty($snapshot)) { + $this->staticAttributes[$className] = $snapshot; + } + } + } + + /** + * Returns a list of all super-global variable arrays. + */ + private function setupSuperGlobalArrays(): void + { + $this->superGlobalArrays = [ + '_ENV', + '_POST', + '_GET', + '_COOKIE', + '_SERVER', + '_FILES', + '_REQUEST', + ]; + } + + private function canBeSerialized($variable): bool + { + if (is_scalar($variable) || $variable === null) { + return true; + } + + if (is_resource($variable)) { + return false; + } + + foreach ($this->enumerateObjectsAndResources($variable) as $value) { + if (is_resource($value)) { + return false; + } + + if (is_object($value)) { + $class = new ReflectionClass($value); + + if ($class->isAnonymous()) { + return false; + } + + try { + @serialize($value); + } catch (Throwable $t) { + return false; + } + } + } + + return true; + } + + private function enumerateObjectsAndResources($variable): array + { + if (isset(func_get_args()[1])) { + $processed = func_get_args()[1]; + } else { + $processed = new Context; + } + + $result = []; + + if ($processed->contains($variable)) { + return $result; + } + + $array = $variable; + $processed->add($variable); + + if (is_array($variable)) { + foreach ($array as $element) { + if (!is_array($element) && !is_object($element) && !is_resource($element)) { + continue; + } + + if (!is_resource($element)) { + /** @noinspection SlowArrayOperationsInLoopInspection */ + $result = array_merge( + $result, + $this->enumerateObjectsAndResources($element, $processed) + ); + } else { + $result[] = $element; + } + } + } else { + $result[] = $variable; + + foreach ((new ObjectReflector)->getAttributes($variable) as $value) { + if (!is_array($value) && !is_object($value) && !is_resource($value)) { + continue; + } + + if (!is_resource($value)) { + /** @noinspection SlowArrayOperationsInLoopInspection */ + $result = array_merge( + $result, + $this->enumerateObjectsAndResources($value, $processed) + ); + } else { + $result[] = $value; + } + } + } + + return $result; + } +} diff --git a/vendor/sebastian/global-state/src/exceptions/Exception.php b/vendor/sebastian/global-state/src/exceptions/Exception.php new file mode 100644 index 00000000..94432008 --- /dev/null +++ b/vendor/sebastian/global-state/src/exceptions/Exception.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\GlobalState; + +use Throwable; + +interface Exception extends Throwable +{ +} diff --git a/vendor/sebastian/global-state/src/exceptions/RuntimeException.php b/vendor/sebastian/global-state/src/exceptions/RuntimeException.php new file mode 100644 index 00000000..79f02a11 --- /dev/null +++ b/vendor/sebastian/global-state/src/exceptions/RuntimeException.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\GlobalState; + +final class RuntimeException extends \RuntimeException implements Exception +{ +} diff --git a/vendor/sebastian/lines-of-code/.psalm/baseline.xml b/vendor/sebastian/lines-of-code/.psalm/baseline.xml new file mode 100644 index 00000000..77e688e0 --- /dev/null +++ b/vendor/sebastian/lines-of-code/.psalm/baseline.xml @@ -0,0 +1,2 @@ + + diff --git a/vendor/sebastian/lines-of-code/.psalm/config.xml b/vendor/sebastian/lines-of-code/.psalm/config.xml new file mode 100644 index 00000000..15abef05 --- /dev/null +++ b/vendor/sebastian/lines-of-code/.psalm/config.xml @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/vendor/sebastian/lines-of-code/ChangeLog.md b/vendor/sebastian/lines-of-code/ChangeLog.md new file mode 100644 index 00000000..2c960705 --- /dev/null +++ b/vendor/sebastian/lines-of-code/ChangeLog.md @@ -0,0 +1,41 @@ +# ChangeLog + +All notable changes are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles. + +## [1.0.4] - 2023-12-22 + +### Changed + +* This component is now compatible with `nikic/php-parser` 5.0 + +## [1.0.3] - 2020-11-28 + +### Fixed + +* Files that do not contain a newline were not handled correctly + +### Changed + +* A line of code is no longer considered to be a Logical Line of Code if it does not contain an `Expr` node + +## [1.0.2] - 2020-10-26 + +### Fixed + +* `SebastianBergmann\LinesOfCode\Exception` now correctly extends `\Throwable` + +## [1.0.1] - 2020-09-28 + +### Changed + +* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3` + +## [1.0.0] - 2020-07-22 + +* Initial release + +[1.0.4]: https://github.com/sebastianbergmann/lines-of-code/compare/1.0.3...1.0.4 +[1.0.3]: https://github.com/sebastianbergmann/lines-of-code/compare/1.0.2...1.0.3 +[1.0.2]: https://github.com/sebastianbergmann/lines-of-code/compare/1.0.1...1.0.2 +[1.0.1]: https://github.com/sebastianbergmann/lines-of-code/compare/1.0.0...1.0.1 +[1.0.0]: https://github.com/sebastianbergmann/lines-of-code/compare/f959e71f00e591288acc024afe9cb966c6cf9bd6...1.0.0 diff --git a/vendor/sebastian/lines-of-code/LICENSE b/vendor/sebastian/lines-of-code/LICENSE new file mode 100644 index 00000000..d170181f --- /dev/null +++ b/vendor/sebastian/lines-of-code/LICENSE @@ -0,0 +1,33 @@ +sebastian/lines-of-code + +Copyright (c) 2020, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/sebastian/lines-of-code/README.md b/vendor/sebastian/lines-of-code/README.md new file mode 100644 index 00000000..9457ef5a --- /dev/null +++ b/vendor/sebastian/lines-of-code/README.md @@ -0,0 +1,22 @@ +# sebastian/lines-of-code + +Library for counting the lines of code in PHP source code. + +[![Latest Stable Version](https://img.shields.io/packagist/v/sebastian/lines-of-code.svg?style=flat-square)](https://packagist.org/packages/sebastian/lines-of-code) +[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.3-8892BF.svg?style=flat-square)](https://php.net/) +[![CI Status](https://github.com/sebastianbergmann/lines-of-code/workflows/CI/badge.svg?branch=master&event=push)](https://phpunit.de/build-status.html) +[![Type Coverage](https://shepherd.dev/github/sebastianbergmann/lines-of-code/coverage.svg)](https://shepherd.dev/github/sebastianbergmann/lines-of-code) + +## Installation + +You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): + +``` +composer require sebastian/lines-of-code +``` + +If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency: + +``` +composer require --dev sebastian/lines-of-code +``` diff --git a/vendor/sebastian/lines-of-code/composer.json b/vendor/sebastian/lines-of-code/composer.json new file mode 100644 index 00000000..3f287865 --- /dev/null +++ b/vendor/sebastian/lines-of-code/composer.json @@ -0,0 +1,42 @@ +{ + "name": "sebastian/lines-of-code", + "description": "Library for counting the lines of code in PHP source code", + "type": "library", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues" + }, + "prefer-stable": true, + "require": { + "php": ">=7.3", + "nikic/php-parser": "^4.18 || ^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "config": { + "platform": { + "php": "7.3.0" + }, + "optimize-autoloader": true, + "sort-packages": true + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + } +} diff --git a/vendor/sebastian/lines-of-code/src/Counter.php b/vendor/sebastian/lines-of-code/src/Counter.php new file mode 100644 index 00000000..8153a7b2 --- /dev/null +++ b/vendor/sebastian/lines-of-code/src/Counter.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\LinesOfCode; + +use function substr_count; +use PhpParser\Error; +use PhpParser\Node; +use PhpParser\NodeTraverser; +use PhpParser\ParserFactory; + +final class Counter +{ + /** + * @throws RuntimeException + */ + public function countInSourceFile(string $sourceFile): LinesOfCode + { + return $this->countInSourceString(file_get_contents($sourceFile)); + } + + /** + * @throws RuntimeException + */ + public function countInSourceString(string $source): LinesOfCode + { + $linesOfCode = substr_count($source, "\n"); + + if ($linesOfCode === 0 && !empty($source)) { + $linesOfCode = 1; + } + + try { + $nodes = (new ParserFactory)->createForHostVersion()->parse($source); + + assert($nodes !== null); + + return $this->countInAbstractSyntaxTree($linesOfCode, $nodes); + + // @codeCoverageIgnoreStart + } catch (Error $error) { + throw new RuntimeException( + $error->getMessage(), + (int) $error->getCode(), + $error + ); + } + // @codeCoverageIgnoreEnd + } + + /** + * @param Node[] $nodes + * + * @throws RuntimeException + */ + public function countInAbstractSyntaxTree(int $linesOfCode, array $nodes): LinesOfCode + { + $traverser = new NodeTraverser; + $visitor = new LineCountingVisitor($linesOfCode); + + $traverser->addVisitor($visitor); + + try { + /* @noinspection UnusedFunctionResultInspection */ + $traverser->traverse($nodes); + // @codeCoverageIgnoreStart + } catch (Error $error) { + throw new RuntimeException( + $error->getMessage(), + (int) $error->getCode(), + $error + ); + } + // @codeCoverageIgnoreEnd + + return $visitor->result(); + } +} diff --git a/vendor/sebastian/lines-of-code/src/Exception/Exception.php b/vendor/sebastian/lines-of-code/src/Exception/Exception.php new file mode 100644 index 00000000..11d543aa --- /dev/null +++ b/vendor/sebastian/lines-of-code/src/Exception/Exception.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\LinesOfCode; + +use Throwable; + +interface Exception extends Throwable +{ +} diff --git a/vendor/sebastian/lines-of-code/src/Exception/IllogicalValuesException.php b/vendor/sebastian/lines-of-code/src/Exception/IllogicalValuesException.php new file mode 100644 index 00000000..46a5c1b1 --- /dev/null +++ b/vendor/sebastian/lines-of-code/src/Exception/IllogicalValuesException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\LinesOfCode; + +use LogicException; + +final class IllogicalValuesException extends LogicException implements Exception +{ +} diff --git a/vendor/sebastian/lines-of-code/src/Exception/NegativeValueException.php b/vendor/sebastian/lines-of-code/src/Exception/NegativeValueException.php new file mode 100644 index 00000000..40d27e1f --- /dev/null +++ b/vendor/sebastian/lines-of-code/src/Exception/NegativeValueException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\LinesOfCode; + +use InvalidArgumentException; + +final class NegativeValueException extends InvalidArgumentException implements Exception +{ +} diff --git a/vendor/sebastian/lines-of-code/src/Exception/RuntimeException.php b/vendor/sebastian/lines-of-code/src/Exception/RuntimeException.php new file mode 100644 index 00000000..4e6d66d0 --- /dev/null +++ b/vendor/sebastian/lines-of-code/src/Exception/RuntimeException.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\LinesOfCode; + +final class RuntimeException extends \RuntimeException implements Exception +{ +} diff --git a/vendor/sebastian/lines-of-code/src/LineCountingVisitor.php b/vendor/sebastian/lines-of-code/src/LineCountingVisitor.php new file mode 100644 index 00000000..ff433b2f --- /dev/null +++ b/vendor/sebastian/lines-of-code/src/LineCountingVisitor.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\LinesOfCode; + +use function array_merge; +use function array_unique; +use function count; +use PhpParser\Comment; +use PhpParser\Node; +use PhpParser\Node\Expr; +use PhpParser\NodeVisitorAbstract; + +final class LineCountingVisitor extends NodeVisitorAbstract +{ + /** + * @var int + */ + private $linesOfCode; + + /** + * @var Comment[] + */ + private $comments = []; + + /** + * @var int[] + */ + private $linesWithStatements = []; + + public function __construct(int $linesOfCode) + { + $this->linesOfCode = $linesOfCode; + } + + public function enterNode(Node $node): void + { + $this->comments = array_merge($this->comments, $node->getComments()); + + if (!$node instanceof Expr) { + return; + } + + $this->linesWithStatements[] = $node->getStartLine(); + } + + public function result(): LinesOfCode + { + $commentLinesOfCode = 0; + + foreach ($this->comments() as $comment) { + $commentLinesOfCode += ($comment->getEndLine() - $comment->getStartLine() + 1); + } + + return new LinesOfCode( + $this->linesOfCode, + $commentLinesOfCode, + $this->linesOfCode - $commentLinesOfCode, + count(array_unique($this->linesWithStatements)) + ); + } + + /** + * @return Comment[] + */ + private function comments(): array + { + $comments = []; + + foreach ($this->comments as $comment) { + $comments[$comment->getStartLine() . '_' . $comment->getStartTokenPos() . '_' . $comment->getEndLine() . '_' . $comment->getEndTokenPos()] = $comment; + } + + return $comments; + } +} diff --git a/vendor/sebastian/lines-of-code/src/LinesOfCode.php b/vendor/sebastian/lines-of-code/src/LinesOfCode.php new file mode 100644 index 00000000..41829981 --- /dev/null +++ b/vendor/sebastian/lines-of-code/src/LinesOfCode.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\LinesOfCode; + +/** + * @psalm-immutable + */ +final class LinesOfCode +{ + /** + * @var int + */ + private $linesOfCode; + + /** + * @var int + */ + private $commentLinesOfCode; + + /** + * @var int + */ + private $nonCommentLinesOfCode; + + /** + * @var int + */ + private $logicalLinesOfCode; + + /** + * @throws IllogicalValuesException + * @throws NegativeValueException + */ + public function __construct(int $linesOfCode, int $commentLinesOfCode, int $nonCommentLinesOfCode, int $logicalLinesOfCode) + { + if ($linesOfCode < 0) { + throw new NegativeValueException('$linesOfCode must not be negative'); + } + + if ($commentLinesOfCode < 0) { + throw new NegativeValueException('$commentLinesOfCode must not be negative'); + } + + if ($nonCommentLinesOfCode < 0) { + throw new NegativeValueException('$nonCommentLinesOfCode must not be negative'); + } + + if ($logicalLinesOfCode < 0) { + throw new NegativeValueException('$logicalLinesOfCode must not be negative'); + } + + if ($linesOfCode - $commentLinesOfCode !== $nonCommentLinesOfCode) { + throw new IllogicalValuesException('$linesOfCode !== $commentLinesOfCode + $nonCommentLinesOfCode'); + } + + $this->linesOfCode = $linesOfCode; + $this->commentLinesOfCode = $commentLinesOfCode; + $this->nonCommentLinesOfCode = $nonCommentLinesOfCode; + $this->logicalLinesOfCode = $logicalLinesOfCode; + } + + public function linesOfCode(): int + { + return $this->linesOfCode; + } + + public function commentLinesOfCode(): int + { + return $this->commentLinesOfCode; + } + + public function nonCommentLinesOfCode(): int + { + return $this->nonCommentLinesOfCode; + } + + public function logicalLinesOfCode(): int + { + return $this->logicalLinesOfCode; + } + + public function plus(self $other): self + { + return new self( + $this->linesOfCode() + $other->linesOfCode(), + $this->commentLinesOfCode() + $other->commentLinesOfCode(), + $this->nonCommentLinesOfCode() + $other->nonCommentLinesOfCode(), + $this->logicalLinesOfCode() + $other->logicalLinesOfCode(), + ); + } +} diff --git a/vendor/sebastian/object-enumerator/.psalm/baseline.xml b/vendor/sebastian/object-enumerator/.psalm/baseline.xml new file mode 100644 index 00000000..180b3f80 --- /dev/null +++ b/vendor/sebastian/object-enumerator/.psalm/baseline.xml @@ -0,0 +1,9 @@ + + + + + !is_array($variable) && !is_object($variable) + is_object($variable) + + + diff --git a/vendor/sebastian/object-enumerator/.psalm/config.xml b/vendor/sebastian/object-enumerator/.psalm/config.xml new file mode 100644 index 00000000..2a4b16f2 --- /dev/null +++ b/vendor/sebastian/object-enumerator/.psalm/config.xml @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/vendor/sebastian/object-enumerator/ChangeLog.md b/vendor/sebastian/object-enumerator/ChangeLog.md new file mode 100644 index 00000000..88655418 --- /dev/null +++ b/vendor/sebastian/object-enumerator/ChangeLog.md @@ -0,0 +1,88 @@ +# Change Log + +All notable changes to `sebastianbergmann/object-enumerator` are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. + +## [4.0.4] - 2020-10-26 + +### Fixed + +* `SebastianBergmann\ObjectEnumerator\Exception` now correctly extends `\Throwable` + +## [4.0.3] - 2020-09-28 + +### Changed + +* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3` + +## [4.0.2] - 2020-06-26 + +### Added + +* This component is now supported on PHP 8 + +## [4.0.1] - 2020-06-15 + +### Changed + +* Tests etc. are now ignored for archive exports + +## [4.0.0] - 2020-02-07 + +### Removed + +* This component is no longer supported on PHP 7.0, PHP 7.1, and PHP 7.2 + +## [3.0.3] - 2017-08-03 + +### Changed + +* Bumped required version of `sebastian/object-reflector` + +## [3.0.2] - 2017-03-12 + +### Changed + +* `sebastian/object-reflector` is now a dependency + +## [3.0.1] - 2017-03-12 + +### Fixed + +* Objects aggregated in inherited attributes are not enumerated + +## [3.0.0] - 2017-03-03 + +### Removed + +* This component is no longer supported on PHP 5.6 + +## [2.0.1] - 2017-02-18 + +### Fixed + +* Fixed [#2](https://github.com/sebastianbergmann/phpunit/pull/2): Exceptions in `ReflectionProperty::getValue()` are not handled + +## [2.0.0] - 2016-11-19 + +### Changed + +* This component is now compatible with `sebastian/recursion-context: ~1.0.4` + +## 1.0.0 - 2016-02-04 + +### Added + +* Initial release + +[4.0.4]: https://github.com/sebastianbergmann/object-enumerator/compare/4.0.3...4.0.4 +[4.0.3]: https://github.com/sebastianbergmann/object-enumerator/compare/4.0.2...4.0.3 +[4.0.2]: https://github.com/sebastianbergmann/object-enumerator/compare/4.0.1...4.0.2 +[4.0.1]: https://github.com/sebastianbergmann/object-enumerator/compare/4.0.0...4.0.1 +[4.0.0]: https://github.com/sebastianbergmann/object-enumerator/compare/3.0.3...4.0.0 +[3.0.3]: https://github.com/sebastianbergmann/object-enumerator/compare/3.0.2...3.0.3 +[3.0.2]: https://github.com/sebastianbergmann/object-enumerator/compare/3.0.1...3.0.2 +[3.0.1]: https://github.com/sebastianbergmann/object-enumerator/compare/3.0.0...3.0.1 +[3.0.0]: https://github.com/sebastianbergmann/object-enumerator/compare/2.0...3.0.0 +[2.0.1]: https://github.com/sebastianbergmann/object-enumerator/compare/2.0.0...2.0.1 +[2.0.0]: https://github.com/sebastianbergmann/object-enumerator/compare/1.0...2.0.0 + diff --git a/vendor/sebastian/object-enumerator/LICENSE b/vendor/sebastian/object-enumerator/LICENSE new file mode 100644 index 00000000..1389ad39 --- /dev/null +++ b/vendor/sebastian/object-enumerator/LICENSE @@ -0,0 +1,33 @@ +Object Enumerator + +Copyright (c) 2016-2020, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/sebastian/object-enumerator/README.md b/vendor/sebastian/object-enumerator/README.md new file mode 100644 index 00000000..afca0177 --- /dev/null +++ b/vendor/sebastian/object-enumerator/README.md @@ -0,0 +1,20 @@ +# sebastian/object-enumerator + +[![CI Status](https://github.com/sebastianbergmann/object-enumerator/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/object-enumerator/actions) +[![Type Coverage](https://shepherd.dev/github/sebastianbergmann/object-enumerator/coverage.svg)](https://shepherd.dev/github/sebastianbergmann/object-enumerator) + +Traverses array structures and object graphs to enumerate all referenced objects. + +## Installation + +You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): + +``` +composer require sebastian/object-enumerator +``` + +If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency: + +``` +composer require --dev sebastian/object-enumerator +``` diff --git a/vendor/sebastian/object-enumerator/composer.json b/vendor/sebastian/object-enumerator/composer.json new file mode 100644 index 00000000..d68a2133 --- /dev/null +++ b/vendor/sebastian/object-enumerator/composer.json @@ -0,0 +1,43 @@ +{ + "name": "sebastian/object-enumerator", + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "prefer-stable": true, + "config": { + "platform": { + "php": "7.3.0" + }, + "optimize-autoloader": true, + "sort-packages": true + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "autoload-dev": { + "classmap": [ + "tests/_fixture/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + } +} diff --git a/vendor/sebastian/object-enumerator/phpunit.xml b/vendor/sebastian/object-enumerator/phpunit.xml new file mode 100644 index 00000000..7be976b1 --- /dev/null +++ b/vendor/sebastian/object-enumerator/phpunit.xml @@ -0,0 +1,24 @@ + + + + + tests + + + + + + src + + + diff --git a/vendor/sebastian/object-enumerator/src/Enumerator.php b/vendor/sebastian/object-enumerator/src/Enumerator.php new file mode 100644 index 00000000..de75d17c --- /dev/null +++ b/vendor/sebastian/object-enumerator/src/Enumerator.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\ObjectEnumerator; + +use function array_merge; +use function func_get_args; +use function is_array; +use function is_object; +use SebastianBergmann\ObjectReflector\ObjectReflector; +use SebastianBergmann\RecursionContext\Context; + +/** + * Traverses array structures and object graphs + * to enumerate all referenced objects. + */ +class Enumerator +{ + /** + * Returns an array of all objects referenced either + * directly or indirectly by a variable. + * + * @param array|object $variable + * + * @return object[] + */ + public function enumerate($variable) + { + if (!is_array($variable) && !is_object($variable)) { + throw new InvalidArgumentException; + } + + if (isset(func_get_args()[1])) { + if (!func_get_args()[1] instanceof Context) { + throw new InvalidArgumentException; + } + + $processed = func_get_args()[1]; + } else { + $processed = new Context; + } + + $objects = []; + + if ($processed->contains($variable)) { + return $objects; + } + + $array = $variable; + $processed->add($variable); + + if (is_array($variable)) { + foreach ($array as $element) { + if (!is_array($element) && !is_object($element)) { + continue; + } + + $objects = array_merge( + $objects, + $this->enumerate($element, $processed) + ); + } + } else { + $objects[] = $variable; + + $reflector = new ObjectReflector; + + foreach ($reflector->getAttributes($variable) as $value) { + if (!is_array($value) && !is_object($value)) { + continue; + } + + $objects = array_merge( + $objects, + $this->enumerate($value, $processed) + ); + } + } + + return $objects; + } +} diff --git a/vendor/sebastian/object-enumerator/src/Exception.php b/vendor/sebastian/object-enumerator/src/Exception.php new file mode 100644 index 00000000..2f09d70a --- /dev/null +++ b/vendor/sebastian/object-enumerator/src/Exception.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\ObjectEnumerator; + +use Throwable; + +interface Exception extends Throwable +{ +} diff --git a/vendor/sebastian/object-enumerator/src/InvalidArgumentException.php b/vendor/sebastian/object-enumerator/src/InvalidArgumentException.php new file mode 100644 index 00000000..ce2037cd --- /dev/null +++ b/vendor/sebastian/object-enumerator/src/InvalidArgumentException.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\ObjectEnumerator; + +class InvalidArgumentException extends \InvalidArgumentException implements Exception +{ +} diff --git a/vendor/sebastian/object-reflector/.psalm/baseline.xml b/vendor/sebastian/object-reflector/.psalm/baseline.xml new file mode 100644 index 00000000..965c1275 --- /dev/null +++ b/vendor/sebastian/object-reflector/.psalm/baseline.xml @@ -0,0 +1,8 @@ + + + + + is_object($object) + + + diff --git a/vendor/sebastian/object-reflector/.psalm/config.xml b/vendor/sebastian/object-reflector/.psalm/config.xml new file mode 100644 index 00000000..2a4b16f2 --- /dev/null +++ b/vendor/sebastian/object-reflector/.psalm/config.xml @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/vendor/sebastian/object-reflector/ChangeLog.md b/vendor/sebastian/object-reflector/ChangeLog.md new file mode 100644 index 00000000..7fa62e90 --- /dev/null +++ b/vendor/sebastian/object-reflector/ChangeLog.md @@ -0,0 +1,55 @@ +# Change Log + +All notable changes to `sebastianbergmann/object-reflector` are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. + +## [2.0.4] - 2020-10-26 + +### Fixed + +* `SebastianBergmann\ObjectReflector\Exception` now correctly extends `\Throwable` + +## [2.0.3] - 2020-09-28 + +### Changed + +* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3` + +## [2.0.2] - 2020-06-26 + +### Added + +* This component is now supported on PHP 8 + +## [2.0.1] - 2020-06-15 + +### Changed + +* Tests etc. are now ignored for archive exports + +## [2.0.0] - 2020-02-07 + +### Removed + +* This component is no longer supported on PHP 7.0, PHP 7.1, and PHP 7.2 + +## [1.1.1] - 2017-03-29 + +* Fixed [#1](https://github.com/sebastianbergmann/object-reflector/issues/1): Attributes with non-string names are not handled correctly + +## [1.1.0] - 2017-03-16 + +### Changed + +* Changed implementation of `ObjectReflector::getattributes()` to use `(array)` cast instead of `ReflectionObject` + +## 1.0.0 - 2017-03-12 + +* Initial release + +[2.0.4]: https://github.com/sebastianbergmann/object-reflector/compare/2.0.3...2.0.4 +[2.0.3]: https://github.com/sebastianbergmann/object-reflector/compare/2.0.2...2.0.3 +[2.0.2]: https://github.com/sebastianbergmann/object-reflector/compare/2.0.1...2.0.2 +[2.0.1]: https://github.com/sebastianbergmann/object-reflector/compare/2.0.0...2.0.1 +[2.0.0]: https://github.com/sebastianbergmann/object-reflector/compare/1.1.1...2.0.0 +[1.1.1]: https://github.com/sebastianbergmann/object-reflector/compare/1.1.0...1.1.1 +[1.1.0]: https://github.com/sebastianbergmann/object-reflector/compare/1.0.0...1.1.0 diff --git a/vendor/sebastian/object-reflector/LICENSE b/vendor/sebastian/object-reflector/LICENSE new file mode 100644 index 00000000..a80c1619 --- /dev/null +++ b/vendor/sebastian/object-reflector/LICENSE @@ -0,0 +1,33 @@ +Object Reflector + +Copyright (c) 2017-2020, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/sebastian/object-reflector/README.md b/vendor/sebastian/object-reflector/README.md new file mode 100644 index 00000000..b7d5ae95 --- /dev/null +++ b/vendor/sebastian/object-reflector/README.md @@ -0,0 +1,20 @@ +# sebastian/object-reflector + +[![CI Status](https://github.com/sebastianbergmann/object-reflector/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/object-reflector/actions) +[![Type Coverage](https://shepherd.dev/github/sebastianbergmann/object-reflector/coverage.svg)](https://shepherd.dev/github/sebastianbergmann/object-reflector) + +Allows reflection of object attributes, including inherited and non-public ones. + +## Installation + +You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): + +``` +composer require sebastian/object-reflector +``` + +If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency: + +``` +composer require --dev sebastian/object-reflector +``` diff --git a/vendor/sebastian/object-reflector/composer.json b/vendor/sebastian/object-reflector/composer.json new file mode 100644 index 00000000..36a33788 --- /dev/null +++ b/vendor/sebastian/object-reflector/composer.json @@ -0,0 +1,41 @@ +{ + "name": "sebastian/object-reflector", + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "prefer-stable": true, + "config": { + "platform": { + "php": "7.3.0" + }, + "optimize-autoloader": true, + "sort-packages": true + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "autoload-dev": { + "classmap": [ + "tests/_fixture/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + } +} diff --git a/vendor/sebastian/object-reflector/src/Exception.php b/vendor/sebastian/object-reflector/src/Exception.php new file mode 100644 index 00000000..36f8efec --- /dev/null +++ b/vendor/sebastian/object-reflector/src/Exception.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\ObjectReflector; + +use Throwable; + +interface Exception extends Throwable +{ +} diff --git a/vendor/sebastian/object-reflector/src/InvalidArgumentException.php b/vendor/sebastian/object-reflector/src/InvalidArgumentException.php new file mode 100644 index 00000000..34b4cca1 --- /dev/null +++ b/vendor/sebastian/object-reflector/src/InvalidArgumentException.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\ObjectReflector; + +class InvalidArgumentException extends \InvalidArgumentException implements Exception +{ +} diff --git a/vendor/sebastian/object-reflector/src/ObjectReflector.php b/vendor/sebastian/object-reflector/src/ObjectReflector.php new file mode 100644 index 00000000..4abb5f55 --- /dev/null +++ b/vendor/sebastian/object-reflector/src/ObjectReflector.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\ObjectReflector; + +use function count; +use function explode; +use function get_class; +use function is_object; + +class ObjectReflector +{ + /** + * @param object $object + * + * @throws InvalidArgumentException + */ + public function getAttributes($object): array + { + if (!is_object($object)) { + throw new InvalidArgumentException; + } + + $attributes = []; + $className = get_class($object); + + foreach ((array) $object as $name => $value) { + $name = explode("\0", (string) $name); + + if (count($name) === 1) { + $name = $name[0]; + } else { + if ($name[1] !== $className) { + $name = $name[1] . '::' . $name[2]; + } else { + $name = $name[2]; + } + } + + $attributes[$name] = $value; + } + + return $attributes; + } +} diff --git a/vendor/sebastian/recursion-context/ChangeLog.md b/vendor/sebastian/recursion-context/ChangeLog.md new file mode 100644 index 00000000..c1a76516 --- /dev/null +++ b/vendor/sebastian/recursion-context/ChangeLog.md @@ -0,0 +1,40 @@ +# ChangeLog + +All notable changes are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles. + +## [4.0.5] - 2023-02-03 + +### Fixed + +* [#26](https://github.com/sebastianbergmann/recursion-context/pull/26): Don't clobber `null` values if `array_key_exists(PHP_INT_MAX, $array)` + +## [4.0.4] - 2020-10-26 + +### Fixed + +* `SebastianBergmann\RecursionContext\Exception` now correctly extends `\Throwable` + +## [4.0.3] - 2020-09-28 + +### Changed + +* [#21](https://github.com/sebastianbergmann/recursion-context/pull/21): Add type annotations for in/out parameters +* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3` + +## [4.0.2] - 2020-06-26 + +### Added + +* This component is now supported on PHP 8 + +## [4.0.1] - 2020-06-15 + +### Changed + +* Tests etc. are now ignored for archive exports + +[4.0.5]: https://github.com/sebastianbergmann/recursion-context/compare/4.0.4...4.0.5 +[4.0.4]: https://github.com/sebastianbergmann/recursion-context/compare/4.0.3...4.0.4 +[4.0.3]: https://github.com/sebastianbergmann/recursion-context/compare/4.0.2...4.0.3 +[4.0.2]: https://github.com/sebastianbergmann/recursion-context/compare/4.0.1...4.0.2 +[4.0.1]: https://github.com/sebastianbergmann/recursion-context/compare/4.0.0...4.0.1 diff --git a/vendor/sebastian/recursion-context/LICENSE b/vendor/sebastian/recursion-context/LICENSE new file mode 100644 index 00000000..4e9b6371 --- /dev/null +++ b/vendor/sebastian/recursion-context/LICENSE @@ -0,0 +1,33 @@ +Recursion Context + +Copyright (c) 2002-2022, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/sebastian/recursion-context/README.md b/vendor/sebastian/recursion-context/README.md new file mode 100644 index 00000000..8e4d2a08 --- /dev/null +++ b/vendor/sebastian/recursion-context/README.md @@ -0,0 +1,18 @@ +# sebastian/recursion-context + +[![CI Status](https://github.com/sebastianbergmann/recursion-context/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/recursion-context/actions) +[![Type Coverage](https://shepherd.dev/github/sebastianbergmann/recursion-context/coverage.svg)](https://shepherd.dev/github/sebastianbergmann/recursion-context) + +## Installation + +You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): + +``` +composer require sebastian/recursion-context +``` + +If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency: + +``` +composer require --dev sebastian/recursion-context +``` diff --git a/vendor/sebastian/recursion-context/composer.json b/vendor/sebastian/recursion-context/composer.json new file mode 100644 index 00000000..cbd39f76 --- /dev/null +++ b/vendor/sebastian/recursion-context/composer.json @@ -0,0 +1,44 @@ +{ + "name": "sebastian/recursion-context", + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "prefer-stable": true, + "config": { + "platform": { + "php": "7.3.0" + }, + "optimize-autoloader": true, + "sort-packages": true + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + } +} diff --git a/vendor/sebastian/recursion-context/src/Context.php b/vendor/sebastian/recursion-context/src/Context.php new file mode 100644 index 00000000..a647938c --- /dev/null +++ b/vendor/sebastian/recursion-context/src/Context.php @@ -0,0 +1,191 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\RecursionContext; + +use const PHP_INT_MAX; +use const PHP_INT_MIN; +use function array_key_exists; +use function array_pop; +use function array_slice; +use function count; +use function is_array; +use function is_object; +use function random_int; +use function spl_object_hash; +use SplObjectStorage; + +/** + * A context containing previously processed arrays and objects + * when recursively processing a value. + */ +final class Context +{ + /** + * @var array[] + */ + private $arrays; + + /** + * @var SplObjectStorage + */ + private $objects; + + /** + * Initialises the context. + */ + public function __construct() + { + $this->arrays = []; + $this->objects = new SplObjectStorage; + } + + /** + * @codeCoverageIgnore + */ + public function __destruct() + { + foreach ($this->arrays as &$array) { + if (is_array($array)) { + array_pop($array); + array_pop($array); + } + } + } + + /** + * Adds a value to the context. + * + * @param array|object $value the value to add + * + * @throws InvalidArgumentException Thrown if $value is not an array or object + * + * @return bool|int|string the ID of the stored value, either as a string or integer + * + * @psalm-template T + * @psalm-param T $value + * @param-out T $value + */ + public function add(&$value) + { + if (is_array($value)) { + return $this->addArray($value); + } + + if (is_object($value)) { + return $this->addObject($value); + } + + throw new InvalidArgumentException( + 'Only arrays and objects are supported' + ); + } + + /** + * Checks if the given value exists within the context. + * + * @param array|object $value the value to check + * + * @throws InvalidArgumentException Thrown if $value is not an array or object + * + * @return false|int|string the string or integer ID of the stored value if it has already been seen, or false if the value is not stored + * + * @psalm-template T + * @psalm-param T $value + * @param-out T $value + */ + public function contains(&$value) + { + if (is_array($value)) { + return $this->containsArray($value); + } + + if (is_object($value)) { + return $this->containsObject($value); + } + + throw new InvalidArgumentException( + 'Only arrays and objects are supported' + ); + } + + /** + * @return bool|int + */ + private function addArray(array &$array) + { + $key = $this->containsArray($array); + + if ($key !== false) { + return $key; + } + + $key = count($this->arrays); + $this->arrays[] = &$array; + + if (!array_key_exists(PHP_INT_MAX, $array) && !array_key_exists(PHP_INT_MAX - 1, $array)) { + $array[] = $key; + $array[] = $this->objects; + } else { /* cover the improbable case too */ + /* Note that array_slice (used in containsArray) will return the + * last two values added *not necessarily* the highest integer + * keys in the array, so the order of these writes to $array + * is important, but the actual keys used is not. */ + do { + $key = random_int(PHP_INT_MIN, PHP_INT_MAX); + } while (array_key_exists($key, $array)); + + $array[$key] = $key; + + do { + $key = random_int(PHP_INT_MIN, PHP_INT_MAX); + } while (array_key_exists($key, $array)); + + $array[$key] = $this->objects; + } + + return $key; + } + + /** + * @param object $object + */ + private function addObject($object): string + { + if (!$this->objects->contains($object)) { + $this->objects->attach($object); + } + + return spl_object_hash($object); + } + + /** + * @return false|int + */ + private function containsArray(array &$array) + { + $end = array_slice($array, -2); + + return isset($end[1]) && $end[1] === $this->objects ? $end[0] : false; + } + + /** + * @param object $value + * + * @return false|string + */ + private function containsObject($value) + { + if ($this->objects->contains($value)) { + return spl_object_hash($value); + } + + return false; + } +} diff --git a/vendor/sebastian/recursion-context/src/Exception.php b/vendor/sebastian/recursion-context/src/Exception.php new file mode 100644 index 00000000..9389a271 --- /dev/null +++ b/vendor/sebastian/recursion-context/src/Exception.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\RecursionContext; + +use Throwable; + +interface Exception extends Throwable +{ +} diff --git a/vendor/sebastian/recursion-context/src/InvalidArgumentException.php b/vendor/sebastian/recursion-context/src/InvalidArgumentException.php new file mode 100644 index 00000000..93d150bc --- /dev/null +++ b/vendor/sebastian/recursion-context/src/InvalidArgumentException.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\RecursionContext; + +final class InvalidArgumentException extends \InvalidArgumentException implements Exception +{ +} diff --git a/vendor/sebastian/resource-operations/.gitattributes b/vendor/sebastian/resource-operations/.gitattributes new file mode 100644 index 00000000..85e55ebc --- /dev/null +++ b/vendor/sebastian/resource-operations/.gitattributes @@ -0,0 +1,7 @@ +/.github export-ignore +/.php_cs.dist export-ignore +/build.xml export-ignore +/phpunit.xml export-ignore +/tests export-ignore + +*.php diff=php diff --git a/vendor/sebastian/resource-operations/.gitignore b/vendor/sebastian/resource-operations/.gitignore new file mode 100644 index 00000000..a086c781 --- /dev/null +++ b/vendor/sebastian/resource-operations/.gitignore @@ -0,0 +1,6 @@ +/.idea +/.php_cs.cache +/build/FunctionSignatureMap.php +/composer.lock +/vendor +/.phpunit.result.cache diff --git a/vendor/sebastian/resource-operations/ChangeLog.md b/vendor/sebastian/resource-operations/ChangeLog.md new file mode 100644 index 00000000..e6dc7392 --- /dev/null +++ b/vendor/sebastian/resource-operations/ChangeLog.md @@ -0,0 +1,54 @@ +# ChangeLog + +All notable changes are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles. + +## [3.0.3] - 2020-09-28 + +### Changed + +* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3` + +## [3.0.2] - 2020-06-26 + +### Added + +* This component is now supported on PHP 8 + +## [3.0.1] - 2020-06-15 + +### Changed + +* Tests etc. are now ignored for archive exports + +## [3.0.0] - 2020-02-07 + +### Removed + +* This component is no longer supported on PHP 7.1 and PHP 7.2 + +## [2.0.1] - 2018-10-04 + +### Fixed + +* Functions and methods with nullable parameters of type `resource` are now also considered + +## [2.0.0] - 2018-09-27 + +### Changed + +* [FunctionSignatureMap.php](https://raw.githubusercontent.com/phan/phan/master/src/Phan/Language/Internal/FunctionSignatureMap.php) from `phan/phan` is now used instead of [arginfo.php](https://raw.githubusercontent.com/rlerdorf/phan/master/includes/arginfo.php) from `rlerdorf/phan` + +### Removed + +* This component is no longer supported on PHP 5.6 and PHP 7.0 + +## 1.0.0 - 2015-07-28 + +* Initial release + +[3.0.3]: https://github.com/sebastianbergmann/comparator/resource-operations/3.0.2...3.0.3 +[3.0.2]: https://github.com/sebastianbergmann/comparator/resource-operations/3.0.1...3.0.2 +[3.0.1]: https://github.com/sebastianbergmann/comparator/resource-operations/3.0.0...3.0.1 +[3.0.0]: https://github.com/sebastianbergmann/comparator/resource-operations/2.0.1...3.0.0 +[2.0.1]: https://github.com/sebastianbergmann/comparator/resource-operations/2.0.0...2.0.1 +[2.0.0]: https://github.com/sebastianbergmann/comparator/resource-operations/1.0.0...2.0.0 diff --git a/vendor/sebastian/resource-operations/LICENSE b/vendor/sebastian/resource-operations/LICENSE new file mode 100644 index 00000000..dccd6b07 --- /dev/null +++ b/vendor/sebastian/resource-operations/LICENSE @@ -0,0 +1,33 @@ +Resource Operations + +Copyright (c) 2015-2020, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/sebastian/resource-operations/README.md b/vendor/sebastian/resource-operations/README.md new file mode 100644 index 00000000..88b05ccb --- /dev/null +++ b/vendor/sebastian/resource-operations/README.md @@ -0,0 +1,14 @@ +# Resource Operations + +Provides a list of PHP built-in functions that operate on resources. + +## Installation + +You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): + + composer require sebastian/resource-operations + +If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency: + + composer require --dev sebastian/resource-operations + diff --git a/vendor/sebastian/resource-operations/build/generate.php b/vendor/sebastian/resource-operations/build/generate.php new file mode 100755 index 00000000..0354dc45 --- /dev/null +++ b/vendor/sebastian/resource-operations/build/generate.php @@ -0,0 +1,65 @@ +#!/usr/bin/env php + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +$functions = require __DIR__ . '/FunctionSignatureMap.php'; +$resourceFunctions = []; + +foreach ($functions as $function => $arguments) { + foreach ($arguments as $argument) { + if (strpos($argument, '?') === 0) { + $argument = substr($argument, 1); + } + + if ($argument === 'resource') { + $resourceFunctions[] = explode('\'', $function)[0]; + } + } +} + +$resourceFunctions = array_unique($resourceFunctions); +sort($resourceFunctions); + +$buffer = << + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\ResourceOperations; + +final class ResourceOperations +{ + /** + * @return string[] + */ + public static function getFunctions(): array + { + return [ + +EOT; + +foreach ($resourceFunctions as $function) { + $buffer .= sprintf(" '%s',\n", $function); +} + +$buffer .= <<< EOT + ]; + } +} + +EOT; + +file_put_contents(__DIR__ . '/../src/ResourceOperations.php', $buffer); + diff --git a/vendor/sebastian/resource-operations/composer.json b/vendor/sebastian/resource-operations/composer.json new file mode 100644 index 00000000..870be3c1 --- /dev/null +++ b/vendor/sebastian/resource-operations/composer.json @@ -0,0 +1,37 @@ +{ + "name": "sebastian/resource-operations", + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "prefer-stable": true, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "config": { + "platform": { + "php": "7.3.0" + }, + "optimize-autoloader": true, + "sort-packages": true + }, + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + } +} + diff --git a/vendor/sebastian/resource-operations/src/ResourceOperations.php b/vendor/sebastian/resource-operations/src/ResourceOperations.php new file mode 100644 index 00000000..f3911f36 --- /dev/null +++ b/vendor/sebastian/resource-operations/src/ResourceOperations.php @@ -0,0 +1,2232 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\ResourceOperations; + +final class ResourceOperations +{ + /** + * @return string[] + */ + public static function getFunctions(): array + { + return [ + 'Directory::close', + 'Directory::read', + 'Directory::rewind', + 'DirectoryIterator::openFile', + 'FilesystemIterator::openFile', + 'Gmagick::readimagefile', + 'HttpResponse::getRequestBodyStream', + 'HttpResponse::getStream', + 'HttpResponse::setStream', + 'Imagick::pingImageFile', + 'Imagick::readImageFile', + 'Imagick::writeImageFile', + 'Imagick::writeImagesFile', + 'MongoGridFSCursor::__construct', + 'MongoGridFSFile::getResource', + 'MysqlndUhConnection::stmtInit', + 'MysqlndUhConnection::storeResult', + 'MysqlndUhConnection::useResult', + 'PDF_activate_item', + 'PDF_add_launchlink', + 'PDF_add_locallink', + 'PDF_add_nameddest', + 'PDF_add_note', + 'PDF_add_pdflink', + 'PDF_add_table_cell', + 'PDF_add_textflow', + 'PDF_add_thumbnail', + 'PDF_add_weblink', + 'PDF_arc', + 'PDF_arcn', + 'PDF_attach_file', + 'PDF_begin_document', + 'PDF_begin_font', + 'PDF_begin_glyph', + 'PDF_begin_item', + 'PDF_begin_layer', + 'PDF_begin_page', + 'PDF_begin_page_ext', + 'PDF_begin_pattern', + 'PDF_begin_template', + 'PDF_begin_template_ext', + 'PDF_circle', + 'PDF_clip', + 'PDF_close', + 'PDF_close_image', + 'PDF_close_pdi', + 'PDF_close_pdi_page', + 'PDF_closepath', + 'PDF_closepath_fill_stroke', + 'PDF_closepath_stroke', + 'PDF_concat', + 'PDF_continue_text', + 'PDF_create_3dview', + 'PDF_create_action', + 'PDF_create_annotation', + 'PDF_create_bookmark', + 'PDF_create_field', + 'PDF_create_fieldgroup', + 'PDF_create_gstate', + 'PDF_create_pvf', + 'PDF_create_textflow', + 'PDF_curveto', + 'PDF_define_layer', + 'PDF_delete', + 'PDF_delete_pvf', + 'PDF_delete_table', + 'PDF_delete_textflow', + 'PDF_encoding_set_char', + 'PDF_end_document', + 'PDF_end_font', + 'PDF_end_glyph', + 'PDF_end_item', + 'PDF_end_layer', + 'PDF_end_page', + 'PDF_end_page_ext', + 'PDF_end_pattern', + 'PDF_end_template', + 'PDF_endpath', + 'PDF_fill', + 'PDF_fill_imageblock', + 'PDF_fill_pdfblock', + 'PDF_fill_stroke', + 'PDF_fill_textblock', + 'PDF_findfont', + 'PDF_fit_image', + 'PDF_fit_pdi_page', + 'PDF_fit_table', + 'PDF_fit_textflow', + 'PDF_fit_textline', + 'PDF_get_apiname', + 'PDF_get_buffer', + 'PDF_get_errmsg', + 'PDF_get_errnum', + 'PDF_get_parameter', + 'PDF_get_pdi_parameter', + 'PDF_get_pdi_value', + 'PDF_get_value', + 'PDF_info_font', + 'PDF_info_matchbox', + 'PDF_info_table', + 'PDF_info_textflow', + 'PDF_info_textline', + 'PDF_initgraphics', + 'PDF_lineto', + 'PDF_load_3ddata', + 'PDF_load_font', + 'PDF_load_iccprofile', + 'PDF_load_image', + 'PDF_makespotcolor', + 'PDF_moveto', + 'PDF_new', + 'PDF_open_ccitt', + 'PDF_open_file', + 'PDF_open_image', + 'PDF_open_image_file', + 'PDF_open_memory_image', + 'PDF_open_pdi', + 'PDF_open_pdi_document', + 'PDF_open_pdi_page', + 'PDF_pcos_get_number', + 'PDF_pcos_get_stream', + 'PDF_pcos_get_string', + 'PDF_place_image', + 'PDF_place_pdi_page', + 'PDF_process_pdi', + 'PDF_rect', + 'PDF_restore', + 'PDF_resume_page', + 'PDF_rotate', + 'PDF_save', + 'PDF_scale', + 'PDF_set_border_color', + 'PDF_set_border_dash', + 'PDF_set_border_style', + 'PDF_set_gstate', + 'PDF_set_info', + 'PDF_set_layer_dependency', + 'PDF_set_parameter', + 'PDF_set_text_pos', + 'PDF_set_value', + 'PDF_setcolor', + 'PDF_setdash', + 'PDF_setdashpattern', + 'PDF_setflat', + 'PDF_setfont', + 'PDF_setgray', + 'PDF_setgray_fill', + 'PDF_setgray_stroke', + 'PDF_setlinecap', + 'PDF_setlinejoin', + 'PDF_setlinewidth', + 'PDF_setmatrix', + 'PDF_setmiterlimit', + 'PDF_setrgbcolor', + 'PDF_setrgbcolor_fill', + 'PDF_setrgbcolor_stroke', + 'PDF_shading', + 'PDF_shading_pattern', + 'PDF_shfill', + 'PDF_show', + 'PDF_show_boxed', + 'PDF_show_xy', + 'PDF_skew', + 'PDF_stringwidth', + 'PDF_stroke', + 'PDF_suspend_page', + 'PDF_translate', + 'PDF_utf16_to_utf8', + 'PDF_utf32_to_utf16', + 'PDF_utf8_to_utf16', + 'PDO::pgsqlLOBOpen', + 'RarEntry::getStream', + 'SQLite3::openBlob', + 'SWFMovie::saveToFile', + 'SplFileInfo::openFile', + 'SplFileObject::openFile', + 'SplTempFileObject::openFile', + 'V8Js::compileString', + 'V8Js::executeScript', + 'Vtiful\Kernel\Excel::setColumn', + 'Vtiful\Kernel\Excel::setRow', + 'Vtiful\Kernel\Format::align', + 'Vtiful\Kernel\Format::bold', + 'Vtiful\Kernel\Format::italic', + 'Vtiful\Kernel\Format::underline', + 'XMLWriter::openMemory', + 'XMLWriter::openURI', + 'ZipArchive::getStream', + 'Zookeeper::setLogStream', + 'apc_bin_dumpfile', + 'apc_bin_loadfile', + 'bbcode_add_element', + 'bbcode_add_smiley', + 'bbcode_create', + 'bbcode_destroy', + 'bbcode_parse', + 'bbcode_set_arg_parser', + 'bbcode_set_flags', + 'bcompiler_read', + 'bcompiler_write_class', + 'bcompiler_write_constant', + 'bcompiler_write_exe_footer', + 'bcompiler_write_file', + 'bcompiler_write_footer', + 'bcompiler_write_function', + 'bcompiler_write_functions_from_file', + 'bcompiler_write_header', + 'bcompiler_write_included_filename', + 'bzclose', + 'bzerrno', + 'bzerror', + 'bzerrstr', + 'bzflush', + 'bzopen', + 'bzread', + 'bzwrite', + 'cairo_surface_write_to_png', + 'closedir', + 'copy', + 'crack_closedict', + 'crack_opendict', + 'cubrid_bind', + 'cubrid_close_prepare', + 'cubrid_close_request', + 'cubrid_col_get', + 'cubrid_col_size', + 'cubrid_column_names', + 'cubrid_column_types', + 'cubrid_commit', + 'cubrid_connect', + 'cubrid_connect_with_url', + 'cubrid_current_oid', + 'cubrid_db_parameter', + 'cubrid_disconnect', + 'cubrid_drop', + 'cubrid_fetch', + 'cubrid_free_result', + 'cubrid_get', + 'cubrid_get_autocommit', + 'cubrid_get_charset', + 'cubrid_get_class_name', + 'cubrid_get_db_parameter', + 'cubrid_get_query_timeout', + 'cubrid_get_server_info', + 'cubrid_insert_id', + 'cubrid_is_instance', + 'cubrid_lob2_bind', + 'cubrid_lob2_close', + 'cubrid_lob2_export', + 'cubrid_lob2_import', + 'cubrid_lob2_new', + 'cubrid_lob2_read', + 'cubrid_lob2_seek', + 'cubrid_lob2_seek64', + 'cubrid_lob2_size', + 'cubrid_lob2_size64', + 'cubrid_lob2_tell', + 'cubrid_lob2_tell64', + 'cubrid_lob2_write', + 'cubrid_lob_export', + 'cubrid_lob_get', + 'cubrid_lob_send', + 'cubrid_lob_size', + 'cubrid_lock_read', + 'cubrid_lock_write', + 'cubrid_move_cursor', + 'cubrid_next_result', + 'cubrid_num_cols', + 'cubrid_num_rows', + 'cubrid_pconnect', + 'cubrid_pconnect_with_url', + 'cubrid_prepare', + 'cubrid_put', + 'cubrid_query', + 'cubrid_rollback', + 'cubrid_schema', + 'cubrid_seq_add', + 'cubrid_seq_drop', + 'cubrid_seq_insert', + 'cubrid_seq_put', + 'cubrid_set_add', + 'cubrid_set_autocommit', + 'cubrid_set_db_parameter', + 'cubrid_set_drop', + 'cubrid_set_query_timeout', + 'cubrid_unbuffered_query', + 'curl_close', + 'curl_copy_handle', + 'curl_errno', + 'curl_error', + 'curl_escape', + 'curl_exec', + 'curl_getinfo', + 'curl_multi_add_handle', + 'curl_multi_close', + 'curl_multi_errno', + 'curl_multi_exec', + 'curl_multi_getcontent', + 'curl_multi_info_read', + 'curl_multi_remove_handle', + 'curl_multi_select', + 'curl_multi_setopt', + 'curl_pause', + 'curl_reset', + 'curl_setopt', + 'curl_setopt_array', + 'curl_share_close', + 'curl_share_errno', + 'curl_share_init', + 'curl_share_setopt', + 'curl_unescape', + 'cyrus_authenticate', + 'cyrus_bind', + 'cyrus_close', + 'cyrus_connect', + 'cyrus_query', + 'cyrus_unbind', + 'db2_autocommit', + 'db2_bind_param', + 'db2_client_info', + 'db2_close', + 'db2_column_privileges', + 'db2_columns', + 'db2_commit', + 'db2_conn_error', + 'db2_conn_errormsg', + 'db2_connect', + 'db2_cursor_type', + 'db2_exec', + 'db2_execute', + 'db2_fetch_array', + 'db2_fetch_assoc', + 'db2_fetch_both', + 'db2_fetch_object', + 'db2_fetch_row', + 'db2_field_display_size', + 'db2_field_name', + 'db2_field_num', + 'db2_field_precision', + 'db2_field_scale', + 'db2_field_type', + 'db2_field_width', + 'db2_foreign_keys', + 'db2_free_result', + 'db2_free_stmt', + 'db2_get_option', + 'db2_last_insert_id', + 'db2_lob_read', + 'db2_next_result', + 'db2_num_fields', + 'db2_num_rows', + 'db2_pclose', + 'db2_pconnect', + 'db2_prepare', + 'db2_primary_keys', + 'db2_procedure_columns', + 'db2_procedures', + 'db2_result', + 'db2_rollback', + 'db2_server_info', + 'db2_set_option', + 'db2_special_columns', + 'db2_statistics', + 'db2_stmt_error', + 'db2_stmt_errormsg', + 'db2_table_privileges', + 'db2_tables', + 'dba_close', + 'dba_delete', + 'dba_exists', + 'dba_fetch', + 'dba_firstkey', + 'dba_insert', + 'dba_nextkey', + 'dba_open', + 'dba_optimize', + 'dba_popen', + 'dba_replace', + 'dba_sync', + 'dbplus_add', + 'dbplus_aql', + 'dbplus_close', + 'dbplus_curr', + 'dbplus_find', + 'dbplus_first', + 'dbplus_flush', + 'dbplus_freelock', + 'dbplus_freerlocks', + 'dbplus_getlock', + 'dbplus_getunique', + 'dbplus_info', + 'dbplus_last', + 'dbplus_lockrel', + 'dbplus_next', + 'dbplus_open', + 'dbplus_prev', + 'dbplus_rchperm', + 'dbplus_rcreate', + 'dbplus_rcrtexact', + 'dbplus_rcrtlike', + 'dbplus_restorepos', + 'dbplus_rkeys', + 'dbplus_ropen', + 'dbplus_rquery', + 'dbplus_rrename', + 'dbplus_rsecindex', + 'dbplus_runlink', + 'dbplus_rzap', + 'dbplus_savepos', + 'dbplus_setindex', + 'dbplus_setindexbynumber', + 'dbplus_sql', + 'dbplus_tremove', + 'dbplus_undo', + 'dbplus_undoprepare', + 'dbplus_unlockrel', + 'dbplus_unselect', + 'dbplus_update', + 'dbplus_xlockrel', + 'dbplus_xunlockrel', + 'deflate_add', + 'dio_close', + 'dio_fcntl', + 'dio_open', + 'dio_read', + 'dio_seek', + 'dio_stat', + 'dio_tcsetattr', + 'dio_truncate', + 'dio_write', + 'dir', + 'eio_busy', + 'eio_cancel', + 'eio_chmod', + 'eio_chown', + 'eio_close', + 'eio_custom', + 'eio_dup2', + 'eio_fallocate', + 'eio_fchmod', + 'eio_fchown', + 'eio_fdatasync', + 'eio_fstat', + 'eio_fstatvfs', + 'eio_fsync', + 'eio_ftruncate', + 'eio_futime', + 'eio_get_last_error', + 'eio_grp', + 'eio_grp_add', + 'eio_grp_cancel', + 'eio_grp_limit', + 'eio_link', + 'eio_lstat', + 'eio_mkdir', + 'eio_mknod', + 'eio_nop', + 'eio_open', + 'eio_read', + 'eio_readahead', + 'eio_readdir', + 'eio_readlink', + 'eio_realpath', + 'eio_rename', + 'eio_rmdir', + 'eio_seek', + 'eio_sendfile', + 'eio_stat', + 'eio_statvfs', + 'eio_symlink', + 'eio_sync', + 'eio_sync_file_range', + 'eio_syncfs', + 'eio_truncate', + 'eio_unlink', + 'eio_utime', + 'eio_write', + 'enchant_broker_describe', + 'enchant_broker_dict_exists', + 'enchant_broker_free', + 'enchant_broker_free_dict', + 'enchant_broker_get_dict_path', + 'enchant_broker_get_error', + 'enchant_broker_init', + 'enchant_broker_list_dicts', + 'enchant_broker_request_dict', + 'enchant_broker_request_pwl_dict', + 'enchant_broker_set_dict_path', + 'enchant_broker_set_ordering', + 'enchant_dict_add_to_personal', + 'enchant_dict_add_to_session', + 'enchant_dict_check', + 'enchant_dict_describe', + 'enchant_dict_get_error', + 'enchant_dict_is_in_session', + 'enchant_dict_quick_check', + 'enchant_dict_store_replacement', + 'enchant_dict_suggest', + 'event_add', + 'event_base_free', + 'event_base_loop', + 'event_base_loopbreak', + 'event_base_loopexit', + 'event_base_new', + 'event_base_priority_init', + 'event_base_reinit', + 'event_base_set', + 'event_buffer_base_set', + 'event_buffer_disable', + 'event_buffer_enable', + 'event_buffer_fd_set', + 'event_buffer_free', + 'event_buffer_new', + 'event_buffer_priority_set', + 'event_buffer_read', + 'event_buffer_set_callback', + 'event_buffer_timeout_set', + 'event_buffer_watermark_set', + 'event_buffer_write', + 'event_del', + 'event_free', + 'event_new', + 'event_priority_set', + 'event_set', + 'event_timer_add', + 'event_timer_del', + 'event_timer_pending', + 'event_timer_set', + 'expect_expectl', + 'expect_popen', + 'fam_cancel_monitor', + 'fam_close', + 'fam_monitor_collection', + 'fam_monitor_directory', + 'fam_monitor_file', + 'fam_next_event', + 'fam_open', + 'fam_pending', + 'fam_resume_monitor', + 'fam_suspend_monitor', + 'fann_cascadetrain_on_data', + 'fann_cascadetrain_on_file', + 'fann_clear_scaling_params', + 'fann_copy', + 'fann_create_from_file', + 'fann_create_shortcut_array', + 'fann_create_standard', + 'fann_create_standard_array', + 'fann_create_train', + 'fann_create_train_from_callback', + 'fann_descale_input', + 'fann_descale_output', + 'fann_descale_train', + 'fann_destroy', + 'fann_destroy_train', + 'fann_duplicate_train_data', + 'fann_get_MSE', + 'fann_get_activation_function', + 'fann_get_activation_steepness', + 'fann_get_bias_array', + 'fann_get_bit_fail', + 'fann_get_bit_fail_limit', + 'fann_get_cascade_activation_functions', + 'fann_get_cascade_activation_functions_count', + 'fann_get_cascade_activation_steepnesses', + 'fann_get_cascade_activation_steepnesses_count', + 'fann_get_cascade_candidate_change_fraction', + 'fann_get_cascade_candidate_limit', + 'fann_get_cascade_candidate_stagnation_epochs', + 'fann_get_cascade_max_cand_epochs', + 'fann_get_cascade_max_out_epochs', + 'fann_get_cascade_min_cand_epochs', + 'fann_get_cascade_min_out_epochs', + 'fann_get_cascade_num_candidate_groups', + 'fann_get_cascade_num_candidates', + 'fann_get_cascade_output_change_fraction', + 'fann_get_cascade_output_stagnation_epochs', + 'fann_get_cascade_weight_multiplier', + 'fann_get_connection_array', + 'fann_get_connection_rate', + 'fann_get_errno', + 'fann_get_errstr', + 'fann_get_layer_array', + 'fann_get_learning_momentum', + 'fann_get_learning_rate', + 'fann_get_network_type', + 'fann_get_num_input', + 'fann_get_num_layers', + 'fann_get_num_output', + 'fann_get_quickprop_decay', + 'fann_get_quickprop_mu', + 'fann_get_rprop_decrease_factor', + 'fann_get_rprop_delta_max', + 'fann_get_rprop_delta_min', + 'fann_get_rprop_delta_zero', + 'fann_get_rprop_increase_factor', + 'fann_get_sarprop_step_error_shift', + 'fann_get_sarprop_step_error_threshold_factor', + 'fann_get_sarprop_temperature', + 'fann_get_sarprop_weight_decay_shift', + 'fann_get_total_connections', + 'fann_get_total_neurons', + 'fann_get_train_error_function', + 'fann_get_train_stop_function', + 'fann_get_training_algorithm', + 'fann_init_weights', + 'fann_length_train_data', + 'fann_merge_train_data', + 'fann_num_input_train_data', + 'fann_num_output_train_data', + 'fann_randomize_weights', + 'fann_read_train_from_file', + 'fann_reset_errno', + 'fann_reset_errstr', + 'fann_run', + 'fann_save', + 'fann_save_train', + 'fann_scale_input', + 'fann_scale_input_train_data', + 'fann_scale_output', + 'fann_scale_output_train_data', + 'fann_scale_train', + 'fann_scale_train_data', + 'fann_set_activation_function', + 'fann_set_activation_function_hidden', + 'fann_set_activation_function_layer', + 'fann_set_activation_function_output', + 'fann_set_activation_steepness', + 'fann_set_activation_steepness_hidden', + 'fann_set_activation_steepness_layer', + 'fann_set_activation_steepness_output', + 'fann_set_bit_fail_limit', + 'fann_set_callback', + 'fann_set_cascade_activation_functions', + 'fann_set_cascade_activation_steepnesses', + 'fann_set_cascade_candidate_change_fraction', + 'fann_set_cascade_candidate_limit', + 'fann_set_cascade_candidate_stagnation_epochs', + 'fann_set_cascade_max_cand_epochs', + 'fann_set_cascade_max_out_epochs', + 'fann_set_cascade_min_cand_epochs', + 'fann_set_cascade_min_out_epochs', + 'fann_set_cascade_num_candidate_groups', + 'fann_set_cascade_output_change_fraction', + 'fann_set_cascade_output_stagnation_epochs', + 'fann_set_cascade_weight_multiplier', + 'fann_set_error_log', + 'fann_set_input_scaling_params', + 'fann_set_learning_momentum', + 'fann_set_learning_rate', + 'fann_set_output_scaling_params', + 'fann_set_quickprop_decay', + 'fann_set_quickprop_mu', + 'fann_set_rprop_decrease_factor', + 'fann_set_rprop_delta_max', + 'fann_set_rprop_delta_min', + 'fann_set_rprop_delta_zero', + 'fann_set_rprop_increase_factor', + 'fann_set_sarprop_step_error_shift', + 'fann_set_sarprop_step_error_threshold_factor', + 'fann_set_sarprop_temperature', + 'fann_set_sarprop_weight_decay_shift', + 'fann_set_scaling_params', + 'fann_set_train_error_function', + 'fann_set_train_stop_function', + 'fann_set_training_algorithm', + 'fann_set_weight', + 'fann_set_weight_array', + 'fann_shuffle_train_data', + 'fann_subset_train_data', + 'fann_test', + 'fann_test_data', + 'fann_train', + 'fann_train_epoch', + 'fann_train_on_data', + 'fann_train_on_file', + 'fbsql_affected_rows', + 'fbsql_autocommit', + 'fbsql_blob_size', + 'fbsql_change_user', + 'fbsql_clob_size', + 'fbsql_close', + 'fbsql_commit', + 'fbsql_connect', + 'fbsql_create_blob', + 'fbsql_create_clob', + 'fbsql_create_db', + 'fbsql_data_seek', + 'fbsql_database', + 'fbsql_database_password', + 'fbsql_db_query', + 'fbsql_db_status', + 'fbsql_drop_db', + 'fbsql_errno', + 'fbsql_error', + 'fbsql_fetch_array', + 'fbsql_fetch_assoc', + 'fbsql_fetch_field', + 'fbsql_fetch_lengths', + 'fbsql_fetch_object', + 'fbsql_fetch_row', + 'fbsql_field_flags', + 'fbsql_field_len', + 'fbsql_field_name', + 'fbsql_field_seek', + 'fbsql_field_table', + 'fbsql_field_type', + 'fbsql_free_result', + 'fbsql_get_autostart_info', + 'fbsql_hostname', + 'fbsql_insert_id', + 'fbsql_list_dbs', + 'fbsql_list_fields', + 'fbsql_list_tables', + 'fbsql_next_result', + 'fbsql_num_fields', + 'fbsql_num_rows', + 'fbsql_password', + 'fbsql_pconnect', + 'fbsql_query', + 'fbsql_read_blob', + 'fbsql_read_clob', + 'fbsql_result', + 'fbsql_rollback', + 'fbsql_rows_fetched', + 'fbsql_select_db', + 'fbsql_set_characterset', + 'fbsql_set_lob_mode', + 'fbsql_set_password', + 'fbsql_set_transaction', + 'fbsql_start_db', + 'fbsql_stop_db', + 'fbsql_table_name', + 'fbsql_username', + 'fclose', + 'fdf_add_doc_javascript', + 'fdf_add_template', + 'fdf_close', + 'fdf_create', + 'fdf_enum_values', + 'fdf_get_ap', + 'fdf_get_attachment', + 'fdf_get_encoding', + 'fdf_get_file', + 'fdf_get_flags', + 'fdf_get_opt', + 'fdf_get_status', + 'fdf_get_value', + 'fdf_get_version', + 'fdf_next_field_name', + 'fdf_open', + 'fdf_open_string', + 'fdf_remove_item', + 'fdf_save', + 'fdf_save_string', + 'fdf_set_ap', + 'fdf_set_encoding', + 'fdf_set_file', + 'fdf_set_flags', + 'fdf_set_javascript_action', + 'fdf_set_on_import_javascript', + 'fdf_set_opt', + 'fdf_set_status', + 'fdf_set_submit_form_action', + 'fdf_set_target_frame', + 'fdf_set_value', + 'fdf_set_version', + 'feof', + 'fflush', + 'ffmpeg_frame::__construct', + 'ffmpeg_frame::toGDImage', + 'fgetc', + 'fgetcsv', + 'fgets', + 'fgetss', + 'file', + 'file_get_contents', + 'file_put_contents', + 'finfo::buffer', + 'finfo::file', + 'finfo_buffer', + 'finfo_close', + 'finfo_file', + 'finfo_open', + 'finfo_set_flags', + 'flock', + 'fopen', + 'fpassthru', + 'fprintf', + 'fputcsv', + 'fputs', + 'fread', + 'fscanf', + 'fseek', + 'fstat', + 'ftell', + 'ftp_alloc', + 'ftp_append', + 'ftp_cdup', + 'ftp_chdir', + 'ftp_chmod', + 'ftp_close', + 'ftp_delete', + 'ftp_exec', + 'ftp_fget', + 'ftp_fput', + 'ftp_get', + 'ftp_get_option', + 'ftp_login', + 'ftp_mdtm', + 'ftp_mkdir', + 'ftp_mlsd', + 'ftp_nb_continue', + 'ftp_nb_fget', + 'ftp_nb_fput', + 'ftp_nb_get', + 'ftp_nb_put', + 'ftp_nlist', + 'ftp_pasv', + 'ftp_put', + 'ftp_pwd', + 'ftp_quit', + 'ftp_raw', + 'ftp_rawlist', + 'ftp_rename', + 'ftp_rmdir', + 'ftp_set_option', + 'ftp_site', + 'ftp_size', + 'ftp_systype', + 'ftruncate', + 'fwrite', + 'get_resource_type', + 'gmp_div', + 'gnupg::init', + 'gnupg_adddecryptkey', + 'gnupg_addencryptkey', + 'gnupg_addsignkey', + 'gnupg_cleardecryptkeys', + 'gnupg_clearencryptkeys', + 'gnupg_clearsignkeys', + 'gnupg_decrypt', + 'gnupg_decryptverify', + 'gnupg_encrypt', + 'gnupg_encryptsign', + 'gnupg_export', + 'gnupg_geterror', + 'gnupg_getprotocol', + 'gnupg_import', + 'gnupg_init', + 'gnupg_keyinfo', + 'gnupg_setarmor', + 'gnupg_seterrormode', + 'gnupg_setsignmode', + 'gnupg_sign', + 'gnupg_verify', + 'gupnp_context_get_host_ip', + 'gupnp_context_get_port', + 'gupnp_context_get_subscription_timeout', + 'gupnp_context_host_path', + 'gupnp_context_new', + 'gupnp_context_set_subscription_timeout', + 'gupnp_context_timeout_add', + 'gupnp_context_unhost_path', + 'gupnp_control_point_browse_start', + 'gupnp_control_point_browse_stop', + 'gupnp_control_point_callback_set', + 'gupnp_control_point_new', + 'gupnp_device_action_callback_set', + 'gupnp_device_info_get', + 'gupnp_device_info_get_service', + 'gupnp_root_device_get_available', + 'gupnp_root_device_get_relative_location', + 'gupnp_root_device_new', + 'gupnp_root_device_set_available', + 'gupnp_root_device_start', + 'gupnp_root_device_stop', + 'gupnp_service_action_get', + 'gupnp_service_action_return', + 'gupnp_service_action_return_error', + 'gupnp_service_action_set', + 'gupnp_service_freeze_notify', + 'gupnp_service_info_get', + 'gupnp_service_info_get_introspection', + 'gupnp_service_introspection_get_state_variable', + 'gupnp_service_notify', + 'gupnp_service_proxy_action_get', + 'gupnp_service_proxy_action_set', + 'gupnp_service_proxy_add_notify', + 'gupnp_service_proxy_callback_set', + 'gupnp_service_proxy_get_subscribed', + 'gupnp_service_proxy_remove_notify', + 'gupnp_service_proxy_send_action', + 'gupnp_service_proxy_set_subscribed', + 'gupnp_service_thaw_notify', + 'gzclose', + 'gzeof', + 'gzgetc', + 'gzgets', + 'gzgetss', + 'gzpassthru', + 'gzputs', + 'gzread', + 'gzrewind', + 'gzseek', + 'gztell', + 'gzwrite', + 'hash_update_stream', + 'http\Env\Response::send', + 'http_get_request_body_stream', + 'ibase_add_user', + 'ibase_affected_rows', + 'ibase_backup', + 'ibase_blob_add', + 'ibase_blob_cancel', + 'ibase_blob_close', + 'ibase_blob_create', + 'ibase_blob_get', + 'ibase_blob_open', + 'ibase_close', + 'ibase_commit', + 'ibase_commit_ret', + 'ibase_connect', + 'ibase_db_info', + 'ibase_delete_user', + 'ibase_drop_db', + 'ibase_execute', + 'ibase_fetch_assoc', + 'ibase_fetch_object', + 'ibase_fetch_row', + 'ibase_field_info', + 'ibase_free_event_handler', + 'ibase_free_query', + 'ibase_free_result', + 'ibase_gen_id', + 'ibase_maintain_db', + 'ibase_modify_user', + 'ibase_name_result', + 'ibase_num_fields', + 'ibase_num_params', + 'ibase_param_info', + 'ibase_pconnect', + 'ibase_prepare', + 'ibase_query', + 'ibase_restore', + 'ibase_rollback', + 'ibase_rollback_ret', + 'ibase_server_info', + 'ibase_service_attach', + 'ibase_service_detach', + 'ibase_set_event_handler', + 'ibase_trans', + 'ifx_affected_rows', + 'ifx_close', + 'ifx_connect', + 'ifx_do', + 'ifx_error', + 'ifx_fetch_row', + 'ifx_fieldproperties', + 'ifx_fieldtypes', + 'ifx_free_result', + 'ifx_getsqlca', + 'ifx_htmltbl_result', + 'ifx_num_fields', + 'ifx_num_rows', + 'ifx_pconnect', + 'ifx_prepare', + 'ifx_query', + 'image2wbmp', + 'imageaffine', + 'imagealphablending', + 'imageantialias', + 'imagearc', + 'imagebmp', + 'imagechar', + 'imagecharup', + 'imagecolorallocate', + 'imagecolorallocatealpha', + 'imagecolorat', + 'imagecolorclosest', + 'imagecolorclosestalpha', + 'imagecolorclosesthwb', + 'imagecolordeallocate', + 'imagecolorexact', + 'imagecolorexactalpha', + 'imagecolormatch', + 'imagecolorresolve', + 'imagecolorresolvealpha', + 'imagecolorset', + 'imagecolorsforindex', + 'imagecolorstotal', + 'imagecolortransparent', + 'imageconvolution', + 'imagecopy', + 'imagecopymerge', + 'imagecopymergegray', + 'imagecopyresampled', + 'imagecopyresized', + 'imagecrop', + 'imagecropauto', + 'imagedashedline', + 'imagedestroy', + 'imageellipse', + 'imagefill', + 'imagefilledarc', + 'imagefilledellipse', + 'imagefilledpolygon', + 'imagefilledrectangle', + 'imagefilltoborder', + 'imagefilter', + 'imageflip', + 'imagefttext', + 'imagegammacorrect', + 'imagegd', + 'imagegd2', + 'imagegetclip', + 'imagegif', + 'imagegrabscreen', + 'imagegrabwindow', + 'imageinterlace', + 'imageistruecolor', + 'imagejpeg', + 'imagelayereffect', + 'imageline', + 'imageopenpolygon', + 'imagepalettecopy', + 'imagepalettetotruecolor', + 'imagepng', + 'imagepolygon', + 'imagepsencodefont', + 'imagepsextendfont', + 'imagepsfreefont', + 'imagepsloadfont', + 'imagepsslantfont', + 'imagepstext', + 'imagerectangle', + 'imageresolution', + 'imagerotate', + 'imagesavealpha', + 'imagescale', + 'imagesetbrush', + 'imagesetclip', + 'imagesetinterpolation', + 'imagesetpixel', + 'imagesetstyle', + 'imagesetthickness', + 'imagesettile', + 'imagestring', + 'imagestringup', + 'imagesx', + 'imagesy', + 'imagetruecolortopalette', + 'imagettftext', + 'imagewbmp', + 'imagewebp', + 'imagexbm', + 'imap_append', + 'imap_body', + 'imap_bodystruct', + 'imap_check', + 'imap_clearflag_full', + 'imap_close', + 'imap_create', + 'imap_createmailbox', + 'imap_delete', + 'imap_deletemailbox', + 'imap_expunge', + 'imap_fetch_overview', + 'imap_fetchbody', + 'imap_fetchheader', + 'imap_fetchmime', + 'imap_fetchstructure', + 'imap_fetchtext', + 'imap_gc', + 'imap_get_quota', + 'imap_get_quotaroot', + 'imap_getacl', + 'imap_getmailboxes', + 'imap_getsubscribed', + 'imap_header', + 'imap_headerinfo', + 'imap_headers', + 'imap_list', + 'imap_listmailbox', + 'imap_listscan', + 'imap_listsubscribed', + 'imap_lsub', + 'imap_mail_copy', + 'imap_mail_move', + 'imap_mailboxmsginfo', + 'imap_msgno', + 'imap_num_msg', + 'imap_num_recent', + 'imap_ping', + 'imap_rename', + 'imap_renamemailbox', + 'imap_reopen', + 'imap_savebody', + 'imap_scan', + 'imap_scanmailbox', + 'imap_search', + 'imap_set_quota', + 'imap_setacl', + 'imap_setflag_full', + 'imap_sort', + 'imap_status', + 'imap_subscribe', + 'imap_thread', + 'imap_uid', + 'imap_undelete', + 'imap_unsubscribe', + 'inflate_add', + 'inflate_get_read_len', + 'inflate_get_status', + 'ingres_autocommit', + 'ingres_autocommit_state', + 'ingres_charset', + 'ingres_close', + 'ingres_commit', + 'ingres_connect', + 'ingres_cursor', + 'ingres_errno', + 'ingres_error', + 'ingres_errsqlstate', + 'ingres_escape_string', + 'ingres_execute', + 'ingres_fetch_array', + 'ingres_fetch_assoc', + 'ingres_fetch_object', + 'ingres_fetch_proc_return', + 'ingres_fetch_row', + 'ingres_field_length', + 'ingres_field_name', + 'ingres_field_nullable', + 'ingres_field_precision', + 'ingres_field_scale', + 'ingres_field_type', + 'ingres_free_result', + 'ingres_next_error', + 'ingres_num_fields', + 'ingres_num_rows', + 'ingres_pconnect', + 'ingres_prepare', + 'ingres_query', + 'ingres_result_seek', + 'ingres_rollback', + 'ingres_set_environment', + 'ingres_unbuffered_query', + 'inotify_add_watch', + 'inotify_init', + 'inotify_queue_len', + 'inotify_read', + 'inotify_rm_watch', + 'kadm5_chpass_principal', + 'kadm5_create_principal', + 'kadm5_delete_principal', + 'kadm5_destroy', + 'kadm5_flush', + 'kadm5_get_policies', + 'kadm5_get_principal', + 'kadm5_get_principals', + 'kadm5_init_with_password', + 'kadm5_modify_principal', + 'ldap_add', + 'ldap_bind', + 'ldap_close', + 'ldap_compare', + 'ldap_control_paged_result', + 'ldap_control_paged_result_response', + 'ldap_count_entries', + 'ldap_delete', + 'ldap_errno', + 'ldap_error', + 'ldap_exop', + 'ldap_exop_passwd', + 'ldap_exop_refresh', + 'ldap_exop_whoami', + 'ldap_first_attribute', + 'ldap_first_entry', + 'ldap_first_reference', + 'ldap_free_result', + 'ldap_get_attributes', + 'ldap_get_dn', + 'ldap_get_entries', + 'ldap_get_option', + 'ldap_get_values', + 'ldap_get_values_len', + 'ldap_mod_add', + 'ldap_mod_del', + 'ldap_mod_replace', + 'ldap_modify', + 'ldap_modify_batch', + 'ldap_next_attribute', + 'ldap_next_entry', + 'ldap_next_reference', + 'ldap_parse_exop', + 'ldap_parse_reference', + 'ldap_parse_result', + 'ldap_rename', + 'ldap_sasl_bind', + 'ldap_set_option', + 'ldap_set_rebind_proc', + 'ldap_sort', + 'ldap_start_tls', + 'ldap_unbind', + 'libxml_set_streams_context', + 'm_checkstatus', + 'm_completeauthorizations', + 'm_connect', + 'm_connectionerror', + 'm_deletetrans', + 'm_destroyconn', + 'm_getcell', + 'm_getcellbynum', + 'm_getcommadelimited', + 'm_getheader', + 'm_initconn', + 'm_iscommadelimited', + 'm_maxconntimeout', + 'm_monitor', + 'm_numcolumns', + 'm_numrows', + 'm_parsecommadelimited', + 'm_responsekeys', + 'm_responseparam', + 'm_returnstatus', + 'm_setblocking', + 'm_setdropfile', + 'm_setip', + 'm_setssl', + 'm_setssl_cafile', + 'm_setssl_files', + 'm_settimeout', + 'm_transactionssent', + 'm_transinqueue', + 'm_transkeyval', + 'm_transnew', + 'm_transsend', + 'm_validateidentifier', + 'm_verifyconnection', + 'm_verifysslcert', + 'mailparse_determine_best_xfer_encoding', + 'mailparse_msg_create', + 'mailparse_msg_extract_part', + 'mailparse_msg_extract_part_file', + 'mailparse_msg_extract_whole_part_file', + 'mailparse_msg_free', + 'mailparse_msg_get_part', + 'mailparse_msg_get_part_data', + 'mailparse_msg_get_structure', + 'mailparse_msg_parse', + 'mailparse_msg_parse_file', + 'mailparse_stream_encode', + 'mailparse_uudecode_all', + 'maxdb::use_result', + 'maxdb_affected_rows', + 'maxdb_connect', + 'maxdb_disable_rpl_parse', + 'maxdb_dump_debug_info', + 'maxdb_embedded_connect', + 'maxdb_enable_reads_from_master', + 'maxdb_enable_rpl_parse', + 'maxdb_errno', + 'maxdb_error', + 'maxdb_fetch_lengths', + 'maxdb_field_tell', + 'maxdb_get_host_info', + 'maxdb_get_proto_info', + 'maxdb_get_server_info', + 'maxdb_get_server_version', + 'maxdb_info', + 'maxdb_init', + 'maxdb_insert_id', + 'maxdb_master_query', + 'maxdb_more_results', + 'maxdb_next_result', + 'maxdb_num_fields', + 'maxdb_num_rows', + 'maxdb_rpl_parse_enabled', + 'maxdb_rpl_probe', + 'maxdb_select_db', + 'maxdb_sqlstate', + 'maxdb_stmt::result_metadata', + 'maxdb_stmt_affected_rows', + 'maxdb_stmt_errno', + 'maxdb_stmt_error', + 'maxdb_stmt_num_rows', + 'maxdb_stmt_param_count', + 'maxdb_stmt_result_metadata', + 'maxdb_stmt_sqlstate', + 'maxdb_thread_id', + 'maxdb_use_result', + 'maxdb_warning_count', + 'mcrypt_enc_get_algorithms_name', + 'mcrypt_enc_get_block_size', + 'mcrypt_enc_get_iv_size', + 'mcrypt_enc_get_key_size', + 'mcrypt_enc_get_modes_name', + 'mcrypt_enc_get_supported_key_sizes', + 'mcrypt_enc_is_block_algorithm', + 'mcrypt_enc_is_block_algorithm_mode', + 'mcrypt_enc_is_block_mode', + 'mcrypt_enc_self_test', + 'mcrypt_generic', + 'mcrypt_generic_deinit', + 'mcrypt_generic_end', + 'mcrypt_generic_init', + 'mcrypt_module_close', + 'mcrypt_module_open', + 'mdecrypt_generic', + 'mkdir', + 'mqseries_back', + 'mqseries_begin', + 'mqseries_close', + 'mqseries_cmit', + 'mqseries_conn', + 'mqseries_connx', + 'mqseries_disc', + 'mqseries_get', + 'mqseries_inq', + 'mqseries_open', + 'mqseries_put', + 'mqseries_put1', + 'mqseries_set', + 'msg_get_queue', + 'msg_receive', + 'msg_remove_queue', + 'msg_send', + 'msg_set_queue', + 'msg_stat_queue', + 'msql_affected_rows', + 'msql_close', + 'msql_connect', + 'msql_create_db', + 'msql_data_seek', + 'msql_db_query', + 'msql_drop_db', + 'msql_fetch_array', + 'msql_fetch_field', + 'msql_fetch_object', + 'msql_fetch_row', + 'msql_field_flags', + 'msql_field_len', + 'msql_field_name', + 'msql_field_seek', + 'msql_field_table', + 'msql_field_type', + 'msql_free_result', + 'msql_list_dbs', + 'msql_list_fields', + 'msql_list_tables', + 'msql_num_fields', + 'msql_num_rows', + 'msql_pconnect', + 'msql_query', + 'msql_result', + 'msql_select_db', + 'mssql_bind', + 'mssql_close', + 'mssql_connect', + 'mssql_data_seek', + 'mssql_execute', + 'mssql_fetch_array', + 'mssql_fetch_assoc', + 'mssql_fetch_batch', + 'mssql_fetch_field', + 'mssql_fetch_object', + 'mssql_fetch_row', + 'mssql_field_length', + 'mssql_field_name', + 'mssql_field_seek', + 'mssql_field_type', + 'mssql_free_result', + 'mssql_free_statement', + 'mssql_init', + 'mssql_next_result', + 'mssql_num_fields', + 'mssql_num_rows', + 'mssql_pconnect', + 'mssql_query', + 'mssql_result', + 'mssql_rows_affected', + 'mssql_select_db', + 'mysql_affected_rows', + 'mysql_client_encoding', + 'mysql_close', + 'mysql_connect', + 'mysql_create_db', + 'mysql_data_seek', + 'mysql_db_name', + 'mysql_db_query', + 'mysql_drop_db', + 'mysql_errno', + 'mysql_error', + 'mysql_fetch_array', + 'mysql_fetch_assoc', + 'mysql_fetch_field', + 'mysql_fetch_lengths', + 'mysql_fetch_object', + 'mysql_fetch_row', + 'mysql_field_flags', + 'mysql_field_len', + 'mysql_field_name', + 'mysql_field_seek', + 'mysql_field_table', + 'mysql_field_type', + 'mysql_free_result', + 'mysql_get_host_info', + 'mysql_get_proto_info', + 'mysql_get_server_info', + 'mysql_info', + 'mysql_insert_id', + 'mysql_list_dbs', + 'mysql_list_fields', + 'mysql_list_processes', + 'mysql_list_tables', + 'mysql_num_fields', + 'mysql_num_rows', + 'mysql_pconnect', + 'mysql_ping', + 'mysql_query', + 'mysql_real_escape_string', + 'mysql_result', + 'mysql_select_db', + 'mysql_set_charset', + 'mysql_stat', + 'mysql_tablename', + 'mysql_thread_id', + 'mysql_unbuffered_query', + 'mysqlnd_uh_convert_to_mysqlnd', + 'ncurses_bottom_panel', + 'ncurses_del_panel', + 'ncurses_delwin', + 'ncurses_getmaxyx', + 'ncurses_getyx', + 'ncurses_hide_panel', + 'ncurses_keypad', + 'ncurses_meta', + 'ncurses_move_panel', + 'ncurses_mvwaddstr', + 'ncurses_new_panel', + 'ncurses_newpad', + 'ncurses_newwin', + 'ncurses_panel_above', + 'ncurses_panel_below', + 'ncurses_panel_window', + 'ncurses_pnoutrefresh', + 'ncurses_prefresh', + 'ncurses_replace_panel', + 'ncurses_show_panel', + 'ncurses_top_panel', + 'ncurses_waddch', + 'ncurses_waddstr', + 'ncurses_wattroff', + 'ncurses_wattron', + 'ncurses_wattrset', + 'ncurses_wborder', + 'ncurses_wclear', + 'ncurses_wcolor_set', + 'ncurses_werase', + 'ncurses_wgetch', + 'ncurses_whline', + 'ncurses_wmouse_trafo', + 'ncurses_wmove', + 'ncurses_wnoutrefresh', + 'ncurses_wrefresh', + 'ncurses_wstandend', + 'ncurses_wstandout', + 'ncurses_wvline', + 'newt_button', + 'newt_button_bar', + 'newt_checkbox', + 'newt_checkbox_get_value', + 'newt_checkbox_set_flags', + 'newt_checkbox_set_value', + 'newt_checkbox_tree', + 'newt_checkbox_tree_add_item', + 'newt_checkbox_tree_find_item', + 'newt_checkbox_tree_get_current', + 'newt_checkbox_tree_get_entry_value', + 'newt_checkbox_tree_get_multi_selection', + 'newt_checkbox_tree_get_selection', + 'newt_checkbox_tree_multi', + 'newt_checkbox_tree_set_current', + 'newt_checkbox_tree_set_entry', + 'newt_checkbox_tree_set_entry_value', + 'newt_checkbox_tree_set_width', + 'newt_compact_button', + 'newt_component_add_callback', + 'newt_component_takes_focus', + 'newt_create_grid', + 'newt_draw_form', + 'newt_entry', + 'newt_entry_get_value', + 'newt_entry_set', + 'newt_entry_set_filter', + 'newt_entry_set_flags', + 'newt_form', + 'newt_form_add_component', + 'newt_form_add_components', + 'newt_form_add_hot_key', + 'newt_form_destroy', + 'newt_form_get_current', + 'newt_form_run', + 'newt_form_set_background', + 'newt_form_set_height', + 'newt_form_set_size', + 'newt_form_set_timer', + 'newt_form_set_width', + 'newt_form_watch_fd', + 'newt_grid_add_components_to_form', + 'newt_grid_basic_window', + 'newt_grid_free', + 'newt_grid_get_size', + 'newt_grid_h_close_stacked', + 'newt_grid_h_stacked', + 'newt_grid_place', + 'newt_grid_set_field', + 'newt_grid_simple_window', + 'newt_grid_v_close_stacked', + 'newt_grid_v_stacked', + 'newt_grid_wrapped_window', + 'newt_grid_wrapped_window_at', + 'newt_label', + 'newt_label_set_text', + 'newt_listbox', + 'newt_listbox_append_entry', + 'newt_listbox_clear', + 'newt_listbox_clear_selection', + 'newt_listbox_delete_entry', + 'newt_listbox_get_current', + 'newt_listbox_get_selection', + 'newt_listbox_insert_entry', + 'newt_listbox_item_count', + 'newt_listbox_select_item', + 'newt_listbox_set_current', + 'newt_listbox_set_current_by_key', + 'newt_listbox_set_data', + 'newt_listbox_set_entry', + 'newt_listbox_set_width', + 'newt_listitem', + 'newt_listitem_get_data', + 'newt_listitem_set', + 'newt_radio_get_current', + 'newt_radiobutton', + 'newt_run_form', + 'newt_scale', + 'newt_scale_set', + 'newt_scrollbar_set', + 'newt_textbox', + 'newt_textbox_get_num_lines', + 'newt_textbox_reflowed', + 'newt_textbox_set_height', + 'newt_textbox_set_text', + 'newt_vertical_scrollbar', + 'oci_bind_array_by_name', + 'oci_bind_by_name', + 'oci_cancel', + 'oci_close', + 'oci_commit', + 'oci_connect', + 'oci_define_by_name', + 'oci_error', + 'oci_execute', + 'oci_fetch', + 'oci_fetch_all', + 'oci_fetch_array', + 'oci_fetch_assoc', + 'oci_fetch_object', + 'oci_fetch_row', + 'oci_field_is_null', + 'oci_field_name', + 'oci_field_precision', + 'oci_field_scale', + 'oci_field_size', + 'oci_field_type', + 'oci_field_type_raw', + 'oci_free_cursor', + 'oci_free_statement', + 'oci_get_implicit_resultset', + 'oci_new_collection', + 'oci_new_connect', + 'oci_new_cursor', + 'oci_new_descriptor', + 'oci_num_fields', + 'oci_num_rows', + 'oci_parse', + 'oci_pconnect', + 'oci_register_taf_callback', + 'oci_result', + 'oci_rollback', + 'oci_server_version', + 'oci_set_action', + 'oci_set_client_identifier', + 'oci_set_client_info', + 'oci_set_module_name', + 'oci_set_prefetch', + 'oci_statement_type', + 'oci_unregister_taf_callback', + 'odbc_autocommit', + 'odbc_close', + 'odbc_columnprivileges', + 'odbc_columns', + 'odbc_commit', + 'odbc_connect', + 'odbc_cursor', + 'odbc_data_source', + 'odbc_do', + 'odbc_error', + 'odbc_errormsg', + 'odbc_exec', + 'odbc_execute', + 'odbc_fetch_array', + 'odbc_fetch_into', + 'odbc_fetch_row', + 'odbc_field_len', + 'odbc_field_name', + 'odbc_field_num', + 'odbc_field_precision', + 'odbc_field_scale', + 'odbc_field_type', + 'odbc_foreignkeys', + 'odbc_free_result', + 'odbc_gettypeinfo', + 'odbc_next_result', + 'odbc_num_fields', + 'odbc_num_rows', + 'odbc_pconnect', + 'odbc_prepare', + 'odbc_primarykeys', + 'odbc_procedurecolumns', + 'odbc_procedures', + 'odbc_result', + 'odbc_result_all', + 'odbc_rollback', + 'odbc_setoption', + 'odbc_specialcolumns', + 'odbc_statistics', + 'odbc_tableprivileges', + 'odbc_tables', + 'openal_buffer_create', + 'openal_buffer_data', + 'openal_buffer_destroy', + 'openal_buffer_get', + 'openal_buffer_loadwav', + 'openal_context_create', + 'openal_context_current', + 'openal_context_destroy', + 'openal_context_process', + 'openal_context_suspend', + 'openal_device_close', + 'openal_device_open', + 'openal_source_create', + 'openal_source_destroy', + 'openal_source_get', + 'openal_source_pause', + 'openal_source_play', + 'openal_source_rewind', + 'openal_source_set', + 'openal_source_stop', + 'openal_stream', + 'opendir', + 'openssl_csr_new', + 'openssl_dh_compute_key', + 'openssl_free_key', + 'openssl_pkey_export', + 'openssl_pkey_free', + 'openssl_pkey_get_details', + 'openssl_spki_new', + 'openssl_x509_free', + 'pclose', + 'pfsockopen', + 'pg_affected_rows', + 'pg_cancel_query', + 'pg_client_encoding', + 'pg_close', + 'pg_connect_poll', + 'pg_connection_busy', + 'pg_connection_reset', + 'pg_connection_status', + 'pg_consume_input', + 'pg_convert', + 'pg_copy_from', + 'pg_copy_to', + 'pg_dbname', + 'pg_delete', + 'pg_end_copy', + 'pg_escape_bytea', + 'pg_escape_identifier', + 'pg_escape_literal', + 'pg_escape_string', + 'pg_execute', + 'pg_fetch_all', + 'pg_fetch_all_columns', + 'pg_fetch_array', + 'pg_fetch_assoc', + 'pg_fetch_row', + 'pg_field_name', + 'pg_field_num', + 'pg_field_size', + 'pg_field_table', + 'pg_field_type', + 'pg_field_type_oid', + 'pg_flush', + 'pg_free_result', + 'pg_get_notify', + 'pg_get_pid', + 'pg_get_result', + 'pg_host', + 'pg_insert', + 'pg_last_error', + 'pg_last_notice', + 'pg_last_oid', + 'pg_lo_close', + 'pg_lo_create', + 'pg_lo_export', + 'pg_lo_import', + 'pg_lo_open', + 'pg_lo_read', + 'pg_lo_read_all', + 'pg_lo_seek', + 'pg_lo_tell', + 'pg_lo_truncate', + 'pg_lo_unlink', + 'pg_lo_write', + 'pg_meta_data', + 'pg_num_fields', + 'pg_num_rows', + 'pg_options', + 'pg_parameter_status', + 'pg_ping', + 'pg_port', + 'pg_prepare', + 'pg_put_line', + 'pg_query', + 'pg_query_params', + 'pg_result_error', + 'pg_result_error_field', + 'pg_result_seek', + 'pg_result_status', + 'pg_select', + 'pg_send_execute', + 'pg_send_prepare', + 'pg_send_query', + 'pg_send_query_params', + 'pg_set_client_encoding', + 'pg_set_error_verbosity', + 'pg_socket', + 'pg_trace', + 'pg_transaction_status', + 'pg_tty', + 'pg_untrace', + 'pg_update', + 'pg_version', + 'php_user_filter::filter', + 'proc_close', + 'proc_get_status', + 'proc_terminate', + 'ps_add_bookmark', + 'ps_add_launchlink', + 'ps_add_locallink', + 'ps_add_note', + 'ps_add_pdflink', + 'ps_add_weblink', + 'ps_arc', + 'ps_arcn', + 'ps_begin_page', + 'ps_begin_pattern', + 'ps_begin_template', + 'ps_circle', + 'ps_clip', + 'ps_close', + 'ps_close_image', + 'ps_closepath', + 'ps_closepath_stroke', + 'ps_continue_text', + 'ps_curveto', + 'ps_delete', + 'ps_end_page', + 'ps_end_pattern', + 'ps_end_template', + 'ps_fill', + 'ps_fill_stroke', + 'ps_findfont', + 'ps_get_buffer', + 'ps_get_parameter', + 'ps_get_value', + 'ps_hyphenate', + 'ps_include_file', + 'ps_lineto', + 'ps_makespotcolor', + 'ps_moveto', + 'ps_new', + 'ps_open_file', + 'ps_open_image', + 'ps_open_image_file', + 'ps_open_memory_image', + 'ps_place_image', + 'ps_rect', + 'ps_restore', + 'ps_rotate', + 'ps_save', + 'ps_scale', + 'ps_set_border_color', + 'ps_set_border_dash', + 'ps_set_border_style', + 'ps_set_info', + 'ps_set_parameter', + 'ps_set_text_pos', + 'ps_set_value', + 'ps_setcolor', + 'ps_setdash', + 'ps_setflat', + 'ps_setfont', + 'ps_setgray', + 'ps_setlinecap', + 'ps_setlinejoin', + 'ps_setlinewidth', + 'ps_setmiterlimit', + 'ps_setoverprintmode', + 'ps_setpolydash', + 'ps_shading', + 'ps_shading_pattern', + 'ps_shfill', + 'ps_show', + 'ps_show2', + 'ps_show_boxed', + 'ps_show_xy', + 'ps_show_xy2', + 'ps_string_geometry', + 'ps_stringwidth', + 'ps_stroke', + 'ps_symbol', + 'ps_symbol_name', + 'ps_symbol_width', + 'ps_translate', + 'px_close', + 'px_create_fp', + 'px_date2string', + 'px_delete', + 'px_delete_record', + 'px_get_field', + 'px_get_info', + 'px_get_parameter', + 'px_get_record', + 'px_get_schema', + 'px_get_value', + 'px_insert_record', + 'px_new', + 'px_numfields', + 'px_numrecords', + 'px_open_fp', + 'px_put_record', + 'px_retrieve_record', + 'px_set_blob_file', + 'px_set_parameter', + 'px_set_tablename', + 'px_set_targetencoding', + 'px_set_value', + 'px_timestamp2string', + 'px_update_record', + 'radius_acct_open', + 'radius_add_server', + 'radius_auth_open', + 'radius_close', + 'radius_config', + 'radius_create_request', + 'radius_demangle', + 'radius_demangle_mppe_key', + 'radius_get_attr', + 'radius_put_addr', + 'radius_put_attr', + 'radius_put_int', + 'radius_put_string', + 'radius_put_vendor_addr', + 'radius_put_vendor_attr', + 'radius_put_vendor_int', + 'radius_put_vendor_string', + 'radius_request_authenticator', + 'radius_salt_encrypt_attr', + 'radius_send_request', + 'radius_server_secret', + 'radius_strerror', + 'readdir', + 'readfile', + 'recode_file', + 'rename', + 'rewind', + 'rewinddir', + 'rmdir', + 'rpm_close', + 'rpm_get_tag', + 'rpm_open', + 'sapi_windows_vt100_support', + 'scandir', + 'sem_acquire', + 'sem_get', + 'sem_release', + 'sem_remove', + 'set_file_buffer', + 'shm_attach', + 'shm_detach', + 'shm_get_var', + 'shm_has_var', + 'shm_put_var', + 'shm_remove', + 'shm_remove_var', + 'shmop_close', + 'shmop_delete', + 'shmop_open', + 'shmop_read', + 'shmop_size', + 'shmop_write', + 'socket_accept', + 'socket_addrinfo_bind', + 'socket_addrinfo_connect', + 'socket_addrinfo_explain', + 'socket_bind', + 'socket_clear_error', + 'socket_close', + 'socket_connect', + 'socket_export_stream', + 'socket_get_option', + 'socket_get_status', + 'socket_getopt', + 'socket_getpeername', + 'socket_getsockname', + 'socket_import_stream', + 'socket_last_error', + 'socket_listen', + 'socket_read', + 'socket_recv', + 'socket_recvfrom', + 'socket_recvmsg', + 'socket_send', + 'socket_sendmsg', + 'socket_sendto', + 'socket_set_block', + 'socket_set_blocking', + 'socket_set_nonblock', + 'socket_set_option', + 'socket_set_timeout', + 'socket_shutdown', + 'socket_write', + 'sqlite_close', + 'sqlite_fetch_string', + 'sqlite_has_more', + 'sqlite_open', + 'sqlite_popen', + 'sqlsrv_begin_transaction', + 'sqlsrv_cancel', + 'sqlsrv_client_info', + 'sqlsrv_close', + 'sqlsrv_commit', + 'sqlsrv_connect', + 'sqlsrv_execute', + 'sqlsrv_fetch', + 'sqlsrv_fetch_array', + 'sqlsrv_fetch_object', + 'sqlsrv_field_metadata', + 'sqlsrv_free_stmt', + 'sqlsrv_get_field', + 'sqlsrv_has_rows', + 'sqlsrv_next_result', + 'sqlsrv_num_fields', + 'sqlsrv_num_rows', + 'sqlsrv_prepare', + 'sqlsrv_query', + 'sqlsrv_rollback', + 'sqlsrv_rows_affected', + 'sqlsrv_send_stream_data', + 'sqlsrv_server_info', + 'ssh2_auth_agent', + 'ssh2_auth_hostbased_file', + 'ssh2_auth_none', + 'ssh2_auth_password', + 'ssh2_auth_pubkey_file', + 'ssh2_disconnect', + 'ssh2_exec', + 'ssh2_fetch_stream', + 'ssh2_fingerprint', + 'ssh2_methods_negotiated', + 'ssh2_publickey_add', + 'ssh2_publickey_init', + 'ssh2_publickey_list', + 'ssh2_publickey_remove', + 'ssh2_scp_recv', + 'ssh2_scp_send', + 'ssh2_sftp', + 'ssh2_sftp_chmod', + 'ssh2_sftp_lstat', + 'ssh2_sftp_mkdir', + 'ssh2_sftp_readlink', + 'ssh2_sftp_realpath', + 'ssh2_sftp_rename', + 'ssh2_sftp_rmdir', + 'ssh2_sftp_stat', + 'ssh2_sftp_symlink', + 'ssh2_sftp_unlink', + 'ssh2_shell', + 'ssh2_tunnel', + 'stomp_connect', + 'streamWrapper::stream_cast', + 'stream_bucket_append', + 'stream_bucket_make_writeable', + 'stream_bucket_new', + 'stream_bucket_prepend', + 'stream_context_create', + 'stream_context_get_default', + 'stream_context_get_options', + 'stream_context_get_params', + 'stream_context_set_default', + 'stream_context_set_params', + 'stream_copy_to_stream', + 'stream_encoding', + 'stream_filter_append', + 'stream_filter_prepend', + 'stream_filter_remove', + 'stream_get_contents', + 'stream_get_line', + 'stream_get_meta_data', + 'stream_isatty', + 'stream_set_blocking', + 'stream_set_chunk_size', + 'stream_set_read_buffer', + 'stream_set_timeout', + 'stream_set_write_buffer', + 'stream_socket_accept', + 'stream_socket_client', + 'stream_socket_enable_crypto', + 'stream_socket_get_name', + 'stream_socket_recvfrom', + 'stream_socket_sendto', + 'stream_socket_server', + 'stream_socket_shutdown', + 'stream_supports_lock', + 'svn_fs_abort_txn', + 'svn_fs_apply_text', + 'svn_fs_begin_txn2', + 'svn_fs_change_node_prop', + 'svn_fs_check_path', + 'svn_fs_contents_changed', + 'svn_fs_copy', + 'svn_fs_delete', + 'svn_fs_dir_entries', + 'svn_fs_file_contents', + 'svn_fs_file_length', + 'svn_fs_is_dir', + 'svn_fs_is_file', + 'svn_fs_make_dir', + 'svn_fs_make_file', + 'svn_fs_node_created_rev', + 'svn_fs_node_prop', + 'svn_fs_props_changed', + 'svn_fs_revision_prop', + 'svn_fs_revision_root', + 'svn_fs_txn_root', + 'svn_fs_youngest_rev', + 'svn_repos_create', + 'svn_repos_fs', + 'svn_repos_fs_begin_txn_for_commit', + 'svn_repos_fs_commit_txn', + 'svn_repos_open', + 'sybase_affected_rows', + 'sybase_close', + 'sybase_connect', + 'sybase_data_seek', + 'sybase_fetch_array', + 'sybase_fetch_assoc', + 'sybase_fetch_field', + 'sybase_fetch_object', + 'sybase_fetch_row', + 'sybase_field_seek', + 'sybase_free_result', + 'sybase_num_fields', + 'sybase_num_rows', + 'sybase_pconnect', + 'sybase_query', + 'sybase_result', + 'sybase_select_db', + 'sybase_set_message_handler', + 'sybase_unbuffered_query', + 'tmpfile', + 'udm_add_search_limit', + 'udm_alloc_agent', + 'udm_alloc_agent_array', + 'udm_cat_list', + 'udm_cat_path', + 'udm_check_charset', + 'udm_clear_search_limits', + 'udm_crc32', + 'udm_errno', + 'udm_error', + 'udm_find', + 'udm_free_agent', + 'udm_free_res', + 'udm_get_doc_count', + 'udm_get_res_field', + 'udm_get_res_param', + 'udm_hash32', + 'udm_load_ispell_data', + 'udm_set_agent_param', + 'unlink', + 'vfprintf', + 'w32api_init_dtype', + 'wddx_add_vars', + 'wddx_packet_end', + 'wddx_packet_start', + 'xml_get_current_byte_index', + 'xml_get_current_column_number', + 'xml_get_current_line_number', + 'xml_get_error_code', + 'xml_parse', + 'xml_parse_into_struct', + 'xml_parser_create', + 'xml_parser_create_ns', + 'xml_parser_free', + 'xml_parser_get_option', + 'xml_parser_set_option', + 'xml_set_character_data_handler', + 'xml_set_default_handler', + 'xml_set_element_handler', + 'xml_set_end_namespace_decl_handler', + 'xml_set_external_entity_ref_handler', + 'xml_set_notation_decl_handler', + 'xml_set_object', + 'xml_set_processing_instruction_handler', + 'xml_set_start_namespace_decl_handler', + 'xml_set_unparsed_entity_decl_handler', + 'xmlrpc_server_add_introspection_data', + 'xmlrpc_server_call_method', + 'xmlrpc_server_create', + 'xmlrpc_server_destroy', + 'xmlrpc_server_register_introspection_callback', + 'xmlrpc_server_register_method', + 'xmlwriter_end_attribute', + 'xmlwriter_end_cdata', + 'xmlwriter_end_comment', + 'xmlwriter_end_document', + 'xmlwriter_end_dtd', + 'xmlwriter_end_dtd_attlist', + 'xmlwriter_end_dtd_element', + 'xmlwriter_end_dtd_entity', + 'xmlwriter_end_element', + 'xmlwriter_end_pi', + 'xmlwriter_flush', + 'xmlwriter_full_end_element', + 'xmlwriter_open_memory', + 'xmlwriter_open_uri', + 'xmlwriter_output_memory', + 'xmlwriter_set_indent', + 'xmlwriter_set_indent_string', + 'xmlwriter_start_attribute', + 'xmlwriter_start_attribute_ns', + 'xmlwriter_start_cdata', + 'xmlwriter_start_comment', + 'xmlwriter_start_document', + 'xmlwriter_start_dtd', + 'xmlwriter_start_dtd_attlist', + 'xmlwriter_start_dtd_element', + 'xmlwriter_start_dtd_entity', + 'xmlwriter_start_element', + 'xmlwriter_start_element_ns', + 'xmlwriter_start_pi', + 'xmlwriter_text', + 'xmlwriter_write_attribute', + 'xmlwriter_write_attribute_ns', + 'xmlwriter_write_cdata', + 'xmlwriter_write_comment', + 'xmlwriter_write_dtd', + 'xmlwriter_write_dtd_attlist', + 'xmlwriter_write_dtd_element', + 'xmlwriter_write_dtd_entity', + 'xmlwriter_write_element', + 'xmlwriter_write_element_ns', + 'xmlwriter_write_pi', + 'xmlwriter_write_raw', + 'xslt_create', + 'yaz_addinfo', + 'yaz_ccl_conf', + 'yaz_ccl_parse', + 'yaz_close', + 'yaz_database', + 'yaz_element', + 'yaz_errno', + 'yaz_error', + 'yaz_es', + 'yaz_es_result', + 'yaz_get_option', + 'yaz_hits', + 'yaz_itemorder', + 'yaz_present', + 'yaz_range', + 'yaz_record', + 'yaz_scan', + 'yaz_scan_result', + 'yaz_schema', + 'yaz_search', + 'yaz_sort', + 'yaz_syntax', + 'zip_close', + 'zip_entry_close', + 'zip_entry_compressedsize', + 'zip_entry_compressionmethod', + 'zip_entry_filesize', + 'zip_entry_name', + 'zip_entry_open', + 'zip_entry_read', + 'zip_open', + 'zip_read', + ]; + } +} diff --git a/vendor/sebastian/type/ChangeLog.md b/vendor/sebastian/type/ChangeLog.md new file mode 100644 index 00000000..0691a9b1 --- /dev/null +++ b/vendor/sebastian/type/ChangeLog.md @@ -0,0 +1,169 @@ +# ChangeLog + +All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. + +## [3.2.1] - 2023-02-03 + +### Fixed + +* [#28](https://github.com/sebastianbergmann/type/pull/28): Potential undefined offset warning/notice + +## [3.2.0] - 2022-09-12 + +### Added + +* [#25](https://github.com/sebastianbergmann/type/issues/25): Support Disjunctive Normal Form types +* Added `ReflectionMapper::fromParameterTypes()` +* Added `IntersectionType::types()` and `UnionType::types()` +* Added `UnionType::containsIntersectionTypes()` + +## [3.1.0] - 2022-08-29 + +### Added + +* [#21](https://github.com/sebastianbergmann/type/issues/21): Support `true` as stand-alone type + +## [3.0.0] - 2022-03-15 + +### Added + +* Support for intersection types introduced in PHP 8.1 +* Support for the `never` return type introduced in PHP 8.1 +* Added `Type::isCallable()`, `Type::isGenericObject()`, `Type::isIterable()`, `Type::isMixed()`, `Type::isNever()`, `Type::isNull()`, `Type::isObject()`, `Type::isSimple()`, `Type::isStatic()`, `Type::isUnion()`, `Type::isUnknown()`, and `Type::isVoid()` + +### Changed + +* Renamed `ReflectionMapper::fromMethodReturnType(ReflectionMethod $method)` to `ReflectionMapper::fromReturnType(ReflectionFunctionAbstract $functionOrMethod)` + +### Removed + +* Removed `Type::getReturnTypeDeclaration()` (use `Type::asString()` instead and prefix its result with `': '`) +* Removed `TypeName::getNamespaceName()` (use `TypeName::namespaceName()` instead) +* Removed `TypeName::getSimpleName()` (use `TypeName::simpleName()` instead) +* Removed `TypeName::getQualifiedName()` (use `TypeName::qualifiedName()` instead) + +## [2.3.4] - 2021-06-15 + +### Fixed + +* Fixed regression introduced in 2.3.3 + +## [2.3.3] - 2021-06-15 [YANKED] + +### Fixed + +* [#15](https://github.com/sebastianbergmann/type/issues/15): "false" pseudo type is not handled properly + +## [2.3.2] - 2021-06-04 + +### Fixed + +* Fixed handling of tentatively declared return types + +## [2.3.1] - 2020-10-26 + +### Fixed + +* `SebastianBergmann\Type\Exception` now correctly extends `\Throwable` + +## [2.3.0] - 2020-10-06 + +### Added + +* [#14](https://github.com/sebastianbergmann/type/issues/14): Support for `static` return type that is introduced in PHP 8 + +## [2.2.2] - 2020-09-28 + +### Changed + +* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3` + +## [2.2.1] - 2020-07-05 + +### Fixed + +* Fixed handling of `mixed` type in `ReflectionMapper::fromMethodReturnType()` + +## [2.2.0] - 2020-07-05 + +### Added + +* Added `MixedType` object for representing PHP 8's `mixed` type + +## [2.1.1] - 2020-06-26 + +### Added + +* This component is now supported on PHP 8 + +## [2.1.0] - 2020-06-01 + +### Added + +* Added `UnionType` object for representing PHP 8's Union Types +* Added `ReflectionMapper::fromMethodReturnType()` for mapping `\ReflectionMethod::getReturnType()` to a `Type` object +* Added `Type::name()` for retrieving the name of a type +* Added `Type::asString()` for retrieving a textual representation of a type + +### Changed + +* Deprecated `Type::getReturnTypeDeclaration()` (use `Type::asString()` instead and prefix its result with `': '`) +* Deprecated `TypeName::getNamespaceName()` (use `TypeName::namespaceName()` instead) +* Deprecated `TypeName::getSimpleName()` (use `TypeName::simpleName()` instead) +* Deprecated `TypeName::getQualifiedName()` (use `TypeName::qualifiedName()` instead) + +## [2.0.0] - 2020-02-07 + +### Removed + +* This component is no longer supported on PHP 7.2 + +## [1.1.3] - 2019-07-02 + +### Fixed + +* Fixed class name comparison in `ObjectType` to be case-insensitive + +## [1.1.2] - 2019-06-19 + +### Fixed + +* Fixed handling of `object` type + +## [1.1.1] - 2019-06-08 + +### Fixed + +* Fixed autoloading of `callback_function.php` fixture file + +## [1.1.0] - 2019-06-07 + +### Added + +* Added support for `callable` type +* Added support for `iterable` type + +## [1.0.0] - 2019-06-06 + +* Initial release based on [code contributed by Michel Hartmann to PHPUnit](https://github.com/sebastianbergmann/phpunit/pull/3673) + +[3.2.1]: https://github.com/sebastianbergmann/type/compare/3.2.0...3.2.1 +[3.2.0]: https://github.com/sebastianbergmann/type/compare/3.1.0...3.2.0 +[3.1.0]: https://github.com/sebastianbergmann/type/compare/3.0.0...3.1.0 +[3.0.0]: https://github.com/sebastianbergmann/type/compare/2.3.4...3.0.0 +[2.3.4]: https://github.com/sebastianbergmann/type/compare/ca39369c41313ed12c071ed38ecda8fcdb248859...2.3.4 +[2.3.3]: https://github.com/sebastianbergmann/type/compare/2.3.2...ca39369c41313ed12c071ed38ecda8fcdb248859 +[2.3.2]: https://github.com/sebastianbergmann/type/compare/2.3.1...2.3.2 +[2.3.1]: https://github.com/sebastianbergmann/type/compare/2.3.0...2.3.1 +[2.3.0]: https://github.com/sebastianbergmann/type/compare/2.2.2...2.3.0 +[2.2.2]: https://github.com/sebastianbergmann/type/compare/2.2.1...2.2.2 +[2.2.1]: https://github.com/sebastianbergmann/type/compare/2.2.0...2.2.1 +[2.2.0]: https://github.com/sebastianbergmann/type/compare/2.1.1...2.2.0 +[2.1.1]: https://github.com/sebastianbergmann/type/compare/2.1.0...2.1.1 +[2.1.0]: https://github.com/sebastianbergmann/type/compare/2.0.0...2.1.0 +[2.0.0]: https://github.com/sebastianbergmann/type/compare/1.1.3...2.0.0 +[1.1.3]: https://github.com/sebastianbergmann/type/compare/1.1.2...1.1.3 +[1.1.2]: https://github.com/sebastianbergmann/type/compare/1.1.1...1.1.2 +[1.1.1]: https://github.com/sebastianbergmann/type/compare/1.1.0...1.1.1 +[1.1.0]: https://github.com/sebastianbergmann/type/compare/1.0.0...1.1.0 +[1.0.0]: https://github.com/sebastianbergmann/type/compare/ff74aa41746bd8d10e931843ebf37d42da513ede...1.0.0 diff --git a/vendor/sebastian/type/LICENSE b/vendor/sebastian/type/LICENSE new file mode 100644 index 00000000..f4e4a328 --- /dev/null +++ b/vendor/sebastian/type/LICENSE @@ -0,0 +1,33 @@ +sebastian/type + +Copyright (c) 2019-2022, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/sebastian/type/README.md b/vendor/sebastian/type/README.md new file mode 100644 index 00000000..1036ce7a --- /dev/null +++ b/vendor/sebastian/type/README.md @@ -0,0 +1,20 @@ +# sebastian/type + +[![CI Status](https://github.com/sebastianbergmann/type/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/type/actions) +[![Type Coverage](https://shepherd.dev/github/sebastianbergmann/type/coverage.svg)](https://shepherd.dev/github/sebastianbergmann/type) + +Collection of value objects that represent the types of the PHP type system. + +## Installation + +You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): + +``` +composer require sebastian/type +``` + +If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency: + +``` +composer require --dev sebastian/type +``` diff --git a/vendor/sebastian/type/composer.json b/vendor/sebastian/type/composer.json new file mode 100644 index 00000000..a0865c93 --- /dev/null +++ b/vendor/sebastian/type/composer.json @@ -0,0 +1,50 @@ +{ + "name": "sebastian/type", + "description": "Collection of value objects that represent the types of the PHP type system", + "type": "library", + "homepage": "https://github.com/sebastianbergmann/type", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues" + }, + "prefer-stable": true, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "config": { + "platform": { + "php": "7.3.0" + }, + "optimize-autoloader": true, + "sort-packages": true + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "autoload-dev": { + "classmap": [ + "tests/_fixture" + ], + "files": [ + "tests/_fixture/callback_function.php", + "tests/_fixture/functions_that_declare_return_types.php" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + } +} diff --git a/vendor/sebastian/type/src/Parameter.php b/vendor/sebastian/type/src/Parameter.php new file mode 100644 index 00000000..1adb061e --- /dev/null +++ b/vendor/sebastian/type/src/Parameter.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Type; + +final class Parameter +{ + /** + * @psalm-var non-empty-string + */ + private $name; + + /** + * @var Type + */ + private $type; + + /** + * @psalm-param non-empty-string $name + */ + public function __construct(string $name, Type $type) + { + $this->name = $name; + $this->type = $type; + } + + public function name(): string + { + return $this->name; + } + + public function type(): Type + { + return $this->type; + } +} diff --git a/vendor/sebastian/type/src/ReflectionMapper.php b/vendor/sebastian/type/src/ReflectionMapper.php new file mode 100644 index 00000000..32099b4b --- /dev/null +++ b/vendor/sebastian/type/src/ReflectionMapper.php @@ -0,0 +1,184 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Type; + +use function assert; +use ReflectionFunctionAbstract; +use ReflectionIntersectionType; +use ReflectionMethod; +use ReflectionNamedType; +use ReflectionType; +use ReflectionUnionType; + +final class ReflectionMapper +{ + /** + * @psalm-return list + */ + public function fromParameterTypes(ReflectionFunctionAbstract $functionOrMethod): array + { + $parameters = []; + + foreach ($functionOrMethod->getParameters() as $parameter) { + $name = $parameter->getName(); + + assert($name !== ''); + + if (!$parameter->hasType()) { + $parameters[] = new Parameter($name, new UnknownType); + + continue; + } + + $type = $parameter->getType(); + + if ($type instanceof ReflectionNamedType) { + $parameters[] = new Parameter( + $name, + $this->mapNamedType($type, $functionOrMethod) + ); + + continue; + } + + if ($type instanceof ReflectionUnionType) { + $parameters[] = new Parameter( + $name, + $this->mapUnionType($type, $functionOrMethod) + ); + + continue; + } + + if ($type instanceof ReflectionIntersectionType) { + $parameters[] = new Parameter( + $name, + $this->mapIntersectionType($type, $functionOrMethod) + ); + } + } + + return $parameters; + } + + public function fromReturnType(ReflectionFunctionAbstract $functionOrMethod): Type + { + if (!$this->hasReturnType($functionOrMethod)) { + return new UnknownType; + } + + $returnType = $this->returnType($functionOrMethod); + + assert($returnType instanceof ReflectionNamedType || $returnType instanceof ReflectionUnionType || $returnType instanceof ReflectionIntersectionType); + + if ($returnType instanceof ReflectionNamedType) { + return $this->mapNamedType($returnType, $functionOrMethod); + } + + if ($returnType instanceof ReflectionUnionType) { + return $this->mapUnionType($returnType, $functionOrMethod); + } + + if ($returnType instanceof ReflectionIntersectionType) { + return $this->mapIntersectionType($returnType, $functionOrMethod); + } + } + + private function mapNamedType(ReflectionNamedType $type, ReflectionFunctionAbstract $functionOrMethod): Type + { + if ($functionOrMethod instanceof ReflectionMethod && $type->getName() === 'self') { + return ObjectType::fromName( + $functionOrMethod->getDeclaringClass()->getName(), + $type->allowsNull() + ); + } + + if ($functionOrMethod instanceof ReflectionMethod && $type->getName() === 'static') { + return new StaticType( + TypeName::fromReflection($functionOrMethod->getDeclaringClass()), + $type->allowsNull() + ); + } + + if ($type->getName() === 'mixed') { + return new MixedType; + } + + if ($functionOrMethod instanceof ReflectionMethod && $type->getName() === 'parent') { + return ObjectType::fromName( + $functionOrMethod->getDeclaringClass()->getParentClass()->getName(), + $type->allowsNull() + ); + } + + return Type::fromName( + $type->getName(), + $type->allowsNull() + ); + } + + private function mapUnionType(ReflectionUnionType $type, ReflectionFunctionAbstract $functionOrMethod): Type + { + $types = []; + + foreach ($type->getTypes() as $_type) { + assert($_type instanceof ReflectionNamedType || $_type instanceof ReflectionIntersectionType); + + if ($_type instanceof ReflectionNamedType) { + $types[] = $this->mapNamedType($_type, $functionOrMethod); + + continue; + } + + $types[] = $this->mapIntersectionType($_type, $functionOrMethod); + } + + return new UnionType(...$types); + } + + private function mapIntersectionType(ReflectionIntersectionType $type, ReflectionFunctionAbstract $functionOrMethod): Type + { + $types = []; + + foreach ($type->getTypes() as $_type) { + assert($_type instanceof ReflectionNamedType); + + $types[] = $this->mapNamedType($_type, $functionOrMethod); + } + + return new IntersectionType(...$types); + } + + private function hasReturnType(ReflectionFunctionAbstract $functionOrMethod): bool + { + if ($functionOrMethod->hasReturnType()) { + return true; + } + + if (!method_exists($functionOrMethod, 'hasTentativeReturnType')) { + return false; + } + + return $functionOrMethod->hasTentativeReturnType(); + } + + private function returnType(ReflectionFunctionAbstract $functionOrMethod): ?ReflectionType + { + if ($functionOrMethod->hasReturnType()) { + return $functionOrMethod->getReturnType(); + } + + if (!method_exists($functionOrMethod, 'getTentativeReturnType')) { + return null; + } + + return $functionOrMethod->getTentativeReturnType(); + } +} diff --git a/vendor/sebastian/type/src/TypeName.php b/vendor/sebastian/type/src/TypeName.php new file mode 100644 index 00000000..17d477cf --- /dev/null +++ b/vendor/sebastian/type/src/TypeName.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Type; + +use function array_pop; +use function explode; +use function implode; +use function substr; +use ReflectionClass; + +final class TypeName +{ + /** + * @var ?string + */ + private $namespaceName; + + /** + * @var string + */ + private $simpleName; + + public static function fromQualifiedName(string $fullClassName): self + { + if ($fullClassName[0] === '\\') { + $fullClassName = substr($fullClassName, 1); + } + + $classNameParts = explode('\\', $fullClassName); + + $simpleName = array_pop($classNameParts); + $namespaceName = implode('\\', $classNameParts); + + return new self($namespaceName, $simpleName); + } + + public static function fromReflection(ReflectionClass $type): self + { + return new self( + $type->getNamespaceName(), + $type->getShortName() + ); + } + + public function __construct(?string $namespaceName, string $simpleName) + { + if ($namespaceName === '') { + $namespaceName = null; + } + + $this->namespaceName = $namespaceName; + $this->simpleName = $simpleName; + } + + public function namespaceName(): ?string + { + return $this->namespaceName; + } + + public function simpleName(): string + { + return $this->simpleName; + } + + public function qualifiedName(): string + { + return $this->namespaceName === null + ? $this->simpleName + : $this->namespaceName . '\\' . $this->simpleName; + } + + public function isNamespaced(): bool + { + return $this->namespaceName !== null; + } +} diff --git a/vendor/sebastian/type/src/exception/Exception.php b/vendor/sebastian/type/src/exception/Exception.php new file mode 100644 index 00000000..e0e7ee57 --- /dev/null +++ b/vendor/sebastian/type/src/exception/Exception.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Type; + +use Throwable; + +interface Exception extends Throwable +{ +} diff --git a/vendor/sebastian/type/src/exception/RuntimeException.php b/vendor/sebastian/type/src/exception/RuntimeException.php new file mode 100644 index 00000000..4dfea6a6 --- /dev/null +++ b/vendor/sebastian/type/src/exception/RuntimeException.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Type; + +final class RuntimeException extends \RuntimeException implements Exception +{ +} diff --git a/vendor/sebastian/type/src/type/CallableType.php b/vendor/sebastian/type/src/type/CallableType.php new file mode 100644 index 00000000..d44fb0ca --- /dev/null +++ b/vendor/sebastian/type/src/type/CallableType.php @@ -0,0 +1,212 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Type; + +use function assert; +use function class_exists; +use function count; +use function explode; +use function function_exists; +use function is_array; +use function is_object; +use function is_string; +use Closure; +use ReflectionClass; +use ReflectionException; +use ReflectionObject; + +final class CallableType extends Type +{ + /** + * @var bool + */ + private $allowsNull; + + public function __construct(bool $nullable) + { + $this->allowsNull = $nullable; + } + + /** + * @throws RuntimeException + */ + public function isAssignable(Type $other): bool + { + if ($this->allowsNull && $other instanceof NullType) { + return true; + } + + if ($other instanceof self) { + return true; + } + + if ($other instanceof ObjectType) { + if ($this->isClosure($other)) { + return true; + } + + if ($this->hasInvokeMethod($other)) { + return true; + } + } + + if ($other instanceof SimpleType) { + if ($this->isFunction($other)) { + return true; + } + + if ($this->isClassCallback($other)) { + return true; + } + + if ($this->isObjectCallback($other)) { + return true; + } + } + + return false; + } + + public function name(): string + { + return 'callable'; + } + + public function allowsNull(): bool + { + return $this->allowsNull; + } + + /** + * @psalm-assert-if-true CallableType $this + */ + public function isCallable(): bool + { + return true; + } + + private function isClosure(ObjectType $type): bool + { + return !$type->className()->isNamespaced() && $type->className()->simpleName() === Closure::class; + } + + /** + * @throws RuntimeException + */ + private function hasInvokeMethod(ObjectType $type): bool + { + $className = $type->className()->qualifiedName(); + assert(class_exists($className)); + + try { + $class = new ReflectionClass($className); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new RuntimeException( + $e->getMessage(), + (int) $e->getCode(), + $e + ); + // @codeCoverageIgnoreEnd + } + + if ($class->hasMethod('__invoke')) { + return true; + } + + return false; + } + + private function isFunction(SimpleType $type): bool + { + if (!is_string($type->value())) { + return false; + } + + return function_exists($type->value()); + } + + private function isObjectCallback(SimpleType $type): bool + { + if (!is_array($type->value())) { + return false; + } + + if (count($type->value()) !== 2) { + return false; + } + + if (!isset($type->value()[0], $type->value()[1])) { + return false; + } + + if (!is_object($type->value()[0]) || !is_string($type->value()[1])) { + return false; + } + + [$object, $methodName] = $type->value(); + + return (new ReflectionObject($object))->hasMethod($methodName); + } + + private function isClassCallback(SimpleType $type): bool + { + if (!is_string($type->value()) && !is_array($type->value())) { + return false; + } + + if (is_string($type->value())) { + if (strpos($type->value(), '::') === false) { + return false; + } + + [$className, $methodName] = explode('::', $type->value()); + } + + if (is_array($type->value())) { + if (count($type->value()) !== 2) { + return false; + } + + if (!isset($type->value()[0], $type->value()[1])) { + return false; + } + + if (!is_string($type->value()[0]) || !is_string($type->value()[1])) { + return false; + } + + [$className, $methodName] = $type->value(); + } + + assert(isset($className) && is_string($className) && class_exists($className)); + assert(isset($methodName) && is_string($methodName)); + + try { + $class = new ReflectionClass($className); + + if ($class->hasMethod($methodName)) { + $method = $class->getMethod($methodName); + + return $method->isPublic() && $method->isStatic(); + } + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new RuntimeException( + $e->getMessage(), + (int) $e->getCode(), + $e + ); + // @codeCoverageIgnoreEnd + } + + return false; + } +} diff --git a/vendor/sebastian/type/src/type/FalseType.php b/vendor/sebastian/type/src/type/FalseType.php new file mode 100644 index 00000000..f417fb69 --- /dev/null +++ b/vendor/sebastian/type/src/type/FalseType.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Type; + +final class FalseType extends Type +{ + public function isAssignable(Type $other): bool + { + if ($other instanceof self) { + return true; + } + + return $other instanceof SimpleType && + $other->name() === 'bool' && + $other->value() === false; + } + + public function name(): string + { + return 'false'; + } + + public function allowsNull(): bool + { + return false; + } + + /** + * @psalm-assert-if-true FalseType $this + */ + public function isFalse(): bool + { + return true; + } +} diff --git a/vendor/sebastian/type/src/type/GenericObjectType.php b/vendor/sebastian/type/src/type/GenericObjectType.php new file mode 100644 index 00000000..d06963f0 --- /dev/null +++ b/vendor/sebastian/type/src/type/GenericObjectType.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Type; + +final class GenericObjectType extends Type +{ + /** + * @var bool + */ + private $allowsNull; + + public function __construct(bool $nullable) + { + $this->allowsNull = $nullable; + } + + public function isAssignable(Type $other): bool + { + if ($this->allowsNull && $other instanceof NullType) { + return true; + } + + if (!$other instanceof ObjectType) { + return false; + } + + return true; + } + + public function name(): string + { + return 'object'; + } + + public function allowsNull(): bool + { + return $this->allowsNull; + } + + /** + * @psalm-assert-if-true GenericObjectType $this + */ + public function isGenericObject(): bool + { + return true; + } +} diff --git a/vendor/sebastian/type/src/type/IntersectionType.php b/vendor/sebastian/type/src/type/IntersectionType.php new file mode 100644 index 00000000..2e133940 --- /dev/null +++ b/vendor/sebastian/type/src/type/IntersectionType.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Type; + +use function assert; +use function count; +use function implode; +use function in_array; +use function sort; + +final class IntersectionType extends Type +{ + /** + * @psalm-var non-empty-list + */ + private $types; + + /** + * @throws RuntimeException + */ + public function __construct(Type ...$types) + { + $this->ensureMinimumOfTwoTypes(...$types); + $this->ensureOnlyValidTypes(...$types); + $this->ensureNoDuplicateTypes(...$types); + + $this->types = $types; + } + + public function isAssignable(Type $other): bool + { + return $other->isObject(); + } + + public function asString(): string + { + return $this->name(); + } + + public function name(): string + { + $types = []; + + foreach ($this->types as $type) { + $types[] = $type->name(); + } + + sort($types); + + return implode('&', $types); + } + + public function allowsNull(): bool + { + return false; + } + + /** + * @psalm-assert-if-true IntersectionType $this + */ + public function isIntersection(): bool + { + return true; + } + + /** + * @psalm-return non-empty-list + */ + public function types(): array + { + return $this->types; + } + + /** + * @throws RuntimeException + */ + private function ensureMinimumOfTwoTypes(Type ...$types): void + { + if (count($types) < 2) { + throw new RuntimeException( + 'An intersection type must be composed of at least two types' + ); + } + } + + /** + * @throws RuntimeException + */ + private function ensureOnlyValidTypes(Type ...$types): void + { + foreach ($types as $type) { + if (!$type->isObject()) { + throw new RuntimeException( + 'An intersection type can only be composed of interfaces and classes' + ); + } + } + } + + /** + * @throws RuntimeException + */ + private function ensureNoDuplicateTypes(Type ...$types): void + { + $names = []; + + foreach ($types as $type) { + assert($type instanceof ObjectType); + + $classQualifiedName = $type->className()->qualifiedName(); + + if (in_array($classQualifiedName, $names, true)) { + throw new RuntimeException('An intersection type must not contain duplicate types'); + } + + $names[] = $classQualifiedName; + } + } +} diff --git a/vendor/sebastian/type/src/type/IterableType.php b/vendor/sebastian/type/src/type/IterableType.php new file mode 100644 index 00000000..7b2a58fa --- /dev/null +++ b/vendor/sebastian/type/src/type/IterableType.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Type; + +use function assert; +use function class_exists; +use function is_iterable; +use ReflectionClass; +use ReflectionException; + +final class IterableType extends Type +{ + /** + * @var bool + */ + private $allowsNull; + + public function __construct(bool $nullable) + { + $this->allowsNull = $nullable; + } + + /** + * @throws RuntimeException + */ + public function isAssignable(Type $other): bool + { + if ($this->allowsNull && $other instanceof NullType) { + return true; + } + + if ($other instanceof self) { + return true; + } + + if ($other instanceof SimpleType) { + return is_iterable($other->value()); + } + + if ($other instanceof ObjectType) { + $className = $other->className()->qualifiedName(); + assert(class_exists($className)); + + try { + return (new ReflectionClass($className))->isIterable(); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new RuntimeException( + $e->getMessage(), + (int) $e->getCode(), + $e + ); + // @codeCoverageIgnoreEnd + } + } + + return false; + } + + public function name(): string + { + return 'iterable'; + } + + public function allowsNull(): bool + { + return $this->allowsNull; + } + + /** + * @psalm-assert-if-true IterableType $this + */ + public function isIterable(): bool + { + return true; + } +} diff --git a/vendor/sebastian/type/src/type/MixedType.php b/vendor/sebastian/type/src/type/MixedType.php new file mode 100644 index 00000000..a1412e45 --- /dev/null +++ b/vendor/sebastian/type/src/type/MixedType.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Type; + +final class MixedType extends Type +{ + public function isAssignable(Type $other): bool + { + return !$other instanceof VoidType; + } + + public function asString(): string + { + return 'mixed'; + } + + public function name(): string + { + return 'mixed'; + } + + public function allowsNull(): bool + { + return true; + } + + /** + * @psalm-assert-if-true MixedType $this + */ + public function isMixed(): bool + { + return true; + } +} diff --git a/vendor/sebastian/type/src/type/NeverType.php b/vendor/sebastian/type/src/type/NeverType.php new file mode 100644 index 00000000..6c144743 --- /dev/null +++ b/vendor/sebastian/type/src/type/NeverType.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Type; + +final class NeverType extends Type +{ + public function isAssignable(Type $other): bool + { + return $other instanceof self; + } + + public function name(): string + { + return 'never'; + } + + public function allowsNull(): bool + { + return false; + } + + /** + * @psalm-assert-if-true NeverType $this + */ + public function isNever(): bool + { + return true; + } +} diff --git a/vendor/sebastian/type/src/type/NullType.php b/vendor/sebastian/type/src/type/NullType.php new file mode 100644 index 00000000..93834eab --- /dev/null +++ b/vendor/sebastian/type/src/type/NullType.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Type; + +final class NullType extends Type +{ + public function isAssignable(Type $other): bool + { + return !($other instanceof VoidType); + } + + public function name(): string + { + return 'null'; + } + + public function asString(): string + { + return 'null'; + } + + public function allowsNull(): bool + { + return true; + } + + /** + * @psalm-assert-if-true NullType $this + */ + public function isNull(): bool + { + return true; + } +} diff --git a/vendor/sebastian/type/src/type/ObjectType.php b/vendor/sebastian/type/src/type/ObjectType.php new file mode 100644 index 00000000..44febb27 --- /dev/null +++ b/vendor/sebastian/type/src/type/ObjectType.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Type; + +use function is_subclass_of; +use function strcasecmp; + +final class ObjectType extends Type +{ + /** + * @var TypeName + */ + private $className; + + /** + * @var bool + */ + private $allowsNull; + + public function __construct(TypeName $className, bool $allowsNull) + { + $this->className = $className; + $this->allowsNull = $allowsNull; + } + + public function isAssignable(Type $other): bool + { + if ($this->allowsNull && $other instanceof NullType) { + return true; + } + + if ($other instanceof self) { + if (0 === strcasecmp($this->className->qualifiedName(), $other->className->qualifiedName())) { + return true; + } + + if (is_subclass_of($other->className->qualifiedName(), $this->className->qualifiedName(), true)) { + return true; + } + } + + return false; + } + + public function name(): string + { + return $this->className->qualifiedName(); + } + + public function allowsNull(): bool + { + return $this->allowsNull; + } + + public function className(): TypeName + { + return $this->className; + } + + /** + * @psalm-assert-if-true ObjectType $this + */ + public function isObject(): bool + { + return true; + } +} diff --git a/vendor/sebastian/type/src/type/SimpleType.php b/vendor/sebastian/type/src/type/SimpleType.php new file mode 100644 index 00000000..4dce75da --- /dev/null +++ b/vendor/sebastian/type/src/type/SimpleType.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Type; + +use function strtolower; + +final class SimpleType extends Type +{ + /** + * @var string + */ + private $name; + + /** + * @var bool + */ + private $allowsNull; + + /** + * @var mixed + */ + private $value; + + public function __construct(string $name, bool $nullable, $value = null) + { + $this->name = $this->normalize($name); + $this->allowsNull = $nullable; + $this->value = $value; + } + + public function isAssignable(Type $other): bool + { + if ($this->allowsNull && $other instanceof NullType) { + return true; + } + + if ($this->name === 'bool' && $other->name() === 'true') { + return true; + } + + if ($this->name === 'bool' && $other->name() === 'false') { + return true; + } + + if ($other instanceof self) { + return $this->name === $other->name; + } + + return false; + } + + public function name(): string + { + return $this->name; + } + + public function allowsNull(): bool + { + return $this->allowsNull; + } + + public function value() + { + return $this->value; + } + + /** + * @psalm-assert-if-true SimpleType $this + */ + public function isSimple(): bool + { + return true; + } + + private function normalize(string $name): string + { + $name = strtolower($name); + + switch ($name) { + case 'boolean': + return 'bool'; + + case 'real': + case 'double': + return 'float'; + + case 'integer': + return 'int'; + + case '[]': + return 'array'; + + default: + return $name; + } + } +} diff --git a/vendor/sebastian/type/src/type/StaticType.php b/vendor/sebastian/type/src/type/StaticType.php new file mode 100644 index 00000000..cbc13f5f --- /dev/null +++ b/vendor/sebastian/type/src/type/StaticType.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Type; + +final class StaticType extends Type +{ + /** + * @var TypeName + */ + private $className; + + /** + * @var bool + */ + private $allowsNull; + + public function __construct(TypeName $className, bool $allowsNull) + { + $this->className = $className; + $this->allowsNull = $allowsNull; + } + + public function isAssignable(Type $other): bool + { + if ($this->allowsNull && $other instanceof NullType) { + return true; + } + + if (!$other instanceof ObjectType) { + return false; + } + + if (0 === strcasecmp($this->className->qualifiedName(), $other->className()->qualifiedName())) { + return true; + } + + if (is_subclass_of($other->className()->qualifiedName(), $this->className->qualifiedName(), true)) { + return true; + } + + return false; + } + + public function name(): string + { + return 'static'; + } + + public function allowsNull(): bool + { + return $this->allowsNull; + } + + /** + * @psalm-assert-if-true StaticType $this + */ + public function isStatic(): bool + { + return true; + } +} diff --git a/vendor/sebastian/type/src/type/TrueType.php b/vendor/sebastian/type/src/type/TrueType.php new file mode 100644 index 00000000..94e5be99 --- /dev/null +++ b/vendor/sebastian/type/src/type/TrueType.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Type; + +final class TrueType extends Type +{ + public function isAssignable(Type $other): bool + { + if ($other instanceof self) { + return true; + } + + return $other instanceof SimpleType && + $other->name() === 'bool' && + $other->value() === true; + } + + public function name(): string + { + return 'true'; + } + + public function allowsNull(): bool + { + return false; + } + + /** + * @psalm-assert-if-true TrueType $this + */ + public function isTrue(): bool + { + return true; + } +} diff --git a/vendor/sebastian/type/src/type/Type.php b/vendor/sebastian/type/src/type/Type.php new file mode 100644 index 00000000..e7536683 --- /dev/null +++ b/vendor/sebastian/type/src/type/Type.php @@ -0,0 +1,226 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Type; + +use const PHP_VERSION; +use function get_class; +use function gettype; +use function strtolower; +use function version_compare; + +abstract class Type +{ + public static function fromValue($value, bool $allowsNull): self + { + if ($allowsNull === false) { + if ($value === true) { + return new TrueType; + } + + if ($value === false) { + return new FalseType; + } + } + + $typeName = gettype($value); + + if ($typeName === 'object') { + return new ObjectType(TypeName::fromQualifiedName(get_class($value)), $allowsNull); + } + + $type = self::fromName($typeName, $allowsNull); + + if ($type instanceof SimpleType) { + $type = new SimpleType($typeName, $allowsNull, $value); + } + + return $type; + } + + public static function fromName(string $typeName, bool $allowsNull): self + { + if (version_compare(PHP_VERSION, '8.1.0-dev', '>=') && strtolower($typeName) === 'never') { + return new NeverType; + } + + switch (strtolower($typeName)) { + case 'callable': + return new CallableType($allowsNull); + + case 'true': + return new TrueType; + + case 'false': + return new FalseType; + + case 'iterable': + return new IterableType($allowsNull); + + case 'null': + return new NullType; + + case 'object': + return new GenericObjectType($allowsNull); + + case 'unknown type': + return new UnknownType; + + case 'void': + return new VoidType; + + case 'array': + case 'bool': + case 'boolean': + case 'double': + case 'float': + case 'int': + case 'integer': + case 'real': + case 'resource': + case 'resource (closed)': + case 'string': + return new SimpleType($typeName, $allowsNull); + + default: + return new ObjectType(TypeName::fromQualifiedName($typeName), $allowsNull); + } + } + + public function asString(): string + { + return ($this->allowsNull() ? '?' : '') . $this->name(); + } + + /** + * @psalm-assert-if-true CallableType $this + */ + public function isCallable(): bool + { + return false; + } + + /** + * @psalm-assert-if-true TrueType $this + */ + public function isTrue(): bool + { + return false; + } + + /** + * @psalm-assert-if-true FalseType $this + */ + public function isFalse(): bool + { + return false; + } + + /** + * @psalm-assert-if-true GenericObjectType $this + */ + public function isGenericObject(): bool + { + return false; + } + + /** + * @psalm-assert-if-true IntersectionType $this + */ + public function isIntersection(): bool + { + return false; + } + + /** + * @psalm-assert-if-true IterableType $this + */ + public function isIterable(): bool + { + return false; + } + + /** + * @psalm-assert-if-true MixedType $this + */ + public function isMixed(): bool + { + return false; + } + + /** + * @psalm-assert-if-true NeverType $this + */ + public function isNever(): bool + { + return false; + } + + /** + * @psalm-assert-if-true NullType $this + */ + public function isNull(): bool + { + return false; + } + + /** + * @psalm-assert-if-true ObjectType $this + */ + public function isObject(): bool + { + return false; + } + + /** + * @psalm-assert-if-true SimpleType $this + */ + public function isSimple(): bool + { + return false; + } + + /** + * @psalm-assert-if-true StaticType $this + */ + public function isStatic(): bool + { + return false; + } + + /** + * @psalm-assert-if-true UnionType $this + */ + public function isUnion(): bool + { + return false; + } + + /** + * @psalm-assert-if-true UnknownType $this + */ + public function isUnknown(): bool + { + return false; + } + + /** + * @psalm-assert-if-true VoidType $this + */ + public function isVoid(): bool + { + return false; + } + + abstract public function isAssignable(self $other): bool; + + abstract public function name(): string; + + abstract public function allowsNull(): bool; +} diff --git a/vendor/sebastian/type/src/type/UnionType.php b/vendor/sebastian/type/src/type/UnionType.php new file mode 100644 index 00000000..427729c5 --- /dev/null +++ b/vendor/sebastian/type/src/type/UnionType.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Type; + +use function count; +use function implode; +use function sort; + +final class UnionType extends Type +{ + /** + * @psalm-var non-empty-list + */ + private $types; + + /** + * @throws RuntimeException + */ + public function __construct(Type ...$types) + { + $this->ensureMinimumOfTwoTypes(...$types); + $this->ensureOnlyValidTypes(...$types); + + $this->types = $types; + } + + public function isAssignable(Type $other): bool + { + foreach ($this->types as $type) { + if ($type->isAssignable($other)) { + return true; + } + } + + return false; + } + + public function asString(): string + { + return $this->name(); + } + + public function name(): string + { + $types = []; + + foreach ($this->types as $type) { + if ($type->isIntersection()) { + $types[] = '(' . $type->name() . ')'; + + continue; + } + + $types[] = $type->name(); + } + + sort($types); + + return implode('|', $types); + } + + public function allowsNull(): bool + { + foreach ($this->types as $type) { + if ($type instanceof NullType) { + return true; + } + } + + return false; + } + + /** + * @psalm-assert-if-true UnionType $this + */ + public function isUnion(): bool + { + return true; + } + + public function containsIntersectionTypes(): bool + { + foreach ($this->types as $type) { + if ($type->isIntersection()) { + return true; + } + } + + return false; + } + + /** + * @psalm-return non-empty-list + */ + public function types(): array + { + return $this->types; + } + + /** + * @throws RuntimeException + */ + private function ensureMinimumOfTwoTypes(Type ...$types): void + { + if (count($types) < 2) { + throw new RuntimeException( + 'A union type must be composed of at least two types' + ); + } + } + + /** + * @throws RuntimeException + */ + private function ensureOnlyValidTypes(Type ...$types): void + { + foreach ($types as $type) { + if ($type instanceof UnknownType) { + throw new RuntimeException( + 'A union type must not be composed of an unknown type' + ); + } + + if ($type instanceof VoidType) { + throw new RuntimeException( + 'A union type must not be composed of a void type' + ); + } + } + } +} diff --git a/vendor/sebastian/type/src/type/UnknownType.php b/vendor/sebastian/type/src/type/UnknownType.php new file mode 100644 index 00000000..dc274407 --- /dev/null +++ b/vendor/sebastian/type/src/type/UnknownType.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Type; + +final class UnknownType extends Type +{ + public function isAssignable(Type $other): bool + { + return true; + } + + public function name(): string + { + return 'unknown type'; + } + + public function asString(): string + { + return ''; + } + + public function allowsNull(): bool + { + return true; + } + + /** + * @psalm-assert-if-true UnknownType $this + */ + public function isUnknown(): bool + { + return true; + } +} diff --git a/vendor/sebastian/type/src/type/VoidType.php b/vendor/sebastian/type/src/type/VoidType.php new file mode 100644 index 00000000..f740fe29 --- /dev/null +++ b/vendor/sebastian/type/src/type/VoidType.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Type; + +final class VoidType extends Type +{ + public function isAssignable(Type $other): bool + { + return $other instanceof self; + } + + public function name(): string + { + return 'void'; + } + + public function allowsNull(): bool + { + return false; + } + + /** + * @psalm-assert-if-true VoidType $this + */ + public function isVoid(): bool + { + return true; + } +} diff --git a/vendor/sebastian/version/.gitattributes b/vendor/sebastian/version/.gitattributes new file mode 100644 index 00000000..54b89530 --- /dev/null +++ b/vendor/sebastian/version/.gitattributes @@ -0,0 +1,4 @@ +/.github export-ignore +/.php_cs.dist export-ignore + +*.php diff=php diff --git a/vendor/sebastian/version/.gitignore b/vendor/sebastian/version/.gitignore new file mode 100644 index 00000000..ff5ec9a0 --- /dev/null +++ b/vendor/sebastian/version/.gitignore @@ -0,0 +1,2 @@ +/.php_cs.cache +/.idea diff --git a/vendor/sebastian/version/ChangeLog.md b/vendor/sebastian/version/ChangeLog.md new file mode 100644 index 00000000..10fd9a1a --- /dev/null +++ b/vendor/sebastian/version/ChangeLog.md @@ -0,0 +1,25 @@ +# ChangeLog + +All notable changes are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles. + +## [3.0.2] - 2020-09-28 + +### Changed + +* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3` + +## [3.0.1] - 2020-06-26 + +### Added + +* This component is now supported on PHP 8 + +## [3.0.0] - 2020-01-21 + +### Removed + +* This component is no longer supported on PHP 7.1 and PHP 7.2 + +[3.0.2]: https://github.com/sebastianbergmann/version/compare/3.0.1...3.0.2 +[3.0.1]: https://github.com/sebastianbergmann/version/compare/3.0.0...3.0.1 +[3.0.0]: https://github.com/sebastianbergmann/version/compare/2.0.1...3.0.0 diff --git a/vendor/sebastian/version/LICENSE b/vendor/sebastian/version/LICENSE new file mode 100644 index 00000000..aa6bca29 --- /dev/null +++ b/vendor/sebastian/version/LICENSE @@ -0,0 +1,33 @@ +Version + +Copyright (c) 2013-2020, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/sebastian/version/README.md b/vendor/sebastian/version/README.md new file mode 100644 index 00000000..2864c812 --- /dev/null +++ b/vendor/sebastian/version/README.md @@ -0,0 +1,43 @@ +# Version + +**Version** is a library that helps with managing the version number of Git-hosted PHP projects. + +## Installation + +You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): + + composer require sebastian/version + +If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency: + + composer require --dev sebastian/version + +## Usage + +The constructor of the `SebastianBergmann\Version` class expects two parameters: + +* `$release` is the version number of the latest release (`X.Y.Z`, for instance) or the name of the release series (`X.Y`) when no release has been made from that branch / for that release series yet. +* `$path` is the path to the directory (or a subdirectory thereof) where the sourcecode of the project can be found. Simply passing `__DIR__` here usually suffices. + +Apart from the constructor, the `SebastianBergmann\Version` class has a single public method: `getVersion()`. + +Here is a contrived example that shows the basic usage: + + getVersion()); + ?> + + string(18) "3.7.10-17-g00f3408" + +When a new release is prepared, the string that is passed to the constructor as the first argument needs to be updated. + +### How SebastianBergmann\Version::getVersion() works + +* If `$path` is not (part of) a Git repository and `$release` is in `X.Y.Z` format then `$release` is returned as-is. +* If `$path` is not (part of) a Git repository and `$release` is in `X.Y` format then `$release` is returned suffixed with `-dev`. +* If `$path` is (part of) a Git repository and `$release` is in `X.Y.Z` format then the output of `git describe --tags` is returned as-is. +* If `$path` is (part of) a Git repository and `$release` is in `X.Y` format then a string is returned that begins with `X.Y` and ends with information from `git describe --tags`. diff --git a/vendor/sebastian/version/composer.json b/vendor/sebastian/version/composer.json new file mode 100644 index 00000000..e76dbf41 --- /dev/null +++ b/vendor/sebastian/version/composer.json @@ -0,0 +1,37 @@ +{ + "name": "sebastian/version", + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues" + }, + "config": { + "platform": { + "php": "7.3.0" + }, + "optimize-autoloader": true, + "sort-packages": true + }, + "prefer-stable": true, + "require": { + "php": ">=7.3" + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + } +} diff --git a/vendor/sebastian/version/src/Version.php b/vendor/sebastian/version/src/Version.php new file mode 100644 index 00000000..53ae7894 --- /dev/null +++ b/vendor/sebastian/version/src/Version.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SebastianBergmann; + +final class Version +{ + /** + * @var string + */ + private $path; + + /** + * @var string + */ + private $release; + + /** + * @var string + */ + private $version; + + public function __construct(string $release, string $path) + { + $this->release = $release; + $this->path = $path; + } + + public function getVersion(): string + { + if ($this->version === null) { + if (\substr_count($this->release, '.') + 1 === 3) { + $this->version = $this->release; + } else { + $this->version = $this->release . '-dev'; + } + + $git = $this->getGitInformation($this->path); + + if ($git) { + if (\substr_count($this->release, '.') + 1 === 3) { + $this->version = $git; + } else { + $git = \explode('-', $git); + + $this->version = $this->release . '-' . \end($git); + } + } + } + + return $this->version; + } + + /** + * @return bool|string + */ + private function getGitInformation(string $path) + { + if (!\is_dir($path . DIRECTORY_SEPARATOR . '.git')) { + return false; + } + + $process = \proc_open( + 'git describe --tags', + [ + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], + ], + $pipes, + $path + ); + + if (!\is_resource($process)) { + return false; + } + + $result = \trim(\stream_get_contents($pipes[1])); + + \fclose($pipes[1]); + \fclose($pipes[2]); + + $returnCode = \proc_close($process); + + if ($returnCode !== 0) { + return false; + } + + return $result; + } +} diff --git a/vendor/symfony/console/Application.php b/vendor/symfony/console/Application.php new file mode 100644 index 00000000..9df983cc --- /dev/null +++ b/vendor/symfony/console/Application.php @@ -0,0 +1,1284 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Command\CompleteCommand; +use Symfony\Component\Console\Command\DumpCompletionCommand; +use Symfony\Component\Console\Command\HelpCommand; +use Symfony\Component\Console\Command\LazyCommand; +use Symfony\Component\Console\Command\ListCommand; +use Symfony\Component\Console\Command\SignalableCommandInterface; +use Symfony\Component\Console\CommandLoader\CommandLoaderInterface; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Completion\Suggestion; +use Symfony\Component\Console\Event\ConsoleCommandEvent; +use Symfony\Component\Console\Event\ConsoleErrorEvent; +use Symfony\Component\Console\Event\ConsoleSignalEvent; +use Symfony\Component\Console\Event\ConsoleTerminateEvent; +use Symfony\Component\Console\Exception\CommandNotFoundException; +use Symfony\Component\Console\Exception\ExceptionInterface; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Exception\NamespaceNotFoundException; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Helper\DebugFormatterHelper; +use Symfony\Component\Console\Helper\DescriptorHelper; +use Symfony\Component\Console\Helper\FormatterHelper; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Helper\ProcessHelper; +use Symfony\Component\Console\Helper\QuestionHelper; +use Symfony\Component\Console\Input\ArgvInput; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputAwareInterface; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\SignalRegistry\SignalRegistry; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\ErrorHandler\ErrorHandler; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * An Application is the container for a collection of commands. + * + * It is the main entry point of a Console application. + * + * This class is optimized for a standard CLI environment. + * + * Usage: + * + * $app = new Application('myapp', '1.0 (stable)'); + * $app->add(new SimpleCommand()); + * $app->run(); + * + * @author Fabien Potencier + */ +class Application implements ResetInterface +{ + private array $commands = []; + private bool $wantHelps = false; + private ?Command $runningCommand = null; + private string $name; + private string $version; + private ?CommandLoaderInterface $commandLoader = null; + private bool $catchExceptions = true; + private bool $catchErrors = false; + private bool $autoExit = true; + private InputDefinition $definition; + private HelperSet $helperSet; + private ?EventDispatcherInterface $dispatcher = null; + private Terminal $terminal; + private string $defaultCommand; + private bool $singleCommand = false; + private bool $initialized = false; + private ?SignalRegistry $signalRegistry = null; + private array $signalsToDispatchEvent = []; + + public function __construct(string $name = 'UNKNOWN', string $version = 'UNKNOWN') + { + $this->name = $name; + $this->version = $version; + $this->terminal = new Terminal(); + $this->defaultCommand = 'list'; + if (\defined('SIGINT') && SignalRegistry::isSupported()) { + $this->signalRegistry = new SignalRegistry(); + $this->signalsToDispatchEvent = [\SIGINT, \SIGTERM, \SIGUSR1, \SIGUSR2]; + } + } + + /** + * @final + */ + public function setDispatcher(EventDispatcherInterface $dispatcher): void + { + $this->dispatcher = $dispatcher; + } + + public function setCommandLoader(CommandLoaderInterface $commandLoader): void + { + $this->commandLoader = $commandLoader; + } + + public function getSignalRegistry(): SignalRegistry + { + if (!$this->signalRegistry) { + throw new RuntimeException('Signals are not supported. Make sure that the "pcntl" extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.'); + } + + return $this->signalRegistry; + } + + public function setSignalsToDispatchEvent(int ...$signalsToDispatchEvent): void + { + $this->signalsToDispatchEvent = $signalsToDispatchEvent; + } + + /** + * Runs the current application. + * + * @return int 0 if everything went fine, or an error code + * + * @throws \Exception When running fails. Bypass this when {@link setCatchExceptions()}. + */ + public function run(?InputInterface $input = null, ?OutputInterface $output = null): int + { + if (\function_exists('putenv')) { + @putenv('LINES='.$this->terminal->getHeight()); + @putenv('COLUMNS='.$this->terminal->getWidth()); + } + + $input ??= new ArgvInput(); + $output ??= new ConsoleOutput(); + + $renderException = function (\Throwable $e) use ($output) { + if ($output instanceof ConsoleOutputInterface) { + $this->renderThrowable($e, $output->getErrorOutput()); + } else { + $this->renderThrowable($e, $output); + } + }; + if ($phpHandler = set_exception_handler($renderException)) { + restore_exception_handler(); + if (!\is_array($phpHandler) || !$phpHandler[0] instanceof ErrorHandler) { + $errorHandler = true; + } elseif ($errorHandler = $phpHandler[0]->setExceptionHandler($renderException)) { + $phpHandler[0]->setExceptionHandler($errorHandler); + } + } + + $this->configureIO($input, $output); + + try { + $exitCode = $this->doRun($input, $output); + } catch (\Throwable $e) { + if ($e instanceof \Exception && !$this->catchExceptions) { + throw $e; + } + if (!$e instanceof \Exception && !$this->catchErrors) { + throw $e; + } + + $renderException($e); + + $exitCode = $e->getCode(); + if (is_numeric($exitCode)) { + $exitCode = (int) $exitCode; + if ($exitCode <= 0) { + $exitCode = 1; + } + } else { + $exitCode = 1; + } + } finally { + // if the exception handler changed, keep it + // otherwise, unregister $renderException + if (!$phpHandler) { + if (set_exception_handler($renderException) === $renderException) { + restore_exception_handler(); + } + restore_exception_handler(); + } elseif (!$errorHandler) { + $finalHandler = $phpHandler[0]->setExceptionHandler(null); + if ($finalHandler !== $renderException) { + $phpHandler[0]->setExceptionHandler($finalHandler); + } + } + } + + if ($this->autoExit) { + if ($exitCode > 255) { + $exitCode = 255; + } + + exit($exitCode); + } + + return $exitCode; + } + + /** + * Runs the current application. + * + * @return int 0 if everything went fine, or an error code + */ + public function doRun(InputInterface $input, OutputInterface $output): int + { + if (true === $input->hasParameterOption(['--version', '-V'], true)) { + $output->writeln($this->getLongVersion()); + + return 0; + } + + try { + // Makes ArgvInput::getFirstArgument() able to distinguish an option from an argument. + $input->bind($this->getDefinition()); + } catch (ExceptionInterface) { + // Errors must be ignored, full binding/validation happens later when the command is known. + } + + $name = $this->getCommandName($input); + if (true === $input->hasParameterOption(['--help', '-h'], true)) { + if (!$name) { + $name = 'help'; + $input = new ArrayInput(['command_name' => $this->defaultCommand]); + } else { + $this->wantHelps = true; + } + } + + if (!$name) { + $name = $this->defaultCommand; + $definition = $this->getDefinition(); + $definition->setArguments(array_merge( + $definition->getArguments(), + [ + 'command' => new InputArgument('command', InputArgument::OPTIONAL, $definition->getArgument('command')->getDescription(), $name), + ] + )); + } + + try { + $this->runningCommand = null; + // the command name MUST be the first element of the input + $command = $this->find($name); + } catch (\Throwable $e) { + if (($e instanceof CommandNotFoundException && !$e instanceof NamespaceNotFoundException) && 1 === \count($alternatives = $e->getAlternatives()) && $input->isInteractive()) { + $alternative = $alternatives[0]; + + $style = new SymfonyStyle($input, $output); + $output->writeln(''); + $formattedBlock = (new FormatterHelper())->formatBlock(sprintf('Command "%s" is not defined.', $name), 'error', true); + $output->writeln($formattedBlock); + if (!$style->confirm(sprintf('Do you want to run "%s" instead? ', $alternative), false)) { + if (null !== $this->dispatcher) { + $event = new ConsoleErrorEvent($input, $output, $e); + $this->dispatcher->dispatch($event, ConsoleEvents::ERROR); + + return $event->getExitCode(); + } + + return 1; + } + + $command = $this->find($alternative); + } else { + if (null !== $this->dispatcher) { + $event = new ConsoleErrorEvent($input, $output, $e); + $this->dispatcher->dispatch($event, ConsoleEvents::ERROR); + + if (0 === $event->getExitCode()) { + return 0; + } + + $e = $event->getError(); + } + + try { + if ($e instanceof CommandNotFoundException && $namespace = $this->findNamespace($name)) { + $helper = new DescriptorHelper(); + $helper->describe($output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output, $this, [ + 'format' => 'txt', + 'raw_text' => false, + 'namespace' => $namespace, + 'short' => false, + ]); + + return isset($event) ? $event->getExitCode() : 1; + } + + throw $e; + } catch (NamespaceNotFoundException) { + throw $e; + } + } + } + + if ($command instanceof LazyCommand) { + $command = $command->getCommand(); + } + + $this->runningCommand = $command; + $exitCode = $this->doRunCommand($command, $input, $output); + $this->runningCommand = null; + + return $exitCode; + } + + public function reset(): void + { + } + + public function setHelperSet(HelperSet $helperSet): void + { + $this->helperSet = $helperSet; + } + + /** + * Get the helper set associated with the command. + */ + public function getHelperSet(): HelperSet + { + return $this->helperSet ??= $this->getDefaultHelperSet(); + } + + public function setDefinition(InputDefinition $definition): void + { + $this->definition = $definition; + } + + /** + * Gets the InputDefinition related to this Application. + */ + public function getDefinition(): InputDefinition + { + $this->definition ??= $this->getDefaultInputDefinition(); + + if ($this->singleCommand) { + $inputDefinition = $this->definition; + $inputDefinition->setArguments(); + + return $inputDefinition; + } + + return $this->definition; + } + + /** + * Adds suggestions to $suggestions for the current completion input (e.g. option or argument). + */ + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ( + CompletionInput::TYPE_ARGUMENT_VALUE === $input->getCompletionType() + && 'command' === $input->getCompletionName() + ) { + foreach ($this->all() as $name => $command) { + // skip hidden commands and aliased commands as they already get added below + if ($command->isHidden() || $command->getName() !== $name) { + continue; + } + $suggestions->suggestValue(new Suggestion($command->getName(), $command->getDescription())); + foreach ($command->getAliases() as $name) { + $suggestions->suggestValue(new Suggestion($name, $command->getDescription())); + } + } + + return; + } + + if (CompletionInput::TYPE_OPTION_NAME === $input->getCompletionType()) { + $suggestions->suggestOptions($this->getDefinition()->getOptions()); + + return; + } + } + + /** + * Gets the help message. + */ + public function getHelp(): string + { + return $this->getLongVersion(); + } + + /** + * Gets whether to catch exceptions or not during commands execution. + */ + public function areExceptionsCaught(): bool + { + return $this->catchExceptions; + } + + /** + * Sets whether to catch exceptions or not during commands execution. + */ + public function setCatchExceptions(bool $boolean): void + { + $this->catchExceptions = $boolean; + } + + /** + * Sets whether to catch errors or not during commands execution. + */ + public function setCatchErrors(bool $catchErrors = true): void + { + $this->catchErrors = $catchErrors; + } + + /** + * Gets whether to automatically exit after a command execution or not. + */ + public function isAutoExitEnabled(): bool + { + return $this->autoExit; + } + + /** + * Sets whether to automatically exit after a command execution or not. + */ + public function setAutoExit(bool $boolean): void + { + $this->autoExit = $boolean; + } + + /** + * Gets the name of the application. + */ + public function getName(): string + { + return $this->name; + } + + /** + * Sets the application name. + */ + public function setName(string $name): void + { + $this->name = $name; + } + + /** + * Gets the application version. + */ + public function getVersion(): string + { + return $this->version; + } + + /** + * Sets the application version. + */ + public function setVersion(string $version): void + { + $this->version = $version; + } + + /** + * Returns the long version of the application. + */ + public function getLongVersion(): string + { + if ('UNKNOWN' !== $this->getName()) { + if ('UNKNOWN' !== $this->getVersion()) { + return sprintf('%s %s', $this->getName(), $this->getVersion()); + } + + return $this->getName(); + } + + return 'Console Tool'; + } + + /** + * Registers a new command. + */ + public function register(string $name): Command + { + return $this->add(new Command($name)); + } + + /** + * Adds an array of command objects. + * + * If a Command is not enabled it will not be added. + * + * @param Command[] $commands An array of commands + */ + public function addCommands(array $commands): void + { + foreach ($commands as $command) { + $this->add($command); + } + } + + /** + * Adds a command object. + * + * If a command with the same name already exists, it will be overridden. + * If the command is not enabled it will not be added. + */ + public function add(Command $command): ?Command + { + $this->init(); + + $command->setApplication($this); + + if (!$command->isEnabled()) { + $command->setApplication(null); + + return null; + } + + if (!$command instanceof LazyCommand) { + // Will throw if the command is not correctly initialized. + $command->getDefinition(); + } + + if (!$command->getName()) { + throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_debug_type($command))); + } + + $this->commands[$command->getName()] = $command; + + foreach ($command->getAliases() as $alias) { + $this->commands[$alias] = $command; + } + + return $command; + } + + /** + * Returns a registered command by name or alias. + * + * @throws CommandNotFoundException When given command name does not exist + */ + public function get(string $name): Command + { + $this->init(); + + if (!$this->has($name)) { + throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name)); + } + + // When the command has a different name than the one used at the command loader level + if (!isset($this->commands[$name])) { + throw new CommandNotFoundException(sprintf('The "%s" command cannot be found because it is registered under multiple names. Make sure you don\'t set a different name via constructor or "setName()".', $name)); + } + + $command = $this->commands[$name]; + + if ($this->wantHelps) { + $this->wantHelps = false; + + $helpCommand = $this->get('help'); + $helpCommand->setCommand($command); + + return $helpCommand; + } + + return $command; + } + + /** + * Returns true if the command exists, false otherwise. + */ + public function has(string $name): bool + { + $this->init(); + + return isset($this->commands[$name]) || ($this->commandLoader?->has($name) && $this->add($this->commandLoader->get($name))); + } + + /** + * Returns an array of all unique namespaces used by currently registered commands. + * + * It does not return the global namespace which always exists. + * + * @return string[] + */ + public function getNamespaces(): array + { + $namespaces = []; + foreach ($this->all() as $command) { + if ($command->isHidden()) { + continue; + } + + $namespaces[] = $this->extractAllNamespaces($command->getName()); + + foreach ($command->getAliases() as $alias) { + $namespaces[] = $this->extractAllNamespaces($alias); + } + } + + return array_values(array_unique(array_filter(array_merge([], ...$namespaces)))); + } + + /** + * Finds a registered namespace by a name or an abbreviation. + * + * @throws NamespaceNotFoundException When namespace is incorrect or ambiguous + */ + public function findNamespace(string $namespace): string + { + $allNamespaces = $this->getNamespaces(); + $expr = implode('[^:]*:', array_map('preg_quote', explode(':', $namespace))).'[^:]*'; + $namespaces = preg_grep('{^'.$expr.'}', $allNamespaces); + + if (empty($namespaces)) { + $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace); + + if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) { + if (1 == \count($alternatives)) { + $message .= "\n\nDid you mean this?\n "; + } else { + $message .= "\n\nDid you mean one of these?\n "; + } + + $message .= implode("\n ", $alternatives); + } + + throw new NamespaceNotFoundException($message, $alternatives); + } + + $exact = \in_array($namespace, $namespaces, true); + if (\count($namespaces) > 1 && !$exact) { + throw new NamespaceNotFoundException(sprintf("The namespace \"%s\" is ambiguous.\nDid you mean one of these?\n%s.", $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))), array_values($namespaces)); + } + + return $exact ? $namespace : reset($namespaces); + } + + /** + * Finds a command by name or alias. + * + * Contrary to get, this command tries to find the best + * match if you give it an abbreviation of a name or alias. + * + * @throws CommandNotFoundException When command name is incorrect or ambiguous + */ + public function find(string $name): Command + { + $this->init(); + + $aliases = []; + + foreach ($this->commands as $command) { + foreach ($command->getAliases() as $alias) { + if (!$this->has($alias)) { + $this->commands[$alias] = $command; + } + } + } + + if ($this->has($name)) { + return $this->get($name); + } + + $allCommands = $this->commandLoader ? array_merge($this->commandLoader->getNames(), array_keys($this->commands)) : array_keys($this->commands); + $expr = implode('[^:]*:', array_map('preg_quote', explode(':', $name))).'[^:]*'; + $commands = preg_grep('{^'.$expr.'}', $allCommands); + + if (empty($commands)) { + $commands = preg_grep('{^'.$expr.'}i', $allCommands); + } + + // if no commands matched or we just matched namespaces + if (empty($commands) || \count(preg_grep('{^'.$expr.'$}i', $commands)) < 1) { + if (false !== $pos = strrpos($name, ':')) { + // check if a namespace exists and contains commands + $this->findNamespace(substr($name, 0, $pos)); + } + + $message = sprintf('Command "%s" is not defined.', $name); + + if ($alternatives = $this->findAlternatives($name, $allCommands)) { + // remove hidden commands + $alternatives = array_filter($alternatives, fn ($name) => !$this->get($name)->isHidden()); + + if (1 == \count($alternatives)) { + $message .= "\n\nDid you mean this?\n "; + } else { + $message .= "\n\nDid you mean one of these?\n "; + } + $message .= implode("\n ", $alternatives); + } + + throw new CommandNotFoundException($message, array_values($alternatives)); + } + + // filter out aliases for commands which are already on the list + if (\count($commands) > 1) { + $commandList = $this->commandLoader ? array_merge(array_flip($this->commandLoader->getNames()), $this->commands) : $this->commands; + $commands = array_unique(array_filter($commands, function ($nameOrAlias) use (&$commandList, $commands, &$aliases) { + if (!$commandList[$nameOrAlias] instanceof Command) { + $commandList[$nameOrAlias] = $this->commandLoader->get($nameOrAlias); + } + + $commandName = $commandList[$nameOrAlias]->getName(); + + $aliases[$nameOrAlias] = $commandName; + + return $commandName === $nameOrAlias || !\in_array($commandName, $commands); + })); + } + + if (\count($commands) > 1) { + $usableWidth = $this->terminal->getWidth() - 10; + $abbrevs = array_values($commands); + $maxLen = 0; + foreach ($abbrevs as $abbrev) { + $maxLen = max(Helper::width($abbrev), $maxLen); + } + $abbrevs = array_map(function ($cmd) use ($commandList, $usableWidth, $maxLen, &$commands) { + if ($commandList[$cmd]->isHidden()) { + unset($commands[array_search($cmd, $commands)]); + + return false; + } + + $abbrev = str_pad($cmd, $maxLen, ' ').' '.$commandList[$cmd]->getDescription(); + + return Helper::width($abbrev) > $usableWidth ? Helper::substr($abbrev, 0, $usableWidth - 3).'...' : $abbrev; + }, array_values($commands)); + + if (\count($commands) > 1) { + $suggestions = $this->getAbbreviationSuggestions(array_filter($abbrevs)); + + throw new CommandNotFoundException(sprintf("Command \"%s\" is ambiguous.\nDid you mean one of these?\n%s.", $name, $suggestions), array_values($commands)); + } + } + + $command = $this->get(reset($commands)); + + if ($command->isHidden()) { + throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name)); + } + + return $command; + } + + /** + * Gets the commands (registered in the given namespace if provided). + * + * The array keys are the full names and the values the command instances. + * + * @return Command[] + */ + public function all(?string $namespace = null): array + { + $this->init(); + + if (null === $namespace) { + if (!$this->commandLoader) { + return $this->commands; + } + + $commands = $this->commands; + foreach ($this->commandLoader->getNames() as $name) { + if (!isset($commands[$name]) && $this->has($name)) { + $commands[$name] = $this->get($name); + } + } + + return $commands; + } + + $commands = []; + foreach ($this->commands as $name => $command) { + if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) { + $commands[$name] = $command; + } + } + + if ($this->commandLoader) { + foreach ($this->commandLoader->getNames() as $name) { + if (!isset($commands[$name]) && $namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1) && $this->has($name)) { + $commands[$name] = $this->get($name); + } + } + } + + return $commands; + } + + /** + * Returns an array of possible abbreviations given a set of names. + * + * @return string[][] + */ + public static function getAbbreviations(array $names): array + { + $abbrevs = []; + foreach ($names as $name) { + for ($len = \strlen($name); $len > 0; --$len) { + $abbrev = substr($name, 0, $len); + $abbrevs[$abbrev][] = $name; + } + } + + return $abbrevs; + } + + public function renderThrowable(\Throwable $e, OutputInterface $output): void + { + $output->writeln('', OutputInterface::VERBOSITY_QUIET); + + $this->doRenderThrowable($e, $output); + + if (null !== $this->runningCommand) { + $output->writeln(sprintf('%s', OutputFormatter::escape(sprintf($this->runningCommand->getSynopsis(), $this->getName()))), OutputInterface::VERBOSITY_QUIET); + $output->writeln('', OutputInterface::VERBOSITY_QUIET); + } + } + + protected function doRenderThrowable(\Throwable $e, OutputInterface $output): void + { + do { + $message = trim($e->getMessage()); + if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { + $class = get_debug_type($e); + $title = sprintf(' [%s%s] ', $class, 0 !== ($code = $e->getCode()) ? ' ('.$code.')' : ''); + $len = Helper::width($title); + } else { + $len = 0; + } + + if (str_contains($message, "@anonymous\0")) { + $message = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', fn ($m) => class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0], $message); + } + + $width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : \PHP_INT_MAX; + $lines = []; + foreach ('' !== $message ? preg_split('/\r?\n/', $message) : [] as $line) { + foreach ($this->splitStringByWidth($line, $width - 4) as $line) { + // pre-format lines to get the right string length + $lineLength = Helper::width($line) + 4; + $lines[] = [$line, $lineLength]; + + $len = max($lineLength, $len); + } + } + + $messages = []; + if (!$e instanceof ExceptionInterface || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { + $messages[] = sprintf('%s', OutputFormatter::escape(sprintf('In %s line %s:', basename($e->getFile()) ?: 'n/a', $e->getLine() ?: 'n/a'))); + } + $messages[] = $emptyLine = sprintf('%s', str_repeat(' ', $len)); + if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { + $messages[] = sprintf('%s%s', $title, str_repeat(' ', max(0, $len - Helper::width($title)))); + } + foreach ($lines as $line) { + $messages[] = sprintf(' %s %s', OutputFormatter::escape($line[0]), str_repeat(' ', $len - $line[1])); + } + $messages[] = $emptyLine; + $messages[] = ''; + + $output->writeln($messages, OutputInterface::VERBOSITY_QUIET); + + if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { + $output->writeln('Exception trace:', OutputInterface::VERBOSITY_QUIET); + + // exception related properties + $trace = $e->getTrace(); + + array_unshift($trace, [ + 'function' => '', + 'file' => $e->getFile() ?: 'n/a', + 'line' => $e->getLine() ?: 'n/a', + 'args' => [], + ]); + + for ($i = 0, $count = \count($trace); $i < $count; ++$i) { + $class = $trace[$i]['class'] ?? ''; + $type = $trace[$i]['type'] ?? ''; + $function = $trace[$i]['function'] ?? ''; + $file = $trace[$i]['file'] ?? 'n/a'; + $line = $trace[$i]['line'] ?? 'n/a'; + + $output->writeln(sprintf(' %s%s at %s:%s', $class, $function ? $type.$function.'()' : '', $file, $line), OutputInterface::VERBOSITY_QUIET); + } + + $output->writeln('', OutputInterface::VERBOSITY_QUIET); + } + } while ($e = $e->getPrevious()); + } + + /** + * Configures the input and output instances based on the user arguments and options. + */ + protected function configureIO(InputInterface $input, OutputInterface $output): void + { + if (true === $input->hasParameterOption(['--ansi'], true)) { + $output->setDecorated(true); + } elseif (true === $input->hasParameterOption(['--no-ansi'], true)) { + $output->setDecorated(false); + } + + if (true === $input->hasParameterOption(['--no-interaction', '-n'], true)) { + $input->setInteractive(false); + } + + switch ($shellVerbosity = (int) getenv('SHELL_VERBOSITY')) { + case -1: + $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); + break; + case 1: + $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); + break; + case 2: + $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE); + break; + case 3: + $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); + break; + default: + $shellVerbosity = 0; + break; + } + + if (true === $input->hasParameterOption(['--quiet', '-q'], true)) { + $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); + $shellVerbosity = -1; + } else { + if ($input->hasParameterOption('-vvv', true) || $input->hasParameterOption('--verbose=3', true) || 3 === $input->getParameterOption('--verbose', false, true)) { + $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); + $shellVerbosity = 3; + } elseif ($input->hasParameterOption('-vv', true) || $input->hasParameterOption('--verbose=2', true) || 2 === $input->getParameterOption('--verbose', false, true)) { + $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE); + $shellVerbosity = 2; + } elseif ($input->hasParameterOption('-v', true) || $input->hasParameterOption('--verbose=1', true) || $input->hasParameterOption('--verbose', true) || $input->getParameterOption('--verbose', false, true)) { + $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); + $shellVerbosity = 1; + } + } + + if (-1 === $shellVerbosity) { + $input->setInteractive(false); + } + + if (\function_exists('putenv')) { + @putenv('SHELL_VERBOSITY='.$shellVerbosity); + } + $_ENV['SHELL_VERBOSITY'] = $shellVerbosity; + $_SERVER['SHELL_VERBOSITY'] = $shellVerbosity; + } + + /** + * Runs the current command. + * + * If an event dispatcher has been attached to the application, + * events are also dispatched during the life-cycle of the command. + * + * @return int 0 if everything went fine, or an error code + */ + protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output): int + { + foreach ($command->getHelperSet() as $helper) { + if ($helper instanceof InputAwareInterface) { + $helper->setInput($input); + } + } + + $commandSignals = $command instanceof SignalableCommandInterface ? $command->getSubscribedSignals() : []; + if ($commandSignals || $this->dispatcher && $this->signalsToDispatchEvent) { + if (!$this->signalRegistry) { + throw new RuntimeException('Unable to subscribe to signal events. Make sure that the "pcntl" extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.'); + } + + if (Terminal::hasSttyAvailable()) { + $sttyMode = shell_exec('stty -g'); + + foreach ([\SIGINT, \SIGTERM] as $signal) { + $this->signalRegistry->register($signal, static fn () => shell_exec('stty '.$sttyMode)); + } + } + + if ($this->dispatcher) { + // We register application signals, so that we can dispatch the event + foreach ($this->signalsToDispatchEvent as $signal) { + $event = new ConsoleSignalEvent($command, $input, $output, $signal); + + $this->signalRegistry->register($signal, function ($signal) use ($event, $command, $commandSignals) { + $this->dispatcher->dispatch($event, ConsoleEvents::SIGNAL); + $exitCode = $event->getExitCode(); + + // If the command is signalable, we call the handleSignal() method + if (\in_array($signal, $commandSignals, true)) { + $exitCode = $command->handleSignal($signal, $exitCode); + } + + if (false !== $exitCode) { + $event = new ConsoleTerminateEvent($command, $event->getInput(), $event->getOutput(), $exitCode, $signal); + $this->dispatcher->dispatch($event, ConsoleEvents::TERMINATE); + + exit($event->getExitCode()); + } + }); + } + + // then we register command signals, but not if already handled after the dispatcher + $commandSignals = array_diff($commandSignals, $this->signalsToDispatchEvent); + } + + foreach ($commandSignals as $signal) { + $this->signalRegistry->register($signal, function (int $signal) use ($command): void { + if (false !== $exitCode = $command->handleSignal($signal)) { + exit($exitCode); + } + }); + } + } + + if (null === $this->dispatcher) { + return $command->run($input, $output); + } + + // bind before the console.command event, so the listeners have access to input options/arguments + try { + $command->mergeApplicationDefinition(); + $input->bind($command->getDefinition()); + } catch (ExceptionInterface) { + // ignore invalid options/arguments for now, to allow the event listeners to customize the InputDefinition + } + + $event = new ConsoleCommandEvent($command, $input, $output); + $e = null; + + try { + $this->dispatcher->dispatch($event, ConsoleEvents::COMMAND); + + if ($event->commandShouldRun()) { + $exitCode = $command->run($input, $output); + } else { + $exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED; + } + } catch (\Throwable $e) { + $event = new ConsoleErrorEvent($input, $output, $e, $command); + $this->dispatcher->dispatch($event, ConsoleEvents::ERROR); + $e = $event->getError(); + + if (0 === $exitCode = $event->getExitCode()) { + $e = null; + } + } + + $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode); + $this->dispatcher->dispatch($event, ConsoleEvents::TERMINATE); + + if (null !== $e) { + throw $e; + } + + return $event->getExitCode(); + } + + /** + * Gets the name of the command based on input. + */ + protected function getCommandName(InputInterface $input): ?string + { + return $this->singleCommand ? $this->defaultCommand : $input->getFirstArgument(); + } + + /** + * Gets the default input definition. + */ + protected function getDefaultInputDefinition(): InputDefinition + { + return new InputDefinition([ + new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'), + new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display help for the given command. When no command is given display help for the '.$this->defaultCommand.' command'), + new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'), + new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'), + new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version'), + new InputOption('--ansi', '', InputOption::VALUE_NEGATABLE, 'Force (or disable --no-ansi) ANSI output', null), + new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'), + ]); + } + + /** + * Gets the default commands that should always be available. + * + * @return Command[] + */ + protected function getDefaultCommands(): array + { + return [new HelpCommand(), new ListCommand(), new CompleteCommand(), new DumpCompletionCommand()]; + } + + /** + * Gets the default helper set with the helpers that should always be available. + */ + protected function getDefaultHelperSet(): HelperSet + { + return new HelperSet([ + new FormatterHelper(), + new DebugFormatterHelper(), + new ProcessHelper(), + new QuestionHelper(), + ]); + } + + /** + * Returns abbreviated suggestions in string format. + */ + private function getAbbreviationSuggestions(array $abbrevs): string + { + return ' '.implode("\n ", $abbrevs); + } + + /** + * Returns the namespace part of the command name. + * + * This method is not part of public API and should not be used directly. + */ + public function extractNamespace(string $name, ?int $limit = null): string + { + $parts = explode(':', $name, -1); + + return implode(':', null === $limit ? $parts : \array_slice($parts, 0, $limit)); + } + + /** + * Finds alternative of $name among $collection, + * if nothing is found in $collection, try in $abbrevs. + * + * @return string[] + */ + private function findAlternatives(string $name, iterable $collection): array + { + $threshold = 1e3; + $alternatives = []; + + $collectionParts = []; + foreach ($collection as $item) { + $collectionParts[$item] = explode(':', $item); + } + + foreach (explode(':', $name) as $i => $subname) { + foreach ($collectionParts as $collectionName => $parts) { + $exists = isset($alternatives[$collectionName]); + if (!isset($parts[$i]) && $exists) { + $alternatives[$collectionName] += $threshold; + continue; + } elseif (!isset($parts[$i])) { + continue; + } + + $lev = levenshtein($subname, $parts[$i]); + if ($lev <= \strlen($subname) / 3 || '' !== $subname && str_contains($parts[$i], $subname)) { + $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev; + } elseif ($exists) { + $alternatives[$collectionName] += $threshold; + } + } + } + + foreach ($collection as $item) { + $lev = levenshtein($name, $item); + if ($lev <= \strlen($name) / 3 || str_contains($item, $name)) { + $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev; + } + } + + $alternatives = array_filter($alternatives, fn ($lev) => $lev < 2 * $threshold); + ksort($alternatives, \SORT_NATURAL | \SORT_FLAG_CASE); + + return array_keys($alternatives); + } + + /** + * Sets the default Command name. + * + * @return $this + */ + public function setDefaultCommand(string $commandName, bool $isSingleCommand = false): static + { + $this->defaultCommand = explode('|', ltrim($commandName, '|'))[0]; + + if ($isSingleCommand) { + // Ensure the command exist + $this->find($commandName); + + $this->singleCommand = true; + } + + return $this; + } + + /** + * @internal + */ + public function isSingleCommand(): bool + { + return $this->singleCommand; + } + + private function splitStringByWidth(string $string, int $width): array + { + // str_split is not suitable for multi-byte characters, we should use preg_split to get char array properly. + // additionally, array_slice() is not enough as some character has doubled width. + // we need a function to split string not by character count but by string width + if (false === $encoding = mb_detect_encoding($string, null, true)) { + return str_split($string, $width); + } + + $utf8String = mb_convert_encoding($string, 'utf8', $encoding); + $lines = []; + $line = ''; + + $offset = 0; + while (preg_match('/.{1,10000}/u', $utf8String, $m, 0, $offset)) { + $offset += \strlen($m[0]); + + foreach (preg_split('//u', $m[0]) as $char) { + // test if $char could be appended to current line + if (mb_strwidth($line.$char, 'utf8') <= $width) { + $line .= $char; + continue; + } + // if not, push current line to array and make new line + $lines[] = str_pad($line, $width); + $line = $char; + } + } + + $lines[] = \count($lines) ? str_pad($line, $width) : $line; + + mb_convert_variables($encoding, 'utf8', $lines); + + return $lines; + } + + /** + * Returns all namespaces of the command name. + * + * @return string[] + */ + private function extractAllNamespaces(string $name): array + { + // -1 as third argument is needed to skip the command short name when exploding + $parts = explode(':', $name, -1); + $namespaces = []; + + foreach ($parts as $part) { + if (\count($namespaces)) { + $namespaces[] = end($namespaces).':'.$part; + } else { + $namespaces[] = $part; + } + } + + return $namespaces; + } + + private function init(): void + { + if ($this->initialized) { + return; + } + $this->initialized = true; + + foreach ($this->getDefaultCommands() as $command) { + $this->add($command); + } + } +} diff --git a/vendor/symfony/console/Attribute/AsCommand.php b/vendor/symfony/console/Attribute/AsCommand.php new file mode 100644 index 00000000..b337f548 --- /dev/null +++ b/vendor/symfony/console/Attribute/AsCommand.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Attribute; + +/** + * Service tag to autoconfigure commands. + */ +#[\Attribute(\Attribute::TARGET_CLASS)] +class AsCommand +{ + public function __construct( + public string $name, + public ?string $description = null, + array $aliases = [], + bool $hidden = false, + ) { + if (!$hidden && !$aliases) { + return; + } + + $name = explode('|', $name); + $name = array_merge($name, $aliases); + + if ($hidden && '' !== $name[0]) { + array_unshift($name, ''); + } + + $this->name = implode('|', $name); + } +} diff --git a/vendor/symfony/console/CHANGELOG.md b/vendor/symfony/console/CHANGELOG.md new file mode 100644 index 00000000..0ff71d2f --- /dev/null +++ b/vendor/symfony/console/CHANGELOG.md @@ -0,0 +1,269 @@ +CHANGELOG +========= + +7.0 +--- + + * Add method `__toString()` to `InputInterface` + * Remove `Command::$defaultName` and `Command::$defaultDescription`, use the `AsCommand` attribute instead + * Require explicit argument when calling `*Command::setApplication()`, `*FormatterStyle::setForeground/setBackground()`, `Helper::setHelpSet()`, `Input*::setDefault()` and `Question::setAutocompleterCallback/setValidator()` + * Remove `StringInput::REGEX_STRING` + +6.4 +--- + + * Add `SignalMap` to map signal value to its name + * Multi-line text in vertical tables is aligned properly + * The application can also catch errors with `Application::setCatchErrors(true)` + * Add `RunCommandMessage` and `RunCommandMessageHandler` + * Dispatch `ConsoleTerminateEvent` after an exit on signal handling and add `ConsoleTerminateEvent::getInterruptingSignal()` + +6.3 +--- + + * Add support for choosing exit code while handling signal, or to not exit at all + * Add `ProgressBar::setPlaceholderFormatter` to set a placeholder attached to a instance, instead of being global. + * Add `ReStructuredTextDescriptor` + +6.2 +--- + + * Improve truecolor terminal detection in some cases + * Add support for 256 color terminals (conversion from Ansi24 to Ansi8 if terminal is capable of it) + * Deprecate calling `*Command::setApplication()`, `*FormatterStyle::setForeground/setBackground()`, `Helper::setHelpSet()`, `Input*::setDefault()`, `Question::setAutocompleterCallback/setValidator()`without any arguments + * Change the signature of `OutputFormatterStyleInterface::setForeground/setBackground()` to `setForeground/setBackground(?string)` + * Change the signature of `HelperInterface::setHelperSet()` to `setHelperSet(?HelperSet)` + +6.1 +--- + + * Add support to display table vertically when calling setVertical() + * Add method `__toString()` to `InputInterface` + * Added `OutputWrapper` to prevent truncated URL in `SymfonyStyle::createBlock`. + * Deprecate `Command::$defaultName` and `Command::$defaultDescription`, use the `AsCommand` attribute instead + * Add suggested values for arguments and options in input definition, for input completion + * Add `$resumeAt` parameter to `ProgressBar#start()`, so that one can easily 'resume' progress on longer tasks, and still get accurate `getEstimate()` and `getRemaining()` results. + +6.0 +--- + + * `Command::setHidden()` has a default value (`true`) for `$hidden` parameter and is final + * Remove `Helper::strlen()`, use `Helper::width()` instead + * Remove `Helper::strlenWithoutDecoration()`, use `Helper::removeDecoration()` instead + * `AddConsoleCommandPass` can not be configured anymore + * Remove `HelperSet::setCommand()` and `getCommand()` without replacement + +5.4 +--- + + * Add `TesterTrait::assertCommandIsSuccessful()` to test command + * Deprecate `HelperSet::setCommand()` and `getCommand()` without replacement + +5.3 +--- + + * Add `GithubActionReporter` to render annotations in a Github Action + * Add `InputOption::VALUE_NEGATABLE` flag to handle `--foo`/`--no-foo` options + * Add the `Command::$defaultDescription` static property and the `description` attribute + on the `console.command` tag to allow the `list` command to instantiate commands lazily + * Add option `--short` to the `list` command + * Add support for bright colors + * Add `#[AsCommand]` attribute for declaring commands on PHP 8 + * Add `Helper::width()` and `Helper::length()` + * The `--ansi` and `--no-ansi` options now default to `null`. + +5.2.0 +----- + + * Added `SingleCommandApplication::setAutoExit()` to allow testing via `CommandTester` + * added support for multiline responses to questions through `Question::setMultiline()` + and `Question::isMultiline()` + * Added `SignalRegistry` class to stack signals handlers + * Added support for signals: + * Added `Application::getSignalRegistry()` and `Application::setSignalsToDispatchEvent()` methods + * Added `SignalableCommandInterface` interface + * Added `TableCellStyle` class to customize table cell + * Removed `php ` prefix invocation from help messages. + +5.1.0 +----- + + * `Command::setHidden()` is final since Symfony 5.1 + * Add `SingleCommandApplication` + * Add `Cursor` class + +5.0.0 +----- + + * removed support for finding hidden commands using an abbreviation, use the full name instead + * removed `TableStyle::setCrossingChar()` method in favor of `TableStyle::setDefaultCrossingChar()` + * removed `TableStyle::setHorizontalBorderChar()` method in favor of `TableStyle::setDefaultCrossingChars()` + * removed `TableStyle::getHorizontalBorderChar()` method in favor of `TableStyle::getBorderChars()` + * removed `TableStyle::setVerticalBorderChar()` method in favor of `TableStyle::setVerticalBorderChars()` + * removed `TableStyle::getVerticalBorderChar()` method in favor of `TableStyle::getBorderChars()` + * removed support for returning `null` from `Command::execute()`, return `0` instead + * `ProcessHelper::run()` accepts only `array|Symfony\Component\Process\Process` for its `command` argument + * `Application::setDispatcher` accepts only `Symfony\Contracts\EventDispatcher\EventDispatcherInterface` + for its `dispatcher` argument + * renamed `Application::renderException()` and `Application::doRenderException()` + to `renderThrowable()` and `doRenderThrowable()` respectively. + +4.4.0 +----- + + * deprecated finding hidden commands using an abbreviation, use the full name instead + * added `Question::setTrimmable` default to true to allow the answer to be trimmed + * added method `minSecondsBetweenRedraws()` and `maxSecondsBetweenRedraws()` on `ProgressBar` + * `Application` implements `ResetInterface` + * marked all dispatched event classes as `@final` + * added support for displaying table horizontally + * deprecated returning `null` from `Command::execute()`, return `0` instead + * Deprecated the `Application::renderException()` and `Application::doRenderException()` methods, + use `renderThrowable()` and `doRenderThrowable()` instead. + * added support for the `NO_COLOR` env var (https://no-color.org/) + +4.3.0 +----- + + * added support for hyperlinks + * added `ProgressBar::iterate()` method that simplify updating the progress bar when iterating + * added `Question::setAutocompleterCallback()` to provide a callback function + that dynamically generates suggestions as the user types + +4.2.0 +----- + + * allowed passing commands as `[$process, 'ENV_VAR' => 'value']` to + `ProcessHelper::run()` to pass environment variables + * deprecated passing a command as a string to `ProcessHelper::run()`, + pass it the command as an array of its arguments instead + * made the `ProcessHelper` class final + * added `WrappableOutputFormatterInterface::formatAndWrap()` (implemented in `OutputFormatter`) + * added `capture_stderr_separately` option to `CommandTester::execute()` + +4.1.0 +----- + + * added option to run suggested command if command is not found and only 1 alternative is available + * added option to modify console output and print multiple modifiable sections + * added support for iterable messages in output `write` and `writeln` methods + +4.0.0 +----- + + * `OutputFormatter` throws an exception when unknown options are used + * removed `QuestionHelper::setInputStream()/getInputStream()` + * removed `Application::getTerminalWidth()/getTerminalHeight()` and + `Application::setTerminalDimensions()/getTerminalDimensions()` + * removed `ConsoleExceptionEvent` + * removed `ConsoleEvents::EXCEPTION` + +3.4.0 +----- + + * added `SHELL_VERBOSITY` env var to control verbosity + * added `CommandLoaderInterface`, `FactoryCommandLoader` and PSR-11 + `ContainerCommandLoader` for commands lazy-loading + * added a case-insensitive command name matching fallback + * added static `Command::$defaultName/getDefaultName()`, allowing for + commands to be registered at compile time in the application command loader. + Setting the `$defaultName` property avoids the need for filling the `command` + attribute on the `console.command` tag when using `AddConsoleCommandPass`. + +3.3.0 +----- + + * added `ExceptionListener` + * added `AddConsoleCommandPass` (originally in FrameworkBundle) + * [BC BREAK] `Input::getOption()` no longer returns the default value for options + with value optional explicitly passed empty + * added console.error event to catch exceptions thrown by other listeners + * deprecated console.exception event in favor of console.error + * added ability to handle `CommandNotFoundException` through the + `console.error` event + * deprecated default validation in `SymfonyQuestionHelper::ask` + +3.2.0 +------ + + * added `setInputs()` method to CommandTester for ease testing of commands expecting inputs + * added `setStream()` and `getStream()` methods to Input (implement StreamableInputInterface) + * added StreamableInputInterface + * added LockableTrait + +3.1.0 +----- + + * added truncate method to FormatterHelper + * added setColumnWidth(s) method to Table + +2.8.3 +----- + + * remove readline support from the question helper as it caused issues + +2.8.0 +----- + + * use readline for user input in the question helper when available to allow + the use of arrow keys + +2.6.0 +----- + + * added a Process helper + * added a DebugFormatter helper + +2.5.0 +----- + + * deprecated the dialog helper (use the question helper instead) + * deprecated TableHelper in favor of Table + * deprecated ProgressHelper in favor of ProgressBar + * added ConsoleLogger + * added a question helper + * added a way to set the process name of a command + * added a way to set a default command instead of `ListCommand` + +2.4.0 +----- + + * added a way to force terminal dimensions + * added a convenient method to detect verbosity level + * [BC BREAK] made descriptors use output instead of returning a string + +2.3.0 +----- + + * added multiselect support to the select dialog helper + * added Table Helper for tabular data rendering + * added support for events in `Application` + * added a way to normalize EOLs in `ApplicationTester::getDisplay()` and `CommandTester::getDisplay()` + * added a way to set the progress bar progress via the `setCurrent` method + * added support for multiple InputOption shortcuts, written as `'-a|-b|-c'` + * added two additional verbosity levels, VERBOSITY_VERY_VERBOSE and VERBOSITY_DEBUG + +2.2.0 +----- + + * added support for colorization on Windows via ConEmu + * add a method to Dialog Helper to ask for a question and hide the response + * added support for interactive selections in console (DialogHelper::select()) + * added support for autocompletion as you type in Dialog Helper + +2.1.0 +----- + + * added ConsoleOutputInterface + * added the possibility to disable a command (Command::isEnabled()) + * added suggestions when a command does not exist + * added a --raw option to the list command + * added support for STDERR in the console output class (errors are now sent + to STDERR) + * made the defaults (helper set, commands, input definition) in Application + more easily customizable + * added support for the shell even if readline is not available + * added support for process isolation in Symfony shell via + `--process-isolation` switch + * added support for `--`, which disables options parsing after that point + (tokens will be parsed as arguments) diff --git a/vendor/symfony/console/CI/GithubActionReporter.php b/vendor/symfony/console/CI/GithubActionReporter.php new file mode 100644 index 00000000..2cae6fd8 --- /dev/null +++ b/vendor/symfony/console/CI/GithubActionReporter.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\CI; + +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Utility class for Github actions. + * + * @author Maxime Steinhausser + */ +class GithubActionReporter +{ + private OutputInterface $output; + + /** + * @see https://github.com/actions/toolkit/blob/5e5e1b7aacba68a53836a34db4a288c3c1c1585b/packages/core/src/command.ts#L80-L85 + */ + private const ESCAPED_DATA = [ + '%' => '%25', + "\r" => '%0D', + "\n" => '%0A', + ]; + + /** + * @see https://github.com/actions/toolkit/blob/5e5e1b7aacba68a53836a34db4a288c3c1c1585b/packages/core/src/command.ts#L87-L94 + */ + private const ESCAPED_PROPERTIES = [ + '%' => '%25', + "\r" => '%0D', + "\n" => '%0A', + ':' => '%3A', + ',' => '%2C', + ]; + + public function __construct(OutputInterface $output) + { + $this->output = $output; + } + + public static function isGithubActionEnvironment(): bool + { + return false !== getenv('GITHUB_ACTIONS'); + } + + /** + * Output an error using the Github annotations format. + * + * @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-an-error-message + */ + public function error(string $message, ?string $file = null, ?int $line = null, ?int $col = null): void + { + $this->log('error', $message, $file, $line, $col); + } + + /** + * Output a warning using the Github annotations format. + * + * @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message + */ + public function warning(string $message, ?string $file = null, ?int $line = null, ?int $col = null): void + { + $this->log('warning', $message, $file, $line, $col); + } + + /** + * Output a debug log using the Github annotations format. + * + * @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-a-debug-message + */ + public function debug(string $message, ?string $file = null, ?int $line = null, ?int $col = null): void + { + $this->log('debug', $message, $file, $line, $col); + } + + private function log(string $type, string $message, ?string $file = null, ?int $line = null, ?int $col = null): void + { + // Some values must be encoded. + $message = strtr($message, self::ESCAPED_DATA); + + if (!$file) { + // No file provided, output the message solely: + $this->output->writeln(sprintf('::%s::%s', $type, $message)); + + return; + } + + $this->output->writeln(sprintf('::%s file=%s,line=%s,col=%s::%s', $type, strtr($file, self::ESCAPED_PROPERTIES), strtr($line ?? 1, self::ESCAPED_PROPERTIES), strtr($col ?? 0, self::ESCAPED_PROPERTIES), $message)); + } +} diff --git a/vendor/symfony/console/Color.php b/vendor/symfony/console/Color.php new file mode 100644 index 00000000..60ed046a --- /dev/null +++ b/vendor/symfony/console/Color.php @@ -0,0 +1,133 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * @author Fabien Potencier + */ +final class Color +{ + private const COLORS = [ + 'black' => 0, + 'red' => 1, + 'green' => 2, + 'yellow' => 3, + 'blue' => 4, + 'magenta' => 5, + 'cyan' => 6, + 'white' => 7, + 'default' => 9, + ]; + + private const BRIGHT_COLORS = [ + 'gray' => 0, + 'bright-red' => 1, + 'bright-green' => 2, + 'bright-yellow' => 3, + 'bright-blue' => 4, + 'bright-magenta' => 5, + 'bright-cyan' => 6, + 'bright-white' => 7, + ]; + + private const AVAILABLE_OPTIONS = [ + 'bold' => ['set' => 1, 'unset' => 22], + 'underscore' => ['set' => 4, 'unset' => 24], + 'blink' => ['set' => 5, 'unset' => 25], + 'reverse' => ['set' => 7, 'unset' => 27], + 'conceal' => ['set' => 8, 'unset' => 28], + ]; + + private string $foreground; + private string $background; + private array $options = []; + + public function __construct(string $foreground = '', string $background = '', array $options = []) + { + $this->foreground = $this->parseColor($foreground); + $this->background = $this->parseColor($background, true); + + foreach ($options as $option) { + if (!isset(self::AVAILABLE_OPTIONS[$option])) { + throw new InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s).', $option, implode(', ', array_keys(self::AVAILABLE_OPTIONS)))); + } + + $this->options[$option] = self::AVAILABLE_OPTIONS[$option]; + } + } + + public function apply(string $text): string + { + return $this->set().$text.$this->unset(); + } + + public function set(): string + { + $setCodes = []; + if ('' !== $this->foreground) { + $setCodes[] = $this->foreground; + } + if ('' !== $this->background) { + $setCodes[] = $this->background; + } + foreach ($this->options as $option) { + $setCodes[] = $option['set']; + } + if (0 === \count($setCodes)) { + return ''; + } + + return sprintf("\033[%sm", implode(';', $setCodes)); + } + + public function unset(): string + { + $unsetCodes = []; + if ('' !== $this->foreground) { + $unsetCodes[] = 39; + } + if ('' !== $this->background) { + $unsetCodes[] = 49; + } + foreach ($this->options as $option) { + $unsetCodes[] = $option['unset']; + } + if (0 === \count($unsetCodes)) { + return ''; + } + + return sprintf("\033[%sm", implode(';', $unsetCodes)); + } + + private function parseColor(string $color, bool $background = false): string + { + if ('' === $color) { + return ''; + } + + if ('#' === $color[0]) { + return ($background ? '4' : '3').Terminal::getColorMode()->convertFromHexToAnsiColorCode($color); + } + + if (isset(self::COLORS[$color])) { + return ($background ? '4' : '3').self::COLORS[$color]; + } + + if (isset(self::BRIGHT_COLORS[$color])) { + return ($background ? '10' : '9').self::BRIGHT_COLORS[$color]; + } + + throw new InvalidArgumentException(sprintf('Invalid "%s" color; expected one of (%s).', $color, implode(', ', array_merge(array_keys(self::COLORS), array_keys(self::BRIGHT_COLORS))))); + } +} diff --git a/vendor/symfony/console/Command/Command.php b/vendor/symfony/console/Command/Command.php new file mode 100644 index 00000000..07b56ec1 --- /dev/null +++ b/vendor/symfony/console/Command/Command.php @@ -0,0 +1,668 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Completion\Suggestion; +use Symfony\Component\Console\Exception\ExceptionInterface; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Helper\HelperInterface; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Base class for all commands. + * + * @author Fabien Potencier + */ +class Command +{ + // see https://tldp.org/LDP/abs/html/exitcodes.html + public const SUCCESS = 0; + public const FAILURE = 1; + public const INVALID = 2; + + private ?Application $application = null; + private ?string $name = null; + private ?string $processTitle = null; + private array $aliases = []; + private InputDefinition $definition; + private bool $hidden = false; + private string $help = ''; + private string $description = ''; + private ?InputDefinition $fullDefinition = null; + private bool $ignoreValidationErrors = false; + private ?\Closure $code = null; + private array $synopsis = []; + private array $usages = []; + private ?HelperSet $helperSet = null; + + public static function getDefaultName(): ?string + { + if ($attribute = (new \ReflectionClass(static::class))->getAttributes(AsCommand::class)) { + return $attribute[0]->newInstance()->name; + } + + return null; + } + + public static function getDefaultDescription(): ?string + { + if ($attribute = (new \ReflectionClass(static::class))->getAttributes(AsCommand::class)) { + return $attribute[0]->newInstance()->description; + } + + return null; + } + + /** + * @param string|null $name The name of the command; passing null means it must be set in configure() + * + * @throws LogicException When the command name is empty + */ + public function __construct(?string $name = null) + { + $this->definition = new InputDefinition(); + + if (null === $name && null !== $name = static::getDefaultName()) { + $aliases = explode('|', $name); + + if ('' === $name = array_shift($aliases)) { + $this->setHidden(true); + $name = array_shift($aliases); + } + + $this->setAliases($aliases); + } + + if (null !== $name) { + $this->setName($name); + } + + if ('' === $this->description) { + $this->setDescription(static::getDefaultDescription() ?? ''); + } + + $this->configure(); + } + + /** + * Ignores validation errors. + * + * This is mainly useful for the help command. + */ + public function ignoreValidationErrors(): void + { + $this->ignoreValidationErrors = true; + } + + public function setApplication(?Application $application): void + { + $this->application = $application; + if ($application) { + $this->setHelperSet($application->getHelperSet()); + } else { + $this->helperSet = null; + } + + $this->fullDefinition = null; + } + + public function setHelperSet(HelperSet $helperSet): void + { + $this->helperSet = $helperSet; + } + + /** + * Gets the helper set. + */ + public function getHelperSet(): ?HelperSet + { + return $this->helperSet; + } + + /** + * Gets the application instance for this command. + */ + public function getApplication(): ?Application + { + return $this->application; + } + + /** + * Checks whether the command is enabled or not in the current environment. + * + * Override this to check for x or y and return false if the command cannot + * run properly under the current conditions. + */ + public function isEnabled(): bool + { + return true; + } + + /** + * Configures the current command. + * + * @return void + */ + protected function configure() + { + } + + /** + * Executes the current command. + * + * This method is not abstract because you can use this class + * as a concrete class. In this case, instead of defining the + * execute() method, you set the code to execute by passing + * a Closure to the setCode() method. + * + * @return int 0 if everything went fine, or an exit code + * + * @throws LogicException When this abstract method is not implemented + * + * @see setCode() + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + throw new LogicException('You must override the execute() method in the concrete command class.'); + } + + /** + * Interacts with the user. + * + * This method is executed before the InputDefinition is validated. + * This means that this is the only place where the command can + * interactively ask for values of missing required arguments. + * + * @return void + */ + protected function interact(InputInterface $input, OutputInterface $output) + { + } + + /** + * Initializes the command after the input has been bound and before the input + * is validated. + * + * This is mainly useful when a lot of commands extends one main command + * where some things need to be initialized based on the input arguments and options. + * + * @see InputInterface::bind() + * @see InputInterface::validate() + * + * @return void + */ + protected function initialize(InputInterface $input, OutputInterface $output) + { + } + + /** + * Runs the command. + * + * The code to execute is either defined directly with the + * setCode() method or by overriding the execute() method + * in a sub-class. + * + * @return int The command exit code + * + * @throws ExceptionInterface When input binding fails. Bypass this by calling {@link ignoreValidationErrors()}. + * + * @see setCode() + * @see execute() + */ + public function run(InputInterface $input, OutputInterface $output): int + { + // add the application arguments and options + $this->mergeApplicationDefinition(); + + // bind the input against the command specific arguments/options + try { + $input->bind($this->getDefinition()); + } catch (ExceptionInterface $e) { + if (!$this->ignoreValidationErrors) { + throw $e; + } + } + + $this->initialize($input, $output); + + if (null !== $this->processTitle) { + if (\function_exists('cli_set_process_title')) { + if (!@cli_set_process_title($this->processTitle)) { + if ('Darwin' === \PHP_OS) { + $output->writeln('Running "cli_set_process_title" as an unprivileged user is not supported on MacOS.', OutputInterface::VERBOSITY_VERY_VERBOSE); + } else { + cli_set_process_title($this->processTitle); + } + } + } elseif (\function_exists('setproctitle')) { + setproctitle($this->processTitle); + } elseif (OutputInterface::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) { + $output->writeln('Install the proctitle PECL to be able to change the process title.'); + } + } + + if ($input->isInteractive()) { + $this->interact($input, $output); + } + + // The command name argument is often omitted when a command is executed directly with its run() method. + // It would fail the validation if we didn't make sure the command argument is present, + // since it's required by the application. + if ($input->hasArgument('command') && null === $input->getArgument('command')) { + $input->setArgument('command', $this->getName()); + } + + $input->validate(); + + if ($this->code) { + $statusCode = ($this->code)($input, $output); + } else { + $statusCode = $this->execute($input, $output); + + if (!\is_int($statusCode)) { + throw new \TypeError(sprintf('Return value of "%s::execute()" must be of the type int, "%s" returned.', static::class, get_debug_type($statusCode))); + } + } + + return is_numeric($statusCode) ? (int) $statusCode : 0; + } + + /** + * Adds suggestions to $suggestions for the current completion input (e.g. option or argument). + */ + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + $definition = $this->getDefinition(); + if (CompletionInput::TYPE_OPTION_VALUE === $input->getCompletionType() && $definition->hasOption($input->getCompletionName())) { + $definition->getOption($input->getCompletionName())->complete($input, $suggestions); + } elseif (CompletionInput::TYPE_ARGUMENT_VALUE === $input->getCompletionType() && $definition->hasArgument($input->getCompletionName())) { + $definition->getArgument($input->getCompletionName())->complete($input, $suggestions); + } + } + + /** + * Sets the code to execute when running this command. + * + * If this method is used, it overrides the code defined + * in the execute() method. + * + * @param callable $code A callable(InputInterface $input, OutputInterface $output) + * + * @return $this + * + * @throws InvalidArgumentException + * + * @see execute() + */ + public function setCode(callable $code): static + { + if ($code instanceof \Closure) { + $r = new \ReflectionFunction($code); + if (null === $r->getClosureThis()) { + set_error_handler(static function () {}); + try { + if ($c = \Closure::bind($code, $this)) { + $code = $c; + } + } finally { + restore_error_handler(); + } + } + } else { + $code = $code(...); + } + + $this->code = $code; + + return $this; + } + + /** + * Merges the application definition with the command definition. + * + * This method is not part of public API and should not be used directly. + * + * @param bool $mergeArgs Whether to merge or not the Application definition arguments to Command definition arguments + * + * @internal + */ + public function mergeApplicationDefinition(bool $mergeArgs = true): void + { + if (null === $this->application) { + return; + } + + $this->fullDefinition = new InputDefinition(); + $this->fullDefinition->setOptions($this->definition->getOptions()); + $this->fullDefinition->addOptions($this->application->getDefinition()->getOptions()); + + if ($mergeArgs) { + $this->fullDefinition->setArguments($this->application->getDefinition()->getArguments()); + $this->fullDefinition->addArguments($this->definition->getArguments()); + } else { + $this->fullDefinition->setArguments($this->definition->getArguments()); + } + } + + /** + * Sets an array of argument and option instances. + * + * @return $this + */ + public function setDefinition(array|InputDefinition $definition): static + { + if ($definition instanceof InputDefinition) { + $this->definition = $definition; + } else { + $this->definition->setDefinition($definition); + } + + $this->fullDefinition = null; + + return $this; + } + + /** + * Gets the InputDefinition attached to this Command. + */ + public function getDefinition(): InputDefinition + { + return $this->fullDefinition ?? $this->getNativeDefinition(); + } + + /** + * Gets the InputDefinition to be used to create representations of this Command. + * + * Can be overridden to provide the original command representation when it would otherwise + * be changed by merging with the application InputDefinition. + * + * This method is not part of public API and should not be used directly. + */ + public function getNativeDefinition(): InputDefinition + { + return $this->definition ?? throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', static::class)); + } + + /** + * Adds an argument. + * + * @param $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL + * @param $default The default value (for InputArgument::OPTIONAL mode only) + * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion + * + * @return $this + * + * @throws InvalidArgumentException When argument mode is not valid + */ + public function addArgument(string $name, ?int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static + { + $this->definition->addArgument(new InputArgument($name, $mode, $description, $default, $suggestedValues)); + $this->fullDefinition?->addArgument(new InputArgument($name, $mode, $description, $default, $suggestedValues)); + + return $this; + } + + /** + * Adds an option. + * + * @param $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts + * @param $mode The option mode: One of the InputOption::VALUE_* constants + * @param $default The default value (must be null for InputOption::VALUE_NONE) + * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion + * + * @return $this + * + * @throws InvalidArgumentException If option mode is invalid or incompatible + */ + public function addOption(string $name, string|array|null $shortcut = null, ?int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static + { + $this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default, $suggestedValues)); + $this->fullDefinition?->addOption(new InputOption($name, $shortcut, $mode, $description, $default, $suggestedValues)); + + return $this; + } + + /** + * Sets the name of the command. + * + * This method can set both the namespace and the name if + * you separate them by a colon (:) + * + * $command->setName('foo:bar'); + * + * @return $this + * + * @throws InvalidArgumentException When the name is invalid + */ + public function setName(string $name): static + { + $this->validateName($name); + + $this->name = $name; + + return $this; + } + + /** + * Sets the process title of the command. + * + * This feature should be used only when creating a long process command, + * like a daemon. + * + * @return $this + */ + public function setProcessTitle(string $title): static + { + $this->processTitle = $title; + + return $this; + } + + /** + * Returns the command name. + */ + public function getName(): ?string + { + return $this->name; + } + + /** + * @param bool $hidden Whether or not the command should be hidden from the list of commands + * + * @return $this + */ + public function setHidden(bool $hidden = true): static + { + $this->hidden = $hidden; + + return $this; + } + + /** + * @return bool whether the command should be publicly shown or not + */ + public function isHidden(): bool + { + return $this->hidden; + } + + /** + * Sets the description for the command. + * + * @return $this + */ + public function setDescription(string $description): static + { + $this->description = $description; + + return $this; + } + + /** + * Returns the description for the command. + */ + public function getDescription(): string + { + return $this->description; + } + + /** + * Sets the help for the command. + * + * @return $this + */ + public function setHelp(string $help): static + { + $this->help = $help; + + return $this; + } + + /** + * Returns the help for the command. + */ + public function getHelp(): string + { + return $this->help; + } + + /** + * Returns the processed help for the command replacing the %command.name% and + * %command.full_name% patterns with the real values dynamically. + */ + public function getProcessedHelp(): string + { + $name = $this->name; + $isSingleCommand = $this->application?->isSingleCommand(); + + $placeholders = [ + '%command.name%', + '%command.full_name%', + ]; + $replacements = [ + $name, + $isSingleCommand ? $_SERVER['PHP_SELF'] : $_SERVER['PHP_SELF'].' '.$name, + ]; + + return str_replace($placeholders, $replacements, $this->getHelp() ?: $this->getDescription()); + } + + /** + * Sets the aliases for the command. + * + * @param string[] $aliases An array of aliases for the command + * + * @return $this + * + * @throws InvalidArgumentException When an alias is invalid + */ + public function setAliases(iterable $aliases): static + { + $list = []; + + foreach ($aliases as $alias) { + $this->validateName($alias); + $list[] = $alias; + } + + $this->aliases = \is_array($aliases) ? $aliases : $list; + + return $this; + } + + /** + * Returns the aliases for the command. + */ + public function getAliases(): array + { + return $this->aliases; + } + + /** + * Returns the synopsis for the command. + * + * @param bool $short Whether to show the short version of the synopsis (with options folded) or not + */ + public function getSynopsis(bool $short = false): string + { + $key = $short ? 'short' : 'long'; + + if (!isset($this->synopsis[$key])) { + $this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short))); + } + + return $this->synopsis[$key]; + } + + /** + * Add a command usage example, it'll be prefixed with the command name. + * + * @return $this + */ + public function addUsage(string $usage): static + { + if (!str_starts_with($usage, $this->name)) { + $usage = sprintf('%s %s', $this->name, $usage); + } + + $this->usages[] = $usage; + + return $this; + } + + /** + * Returns alternative usages of the command. + */ + public function getUsages(): array + { + return $this->usages; + } + + /** + * Gets a helper instance by name. + * + * @throws LogicException if no HelperSet is defined + * @throws InvalidArgumentException if the helper is not defined + */ + public function getHelper(string $name): HelperInterface + { + if (null === $this->helperSet) { + throw new LogicException(sprintf('Cannot retrieve helper "%s" because there is no HelperSet defined. Did you forget to add your command to the application or to set the application on the command using the setApplication() method? You can also set the HelperSet directly using the setHelperSet() method.', $name)); + } + + return $this->helperSet->get($name); + } + + /** + * Validates a command name. + * + * It must be non-empty and parts can optionally be separated by ":". + * + * @throws InvalidArgumentException When the name is invalid + */ + private function validateName(string $name): void + { + if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) { + throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name)); + } + } +} diff --git a/vendor/symfony/console/Command/CompleteCommand.php b/vendor/symfony/console/Command/CompleteCommand.php new file mode 100644 index 00000000..38aa737f --- /dev/null +++ b/vendor/symfony/console/Command/CompleteCommand.php @@ -0,0 +1,212 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Completion\Output\BashCompletionOutput; +use Symfony\Component\Console\Completion\Output\CompletionOutputInterface; +use Symfony\Component\Console\Completion\Output\FishCompletionOutput; +use Symfony\Component\Console\Completion\Output\ZshCompletionOutput; +use Symfony\Component\Console\Exception\CommandNotFoundException; +use Symfony\Component\Console\Exception\ExceptionInterface; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Responsible for providing the values to the shell completion. + * + * @author Wouter de Jong + */ +#[AsCommand(name: '|_complete', description: 'Internal command to provide shell completion suggestions')] +final class CompleteCommand extends Command +{ + public const COMPLETION_API_VERSION = '1'; + + private array $completionOutputs; + private bool $isDebug = false; + + /** + * @param array> $completionOutputs A list of additional completion outputs, with shell name as key and FQCN as value + */ + public function __construct(array $completionOutputs = []) + { + // must be set before the parent constructor, as the property value is used in configure() + $this->completionOutputs = $completionOutputs + [ + 'bash' => BashCompletionOutput::class, + 'fish' => FishCompletionOutput::class, + 'zsh' => ZshCompletionOutput::class, + ]; + + parent::__construct(); + } + + protected function configure(): void + { + $this + ->addOption('shell', 's', InputOption::VALUE_REQUIRED, 'The shell type ("'.implode('", "', array_keys($this->completionOutputs)).'")') + ->addOption('input', 'i', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'An array of input tokens (e.g. COMP_WORDS or argv)') + ->addOption('current', 'c', InputOption::VALUE_REQUIRED, 'The index of the "input" array that the cursor is in (e.g. COMP_CWORD)') + ->addOption('api-version', 'a', InputOption::VALUE_REQUIRED, 'The API version of the completion script') + ->addOption('symfony', 'S', InputOption::VALUE_REQUIRED, 'deprecated') + ; + } + + protected function initialize(InputInterface $input, OutputInterface $output): void + { + $this->isDebug = filter_var(getenv('SYMFONY_COMPLETION_DEBUG'), \FILTER_VALIDATE_BOOL); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + try { + // "symfony" must be kept for compat with the shell scripts generated by Symfony Console 5.4 - 6.1 + $version = $input->getOption('symfony') ? '1' : $input->getOption('api-version'); + if ($version && version_compare($version, self::COMPLETION_API_VERSION, '<')) { + $message = sprintf('Completion script version is not supported ("%s" given, ">=%s" required).', $version, self::COMPLETION_API_VERSION); + $this->log($message); + + $output->writeln($message.' Install the Symfony completion script again by using the "completion" command.'); + + return 126; + } + + $shell = $input->getOption('shell'); + if (!$shell) { + throw new \RuntimeException('The "--shell" option must be set.'); + } + + if (!$completionOutput = $this->completionOutputs[$shell] ?? false) { + throw new \RuntimeException(sprintf('Shell completion is not supported for your shell: "%s" (supported: "%s").', $shell, implode('", "', array_keys($this->completionOutputs)))); + } + + $completionInput = $this->createCompletionInput($input); + $suggestions = new CompletionSuggestions(); + + $this->log([ + '', + ''.date('Y-m-d H:i:s').'', + 'Input: ("|" indicates the cursor position)', + ' '.(string) $completionInput, + 'Command:', + ' '.(string) implode(' ', $_SERVER['argv']), + 'Messages:', + ]); + + $command = $this->findCommand($completionInput, $output); + if (null === $command) { + $this->log(' No command found, completing using the Application class.'); + + $this->getApplication()->complete($completionInput, $suggestions); + } elseif ( + $completionInput->mustSuggestArgumentValuesFor('command') + && $command->getName() !== $completionInput->getCompletionValue() + && !\in_array($completionInput->getCompletionValue(), $command->getAliases(), true) + ) { + $this->log(' No command found, completing using the Application class.'); + + // expand shortcut names ("cache:cl") into their full name ("cache:clear") + $suggestions->suggestValues(array_filter(array_merge([$command->getName()], $command->getAliases()))); + } else { + $command->mergeApplicationDefinition(); + $completionInput->bind($command->getDefinition()); + + if (CompletionInput::TYPE_OPTION_NAME === $completionInput->getCompletionType()) { + $this->log(' Completing option names for the '.($command instanceof LazyCommand ? $command->getCommand() : $command)::class.' command.'); + + $suggestions->suggestOptions($command->getDefinition()->getOptions()); + } else { + $this->log([ + ' Completing using the '.($command instanceof LazyCommand ? $command->getCommand() : $command)::class.' class.', + ' Completing '.$completionInput->getCompletionType().' for '.$completionInput->getCompletionName().'', + ]); + if (null !== $compval = $completionInput->getCompletionValue()) { + $this->log(' Current value: '.$compval.''); + } + + $command->complete($completionInput, $suggestions); + } + } + + /** @var CompletionOutputInterface $completionOutput */ + $completionOutput = new $completionOutput(); + + $this->log('Suggestions:'); + if ($options = $suggestions->getOptionSuggestions()) { + $this->log(' --'.implode(' --', array_map(fn ($o) => $o->getName(), $options))); + } elseif ($values = $suggestions->getValueSuggestions()) { + $this->log(' '.implode(' ', $values)); + } else { + $this->log(' No suggestions were provided'); + } + + $completionOutput->write($suggestions, $output); + } catch (\Throwable $e) { + $this->log([ + 'Error!', + (string) $e, + ]); + + if ($output->isDebug()) { + throw $e; + } + + return 2; + } + + return 0; + } + + private function createCompletionInput(InputInterface $input): CompletionInput + { + $currentIndex = $input->getOption('current'); + if (!$currentIndex || !ctype_digit($currentIndex)) { + throw new \RuntimeException('The "--current" option must be set and it must be an integer.'); + } + + $completionInput = CompletionInput::fromTokens($input->getOption('input'), (int) $currentIndex); + + try { + $completionInput->bind($this->getApplication()->getDefinition()); + } catch (ExceptionInterface) { + } + + return $completionInput; + } + + private function findCommand(CompletionInput $completionInput, OutputInterface $output): ?Command + { + try { + $inputName = $completionInput->getFirstArgument(); + if (null === $inputName) { + return null; + } + + return $this->getApplication()->find($inputName); + } catch (CommandNotFoundException) { + } + + return null; + } + + private function log($messages): void + { + if (!$this->isDebug) { + return; + } + + $commandName = basename($_SERVER['argv'][0]); + file_put_contents(sys_get_temp_dir().'/sf_'.$commandName.'.log', implode(\PHP_EOL, (array) $messages).\PHP_EOL, \FILE_APPEND); + } +} diff --git a/vendor/symfony/console/Command/DumpCompletionCommand.php b/vendor/symfony/console/Command/DumpCompletionCommand.php new file mode 100644 index 00000000..be6f5459 --- /dev/null +++ b/vendor/symfony/console/Command/DumpCompletionCommand.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Process\Process; + +/** + * Dumps the completion script for the current shell. + * + * @author Wouter de Jong + */ +#[AsCommand(name: 'completion', description: 'Dump the shell completion script')] +final class DumpCompletionCommand extends Command +{ + private array $supportedShells; + + protected function configure(): void + { + $fullCommand = $_SERVER['PHP_SELF']; + $commandName = basename($fullCommand); + $fullCommand = @realpath($fullCommand) ?: $fullCommand; + + $shell = $this->guessShell(); + [$rcFile, $completionFile] = match ($shell) { + 'fish' => ['~/.config/fish/config.fish', "/etc/fish/completions/$commandName.fish"], + 'zsh' => ['~/.zshrc', '$fpath[1]/_'.$commandName], + default => ['~/.bashrc', "/etc/bash_completion.d/$commandName"], + }; + + $supportedShells = implode(', ', $this->getSupportedShells()); + + $this + ->setHelp(<<%command.name% command dumps the shell completion script required +to use shell autocompletion (currently, {$supportedShells} completion are supported). + +Static installation +------------------- + +Dump the script to a global completion file and restart your shell: + + %command.full_name% {$shell} | sudo tee {$completionFile} + +Or dump the script to a local file and source it: + + %command.full_name% {$shell} > completion.sh + + # source the file whenever you use the project + source completion.sh + + # or add this line at the end of your "{$rcFile}" file: + source /path/to/completion.sh + +Dynamic installation +-------------------- + +Add this to the end of your shell configuration file (e.g. "{$rcFile}"): + + eval "$({$fullCommand} completion {$shell})" +EOH + ) + ->addArgument('shell', InputArgument::OPTIONAL, 'The shell type (e.g. "bash"), the value of the "$SHELL" env var will be used if this is not given', null, $this->getSupportedShells(...)) + ->addOption('debug', null, InputOption::VALUE_NONE, 'Tail the completion debug log') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $commandName = basename($_SERVER['argv'][0]); + + if ($input->getOption('debug')) { + $this->tailDebugLog($commandName, $output); + + return 0; + } + + $shell = $input->getArgument('shell') ?? self::guessShell(); + $completionFile = __DIR__.'/../Resources/completion.'.$shell; + if (!file_exists($completionFile)) { + $supportedShells = $this->getSupportedShells(); + + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + if ($shell) { + $output->writeln(sprintf('Detected shell "%s", which is not supported by Symfony shell completion (supported shells: "%s").', $shell, implode('", "', $supportedShells))); + } else { + $output->writeln(sprintf('Shell not detected, Symfony shell completion only supports "%s").', implode('", "', $supportedShells))); + } + + return 2; + } + + $output->write(str_replace(['{{ COMMAND_NAME }}', '{{ VERSION }}'], [$commandName, CompleteCommand::COMPLETION_API_VERSION], file_get_contents($completionFile))); + + return 0; + } + + private static function guessShell(): string + { + return basename($_SERVER['SHELL'] ?? ''); + } + + private function tailDebugLog(string $commandName, OutputInterface $output): void + { + $debugFile = sys_get_temp_dir().'/sf_'.$commandName.'.log'; + if (!file_exists($debugFile)) { + touch($debugFile); + } + $process = new Process(['tail', '-f', $debugFile], null, null, null, 0); + $process->run(function (string $type, string $line) use ($output): void { + $output->write($line); + }); + } + + /** + * @return string[] + */ + private function getSupportedShells(): array + { + if (isset($this->supportedShells)) { + return $this->supportedShells; + } + + $shells = []; + + foreach (new \DirectoryIterator(__DIR__.'/../Resources/') as $file) { + if (str_starts_with($file->getBasename(), 'completion.') && $file->isFile()) { + $shells[] = $file->getExtension(); + } + } + sort($shells); + + return $this->supportedShells = $shells; + } +} diff --git a/vendor/symfony/console/Command/HelpCommand.php b/vendor/symfony/console/Command/HelpCommand.php new file mode 100644 index 00000000..a2a72dab --- /dev/null +++ b/vendor/symfony/console/Command/HelpCommand.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Descriptor\ApplicationDescription; +use Symfony\Component\Console\Helper\DescriptorHelper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * HelpCommand displays the help for a given command. + * + * @author Fabien Potencier + */ +class HelpCommand extends Command +{ + private Command $command; + + protected function configure(): void + { + $this->ignoreValidationErrors(); + + $this + ->setName('help') + ->setDefinition([ + new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help', fn () => array_keys((new ApplicationDescription($this->getApplication()))->getCommands())), + new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt', fn () => (new DescriptorHelper())->getFormats()), + new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'), + ]) + ->setDescription('Display help for a command') + ->setHelp(<<<'EOF' +The %command.name% command displays help for a given command: + + %command.full_name% list + +You can also output the help in other formats by using the --format option: + + %command.full_name% --format=xml list + +To display the list of available commands, please use the list command. +EOF + ) + ; + } + + public function setCommand(Command $command): void + { + $this->command = $command; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $this->command ??= $this->getApplication()->find($input->getArgument('command_name')); + + $helper = new DescriptorHelper(); + $helper->describe($output, $this->command, [ + 'format' => $input->getOption('format'), + 'raw_text' => $input->getOption('raw'), + ]); + + unset($this->command); + + return 0; + } +} diff --git a/vendor/symfony/console/Command/LazyCommand.php b/vendor/symfony/console/Command/LazyCommand.php new file mode 100644 index 00000000..d2fe8a1b --- /dev/null +++ b/vendor/symfony/console/Command/LazyCommand.php @@ -0,0 +1,202 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Completion\Suggestion; +use Symfony\Component\Console\Helper\HelperInterface; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Nicolas Grekas + */ +final class LazyCommand extends Command +{ + private \Closure|Command $command; + private ?bool $isEnabled; + + public function __construct(string $name, array $aliases, string $description, bool $isHidden, \Closure $commandFactory, ?bool $isEnabled = true) + { + $this->setName($name) + ->setAliases($aliases) + ->setHidden($isHidden) + ->setDescription($description); + + $this->command = $commandFactory; + $this->isEnabled = $isEnabled; + } + + public function ignoreValidationErrors(): void + { + $this->getCommand()->ignoreValidationErrors(); + } + + public function setApplication(?Application $application): void + { + if ($this->command instanceof parent) { + $this->command->setApplication($application); + } + + parent::setApplication($application); + } + + public function setHelperSet(HelperSet $helperSet): void + { + if ($this->command instanceof parent) { + $this->command->setHelperSet($helperSet); + } + + parent::setHelperSet($helperSet); + } + + public function isEnabled(): bool + { + return $this->isEnabled ?? $this->getCommand()->isEnabled(); + } + + public function run(InputInterface $input, OutputInterface $output): int + { + return $this->getCommand()->run($input, $output); + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + $this->getCommand()->complete($input, $suggestions); + } + + public function setCode(callable $code): static + { + $this->getCommand()->setCode($code); + + return $this; + } + + /** + * @internal + */ + public function mergeApplicationDefinition(bool $mergeArgs = true): void + { + $this->getCommand()->mergeApplicationDefinition($mergeArgs); + } + + public function setDefinition(array|InputDefinition $definition): static + { + $this->getCommand()->setDefinition($definition); + + return $this; + } + + public function getDefinition(): InputDefinition + { + return $this->getCommand()->getDefinition(); + } + + public function getNativeDefinition(): InputDefinition + { + return $this->getCommand()->getNativeDefinition(); + } + + /** + * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion + */ + public function addArgument(string $name, ?int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static + { + $this->getCommand()->addArgument($name, $mode, $description, $default, $suggestedValues); + + return $this; + } + + /** + * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion + */ + public function addOption(string $name, string|array|null $shortcut = null, ?int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static + { + $this->getCommand()->addOption($name, $shortcut, $mode, $description, $default, $suggestedValues); + + return $this; + } + + public function setProcessTitle(string $title): static + { + $this->getCommand()->setProcessTitle($title); + + return $this; + } + + public function setHelp(string $help): static + { + $this->getCommand()->setHelp($help); + + return $this; + } + + public function getHelp(): string + { + return $this->getCommand()->getHelp(); + } + + public function getProcessedHelp(): string + { + return $this->getCommand()->getProcessedHelp(); + } + + public function getSynopsis(bool $short = false): string + { + return $this->getCommand()->getSynopsis($short); + } + + public function addUsage(string $usage): static + { + $this->getCommand()->addUsage($usage); + + return $this; + } + + public function getUsages(): array + { + return $this->getCommand()->getUsages(); + } + + public function getHelper(string $name): HelperInterface + { + return $this->getCommand()->getHelper($name); + } + + public function getCommand(): parent + { + if (!$this->command instanceof \Closure) { + return $this->command; + } + + $command = $this->command = ($this->command)(); + $command->setApplication($this->getApplication()); + + if (null !== $this->getHelperSet()) { + $command->setHelperSet($this->getHelperSet()); + } + + $command->setName($this->getName()) + ->setAliases($this->getAliases()) + ->setHidden($this->isHidden()) + ->setDescription($this->getDescription()); + + // Will throw if the command is not correctly initialized. + $command->getDefinition(); + + return $command; + } +} diff --git a/vendor/symfony/console/Command/ListCommand.php b/vendor/symfony/console/Command/ListCommand.php new file mode 100644 index 00000000..61b4b1b3 --- /dev/null +++ b/vendor/symfony/console/Command/ListCommand.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Descriptor\ApplicationDescription; +use Symfony\Component\Console\Helper\DescriptorHelper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * ListCommand displays the list of all available commands for the application. + * + * @author Fabien Potencier + */ +class ListCommand extends Command +{ + protected function configure(): void + { + $this + ->setName('list') + ->setDefinition([ + new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name', null, fn () => array_keys((new ApplicationDescription($this->getApplication()))->getNamespaces())), + new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt', fn () => (new DescriptorHelper())->getFormats()), + new InputOption('short', null, InputOption::VALUE_NONE, 'To skip describing commands\' arguments'), + ]) + ->setDescription('List commands') + ->setHelp(<<<'EOF' +The %command.name% command lists all commands: + + %command.full_name% + +You can also display the commands for a specific namespace: + + %command.full_name% test + +You can also output the information in other formats by using the --format option: + + %command.full_name% --format=xml + +It's also possible to get raw list of commands (useful for embedding command runner): + + %command.full_name% --raw +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $helper = new DescriptorHelper(); + $helper->describe($output, $this->getApplication(), [ + 'format' => $input->getOption('format'), + 'raw_text' => $input->getOption('raw'), + 'namespace' => $input->getArgument('namespace'), + 'short' => $input->getOption('short'), + ]); + + return 0; + } +} diff --git a/vendor/symfony/console/Command/LockableTrait.php b/vendor/symfony/console/Command/LockableTrait.php new file mode 100644 index 00000000..cd7548f0 --- /dev/null +++ b/vendor/symfony/console/Command/LockableTrait.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Lock\LockFactory; +use Symfony\Component\Lock\LockInterface; +use Symfony\Component\Lock\Store\FlockStore; +use Symfony\Component\Lock\Store\SemaphoreStore; + +/** + * Basic lock feature for commands. + * + * @author Geoffrey Brier + */ +trait LockableTrait +{ + private ?LockInterface $lock = null; + + /** + * Locks a command. + */ + private function lock(?string $name = null, bool $blocking = false): bool + { + if (!class_exists(SemaphoreStore::class)) { + throw new LogicException('To enable the locking feature you must install the symfony/lock component. Try running "composer require symfony/lock".'); + } + + if (null !== $this->lock) { + throw new LogicException('A lock is already in place.'); + } + + if (SemaphoreStore::isSupported()) { + $store = new SemaphoreStore(); + } else { + $store = new FlockStore(); + } + + $this->lock = (new LockFactory($store))->createLock($name ?: $this->getName()); + if (!$this->lock->acquire($blocking)) { + $this->lock = null; + + return false; + } + + return true; + } + + /** + * Releases the command lock if there is one. + */ + private function release(): void + { + if ($this->lock) { + $this->lock->release(); + $this->lock = null; + } + } +} diff --git a/vendor/symfony/console/Command/SignalableCommandInterface.php b/vendor/symfony/console/Command/SignalableCommandInterface.php new file mode 100644 index 00000000..40b301d1 --- /dev/null +++ b/vendor/symfony/console/Command/SignalableCommandInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +/** + * Interface for command reacting to signal. + * + * @author Grégoire Pineau + */ +interface SignalableCommandInterface +{ + /** + * Returns the list of signals to subscribe. + */ + public function getSubscribedSignals(): array; + + /** + * The method will be called when the application is signaled. + * + * @return int|false The exit code to return or false to continue the normal execution + */ + public function handleSignal(int $signal, int|false $previousExitCode = 0): int|false; +} diff --git a/vendor/symfony/console/Command/TraceableCommand.php b/vendor/symfony/console/Command/TraceableCommand.php new file mode 100644 index 00000000..9ffb68da --- /dev/null +++ b/vendor/symfony/console/Command/TraceableCommand.php @@ -0,0 +1,356 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Helper\HelperInterface; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Stopwatch\Stopwatch; + +/** + * @internal + * + * @author Jules Pietri + */ +final class TraceableCommand extends Command implements SignalableCommandInterface +{ + public readonly Command $command; + public int $exitCode; + public ?int $interruptedBySignal = null; + public bool $ignoreValidation; + public bool $isInteractive = false; + public string $duration = 'n/a'; + public string $maxMemoryUsage = 'n/a'; + public InputInterface $input; + public OutputInterface $output; + /** @var array */ + public array $arguments; + /** @var array */ + public array $options; + /** @var array */ + public array $interactiveInputs = []; + public array $handledSignals = []; + + public function __construct( + Command $command, + private readonly Stopwatch $stopwatch, + ) { + if ($command instanceof LazyCommand) { + $command = $command->getCommand(); + } + + $this->command = $command; + + // prevent call to self::getDefaultDescription() + $this->setDescription($command->getDescription()); + + parent::__construct($command->getName()); + + // init below enables calling {@see parent::run()} + [$code, $processTitle, $ignoreValidationErrors] = \Closure::bind(function () { + return [$this->code, $this->processTitle, $this->ignoreValidationErrors]; + }, $command, Command::class)(); + + if (\is_callable($code)) { + $this->setCode($code); + } + + if ($processTitle) { + parent::setProcessTitle($processTitle); + } + + if ($ignoreValidationErrors) { + parent::ignoreValidationErrors(); + } + + $this->ignoreValidation = $ignoreValidationErrors; + } + + public function __call(string $name, array $arguments): mixed + { + return $this->command->{$name}(...$arguments); + } + + public function getSubscribedSignals(): array + { + return $this->command instanceof SignalableCommandInterface ? $this->command->getSubscribedSignals() : []; + } + + public function handleSignal(int $signal, int|false $previousExitCode = 0): int|false + { + if (!$this->command instanceof SignalableCommandInterface) { + return false; + } + + $event = $this->stopwatch->start($this->getName().'.handle_signal'); + + $exit = $this->command->handleSignal($signal, $previousExitCode); + + $event->stop(); + + if (!isset($this->handledSignals[$signal])) { + $this->handledSignals[$signal] = [ + 'handled' => 0, + 'duration' => 0, + 'memory' => 0, + ]; + } + + ++$this->handledSignals[$signal]['handled']; + $this->handledSignals[$signal]['duration'] += $event->getDuration(); + $this->handledSignals[$signal]['memory'] = max( + $this->handledSignals[$signal]['memory'], + $event->getMemory() >> 20 + ); + + return $exit; + } + + /** + * {@inheritdoc} + * + * Calling parent method is required to be used in {@see parent::run()}. + */ + public function ignoreValidationErrors(): void + { + $this->ignoreValidation = true; + $this->command->ignoreValidationErrors(); + + parent::ignoreValidationErrors(); + } + + public function setApplication(?Application $application = null): void + { + $this->command->setApplication($application); + } + + public function getApplication(): ?Application + { + return $this->command->getApplication(); + } + + public function setHelperSet(HelperSet $helperSet): void + { + $this->command->setHelperSet($helperSet); + } + + public function getHelperSet(): ?HelperSet + { + return $this->command->getHelperSet(); + } + + public function isEnabled(): bool + { + return $this->command->isEnabled(); + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + $this->command->complete($input, $suggestions); + } + + /** + * {@inheritdoc} + * + * Calling parent method is required to be used in {@see parent::run()}. + */ + public function setCode(callable $code): static + { + $this->command->setCode($code); + + return parent::setCode(function (InputInterface $input, OutputInterface $output) use ($code): int { + $event = $this->stopwatch->start($this->getName().'.code'); + + $this->exitCode = $code($input, $output); + + $event->stop(); + + return $this->exitCode; + }); + } + + /** + * @internal + */ + public function mergeApplicationDefinition(bool $mergeArgs = true): void + { + $this->command->mergeApplicationDefinition($mergeArgs); + } + + public function setDefinition(array|InputDefinition $definition): static + { + $this->command->setDefinition($definition); + + return $this; + } + + public function getDefinition(): InputDefinition + { + return $this->command->getDefinition(); + } + + public function getNativeDefinition(): InputDefinition + { + return $this->command->getNativeDefinition(); + } + + public function addArgument(string $name, ?int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static + { + $this->command->addArgument($name, $mode, $description, $default, $suggestedValues); + + return $this; + } + + public function addOption(string $name, string|array|null $shortcut = null, ?int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static + { + $this->command->addOption($name, $shortcut, $mode, $description, $default, $suggestedValues); + + return $this; + } + + /** + * {@inheritdoc} + * + * Calling parent method is required to be used in {@see parent::run()}. + */ + public function setProcessTitle(string $title): static + { + $this->command->setProcessTitle($title); + + return parent::setProcessTitle($title); + } + + public function setHelp(string $help): static + { + $this->command->setHelp($help); + + return $this; + } + + public function getHelp(): string + { + return $this->command->getHelp(); + } + + public function getProcessedHelp(): string + { + return $this->command->getProcessedHelp(); + } + + public function getSynopsis(bool $short = false): string + { + return $this->command->getSynopsis($short); + } + + public function addUsage(string $usage): static + { + $this->command->addUsage($usage); + + return $this; + } + + public function getUsages(): array + { + return $this->command->getUsages(); + } + + public function getHelper(string $name): HelperInterface + { + return $this->command->getHelper($name); + } + + public function run(InputInterface $input, OutputInterface $output): int + { + $this->input = $input; + $this->output = $output; + $this->arguments = $input->getArguments(); + $this->options = $input->getOptions(); + $event = $this->stopwatch->start($this->getName(), 'command'); + + try { + $this->exitCode = parent::run($input, $output); + } finally { + $event->stop(); + + if ($output instanceof ConsoleOutputInterface && $output->isDebug()) { + $output->getErrorOutput()->writeln((string) $event); + } + + $this->duration = $event->getDuration().' ms'; + $this->maxMemoryUsage = ($event->getMemory() >> 20).' MiB'; + + if ($this->isInteractive) { + $this->extractInteractiveInputs($input->getArguments(), $input->getOptions()); + } + } + + return $this->exitCode; + } + + protected function initialize(InputInterface $input, OutputInterface $output): void + { + $event = $this->stopwatch->start($this->getName().'.init', 'command'); + + $this->command->initialize($input, $output); + + $event->stop(); + } + + protected function interact(InputInterface $input, OutputInterface $output): void + { + if (!$this->isInteractive = Command::class !== (new \ReflectionMethod($this->command, 'interact'))->getDeclaringClass()->getName()) { + return; + } + + $event = $this->stopwatch->start($this->getName().'.interact', 'command'); + + $this->command->interact($input, $output); + + $event->stop(); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $event = $this->stopwatch->start($this->getName().'.execute', 'command'); + + $exitCode = $this->command->execute($input, $output); + + $event->stop(); + + return $exitCode; + } + + private function extractInteractiveInputs(array $arguments, array $options): void + { + foreach ($arguments as $argName => $argValue) { + if (\array_key_exists($argName, $this->arguments) && $this->arguments[$argName] === $argValue) { + continue; + } + + $this->interactiveInputs[$argName] = $argValue; + } + + foreach ($options as $optName => $optValue) { + if (\array_key_exists($optName, $this->options) && $this->options[$optName] === $optValue) { + continue; + } + + $this->interactiveInputs['--'.$optName] = $optValue; + } + } +} diff --git a/vendor/symfony/console/CommandLoader/CommandLoaderInterface.php b/vendor/symfony/console/CommandLoader/CommandLoaderInterface.php new file mode 100644 index 00000000..b6b637ce --- /dev/null +++ b/vendor/symfony/console/CommandLoader/CommandLoaderInterface.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\CommandLoader; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\CommandNotFoundException; + +/** + * @author Robin Chalas + */ +interface CommandLoaderInterface +{ + /** + * Loads a command. + * + * @throws CommandNotFoundException + */ + public function get(string $name): Command; + + /** + * Checks if a command exists. + */ + public function has(string $name): bool; + + /** + * @return string[] + */ + public function getNames(): array; +} diff --git a/vendor/symfony/console/CommandLoader/ContainerCommandLoader.php b/vendor/symfony/console/CommandLoader/ContainerCommandLoader.php new file mode 100644 index 00000000..bfa0ac46 --- /dev/null +++ b/vendor/symfony/console/CommandLoader/ContainerCommandLoader.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\CommandLoader; + +use Psr\Container\ContainerInterface; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\CommandNotFoundException; + +/** + * Loads commands from a PSR-11 container. + * + * @author Robin Chalas + */ +class ContainerCommandLoader implements CommandLoaderInterface +{ + private ContainerInterface $container; + private array $commandMap; + + /** + * @param array $commandMap An array with command names as keys and service ids as values + */ + public function __construct(ContainerInterface $container, array $commandMap) + { + $this->container = $container; + $this->commandMap = $commandMap; + } + + public function get(string $name): Command + { + if (!$this->has($name)) { + throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name)); + } + + return $this->container->get($this->commandMap[$name]); + } + + public function has(string $name): bool + { + return isset($this->commandMap[$name]) && $this->container->has($this->commandMap[$name]); + } + + public function getNames(): array + { + return array_keys($this->commandMap); + } +} diff --git a/vendor/symfony/console/CommandLoader/FactoryCommandLoader.php b/vendor/symfony/console/CommandLoader/FactoryCommandLoader.php new file mode 100644 index 00000000..9ced75ae --- /dev/null +++ b/vendor/symfony/console/CommandLoader/FactoryCommandLoader.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\CommandLoader; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\CommandNotFoundException; + +/** + * A simple command loader using factories to instantiate commands lazily. + * + * @author Maxime Steinhausser + */ +class FactoryCommandLoader implements CommandLoaderInterface +{ + private array $factories; + + /** + * @param callable[] $factories Indexed by command names + */ + public function __construct(array $factories) + { + $this->factories = $factories; + } + + public function has(string $name): bool + { + return isset($this->factories[$name]); + } + + public function get(string $name): Command + { + if (!isset($this->factories[$name])) { + throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name)); + } + + $factory = $this->factories[$name]; + + return $factory(); + } + + public function getNames(): array + { + return array_keys($this->factories); + } +} diff --git a/vendor/symfony/console/Completion/CompletionInput.php b/vendor/symfony/console/Completion/CompletionInput.php new file mode 100644 index 00000000..7ba41c08 --- /dev/null +++ b/vendor/symfony/console/Completion/CompletionInput.php @@ -0,0 +1,248 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Completion; + +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Input\ArgvInput; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; + +/** + * An input specialized for shell completion. + * + * This input allows unfinished option names or values and exposes what kind of + * completion is expected. + * + * @author Wouter de Jong + */ +final class CompletionInput extends ArgvInput +{ + public const TYPE_ARGUMENT_VALUE = 'argument_value'; + public const TYPE_OPTION_VALUE = 'option_value'; + public const TYPE_OPTION_NAME = 'option_name'; + public const TYPE_NONE = 'none'; + + private array $tokens; + private int $currentIndex; + private string $completionType; + private ?string $completionName = null; + private string $completionValue = ''; + + /** + * Converts a terminal string into tokens. + * + * This is required for shell completions without COMP_WORDS support. + */ + public static function fromString(string $inputStr, int $currentIndex): self + { + preg_match_all('/(?<=^|\s)([\'"]?)(.+?)(?tokens = $tokens; + $input->currentIndex = $currentIndex; + + return $input; + } + + public function bind(InputDefinition $definition): void + { + parent::bind($definition); + + $relevantToken = $this->getRelevantToken(); + if ('-' === $relevantToken[0]) { + // the current token is an input option: complete either option name or option value + [$optionToken, $optionValue] = explode('=', $relevantToken, 2) + ['', '']; + + $option = $this->getOptionFromToken($optionToken); + if (null === $option && !$this->isCursorFree()) { + $this->completionType = self::TYPE_OPTION_NAME; + $this->completionValue = $relevantToken; + + return; + } + + if ($option?->acceptValue()) { + $this->completionType = self::TYPE_OPTION_VALUE; + $this->completionName = $option->getName(); + $this->completionValue = $optionValue ?: (!str_starts_with($optionToken, '--') ? substr($optionToken, 2) : ''); + + return; + } + } + + $previousToken = $this->tokens[$this->currentIndex - 1]; + if ('-' === $previousToken[0] && '' !== trim($previousToken, '-')) { + // check if previous option accepted a value + $previousOption = $this->getOptionFromToken($previousToken); + if ($previousOption?->acceptValue()) { + $this->completionType = self::TYPE_OPTION_VALUE; + $this->completionName = $previousOption->getName(); + $this->completionValue = $relevantToken; + + return; + } + } + + // complete argument value + $this->completionType = self::TYPE_ARGUMENT_VALUE; + + foreach ($this->definition->getArguments() as $argumentName => $argument) { + if (!isset($this->arguments[$argumentName])) { + break; + } + + $argumentValue = $this->arguments[$argumentName]; + $this->completionName = $argumentName; + if (\is_array($argumentValue)) { + $this->completionValue = $argumentValue ? $argumentValue[array_key_last($argumentValue)] : null; + } else { + $this->completionValue = $argumentValue; + } + } + + if ($this->currentIndex >= \count($this->tokens)) { + if (!isset($this->arguments[$argumentName]) || $this->definition->getArgument($argumentName)->isArray()) { + $this->completionName = $argumentName; + $this->completionValue = ''; + } else { + // we've reached the end + $this->completionType = self::TYPE_NONE; + $this->completionName = null; + $this->completionValue = ''; + } + } + } + + /** + * Returns the type of completion required. + * + * TYPE_ARGUMENT_VALUE when completing the value of an input argument + * TYPE_OPTION_VALUE when completing the value of an input option + * TYPE_OPTION_NAME when completing the name of an input option + * TYPE_NONE when nothing should be completed + * + * TYPE_OPTION_NAME and TYPE_NONE are already implemented by the Console component. + * + * @return self::TYPE_* + */ + public function getCompletionType(): string + { + return $this->completionType; + } + + /** + * The name of the input option or argument when completing a value. + * + * @return string|null returns null when completing an option name + */ + public function getCompletionName(): ?string + { + return $this->completionName; + } + + /** + * The value already typed by the user (or empty string). + */ + public function getCompletionValue(): string + { + return $this->completionValue; + } + + public function mustSuggestOptionValuesFor(string $optionName): bool + { + return self::TYPE_OPTION_VALUE === $this->getCompletionType() && $optionName === $this->getCompletionName(); + } + + public function mustSuggestArgumentValuesFor(string $argumentName): bool + { + return self::TYPE_ARGUMENT_VALUE === $this->getCompletionType() && $argumentName === $this->getCompletionName(); + } + + protected function parseToken(string $token, bool $parseOptions): bool + { + try { + return parent::parseToken($token, $parseOptions); + } catch (RuntimeException) { + // suppress errors, completed input is almost never valid + } + + return $parseOptions; + } + + private function getOptionFromToken(string $optionToken): ?InputOption + { + $optionName = ltrim($optionToken, '-'); + if (!$optionName) { + return null; + } + + if ('-' === ($optionToken[1] ?? ' ')) { + // long option name + return $this->definition->hasOption($optionName) ? $this->definition->getOption($optionName) : null; + } + + // short option name + return $this->definition->hasShortcut($optionName[0]) ? $this->definition->getOptionForShortcut($optionName[0]) : null; + } + + /** + * The token of the cursor, or the last token if the cursor is at the end of the input. + */ + private function getRelevantToken(): string + { + return $this->tokens[$this->isCursorFree() ? $this->currentIndex - 1 : $this->currentIndex]; + } + + /** + * Whether the cursor is "free" (i.e. at the end of the input preceded by a space). + */ + private function isCursorFree(): bool + { + $nrOfTokens = \count($this->tokens); + if ($this->currentIndex > $nrOfTokens) { + throw new \LogicException('Current index is invalid, it must be the number of input tokens or one more.'); + } + + return $this->currentIndex >= $nrOfTokens; + } + + public function __toString() + { + $str = ''; + foreach ($this->tokens as $i => $token) { + $str .= $token; + + if ($this->currentIndex === $i) { + $str .= '|'; + } + + $str .= ' '; + } + + if ($this->currentIndex > $i) { + $str .= '|'; + } + + return rtrim($str); + } +} diff --git a/vendor/symfony/console/Completion/CompletionSuggestions.php b/vendor/symfony/console/Completion/CompletionSuggestions.php new file mode 100644 index 00000000..549bbafb --- /dev/null +++ b/vendor/symfony/console/Completion/CompletionSuggestions.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Completion; + +use Symfony\Component\Console\Input\InputOption; + +/** + * Stores all completion suggestions for the current input. + * + * @author Wouter de Jong + */ +final class CompletionSuggestions +{ + private array $valueSuggestions = []; + private array $optionSuggestions = []; + + /** + * Add a suggested value for an input option or argument. + * + * @return $this + */ + public function suggestValue(string|Suggestion $value): static + { + $this->valueSuggestions[] = !$value instanceof Suggestion ? new Suggestion($value) : $value; + + return $this; + } + + /** + * Add multiple suggested values at once for an input option or argument. + * + * @param list $values + * + * @return $this + */ + public function suggestValues(array $values): static + { + foreach ($values as $value) { + $this->suggestValue($value); + } + + return $this; + } + + /** + * Add a suggestion for an input option name. + * + * @return $this + */ + public function suggestOption(InputOption $option): static + { + $this->optionSuggestions[] = $option; + + return $this; + } + + /** + * Add multiple suggestions for input option names at once. + * + * @param InputOption[] $options + * + * @return $this + */ + public function suggestOptions(array $options): static + { + foreach ($options as $option) { + $this->suggestOption($option); + } + + return $this; + } + + /** + * @return InputOption[] + */ + public function getOptionSuggestions(): array + { + return $this->optionSuggestions; + } + + /** + * @return Suggestion[] + */ + public function getValueSuggestions(): array + { + return $this->valueSuggestions; + } +} diff --git a/vendor/symfony/console/Completion/Output/BashCompletionOutput.php b/vendor/symfony/console/Completion/Output/BashCompletionOutput.php new file mode 100644 index 00000000..c6f76eb8 --- /dev/null +++ b/vendor/symfony/console/Completion/Output/BashCompletionOutput.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Completion\Output; + +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Wouter de Jong + */ +class BashCompletionOutput implements CompletionOutputInterface +{ + public function write(CompletionSuggestions $suggestions, OutputInterface $output): void + { + $values = $suggestions->getValueSuggestions(); + foreach ($suggestions->getOptionSuggestions() as $option) { + $values[] = '--'.$option->getName(); + if ($option->isNegatable()) { + $values[] = '--no-'.$option->getName(); + } + } + $output->writeln(implode("\n", $values)); + } +} diff --git a/vendor/symfony/console/Completion/Output/CompletionOutputInterface.php b/vendor/symfony/console/Completion/Output/CompletionOutputInterface.php new file mode 100644 index 00000000..659e5965 --- /dev/null +++ b/vendor/symfony/console/Completion/Output/CompletionOutputInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Completion\Output; + +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Transforms the {@see CompletionSuggestions} object into output readable by the shell completion. + * + * @author Wouter de Jong + */ +interface CompletionOutputInterface +{ + public function write(CompletionSuggestions $suggestions, OutputInterface $output): void; +} diff --git a/vendor/symfony/console/Completion/Output/FishCompletionOutput.php b/vendor/symfony/console/Completion/Output/FishCompletionOutput.php new file mode 100644 index 00000000..d2c414e4 --- /dev/null +++ b/vendor/symfony/console/Completion/Output/FishCompletionOutput.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Completion\Output; + +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Guillaume Aveline + */ +class FishCompletionOutput implements CompletionOutputInterface +{ + public function write(CompletionSuggestions $suggestions, OutputInterface $output): void + { + $values = $suggestions->getValueSuggestions(); + foreach ($suggestions->getOptionSuggestions() as $option) { + $values[] = '--'.$option->getName(); + if ($option->isNegatable()) { + $values[] = '--no-'.$option->getName(); + } + } + $output->write(implode("\n", $values)); + } +} diff --git a/vendor/symfony/console/Completion/Output/ZshCompletionOutput.php b/vendor/symfony/console/Completion/Output/ZshCompletionOutput.php new file mode 100644 index 00000000..bb4ce70b --- /dev/null +++ b/vendor/symfony/console/Completion/Output/ZshCompletionOutput.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Completion\Output; + +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Jitendra A + */ +class ZshCompletionOutput implements CompletionOutputInterface +{ + public function write(CompletionSuggestions $suggestions, OutputInterface $output): void + { + $values = []; + foreach ($suggestions->getValueSuggestions() as $value) { + $values[] = $value->getValue().($value->getDescription() ? "\t".$value->getDescription() : ''); + } + foreach ($suggestions->getOptionSuggestions() as $option) { + $values[] = '--'.$option->getName().($option->getDescription() ? "\t".$option->getDescription() : ''); + if ($option->isNegatable()) { + $values[] = '--no-'.$option->getName().($option->getDescription() ? "\t".$option->getDescription() : ''); + } + } + $output->write(implode("\n", $values)."\n"); + } +} diff --git a/vendor/symfony/console/Completion/Suggestion.php b/vendor/symfony/console/Completion/Suggestion.php new file mode 100644 index 00000000..7392965a --- /dev/null +++ b/vendor/symfony/console/Completion/Suggestion.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Completion; + +/** + * Represents a single suggested value. + * + * @author Wouter de Jong + */ +class Suggestion implements \Stringable +{ + public function __construct( + private readonly string $value, + private readonly string $description = '' + ) { + } + + public function getValue(): string + { + return $this->value; + } + + public function getDescription(): string + { + return $this->description; + } + + public function __toString(): string + { + return $this->getValue(); + } +} diff --git a/vendor/symfony/console/ConsoleEvents.php b/vendor/symfony/console/ConsoleEvents.php new file mode 100644 index 00000000..6ae8f32b --- /dev/null +++ b/vendor/symfony/console/ConsoleEvents.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console; + +use Symfony\Component\Console\Event\ConsoleCommandEvent; +use Symfony\Component\Console\Event\ConsoleErrorEvent; +use Symfony\Component\Console\Event\ConsoleSignalEvent; +use Symfony\Component\Console\Event\ConsoleTerminateEvent; + +/** + * Contains all events dispatched by an Application. + * + * @author Francesco Levorato + */ +final class ConsoleEvents +{ + /** + * The COMMAND event allows you to attach listeners before any command is + * executed by the console. It also allows you to modify the command, input and output + * before they are handed to the command. + * + * @Event("Symfony\Component\Console\Event\ConsoleCommandEvent") + */ + public const COMMAND = 'console.command'; + + /** + * The SIGNAL event allows you to perform some actions + * after the command execution was interrupted. + * + * @Event("Symfony\Component\Console\Event\ConsoleSignalEvent") + */ + public const SIGNAL = 'console.signal'; + + /** + * The TERMINATE event allows you to attach listeners after a command is + * executed by the console. + * + * @Event("Symfony\Component\Console\Event\ConsoleTerminateEvent") + */ + public const TERMINATE = 'console.terminate'; + + /** + * The ERROR event occurs when an uncaught exception or error appears. + * + * This event allows you to deal with the exception/error or + * to modify the thrown exception. + * + * @Event("Symfony\Component\Console\Event\ConsoleErrorEvent") + */ + public const ERROR = 'console.error'; + + /** + * Event aliases. + * + * These aliases can be consumed by RegisterListenersPass. + */ + public const ALIASES = [ + ConsoleCommandEvent::class => self::COMMAND, + ConsoleErrorEvent::class => self::ERROR, + ConsoleSignalEvent::class => self::SIGNAL, + ConsoleTerminateEvent::class => self::TERMINATE, + ]; +} diff --git a/vendor/symfony/console/Cursor.php b/vendor/symfony/console/Cursor.php new file mode 100644 index 00000000..69fd3821 --- /dev/null +++ b/vendor/symfony/console/Cursor.php @@ -0,0 +1,204 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console; + +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Pierre du Plessis + */ +final class Cursor +{ + private OutputInterface $output; + /** @var resource */ + private $input; + + /** + * @param resource|null $input + */ + public function __construct(OutputInterface $output, $input = null) + { + $this->output = $output; + $this->input = $input ?? (\defined('STDIN') ? \STDIN : fopen('php://input', 'r+')); + } + + /** + * @return $this + */ + public function moveUp(int $lines = 1): static + { + $this->output->write(sprintf("\x1b[%dA", $lines)); + + return $this; + } + + /** + * @return $this + */ + public function moveDown(int $lines = 1): static + { + $this->output->write(sprintf("\x1b[%dB", $lines)); + + return $this; + } + + /** + * @return $this + */ + public function moveRight(int $columns = 1): static + { + $this->output->write(sprintf("\x1b[%dC", $columns)); + + return $this; + } + + /** + * @return $this + */ + public function moveLeft(int $columns = 1): static + { + $this->output->write(sprintf("\x1b[%dD", $columns)); + + return $this; + } + + /** + * @return $this + */ + public function moveToColumn(int $column): static + { + $this->output->write(sprintf("\x1b[%dG", $column)); + + return $this; + } + + /** + * @return $this + */ + public function moveToPosition(int $column, int $row): static + { + $this->output->write(sprintf("\x1b[%d;%dH", $row + 1, $column)); + + return $this; + } + + /** + * @return $this + */ + public function savePosition(): static + { + $this->output->write("\x1b7"); + + return $this; + } + + /** + * @return $this + */ + public function restorePosition(): static + { + $this->output->write("\x1b8"); + + return $this; + } + + /** + * @return $this + */ + public function hide(): static + { + $this->output->write("\x1b[?25l"); + + return $this; + } + + /** + * @return $this + */ + public function show(): static + { + $this->output->write("\x1b[?25h\x1b[?0c"); + + return $this; + } + + /** + * Clears all the output from the current line. + * + * @return $this + */ + public function clearLine(): static + { + $this->output->write("\x1b[2K"); + + return $this; + } + + /** + * Clears all the output from the current line after the current position. + */ + public function clearLineAfter(): self + { + $this->output->write("\x1b[K"); + + return $this; + } + + /** + * Clears all the output from the cursors' current position to the end of the screen. + * + * @return $this + */ + public function clearOutput(): static + { + $this->output->write("\x1b[0J"); + + return $this; + } + + /** + * Clears the entire screen. + * + * @return $this + */ + public function clearScreen(): static + { + $this->output->write("\x1b[2J"); + + return $this; + } + + /** + * Returns the current cursor position as x,y coordinates. + */ + public function getCurrentPosition(): array + { + static $isTtySupported; + + if (!$isTtySupported ??= '/' === \DIRECTORY_SEPARATOR && stream_isatty(\STDOUT)) { + return [1, 1]; + } + + $sttyMode = shell_exec('stty -g'); + shell_exec('stty -icanon -echo'); + + @fwrite($this->input, "\033[6n"); + + $code = trim(fread($this->input, 1024)); + + shell_exec(sprintf('stty %s', $sttyMode)); + + sscanf($code, "\033[%d;%dR", $row, $col); + + return [$col, $row]; + } +} diff --git a/vendor/symfony/console/DataCollector/CommandDataCollector.php b/vendor/symfony/console/DataCollector/CommandDataCollector.php new file mode 100644 index 00000000..45138c7d --- /dev/null +++ b/vendor/symfony/console/DataCollector/CommandDataCollector.php @@ -0,0 +1,234 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\DataCollector; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Debug\CliRequest; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\SignalRegistry\SignalMap; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DataCollector; +use Symfony\Component\VarDumper\Cloner\Data; + +/** + * @internal + * + * @author Jules Pietri + */ +final class CommandDataCollector extends DataCollector +{ + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void + { + if (!$request instanceof CliRequest) { + return; + } + + $command = $request->command; + $application = $command->getApplication(); + + $this->data = [ + 'command' => $this->cloneVar($command->command), + 'exit_code' => $command->exitCode, + 'interrupted_by_signal' => $command->interruptedBySignal, + 'duration' => $command->duration, + 'max_memory_usage' => $command->maxMemoryUsage, + 'verbosity_level' => match ($command->output->getVerbosity()) { + OutputInterface::VERBOSITY_QUIET => 'quiet', + OutputInterface::VERBOSITY_NORMAL => 'normal', + OutputInterface::VERBOSITY_VERBOSE => 'verbose', + OutputInterface::VERBOSITY_VERY_VERBOSE => 'very verbose', + OutputInterface::VERBOSITY_DEBUG => 'debug', + }, + 'interactive' => $command->isInteractive, + 'validate_input' => !$command->ignoreValidation, + 'enabled' => $command->isEnabled(), + 'visible' => !$command->isHidden(), + 'input' => $this->cloneVar($command->input), + 'output' => $this->cloneVar($command->output), + 'interactive_inputs' => array_map($this->cloneVar(...), $command->interactiveInputs), + 'signalable' => $command->getSubscribedSignals(), + 'handled_signals' => $command->handledSignals, + 'helper_set' => array_map($this->cloneVar(...), iterator_to_array($command->getHelperSet())), + ]; + + $baseDefinition = $application->getDefinition(); + + foreach ($command->arguments as $argName => $argValue) { + if ($baseDefinition->hasArgument($argName)) { + $this->data['application_inputs'][$argName] = $this->cloneVar($argValue); + } else { + $this->data['arguments'][$argName] = $this->cloneVar($argValue); + } + } + + foreach ($command->options as $optName => $optValue) { + if ($baseDefinition->hasOption($optName)) { + $this->data['application_inputs']['--'.$optName] = $this->cloneVar($optValue); + } else { + $this->data['options'][$optName] = $this->cloneVar($optValue); + } + } + } + + public function getName(): string + { + return 'command'; + } + + /** + * @return array{ + * class?: class-string, + * executor?: string, + * file: string, + * line: int, + * } + */ + public function getCommand(): array + { + $class = $this->data['command']->getType(); + $r = new \ReflectionMethod($class, 'execute'); + + if (Command::class !== $r->getDeclaringClass()) { + return [ + 'executor' => $class.'::'.$r->name, + 'file' => $r->getFileName(), + 'line' => $r->getStartLine(), + ]; + } + + $r = new \ReflectionClass($class); + + return [ + 'class' => $class, + 'file' => $r->getFileName(), + 'line' => $r->getStartLine(), + ]; + } + + public function getInterruptedBySignal(): ?string + { + if (isset($this->data['interrupted_by_signal'])) { + return sprintf('%s (%d)', SignalMap::getSignalName($this->data['interrupted_by_signal']), $this->data['interrupted_by_signal']); + } + + return null; + } + + public function getDuration(): string + { + return $this->data['duration']; + } + + public function getMaxMemoryUsage(): string + { + return $this->data['max_memory_usage']; + } + + public function getVerbosityLevel(): string + { + return $this->data['verbosity_level']; + } + + public function getInteractive(): bool + { + return $this->data['interactive']; + } + + public function getValidateInput(): bool + { + return $this->data['validate_input']; + } + + public function getEnabled(): bool + { + return $this->data['enabled']; + } + + public function getVisible(): bool + { + return $this->data['visible']; + } + + public function getInput(): Data + { + return $this->data['input']; + } + + public function getOutput(): Data + { + return $this->data['output']; + } + + /** + * @return Data[] + */ + public function getArguments(): array + { + return $this->data['arguments'] ?? []; + } + + /** + * @return Data[] + */ + public function getOptions(): array + { + return $this->data['options'] ?? []; + } + + /** + * @return Data[] + */ + public function getApplicationInputs(): array + { + return $this->data['application_inputs'] ?? []; + } + + /** + * @return Data[] + */ + public function getInteractiveInputs(): array + { + return $this->data['interactive_inputs'] ?? []; + } + + public function getSignalable(): array + { + return array_map( + static fn (int $signal): string => sprintf('%s (%d)', SignalMap::getSignalName($signal), $signal), + $this->data['signalable'] + ); + } + + public function getHandledSignals(): array + { + $keys = array_map( + static fn (int $signal): string => sprintf('%s (%d)', SignalMap::getSignalName($signal), $signal), + array_keys($this->data['handled_signals']) + ); + + return array_combine($keys, array_values($this->data['handled_signals'])); + } + + /** + * @return Data[] + */ + public function getHelperSet(): array + { + return $this->data['helper_set'] ?? []; + } + + public function reset(): void + { + $this->data = []; + } +} diff --git a/vendor/symfony/console/Debug/CliRequest.php b/vendor/symfony/console/Debug/CliRequest.php new file mode 100644 index 00000000..b023db07 --- /dev/null +++ b/vendor/symfony/console/Debug/CliRequest.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Debug; + +use Symfony\Component\Console\Command\TraceableCommand; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * @internal + */ +final class CliRequest extends Request +{ + public function __construct( + public readonly TraceableCommand $command, + ) { + parent::__construct( + attributes: ['_controller' => \get_class($command->command), '_virtual_type' => 'command'], + server: $_SERVER, + ); + } + + // Methods below allow to populate a profile, thus enable search and filtering + public function getUri(): string + { + if ($this->server->has('SYMFONY_CLI_BINARY_NAME')) { + $binary = $this->server->get('SYMFONY_CLI_BINARY_NAME').' console'; + } else { + $binary = $this->server->get('argv')[0]; + } + + return $binary.' '.$this->command->input; + } + + public function getMethod(): string + { + return $this->command->isInteractive ? 'INTERACTIVE' : 'BATCH'; + } + + public function getResponse(): Response + { + return new class($this->command->exitCode) extends Response { + public function __construct(private readonly int $exitCode) + { + parent::__construct(); + } + + public function getStatusCode(): int + { + return $this->exitCode; + } + }; + } + + public function getClientIp(): string + { + $application = $this->command->getApplication(); + + return $application->getName().' '.$application->getVersion(); + } +} diff --git a/vendor/symfony/console/DependencyInjection/AddConsoleCommandPass.php b/vendor/symfony/console/DependencyInjection/AddConsoleCommandPass.php new file mode 100644 index 00000000..f712c614 --- /dev/null +++ b/vendor/symfony/console/DependencyInjection/AddConsoleCommandPass.php @@ -0,0 +1,131 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\DependencyInjection; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Command\LazyCommand; +use Symfony\Component\Console\CommandLoader\ContainerCommandLoader; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\TypedReference; + +/** + * Registers console commands. + * + * @author Grégoire Pineau + */ +class AddConsoleCommandPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + $commandServices = $container->findTaggedServiceIds('console.command', true); + $lazyCommandMap = []; + $lazyCommandRefs = []; + $serviceIds = []; + + foreach ($commandServices as $id => $tags) { + $definition = $container->getDefinition($id); + $definition->addTag('container.no_preload'); + $class = $container->getParameterBag()->resolveValue($definition->getClass()); + + if (isset($tags[0]['command'])) { + $aliases = $tags[0]['command']; + } else { + if (!$r = $container->getReflectionClass($class)) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + if (!$r->isSubclassOf(Command::class)) { + throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, 'console.command', Command::class)); + } + $aliases = str_replace('%', '%%', $class::getDefaultName() ?? ''); + } + + $aliases = explode('|', $aliases ?? ''); + $commandName = array_shift($aliases); + + if ($isHidden = '' === $commandName) { + $commandName = array_shift($aliases); + } + + if (null === $commandName) { + if (!$definition->isPublic() || $definition->isPrivate() || $definition->hasTag('container.private')) { + $commandId = 'console.command.public_alias.'.$id; + $container->setAlias($commandId, $id)->setPublic(true); + $id = $commandId; + } + $serviceIds[] = $id; + + continue; + } + + $description = $tags[0]['description'] ?? null; + + unset($tags[0]); + $lazyCommandMap[$commandName] = $id; + $lazyCommandRefs[$id] = new TypedReference($id, $class); + + foreach ($aliases as $alias) { + $lazyCommandMap[$alias] = $id; + } + + foreach ($tags as $tag) { + if (isset($tag['command'])) { + $aliases[] = $tag['command']; + $lazyCommandMap[$tag['command']] = $id; + } + + $description ??= $tag['description'] ?? null; + } + + $definition->addMethodCall('setName', [$commandName]); + + if ($aliases) { + $definition->addMethodCall('setAliases', [$aliases]); + } + + if ($isHidden) { + $definition->addMethodCall('setHidden', [true]); + } + + if (!$description) { + if (!$r = $container->getReflectionClass($class)) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + if (!$r->isSubclassOf(Command::class)) { + throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, 'console.command', Command::class)); + } + $description = str_replace('%', '%%', $class::getDefaultDescription() ?? ''); + } + + if ($description) { + $definition->addMethodCall('setDescription', [$description]); + + $container->register('.'.$id.'.lazy', LazyCommand::class) + ->setArguments([$commandName, $aliases, $description, $isHidden, new ServiceClosureArgument($lazyCommandRefs[$id])]); + + $lazyCommandRefs[$id] = new Reference('.'.$id.'.lazy'); + } + } + + $container + ->register('console.command_loader', ContainerCommandLoader::class) + ->setPublic(true) + ->addTag('container.no_preload') + ->setArguments([ServiceLocatorTagPass::register($container, $lazyCommandRefs), $lazyCommandMap]); + + $container->setParameter('console.command.ids', $serviceIds); + } +} diff --git a/vendor/symfony/console/Descriptor/ApplicationDescription.php b/vendor/symfony/console/Descriptor/ApplicationDescription.php new file mode 100644 index 00000000..ef9e8a63 --- /dev/null +++ b/vendor/symfony/console/Descriptor/ApplicationDescription.php @@ -0,0 +1,139 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\CommandNotFoundException; + +/** + * @author Jean-François Simon + * + * @internal + */ +class ApplicationDescription +{ + public const GLOBAL_NAMESPACE = '_global'; + + private Application $application; + private ?string $namespace; + private bool $showHidden; + private array $namespaces; + + /** + * @var array + */ + private array $commands; + + /** + * @var array + */ + private array $aliases = []; + + public function __construct(Application $application, ?string $namespace = null, bool $showHidden = false) + { + $this->application = $application; + $this->namespace = $namespace; + $this->showHidden = $showHidden; + } + + public function getNamespaces(): array + { + if (!isset($this->namespaces)) { + $this->inspectApplication(); + } + + return $this->namespaces; + } + + /** + * @return Command[] + */ + public function getCommands(): array + { + if (!isset($this->commands)) { + $this->inspectApplication(); + } + + return $this->commands; + } + + /** + * @throws CommandNotFoundException + */ + public function getCommand(string $name): Command + { + if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) { + throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name)); + } + + return $this->commands[$name] ?? $this->aliases[$name]; + } + + private function inspectApplication(): void + { + $this->commands = []; + $this->namespaces = []; + + $all = $this->application->all($this->namespace ? $this->application->findNamespace($this->namespace) : null); + foreach ($this->sortCommands($all) as $namespace => $commands) { + $names = []; + + /** @var Command $command */ + foreach ($commands as $name => $command) { + if (!$command->getName() || (!$this->showHidden && $command->isHidden())) { + continue; + } + + if ($command->getName() === $name) { + $this->commands[$name] = $command; + } else { + $this->aliases[$name] = $command; + } + + $names[] = $name; + } + + $this->namespaces[$namespace] = ['id' => $namespace, 'commands' => $names]; + } + } + + private function sortCommands(array $commands): array + { + $namespacedCommands = []; + $globalCommands = []; + $sortedCommands = []; + foreach ($commands as $name => $command) { + $key = $this->application->extractNamespace($name, 1); + if (\in_array($key, ['', self::GLOBAL_NAMESPACE], true)) { + $globalCommands[$name] = $command; + } else { + $namespacedCommands[$key][$name] = $command; + } + } + + if ($globalCommands) { + ksort($globalCommands); + $sortedCommands[self::GLOBAL_NAMESPACE] = $globalCommands; + } + + if ($namespacedCommands) { + ksort($namespacedCommands, \SORT_STRING); + foreach ($namespacedCommands as $key => $commandsSet) { + ksort($commandsSet); + $sortedCommands[$key] = $commandsSet; + } + } + + return $sortedCommands; + } +} diff --git a/vendor/symfony/console/Descriptor/Descriptor.php b/vendor/symfony/console/Descriptor/Descriptor.php new file mode 100644 index 00000000..7b2509c6 --- /dev/null +++ b/vendor/symfony/console/Descriptor/Descriptor.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Jean-François Simon + * + * @internal + */ +abstract class Descriptor implements DescriptorInterface +{ + protected OutputInterface $output; + + public function describe(OutputInterface $output, object $object, array $options = []): void + { + $this->output = $output; + + match (true) { + $object instanceof InputArgument => $this->describeInputArgument($object, $options), + $object instanceof InputOption => $this->describeInputOption($object, $options), + $object instanceof InputDefinition => $this->describeInputDefinition($object, $options), + $object instanceof Command => $this->describeCommand($object, $options), + $object instanceof Application => $this->describeApplication($object, $options), + default => throw new InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_debug_type($object))), + }; + } + + protected function write(string $content, bool $decorated = false): void + { + $this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW); + } + + /** + * Describes an InputArgument instance. + */ + abstract protected function describeInputArgument(InputArgument $argument, array $options = []): void; + + /** + * Describes an InputOption instance. + */ + abstract protected function describeInputOption(InputOption $option, array $options = []): void; + + /** + * Describes an InputDefinition instance. + */ + abstract protected function describeInputDefinition(InputDefinition $definition, array $options = []): void; + + /** + * Describes a Command instance. + */ + abstract protected function describeCommand(Command $command, array $options = []): void; + + /** + * Describes an Application instance. + */ + abstract protected function describeApplication(Application $application, array $options = []): void; +} diff --git a/vendor/symfony/console/Descriptor/DescriptorInterface.php b/vendor/symfony/console/Descriptor/DescriptorInterface.php new file mode 100644 index 00000000..04e5a7c8 --- /dev/null +++ b/vendor/symfony/console/Descriptor/DescriptorInterface.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Descriptor interface. + * + * @author Jean-François Simon + */ +interface DescriptorInterface +{ + public function describe(OutputInterface $output, object $object, array $options = []): void; +} diff --git a/vendor/symfony/console/Descriptor/JsonDescriptor.php b/vendor/symfony/console/Descriptor/JsonDescriptor.php new file mode 100644 index 00000000..95630370 --- /dev/null +++ b/vendor/symfony/console/Descriptor/JsonDescriptor.php @@ -0,0 +1,166 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; + +/** + * JSON descriptor. + * + * @author Jean-François Simon + * + * @internal + */ +class JsonDescriptor extends Descriptor +{ + protected function describeInputArgument(InputArgument $argument, array $options = []): void + { + $this->writeData($this->getInputArgumentData($argument), $options); + } + + protected function describeInputOption(InputOption $option, array $options = []): void + { + $this->writeData($this->getInputOptionData($option), $options); + if ($option->isNegatable()) { + $this->writeData($this->getInputOptionData($option, true), $options); + } + } + + protected function describeInputDefinition(InputDefinition $definition, array $options = []): void + { + $this->writeData($this->getInputDefinitionData($definition), $options); + } + + protected function describeCommand(Command $command, array $options = []): void + { + $this->writeData($this->getCommandData($command, $options['short'] ?? false), $options); + } + + protected function describeApplication(Application $application, array $options = []): void + { + $describedNamespace = $options['namespace'] ?? null; + $description = new ApplicationDescription($application, $describedNamespace, true); + $commands = []; + + foreach ($description->getCommands() as $command) { + $commands[] = $this->getCommandData($command, $options['short'] ?? false); + } + + $data = []; + if ('UNKNOWN' !== $application->getName()) { + $data['application']['name'] = $application->getName(); + if ('UNKNOWN' !== $application->getVersion()) { + $data['application']['version'] = $application->getVersion(); + } + } + + $data['commands'] = $commands; + + if ($describedNamespace) { + $data['namespace'] = $describedNamespace; + } else { + $data['namespaces'] = array_values($description->getNamespaces()); + } + + $this->writeData($data, $options); + } + + /** + * Writes data as json. + */ + private function writeData(array $data, array $options): void + { + $flags = $options['json_encoding'] ?? 0; + + $this->write(json_encode($data, $flags)); + } + + private function getInputArgumentData(InputArgument $argument): array + { + return [ + 'name' => $argument->getName(), + 'is_required' => $argument->isRequired(), + 'is_array' => $argument->isArray(), + 'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $argument->getDescription()), + 'default' => \INF === $argument->getDefault() ? 'INF' : $argument->getDefault(), + ]; + } + + private function getInputOptionData(InputOption $option, bool $negated = false): array + { + return $negated ? [ + 'name' => '--no-'.$option->getName(), + 'shortcut' => '', + 'accept_value' => false, + 'is_value_required' => false, + 'is_multiple' => false, + 'description' => 'Negate the "--'.$option->getName().'" option', + 'default' => false, + ] : [ + 'name' => '--'.$option->getName(), + 'shortcut' => $option->getShortcut() ? '-'.str_replace('|', '|-', $option->getShortcut()) : '', + 'accept_value' => $option->acceptValue(), + 'is_value_required' => $option->isValueRequired(), + 'is_multiple' => $option->isArray(), + 'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $option->getDescription()), + 'default' => \INF === $option->getDefault() ? 'INF' : $option->getDefault(), + ]; + } + + private function getInputDefinitionData(InputDefinition $definition): array + { + $inputArguments = []; + foreach ($definition->getArguments() as $name => $argument) { + $inputArguments[$name] = $this->getInputArgumentData($argument); + } + + $inputOptions = []; + foreach ($definition->getOptions() as $name => $option) { + $inputOptions[$name] = $this->getInputOptionData($option); + if ($option->isNegatable()) { + $inputOptions['no-'.$name] = $this->getInputOptionData($option, true); + } + } + + return ['arguments' => $inputArguments, 'options' => $inputOptions]; + } + + private function getCommandData(Command $command, bool $short = false): array + { + $data = [ + 'name' => $command->getName(), + 'description' => $command->getDescription(), + ]; + + if ($short) { + $data += [ + 'usage' => $command->getAliases(), + ]; + } else { + $command->mergeApplicationDefinition(false); + + $data += [ + 'usage' => array_merge([$command->getSynopsis()], $command->getUsages(), $command->getAliases()), + 'help' => $command->getProcessedHelp(), + 'definition' => $this->getInputDefinitionData($command->getDefinition()), + ]; + } + + $data['hidden'] = $command->isHidden(); + + return $data; + } +} diff --git a/vendor/symfony/console/Descriptor/MarkdownDescriptor.php b/vendor/symfony/console/Descriptor/MarkdownDescriptor.php new file mode 100644 index 00000000..b3f16ee9 --- /dev/null +++ b/vendor/symfony/console/Descriptor/MarkdownDescriptor.php @@ -0,0 +1,173 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Markdown descriptor. + * + * @author Jean-François Simon + * + * @internal + */ +class MarkdownDescriptor extends Descriptor +{ + public function describe(OutputInterface $output, object $object, array $options = []): void + { + $decorated = $output->isDecorated(); + $output->setDecorated(false); + + parent::describe($output, $object, $options); + + $output->setDecorated($decorated); + } + + protected function write(string $content, bool $decorated = true): void + { + parent::write($content, $decorated); + } + + protected function describeInputArgument(InputArgument $argument, array $options = []): void + { + $this->write( + '#### `'.($argument->getName() ?: '')."`\n\n" + .($argument->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $argument->getDescription())."\n\n" : '') + .'* Is required: '.($argument->isRequired() ? 'yes' : 'no')."\n" + .'* Is array: '.($argument->isArray() ? 'yes' : 'no')."\n" + .'* Default: `'.str_replace("\n", '', var_export($argument->getDefault(), true)).'`' + ); + } + + protected function describeInputOption(InputOption $option, array $options = []): void + { + $name = '--'.$option->getName(); + if ($option->isNegatable()) { + $name .= '|--no-'.$option->getName(); + } + if ($option->getShortcut()) { + $name .= '|-'.str_replace('|', '|-', $option->getShortcut()).''; + } + + $this->write( + '#### `'.$name.'`'."\n\n" + .($option->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $option->getDescription())."\n\n" : '') + .'* Accept value: '.($option->acceptValue() ? 'yes' : 'no')."\n" + .'* Is value required: '.($option->isValueRequired() ? 'yes' : 'no')."\n" + .'* Is multiple: '.($option->isArray() ? 'yes' : 'no')."\n" + .'* Is negatable: '.($option->isNegatable() ? 'yes' : 'no')."\n" + .'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`' + ); + } + + protected function describeInputDefinition(InputDefinition $definition, array $options = []): void + { + if ($showArguments = \count($definition->getArguments()) > 0) { + $this->write('### Arguments'); + foreach ($definition->getArguments() as $argument) { + $this->write("\n\n"); + $this->describeInputArgument($argument); + } + } + + if (\count($definition->getOptions()) > 0) { + if ($showArguments) { + $this->write("\n\n"); + } + + $this->write('### Options'); + foreach ($definition->getOptions() as $option) { + $this->write("\n\n"); + $this->describeInputOption($option); + } + } + } + + protected function describeCommand(Command $command, array $options = []): void + { + if ($options['short'] ?? false) { + $this->write( + '`'.$command->getName()."`\n" + .str_repeat('-', Helper::width($command->getName()) + 2)."\n\n" + .($command->getDescription() ? $command->getDescription()."\n\n" : '') + .'### Usage'."\n\n" + .array_reduce($command->getAliases(), fn ($carry, $usage) => $carry.'* `'.$usage.'`'."\n") + ); + + return; + } + + $command->mergeApplicationDefinition(false); + + $this->write( + '`'.$command->getName()."`\n" + .str_repeat('-', Helper::width($command->getName()) + 2)."\n\n" + .($command->getDescription() ? $command->getDescription()."\n\n" : '') + .'### Usage'."\n\n" + .array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), fn ($carry, $usage) => $carry.'* `'.$usage.'`'."\n") + ); + + if ($help = $command->getProcessedHelp()) { + $this->write("\n"); + $this->write($help); + } + + $definition = $command->getDefinition(); + if ($definition->getOptions() || $definition->getArguments()) { + $this->write("\n\n"); + $this->describeInputDefinition($definition); + } + } + + protected function describeApplication(Application $application, array $options = []): void + { + $describedNamespace = $options['namespace'] ?? null; + $description = new ApplicationDescription($application, $describedNamespace); + $title = $this->getApplicationTitle($application); + + $this->write($title."\n".str_repeat('=', Helper::width($title))); + + foreach ($description->getNamespaces() as $namespace) { + if (ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) { + $this->write("\n\n"); + $this->write('**'.$namespace['id'].':**'); + } + + $this->write("\n\n"); + $this->write(implode("\n", array_map(fn ($commandName) => sprintf('* [`%s`](#%s)', $commandName, str_replace(':', '', $description->getCommand($commandName)->getName())), $namespace['commands']))); + } + + foreach ($description->getCommands() as $command) { + $this->write("\n\n"); + $this->describeCommand($command, $options); + } + } + + private function getApplicationTitle(Application $application): string + { + if ('UNKNOWN' !== $application->getName()) { + if ('UNKNOWN' !== $application->getVersion()) { + return sprintf('%s %s', $application->getName(), $application->getVersion()); + } + + return $application->getName(); + } + + return 'Console Tool'; + } +} diff --git a/vendor/symfony/console/Descriptor/ReStructuredTextDescriptor.php b/vendor/symfony/console/Descriptor/ReStructuredTextDescriptor.php new file mode 100644 index 00000000..d4423fd3 --- /dev/null +++ b/vendor/symfony/console/Descriptor/ReStructuredTextDescriptor.php @@ -0,0 +1,272 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\String\UnicodeString; + +class ReStructuredTextDescriptor extends Descriptor +{ + //

      + private string $partChar = '='; + //

      + private string $chapterChar = '-'; + //

      + private string $sectionChar = '~'; + //

      + private string $subsectionChar = '.'; + //

      + private string $subsubsectionChar = '^'; + //
      + private string $paragraphsChar = '"'; + + private array $visibleNamespaces = []; + + public function describe(OutputInterface $output, object $object, array $options = []): void + { + $decorated = $output->isDecorated(); + $output->setDecorated(false); + + parent::describe($output, $object, $options); + + $output->setDecorated($decorated); + } + + /** + * Override parent method to set $decorated = true. + */ + protected function write(string $content, bool $decorated = true): void + { + parent::write($content, $decorated); + } + + protected function describeInputArgument(InputArgument $argument, array $options = []): void + { + $this->write( + $argument->getName() ?: ''."\n".str_repeat($this->paragraphsChar, Helper::width($argument->getName()))."\n\n" + .($argument->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $argument->getDescription())."\n\n" : '') + .'- **Is required**: '.($argument->isRequired() ? 'yes' : 'no')."\n" + .'- **Is array**: '.($argument->isArray() ? 'yes' : 'no')."\n" + .'- **Default**: ``'.str_replace("\n", '', var_export($argument->getDefault(), true)).'``' + ); + } + + protected function describeInputOption(InputOption $option, array $options = []): void + { + $name = '\-\-'.$option->getName(); + if ($option->isNegatable()) { + $name .= '|\-\-no-'.$option->getName(); + } + if ($option->getShortcut()) { + $name .= '|-'.str_replace('|', '|-', $option->getShortcut()); + } + + $optionDescription = $option->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n\n", $option->getDescription())."\n\n" : ''; + $optionDescription = (new UnicodeString($optionDescription))->ascii(); + $this->write( + $name."\n".str_repeat($this->paragraphsChar, Helper::width($name))."\n\n" + .$optionDescription + .'- **Accept value**: '.($option->acceptValue() ? 'yes' : 'no')."\n" + .'- **Is value required**: '.($option->isValueRequired() ? 'yes' : 'no')."\n" + .'- **Is multiple**: '.($option->isArray() ? 'yes' : 'no')."\n" + .'- **Is negatable**: '.($option->isNegatable() ? 'yes' : 'no')."\n" + .'- **Default**: ``'.str_replace("\n", '', var_export($option->getDefault(), true)).'``'."\n" + ); + } + + protected function describeInputDefinition(InputDefinition $definition, array $options = []): void + { + if ($showArguments = ((bool) $definition->getArguments())) { + $this->write("Arguments\n".str_repeat($this->subsubsectionChar, 9))."\n\n"; + foreach ($definition->getArguments() as $argument) { + $this->write("\n\n"); + $this->describeInputArgument($argument); + } + } + + if ($nonDefaultOptions = $this->getNonDefaultOptions($definition)) { + if ($showArguments) { + $this->write("\n\n"); + } + + $this->write("Options\n".str_repeat($this->subsubsectionChar, 7)."\n\n"); + foreach ($nonDefaultOptions as $option) { + $this->describeInputOption($option); + $this->write("\n"); + } + } + } + + protected function describeCommand(Command $command, array $options = []): void + { + if ($options['short'] ?? false) { + $this->write( + '``'.$command->getName()."``\n" + .str_repeat($this->subsectionChar, Helper::width($command->getName()))."\n\n" + .($command->getDescription() ? $command->getDescription()."\n\n" : '') + ."Usage\n".str_repeat($this->paragraphsChar, 5)."\n\n" + .array_reduce($command->getAliases(), static fn ($carry, $usage) => $carry.'- ``'.$usage.'``'."\n") + ); + + return; + } + + $command->mergeApplicationDefinition(false); + + foreach ($command->getAliases() as $alias) { + $this->write('.. _'.$alias.":\n\n"); + } + $this->write( + $command->getName()."\n" + .str_repeat($this->subsectionChar, Helper::width($command->getName()))."\n\n" + .($command->getDescription() ? $command->getDescription()."\n\n" : '') + ."Usage\n".str_repeat($this->subsubsectionChar, 5)."\n\n" + .array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), static fn ($carry, $usage) => $carry.'- ``'.$usage.'``'."\n") + ); + + if ($help = $command->getProcessedHelp()) { + $this->write("\n"); + $this->write($help); + } + + $definition = $command->getDefinition(); + if ($definition->getOptions() || $definition->getArguments()) { + $this->write("\n\n"); + $this->describeInputDefinition($definition); + } + } + + protected function describeApplication(Application $application, array $options = []): void + { + $description = new ApplicationDescription($application, $options['namespace'] ?? null); + $title = $this->getApplicationTitle($application); + + $this->write($title."\n".str_repeat($this->partChar, Helper::width($title))); + $this->createTableOfContents($description, $application); + $this->describeCommands($application, $options); + } + + private function getApplicationTitle(Application $application): string + { + if ('UNKNOWN' === $application->getName()) { + return 'Console Tool'; + } + if ('UNKNOWN' !== $application->getVersion()) { + return sprintf('%s %s', $application->getName(), $application->getVersion()); + } + + return $application->getName(); + } + + private function describeCommands($application, array $options): void + { + $title = 'Commands'; + $this->write("\n\n$title\n".str_repeat($this->chapterChar, Helper::width($title))."\n\n"); + foreach ($this->visibleNamespaces as $namespace) { + if ('_global' === $namespace) { + $commands = $application->all(''); + $this->write('Global'."\n".str_repeat($this->sectionChar, Helper::width('Global'))."\n\n"); + } else { + $commands = $application->all($namespace); + $this->write($namespace."\n".str_repeat($this->sectionChar, Helper::width($namespace))."\n\n"); + } + + foreach ($this->removeAliasesAndHiddenCommands($commands) as $command) { + $this->describeCommand($command, $options); + $this->write("\n\n"); + } + } + } + + private function createTableOfContents(ApplicationDescription $description, Application $application): void + { + $this->setVisibleNamespaces($description); + $chapterTitle = 'Table of Contents'; + $this->write("\n\n$chapterTitle\n".str_repeat($this->chapterChar, Helper::width($chapterTitle))."\n\n"); + foreach ($this->visibleNamespaces as $namespace) { + if ('_global' === $namespace) { + $commands = $application->all(''); + } else { + $commands = $application->all($namespace); + $this->write("\n\n"); + $this->write($namespace."\n".str_repeat($this->sectionChar, Helper::width($namespace))."\n\n"); + } + $commands = $this->removeAliasesAndHiddenCommands($commands); + + $this->write("\n\n"); + $this->write(implode("\n", array_map(static fn ($commandName) => sprintf('- `%s`_', $commandName), array_keys($commands)))); + } + } + + private function getNonDefaultOptions(InputDefinition $definition): array + { + $globalOptions = [ + 'help', + 'quiet', + 'verbose', + 'version', + 'ansi', + 'no-interaction', + ]; + $nonDefaultOptions = []; + foreach ($definition->getOptions() as $option) { + // Skip global options. + if (!\in_array($option->getName(), $globalOptions)) { + $nonDefaultOptions[] = $option; + } + } + + return $nonDefaultOptions; + } + + private function setVisibleNamespaces(ApplicationDescription $description): void + { + $commands = $description->getCommands(); + foreach ($description->getNamespaces() as $namespace) { + try { + $namespaceCommands = $namespace['commands']; + foreach ($namespaceCommands as $key => $commandName) { + if (!\array_key_exists($commandName, $commands)) { + // If the array key does not exist, then this is an alias. + unset($namespaceCommands[$key]); + } elseif ($commands[$commandName]->isHidden()) { + unset($namespaceCommands[$key]); + } + } + if (!$namespaceCommands) { + // If the namespace contained only aliases or hidden commands, skip the namespace. + continue; + } + } catch (\Exception) { + } + $this->visibleNamespaces[] = $namespace['id']; + } + } + + private function removeAliasesAndHiddenCommands(array $commands): array + { + foreach ($commands as $key => $command) { + if ($command->isHidden() || \in_array($key, $command->getAliases(), true)) { + unset($commands[$key]); + } + } + unset($commands['completion']); + + return $commands; + } +} diff --git a/vendor/symfony/console/Descriptor/TextDescriptor.php b/vendor/symfony/console/Descriptor/TextDescriptor.php new file mode 100644 index 00000000..d04d1023 --- /dev/null +++ b/vendor/symfony/console/Descriptor/TextDescriptor.php @@ -0,0 +1,317 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; + +/** + * Text descriptor. + * + * @author Jean-François Simon + * + * @internal + */ +class TextDescriptor extends Descriptor +{ + protected function describeInputArgument(InputArgument $argument, array $options = []): void + { + if (null !== $argument->getDefault() && (!\is_array($argument->getDefault()) || \count($argument->getDefault()))) { + $default = sprintf(' [default: %s]', $this->formatDefaultValue($argument->getDefault())); + } else { + $default = ''; + } + + $totalWidth = $options['total_width'] ?? Helper::width($argument->getName()); + $spacingWidth = $totalWidth - \strlen($argument->getName()); + + $this->writeText(sprintf(' %s %s%s%s', + $argument->getName(), + str_repeat(' ', $spacingWidth), + // + 4 = 2 spaces before , 2 spaces after + preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $argument->getDescription()), + $default + ), $options); + } + + protected function describeInputOption(InputOption $option, array $options = []): void + { + if ($option->acceptValue() && null !== $option->getDefault() && (!\is_array($option->getDefault()) || \count($option->getDefault()))) { + $default = sprintf(' [default: %s]', $this->formatDefaultValue($option->getDefault())); + } else { + $default = ''; + } + + $value = ''; + if ($option->acceptValue()) { + $value = '='.strtoupper($option->getName()); + + if ($option->isValueOptional()) { + $value = '['.$value.']'; + } + } + + $totalWidth = $options['total_width'] ?? $this->calculateTotalWidthForOptions([$option]); + $synopsis = sprintf('%s%s', + $option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : ' ', + sprintf($option->isNegatable() ? '--%1$s|--no-%1$s' : '--%1$s%2$s', $option->getName(), $value) + ); + + $spacingWidth = $totalWidth - Helper::width($synopsis); + + $this->writeText(sprintf(' %s %s%s%s%s', + $synopsis, + str_repeat(' ', $spacingWidth), + // + 4 = 2 spaces before , 2 spaces after + preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $option->getDescription()), + $default, + $option->isArray() ? ' (multiple values allowed)' : '' + ), $options); + } + + protected function describeInputDefinition(InputDefinition $definition, array $options = []): void + { + $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions()); + foreach ($definition->getArguments() as $argument) { + $totalWidth = max($totalWidth, Helper::width($argument->getName())); + } + + if ($definition->getArguments()) { + $this->writeText('Arguments:', $options); + $this->writeText("\n"); + foreach ($definition->getArguments() as $argument) { + $this->describeInputArgument($argument, array_merge($options, ['total_width' => $totalWidth])); + $this->writeText("\n"); + } + } + + if ($definition->getArguments() && $definition->getOptions()) { + $this->writeText("\n"); + } + + if ($definition->getOptions()) { + $laterOptions = []; + + $this->writeText('Options:', $options); + foreach ($definition->getOptions() as $option) { + if (\strlen($option->getShortcut() ?? '') > 1) { + $laterOptions[] = $option; + continue; + } + $this->writeText("\n"); + $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth])); + } + foreach ($laterOptions as $option) { + $this->writeText("\n"); + $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth])); + } + } + } + + protected function describeCommand(Command $command, array $options = []): void + { + $command->mergeApplicationDefinition(false); + + if ($description = $command->getDescription()) { + $this->writeText('Description:', $options); + $this->writeText("\n"); + $this->writeText(' '.$description); + $this->writeText("\n\n"); + } + + $this->writeText('Usage:', $options); + foreach (array_merge([$command->getSynopsis(true)], $command->getAliases(), $command->getUsages()) as $usage) { + $this->writeText("\n"); + $this->writeText(' '.OutputFormatter::escape($usage), $options); + } + $this->writeText("\n"); + + $definition = $command->getDefinition(); + if ($definition->getOptions() || $definition->getArguments()) { + $this->writeText("\n"); + $this->describeInputDefinition($definition, $options); + $this->writeText("\n"); + } + + $help = $command->getProcessedHelp(); + if ($help && $help !== $description) { + $this->writeText("\n"); + $this->writeText('Help:', $options); + $this->writeText("\n"); + $this->writeText(' '.str_replace("\n", "\n ", $help), $options); + $this->writeText("\n"); + } + } + + protected function describeApplication(Application $application, array $options = []): void + { + $describedNamespace = $options['namespace'] ?? null; + $description = new ApplicationDescription($application, $describedNamespace); + + if (isset($options['raw_text']) && $options['raw_text']) { + $width = $this->getColumnWidth($description->getCommands()); + + foreach ($description->getCommands() as $command) { + $this->writeText(sprintf("%-{$width}s %s", $command->getName(), $command->getDescription()), $options); + $this->writeText("\n"); + } + } else { + if ('' != $help = $application->getHelp()) { + $this->writeText("$help\n\n", $options); + } + + $this->writeText("Usage:\n", $options); + $this->writeText(" command [options] [arguments]\n\n", $options); + + $this->describeInputDefinition(new InputDefinition($application->getDefinition()->getOptions()), $options); + + $this->writeText("\n"); + $this->writeText("\n"); + + $commands = $description->getCommands(); + $namespaces = $description->getNamespaces(); + if ($describedNamespace && $namespaces) { + // make sure all alias commands are included when describing a specific namespace + $describedNamespaceInfo = reset($namespaces); + foreach ($describedNamespaceInfo['commands'] as $name) { + $commands[$name] = $description->getCommand($name); + } + } + + // calculate max. width based on available commands per namespace + $width = $this->getColumnWidth(array_merge(...array_values(array_map(fn ($namespace) => array_intersect($namespace['commands'], array_keys($commands)), array_values($namespaces))))); + + if ($describedNamespace) { + $this->writeText(sprintf('Available commands for the "%s" namespace:', $describedNamespace), $options); + } else { + $this->writeText('Available commands:', $options); + } + + foreach ($namespaces as $namespace) { + $namespace['commands'] = array_filter($namespace['commands'], fn ($name) => isset($commands[$name])); + + if (!$namespace['commands']) { + continue; + } + + if (!$describedNamespace && ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) { + $this->writeText("\n"); + $this->writeText(' '.$namespace['id'].'', $options); + } + + foreach ($namespace['commands'] as $name) { + $this->writeText("\n"); + $spacingWidth = $width - Helper::width($name); + $command = $commands[$name]; + $commandAliases = $name === $command->getName() ? $this->getCommandAliasesText($command) : ''; + $this->writeText(sprintf(' %s%s%s', $name, str_repeat(' ', $spacingWidth), $commandAliases.$command->getDescription()), $options); + } + } + + $this->writeText("\n"); + } + } + + private function writeText(string $content, array $options = []): void + { + $this->write( + isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content, + isset($options['raw_output']) ? !$options['raw_output'] : true + ); + } + + /** + * Formats command aliases to show them in the command description. + */ + private function getCommandAliasesText(Command $command): string + { + $text = ''; + $aliases = $command->getAliases(); + + if ($aliases) { + $text = '['.implode('|', $aliases).'] '; + } + + return $text; + } + + /** + * Formats input option/argument default value. + */ + private function formatDefaultValue(mixed $default): string + { + if (\INF === $default) { + return 'INF'; + } + + if (\is_string($default)) { + $default = OutputFormatter::escape($default); + } elseif (\is_array($default)) { + foreach ($default as $key => $value) { + if (\is_string($value)) { + $default[$key] = OutputFormatter::escape($value); + } + } + } + + return str_replace('\\\\', '\\', json_encode($default, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE)); + } + + /** + * @param array $commands + */ + private function getColumnWidth(array $commands): int + { + $widths = []; + + foreach ($commands as $command) { + if ($command instanceof Command) { + $widths[] = Helper::width($command->getName()); + foreach ($command->getAliases() as $alias) { + $widths[] = Helper::width($alias); + } + } else { + $widths[] = Helper::width($command); + } + } + + return $widths ? max($widths) + 2 : 0; + } + + /** + * @param InputOption[] $options + */ + private function calculateTotalWidthForOptions(array $options): int + { + $totalWidth = 0; + foreach ($options as $option) { + // "-" + shortcut + ", --" + name + $nameLength = 1 + max(Helper::width($option->getShortcut()), 1) + 4 + Helper::width($option->getName()); + if ($option->isNegatable()) { + $nameLength += 6 + Helper::width($option->getName()); // |--no- + name + } elseif ($option->acceptValue()) { + $valueLength = 1 + Helper::width($option->getName()); // = + value + $valueLength += $option->isValueOptional() ? 2 : 0; // [ + ] + + $nameLength += $valueLength; + } + $totalWidth = max($totalWidth, $nameLength); + } + + return $totalWidth; + } +} diff --git a/vendor/symfony/console/Descriptor/XmlDescriptor.php b/vendor/symfony/console/Descriptor/XmlDescriptor.php new file mode 100644 index 00000000..866c7185 --- /dev/null +++ b/vendor/symfony/console/Descriptor/XmlDescriptor.php @@ -0,0 +1,232 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; + +/** + * XML descriptor. + * + * @author Jean-François Simon + * + * @internal + */ +class XmlDescriptor extends Descriptor +{ + public function getInputDefinitionDocument(InputDefinition $definition): \DOMDocument + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($definitionXML = $dom->createElement('definition')); + + $definitionXML->appendChild($argumentsXML = $dom->createElement('arguments')); + foreach ($definition->getArguments() as $argument) { + $this->appendDocument($argumentsXML, $this->getInputArgumentDocument($argument)); + } + + $definitionXML->appendChild($optionsXML = $dom->createElement('options')); + foreach ($definition->getOptions() as $option) { + $this->appendDocument($optionsXML, $this->getInputOptionDocument($option)); + } + + return $dom; + } + + public function getCommandDocument(Command $command, bool $short = false): \DOMDocument + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($commandXML = $dom->createElement('command')); + + $commandXML->setAttribute('id', $command->getName()); + $commandXML->setAttribute('name', $command->getName()); + $commandXML->setAttribute('hidden', $command->isHidden() ? 1 : 0); + + $commandXML->appendChild($usagesXML = $dom->createElement('usages')); + + $commandXML->appendChild($descriptionXML = $dom->createElement('description')); + $descriptionXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getDescription()))); + + if ($short) { + foreach ($command->getAliases() as $usage) { + $usagesXML->appendChild($dom->createElement('usage', $usage)); + } + } else { + $command->mergeApplicationDefinition(false); + + foreach (array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()) as $usage) { + $usagesXML->appendChild($dom->createElement('usage', $usage)); + } + + $commandXML->appendChild($helpXML = $dom->createElement('help')); + $helpXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getProcessedHelp()))); + + $definitionXML = $this->getInputDefinitionDocument($command->getDefinition()); + $this->appendDocument($commandXML, $definitionXML->getElementsByTagName('definition')->item(0)); + } + + return $dom; + } + + public function getApplicationDocument(Application $application, ?string $namespace = null, bool $short = false): \DOMDocument + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($rootXml = $dom->createElement('symfony')); + + if ('UNKNOWN' !== $application->getName()) { + $rootXml->setAttribute('name', $application->getName()); + if ('UNKNOWN' !== $application->getVersion()) { + $rootXml->setAttribute('version', $application->getVersion()); + } + } + + $rootXml->appendChild($commandsXML = $dom->createElement('commands')); + + $description = new ApplicationDescription($application, $namespace, true); + + if ($namespace) { + $commandsXML->setAttribute('namespace', $namespace); + } + + foreach ($description->getCommands() as $command) { + $this->appendDocument($commandsXML, $this->getCommandDocument($command, $short)); + } + + if (!$namespace) { + $rootXml->appendChild($namespacesXML = $dom->createElement('namespaces')); + + foreach ($description->getNamespaces() as $namespaceDescription) { + $namespacesXML->appendChild($namespaceArrayXML = $dom->createElement('namespace')); + $namespaceArrayXML->setAttribute('id', $namespaceDescription['id']); + + foreach ($namespaceDescription['commands'] as $name) { + $namespaceArrayXML->appendChild($commandXML = $dom->createElement('command')); + $commandXML->appendChild($dom->createTextNode($name)); + } + } + } + + return $dom; + } + + protected function describeInputArgument(InputArgument $argument, array $options = []): void + { + $this->writeDocument($this->getInputArgumentDocument($argument)); + } + + protected function describeInputOption(InputOption $option, array $options = []): void + { + $this->writeDocument($this->getInputOptionDocument($option)); + } + + protected function describeInputDefinition(InputDefinition $definition, array $options = []): void + { + $this->writeDocument($this->getInputDefinitionDocument($definition)); + } + + protected function describeCommand(Command $command, array $options = []): void + { + $this->writeDocument($this->getCommandDocument($command, $options['short'] ?? false)); + } + + protected function describeApplication(Application $application, array $options = []): void + { + $this->writeDocument($this->getApplicationDocument($application, $options['namespace'] ?? null, $options['short'] ?? false)); + } + + /** + * Appends document children to parent node. + */ + private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent): void + { + foreach ($importedParent->childNodes as $childNode) { + $parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true)); + } + } + + /** + * Writes DOM document. + */ + private function writeDocument(\DOMDocument $dom): void + { + $dom->formatOutput = true; + $this->write($dom->saveXML()); + } + + private function getInputArgumentDocument(InputArgument $argument): \DOMDocument + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + + $dom->appendChild($objectXML = $dom->createElement('argument')); + $objectXML->setAttribute('name', $argument->getName()); + $objectXML->setAttribute('is_required', $argument->isRequired() ? 1 : 0); + $objectXML->setAttribute('is_array', $argument->isArray() ? 1 : 0); + $objectXML->appendChild($descriptionXML = $dom->createElement('description')); + $descriptionXML->appendChild($dom->createTextNode($argument->getDescription())); + + $objectXML->appendChild($defaultsXML = $dom->createElement('defaults')); + $defaults = \is_array($argument->getDefault()) ? $argument->getDefault() : (\is_bool($argument->getDefault()) ? [var_export($argument->getDefault(), true)] : ($argument->getDefault() ? [$argument->getDefault()] : [])); + foreach ($defaults as $default) { + $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); + $defaultXML->appendChild($dom->createTextNode($default)); + } + + return $dom; + } + + private function getInputOptionDocument(InputOption $option): \DOMDocument + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + + $dom->appendChild($objectXML = $dom->createElement('option')); + $objectXML->setAttribute('name', '--'.$option->getName()); + $pos = strpos($option->getShortcut() ?? '', '|'); + if (false !== $pos) { + $objectXML->setAttribute('shortcut', '-'.substr($option->getShortcut(), 0, $pos)); + $objectXML->setAttribute('shortcuts', '-'.str_replace('|', '|-', $option->getShortcut())); + } else { + $objectXML->setAttribute('shortcut', $option->getShortcut() ? '-'.$option->getShortcut() : ''); + } + $objectXML->setAttribute('accept_value', $option->acceptValue() ? 1 : 0); + $objectXML->setAttribute('is_value_required', $option->isValueRequired() ? 1 : 0); + $objectXML->setAttribute('is_multiple', $option->isArray() ? 1 : 0); + $objectXML->appendChild($descriptionXML = $dom->createElement('description')); + $descriptionXML->appendChild($dom->createTextNode($option->getDescription())); + + if ($option->acceptValue()) { + $defaults = \is_array($option->getDefault()) ? $option->getDefault() : (\is_bool($option->getDefault()) ? [var_export($option->getDefault(), true)] : ($option->getDefault() ? [$option->getDefault()] : [])); + $objectXML->appendChild($defaultsXML = $dom->createElement('defaults')); + + if (!empty($defaults)) { + foreach ($defaults as $default) { + $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); + $defaultXML->appendChild($dom->createTextNode($default)); + } + } + } + + if ($option->isNegatable()) { + $dom->appendChild($objectXML = $dom->createElement('option')); + $objectXML->setAttribute('name', '--no-'.$option->getName()); + $objectXML->setAttribute('shortcut', ''); + $objectXML->setAttribute('accept_value', 0); + $objectXML->setAttribute('is_value_required', 0); + $objectXML->setAttribute('is_multiple', 0); + $objectXML->appendChild($descriptionXML = $dom->createElement('description')); + $descriptionXML->appendChild($dom->createTextNode('Negate the "--'.$option->getName().'" option')); + } + + return $dom; + } +} diff --git a/vendor/symfony/console/Event/ConsoleCommandEvent.php b/vendor/symfony/console/Event/ConsoleCommandEvent.php new file mode 100644 index 00000000..0757a23f --- /dev/null +++ b/vendor/symfony/console/Event/ConsoleCommandEvent.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Event; + +/** + * Allows to do things before the command is executed, like skipping the command or executing code before the command is + * going to be executed. + * + * Changing the input arguments will have no effect. + * + * @author Fabien Potencier + */ +final class ConsoleCommandEvent extends ConsoleEvent +{ + /** + * The return code for skipped commands, this will also be passed into the terminate event. + */ + public const RETURN_CODE_DISABLED = 113; + + /** + * Indicates if the command should be run or skipped. + */ + private bool $commandShouldRun = true; + + /** + * Disables the command, so it won't be run. + */ + public function disableCommand(): bool + { + return $this->commandShouldRun = false; + } + + public function enableCommand(): bool + { + return $this->commandShouldRun = true; + } + + /** + * Returns true if the command is runnable, false otherwise. + */ + public function commandShouldRun(): bool + { + return $this->commandShouldRun; + } +} diff --git a/vendor/symfony/console/Event/ConsoleErrorEvent.php b/vendor/symfony/console/Event/ConsoleErrorEvent.php new file mode 100644 index 00000000..7be2ff83 --- /dev/null +++ b/vendor/symfony/console/Event/ConsoleErrorEvent.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Event; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Allows to handle throwables thrown while running a command. + * + * @author Wouter de Jong + */ +final class ConsoleErrorEvent extends ConsoleEvent +{ + private \Throwable $error; + private int $exitCode; + + public function __construct(InputInterface $input, OutputInterface $output, \Throwable $error, ?Command $command = null) + { + parent::__construct($command, $input, $output); + + $this->error = $error; + } + + public function getError(): \Throwable + { + return $this->error; + } + + public function setError(\Throwable $error): void + { + $this->error = $error; + } + + public function setExitCode(int $exitCode): void + { + $this->exitCode = $exitCode; + + $r = new \ReflectionProperty($this->error, 'code'); + $r->setValue($this->error, $this->exitCode); + } + + public function getExitCode(): int + { + return $this->exitCode ?? (\is_int($this->error->getCode()) && 0 !== $this->error->getCode() ? $this->error->getCode() : 1); + } +} diff --git a/vendor/symfony/console/Event/ConsoleEvent.php b/vendor/symfony/console/Event/ConsoleEvent.php new file mode 100644 index 00000000..437a58e1 --- /dev/null +++ b/vendor/symfony/console/Event/ConsoleEvent.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Event; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Contracts\EventDispatcher\Event; + +/** + * Allows to inspect input and output of a command. + * + * @author Francesco Levorato + */ +class ConsoleEvent extends Event +{ + protected ?Command $command; + + private InputInterface $input; + private OutputInterface $output; + + public function __construct(?Command $command, InputInterface $input, OutputInterface $output) + { + $this->command = $command; + $this->input = $input; + $this->output = $output; + } + + /** + * Gets the command that is executed. + */ + public function getCommand(): ?Command + { + return $this->command; + } + + /** + * Gets the input instance. + */ + public function getInput(): InputInterface + { + return $this->input; + } + + /** + * Gets the output instance. + */ + public function getOutput(): OutputInterface + { + return $this->output; + } +} diff --git a/vendor/symfony/console/Event/ConsoleSignalEvent.php b/vendor/symfony/console/Event/ConsoleSignalEvent.php new file mode 100644 index 00000000..95af1f91 --- /dev/null +++ b/vendor/symfony/console/Event/ConsoleSignalEvent.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Event; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author marie + */ +final class ConsoleSignalEvent extends ConsoleEvent +{ + private int $handlingSignal; + private int|false $exitCode; + + public function __construct(Command $command, InputInterface $input, OutputInterface $output, int $handlingSignal, int|false $exitCode = 0) + { + parent::__construct($command, $input, $output); + $this->handlingSignal = $handlingSignal; + $this->exitCode = $exitCode; + } + + public function getHandlingSignal(): int + { + return $this->handlingSignal; + } + + public function setExitCode(int $exitCode): void + { + if ($exitCode < 0 || $exitCode > 255) { + throw new \InvalidArgumentException('Exit code must be between 0 and 255.'); + } + + $this->exitCode = $exitCode; + } + + public function abortExit(): void + { + $this->exitCode = false; + } + + public function getExitCode(): int|false + { + return $this->exitCode; + } +} diff --git a/vendor/symfony/console/Event/ConsoleTerminateEvent.php b/vendor/symfony/console/Event/ConsoleTerminateEvent.php new file mode 100644 index 00000000..38f7253a --- /dev/null +++ b/vendor/symfony/console/Event/ConsoleTerminateEvent.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Event; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Allows to manipulate the exit code of a command after its execution. + * + * @author Francesco Levorato + * @author Jules Pietri + */ +final class ConsoleTerminateEvent extends ConsoleEvent +{ + public function __construct( + Command $command, + InputInterface $input, + OutputInterface $output, + private int $exitCode, + private readonly ?int $interruptingSignal = null, + ) { + parent::__construct($command, $input, $output); + } + + public function setExitCode(int $exitCode): void + { + $this->exitCode = $exitCode; + } + + public function getExitCode(): int + { + return $this->exitCode; + } + + public function getInterruptingSignal(): ?int + { + return $this->interruptingSignal; + } +} diff --git a/vendor/symfony/console/EventListener/ErrorListener.php b/vendor/symfony/console/EventListener/ErrorListener.php new file mode 100644 index 00000000..1e41e759 --- /dev/null +++ b/vendor/symfony/console/EventListener/ErrorListener.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\EventListener; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\Console\Event\ConsoleErrorEvent; +use Symfony\Component\Console\Event\ConsoleEvent; +use Symfony\Component\Console\Event\ConsoleTerminateEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * @author James Halsall + * @author Robin Chalas + */ +class ErrorListener implements EventSubscriberInterface +{ + private ?LoggerInterface $logger; + + public function __construct(?LoggerInterface $logger = null) + { + $this->logger = $logger; + } + + public function onConsoleError(ConsoleErrorEvent $event): void + { + if (null === $this->logger) { + return; + } + + $error = $event->getError(); + + if (!$inputString = $this->getInputString($event)) { + $this->logger->critical('An error occurred while using the console. Message: "{message}"', ['exception' => $error, 'message' => $error->getMessage()]); + + return; + } + + $this->logger->critical('Error thrown while running command "{command}". Message: "{message}"', ['exception' => $error, 'command' => $inputString, 'message' => $error->getMessage()]); + } + + public function onConsoleTerminate(ConsoleTerminateEvent $event): void + { + if (null === $this->logger) { + return; + } + + $exitCode = $event->getExitCode(); + + if (0 === $exitCode) { + return; + } + + if (!$inputString = $this->getInputString($event)) { + $this->logger->debug('The console exited with code "{code}"', ['code' => $exitCode]); + + return; + } + + $this->logger->debug('Command "{command}" exited with code "{code}"', ['command' => $inputString, 'code' => $exitCode]); + } + + public static function getSubscribedEvents(): array + { + return [ + ConsoleEvents::ERROR => ['onConsoleError', -128], + ConsoleEvents::TERMINATE => ['onConsoleTerminate', -128], + ]; + } + + private static function getInputString(ConsoleEvent $event): ?string + { + $commandName = $event->getCommand()?->getName(); + $input = $event->getInput(); + + if ($input instanceof \Stringable) { + if ($commandName) { + return str_replace(["'$commandName'", "\"$commandName\""], $commandName, (string) $input); + } + + return (string) $input; + } + + return $commandName; + } +} diff --git a/vendor/symfony/console/Exception/CommandNotFoundException.php b/vendor/symfony/console/Exception/CommandNotFoundException.php new file mode 100644 index 00000000..541b32b2 --- /dev/null +++ b/vendor/symfony/console/Exception/CommandNotFoundException.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * Represents an incorrect command name typed in the console. + * + * @author Jérôme Tamarelle + */ +class CommandNotFoundException extends \InvalidArgumentException implements ExceptionInterface +{ + private array $alternatives; + + /** + * @param string $message Exception message to throw + * @param string[] $alternatives List of similar defined names + * @param int $code Exception code + * @param \Throwable|null $previous Previous exception used for the exception chaining + */ + public function __construct(string $message, array $alternatives = [], int $code = 0, ?\Throwable $previous = null) + { + parent::__construct($message, $code, $previous); + + $this->alternatives = $alternatives; + } + + /** + * @return string[] + */ + public function getAlternatives(): array + { + return $this->alternatives; + } +} diff --git a/vendor/symfony/console/Exception/ExceptionInterface.php b/vendor/symfony/console/Exception/ExceptionInterface.php new file mode 100644 index 00000000..1624e13d --- /dev/null +++ b/vendor/symfony/console/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * ExceptionInterface. + * + * @author Jérôme Tamarelle + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/console/Exception/InvalidArgumentException.php b/vendor/symfony/console/Exception/InvalidArgumentException.php new file mode 100644 index 00000000..07cc0b61 --- /dev/null +++ b/vendor/symfony/console/Exception/InvalidArgumentException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * @author Jérôme Tamarelle + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/console/Exception/InvalidOptionException.php b/vendor/symfony/console/Exception/InvalidOptionException.php new file mode 100644 index 00000000..5cf62792 --- /dev/null +++ b/vendor/symfony/console/Exception/InvalidOptionException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * Represents an incorrect option name or value typed in the console. + * + * @author Jérôme Tamarelle + */ +class InvalidOptionException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/console/Exception/LogicException.php b/vendor/symfony/console/Exception/LogicException.php new file mode 100644 index 00000000..fc37b8d8 --- /dev/null +++ b/vendor/symfony/console/Exception/LogicException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * @author Jérôme Tamarelle + */ +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/console/Exception/MissingInputException.php b/vendor/symfony/console/Exception/MissingInputException.php new file mode 100644 index 00000000..04f02ade --- /dev/null +++ b/vendor/symfony/console/Exception/MissingInputException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * Represents failure to read input from stdin. + * + * @author Gabriel Ostrolucký + */ +class MissingInputException extends RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/console/Exception/NamespaceNotFoundException.php b/vendor/symfony/console/Exception/NamespaceNotFoundException.php new file mode 100644 index 00000000..dd16e450 --- /dev/null +++ b/vendor/symfony/console/Exception/NamespaceNotFoundException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * Represents an incorrect namespace typed in the console. + * + * @author Pierre du Plessis + */ +class NamespaceNotFoundException extends CommandNotFoundException +{ +} diff --git a/vendor/symfony/console/Exception/RunCommandFailedException.php b/vendor/symfony/console/Exception/RunCommandFailedException.php new file mode 100644 index 00000000..5d87ec94 --- /dev/null +++ b/vendor/symfony/console/Exception/RunCommandFailedException.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +use Symfony\Component\Console\Messenger\RunCommandContext; + +/** + * @author Kevin Bond + */ +final class RunCommandFailedException extends RuntimeException +{ + public function __construct(\Throwable|string $exception, public readonly RunCommandContext $context) + { + parent::__construct( + $exception instanceof \Throwable ? $exception->getMessage() : $exception, + $exception instanceof \Throwable ? $exception->getCode() : 0, + $exception instanceof \Throwable ? $exception : null, + ); + } +} diff --git a/vendor/symfony/console/Exception/RuntimeException.php b/vendor/symfony/console/Exception/RuntimeException.php new file mode 100644 index 00000000..51d7d80a --- /dev/null +++ b/vendor/symfony/console/Exception/RuntimeException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * @author Jérôme Tamarelle + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/console/Formatter/NullOutputFormatter.php b/vendor/symfony/console/Formatter/NullOutputFormatter.php new file mode 100644 index 00000000..5c11c764 --- /dev/null +++ b/vendor/symfony/console/Formatter/NullOutputFormatter.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +/** + * @author Tien Xuan Vo + */ +final class NullOutputFormatter implements OutputFormatterInterface +{ + private NullOutputFormatterStyle $style; + + public function format(?string $message): ?string + { + return null; + } + + public function getStyle(string $name): OutputFormatterStyleInterface + { + // to comply with the interface we must return a OutputFormatterStyleInterface + return $this->style ??= new NullOutputFormatterStyle(); + } + + public function hasStyle(string $name): bool + { + return false; + } + + public function isDecorated(): bool + { + return false; + } + + public function setDecorated(bool $decorated): void + { + // do nothing + } + + public function setStyle(string $name, OutputFormatterStyleInterface $style): void + { + // do nothing + } +} diff --git a/vendor/symfony/console/Formatter/NullOutputFormatterStyle.php b/vendor/symfony/console/Formatter/NullOutputFormatterStyle.php new file mode 100644 index 00000000..06fa6e40 --- /dev/null +++ b/vendor/symfony/console/Formatter/NullOutputFormatterStyle.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +/** + * @author Tien Xuan Vo + */ +final class NullOutputFormatterStyle implements OutputFormatterStyleInterface +{ + public function apply(string $text): string + { + return $text; + } + + public function setBackground(?string $color): void + { + // do nothing + } + + public function setForeground(?string $color): void + { + // do nothing + } + + public function setOption(string $option): void + { + // do nothing + } + + public function setOptions(array $options): void + { + // do nothing + } + + public function unsetOption(string $option): void + { + // do nothing + } +} diff --git a/vendor/symfony/console/Formatter/OutputFormatter.php b/vendor/symfony/console/Formatter/OutputFormatter.php new file mode 100644 index 00000000..8e81e590 --- /dev/null +++ b/vendor/symfony/console/Formatter/OutputFormatter.php @@ -0,0 +1,268 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +use function Symfony\Component\String\b; + +/** + * Formatter class for console output. + * + * @author Konstantin Kudryashov + * @author Roland Franssen + */ +class OutputFormatter implements WrappableOutputFormatterInterface +{ + private bool $decorated; + private array $styles = []; + private OutputFormatterStyleStack $styleStack; + + public function __clone() + { + $this->styleStack = clone $this->styleStack; + foreach ($this->styles as $key => $value) { + $this->styles[$key] = clone $value; + } + } + + /** + * Escapes "<" and ">" special chars in given text. + */ + public static function escape(string $text): string + { + $text = preg_replace('/([^\\\\]|^)([<>])/', '$1\\\\$2', $text); + + return self::escapeTrailingBackslash($text); + } + + /** + * Escapes trailing "\" in given text. + * + * @internal + */ + public static function escapeTrailingBackslash(string $text): string + { + if (str_ends_with($text, '\\')) { + $len = \strlen($text); + $text = rtrim($text, '\\'); + $text = str_replace("\0", '', $text); + $text .= str_repeat("\0", $len - \strlen($text)); + } + + return $text; + } + + /** + * Initializes console output formatter. + * + * @param OutputFormatterStyleInterface[] $styles Array of "name => FormatterStyle" instances + */ + public function __construct(bool $decorated = false, array $styles = []) + { + $this->decorated = $decorated; + + $this->setStyle('error', new OutputFormatterStyle('white', 'red')); + $this->setStyle('info', new OutputFormatterStyle('green')); + $this->setStyle('comment', new OutputFormatterStyle('yellow')); + $this->setStyle('question', new OutputFormatterStyle('black', 'cyan')); + + foreach ($styles as $name => $style) { + $this->setStyle($name, $style); + } + + $this->styleStack = new OutputFormatterStyleStack(); + } + + public function setDecorated(bool $decorated): void + { + $this->decorated = $decorated; + } + + public function isDecorated(): bool + { + return $this->decorated; + } + + public function setStyle(string $name, OutputFormatterStyleInterface $style): void + { + $this->styles[strtolower($name)] = $style; + } + + public function hasStyle(string $name): bool + { + return isset($this->styles[strtolower($name)]); + } + + public function getStyle(string $name): OutputFormatterStyleInterface + { + if (!$this->hasStyle($name)) { + throw new InvalidArgumentException(sprintf('Undefined style: "%s".', $name)); + } + + return $this->styles[strtolower($name)]; + } + + public function format(?string $message): ?string + { + return $this->formatAndWrap($message, 0); + } + + public function formatAndWrap(?string $message, int $width): string + { + if (null === $message) { + return ''; + } + + $offset = 0; + $output = ''; + $openTagRegex = '[a-z](?:[^\\\\<>]*+ | \\\\.)*'; + $closeTagRegex = '[a-z][^<>]*+'; + $currentLineLength = 0; + preg_match_all("#<(($openTagRegex) | /($closeTagRegex)?)>#ix", $message, $matches, \PREG_OFFSET_CAPTURE); + foreach ($matches[0] as $i => $match) { + $pos = $match[1]; + $text = $match[0]; + + if (0 != $pos && '\\' == $message[$pos - 1]) { + continue; + } + + // add the text up to the next tag + $output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset), $output, $width, $currentLineLength); + $offset = $pos + \strlen($text); + + // opening tag? + if ($open = '/' !== $text[1]) { + $tag = $matches[1][$i][0]; + } else { + $tag = $matches[3][$i][0] ?? ''; + } + + if (!$open && !$tag) { + // + $this->styleStack->pop(); + } elseif (null === $style = $this->createStyleFromString($tag)) { + $output .= $this->applyCurrentStyle($text, $output, $width, $currentLineLength); + } elseif ($open) { + $this->styleStack->push($style); + } else { + $this->styleStack->pop($style); + } + } + + $output .= $this->applyCurrentStyle(substr($message, $offset), $output, $width, $currentLineLength); + + return strtr($output, ["\0" => '\\', '\\<' => '<', '\\>' => '>']); + } + + public function getStyleStack(): OutputFormatterStyleStack + { + return $this->styleStack; + } + + /** + * Tries to create new style instance from string. + */ + private function createStyleFromString(string $string): ?OutputFormatterStyleInterface + { + if (isset($this->styles[$string])) { + return $this->styles[$string]; + } + + if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', $string, $matches, \PREG_SET_ORDER)) { + return null; + } + + $style = new OutputFormatterStyle(); + foreach ($matches as $match) { + array_shift($match); + $match[0] = strtolower($match[0]); + + if ('fg' == $match[0]) { + $style->setForeground(strtolower($match[1])); + } elseif ('bg' == $match[0]) { + $style->setBackground(strtolower($match[1])); + } elseif ('href' === $match[0]) { + $url = preg_replace('{\\\\([<>])}', '$1', $match[1]); + $style->setHref($url); + } elseif ('options' === $match[0]) { + preg_match_all('([^,;]+)', strtolower($match[1]), $options); + $options = array_shift($options); + foreach ($options as $option) { + $style->setOption($option); + } + } else { + return null; + } + } + + return $style; + } + + /** + * Applies current style from stack to text, if must be applied. + */ + private function applyCurrentStyle(string $text, string $current, int $width, int &$currentLineLength): string + { + if ('' === $text) { + return ''; + } + + if (!$width) { + return $this->isDecorated() ? $this->styleStack->getCurrent()->apply($text) : $text; + } + + if (!$currentLineLength && '' !== $current) { + $text = ltrim($text); + } + + if ($currentLineLength) { + $prefix = substr($text, 0, $i = $width - $currentLineLength)."\n"; + $text = substr($text, $i); + } else { + $prefix = ''; + } + + preg_match('~(\\n)$~', $text, $matches); + $text = $prefix.$this->addLineBreaks($text, $width); + $text = rtrim($text, "\n").($matches[1] ?? ''); + + if (!$currentLineLength && '' !== $current && !str_ends_with($current, "\n")) { + $text = "\n".$text; + } + + $lines = explode("\n", $text); + + foreach ($lines as $line) { + $currentLineLength += \strlen($line); + if ($width <= $currentLineLength) { + $currentLineLength = 0; + } + } + + if ($this->isDecorated()) { + foreach ($lines as $i => $line) { + $lines[$i] = $this->styleStack->getCurrent()->apply($line); + } + } + + return implode("\n", $lines); + } + + private function addLineBreaks(string $text, int $width): string + { + $encoding = mb_detect_encoding($text, null, true) ?: 'UTF-8'; + + return b($text)->toCodePointString($encoding)->wordwrap($width, "\n", true)->toByteString($encoding); + } +} diff --git a/vendor/symfony/console/Formatter/OutputFormatterInterface.php b/vendor/symfony/console/Formatter/OutputFormatterInterface.php new file mode 100644 index 00000000..947347fa --- /dev/null +++ b/vendor/symfony/console/Formatter/OutputFormatterInterface.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +/** + * Formatter interface for console output. + * + * @author Konstantin Kudryashov + */ +interface OutputFormatterInterface +{ + /** + * Sets the decorated flag. + */ + public function setDecorated(bool $decorated): void; + + /** + * Whether the output will decorate messages. + */ + public function isDecorated(): bool; + + /** + * Sets a new style. + */ + public function setStyle(string $name, OutputFormatterStyleInterface $style): void; + + /** + * Checks if output formatter has style with specified name. + */ + public function hasStyle(string $name): bool; + + /** + * Gets style options from style with specified name. + * + * @throws \InvalidArgumentException When style isn't defined + */ + public function getStyle(string $name): OutputFormatterStyleInterface; + + /** + * Formats a message according to the given styles. + */ + public function format(?string $message): ?string; +} diff --git a/vendor/symfony/console/Formatter/OutputFormatterStyle.php b/vendor/symfony/console/Formatter/OutputFormatterStyle.php new file mode 100644 index 00000000..20a65b51 --- /dev/null +++ b/vendor/symfony/console/Formatter/OutputFormatterStyle.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +use Symfony\Component\Console\Color; + +/** + * Formatter style class for defining styles. + * + * @author Konstantin Kudryashov + */ +class OutputFormatterStyle implements OutputFormatterStyleInterface +{ + private Color $color; + private string $foreground; + private string $background; + private array $options; + private ?string $href = null; + private bool $handlesHrefGracefully; + + /** + * Initializes output formatter style. + * + * @param string|null $foreground The style foreground color name + * @param string|null $background The style background color name + */ + public function __construct(?string $foreground = null, ?string $background = null, array $options = []) + { + $this->color = new Color($this->foreground = $foreground ?: '', $this->background = $background ?: '', $this->options = $options); + } + + public function setForeground(?string $color): void + { + $this->color = new Color($this->foreground = $color ?: '', $this->background, $this->options); + } + + public function setBackground(?string $color): void + { + $this->color = new Color($this->foreground, $this->background = $color ?: '', $this->options); + } + + public function setHref(string $url): void + { + $this->href = $url; + } + + public function setOption(string $option): void + { + $this->options[] = $option; + $this->color = new Color($this->foreground, $this->background, $this->options); + } + + public function unsetOption(string $option): void + { + $pos = array_search($option, $this->options); + if (false !== $pos) { + unset($this->options[$pos]); + } + + $this->color = new Color($this->foreground, $this->background, $this->options); + } + + public function setOptions(array $options): void + { + $this->color = new Color($this->foreground, $this->background, $this->options = $options); + } + + public function apply(string $text): string + { + $this->handlesHrefGracefully ??= 'JetBrains-JediTerm' !== getenv('TERMINAL_EMULATOR') + && (!getenv('KONSOLE_VERSION') || (int) getenv('KONSOLE_VERSION') > 201100) + && !isset($_SERVER['IDEA_INITIAL_DIRECTORY']); + + if (null !== $this->href && $this->handlesHrefGracefully) { + $text = "\033]8;;$this->href\033\\$text\033]8;;\033\\"; + } + + return $this->color->apply($text); + } +} diff --git a/vendor/symfony/console/Formatter/OutputFormatterStyleInterface.php b/vendor/symfony/console/Formatter/OutputFormatterStyleInterface.php new file mode 100644 index 00000000..03741927 --- /dev/null +++ b/vendor/symfony/console/Formatter/OutputFormatterStyleInterface.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +/** + * Formatter style interface for defining styles. + * + * @author Konstantin Kudryashov + */ +interface OutputFormatterStyleInterface +{ + /** + * Sets style foreground color. + */ + public function setForeground(?string $color): void; + + /** + * Sets style background color. + */ + public function setBackground(?string $color): void; + + /** + * Sets some specific style option. + */ + public function setOption(string $option): void; + + /** + * Unsets some specific style option. + */ + public function unsetOption(string $option): void; + + /** + * Sets multiple style options at once. + */ + public function setOptions(array $options): void; + + /** + * Applies the style to a given text. + */ + public function apply(string $text): string; +} diff --git a/vendor/symfony/console/Formatter/OutputFormatterStyleStack.php b/vendor/symfony/console/Formatter/OutputFormatterStyleStack.php new file mode 100644 index 00000000..4985213a --- /dev/null +++ b/vendor/symfony/console/Formatter/OutputFormatterStyleStack.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Contracts\Service\ResetInterface; + +/** + * @author Jean-François Simon + */ +class OutputFormatterStyleStack implements ResetInterface +{ + /** + * @var OutputFormatterStyleInterface[] + */ + private array $styles = []; + + private OutputFormatterStyleInterface $emptyStyle; + + public function __construct(?OutputFormatterStyleInterface $emptyStyle = null) + { + $this->emptyStyle = $emptyStyle ?? new OutputFormatterStyle(); + $this->reset(); + } + + /** + * Resets stack (ie. empty internal arrays). + */ + public function reset(): void + { + $this->styles = []; + } + + /** + * Pushes a style in the stack. + */ + public function push(OutputFormatterStyleInterface $style): void + { + $this->styles[] = $style; + } + + /** + * Pops a style from the stack. + * + * @throws InvalidArgumentException When style tags incorrectly nested + */ + public function pop(?OutputFormatterStyleInterface $style = null): OutputFormatterStyleInterface + { + if (!$this->styles) { + return $this->emptyStyle; + } + + if (null === $style) { + return array_pop($this->styles); + } + + foreach (array_reverse($this->styles, true) as $index => $stackedStyle) { + if ($style->apply('') === $stackedStyle->apply('')) { + $this->styles = \array_slice($this->styles, 0, $index); + + return $stackedStyle; + } + } + + throw new InvalidArgumentException('Incorrectly nested style tag found.'); + } + + /** + * Computes current style with stacks top codes. + */ + public function getCurrent(): OutputFormatterStyleInterface + { + if (!$this->styles) { + return $this->emptyStyle; + } + + return $this->styles[\count($this->styles) - 1]; + } + + /** + * @return $this + */ + public function setEmptyStyle(OutputFormatterStyleInterface $emptyStyle): static + { + $this->emptyStyle = $emptyStyle; + + return $this; + } + + public function getEmptyStyle(): OutputFormatterStyleInterface + { + return $this->emptyStyle; + } +} diff --git a/vendor/symfony/console/Formatter/WrappableOutputFormatterInterface.php b/vendor/symfony/console/Formatter/WrappableOutputFormatterInterface.php new file mode 100644 index 00000000..412d9976 --- /dev/null +++ b/vendor/symfony/console/Formatter/WrappableOutputFormatterInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +/** + * Formatter interface for console output that supports word wrapping. + * + * @author Roland Franssen + */ +interface WrappableOutputFormatterInterface extends OutputFormatterInterface +{ + /** + * Formats a message according to the given styles, wrapping at `$width` (0 means no wrapping). + */ + public function formatAndWrap(?string $message, int $width): string; +} diff --git a/vendor/symfony/console/Helper/DebugFormatterHelper.php b/vendor/symfony/console/Helper/DebugFormatterHelper.php new file mode 100644 index 00000000..9ea7fb91 --- /dev/null +++ b/vendor/symfony/console/Helper/DebugFormatterHelper.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +/** + * Helps outputting debug information when running an external program from a command. + * + * An external program can be a Process, an HTTP request, or anything else. + * + * @author Fabien Potencier + */ +class DebugFormatterHelper extends Helper +{ + private const COLORS = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'default']; + private array $started = []; + private int $count = -1; + + /** + * Starts a debug formatting session. + */ + public function start(string $id, string $message, string $prefix = 'RUN'): string + { + $this->started[$id] = ['border' => ++$this->count % \count(self::COLORS)]; + + return sprintf("%s %s %s\n", $this->getBorder($id), $prefix, $message); + } + + /** + * Adds progress to a formatting session. + */ + public function progress(string $id, string $buffer, bool $error = false, string $prefix = 'OUT', string $errorPrefix = 'ERR'): string + { + $message = ''; + + if ($error) { + if (isset($this->started[$id]['out'])) { + $message .= "\n"; + unset($this->started[$id]['out']); + } + if (!isset($this->started[$id]['err'])) { + $message .= sprintf('%s %s ', $this->getBorder($id), $errorPrefix); + $this->started[$id]['err'] = true; + } + + $message .= str_replace("\n", sprintf("\n%s %s ", $this->getBorder($id), $errorPrefix), $buffer); + } else { + if (isset($this->started[$id]['err'])) { + $message .= "\n"; + unset($this->started[$id]['err']); + } + if (!isset($this->started[$id]['out'])) { + $message .= sprintf('%s %s ', $this->getBorder($id), $prefix); + $this->started[$id]['out'] = true; + } + + $message .= str_replace("\n", sprintf("\n%s %s ", $this->getBorder($id), $prefix), $buffer); + } + + return $message; + } + + /** + * Stops a formatting session. + */ + public function stop(string $id, string $message, bool $successful, string $prefix = 'RES'): string + { + $trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : ''; + + if ($successful) { + return sprintf("%s%s %s %s\n", $trailingEOL, $this->getBorder($id), $prefix, $message); + } + + $message = sprintf("%s%s %s %s\n", $trailingEOL, $this->getBorder($id), $prefix, $message); + + unset($this->started[$id]['out'], $this->started[$id]['err']); + + return $message; + } + + private function getBorder(string $id): string + { + return sprintf(' ', self::COLORS[$this->started[$id]['border']]); + } + + public function getName(): string + { + return 'debug_formatter'; + } +} diff --git a/vendor/symfony/console/Helper/DescriptorHelper.php b/vendor/symfony/console/Helper/DescriptorHelper.php new file mode 100644 index 00000000..300c7b10 --- /dev/null +++ b/vendor/symfony/console/Helper/DescriptorHelper.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Descriptor\DescriptorInterface; +use Symfony\Component\Console\Descriptor\JsonDescriptor; +use Symfony\Component\Console\Descriptor\MarkdownDescriptor; +use Symfony\Component\Console\Descriptor\ReStructuredTextDescriptor; +use Symfony\Component\Console\Descriptor\TextDescriptor; +use Symfony\Component\Console\Descriptor\XmlDescriptor; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * This class adds helper method to describe objects in various formats. + * + * @author Jean-François Simon + */ +class DescriptorHelper extends Helper +{ + /** + * @var DescriptorInterface[] + */ + private array $descriptors = []; + + public function __construct() + { + $this + ->register('txt', new TextDescriptor()) + ->register('xml', new XmlDescriptor()) + ->register('json', new JsonDescriptor()) + ->register('md', new MarkdownDescriptor()) + ->register('rst', new ReStructuredTextDescriptor()) + ; + } + + /** + * Describes an object if supported. + * + * Available options are: + * * format: string, the output format name + * * raw_text: boolean, sets output type as raw + * + * @throws InvalidArgumentException when the given format is not supported + */ + public function describe(OutputInterface $output, ?object $object, array $options = []): void + { + $options = array_merge([ + 'raw_text' => false, + 'format' => 'txt', + ], $options); + + if (!isset($this->descriptors[$options['format']])) { + throw new InvalidArgumentException(sprintf('Unsupported format "%s".', $options['format'])); + } + + $descriptor = $this->descriptors[$options['format']]; + $descriptor->describe($output, $object, $options); + } + + /** + * Registers a descriptor. + * + * @return $this + */ + public function register(string $format, DescriptorInterface $descriptor): static + { + $this->descriptors[$format] = $descriptor; + + return $this; + } + + public function getName(): string + { + return 'descriptor'; + } + + public function getFormats(): array + { + return array_keys($this->descriptors); + } +} diff --git a/vendor/symfony/console/Helper/Dumper.php b/vendor/symfony/console/Helper/Dumper.php new file mode 100644 index 00000000..a3b8e395 --- /dev/null +++ b/vendor/symfony/console/Helper/Dumper.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\VarDumper\Cloner\ClonerInterface; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\CliDumper; + +/** + * @author Roland Franssen + */ +final class Dumper +{ + private OutputInterface $output; + private ?CliDumper $dumper; + private ?ClonerInterface $cloner; + private \Closure $handler; + + public function __construct(OutputInterface $output, ?CliDumper $dumper = null, ?ClonerInterface $cloner = null) + { + $this->output = $output; + $this->dumper = $dumper; + $this->cloner = $cloner; + + if (class_exists(CliDumper::class)) { + $this->handler = function ($var): string { + $dumper = $this->dumper ??= new CliDumper(null, null, CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR); + $dumper->setColors($this->output->isDecorated()); + + return rtrim($dumper->dump(($this->cloner ??= new VarCloner())->cloneVar($var)->withRefHandles(false), true)); + }; + } else { + $this->handler = fn ($var): string => match (true) { + null === $var => 'null', + true === $var => 'true', + false === $var => 'false', + \is_string($var) => '"'.$var.'"', + default => rtrim(print_r($var, true)), + }; + } + } + + public function __invoke(mixed $var): string + { + return ($this->handler)($var); + } +} diff --git a/vendor/symfony/console/Helper/FormatterHelper.php b/vendor/symfony/console/Helper/FormatterHelper.php new file mode 100644 index 00000000..279e4c79 --- /dev/null +++ b/vendor/symfony/console/Helper/FormatterHelper.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Formatter\OutputFormatter; + +/** + * The Formatter class provides helpers to format messages. + * + * @author Fabien Potencier + */ +class FormatterHelper extends Helper +{ + /** + * Formats a message within a section. + */ + public function formatSection(string $section, string $message, string $style = 'info'): string + { + return sprintf('<%s>[%s] %s', $style, $section, $style, $message); + } + + /** + * Formats a message as a block of text. + */ + public function formatBlock(string|array $messages, string $style, bool $large = false): string + { + if (!\is_array($messages)) { + $messages = [$messages]; + } + + $len = 0; + $lines = []; + foreach ($messages as $message) { + $message = OutputFormatter::escape($message); + $lines[] = sprintf($large ? ' %s ' : ' %s ', $message); + $len = max(self::width($message) + ($large ? 4 : 2), $len); + } + + $messages = $large ? [str_repeat(' ', $len)] : []; + for ($i = 0; isset($lines[$i]); ++$i) { + $messages[] = $lines[$i].str_repeat(' ', $len - self::width($lines[$i])); + } + if ($large) { + $messages[] = str_repeat(' ', $len); + } + + for ($i = 0; isset($messages[$i]); ++$i) { + $messages[$i] = sprintf('<%s>%s', $style, $messages[$i], $style); + } + + return implode("\n", $messages); + } + + /** + * Truncates a message to the given length. + */ + public function truncate(string $message, int $length, string $suffix = '...'): string + { + $computedLength = $length - self::width($suffix); + + if ($computedLength > self::width($message)) { + return $message; + } + + return self::substr($message, 0, $length).$suffix; + } + + public function getName(): string + { + return 'formatter'; + } +} diff --git a/vendor/symfony/console/Helper/Helper.php b/vendor/symfony/console/Helper/Helper.php new file mode 100644 index 00000000..de090063 --- /dev/null +++ b/vendor/symfony/console/Helper/Helper.php @@ -0,0 +1,159 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; +use Symfony\Component\String\UnicodeString; + +/** + * Helper is the base class for all helper classes. + * + * @author Fabien Potencier + */ +abstract class Helper implements HelperInterface +{ + protected ?HelperSet $helperSet = null; + + public function setHelperSet(?HelperSet $helperSet): void + { + $this->helperSet = $helperSet; + } + + public function getHelperSet(): ?HelperSet + { + return $this->helperSet; + } + + /** + * Returns the width of a string, using mb_strwidth if it is available. + * The width is how many characters positions the string will use. + */ + public static function width(?string $string): int + { + $string ??= ''; + + if (preg_match('//u', $string)) { + return (new UnicodeString($string))->width(false); + } + + if (false === $encoding = mb_detect_encoding($string, null, true)) { + return \strlen($string); + } + + return mb_strwidth($string, $encoding); + } + + /** + * Returns the length of a string, using mb_strlen if it is available. + * The length is related to how many bytes the string will use. + */ + public static function length(?string $string): int + { + $string ??= ''; + + if (preg_match('//u', $string)) { + return (new UnicodeString($string))->length(); + } + + if (false === $encoding = mb_detect_encoding($string, null, true)) { + return \strlen($string); + } + + return mb_strlen($string, $encoding); + } + + /** + * Returns the subset of a string, using mb_substr if it is available. + */ + public static function substr(?string $string, int $from, ?int $length = null): string + { + $string ??= ''; + + if (false === $encoding = mb_detect_encoding($string, null, true)) { + return substr($string, $from, $length); + } + + return mb_substr($string, $from, $length, $encoding); + } + + public static function formatTime(int|float $secs, int $precision = 1): string + { + $secs = (int) floor($secs); + + if (0 === $secs) { + return '< 1 sec'; + } + + static $timeFormats = [ + [1, '1 sec', 'secs'], + [60, '1 min', 'mins'], + [3600, '1 hr', 'hrs'], + [86400, '1 day', 'days'], + ]; + + $times = []; + foreach ($timeFormats as $index => $format) { + $seconds = isset($timeFormats[$index + 1]) ? $secs % $timeFormats[$index + 1][0] : $secs; + + if (isset($times[$index - $precision])) { + unset($times[$index - $precision]); + } + + if (0 === $seconds) { + continue; + } + + $unitCount = ($seconds / $format[0]); + $times[$index] = 1 === $unitCount ? $format[1] : $unitCount.' '.$format[2]; + + if ($secs === $seconds) { + break; + } + + $secs -= $seconds; + } + + return implode(', ', array_reverse($times)); + } + + public static function formatMemory(int $memory): string + { + if ($memory >= 1024 * 1024 * 1024) { + return sprintf('%.1f GiB', $memory / 1024 / 1024 / 1024); + } + + if ($memory >= 1024 * 1024) { + return sprintf('%.1f MiB', $memory / 1024 / 1024); + } + + if ($memory >= 1024) { + return sprintf('%d KiB', $memory / 1024); + } + + return sprintf('%d B', $memory); + } + + public static function removeDecoration(OutputFormatterInterface $formatter, ?string $string): string + { + $isDecorated = $formatter->isDecorated(); + $formatter->setDecorated(false); + // remove <...> formatting + $string = $formatter->format($string ?? ''); + // remove already formatted characters + $string = preg_replace("/\033\[[^m]*m/", '', $string ?? ''); + // remove terminal hyperlinks + $string = preg_replace('/\\033]8;[^;]*;[^\\033]*\\033\\\\/', '', $string ?? ''); + $formatter->setDecorated($isDecorated); + + return $string; + } +} diff --git a/vendor/symfony/console/Helper/HelperInterface.php b/vendor/symfony/console/Helper/HelperInterface.php new file mode 100644 index 00000000..8c4da3c9 --- /dev/null +++ b/vendor/symfony/console/Helper/HelperInterface.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +/** + * HelperInterface is the interface all helpers must implement. + * + * @author Fabien Potencier + */ +interface HelperInterface +{ + /** + * Sets the helper set associated with this helper. + */ + public function setHelperSet(?HelperSet $helperSet): void; + + /** + * Gets the helper set associated with this helper. + */ + public function getHelperSet(): ?HelperSet; + + /** + * Returns the canonical name of this helper. + */ + public function getName(): string; +} diff --git a/vendor/symfony/console/Helper/HelperSet.php b/vendor/symfony/console/Helper/HelperSet.php new file mode 100644 index 00000000..30df9f95 --- /dev/null +++ b/vendor/symfony/console/Helper/HelperSet.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * HelperSet represents a set of helpers to be used with a command. + * + * @author Fabien Potencier + * + * @implements \IteratorAggregate + */ +class HelperSet implements \IteratorAggregate +{ + /** @var array */ + private array $helpers = []; + + /** + * @param HelperInterface[] $helpers + */ + public function __construct(array $helpers = []) + { + foreach ($helpers as $alias => $helper) { + $this->set($helper, \is_int($alias) ? null : $alias); + } + } + + public function set(HelperInterface $helper, ?string $alias = null): void + { + $this->helpers[$helper->getName()] = $helper; + if (null !== $alias) { + $this->helpers[$alias] = $helper; + } + + $helper->setHelperSet($this); + } + + /** + * Returns true if the helper if defined. + */ + public function has(string $name): bool + { + return isset($this->helpers[$name]); + } + + /** + * Gets a helper value. + * + * @throws InvalidArgumentException if the helper is not defined + */ + public function get(string $name): HelperInterface + { + if (!$this->has($name)) { + throw new InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name)); + } + + return $this->helpers[$name]; + } + + public function getIterator(): \Traversable + { + return new \ArrayIterator($this->helpers); + } +} diff --git a/vendor/symfony/console/Helper/InputAwareHelper.php b/vendor/symfony/console/Helper/InputAwareHelper.php new file mode 100644 index 00000000..47126bda --- /dev/null +++ b/vendor/symfony/console/Helper/InputAwareHelper.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Input\InputAwareInterface; +use Symfony\Component\Console\Input\InputInterface; + +/** + * An implementation of InputAwareInterface for Helpers. + * + * @author Wouter J + */ +abstract class InputAwareHelper extends Helper implements InputAwareInterface +{ + protected InputInterface $input; + + public function setInput(InputInterface $input): void + { + $this->input = $input; + } +} diff --git a/vendor/symfony/console/Helper/OutputWrapper.php b/vendor/symfony/console/Helper/OutputWrapper.php new file mode 100644 index 00000000..2ec819c7 --- /dev/null +++ b/vendor/symfony/console/Helper/OutputWrapper.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +/** + * Simple output wrapper for "tagged outputs" instead of wordwrap(). This solution is based on a StackOverflow + * answer: https://stackoverflow.com/a/20434776/1476819 from user557597 (alias SLN). + * + * (?: + * # -- Words/Characters + * ( # (1 start) + * (?> # Atomic Group - Match words with valid breaks + * .{1,16} # 1-N characters + * # Followed by one of 4 prioritized, non-linebreak whitespace + * (?: # break types: + * (?<= [^\S\r\n] ) # 1. - Behind a non-linebreak whitespace + * [^\S\r\n]? # ( optionally accept an extra non-linebreak whitespace ) + * | (?= \r? \n ) # 2. - Ahead a linebreak + * | $ # 3. - EOS + * | [^\S\r\n] # 4. - Accept an extra non-linebreak whitespace + * ) + * ) # End atomic group + * | + * .{1,16} # No valid word breaks, just break on the N'th character + * ) # (1 end) + * (?: \r? \n )? # Optional linebreak after Words/Characters + * | + * # -- Or, Linebreak + * (?: \r? \n | $ ) # Stand alone linebreak or at EOS + * ) + * + * @author Krisztián Ferenczi + * + * @see https://stackoverflow.com/a/20434776/1476819 + */ +final class OutputWrapper +{ + private const TAG_OPEN_REGEX_SEGMENT = '[a-z](?:[^\\\\<>]*+ | \\\\.)*'; + private const TAG_CLOSE_REGEX_SEGMENT = '[a-z][^<>]*+'; + private const URL_PATTERN = 'https?://\S+'; + + public function __construct( + private bool $allowCutUrls = false + ) { + } + + public function wrap(string $text, int $width, string $break = "\n"): string + { + if (!$width) { + return $text; + } + + $tagPattern = sprintf('<(?:(?:%s)|/(?:%s)?)>', self::TAG_OPEN_REGEX_SEGMENT, self::TAG_CLOSE_REGEX_SEGMENT); + $limitPattern = "{1,$width}"; + $patternBlocks = [$tagPattern]; + if (!$this->allowCutUrls) { + $patternBlocks[] = self::URL_PATTERN; + } + $patternBlocks[] = '.'; + $blocks = implode('|', $patternBlocks); + $rowPattern = "(?:$blocks)$limitPattern"; + $pattern = sprintf('#(?:((?>(%1$s)((?<=[^\S\r\n])[^\S\r\n]?|(?=\r?\n)|$|[^\S\r\n]))|(%1$s))(?:\r?\n)?|(?:\r?\n|$))#imux', $rowPattern); + $output = rtrim(preg_replace($pattern, '\\1'.$break, $text), $break); + + return str_replace(' '.$break, $break, $output); + } +} diff --git a/vendor/symfony/console/Helper/ProcessHelper.php b/vendor/symfony/console/Helper/ProcessHelper.php new file mode 100644 index 00000000..3ef6f71f --- /dev/null +++ b/vendor/symfony/console/Helper/ProcessHelper.php @@ -0,0 +1,137 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Process\Exception\ProcessFailedException; +use Symfony\Component\Process\Process; + +/** + * The ProcessHelper class provides helpers to run external processes. + * + * @author Fabien Potencier + * + * @final + */ +class ProcessHelper extends Helper +{ + /** + * Runs an external process. + * + * @param array|Process $cmd An instance of Process or an array of the command and arguments + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + */ + public function run(OutputInterface $output, array|Process $cmd, ?string $error = null, ?callable $callback = null, int $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE): Process + { + if (!class_exists(Process::class)) { + throw new \LogicException('The ProcessHelper cannot be run as the Process component is not installed. Try running "compose require symfony/process".'); + } + + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + $formatter = $this->getHelperSet()->get('debug_formatter'); + + if ($cmd instanceof Process) { + $cmd = [$cmd]; + } + + if (\is_string($cmd[0] ?? null)) { + $process = new Process($cmd); + $cmd = []; + } elseif (($cmd[0] ?? null) instanceof Process) { + $process = $cmd[0]; + unset($cmd[0]); + } else { + throw new \InvalidArgumentException(sprintf('Invalid command provided to "%s()": the command should be an array whose first element is either the path to the binary to run or a "Process" object.', __METHOD__)); + } + + if ($verbosity <= $output->getVerbosity()) { + $output->write($formatter->start(spl_object_hash($process), $this->escapeString($process->getCommandLine()))); + } + + if ($output->isDebug()) { + $callback = $this->wrapCallback($output, $process, $callback); + } + + $process->run($callback, $cmd); + + if ($verbosity <= $output->getVerbosity()) { + $message = $process->isSuccessful() ? 'Command ran successfully' : sprintf('%s Command did not run successfully', $process->getExitCode()); + $output->write($formatter->stop(spl_object_hash($process), $message, $process->isSuccessful())); + } + + if (!$process->isSuccessful() && null !== $error) { + $output->writeln(sprintf('%s', $this->escapeString($error))); + } + + return $process; + } + + /** + * Runs the process. + * + * This is identical to run() except that an exception is thrown if the process + * exits with a non-zero exit code. + * + * @param array|Process $cmd An instance of Process or a command to run + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * + * @throws ProcessFailedException + * + * @see run() + */ + public function mustRun(OutputInterface $output, array|Process $cmd, ?string $error = null, ?callable $callback = null): Process + { + $process = $this->run($output, $cmd, $error, $callback); + + if (!$process->isSuccessful()) { + throw new ProcessFailedException($process); + } + + return $process; + } + + /** + * Wraps a Process callback to add debugging output. + */ + public function wrapCallback(OutputInterface $output, Process $process, ?callable $callback = null): callable + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + $formatter = $this->getHelperSet()->get('debug_formatter'); + + return function ($type, $buffer) use ($output, $process, $callback, $formatter) { + $output->write($formatter->progress(spl_object_hash($process), $this->escapeString($buffer), Process::ERR === $type)); + + if (null !== $callback) { + $callback($type, $buffer); + } + }; + } + + private function escapeString(string $str): string + { + return str_replace('<', '\\<', $str); + } + + public function getName(): string + { + return 'process'; + } +} diff --git a/vendor/symfony/console/Helper/ProgressBar.php b/vendor/symfony/console/Helper/ProgressBar.php new file mode 100644 index 00000000..f4eec051 --- /dev/null +++ b/vendor/symfony/console/Helper/ProgressBar.php @@ -0,0 +1,618 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Cursor; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\ConsoleSectionOutput; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Terminal; + +/** + * The ProgressBar provides helpers to display progress output. + * + * @author Fabien Potencier + * @author Chris Jones + */ +final class ProgressBar +{ + public const FORMAT_VERBOSE = 'verbose'; + public const FORMAT_VERY_VERBOSE = 'very_verbose'; + public const FORMAT_DEBUG = 'debug'; + public const FORMAT_NORMAL = 'normal'; + + private const FORMAT_VERBOSE_NOMAX = 'verbose_nomax'; + private const FORMAT_VERY_VERBOSE_NOMAX = 'very_verbose_nomax'; + private const FORMAT_DEBUG_NOMAX = 'debug_nomax'; + private const FORMAT_NORMAL_NOMAX = 'normal_nomax'; + + private int $barWidth = 28; + private string $barChar; + private string $emptyBarChar = '-'; + private string $progressChar = '>'; + private ?string $format = null; + private ?string $internalFormat = null; + private ?int $redrawFreq = 1; + private int $writeCount = 0; + private float $lastWriteTime = 0; + private float $minSecondsBetweenRedraws = 0; + private float $maxSecondsBetweenRedraws = 1; + private OutputInterface $output; + private int $step = 0; + private int $startingStep = 0; + private ?int $max = null; + private int $startTime; + private int $stepWidth; + private float $percent = 0.0; + private array $messages = []; + private bool $overwrite = true; + private Terminal $terminal; + private ?string $previousMessage = null; + private Cursor $cursor; + private array $placeholders = []; + + private static array $formatters; + private static array $formats; + + /** + * @param int $max Maximum steps (0 if unknown) + */ + public function __construct(OutputInterface $output, int $max = 0, float $minSecondsBetweenRedraws = 1 / 25) + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + $this->output = $output; + $this->setMaxSteps($max); + $this->terminal = new Terminal(); + + if (0 < $minSecondsBetweenRedraws) { + $this->redrawFreq = null; + $this->minSecondsBetweenRedraws = $minSecondsBetweenRedraws; + } + + if (!$this->output->isDecorated()) { + // disable overwrite when output does not support ANSI codes. + $this->overwrite = false; + + // set a reasonable redraw frequency so output isn't flooded + $this->redrawFreq = null; + } + + $this->startTime = time(); + $this->cursor = new Cursor($output); + } + + /** + * Sets a placeholder formatter for a given name, globally for all instances of ProgressBar. + * + * This method also allow you to override an existing placeholder. + * + * @param string $name The placeholder name (including the delimiter char like %) + * @param callable(ProgressBar):string $callable A PHP callable + */ + public static function setPlaceholderFormatterDefinition(string $name, callable $callable): void + { + self::$formatters ??= self::initPlaceholderFormatters(); + + self::$formatters[$name] = $callable; + } + + /** + * Gets the placeholder formatter for a given name. + * + * @param string $name The placeholder name (including the delimiter char like %) + */ + public static function getPlaceholderFormatterDefinition(string $name): ?callable + { + self::$formatters ??= self::initPlaceholderFormatters(); + + return self::$formatters[$name] ?? null; + } + + /** + * Sets a placeholder formatter for a given name, for this instance only. + * + * @param callable(ProgressBar):string $callable A PHP callable + */ + public function setPlaceholderFormatter(string $name, callable $callable): void + { + $this->placeholders[$name] = $callable; + } + + /** + * Gets the placeholder formatter for a given name. + * + * @param string $name The placeholder name (including the delimiter char like %) + */ + public function getPlaceholderFormatter(string $name): ?callable + { + return $this->placeholders[$name] ?? $this::getPlaceholderFormatterDefinition($name); + } + + /** + * Sets a format for a given name. + * + * This method also allow you to override an existing format. + * + * @param string $name The format name + * @param string $format A format string + */ + public static function setFormatDefinition(string $name, string $format): void + { + self::$formats ??= self::initFormats(); + + self::$formats[$name] = $format; + } + + /** + * Gets the format for a given name. + * + * @param string $name The format name + */ + public static function getFormatDefinition(string $name): ?string + { + self::$formats ??= self::initFormats(); + + return self::$formats[$name] ?? null; + } + + /** + * Associates a text with a named placeholder. + * + * The text is displayed when the progress bar is rendered but only + * when the corresponding placeholder is part of the custom format line + * (by wrapping the name with %). + * + * @param string $message The text to associate with the placeholder + * @param string $name The name of the placeholder + */ + public function setMessage(string $message, string $name = 'message'): void + { + $this->messages[$name] = $message; + } + + public function getMessage(string $name = 'message'): string + { + return $this->messages[$name]; + } + + public function getStartTime(): int + { + return $this->startTime; + } + + public function getMaxSteps(): int + { + return $this->max; + } + + public function getProgress(): int + { + return $this->step; + } + + private function getStepWidth(): int + { + return $this->stepWidth; + } + + public function getProgressPercent(): float + { + return $this->percent; + } + + public function getBarOffset(): float + { + return floor($this->max ? $this->percent * $this->barWidth : (null === $this->redrawFreq ? (int) (min(5, $this->barWidth / 15) * $this->writeCount) : $this->step) % $this->barWidth); + } + + public function getEstimated(): float + { + if (0 === $this->step || $this->step === $this->startingStep) { + return 0; + } + + return round((time() - $this->startTime) / ($this->step - $this->startingStep) * $this->max); + } + + public function getRemaining(): float + { + if (!$this->step) { + return 0; + } + + return round((time() - $this->startTime) / ($this->step - $this->startingStep) * ($this->max - $this->step)); + } + + public function setBarWidth(int $size): void + { + $this->barWidth = max(1, $size); + } + + public function getBarWidth(): int + { + return $this->barWidth; + } + + public function setBarCharacter(string $char): void + { + $this->barChar = $char; + } + + public function getBarCharacter(): string + { + return $this->barChar ?? ($this->max ? '=' : $this->emptyBarChar); + } + + public function setEmptyBarCharacter(string $char): void + { + $this->emptyBarChar = $char; + } + + public function getEmptyBarCharacter(): string + { + return $this->emptyBarChar; + } + + public function setProgressCharacter(string $char): void + { + $this->progressChar = $char; + } + + public function getProgressCharacter(): string + { + return $this->progressChar; + } + + public function setFormat(string $format): void + { + $this->format = null; + $this->internalFormat = $format; + } + + /** + * Sets the redraw frequency. + * + * @param int|null $freq The frequency in steps + */ + public function setRedrawFrequency(?int $freq): void + { + $this->redrawFreq = null !== $freq ? max(1, $freq) : null; + } + + public function minSecondsBetweenRedraws(float $seconds): void + { + $this->minSecondsBetweenRedraws = $seconds; + } + + public function maxSecondsBetweenRedraws(float $seconds): void + { + $this->maxSecondsBetweenRedraws = $seconds; + } + + /** + * Returns an iterator that will automatically update the progress bar when iterated. + * + * @template TKey + * @template TValue + * + * @param iterable $iterable + * @param int|null $max Number of steps to complete the bar (0 if indeterminate), if null it will be inferred from $iterable + * + * @return iterable + */ + public function iterate(iterable $iterable, ?int $max = null): iterable + { + $this->start($max ?? (is_countable($iterable) ? \count($iterable) : 0)); + + foreach ($iterable as $key => $value) { + yield $key => $value; + + $this->advance(); + } + + $this->finish(); + } + + /** + * Starts the progress output. + * + * @param int|null $max Number of steps to complete the bar (0 if indeterminate), null to leave unchanged + * @param int $startAt The starting point of the bar (useful e.g. when resuming a previously started bar) + */ + public function start(?int $max = null, int $startAt = 0): void + { + $this->startTime = time(); + $this->step = $startAt; + $this->startingStep = $startAt; + + $startAt > 0 ? $this->setProgress($startAt) : $this->percent = 0.0; + + if (null !== $max) { + $this->setMaxSteps($max); + } + + $this->display(); + } + + /** + * Advances the progress output X steps. + * + * @param int $step Number of steps to advance + */ + public function advance(int $step = 1): void + { + $this->setProgress($this->step + $step); + } + + /** + * Sets whether to overwrite the progressbar, false for new line. + */ + public function setOverwrite(bool $overwrite): void + { + $this->overwrite = $overwrite; + } + + public function setProgress(int $step): void + { + if ($this->max && $step > $this->max) { + $this->max = $step; + } elseif ($step < 0) { + $step = 0; + } + + $redrawFreq = $this->redrawFreq ?? (($this->max ?: 10) / 10); + $prevPeriod = (int) ($this->step / $redrawFreq); + $currPeriod = (int) ($step / $redrawFreq); + $this->step = $step; + $this->percent = $this->max ? (float) $this->step / $this->max : 0; + $timeInterval = microtime(true) - $this->lastWriteTime; + + // Draw regardless of other limits + if ($this->max === $step) { + $this->display(); + + return; + } + + // Throttling + if ($timeInterval < $this->minSecondsBetweenRedraws) { + return; + } + + // Draw each step period, but not too late + if ($prevPeriod !== $currPeriod || $timeInterval >= $this->maxSecondsBetweenRedraws) { + $this->display(); + } + } + + public function setMaxSteps(int $max): void + { + $this->format = null; + $this->max = max(0, $max); + $this->stepWidth = $this->max ? Helper::width((string) $this->max) : 4; + } + + /** + * Finishes the progress output. + */ + public function finish(): void + { + if (!$this->max) { + $this->max = $this->step; + } + + if ($this->step === $this->max && !$this->overwrite) { + // prevent double 100% output + return; + } + + $this->setProgress($this->max); + } + + /** + * Outputs the current progress string. + */ + public function display(): void + { + if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) { + return; + } + + if (null === $this->format) { + $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat()); + } + + $this->overwrite($this->buildLine()); + } + + /** + * Removes the progress bar from the current line. + * + * This is useful if you wish to write some output + * while a progress bar is running. + * Call display() to show the progress bar again. + */ + public function clear(): void + { + if (!$this->overwrite) { + return; + } + + if (null === $this->format) { + $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat()); + } + + $this->overwrite(''); + } + + private function setRealFormat(string $format): void + { + // try to use the _nomax variant if available + if (!$this->max && null !== self::getFormatDefinition($format.'_nomax')) { + $this->format = self::getFormatDefinition($format.'_nomax'); + } elseif (null !== self::getFormatDefinition($format)) { + $this->format = self::getFormatDefinition($format); + } else { + $this->format = $format; + } + } + + /** + * Overwrites a previous message to the output. + */ + private function overwrite(string $message): void + { + if ($this->previousMessage === $message) { + return; + } + + $originalMessage = $message; + + if ($this->overwrite) { + if (null !== $this->previousMessage) { + if ($this->output instanceof ConsoleSectionOutput) { + $messageLines = explode("\n", $this->previousMessage); + $lineCount = \count($messageLines); + foreach ($messageLines as $messageLine) { + $messageLineLength = Helper::width(Helper::removeDecoration($this->output->getFormatter(), $messageLine)); + if ($messageLineLength > $this->terminal->getWidth()) { + $lineCount += floor($messageLineLength / $this->terminal->getWidth()); + } + } + $this->output->clear($lineCount); + } else { + $lineCount = substr_count($this->previousMessage, "\n"); + for ($i = 0; $i < $lineCount; ++$i) { + $this->cursor->moveToColumn(1); + $this->cursor->clearLine(); + $this->cursor->moveUp(); + } + + $this->cursor->moveToColumn(1); + $this->cursor->clearLine(); + } + } + } elseif ($this->step > 0) { + $message = \PHP_EOL.$message; + } + + $this->previousMessage = $originalMessage; + $this->lastWriteTime = microtime(true); + + $this->output->write($message); + ++$this->writeCount; + } + + private function determineBestFormat(): string + { + return match ($this->output->getVerbosity()) { + // OutputInterface::VERBOSITY_QUIET: display is disabled anyway + OutputInterface::VERBOSITY_VERBOSE => $this->max ? self::FORMAT_VERBOSE : self::FORMAT_VERBOSE_NOMAX, + OutputInterface::VERBOSITY_VERY_VERBOSE => $this->max ? self::FORMAT_VERY_VERBOSE : self::FORMAT_VERY_VERBOSE_NOMAX, + OutputInterface::VERBOSITY_DEBUG => $this->max ? self::FORMAT_DEBUG : self::FORMAT_DEBUG_NOMAX, + default => $this->max ? self::FORMAT_NORMAL : self::FORMAT_NORMAL_NOMAX, + }; + } + + private static function initPlaceholderFormatters(): array + { + return [ + 'bar' => function (self $bar, OutputInterface $output) { + $completeBars = $bar->getBarOffset(); + $display = str_repeat($bar->getBarCharacter(), $completeBars); + if ($completeBars < $bar->getBarWidth()) { + $emptyBars = $bar->getBarWidth() - $completeBars - Helper::length(Helper::removeDecoration($output->getFormatter(), $bar->getProgressCharacter())); + $display .= $bar->getProgressCharacter().str_repeat($bar->getEmptyBarCharacter(), $emptyBars); + } + + return $display; + }, + 'elapsed' => fn (self $bar) => Helper::formatTime(time() - $bar->getStartTime(), 2), + 'remaining' => function (self $bar) { + if (!$bar->getMaxSteps()) { + throw new LogicException('Unable to display the remaining time if the maximum number of steps is not set.'); + } + + return Helper::formatTime($bar->getRemaining(), 2); + }, + 'estimated' => function (self $bar) { + if (!$bar->getMaxSteps()) { + throw new LogicException('Unable to display the estimated time if the maximum number of steps is not set.'); + } + + return Helper::formatTime($bar->getEstimated(), 2); + }, + 'memory' => fn (self $bar) => Helper::formatMemory(memory_get_usage(true)), + 'current' => fn (self $bar) => str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', \STR_PAD_LEFT), + 'max' => fn (self $bar) => $bar->getMaxSteps(), + 'percent' => fn (self $bar) => floor($bar->getProgressPercent() * 100), + ]; + } + + private static function initFormats(): array + { + return [ + self::FORMAT_NORMAL => ' %current%/%max% [%bar%] %percent:3s%%', + self::FORMAT_NORMAL_NOMAX => ' %current% [%bar%]', + + self::FORMAT_VERBOSE => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%', + self::FORMAT_VERBOSE_NOMAX => ' %current% [%bar%] %elapsed:6s%', + + self::FORMAT_VERY_VERBOSE => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%', + self::FORMAT_VERY_VERBOSE_NOMAX => ' %current% [%bar%] %elapsed:6s%', + + self::FORMAT_DEBUG => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%', + self::FORMAT_DEBUG_NOMAX => ' %current% [%bar%] %elapsed:6s% %memory:6s%', + ]; + } + + private function buildLine(): string + { + \assert(null !== $this->format); + + $regex = "{%([a-z\-_]+)(?:\:([^%]+))?%}i"; + $callback = function ($matches) { + if ($formatter = $this->getPlaceholderFormatter($matches[1])) { + $text = $formatter($this, $this->output); + } elseif (isset($this->messages[$matches[1]])) { + $text = $this->messages[$matches[1]]; + } else { + return $matches[0]; + } + + if (isset($matches[2])) { + $text = sprintf('%'.$matches[2], $text); + } + + return $text; + }; + $line = preg_replace_callback($regex, $callback, $this->format); + + // gets string length for each sub line with multiline format + $linesLength = array_map(fn ($subLine) => Helper::width(Helper::removeDecoration($this->output->getFormatter(), rtrim($subLine, "\r"))), explode("\n", $line)); + + $linesWidth = max($linesLength); + + $terminalWidth = $this->terminal->getWidth(); + if ($linesWidth <= $terminalWidth) { + return $line; + } + + $this->setBarWidth($this->barWidth - $linesWidth + $terminalWidth); + + return preg_replace_callback($regex, $callback, $this->format); + } +} diff --git a/vendor/symfony/console/Helper/ProgressIndicator.php b/vendor/symfony/console/Helper/ProgressIndicator.php new file mode 100644 index 00000000..2ffac2ec --- /dev/null +++ b/vendor/symfony/console/Helper/ProgressIndicator.php @@ -0,0 +1,225 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Kevin Bond + */ +class ProgressIndicator +{ + private const FORMATS = [ + 'normal' => ' %indicator% %message%', + 'normal_no_ansi' => ' %message%', + + 'verbose' => ' %indicator% %message% (%elapsed:6s%)', + 'verbose_no_ansi' => ' %message% (%elapsed:6s%)', + + 'very_verbose' => ' %indicator% %message% (%elapsed:6s%, %memory:6s%)', + 'very_verbose_no_ansi' => ' %message% (%elapsed:6s%, %memory:6s%)', + ]; + + private OutputInterface $output; + private int $startTime; + private ?string $format = null; + private ?string $message = null; + private array $indicatorValues; + private int $indicatorCurrent; + private int $indicatorChangeInterval; + private float $indicatorUpdateTime; + private bool $started = false; + + /** + * @var array + */ + private static array $formatters; + + /** + * @param int $indicatorChangeInterval Change interval in milliseconds + * @param array|null $indicatorValues Animated indicator characters + */ + public function __construct(OutputInterface $output, ?string $format = null, int $indicatorChangeInterval = 100, ?array $indicatorValues = null) + { + $this->output = $output; + + $format ??= $this->determineBestFormat(); + $indicatorValues ??= ['-', '\\', '|', '/']; + $indicatorValues = array_values($indicatorValues); + + if (2 > \count($indicatorValues)) { + throw new InvalidArgumentException('Must have at least 2 indicator value characters.'); + } + + $this->format = self::getFormatDefinition($format); + $this->indicatorChangeInterval = $indicatorChangeInterval; + $this->indicatorValues = $indicatorValues; + $this->startTime = time(); + } + + /** + * Sets the current indicator message. + */ + public function setMessage(?string $message): void + { + $this->message = $message; + + $this->display(); + } + + /** + * Starts the indicator output. + */ + public function start(string $message): void + { + if ($this->started) { + throw new LogicException('Progress indicator already started.'); + } + + $this->message = $message; + $this->started = true; + $this->startTime = time(); + $this->indicatorUpdateTime = $this->getCurrentTimeInMilliseconds() + $this->indicatorChangeInterval; + $this->indicatorCurrent = 0; + + $this->display(); + } + + /** + * Advances the indicator. + */ + public function advance(): void + { + if (!$this->started) { + throw new LogicException('Progress indicator has not yet been started.'); + } + + if (!$this->output->isDecorated()) { + return; + } + + $currentTime = $this->getCurrentTimeInMilliseconds(); + + if ($currentTime < $this->indicatorUpdateTime) { + return; + } + + $this->indicatorUpdateTime = $currentTime + $this->indicatorChangeInterval; + ++$this->indicatorCurrent; + + $this->display(); + } + + /** + * Finish the indicator with message. + */ + public function finish(string $message): void + { + if (!$this->started) { + throw new LogicException('Progress indicator has not yet been started.'); + } + + $this->message = $message; + $this->display(); + $this->output->writeln(''); + $this->started = false; + } + + /** + * Gets the format for a given name. + */ + public static function getFormatDefinition(string $name): ?string + { + return self::FORMATS[$name] ?? null; + } + + /** + * Sets a placeholder formatter for a given name. + * + * This method also allow you to override an existing placeholder. + */ + public static function setPlaceholderFormatterDefinition(string $name, callable $callable): void + { + self::$formatters ??= self::initPlaceholderFormatters(); + + self::$formatters[$name] = $callable; + } + + /** + * Gets the placeholder formatter for a given name (including the delimiter char like %). + */ + public static function getPlaceholderFormatterDefinition(string $name): ?callable + { + self::$formatters ??= self::initPlaceholderFormatters(); + + return self::$formatters[$name] ?? null; + } + + private function display(): void + { + if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) { + return; + } + + $this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) { + if ($formatter = self::getPlaceholderFormatterDefinition($matches[1])) { + return $formatter($this); + } + + return $matches[0]; + }, $this->format ?? '')); + } + + private function determineBestFormat(): string + { + return match ($this->output->getVerbosity()) { + // OutputInterface::VERBOSITY_QUIET: display is disabled anyway + OutputInterface::VERBOSITY_VERBOSE => $this->output->isDecorated() ? 'verbose' : 'verbose_no_ansi', + OutputInterface::VERBOSITY_VERY_VERBOSE, + OutputInterface::VERBOSITY_DEBUG => $this->output->isDecorated() ? 'very_verbose' : 'very_verbose_no_ansi', + default => $this->output->isDecorated() ? 'normal' : 'normal_no_ansi', + }; + } + + /** + * Overwrites a previous message to the output. + */ + private function overwrite(string $message): void + { + if ($this->output->isDecorated()) { + $this->output->write("\x0D\x1B[2K"); + $this->output->write($message); + } else { + $this->output->writeln($message); + } + } + + private function getCurrentTimeInMilliseconds(): float + { + return round(microtime(true) * 1000); + } + + /** + * @return array + */ + private static function initPlaceholderFormatters(): array + { + return [ + 'indicator' => fn (self $indicator) => $indicator->indicatorValues[$indicator->indicatorCurrent % \count($indicator->indicatorValues)], + 'message' => fn (self $indicator) => $indicator->message, + 'elapsed' => fn (self $indicator) => Helper::formatTime(time() - $indicator->startTime, 2), + 'memory' => fn () => Helper::formatMemory(memory_get_usage(true)), + ]; + } +} diff --git a/vendor/symfony/console/Helper/QuestionHelper.php b/vendor/symfony/console/Helper/QuestionHelper.php new file mode 100644 index 00000000..f66d7bc5 --- /dev/null +++ b/vendor/symfony/console/Helper/QuestionHelper.php @@ -0,0 +1,594 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Cursor; +use Symfony\Component\Console\Exception\MissingInputException; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Formatter\OutputFormatterStyle; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\StreamableInputInterface; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\ConsoleSectionOutput; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ChoiceQuestion; +use Symfony\Component\Console\Question\Question; +use Symfony\Component\Console\Terminal; + +use function Symfony\Component\String\s; + +/** + * The QuestionHelper class provides helpers to interact with the user. + * + * @author Fabien Potencier + */ +class QuestionHelper extends Helper +{ + /** + * @var resource|null + */ + private $inputStream; + + private static bool $stty = true; + private static bool $stdinIsInteractive; + + /** + * Asks a question to the user. + * + * @return mixed The user answer + * + * @throws RuntimeException If there is no data to read in the input stream + */ + public function ask(InputInterface $input, OutputInterface $output, Question $question): mixed + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + if (!$input->isInteractive()) { + return $this->getDefaultAnswer($question); + } + + if ($input instanceof StreamableInputInterface && $stream = $input->getStream()) { + $this->inputStream = $stream; + } + + try { + if (!$question->getValidator()) { + return $this->doAsk($output, $question); + } + + $interviewer = fn () => $this->doAsk($output, $question); + + return $this->validateAttempts($interviewer, $output, $question); + } catch (MissingInputException $exception) { + $input->setInteractive(false); + + if (null === $fallbackOutput = $this->getDefaultAnswer($question)) { + throw $exception; + } + + return $fallbackOutput; + } + } + + public function getName(): string + { + return 'question'; + } + + /** + * Prevents usage of stty. + */ + public static function disableStty(): void + { + self::$stty = false; + } + + /** + * Asks the question to the user. + * + * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden + */ + private function doAsk(OutputInterface $output, Question $question): mixed + { + $this->writePrompt($output, $question); + + $inputStream = $this->inputStream ?: \STDIN; + $autocomplete = $question->getAutocompleterCallback(); + + if (null === $autocomplete || !self::$stty || !Terminal::hasSttyAvailable()) { + $ret = false; + if ($question->isHidden()) { + try { + $hiddenResponse = $this->getHiddenResponse($output, $inputStream, $question->isTrimmable()); + $ret = $question->isTrimmable() ? trim($hiddenResponse) : $hiddenResponse; + } catch (RuntimeException $e) { + if (!$question->isHiddenFallback()) { + throw $e; + } + } + } + + if (false === $ret) { + $isBlocked = stream_get_meta_data($inputStream)['blocked'] ?? true; + + if (!$isBlocked) { + stream_set_blocking($inputStream, true); + } + + $ret = $this->readInput($inputStream, $question); + + if (!$isBlocked) { + stream_set_blocking($inputStream, false); + } + + if (false === $ret) { + throw new MissingInputException('Aborted.'); + } + if ($question->isTrimmable()) { + $ret = trim($ret); + } + } + } else { + $autocomplete = $this->autocomplete($output, $question, $inputStream, $autocomplete); + $ret = $question->isTrimmable() ? trim($autocomplete) : $autocomplete; + } + + if ($output instanceof ConsoleSectionOutput) { + $output->addContent(''); // add EOL to the question + $output->addContent($ret); + } + + $ret = \strlen($ret) > 0 ? $ret : $question->getDefault(); + + if ($normalizer = $question->getNormalizer()) { + return $normalizer($ret); + } + + return $ret; + } + + private function getDefaultAnswer(Question $question): mixed + { + $default = $question->getDefault(); + + if (null === $default) { + return $default; + } + + if ($validator = $question->getValidator()) { + return \call_user_func($validator, $default); + } elseif ($question instanceof ChoiceQuestion) { + $choices = $question->getChoices(); + + if (!$question->isMultiselect()) { + return $choices[$default] ?? $default; + } + + $default = explode(',', $default); + foreach ($default as $k => $v) { + $v = $question->isTrimmable() ? trim($v) : $v; + $default[$k] = $choices[$v] ?? $v; + } + } + + return $default; + } + + /** + * Outputs the question prompt. + */ + protected function writePrompt(OutputInterface $output, Question $question): void + { + $message = $question->getQuestion(); + + if ($question instanceof ChoiceQuestion) { + $output->writeln(array_merge([ + $question->getQuestion(), + ], $this->formatChoiceQuestionChoices($question, 'info'))); + + $message = $question->getPrompt(); + } + + $output->write($message); + } + + /** + * @return string[] + */ + protected function formatChoiceQuestionChoices(ChoiceQuestion $question, string $tag): array + { + $messages = []; + + $maxWidth = max(array_map([__CLASS__, 'width'], array_keys($choices = $question->getChoices()))); + + foreach ($choices as $key => $value) { + $padding = str_repeat(' ', $maxWidth - self::width($key)); + + $messages[] = sprintf(" [<$tag>%s$padding] %s", $key, $value); + } + + return $messages; + } + + /** + * Outputs an error message. + */ + protected function writeError(OutputInterface $output, \Exception $error): void + { + if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) { + $message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error'); + } else { + $message = ''.$error->getMessage().''; + } + + $output->writeln($message); + } + + /** + * Autocompletes a question. + * + * @param resource $inputStream + */ + private function autocomplete(OutputInterface $output, Question $question, $inputStream, callable $autocomplete): string + { + $cursor = new Cursor($output, $inputStream); + + $fullChoice = ''; + $ret = ''; + + $i = 0; + $ofs = -1; + $matches = $autocomplete($ret); + $numMatches = \count($matches); + + $sttyMode = shell_exec('stty -g'); + $isStdin = 'php://stdin' === (stream_get_meta_data($inputStream)['uri'] ?? null); + $r = [$inputStream]; + $w = []; + + // Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead) + shell_exec('stty -icanon -echo'); + + // Add highlighted text style + $output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white')); + + // Read a keypress + while (!feof($inputStream)) { + while ($isStdin && 0 === @stream_select($r, $w, $w, 0, 100)) { + // Give signal handlers a chance to run + $r = [$inputStream]; + } + $c = fread($inputStream, 1); + + // as opposed to fgets(), fread() returns an empty string when the stream content is empty, not false. + if (false === $c || ('' === $ret && '' === $c && null === $question->getDefault())) { + shell_exec('stty '.$sttyMode); + throw new MissingInputException('Aborted.'); + } elseif ("\177" === $c) { // Backspace Character + if (0 === $numMatches && 0 !== $i) { + --$i; + $cursor->moveLeft(s($fullChoice)->slice(-1)->width(false)); + + $fullChoice = self::substr($fullChoice, 0, $i); + } + + if (0 === $i) { + $ofs = -1; + $matches = $autocomplete($ret); + $numMatches = \count($matches); + } else { + $numMatches = 0; + } + + // Pop the last character off the end of our string + $ret = self::substr($ret, 0, $i); + } elseif ("\033" === $c) { + // Did we read an escape sequence? + $c .= fread($inputStream, 2); + + // A = Up Arrow. B = Down Arrow + if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) { + if ('A' === $c[2] && -1 === $ofs) { + $ofs = 0; + } + + if (0 === $numMatches) { + continue; + } + + $ofs += ('A' === $c[2]) ? -1 : 1; + $ofs = ($numMatches + $ofs) % $numMatches; + } + } elseif (\ord($c) < 32) { + if ("\t" === $c || "\n" === $c) { + if ($numMatches > 0 && -1 !== $ofs) { + $ret = (string) $matches[$ofs]; + // Echo out remaining chars for current match + $remainingCharacters = substr($ret, \strlen(trim($this->mostRecentlyEnteredValue($fullChoice)))); + $output->write($remainingCharacters); + $fullChoice .= $remainingCharacters; + $i = (false === $encoding = mb_detect_encoding($fullChoice, null, true)) ? \strlen($fullChoice) : mb_strlen($fullChoice, $encoding); + + $matches = array_filter( + $autocomplete($ret), + fn ($match) => '' === $ret || str_starts_with($match, $ret) + ); + $numMatches = \count($matches); + $ofs = -1; + } + + if ("\n" === $c) { + $output->write($c); + break; + } + + $numMatches = 0; + } + + continue; + } else { + if ("\x80" <= $c) { + $c .= fread($inputStream, ["\xC0" => 1, "\xD0" => 1, "\xE0" => 2, "\xF0" => 3][$c & "\xF0"]); + } + + $output->write($c); + $ret .= $c; + $fullChoice .= $c; + ++$i; + + $tempRet = $ret; + + if ($question instanceof ChoiceQuestion && $question->isMultiselect()) { + $tempRet = $this->mostRecentlyEnteredValue($fullChoice); + } + + $numMatches = 0; + $ofs = 0; + + foreach ($autocomplete($ret) as $value) { + // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle) + if (str_starts_with($value, $tempRet)) { + $matches[$numMatches++] = $value; + } + } + } + + $cursor->clearLineAfter(); + + if ($numMatches > 0 && -1 !== $ofs) { + $cursor->savePosition(); + // Write highlighted text, complete the partially entered response + $charactersEntered = \strlen(trim($this->mostRecentlyEnteredValue($fullChoice))); + $output->write(''.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $charactersEntered)).''); + $cursor->restorePosition(); + } + } + + // Reset stty so it behaves normally again + shell_exec('stty '.$sttyMode); + + return $fullChoice; + } + + private function mostRecentlyEnteredValue(string $entered): string + { + // Determine the most recent value that the user entered + if (!str_contains($entered, ',')) { + return $entered; + } + + $choices = explode(',', $entered); + if ('' !== $lastChoice = trim($choices[\count($choices) - 1])) { + return $lastChoice; + } + + return $entered; + } + + /** + * Gets a hidden response from user. + * + * @param resource $inputStream The handler resource + * @param bool $trimmable Is the answer trimmable + * + * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden + */ + private function getHiddenResponse(OutputInterface $output, $inputStream, bool $trimmable = true): string + { + if ('\\' === \DIRECTORY_SEPARATOR) { + $exe = __DIR__.'/../Resources/bin/hiddeninput.exe'; + + // handle code running from a phar + if (str_starts_with(__FILE__, 'phar:')) { + $tmpExe = sys_get_temp_dir().'/hiddeninput.exe'; + copy($exe, $tmpExe); + $exe = $tmpExe; + } + + $sExec = shell_exec('"'.$exe.'"'); + $value = $trimmable ? rtrim($sExec) : $sExec; + $output->writeln(''); + + if (isset($tmpExe)) { + unlink($tmpExe); + } + + return $value; + } + + if (self::$stty && Terminal::hasSttyAvailable()) { + $sttyMode = shell_exec('stty -g'); + shell_exec('stty -echo'); + } elseif ($this->isInteractiveInput($inputStream)) { + throw new RuntimeException('Unable to hide the response.'); + } + + $value = fgets($inputStream, 4096); + + if (4095 === \strlen($value)) { + $errOutput = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output; + $errOutput->warning('The value was possibly truncated by your shell or terminal emulator'); + } + + if (self::$stty && Terminal::hasSttyAvailable()) { + shell_exec('stty '.$sttyMode); + } + + if (false === $value) { + throw new MissingInputException('Aborted.'); + } + if ($trimmable) { + $value = trim($value); + } + $output->writeln(''); + + return $value; + } + + /** + * Validates an attempt. + * + * @param callable $interviewer A callable that will ask for a question and return the result + * + * @throws \Exception In case the max number of attempts has been reached and no valid response has been given + */ + private function validateAttempts(callable $interviewer, OutputInterface $output, Question $question): mixed + { + $error = null; + $attempts = $question->getMaxAttempts(); + + while (null === $attempts || $attempts--) { + if (null !== $error) { + $this->writeError($output, $error); + } + + try { + return $question->getValidator()($interviewer()); + } catch (RuntimeException $e) { + throw $e; + } catch (\Exception $error) { + } + } + + throw $error; + } + + private function isInteractiveInput($inputStream): bool + { + if ('php://stdin' !== (stream_get_meta_data($inputStream)['uri'] ?? null)) { + return false; + } + + if (isset(self::$stdinIsInteractive)) { + return self::$stdinIsInteractive; + } + + return self::$stdinIsInteractive = @stream_isatty(fopen('php://stdin', 'r')); + } + + /** + * Reads one or more lines of input and returns what is read. + * + * @param resource $inputStream The handler resource + * @param Question $question The question being asked + */ + private function readInput($inputStream, Question $question): string|false + { + if (!$question->isMultiline()) { + $cp = $this->setIOCodepage(); + $ret = fgets($inputStream, 4096); + + return $this->resetIOCodepage($cp, $ret); + } + + $multiLineStreamReader = $this->cloneInputStream($inputStream); + if (null === $multiLineStreamReader) { + return false; + } + + $ret = ''; + $cp = $this->setIOCodepage(); + while (false !== ($char = fgetc($multiLineStreamReader))) { + if (\PHP_EOL === "{$ret}{$char}") { + break; + } + $ret .= $char; + } + + return $this->resetIOCodepage($cp, $ret); + } + + private function setIOCodepage(): int + { + if (\function_exists('sapi_windows_cp_set')) { + $cp = sapi_windows_cp_get(); + sapi_windows_cp_set(sapi_windows_cp_get('oem')); + + return $cp; + } + + return 0; + } + + /** + * Sets console I/O to the specified code page and converts the user input. + */ + private function resetIOCodepage(int $cp, string|false $input): string|false + { + if (0 !== $cp) { + sapi_windows_cp_set($cp); + + if (false !== $input && '' !== $input) { + $input = sapi_windows_cp_conv(sapi_windows_cp_get('oem'), $cp, $input); + } + } + + return $input; + } + + /** + * Clones an input stream in order to act on one instance of the same + * stream without affecting the other instance. + * + * @param resource $inputStream The handler resource + * + * @return resource|null The cloned resource, null in case it could not be cloned + */ + private function cloneInputStream($inputStream) + { + $streamMetaData = stream_get_meta_data($inputStream); + $seekable = $streamMetaData['seekable'] ?? false; + $mode = $streamMetaData['mode'] ?? 'rb'; + $uri = $streamMetaData['uri'] ?? null; + + if (null === $uri) { + return null; + } + + $cloneStream = fopen($uri, $mode); + + // For seekable and writable streams, add all the same data to the + // cloned stream and then seek to the same offset. + if (true === $seekable && !\in_array($mode, ['r', 'rb', 'rt'])) { + $offset = ftell($inputStream); + rewind($inputStream); + stream_copy_to_stream($inputStream, $cloneStream); + fseek($inputStream, $offset); + fseek($cloneStream, $offset); + } + + return $cloneStream; + } +} diff --git a/vendor/symfony/console/Helper/SymfonyQuestionHelper.php b/vendor/symfony/console/Helper/SymfonyQuestionHelper.php new file mode 100644 index 00000000..48d947b7 --- /dev/null +++ b/vendor/symfony/console/Helper/SymfonyQuestionHelper.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ChoiceQuestion; +use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Question\Question; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * Symfony Style Guide compliant question helper. + * + * @author Kevin Bond + */ +class SymfonyQuestionHelper extends QuestionHelper +{ + protected function writePrompt(OutputInterface $output, Question $question): void + { + $text = OutputFormatter::escapeTrailingBackslash($question->getQuestion()); + $default = $question->getDefault(); + + if ($question->isMultiline()) { + $text .= sprintf(' (press %s to continue)', $this->getEofShortcut()); + } + + switch (true) { + case null === $default: + $text = sprintf(' %s:', $text); + + break; + + case $question instanceof ConfirmationQuestion: + $text = sprintf(' %s (yes/no) [%s]:', $text, $default ? 'yes' : 'no'); + + break; + + case $question instanceof ChoiceQuestion && $question->isMultiselect(): + $choices = $question->getChoices(); + $default = explode(',', $default); + + foreach ($default as $key => $value) { + $default[$key] = $choices[trim($value)]; + } + + $text = sprintf(' %s [%s]:', $text, OutputFormatter::escape(implode(', ', $default))); + + break; + + case $question instanceof ChoiceQuestion: + $choices = $question->getChoices(); + $text = sprintf(' %s [%s]:', $text, OutputFormatter::escape($choices[$default] ?? $default)); + + break; + + default: + $text = sprintf(' %s [%s]:', $text, OutputFormatter::escape($default)); + } + + $output->writeln($text); + + $prompt = ' > '; + + if ($question instanceof ChoiceQuestion) { + $output->writeln($this->formatChoiceQuestionChoices($question, 'comment')); + + $prompt = $question->getPrompt(); + } + + $output->write($prompt); + } + + protected function writeError(OutputInterface $output, \Exception $error): void + { + if ($output instanceof SymfonyStyle) { + $output->newLine(); + $output->error($error->getMessage()); + + return; + } + + parent::writeError($output, $error); + } + + private function getEofShortcut(): string + { + if ('Windows' === \PHP_OS_FAMILY) { + return 'Ctrl+Z then Enter'; + } + + return 'Ctrl+D'; + } +} diff --git a/vendor/symfony/console/Helper/Table.php b/vendor/symfony/console/Helper/Table.php new file mode 100644 index 00000000..bc6697a4 --- /dev/null +++ b/vendor/symfony/console/Helper/Table.php @@ -0,0 +1,925 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Formatter\WrappableOutputFormatterInterface; +use Symfony\Component\Console\Output\ConsoleSectionOutput; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Provides helpers to display a table. + * + * @author Fabien Potencier + * @author Саша Стаменковић + * @author Abdellatif Ait boudad + * @author Max Grigorian + * @author Dany Maillard + */ +class Table +{ + private const SEPARATOR_TOP = 0; + private const SEPARATOR_TOP_BOTTOM = 1; + private const SEPARATOR_MID = 2; + private const SEPARATOR_BOTTOM = 3; + private const BORDER_OUTSIDE = 0; + private const BORDER_INSIDE = 1; + private const DISPLAY_ORIENTATION_DEFAULT = 'default'; + private const DISPLAY_ORIENTATION_HORIZONTAL = 'horizontal'; + private const DISPLAY_ORIENTATION_VERTICAL = 'vertical'; + + private ?string $headerTitle = null; + private ?string $footerTitle = null; + private array $headers = []; + private array $rows = []; + private array $effectiveColumnWidths = []; + private int $numberOfColumns; + private OutputInterface $output; + private TableStyle $style; + private array $columnStyles = []; + private array $columnWidths = []; + private array $columnMaxWidths = []; + private bool $rendered = false; + private string $displayOrientation = self::DISPLAY_ORIENTATION_DEFAULT; + + private static array $styles; + + public function __construct(OutputInterface $output) + { + $this->output = $output; + + self::$styles ??= self::initStyles(); + + $this->setStyle('default'); + } + + /** + * Sets a style definition. + */ + public static function setStyleDefinition(string $name, TableStyle $style): void + { + self::$styles ??= self::initStyles(); + + self::$styles[$name] = $style; + } + + /** + * Gets a style definition by name. + */ + public static function getStyleDefinition(string $name): TableStyle + { + self::$styles ??= self::initStyles(); + + return self::$styles[$name] ?? throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); + } + + /** + * Sets table style. + * + * @return $this + */ + public function setStyle(TableStyle|string $name): static + { + $this->style = $this->resolveStyle($name); + + return $this; + } + + /** + * Gets the current table style. + */ + public function getStyle(): TableStyle + { + return $this->style; + } + + /** + * Sets table column style. + * + * @param TableStyle|string $name The style name or a TableStyle instance + * + * @return $this + */ + public function setColumnStyle(int $columnIndex, TableStyle|string $name): static + { + $this->columnStyles[$columnIndex] = $this->resolveStyle($name); + + return $this; + } + + /** + * Gets the current style for a column. + * + * If style was not set, it returns the global table style. + */ + public function getColumnStyle(int $columnIndex): TableStyle + { + return $this->columnStyles[$columnIndex] ?? $this->getStyle(); + } + + /** + * Sets the minimum width of a column. + * + * @return $this + */ + public function setColumnWidth(int $columnIndex, int $width): static + { + $this->columnWidths[$columnIndex] = $width; + + return $this; + } + + /** + * Sets the minimum width of all columns. + * + * @return $this + */ + public function setColumnWidths(array $widths): static + { + $this->columnWidths = []; + foreach ($widths as $index => $width) { + $this->setColumnWidth($index, $width); + } + + return $this; + } + + /** + * Sets the maximum width of a column. + * + * Any cell within this column which contents exceeds the specified width will be wrapped into multiple lines, while + * formatted strings are preserved. + * + * @return $this + */ + public function setColumnMaxWidth(int $columnIndex, int $width): static + { + if (!$this->output->getFormatter() instanceof WrappableOutputFormatterInterface) { + throw new \LogicException(sprintf('Setting a maximum column width is only supported when using a "%s" formatter, got "%s".', WrappableOutputFormatterInterface::class, get_debug_type($this->output->getFormatter()))); + } + + $this->columnMaxWidths[$columnIndex] = $width; + + return $this; + } + + /** + * @return $this + */ + public function setHeaders(array $headers): static + { + $headers = array_values($headers); + if ($headers && !\is_array($headers[0])) { + $headers = [$headers]; + } + + $this->headers = $headers; + + return $this; + } + + /** + * @return $this + */ + public function setRows(array $rows): static + { + $this->rows = []; + + return $this->addRows($rows); + } + + /** + * @return $this + */ + public function addRows(array $rows): static + { + foreach ($rows as $row) { + $this->addRow($row); + } + + return $this; + } + + /** + * @return $this + */ + public function addRow(TableSeparator|array $row): static + { + if ($row instanceof TableSeparator) { + $this->rows[] = $row; + + return $this; + } + + $this->rows[] = array_values($row); + + return $this; + } + + /** + * Adds a row to the table, and re-renders the table. + * + * @return $this + */ + public function appendRow(TableSeparator|array $row): static + { + if (!$this->output instanceof ConsoleSectionOutput) { + throw new RuntimeException(sprintf('Output should be an instance of "%s" when calling "%s".', ConsoleSectionOutput::class, __METHOD__)); + } + + if ($this->rendered) { + $this->output->clear($this->calculateRowCount()); + } + + $this->addRow($row); + $this->render(); + + return $this; + } + + /** + * @return $this + */ + public function setRow(int|string $column, array $row): static + { + $this->rows[$column] = $row; + + return $this; + } + + /** + * @return $this + */ + public function setHeaderTitle(?string $title): static + { + $this->headerTitle = $title; + + return $this; + } + + /** + * @return $this + */ + public function setFooterTitle(?string $title): static + { + $this->footerTitle = $title; + + return $this; + } + + /** + * @return $this + */ + public function setHorizontal(bool $horizontal = true): static + { + $this->displayOrientation = $horizontal ? self::DISPLAY_ORIENTATION_HORIZONTAL : self::DISPLAY_ORIENTATION_DEFAULT; + + return $this; + } + + /** + * @return $this + */ + public function setVertical(bool $vertical = true): static + { + $this->displayOrientation = $vertical ? self::DISPLAY_ORIENTATION_VERTICAL : self::DISPLAY_ORIENTATION_DEFAULT; + + return $this; + } + + /** + * Renders table to output. + * + * Example: + * + * +---------------+-----------------------+------------------+ + * | ISBN | Title | Author | + * +---------------+-----------------------+------------------+ + * | 99921-58-10-7 | Divine Comedy | Dante Alighieri | + * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | + * | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien | + * +---------------+-----------------------+------------------+ + */ + public function render(): void + { + $divider = new TableSeparator(); + $isCellWithColspan = static fn ($cell) => $cell instanceof TableCell && $cell->getColspan() >= 2; + + $horizontal = self::DISPLAY_ORIENTATION_HORIZONTAL === $this->displayOrientation; + $vertical = self::DISPLAY_ORIENTATION_VERTICAL === $this->displayOrientation; + + $rows = []; + if ($horizontal) { + foreach ($this->headers[0] ?? [] as $i => $header) { + $rows[$i] = [$header]; + foreach ($this->rows as $row) { + if ($row instanceof TableSeparator) { + continue; + } + if (isset($row[$i])) { + $rows[$i][] = $row[$i]; + } elseif ($isCellWithColspan($rows[$i][0])) { + // Noop, there is a "title" + } else { + $rows[$i][] = null; + } + } + } + } elseif ($vertical) { + $formatter = $this->output->getFormatter(); + $maxHeaderLength = array_reduce($this->headers[0] ?? [], static fn ($max, $header) => max($max, Helper::width(Helper::removeDecoration($formatter, $header))), 0); + + foreach ($this->rows as $row) { + if ($row instanceof TableSeparator) { + continue; + } + + if ($rows) { + $rows[] = [$divider]; + } + + $containsColspan = false; + foreach ($row as $cell) { + if ($containsColspan = $isCellWithColspan($cell)) { + break; + } + } + + $headers = $this->headers[0] ?? []; + $maxRows = max(\count($headers), \count($row)); + for ($i = 0; $i < $maxRows; ++$i) { + $cell = (string) ($row[$i] ?? ''); + + $eol = str_contains($cell, "\r\n") ? "\r\n" : "\n"; + $parts = explode($eol, $cell); + foreach ($parts as $idx => $part) { + if ($headers && !$containsColspan) { + if (0 === $idx) { + $rows[] = [sprintf( + '%s: %s', + str_pad($headers[$i] ?? '', $maxHeaderLength, ' ', \STR_PAD_LEFT), + $part + )]; + } else { + $rows[] = [sprintf( + '%s %s', + str_pad('', $maxHeaderLength, ' ', \STR_PAD_LEFT), + $part + )]; + } + } elseif ('' !== $cell) { + $rows[] = [$part]; + } + } + } + } + } else { + $rows = array_merge($this->headers, [$divider], $this->rows); + } + + $this->calculateNumberOfColumns($rows); + + $rowGroups = $this->buildTableRows($rows); + $this->calculateColumnsWidth($rowGroups); + + $isHeader = !$horizontal; + $isFirstRow = $horizontal; + $hasTitle = (bool) $this->headerTitle; + + foreach ($rowGroups as $rowGroup) { + $isHeaderSeparatorRendered = false; + + foreach ($rowGroup as $row) { + if ($divider === $row) { + $isHeader = false; + $isFirstRow = true; + + continue; + } + + if ($row instanceof TableSeparator) { + $this->renderRowSeparator(); + + continue; + } + + if (!$row) { + continue; + } + + if ($isHeader && !$isHeaderSeparatorRendered) { + $this->renderRowSeparator( + self::SEPARATOR_TOP, + $hasTitle ? $this->headerTitle : null, + $hasTitle ? $this->style->getHeaderTitleFormat() : null + ); + $hasTitle = false; + $isHeaderSeparatorRendered = true; + } + + if ($isFirstRow) { + $this->renderRowSeparator( + $horizontal ? self::SEPARATOR_TOP : self::SEPARATOR_TOP_BOTTOM, + $hasTitle ? $this->headerTitle : null, + $hasTitle ? $this->style->getHeaderTitleFormat() : null + ); + $isFirstRow = false; + $hasTitle = false; + } + + if ($vertical) { + $isHeader = false; + $isFirstRow = false; + } + + if ($horizontal) { + $this->renderRow($row, $this->style->getCellRowFormat(), $this->style->getCellHeaderFormat()); + } else { + $this->renderRow($row, $isHeader ? $this->style->getCellHeaderFormat() : $this->style->getCellRowFormat()); + } + } + } + $this->renderRowSeparator(self::SEPARATOR_BOTTOM, $this->footerTitle, $this->style->getFooterTitleFormat()); + + $this->cleanup(); + $this->rendered = true; + } + + /** + * Renders horizontal header separator. + * + * Example: + * + * +-----+-----------+-------+ + */ + private function renderRowSeparator(int $type = self::SEPARATOR_MID, ?string $title = null, ?string $titleFormat = null): void + { + if (!$count = $this->numberOfColumns) { + return; + } + + $borders = $this->style->getBorderChars(); + if (!$borders[0] && !$borders[2] && !$this->style->getCrossingChar()) { + return; + } + + $crossings = $this->style->getCrossingChars(); + if (self::SEPARATOR_MID === $type) { + [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[2], $crossings[8], $crossings[0], $crossings[4]]; + } elseif (self::SEPARATOR_TOP === $type) { + [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[1], $crossings[2], $crossings[3]]; + } elseif (self::SEPARATOR_TOP_BOTTOM === $type) { + [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[9], $crossings[10], $crossings[11]]; + } else { + [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[7], $crossings[6], $crossings[5]]; + } + + $markup = $leftChar; + for ($column = 0; $column < $count; ++$column) { + $markup .= str_repeat($horizontal, $this->effectiveColumnWidths[$column]); + $markup .= $column === $count - 1 ? $rightChar : $midChar; + } + + if (null !== $title) { + $titleLength = Helper::width(Helper::removeDecoration($formatter = $this->output->getFormatter(), $formattedTitle = sprintf($titleFormat, $title))); + $markupLength = Helper::width($markup); + if ($titleLength > $limit = $markupLength - 4) { + $titleLength = $limit; + $formatLength = Helper::width(Helper::removeDecoration($formatter, sprintf($titleFormat, ''))); + $formattedTitle = sprintf($titleFormat, Helper::substr($title, 0, $limit - $formatLength - 3).'...'); + } + + $titleStart = intdiv($markupLength - $titleLength, 2); + if (false === mb_detect_encoding($markup, null, true)) { + $markup = substr_replace($markup, $formattedTitle, $titleStart, $titleLength); + } else { + $markup = mb_substr($markup, 0, $titleStart).$formattedTitle.mb_substr($markup, $titleStart + $titleLength); + } + } + + $this->output->writeln(sprintf($this->style->getBorderFormat(), $markup)); + } + + /** + * Renders vertical column separator. + */ + private function renderColumnSeparator(int $type = self::BORDER_OUTSIDE): string + { + $borders = $this->style->getBorderChars(); + + return sprintf($this->style->getBorderFormat(), self::BORDER_OUTSIDE === $type ? $borders[1] : $borders[3]); + } + + /** + * Renders table row. + * + * Example: + * + * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | + */ + private function renderRow(array $row, string $cellFormat, ?string $firstCellFormat = null): void + { + $rowContent = $this->renderColumnSeparator(self::BORDER_OUTSIDE); + $columns = $this->getRowColumns($row); + $last = \count($columns) - 1; + foreach ($columns as $i => $column) { + if ($firstCellFormat && 0 === $i) { + $rowContent .= $this->renderCell($row, $column, $firstCellFormat); + } else { + $rowContent .= $this->renderCell($row, $column, $cellFormat); + } + $rowContent .= $this->renderColumnSeparator($last === $i ? self::BORDER_OUTSIDE : self::BORDER_INSIDE); + } + $this->output->writeln($rowContent); + } + + /** + * Renders table cell with padding. + */ + private function renderCell(array $row, int $column, string $cellFormat): string + { + $cell = $row[$column] ?? ''; + $width = $this->effectiveColumnWidths[$column]; + if ($cell instanceof TableCell && $cell->getColspan() > 1) { + // add the width of the following columns(numbers of colspan). + foreach (range($column + 1, $column + $cell->getColspan() - 1) as $nextColumn) { + $width += $this->getColumnSeparatorWidth() + $this->effectiveColumnWidths[$nextColumn]; + } + } + + // str_pad won't work properly with multi-byte strings, we need to fix the padding + if (false !== $encoding = mb_detect_encoding($cell, null, true)) { + $width += \strlen($cell) - mb_strwidth($cell, $encoding); + } + + $style = $this->getColumnStyle($column); + + if ($cell instanceof TableSeparator) { + return sprintf($style->getBorderFormat(), str_repeat($style->getBorderChars()[2], $width)); + } + + $width += Helper::length($cell) - Helper::length(Helper::removeDecoration($this->output->getFormatter(), $cell)); + $content = sprintf($style->getCellRowContentFormat(), $cell); + + $padType = $style->getPadType(); + if ($cell instanceof TableCell && $cell->getStyle() instanceof TableCellStyle) { + $isNotStyledByTag = !preg_match('/^<(\w+|(\w+=[\w,]+;?)*)>.+<\/(\w+|(\w+=\w+;?)*)?>$/', $cell); + if ($isNotStyledByTag) { + $cellFormat = $cell->getStyle()->getCellFormat(); + if (!\is_string($cellFormat)) { + $tag = http_build_query($cell->getStyle()->getTagOptions(), '', ';'); + $cellFormat = '<'.$tag.'>%s'; + } + + if (str_contains($content, '')) { + $content = str_replace('', '', $content); + $width -= 3; + } + if (str_contains($content, '')) { + $content = str_replace('', '', $content); + $width -= \strlen(''); + } + } + + $padType = $cell->getStyle()->getPadByAlign(); + } + + return sprintf($cellFormat, str_pad($content, $width, $style->getPaddingChar(), $padType)); + } + + /** + * Calculate number of columns for this table. + */ + private function calculateNumberOfColumns(array $rows): void + { + $columns = [0]; + foreach ($rows as $row) { + if ($row instanceof TableSeparator) { + continue; + } + + $columns[] = $this->getNumberOfColumns($row); + } + + $this->numberOfColumns = max($columns); + } + + private function buildTableRows(array $rows): TableRows + { + /** @var WrappableOutputFormatterInterface $formatter */ + $formatter = $this->output->getFormatter(); + $unmergedRows = []; + for ($rowKey = 0; $rowKey < \count($rows); ++$rowKey) { + $rows = $this->fillNextRows($rows, $rowKey); + + // Remove any new line breaks and replace it with a new line + foreach ($rows[$rowKey] as $column => $cell) { + $colspan = $cell instanceof TableCell ? $cell->getColspan() : 1; + + if (isset($this->columnMaxWidths[$column]) && Helper::width(Helper::removeDecoration($formatter, $cell)) > $this->columnMaxWidths[$column]) { + $cell = $formatter->formatAndWrap($cell, $this->columnMaxWidths[$column] * $colspan); + } + if (!str_contains($cell ?? '', "\n")) { + continue; + } + $eol = str_contains($cell ?? '', "\r\n") ? "\r\n" : "\n"; + $escaped = implode($eol, array_map(OutputFormatter::escapeTrailingBackslash(...), explode($eol, $cell))); + $cell = $cell instanceof TableCell ? new TableCell($escaped, ['colspan' => $cell->getColspan()]) : $escaped; + $lines = explode($eol, str_replace($eol, ''.$eol, $cell)); + foreach ($lines as $lineKey => $line) { + if ($colspan > 1) { + $line = new TableCell($line, ['colspan' => $colspan]); + } + if (0 === $lineKey) { + $rows[$rowKey][$column] = $line; + } else { + if (!\array_key_exists($rowKey, $unmergedRows) || !\array_key_exists($lineKey, $unmergedRows[$rowKey])) { + $unmergedRows[$rowKey][$lineKey] = $this->copyRow($rows, $rowKey); + } + $unmergedRows[$rowKey][$lineKey][$column] = $line; + } + } + } + } + + return new TableRows(function () use ($rows, $unmergedRows): \Traversable { + foreach ($rows as $rowKey => $row) { + $rowGroup = [$row instanceof TableSeparator ? $row : $this->fillCells($row)]; + + if (isset($unmergedRows[$rowKey])) { + foreach ($unmergedRows[$rowKey] as $row) { + $rowGroup[] = $row instanceof TableSeparator ? $row : $this->fillCells($row); + } + } + yield $rowGroup; + } + }); + } + + private function calculateRowCount(): int + { + $numberOfRows = \count(iterator_to_array($this->buildTableRows(array_merge($this->headers, [new TableSeparator()], $this->rows)))); + + if ($this->headers) { + ++$numberOfRows; // Add row for header separator + } + + if ($this->rows) { + ++$numberOfRows; // Add row for footer separator + } + + return $numberOfRows; + } + + /** + * fill rows that contains rowspan > 1. + * + * @throws InvalidArgumentException + */ + private function fillNextRows(array $rows, int $line): array + { + $unmergedRows = []; + foreach ($rows[$line] as $column => $cell) { + if (null !== $cell && !$cell instanceof TableCell && !\is_scalar($cell) && !$cell instanceof \Stringable) { + throw new InvalidArgumentException(sprintf('A cell must be a TableCell, a scalar or an object implementing "__toString()", "%s" given.', get_debug_type($cell))); + } + if ($cell instanceof TableCell && $cell->getRowspan() > 1) { + $nbLines = $cell->getRowspan() - 1; + $lines = [$cell]; + if (str_contains($cell, "\n")) { + $eol = str_contains($cell, "\r\n") ? "\r\n" : "\n"; + $lines = explode($eol, str_replace($eol, ''.$eol.'', $cell)); + $nbLines = \count($lines) > $nbLines ? substr_count($cell, $eol) : $nbLines; + + $rows[$line][$column] = new TableCell($lines[0], ['colspan' => $cell->getColspan(), 'style' => $cell->getStyle()]); + unset($lines[0]); + } + + // create a two dimensional array (rowspan x colspan) + $unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, []), $unmergedRows); + foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { + $value = $lines[$unmergedRowKey - $line] ?? ''; + $unmergedRows[$unmergedRowKey][$column] = new TableCell($value, ['colspan' => $cell->getColspan(), 'style' => $cell->getStyle()]); + if ($nbLines === $unmergedRowKey - $line) { + break; + } + } + } + } + + foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { + // we need to know if $unmergedRow will be merged or inserted into $rows + if (isset($rows[$unmergedRowKey]) && \is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns)) { + foreach ($unmergedRow as $cellKey => $cell) { + // insert cell into row at cellKey position + array_splice($rows[$unmergedRowKey], $cellKey, 0, [$cell]); + } + } else { + $row = $this->copyRow($rows, $unmergedRowKey - 1); + foreach ($unmergedRow as $column => $cell) { + if (!empty($cell)) { + $row[$column] = $unmergedRow[$column]; + } + } + array_splice($rows, $unmergedRowKey, 0, [$row]); + } + } + + return $rows; + } + + /** + * fill cells for a row that contains colspan > 1. + */ + private function fillCells(iterable $row): iterable + { + $newRow = []; + + foreach ($row as $column => $cell) { + $newRow[] = $cell; + if ($cell instanceof TableCell && $cell->getColspan() > 1) { + foreach (range($column + 1, $column + $cell->getColspan() - 1) as $position) { + // insert empty value at column position + $newRow[] = ''; + } + } + } + + return $newRow ?: $row; + } + + private function copyRow(array $rows, int $line): array + { + $row = $rows[$line]; + foreach ($row as $cellKey => $cellValue) { + $row[$cellKey] = ''; + if ($cellValue instanceof TableCell) { + $row[$cellKey] = new TableCell('', ['colspan' => $cellValue->getColspan()]); + } + } + + return $row; + } + + /** + * Gets number of columns by row. + */ + private function getNumberOfColumns(array $row): int + { + $columns = \count($row); + foreach ($row as $column) { + $columns += $column instanceof TableCell ? ($column->getColspan() - 1) : 0; + } + + return $columns; + } + + /** + * Gets list of columns for the given row. + */ + private function getRowColumns(array $row): array + { + $columns = range(0, $this->numberOfColumns - 1); + foreach ($row as $cellKey => $cell) { + if ($cell instanceof TableCell && $cell->getColspan() > 1) { + // exclude grouped columns. + $columns = array_diff($columns, range($cellKey + 1, $cellKey + $cell->getColspan() - 1)); + } + } + + return $columns; + } + + /** + * Calculates columns widths. + */ + private function calculateColumnsWidth(iterable $groups): void + { + for ($column = 0; $column < $this->numberOfColumns; ++$column) { + $lengths = []; + foreach ($groups as $group) { + foreach ($group as $row) { + if ($row instanceof TableSeparator) { + continue; + } + + foreach ($row as $i => $cell) { + if ($cell instanceof TableCell) { + $textContent = Helper::removeDecoration($this->output->getFormatter(), $cell); + $textLength = Helper::width($textContent); + if ($textLength > 0) { + $contentColumns = mb_str_split($textContent, ceil($textLength / $cell->getColspan())); + foreach ($contentColumns as $position => $content) { + $row[$i + $position] = $content; + } + } + } + } + + $lengths[] = $this->getCellWidth($row, $column); + } + } + + $this->effectiveColumnWidths[$column] = max($lengths) + Helper::width($this->style->getCellRowContentFormat()) - 2; + } + } + + private function getColumnSeparatorWidth(): int + { + return Helper::width(sprintf($this->style->getBorderFormat(), $this->style->getBorderChars()[3])); + } + + private function getCellWidth(array $row, int $column): int + { + $cellWidth = 0; + + if (isset($row[$column])) { + $cell = $row[$column]; + $cellWidth = Helper::width(Helper::removeDecoration($this->output->getFormatter(), $cell)); + } + + $columnWidth = $this->columnWidths[$column] ?? 0; + $cellWidth = max($cellWidth, $columnWidth); + + return isset($this->columnMaxWidths[$column]) ? min($this->columnMaxWidths[$column], $cellWidth) : $cellWidth; + } + + /** + * Called after rendering to cleanup cache data. + */ + private function cleanup(): void + { + $this->effectiveColumnWidths = []; + unset($this->numberOfColumns); + } + + /** + * @return array + */ + private static function initStyles(): array + { + $borderless = new TableStyle(); + $borderless + ->setHorizontalBorderChars('=') + ->setVerticalBorderChars(' ') + ->setDefaultCrossingChar(' ') + ; + + $compact = new TableStyle(); + $compact + ->setHorizontalBorderChars('') + ->setVerticalBorderChars('') + ->setDefaultCrossingChar('') + ->setCellRowContentFormat('%s ') + ; + + $styleGuide = new TableStyle(); + $styleGuide + ->setHorizontalBorderChars('-') + ->setVerticalBorderChars(' ') + ->setDefaultCrossingChar(' ') + ->setCellHeaderFormat('%s') + ; + + $box = (new TableStyle()) + ->setHorizontalBorderChars('─') + ->setVerticalBorderChars('│') + ->setCrossingChars('┼', '┌', '┬', '┐', '┤', '┘', '┴', '└', '├') + ; + + $boxDouble = (new TableStyle()) + ->setHorizontalBorderChars('═', '─') + ->setVerticalBorderChars('║', '│') + ->setCrossingChars('┼', '╔', '╤', '╗', '╢', '╝', '╧', '╚', '╟', '╠', '╪', '╣') + ; + + return [ + 'default' => new TableStyle(), + 'borderless' => $borderless, + 'compact' => $compact, + 'symfony-style-guide' => $styleGuide, + 'box' => $box, + 'box-double' => $boxDouble, + ]; + } + + private function resolveStyle(TableStyle|string $name): TableStyle + { + if ($name instanceof TableStyle) { + return $name; + } + + return self::$styles[$name] ?? throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); + } +} diff --git a/vendor/symfony/console/Helper/TableCell.php b/vendor/symfony/console/Helper/TableCell.php new file mode 100644 index 00000000..394b2bc9 --- /dev/null +++ b/vendor/symfony/console/Helper/TableCell.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * @author Abdellatif Ait boudad + */ +class TableCell +{ + private string $value; + private array $options = [ + 'rowspan' => 1, + 'colspan' => 1, + 'style' => null, + ]; + + public function __construct(string $value = '', array $options = []) + { + $this->value = $value; + + // check option names + if ($diff = array_diff(array_keys($options), array_keys($this->options))) { + throw new InvalidArgumentException(sprintf('The TableCell does not support the following options: \'%s\'.', implode('\', \'', $diff))); + } + + if (isset($options['style']) && !$options['style'] instanceof TableCellStyle) { + throw new InvalidArgumentException('The style option must be an instance of "TableCellStyle".'); + } + + $this->options = array_merge($this->options, $options); + } + + /** + * Returns the cell value. + */ + public function __toString(): string + { + return $this->value; + } + + /** + * Gets number of colspan. + */ + public function getColspan(): int + { + return (int) $this->options['colspan']; + } + + /** + * Gets number of rowspan. + */ + public function getRowspan(): int + { + return (int) $this->options['rowspan']; + } + + public function getStyle(): ?TableCellStyle + { + return $this->options['style']; + } +} diff --git a/vendor/symfony/console/Helper/TableCellStyle.php b/vendor/symfony/console/Helper/TableCellStyle.php new file mode 100644 index 00000000..9419dcb4 --- /dev/null +++ b/vendor/symfony/console/Helper/TableCellStyle.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * @author Yewhen Khoptynskyi + */ +class TableCellStyle +{ + public const DEFAULT_ALIGN = 'left'; + + private const TAG_OPTIONS = [ + 'fg', + 'bg', + 'options', + ]; + + private const ALIGN_MAP = [ + 'left' => \STR_PAD_RIGHT, + 'center' => \STR_PAD_BOTH, + 'right' => \STR_PAD_LEFT, + ]; + + private array $options = [ + 'fg' => 'default', + 'bg' => 'default', + 'options' => null, + 'align' => self::DEFAULT_ALIGN, + 'cellFormat' => null, + ]; + + public function __construct(array $options = []) + { + if ($diff = array_diff(array_keys($options), array_keys($this->options))) { + throw new InvalidArgumentException(sprintf('The TableCellStyle does not support the following options: \'%s\'.', implode('\', \'', $diff))); + } + + if (isset($options['align']) && !\array_key_exists($options['align'], self::ALIGN_MAP)) { + throw new InvalidArgumentException(sprintf('Wrong align value. Value must be following: \'%s\'.', implode('\', \'', array_keys(self::ALIGN_MAP)))); + } + + $this->options = array_merge($this->options, $options); + } + + public function getOptions(): array + { + return $this->options; + } + + /** + * Gets options we need for tag for example fg, bg. + * + * @return string[] + */ + public function getTagOptions(): array + { + return array_filter( + $this->getOptions(), + fn ($key) => \in_array($key, self::TAG_OPTIONS) && isset($this->options[$key]), + \ARRAY_FILTER_USE_KEY + ); + } + + public function getPadByAlign(): int + { + return self::ALIGN_MAP[$this->getOptions()['align']]; + } + + public function getCellFormat(): ?string + { + return $this->getOptions()['cellFormat']; + } +} diff --git a/vendor/symfony/console/Helper/TableRows.php b/vendor/symfony/console/Helper/TableRows.php new file mode 100644 index 00000000..97d07726 --- /dev/null +++ b/vendor/symfony/console/Helper/TableRows.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +/** + * @internal + */ +class TableRows implements \IteratorAggregate +{ + private \Closure $generator; + + public function __construct(\Closure $generator) + { + $this->generator = $generator; + } + + public function getIterator(): \Traversable + { + return ($this->generator)(); + } +} diff --git a/vendor/symfony/console/Helper/TableSeparator.php b/vendor/symfony/console/Helper/TableSeparator.php new file mode 100644 index 00000000..e541c531 --- /dev/null +++ b/vendor/symfony/console/Helper/TableSeparator.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +/** + * Marks a row as being a separator. + * + * @author Fabien Potencier + */ +class TableSeparator extends TableCell +{ + public function __construct(array $options = []) + { + parent::__construct('', $options); + } +} diff --git a/vendor/symfony/console/Helper/TableStyle.php b/vendor/symfony/console/Helper/TableStyle.php new file mode 100644 index 00000000..be956c10 --- /dev/null +++ b/vendor/symfony/console/Helper/TableStyle.php @@ -0,0 +1,362 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; + +/** + * Defines the styles for a Table. + * + * @author Fabien Potencier + * @author Саша Стаменковић + * @author Dany Maillard + */ +class TableStyle +{ + private string $paddingChar = ' '; + private string $horizontalOutsideBorderChar = '-'; + private string $horizontalInsideBorderChar = '-'; + private string $verticalOutsideBorderChar = '|'; + private string $verticalInsideBorderChar = '|'; + private string $crossingChar = '+'; + private string $crossingTopRightChar = '+'; + private string $crossingTopMidChar = '+'; + private string $crossingTopLeftChar = '+'; + private string $crossingMidRightChar = '+'; + private string $crossingBottomRightChar = '+'; + private string $crossingBottomMidChar = '+'; + private string $crossingBottomLeftChar = '+'; + private string $crossingMidLeftChar = '+'; + private string $crossingTopLeftBottomChar = '+'; + private string $crossingTopMidBottomChar = '+'; + private string $crossingTopRightBottomChar = '+'; + private string $headerTitleFormat = ' %s '; + private string $footerTitleFormat = ' %s '; + private string $cellHeaderFormat = '%s'; + private string $cellRowFormat = '%s'; + private string $cellRowContentFormat = ' %s '; + private string $borderFormat = '%s'; + private int $padType = \STR_PAD_RIGHT; + + /** + * Sets padding character, used for cell padding. + * + * @return $this + */ + public function setPaddingChar(string $paddingChar): static + { + if (!$paddingChar) { + throw new LogicException('The padding char must not be empty.'); + } + + $this->paddingChar = $paddingChar; + + return $this; + } + + /** + * Gets padding character, used for cell padding. + */ + public function getPaddingChar(): string + { + return $this->paddingChar; + } + + /** + * Sets horizontal border characters. + * + * + * ╔═══════════════╤══════════════════════════╤══════════════════╗ + * 1 ISBN 2 Title │ Author ║ + * ╠═══════════════╪══════════════════════════╪══════════════════╣ + * ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║ + * ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║ + * ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║ + * ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║ + * ╚═══════════════╧══════════════════════════╧══════════════════╝ + * + * + * @return $this + */ + public function setHorizontalBorderChars(string $outside, ?string $inside = null): static + { + $this->horizontalOutsideBorderChar = $outside; + $this->horizontalInsideBorderChar = $inside ?? $outside; + + return $this; + } + + /** + * Sets vertical border characters. + * + * + * ╔═══════════════╤══════════════════════════╤══════════════════╗ + * ║ ISBN │ Title │ Author ║ + * ╠═══════1═══════╪══════════════════════════╪══════════════════╣ + * ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║ + * ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║ + * ╟───────2───────┼──────────────────────────┼──────────────────╢ + * ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║ + * ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║ + * ╚═══════════════╧══════════════════════════╧══════════════════╝ + * + * + * @return $this + */ + public function setVerticalBorderChars(string $outside, ?string $inside = null): static + { + $this->verticalOutsideBorderChar = $outside; + $this->verticalInsideBorderChar = $inside ?? $outside; + + return $this; + } + + /** + * Gets border characters. + * + * @internal + */ + public function getBorderChars(): array + { + return [ + $this->horizontalOutsideBorderChar, + $this->verticalOutsideBorderChar, + $this->horizontalInsideBorderChar, + $this->verticalInsideBorderChar, + ]; + } + + /** + * Sets crossing characters. + * + * Example: + * + * 1═══════════════2══════════════════════════2══════════════════3 + * ║ ISBN │ Title │ Author ║ + * 8'══════════════0'═════════════════════════0'═════════════════4' + * ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║ + * ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║ + * 8───────────────0──────────────────────────0──────────────────4 + * ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║ + * ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║ + * 7═══════════════6══════════════════════════6══════════════════5 + * + * + * @param string $cross Crossing char (see #0 of example) + * @param string $topLeft Top left char (see #1 of example) + * @param string $topMid Top mid char (see #2 of example) + * @param string $topRight Top right char (see #3 of example) + * @param string $midRight Mid right char (see #4 of example) + * @param string $bottomRight Bottom right char (see #5 of example) + * @param string $bottomMid Bottom mid char (see #6 of example) + * @param string $bottomLeft Bottom left char (see #7 of example) + * @param string $midLeft Mid left char (see #8 of example) + * @param string|null $topLeftBottom Top left bottom char (see #8' of example), equals to $midLeft if null + * @param string|null $topMidBottom Top mid bottom char (see #0' of example), equals to $cross if null + * @param string|null $topRightBottom Top right bottom char (see #4' of example), equals to $midRight if null + * + * @return $this + */ + public function setCrossingChars(string $cross, string $topLeft, string $topMid, string $topRight, string $midRight, string $bottomRight, string $bottomMid, string $bottomLeft, string $midLeft, ?string $topLeftBottom = null, ?string $topMidBottom = null, ?string $topRightBottom = null): static + { + $this->crossingChar = $cross; + $this->crossingTopLeftChar = $topLeft; + $this->crossingTopMidChar = $topMid; + $this->crossingTopRightChar = $topRight; + $this->crossingMidRightChar = $midRight; + $this->crossingBottomRightChar = $bottomRight; + $this->crossingBottomMidChar = $bottomMid; + $this->crossingBottomLeftChar = $bottomLeft; + $this->crossingMidLeftChar = $midLeft; + $this->crossingTopLeftBottomChar = $topLeftBottom ?? $midLeft; + $this->crossingTopMidBottomChar = $topMidBottom ?? $cross; + $this->crossingTopRightBottomChar = $topRightBottom ?? $midRight; + + return $this; + } + + /** + * Sets default crossing character used for each cross. + * + * @see {@link setCrossingChars()} for setting each crossing individually. + */ + public function setDefaultCrossingChar(string $char): self + { + return $this->setCrossingChars($char, $char, $char, $char, $char, $char, $char, $char, $char); + } + + /** + * Gets crossing character. + */ + public function getCrossingChar(): string + { + return $this->crossingChar; + } + + /** + * Gets crossing characters. + * + * @internal + */ + public function getCrossingChars(): array + { + return [ + $this->crossingChar, + $this->crossingTopLeftChar, + $this->crossingTopMidChar, + $this->crossingTopRightChar, + $this->crossingMidRightChar, + $this->crossingBottomRightChar, + $this->crossingBottomMidChar, + $this->crossingBottomLeftChar, + $this->crossingMidLeftChar, + $this->crossingTopLeftBottomChar, + $this->crossingTopMidBottomChar, + $this->crossingTopRightBottomChar, + ]; + } + + /** + * Sets header cell format. + * + * @return $this + */ + public function setCellHeaderFormat(string $cellHeaderFormat): static + { + $this->cellHeaderFormat = $cellHeaderFormat; + + return $this; + } + + /** + * Gets header cell format. + */ + public function getCellHeaderFormat(): string + { + return $this->cellHeaderFormat; + } + + /** + * Sets row cell format. + * + * @return $this + */ + public function setCellRowFormat(string $cellRowFormat): static + { + $this->cellRowFormat = $cellRowFormat; + + return $this; + } + + /** + * Gets row cell format. + */ + public function getCellRowFormat(): string + { + return $this->cellRowFormat; + } + + /** + * Sets row cell content format. + * + * @return $this + */ + public function setCellRowContentFormat(string $cellRowContentFormat): static + { + $this->cellRowContentFormat = $cellRowContentFormat; + + return $this; + } + + /** + * Gets row cell content format. + */ + public function getCellRowContentFormat(): string + { + return $this->cellRowContentFormat; + } + + /** + * Sets table border format. + * + * @return $this + */ + public function setBorderFormat(string $borderFormat): static + { + $this->borderFormat = $borderFormat; + + return $this; + } + + /** + * Gets table border format. + */ + public function getBorderFormat(): string + { + return $this->borderFormat; + } + + /** + * Sets cell padding type. + * + * @return $this + */ + public function setPadType(int $padType): static + { + if (!\in_array($padType, [\STR_PAD_LEFT, \STR_PAD_RIGHT, \STR_PAD_BOTH], true)) { + throw new InvalidArgumentException('Invalid padding type. Expected one of (STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH).'); + } + + $this->padType = $padType; + + return $this; + } + + /** + * Gets cell padding type. + */ + public function getPadType(): int + { + return $this->padType; + } + + public function getHeaderTitleFormat(): string + { + return $this->headerTitleFormat; + } + + /** + * @return $this + */ + public function setHeaderTitleFormat(string $format): static + { + $this->headerTitleFormat = $format; + + return $this; + } + + public function getFooterTitleFormat(): string + { + return $this->footerTitleFormat; + } + + /** + * @return $this + */ + public function setFooterTitleFormat(string $format): static + { + $this->footerTitleFormat = $format; + + return $this; + } +} diff --git a/vendor/symfony/console/Input/ArgvInput.php b/vendor/symfony/console/Input/ArgvInput.php new file mode 100644 index 00000000..8621d62c --- /dev/null +++ b/vendor/symfony/console/Input/ArgvInput.php @@ -0,0 +1,364 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\RuntimeException; + +/** + * ArgvInput represents an input coming from the CLI arguments. + * + * Usage: + * + * $input = new ArgvInput(); + * + * By default, the `$_SERVER['argv']` array is used for the input values. + * + * This can be overridden by explicitly passing the input values in the constructor: + * + * $input = new ArgvInput($_SERVER['argv']); + * + * If you pass it yourself, don't forget that the first element of the array + * is the name of the running application. + * + * When passing an argument to the constructor, be sure that it respects + * the same rules as the argv one. It's almost always better to use the + * `StringInput` when you want to provide your own input. + * + * @author Fabien Potencier + * + * @see http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html + * @see http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html#tag_12_02 + */ +class ArgvInput extends Input +{ + private array $tokens; + private array $parsed; + + public function __construct(?array $argv = null, ?InputDefinition $definition = null) + { + $argv ??= $_SERVER['argv'] ?? []; + + // strip the application name + array_shift($argv); + + $this->tokens = $argv; + + parent::__construct($definition); + } + + protected function setTokens(array $tokens): void + { + $this->tokens = $tokens; + } + + protected function parse(): void + { + $parseOptions = true; + $this->parsed = $this->tokens; + while (null !== $token = array_shift($this->parsed)) { + $parseOptions = $this->parseToken($token, $parseOptions); + } + } + + protected function parseToken(string $token, bool $parseOptions): bool + { + if ($parseOptions && '' == $token) { + $this->parseArgument($token); + } elseif ($parseOptions && '--' == $token) { + return false; + } elseif ($parseOptions && str_starts_with($token, '--')) { + $this->parseLongOption($token); + } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) { + $this->parseShortOption($token); + } else { + $this->parseArgument($token); + } + + return $parseOptions; + } + + /** + * Parses a short option. + */ + private function parseShortOption(string $token): void + { + $name = substr($token, 1); + + if (\strlen($name) > 1) { + if ($this->definition->hasShortcut($name[0]) && $this->definition->getOptionForShortcut($name[0])->acceptValue()) { + // an option with a value (with no space) + $this->addShortOption($name[0], substr($name, 1)); + } else { + $this->parseShortOptionSet($name); + } + } else { + $this->addShortOption($name, null); + } + } + + /** + * Parses a short option set. + * + * @throws RuntimeException When option given doesn't exist + */ + private function parseShortOptionSet(string $name): void + { + $len = \strlen($name); + for ($i = 0; $i < $len; ++$i) { + if (!$this->definition->hasShortcut($name[$i])) { + $encoding = mb_detect_encoding($name, null, true); + throw new RuntimeException(sprintf('The "-%s" option does not exist.', false === $encoding ? $name[$i] : mb_substr($name, $i, 1, $encoding))); + } + + $option = $this->definition->getOptionForShortcut($name[$i]); + if ($option->acceptValue()) { + $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1)); + + break; + } else { + $this->addLongOption($option->getName(), null); + } + } + } + + /** + * Parses a long option. + */ + private function parseLongOption(string $token): void + { + $name = substr($token, 2); + + if (false !== $pos = strpos($name, '=')) { + if ('' === $value = substr($name, $pos + 1)) { + array_unshift($this->parsed, $value); + } + $this->addLongOption(substr($name, 0, $pos), $value); + } else { + $this->addLongOption($name, null); + } + } + + /** + * Parses an argument. + * + * @throws RuntimeException When too many arguments are given + */ + private function parseArgument(string $token): void + { + $c = \count($this->arguments); + + // if input is expecting another argument, add it + if ($this->definition->hasArgument($c)) { + $arg = $this->definition->getArgument($c); + $this->arguments[$arg->getName()] = $arg->isArray() ? [$token] : $token; + + // if last argument isArray(), append token to last argument + } elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) { + $arg = $this->definition->getArgument($c - 1); + $this->arguments[$arg->getName()][] = $token; + + // unexpected argument + } else { + $all = $this->definition->getArguments(); + $symfonyCommandName = null; + if (($inputArgument = $all[$key = array_key_first($all)] ?? null) && 'command' === $inputArgument->getName()) { + $symfonyCommandName = $this->arguments['command'] ?? null; + unset($all[$key]); + } + + if (\count($all)) { + if ($symfonyCommandName) { + $message = sprintf('Too many arguments to "%s" command, expected arguments "%s".', $symfonyCommandName, implode('" "', array_keys($all))); + } else { + $message = sprintf('Too many arguments, expected arguments "%s".', implode('" "', array_keys($all))); + } + } elseif ($symfonyCommandName) { + $message = sprintf('No arguments expected for "%s" command, got "%s".', $symfonyCommandName, $token); + } else { + $message = sprintf('No arguments expected, got "%s".', $token); + } + + throw new RuntimeException($message); + } + } + + /** + * Adds a short option value. + * + * @throws RuntimeException When option given doesn't exist + */ + private function addShortOption(string $shortcut, mixed $value): void + { + if (!$this->definition->hasShortcut($shortcut)) { + throw new RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut)); + } + + $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); + } + + /** + * Adds a long option value. + * + * @throws RuntimeException When option given doesn't exist + */ + private function addLongOption(string $name, mixed $value): void + { + if (!$this->definition->hasOption($name)) { + if (!$this->definition->hasNegation($name)) { + throw new RuntimeException(sprintf('The "--%s" option does not exist.', $name)); + } + + $optionName = $this->definition->negationToName($name); + if (null !== $value) { + throw new RuntimeException(sprintf('The "--%s" option does not accept a value.', $name)); + } + $this->options[$optionName] = false; + + return; + } + + $option = $this->definition->getOption($name); + + if (null !== $value && !$option->acceptValue()) { + throw new RuntimeException(sprintf('The "--%s" option does not accept a value.', $name)); + } + + if (\in_array($value, ['', null], true) && $option->acceptValue() && \count($this->parsed)) { + // if option accepts an optional or mandatory argument + // let's see if there is one provided + $next = array_shift($this->parsed); + if ((isset($next[0]) && '-' !== $next[0]) || \in_array($next, ['', null], true)) { + $value = $next; + } else { + array_unshift($this->parsed, $next); + } + } + + if (null === $value) { + if ($option->isValueRequired()) { + throw new RuntimeException(sprintf('The "--%s" option requires a value.', $name)); + } + + if (!$option->isArray() && !$option->isValueOptional()) { + $value = true; + } + } + + if ($option->isArray()) { + $this->options[$name][] = $value; + } else { + $this->options[$name] = $value; + } + } + + public function getFirstArgument(): ?string + { + $isOption = false; + foreach ($this->tokens as $i => $token) { + if ($token && '-' === $token[0]) { + if (str_contains($token, '=') || !isset($this->tokens[$i + 1])) { + continue; + } + + // If it's a long option, consider that everything after "--" is the option name. + // Otherwise, use the last char (if it's a short option set, only the last one can take a value with space separator) + $name = '-' === $token[1] ? substr($token, 2) : substr($token, -1); + if (!isset($this->options[$name]) && !$this->definition->hasShortcut($name)) { + // noop + } elseif ((isset($this->options[$name]) || isset($this->options[$name = $this->definition->shortcutToName($name)])) && $this->tokens[$i + 1] === $this->options[$name]) { + $isOption = true; + } + + continue; + } + + if ($isOption) { + $isOption = false; + continue; + } + + return $token; + } + + return null; + } + + public function hasParameterOption(string|array $values, bool $onlyParams = false): bool + { + $values = (array) $values; + + foreach ($this->tokens as $token) { + if ($onlyParams && '--' === $token) { + return false; + } + foreach ($values as $value) { + // Options with values: + // For long options, test for '--option=' at beginning + // For short options, test for '-o' at beginning + $leading = str_starts_with($value, '--') ? $value.'=' : $value; + if ($token === $value || '' !== $leading && str_starts_with($token, $leading)) { + return true; + } + } + } + + return false; + } + + public function getParameterOption(string|array $values, string|bool|int|float|array|null $default = false, bool $onlyParams = false): mixed + { + $values = (array) $values; + $tokens = $this->tokens; + + while (0 < \count($tokens)) { + $token = array_shift($tokens); + if ($onlyParams && '--' === $token) { + return $default; + } + + foreach ($values as $value) { + if ($token === $value) { + return array_shift($tokens); + } + // Options with values: + // For long options, test for '--option=' at beginning + // For short options, test for '-o' at beginning + $leading = str_starts_with($value, '--') ? $value.'=' : $value; + if ('' !== $leading && str_starts_with($token, $leading)) { + return substr($token, \strlen($leading)); + } + } + } + + return $default; + } + + /** + * Returns a stringified representation of the args passed to the command. + */ + public function __toString(): string + { + $tokens = array_map(function ($token) { + if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) { + return $match[1].$this->escapeToken($match[2]); + } + + if ($token && '-' !== $token[0]) { + return $this->escapeToken($token); + } + + return $token; + }, $this->tokens); + + return implode(' ', $tokens); + } +} diff --git a/vendor/symfony/console/Input/ArrayInput.php b/vendor/symfony/console/Input/ArrayInput.php new file mode 100644 index 00000000..9f1fdafc --- /dev/null +++ b/vendor/symfony/console/Input/ArrayInput.php @@ -0,0 +1,193 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\InvalidOptionException; + +/** + * ArrayInput represents an input provided as an array. + * + * Usage: + * + * $input = new ArrayInput(['command' => 'foo:bar', 'foo' => 'bar', '--bar' => 'foobar']); + * + * @author Fabien Potencier + */ +class ArrayInput extends Input +{ + private array $parameters; + + public function __construct(array $parameters, ?InputDefinition $definition = null) + { + $this->parameters = $parameters; + + parent::__construct($definition); + } + + public function getFirstArgument(): ?string + { + foreach ($this->parameters as $param => $value) { + if ($param && \is_string($param) && '-' === $param[0]) { + continue; + } + + return $value; + } + + return null; + } + + public function hasParameterOption(string|array $values, bool $onlyParams = false): bool + { + $values = (array) $values; + + foreach ($this->parameters as $k => $v) { + if (!\is_int($k)) { + $v = $k; + } + + if ($onlyParams && '--' === $v) { + return false; + } + + if (\in_array($v, $values)) { + return true; + } + } + + return false; + } + + public function getParameterOption(string|array $values, string|bool|int|float|array|null $default = false, bool $onlyParams = false): mixed + { + $values = (array) $values; + + foreach ($this->parameters as $k => $v) { + if ($onlyParams && ('--' === $k || (\is_int($k) && '--' === $v))) { + return $default; + } + + if (\is_int($k)) { + if (\in_array($v, $values)) { + return true; + } + } elseif (\in_array($k, $values)) { + return $v; + } + } + + return $default; + } + + /** + * Returns a stringified representation of the args passed to the command. + */ + public function __toString(): string + { + $params = []; + foreach ($this->parameters as $param => $val) { + if ($param && \is_string($param) && '-' === $param[0]) { + $glue = ('-' === $param[1]) ? '=' : ' '; + if (\is_array($val)) { + foreach ($val as $v) { + $params[] = $param.('' != $v ? $glue.$this->escapeToken($v) : ''); + } + } else { + $params[] = $param.('' != $val ? $glue.$this->escapeToken($val) : ''); + } + } else { + $params[] = \is_array($val) ? implode(' ', array_map($this->escapeToken(...), $val)) : $this->escapeToken($val); + } + } + + return implode(' ', $params); + } + + protected function parse(): void + { + foreach ($this->parameters as $key => $value) { + if ('--' === $key) { + return; + } + if (str_starts_with($key, '--')) { + $this->addLongOption(substr($key, 2), $value); + } elseif (str_starts_with($key, '-')) { + $this->addShortOption(substr($key, 1), $value); + } else { + $this->addArgument($key, $value); + } + } + } + + /** + * Adds a short option value. + * + * @throws InvalidOptionException When option given doesn't exist + */ + private function addShortOption(string $shortcut, mixed $value): void + { + if (!$this->definition->hasShortcut($shortcut)) { + throw new InvalidOptionException(sprintf('The "-%s" option does not exist.', $shortcut)); + } + + $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); + } + + /** + * Adds a long option value. + * + * @throws InvalidOptionException When option given doesn't exist + * @throws InvalidOptionException When a required value is missing + */ + private function addLongOption(string $name, mixed $value): void + { + if (!$this->definition->hasOption($name)) { + if (!$this->definition->hasNegation($name)) { + throw new InvalidOptionException(sprintf('The "--%s" option does not exist.', $name)); + } + + $optionName = $this->definition->negationToName($name); + $this->options[$optionName] = false; + + return; + } + + $option = $this->definition->getOption($name); + + if (null === $value) { + if ($option->isValueRequired()) { + throw new InvalidOptionException(sprintf('The "--%s" option requires a value.', $name)); + } + + if (!$option->isValueOptional()) { + $value = true; + } + } + + $this->options[$name] = $value; + } + + /** + * Adds an argument value. + * + * @throws InvalidArgumentException When argument given doesn't exist + */ + private function addArgument(string|int $name, mixed $value): void + { + if (!$this->definition->hasArgument($name)) { + throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $this->arguments[$name] = $value; + } +} diff --git a/vendor/symfony/console/Input/Input.php b/vendor/symfony/console/Input/Input.php new file mode 100644 index 00000000..5a8b9a27 --- /dev/null +++ b/vendor/symfony/console/Input/Input.php @@ -0,0 +1,174 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; + +/** + * Input is the base class for all concrete Input classes. + * + * Three concrete classes are provided by default: + * + * * `ArgvInput`: The input comes from the CLI arguments (argv) + * * `StringInput`: The input is provided as a string + * * `ArrayInput`: The input is provided as an array + * + * @author Fabien Potencier + */ +abstract class Input implements InputInterface, StreamableInputInterface +{ + protected InputDefinition $definition; + /** @var resource */ + protected $stream; + protected array $options = []; + protected array $arguments = []; + protected bool $interactive = true; + + public function __construct(?InputDefinition $definition = null) + { + if (null === $definition) { + $this->definition = new InputDefinition(); + } else { + $this->bind($definition); + $this->validate(); + } + } + + public function bind(InputDefinition $definition): void + { + $this->arguments = []; + $this->options = []; + $this->definition = $definition; + + $this->parse(); + } + + /** + * Processes command line arguments. + */ + abstract protected function parse(): void; + + public function validate(): void + { + $definition = $this->definition; + $givenArguments = $this->arguments; + + $missingArguments = array_filter(array_keys($definition->getArguments()), fn ($argument) => !\array_key_exists($argument, $givenArguments) && $definition->getArgument($argument)->isRequired()); + + if (\count($missingArguments) > 0) { + throw new RuntimeException(sprintf('Not enough arguments (missing: "%s").', implode(', ', $missingArguments))); + } + } + + public function isInteractive(): bool + { + return $this->interactive; + } + + public function setInteractive(bool $interactive): void + { + $this->interactive = $interactive; + } + + public function getArguments(): array + { + return array_merge($this->definition->getArgumentDefaults(), $this->arguments); + } + + public function getArgument(string $name): mixed + { + if (!$this->definition->hasArgument($name)) { + throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + return $this->arguments[$name] ?? $this->definition->getArgument($name)->getDefault(); + } + + public function setArgument(string $name, mixed $value): void + { + if (!$this->definition->hasArgument($name)) { + throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $this->arguments[$name] = $value; + } + + public function hasArgument(string $name): bool + { + return $this->definition->hasArgument($name); + } + + public function getOptions(): array + { + return array_merge($this->definition->getOptionDefaults(), $this->options); + } + + public function getOption(string $name): mixed + { + if ($this->definition->hasNegation($name)) { + if (null === $value = $this->getOption($this->definition->negationToName($name))) { + return $value; + } + + return !$value; + } + + if (!$this->definition->hasOption($name)) { + throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); + } + + return \array_key_exists($name, $this->options) ? $this->options[$name] : $this->definition->getOption($name)->getDefault(); + } + + public function setOption(string $name, mixed $value): void + { + if ($this->definition->hasNegation($name)) { + $this->options[$this->definition->negationToName($name)] = !$value; + + return; + } elseif (!$this->definition->hasOption($name)) { + throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); + } + + $this->options[$name] = $value; + } + + public function hasOption(string $name): bool + { + return $this->definition->hasOption($name) || $this->definition->hasNegation($name); + } + + /** + * Escapes a token through escapeshellarg if it contains unsafe chars. + */ + public function escapeToken(string $token): string + { + return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token); + } + + /** + * @param resource $stream + */ + public function setStream($stream): void + { + $this->stream = $stream; + } + + /** + * @return resource + */ + public function getStream() + { + return $this->stream; + } +} diff --git a/vendor/symfony/console/Input/InputArgument.php b/vendor/symfony/console/Input/InputArgument.php new file mode 100644 index 00000000..bcc464c9 --- /dev/null +++ b/vendor/symfony/console/Input/InputArgument.php @@ -0,0 +1,149 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Completion\Suggestion; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; + +/** + * Represents a command line argument. + * + * @author Fabien Potencier + */ +class InputArgument +{ + public const REQUIRED = 1; + public const OPTIONAL = 2; + public const IS_ARRAY = 4; + + private string $name; + private int $mode; + private string|int|bool|array|null|float $default; + private array|\Closure $suggestedValues; + private string $description; + + /** + * @param string $name The argument name + * @param int|null $mode The argument mode: a bit mask of self::REQUIRED, self::OPTIONAL and self::IS_ARRAY + * @param string $description A description text + * @param string|bool|int|float|array|null $default The default value (for self::OPTIONAL mode only) + * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion + * + * @throws InvalidArgumentException When argument mode is not valid + */ + public function __construct(string $name, ?int $mode = null, string $description = '', string|bool|int|float|array|null $default = null, \Closure|array $suggestedValues = []) + { + if (null === $mode) { + $mode = self::OPTIONAL; + } elseif ($mode > 7 || $mode < 1) { + throw new InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode)); + } + + $this->name = $name; + $this->mode = $mode; + $this->description = $description; + $this->suggestedValues = $suggestedValues; + + $this->setDefault($default); + } + + /** + * Returns the argument name. + */ + public function getName(): string + { + return $this->name; + } + + /** + * Returns true if the argument is required. + * + * @return bool true if parameter mode is self::REQUIRED, false otherwise + */ + public function isRequired(): bool + { + return self::REQUIRED === (self::REQUIRED & $this->mode); + } + + /** + * Returns true if the argument can take multiple values. + * + * @return bool true if mode is self::IS_ARRAY, false otherwise + */ + public function isArray(): bool + { + return self::IS_ARRAY === (self::IS_ARRAY & $this->mode); + } + + /** + * Sets the default value. + * + * @throws LogicException When incorrect default value is given + */ + public function setDefault(string|bool|int|float|array|null $default): void + { + if ($this->isRequired() && null !== $default) { + throw new LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.'); + } + + if ($this->isArray()) { + if (null === $default) { + $default = []; + } elseif (!\is_array($default)) { + throw new LogicException('A default value for an array argument must be an array.'); + } + } + + $this->default = $default; + } + + /** + * Returns the default value. + */ + public function getDefault(): string|bool|int|float|array|null + { + return $this->default; + } + + public function hasCompletion(): bool + { + return [] !== $this->suggestedValues; + } + + /** + * Adds suggestions to $suggestions for the current completion input. + * + * @see Command::complete() + */ + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + $values = $this->suggestedValues; + if ($values instanceof \Closure && !\is_array($values = $values($input))) { + throw new LogicException(sprintf('Closure for argument "%s" must return an array. Got "%s".', $this->name, get_debug_type($values))); + } + if ($values) { + $suggestions->suggestValues($values); + } + } + + /** + * Returns the description text. + */ + public function getDescription(): string + { + return $this->description; + } +} diff --git a/vendor/symfony/console/Input/InputAwareInterface.php b/vendor/symfony/console/Input/InputAwareInterface.php new file mode 100644 index 00000000..ba4664cd --- /dev/null +++ b/vendor/symfony/console/Input/InputAwareInterface.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +/** + * InputAwareInterface should be implemented by classes that depends on the + * Console Input. + * + * @author Wouter J + */ +interface InputAwareInterface +{ + /** + * Sets the Console Input. + */ + public function setInput(InputInterface $input): void; +} diff --git a/vendor/symfony/console/Input/InputDefinition.php b/vendor/symfony/console/Input/InputDefinition.php new file mode 100644 index 00000000..f27e2974 --- /dev/null +++ b/vendor/symfony/console/Input/InputDefinition.php @@ -0,0 +1,402 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; + +/** + * A InputDefinition represents a set of valid command line arguments and options. + * + * Usage: + * + * $definition = new InputDefinition([ + * new InputArgument('name', InputArgument::REQUIRED), + * new InputOption('foo', 'f', InputOption::VALUE_REQUIRED), + * ]); + * + * @author Fabien Potencier + */ +class InputDefinition +{ + private array $arguments = []; + private int $requiredCount = 0; + private ?InputArgument $lastArrayArgument = null; + private ?InputArgument $lastOptionalArgument = null; + private array $options = []; + private array $negations = []; + private array $shortcuts = []; + + /** + * @param array $definition An array of InputArgument and InputOption instance + */ + public function __construct(array $definition = []) + { + $this->setDefinition($definition); + } + + /** + * Sets the definition of the input. + */ + public function setDefinition(array $definition): void + { + $arguments = []; + $options = []; + foreach ($definition as $item) { + if ($item instanceof InputOption) { + $options[] = $item; + } else { + $arguments[] = $item; + } + } + + $this->setArguments($arguments); + $this->setOptions($options); + } + + /** + * Sets the InputArgument objects. + * + * @param InputArgument[] $arguments An array of InputArgument objects + */ + public function setArguments(array $arguments = []): void + { + $this->arguments = []; + $this->requiredCount = 0; + $this->lastOptionalArgument = null; + $this->lastArrayArgument = null; + $this->addArguments($arguments); + } + + /** + * Adds an array of InputArgument objects. + * + * @param InputArgument[] $arguments An array of InputArgument objects + */ + public function addArguments(?array $arguments = []): void + { + if (null !== $arguments) { + foreach ($arguments as $argument) { + $this->addArgument($argument); + } + } + } + + /** + * @throws LogicException When incorrect argument is given + */ + public function addArgument(InputArgument $argument): void + { + if (isset($this->arguments[$argument->getName()])) { + throw new LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName())); + } + + if (null !== $this->lastArrayArgument) { + throw new LogicException(sprintf('Cannot add a required argument "%s" after an array argument "%s".', $argument->getName(), $this->lastArrayArgument->getName())); + } + + if ($argument->isRequired() && null !== $this->lastOptionalArgument) { + throw new LogicException(sprintf('Cannot add a required argument "%s" after an optional one "%s".', $argument->getName(), $this->lastOptionalArgument->getName())); + } + + if ($argument->isArray()) { + $this->lastArrayArgument = $argument; + } + + if ($argument->isRequired()) { + ++$this->requiredCount; + } else { + $this->lastOptionalArgument = $argument; + } + + $this->arguments[$argument->getName()] = $argument; + } + + /** + * Returns an InputArgument by name or by position. + * + * @throws InvalidArgumentException When argument given doesn't exist + */ + public function getArgument(string|int $name): InputArgument + { + if (!$this->hasArgument($name)) { + throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $arguments = \is_int($name) ? array_values($this->arguments) : $this->arguments; + + return $arguments[$name]; + } + + /** + * Returns true if an InputArgument object exists by name or position. + */ + public function hasArgument(string|int $name): bool + { + $arguments = \is_int($name) ? array_values($this->arguments) : $this->arguments; + + return isset($arguments[$name]); + } + + /** + * Gets the array of InputArgument objects. + * + * @return InputArgument[] + */ + public function getArguments(): array + { + return $this->arguments; + } + + /** + * Returns the number of InputArguments. + */ + public function getArgumentCount(): int + { + return null !== $this->lastArrayArgument ? \PHP_INT_MAX : \count($this->arguments); + } + + /** + * Returns the number of required InputArguments. + */ + public function getArgumentRequiredCount(): int + { + return $this->requiredCount; + } + + /** + * @return array + */ + public function getArgumentDefaults(): array + { + $values = []; + foreach ($this->arguments as $argument) { + $values[$argument->getName()] = $argument->getDefault(); + } + + return $values; + } + + /** + * Sets the InputOption objects. + * + * @param InputOption[] $options An array of InputOption objects + */ + public function setOptions(array $options = []): void + { + $this->options = []; + $this->shortcuts = []; + $this->negations = []; + $this->addOptions($options); + } + + /** + * Adds an array of InputOption objects. + * + * @param InputOption[] $options An array of InputOption objects + */ + public function addOptions(array $options = []): void + { + foreach ($options as $option) { + $this->addOption($option); + } + } + + /** + * @throws LogicException When option given already exist + */ + public function addOption(InputOption $option): void + { + if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) { + throw new LogicException(sprintf('An option named "%s" already exists.', $option->getName())); + } + if (isset($this->negations[$option->getName()])) { + throw new LogicException(sprintf('An option named "%s" already exists.', $option->getName())); + } + + if ($option->getShortcut()) { + foreach (explode('|', $option->getShortcut()) as $shortcut) { + if (isset($this->shortcuts[$shortcut]) && !$option->equals($this->options[$this->shortcuts[$shortcut]])) { + throw new LogicException(sprintf('An option with shortcut "%s" already exists.', $shortcut)); + } + } + } + + $this->options[$option->getName()] = $option; + if ($option->getShortcut()) { + foreach (explode('|', $option->getShortcut()) as $shortcut) { + $this->shortcuts[$shortcut] = $option->getName(); + } + } + + if ($option->isNegatable()) { + $negatedName = 'no-'.$option->getName(); + if (isset($this->options[$negatedName])) { + throw new LogicException(sprintf('An option named "%s" already exists.', $negatedName)); + } + $this->negations[$negatedName] = $option->getName(); + } + } + + /** + * Returns an InputOption by name. + * + * @throws InvalidArgumentException When option given doesn't exist + */ + public function getOption(string $name): InputOption + { + if (!$this->hasOption($name)) { + throw new InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name)); + } + + return $this->options[$name]; + } + + /** + * Returns true if an InputOption object exists by name. + * + * This method can't be used to check if the user included the option when + * executing the command (use getOption() instead). + */ + public function hasOption(string $name): bool + { + return isset($this->options[$name]); + } + + /** + * Gets the array of InputOption objects. + * + * @return InputOption[] + */ + public function getOptions(): array + { + return $this->options; + } + + /** + * Returns true if an InputOption object exists by shortcut. + */ + public function hasShortcut(string $name): bool + { + return isset($this->shortcuts[$name]); + } + + /** + * Returns true if an InputOption object exists by negated name. + */ + public function hasNegation(string $name): bool + { + return isset($this->negations[$name]); + } + + /** + * Gets an InputOption by shortcut. + */ + public function getOptionForShortcut(string $shortcut): InputOption + { + return $this->getOption($this->shortcutToName($shortcut)); + } + + /** + * @return array + */ + public function getOptionDefaults(): array + { + $values = []; + foreach ($this->options as $option) { + $values[$option->getName()] = $option->getDefault(); + } + + return $values; + } + + /** + * Returns the InputOption name given a shortcut. + * + * @throws InvalidArgumentException When option given does not exist + * + * @internal + */ + public function shortcutToName(string $shortcut): string + { + if (!isset($this->shortcuts[$shortcut])) { + throw new InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut)); + } + + return $this->shortcuts[$shortcut]; + } + + /** + * Returns the InputOption name given a negation. + * + * @throws InvalidArgumentException When option given does not exist + * + * @internal + */ + public function negationToName(string $negation): string + { + if (!isset($this->negations[$negation])) { + throw new InvalidArgumentException(sprintf('The "--%s" option does not exist.', $negation)); + } + + return $this->negations[$negation]; + } + + /** + * Gets the synopsis. + */ + public function getSynopsis(bool $short = false): string + { + $elements = []; + + if ($short && $this->getOptions()) { + $elements[] = '[options]'; + } elseif (!$short) { + foreach ($this->getOptions() as $option) { + $value = ''; + if ($option->acceptValue()) { + $value = sprintf( + ' %s%s%s', + $option->isValueOptional() ? '[' : '', + strtoupper($option->getName()), + $option->isValueOptional() ? ']' : '' + ); + } + + $shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : ''; + $negation = $option->isNegatable() ? sprintf('|--no-%s', $option->getName()) : ''; + $elements[] = sprintf('[%s--%s%s%s]', $shortcut, $option->getName(), $value, $negation); + } + } + + if (\count($elements) && $this->getArguments()) { + $elements[] = '[--]'; + } + + $tail = ''; + foreach ($this->getArguments() as $argument) { + $element = '<'.$argument->getName().'>'; + if ($argument->isArray()) { + $element .= '...'; + } + + if (!$argument->isRequired()) { + $element = '['.$element; + $tail .= ']'; + } + + $elements[] = $element; + } + + return implode(' ', $elements).$tail; + } +} diff --git a/vendor/symfony/console/Input/InputInterface.php b/vendor/symfony/console/Input/InputInterface.php new file mode 100644 index 00000000..c177d960 --- /dev/null +++ b/vendor/symfony/console/Input/InputInterface.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; + +/** + * InputInterface is the interface implemented by all input classes. + * + * @author Fabien Potencier + */ +interface InputInterface +{ + /** + * Returns the first argument from the raw parameters (not parsed). + */ + public function getFirstArgument(): ?string; + + /** + * Returns true if the raw parameters (not parsed) contain a value. + * + * This method is to be used to introspect the input parameters + * before they have been validated. It must be used carefully. + * Does not necessarily return the correct result for short options + * when multiple flags are combined in the same option. + * + * @param string|array $values The values to look for in the raw parameters (can be an array) + * @param bool $onlyParams Only check real parameters, skip those following an end of options (--) signal + */ + public function hasParameterOption(string|array $values, bool $onlyParams = false): bool; + + /** + * Returns the value of a raw option (not parsed). + * + * This method is to be used to introspect the input parameters + * before they have been validated. It must be used carefully. + * Does not necessarily return the correct result for short options + * when multiple flags are combined in the same option. + * + * @param string|array $values The value(s) to look for in the raw parameters (can be an array) + * @param string|bool|int|float|array|null $default The default value to return if no result is found + * @param bool $onlyParams Only check real parameters, skip those following an end of options (--) signal + */ + public function getParameterOption(string|array $values, string|bool|int|float|array|null $default = false, bool $onlyParams = false): mixed; + + /** + * Binds the current Input instance with the given arguments and options. + * + * @throws RuntimeException + */ + public function bind(InputDefinition $definition): void; + + /** + * Validates the input. + * + * @throws RuntimeException When not enough arguments are given + */ + public function validate(): void; + + /** + * Returns all the given arguments merged with the default values. + * + * @return array + */ + public function getArguments(): array; + + /** + * Returns the argument value for a given argument name. + * + * @throws InvalidArgumentException When argument given doesn't exist + */ + public function getArgument(string $name): mixed; + + /** + * Sets an argument value by name. + * + * @throws InvalidArgumentException When argument given doesn't exist + */ + public function setArgument(string $name, mixed $value): void; + + /** + * Returns true if an InputArgument object exists by name or position. + */ + public function hasArgument(string $name): bool; + + /** + * Returns all the given options merged with the default values. + * + * @return array + */ + public function getOptions(): array; + + /** + * Returns the option value for a given option name. + * + * @throws InvalidArgumentException When option given doesn't exist + */ + public function getOption(string $name): mixed; + + /** + * Sets an option value by name. + * + * @throws InvalidArgumentException When option given doesn't exist + */ + public function setOption(string $name, mixed $value): void; + + /** + * Returns true if an InputOption object exists by name. + */ + public function hasOption(string $name): bool; + + /** + * Is this input means interactive? + */ + public function isInteractive(): bool; + + /** + * Sets the input interactivity. + */ + public function setInteractive(bool $interactive): void; + + /** + * Returns a stringified representation of the args passed to the command. + * + * InputArguments MUST be escaped as well as the InputOption values passed to the command. + */ + public function __toString(): string; +} diff --git a/vendor/symfony/console/Input/InputOption.php b/vendor/symfony/console/Input/InputOption.php new file mode 100644 index 00000000..56691c3b --- /dev/null +++ b/vendor/symfony/console/Input/InputOption.php @@ -0,0 +1,249 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Completion\Suggestion; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; + +/** + * Represents a command line option. + * + * @author Fabien Potencier + */ +class InputOption +{ + /** + * Do not accept input for the option (e.g. --yell). This is the default behavior of options. + */ + public const VALUE_NONE = 1; + + /** + * A value must be passed when the option is used (e.g. --iterations=5 or -i5). + */ + public const VALUE_REQUIRED = 2; + + /** + * The option may or may not have a value (e.g. --yell or --yell=loud). + */ + public const VALUE_OPTIONAL = 4; + + /** + * The option accepts multiple values (e.g. --dir=/foo --dir=/bar). + */ + public const VALUE_IS_ARRAY = 8; + + /** + * The option may have either positive or negative value (e.g. --ansi or --no-ansi). + */ + public const VALUE_NEGATABLE = 16; + + private string $name; + private string|array|null $shortcut; + private int $mode; + private string|int|bool|array|null|float $default; + private array|\Closure $suggestedValues; + private string $description; + + /** + * @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts + * @param int|null $mode The option mode: One of the VALUE_* constants + * @param string|bool|int|float|array|null $default The default value (must be null for self::VALUE_NONE) + * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion + * + * @throws InvalidArgumentException If option mode is invalid or incompatible + */ + public function __construct(string $name, string|array|null $shortcut = null, ?int $mode = null, string $description = '', string|bool|int|float|array|null $default = null, array|\Closure $suggestedValues = []) + { + if (str_starts_with($name, '--')) { + $name = substr($name, 2); + } + + if (empty($name)) { + throw new InvalidArgumentException('An option name cannot be empty.'); + } + + if ('' === $shortcut || [] === $shortcut || false === $shortcut) { + $shortcut = null; + } + + if (null !== $shortcut) { + if (\is_array($shortcut)) { + $shortcut = implode('|', $shortcut); + } + $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-')); + $shortcuts = array_filter($shortcuts, 'strlen'); + $shortcut = implode('|', $shortcuts); + + if ('' === $shortcut) { + throw new InvalidArgumentException('An option shortcut cannot be empty.'); + } + } + + if (null === $mode) { + $mode = self::VALUE_NONE; + } elseif ($mode >= (self::VALUE_NEGATABLE << 1) || $mode < 1) { + throw new InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode)); + } + + $this->name = $name; + $this->shortcut = $shortcut; + $this->mode = $mode; + $this->description = $description; + $this->suggestedValues = $suggestedValues; + + if ($suggestedValues && !$this->acceptValue()) { + throw new LogicException('Cannot set suggested values if the option does not accept a value.'); + } + if ($this->isArray() && !$this->acceptValue()) { + throw new InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.'); + } + if ($this->isNegatable() && $this->acceptValue()) { + throw new InvalidArgumentException('Impossible to have an option mode VALUE_NEGATABLE if the option also accepts a value.'); + } + + $this->setDefault($default); + } + + /** + * Returns the option shortcut. + */ + public function getShortcut(): ?string + { + return $this->shortcut; + } + + /** + * Returns the option name. + */ + public function getName(): string + { + return $this->name; + } + + /** + * Returns true if the option accepts a value. + * + * @return bool true if value mode is not self::VALUE_NONE, false otherwise + */ + public function acceptValue(): bool + { + return $this->isValueRequired() || $this->isValueOptional(); + } + + /** + * Returns true if the option requires a value. + * + * @return bool true if value mode is self::VALUE_REQUIRED, false otherwise + */ + public function isValueRequired(): bool + { + return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode); + } + + /** + * Returns true if the option takes an optional value. + * + * @return bool true if value mode is self::VALUE_OPTIONAL, false otherwise + */ + public function isValueOptional(): bool + { + return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode); + } + + /** + * Returns true if the option can take multiple values. + * + * @return bool true if mode is self::VALUE_IS_ARRAY, false otherwise + */ + public function isArray(): bool + { + return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode); + } + + public function isNegatable(): bool + { + return self::VALUE_NEGATABLE === (self::VALUE_NEGATABLE & $this->mode); + } + + public function setDefault(string|bool|int|float|array|null $default): void + { + if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) { + throw new LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.'); + } + + if ($this->isArray()) { + if (null === $default) { + $default = []; + } elseif (!\is_array($default)) { + throw new LogicException('A default value for an array option must be an array.'); + } + } + + $this->default = $this->acceptValue() || $this->isNegatable() ? $default : false; + } + + /** + * Returns the default value. + */ + public function getDefault(): string|bool|int|float|array|null + { + return $this->default; + } + + /** + * Returns the description text. + */ + public function getDescription(): string + { + return $this->description; + } + + public function hasCompletion(): bool + { + return [] !== $this->suggestedValues; + } + + /** + * Adds suggestions to $suggestions for the current completion input. + * + * @see Command::complete() + */ + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + $values = $this->suggestedValues; + if ($values instanceof \Closure && !\is_array($values = $values($input))) { + throw new LogicException(sprintf('Closure for option "%s" must return an array. Got "%s".', $this->name, get_debug_type($values))); + } + if ($values) { + $suggestions->suggestValues($values); + } + } + + /** + * Checks whether the given option equals this one. + */ + public function equals(self $option): bool + { + return $option->getName() === $this->getName() + && $option->getShortcut() === $this->getShortcut() + && $option->getDefault() === $this->getDefault() + && $option->isNegatable() === $this->isNegatable() + && $option->isArray() === $this->isArray() + && $option->isValueRequired() === $this->isValueRequired() + && $option->isValueOptional() === $this->isValueOptional() + ; + } +} diff --git a/vendor/symfony/console/Input/StreamableInputInterface.php b/vendor/symfony/console/Input/StreamableInputInterface.php new file mode 100644 index 00000000..4a0dc017 --- /dev/null +++ b/vendor/symfony/console/Input/StreamableInputInterface.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +/** + * StreamableInputInterface is the interface implemented by all input classes + * that have an input stream. + * + * @author Robin Chalas + */ +interface StreamableInputInterface extends InputInterface +{ + /** + * Sets the input stream to read from when interacting with the user. + * + * This is mainly useful for testing purpose. + * + * @param resource $stream The input stream + */ + public function setStream($stream): void; + + /** + * Returns the input stream. + * + * @return resource|null + */ + public function getStream(); +} diff --git a/vendor/symfony/console/Input/StringInput.php b/vendor/symfony/console/Input/StringInput.php new file mode 100644 index 00000000..33f0f4b3 --- /dev/null +++ b/vendor/symfony/console/Input/StringInput.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * StringInput represents an input provided as a string. + * + * Usage: + * + * $input = new StringInput('foo --bar="foobar"'); + * + * @author Fabien Potencier + */ +class StringInput extends ArgvInput +{ + public const REGEX_UNQUOTED_STRING = '([^\s\\\\]+?)'; + public const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')'; + + /** + * @param string $input A string representing the parameters from the CLI + */ + public function __construct(string $input) + { + parent::__construct([]); + + $this->setTokens($this->tokenize($input)); + } + + /** + * Tokenizes a string. + * + * @throws InvalidArgumentException When unable to parse input (should never happen) + */ + private function tokenize(string $input): array + { + $tokens = []; + $length = \strlen($input); + $cursor = 0; + $token = null; + while ($cursor < $length) { + if ('\\' === $input[$cursor]) { + $token .= $input[++$cursor] ?? ''; + ++$cursor; + continue; + } + + if (preg_match('/\s+/A', $input, $match, 0, $cursor)) { + if (null !== $token) { + $tokens[] = $token; + $token = null; + } + } elseif (preg_match('/([^="\'\s]+?)(=?)('.self::REGEX_QUOTED_STRING.'+)/A', $input, $match, 0, $cursor)) { + $token .= $match[1].$match[2].stripcslashes(str_replace(['"\'', '\'"', '\'\'', '""'], '', substr($match[3], 1, -1))); + } elseif (preg_match('/'.self::REGEX_QUOTED_STRING.'/A', $input, $match, 0, $cursor)) { + $token .= stripcslashes(substr($match[0], 1, -1)); + } elseif (preg_match('/'.self::REGEX_UNQUOTED_STRING.'/A', $input, $match, 0, $cursor)) { + $token .= $match[1]; + } else { + // should never happen + throw new InvalidArgumentException(sprintf('Unable to parse input near "... %s ...".', substr($input, $cursor, 10))); + } + + $cursor += \strlen($match[0]); + } + + if (null !== $token) { + $tokens[] = $token; + } + + return $tokens; + } +} diff --git a/vendor/symfony/console/LICENSE b/vendor/symfony/console/LICENSE new file mode 100644 index 00000000..0138f8f0 --- /dev/null +++ b/vendor/symfony/console/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/console/Logger/ConsoleLogger.php b/vendor/symfony/console/Logger/ConsoleLogger.php new file mode 100644 index 00000000..fddef50c --- /dev/null +++ b/vendor/symfony/console/Logger/ConsoleLogger.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Logger; + +use Psr\Log\AbstractLogger; +use Psr\Log\InvalidArgumentException; +use Psr\Log\LogLevel; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * PSR-3 compliant console logger. + * + * @author Kévin Dunglas + * + * @see https://www.php-fig.org/psr/psr-3/ + */ +class ConsoleLogger extends AbstractLogger +{ + public const INFO = 'info'; + public const ERROR = 'error'; + + private OutputInterface $output; + private array $verbosityLevelMap = [ + LogLevel::EMERGENCY => OutputInterface::VERBOSITY_NORMAL, + LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL, + LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL, + LogLevel::ERROR => OutputInterface::VERBOSITY_NORMAL, + LogLevel::WARNING => OutputInterface::VERBOSITY_NORMAL, + LogLevel::NOTICE => OutputInterface::VERBOSITY_VERBOSE, + LogLevel::INFO => OutputInterface::VERBOSITY_VERY_VERBOSE, + LogLevel::DEBUG => OutputInterface::VERBOSITY_DEBUG, + ]; + private array $formatLevelMap = [ + LogLevel::EMERGENCY => self::ERROR, + LogLevel::ALERT => self::ERROR, + LogLevel::CRITICAL => self::ERROR, + LogLevel::ERROR => self::ERROR, + LogLevel::WARNING => self::INFO, + LogLevel::NOTICE => self::INFO, + LogLevel::INFO => self::INFO, + LogLevel::DEBUG => self::INFO, + ]; + private bool $errored = false; + + public function __construct(OutputInterface $output, array $verbosityLevelMap = [], array $formatLevelMap = []) + { + $this->output = $output; + $this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap; + $this->formatLevelMap = $formatLevelMap + $this->formatLevelMap; + } + + public function log($level, $message, array $context = []): void + { + if (!isset($this->verbosityLevelMap[$level])) { + throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level)); + } + + $output = $this->output; + + // Write to the error output if necessary and available + if (self::ERROR === $this->formatLevelMap[$level]) { + if ($this->output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + $this->errored = true; + } + + // the if condition check isn't necessary -- it's the same one that $output will do internally anyway. + // We only do it for efficiency here as the message formatting is relatively expensive. + if ($output->getVerbosity() >= $this->verbosityLevelMap[$level]) { + $output->writeln(sprintf('<%1$s>[%2$s] %3$s', $this->formatLevelMap[$level], $level, $this->interpolate($message, $context)), $this->verbosityLevelMap[$level]); + } + } + + /** + * Returns true when any messages have been logged at error levels. + */ + public function hasErrored(): bool + { + return $this->errored; + } + + /** + * Interpolates context values into the message placeholders. + * + * @author PHP Framework Interoperability Group + */ + private function interpolate(string $message, array $context): string + { + if (!str_contains($message, '{')) { + return $message; + } + + $replacements = []; + foreach ($context as $key => $val) { + if (null === $val || \is_scalar($val) || $val instanceof \Stringable) { + $replacements["{{$key}}"] = $val; + } elseif ($val instanceof \DateTimeInterface) { + $replacements["{{$key}}"] = $val->format(\DateTimeInterface::RFC3339); + } elseif (\is_object($val)) { + $replacements["{{$key}}"] = '[object '.$val::class.']'; + } else { + $replacements["{{$key}}"] = '['.\gettype($val).']'; + } + } + + return strtr($message, $replacements); + } +} diff --git a/vendor/symfony/console/Messenger/RunCommandContext.php b/vendor/symfony/console/Messenger/RunCommandContext.php new file mode 100644 index 00000000..2ee5415c --- /dev/null +++ b/vendor/symfony/console/Messenger/RunCommandContext.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Messenger; + +/** + * @author Kevin Bond + */ +final class RunCommandContext +{ + public function __construct( + public readonly RunCommandMessage $message, + public readonly int $exitCode, + public readonly string $output, + ) { + } +} diff --git a/vendor/symfony/console/Messenger/RunCommandMessage.php b/vendor/symfony/console/Messenger/RunCommandMessage.php new file mode 100644 index 00000000..b530c438 --- /dev/null +++ b/vendor/symfony/console/Messenger/RunCommandMessage.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Messenger; + +use Symfony\Component\Console\Exception\RunCommandFailedException; + +/** + * @author Kevin Bond + */ +class RunCommandMessage implements \Stringable +{ + /** + * @param bool $throwOnFailure If the command has a non-zero exit code, throw {@see RunCommandFailedException} + * @param bool $catchExceptions @see Application::setCatchExceptions() + */ + public function __construct( + public readonly string $input, + public readonly bool $throwOnFailure = true, + public readonly bool $catchExceptions = false, + ) { + } + + public function __toString(): string + { + return $this->input; + } +} diff --git a/vendor/symfony/console/Messenger/RunCommandMessageHandler.php b/vendor/symfony/console/Messenger/RunCommandMessageHandler.php new file mode 100644 index 00000000..14f9c176 --- /dev/null +++ b/vendor/symfony/console/Messenger/RunCommandMessageHandler.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Messenger; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\RunCommandFailedException; +use Symfony\Component\Console\Input\StringInput; +use Symfony\Component\Console\Output\BufferedOutput; + +/** + * @author Kevin Bond + */ +final class RunCommandMessageHandler +{ + public function __construct(private readonly Application $application) + { + } + + public function __invoke(RunCommandMessage $message): RunCommandContext + { + $input = new StringInput($message->input); + $output = new BufferedOutput(); + + $this->application->setCatchExceptions($message->catchExceptions); + + try { + $exitCode = $this->application->run($input, $output); + } catch (\Throwable $e) { + throw new RunCommandFailedException($e, new RunCommandContext($message, Command::FAILURE, $output->fetch())); + } + + if ($message->throwOnFailure && Command::SUCCESS !== $exitCode) { + throw new RunCommandFailedException(sprintf('Command "%s" exited with code "%s".', $message->input, $exitCode), new RunCommandContext($message, $exitCode, $output->fetch())); + } + + return new RunCommandContext($message, $exitCode, $output->fetch()); + } +} diff --git a/vendor/symfony/console/Output/AnsiColorMode.php b/vendor/symfony/console/Output/AnsiColorMode.php new file mode 100644 index 00000000..5f9f744f --- /dev/null +++ b/vendor/symfony/console/Output/AnsiColorMode.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * @author Fabien Potencier + * @author Julien Boudry + */ +enum AnsiColorMode +{ + /* + * Classical 4-bit Ansi colors, including 8 classical colors and 8 bright color. Output syntax is "ESC[${foreGroundColorcode};${backGroundColorcode}m" + * Must be compatible with all terminals and it's the minimal version supported. + */ + case Ansi4; + + /* + * 8-bit Ansi colors (240 different colors + 16 duplicate color codes, ensuring backward compatibility). + * Output syntax is: "ESC[38;5;${foreGroundColorcode};48;5;${backGroundColorcode}m" + * Should be compatible with most terminals. + */ + case Ansi8; + + /* + * 24-bit Ansi colors (RGB). + * Output syntax is: "ESC[38;2;${foreGroundColorcodeRed};${foreGroundColorcodeGreen};${foreGroundColorcodeBlue};48;2;${backGroundColorcodeRed};${backGroundColorcodeGreen};${backGroundColorcodeBlue}m" + * May be compatible with many modern terminals. + */ + case Ansi24; + + /** + * Converts an RGB hexadecimal color to the corresponding Ansi code. + */ + public function convertFromHexToAnsiColorCode(string $hexColor): string + { + $hexColor = str_replace('#', '', $hexColor); + + if (3 === \strlen($hexColor)) { + $hexColor = $hexColor[0].$hexColor[0].$hexColor[1].$hexColor[1].$hexColor[2].$hexColor[2]; + } + + if (6 !== \strlen($hexColor)) { + throw new InvalidArgumentException(sprintf('Invalid "#%s" color.', $hexColor)); + } + + $color = hexdec($hexColor); + + $r = ($color >> 16) & 255; + $g = ($color >> 8) & 255; + $b = $color & 255; + + return match ($this) { + self::Ansi4 => (string) $this->convertFromRGB($r, $g, $b), + self::Ansi8 => '8;5;'.((string) $this->convertFromRGB($r, $g, $b)), + self::Ansi24 => sprintf('8;2;%d;%d;%d', $r, $g, $b) + }; + } + + private function convertFromRGB(int $r, int $g, int $b): int + { + return match ($this) { + self::Ansi4 => $this->degradeHexColorToAnsi4($r, $g, $b), + self::Ansi8 => $this->degradeHexColorToAnsi8($r, $g, $b), + default => throw new InvalidArgumentException("RGB cannot be converted to {$this->name}.") + }; + } + + private function degradeHexColorToAnsi4(int $r, int $g, int $b): int + { + return round($b / 255) << 2 | (round($g / 255) << 1) | round($r / 255); + } + + /** + * Inspired from https://github.com/ajalt/colormath/blob/e464e0da1b014976736cf97250063248fc77b8e7/colormath/src/commonMain/kotlin/com/github/ajalt/colormath/model/Ansi256.kt code (MIT license). + */ + private function degradeHexColorToAnsi8(int $r, int $g, int $b): int + { + if ($r === $g && $g === $b) { + if ($r < 8) { + return 16; + } + + if ($r > 248) { + return 231; + } + + return (int) round(($r - 8) / 247 * 24) + 232; + } else { + return 16 + + (36 * (int) round($r / 255 * 5)) + + (6 * (int) round($g / 255 * 5)) + + (int) round($b / 255 * 5); + } + } +} diff --git a/vendor/symfony/console/Output/BufferedOutput.php b/vendor/symfony/console/Output/BufferedOutput.php new file mode 100644 index 00000000..3c8d3906 --- /dev/null +++ b/vendor/symfony/console/Output/BufferedOutput.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +/** + * @author Jean-François Simon + */ +class BufferedOutput extends Output +{ + private string $buffer = ''; + + /** + * Empties buffer and returns its content. + */ + public function fetch(): string + { + $content = $this->buffer; + $this->buffer = ''; + + return $content; + } + + protected function doWrite(string $message, bool $newline): void + { + $this->buffer .= $message; + + if ($newline) { + $this->buffer .= \PHP_EOL; + } + } +} diff --git a/vendor/symfony/console/Output/ConsoleOutput.php b/vendor/symfony/console/Output/ConsoleOutput.php new file mode 100644 index 00000000..2ad3dbcf --- /dev/null +++ b/vendor/symfony/console/Output/ConsoleOutput.php @@ -0,0 +1,153 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * ConsoleOutput is the default class for all CLI output. It uses STDOUT and STDERR. + * + * This class is a convenient wrapper around `StreamOutput` for both STDOUT and STDERR. + * + * $output = new ConsoleOutput(); + * + * This is equivalent to: + * + * $output = new StreamOutput(fopen('php://stdout', 'w')); + * $stdErr = new StreamOutput(fopen('php://stderr', 'w')); + * + * @author Fabien Potencier + */ +class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface +{ + private OutputInterface $stderr; + private array $consoleSectionOutputs = []; + + /** + * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) + * @param bool|null $decorated Whether to decorate messages (null for auto-guessing) + * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) + */ + public function __construct(int $verbosity = self::VERBOSITY_NORMAL, ?bool $decorated = null, ?OutputFormatterInterface $formatter = null) + { + parent::__construct($this->openOutputStream(), $verbosity, $decorated, $formatter); + + if (null === $formatter) { + // for BC reasons, stdErr has it own Formatter only when user don't inject a specific formatter. + $this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated); + + return; + } + + $actualDecorated = $this->isDecorated(); + $this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated, $this->getFormatter()); + + if (null === $decorated) { + $this->setDecorated($actualDecorated && $this->stderr->isDecorated()); + } + } + + /** + * Creates a new output section. + */ + public function section(): ConsoleSectionOutput + { + return new ConsoleSectionOutput($this->getStream(), $this->consoleSectionOutputs, $this->getVerbosity(), $this->isDecorated(), $this->getFormatter()); + } + + public function setDecorated(bool $decorated): void + { + parent::setDecorated($decorated); + $this->stderr->setDecorated($decorated); + } + + public function setFormatter(OutputFormatterInterface $formatter): void + { + parent::setFormatter($formatter); + $this->stderr->setFormatter($formatter); + } + + public function setVerbosity(int $level): void + { + parent::setVerbosity($level); + $this->stderr->setVerbosity($level); + } + + public function getErrorOutput(): OutputInterface + { + return $this->stderr; + } + + public function setErrorOutput(OutputInterface $error): void + { + $this->stderr = $error; + } + + /** + * Returns true if current environment supports writing console output to + * STDOUT. + */ + protected function hasStdoutSupport(): bool + { + return false === $this->isRunningOS400(); + } + + /** + * Returns true if current environment supports writing console output to + * STDERR. + */ + protected function hasStderrSupport(): bool + { + return false === $this->isRunningOS400(); + } + + /** + * Checks if current executing environment is IBM iSeries (OS400), which + * doesn't properly convert character-encodings between ASCII to EBCDIC. + */ + private function isRunningOS400(): bool + { + $checks = [ + \function_exists('php_uname') ? php_uname('s') : '', + getenv('OSTYPE'), + \PHP_OS, + ]; + + return false !== stripos(implode(';', $checks), 'OS400'); + } + + /** + * @return resource + */ + private function openOutputStream() + { + if (!$this->hasStdoutSupport()) { + return fopen('php://output', 'w'); + } + + // Use STDOUT when possible to prevent from opening too many file descriptors + return \defined('STDOUT') ? \STDOUT : (@fopen('php://stdout', 'w') ?: fopen('php://output', 'w')); + } + + /** + * @return resource + */ + private function openErrorStream() + { + if (!$this->hasStderrSupport()) { + return fopen('php://output', 'w'); + } + + // Use STDERR when possible to prevent from opening too many file descriptors + return \defined('STDERR') ? \STDERR : (@fopen('php://stderr', 'w') ?: fopen('php://output', 'w')); + } +} diff --git a/vendor/symfony/console/Output/ConsoleOutputInterface.php b/vendor/symfony/console/Output/ConsoleOutputInterface.php new file mode 100644 index 00000000..1f8f147c --- /dev/null +++ b/vendor/symfony/console/Output/ConsoleOutputInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +/** + * ConsoleOutputInterface is the interface implemented by ConsoleOutput class. + * This adds information about stderr and section output stream. + * + * @author Dariusz Górecki + */ +interface ConsoleOutputInterface extends OutputInterface +{ + /** + * Gets the OutputInterface for errors. + */ + public function getErrorOutput(): OutputInterface; + + public function setErrorOutput(OutputInterface $error): void; + + public function section(): ConsoleSectionOutput; +} diff --git a/vendor/symfony/console/Output/ConsoleSectionOutput.php b/vendor/symfony/console/Output/ConsoleSectionOutput.php new file mode 100644 index 00000000..ded97c70 --- /dev/null +++ b/vendor/symfony/console/Output/ConsoleSectionOutput.php @@ -0,0 +1,237 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Terminal; + +/** + * @author Pierre du Plessis + * @author Gabriel Ostrolucký + */ +class ConsoleSectionOutput extends StreamOutput +{ + private array $content = []; + private int $lines = 0; + private array $sections; + private Terminal $terminal; + private int $maxHeight = 0; + + /** + * @param resource $stream + * @param ConsoleSectionOutput[] $sections + */ + public function __construct($stream, array &$sections, int $verbosity, bool $decorated, OutputFormatterInterface $formatter) + { + parent::__construct($stream, $verbosity, $decorated, $formatter); + array_unshift($sections, $this); + $this->sections = &$sections; + $this->terminal = new Terminal(); + } + + /** + * Defines a maximum number of lines for this section. + * + * When more lines are added, the section will automatically scroll to the + * end (i.e. remove the first lines to comply with the max height). + */ + public function setMaxHeight(int $maxHeight): void + { + // when changing max height, clear output of current section and redraw again with the new height + $previousMaxHeight = $this->maxHeight; + $this->maxHeight = $maxHeight; + $existingContent = $this->popStreamContentUntilCurrentSection($previousMaxHeight ? min($previousMaxHeight, $this->lines) : $this->lines); + + parent::doWrite($this->getVisibleContent(), false); + parent::doWrite($existingContent, false); + } + + /** + * Clears previous output for this section. + * + * @param int $lines Number of lines to clear. If null, then the entire output of this section is cleared + */ + public function clear(?int $lines = null): void + { + if (empty($this->content) || !$this->isDecorated()) { + return; + } + + if ($lines) { + array_splice($this->content, -$lines); + } else { + $lines = $this->lines; + $this->content = []; + } + + $this->lines -= $lines; + + parent::doWrite($this->popStreamContentUntilCurrentSection($this->maxHeight ? min($this->maxHeight, $lines) : $lines), false); + } + + /** + * Overwrites the previous output with a new message. + */ + public function overwrite(string|iterable $message): void + { + $this->clear(); + $this->writeln($message); + } + + public function getContent(): string + { + return implode('', $this->content); + } + + public function getVisibleContent(): string + { + if (0 === $this->maxHeight) { + return $this->getContent(); + } + + return implode('', \array_slice($this->content, -$this->maxHeight)); + } + + /** + * @internal + */ + public function addContent(string $input, bool $newline = true): int + { + $width = $this->terminal->getWidth(); + $lines = explode(\PHP_EOL, $input); + $linesAdded = 0; + $count = \count($lines) - 1; + foreach ($lines as $i => $lineContent) { + // re-add the line break (that has been removed in the above `explode()` for + // - every line that is not the last line + // - if $newline is required, also add it to the last line + if ($i < $count || $newline) { + $lineContent .= \PHP_EOL; + } + + // skip line if there is no text (or newline for that matter) + if ('' === $lineContent) { + continue; + } + + // For the first line, check if the previous line (last entry of `$this->content`) + // needs to be continued (i.e. does not end with a line break). + if (0 === $i + && (false !== $lastLine = end($this->content)) + && !str_ends_with($lastLine, \PHP_EOL) + ) { + // deduct the line count of the previous line + $this->lines -= (int) ceil($this->getDisplayLength($lastLine) / $width) ?: 1; + // concatenate previous and new line + $lineContent = $lastLine.$lineContent; + // replace last entry of `$this->content` with the new expanded line + array_splice($this->content, -1, 1, $lineContent); + } else { + // otherwise just add the new content + $this->content[] = $lineContent; + } + + $linesAdded += (int) ceil($this->getDisplayLength($lineContent) / $width) ?: 1; + } + + $this->lines += $linesAdded; + + return $linesAdded; + } + + /** + * @internal + */ + public function addNewLineOfInputSubmit(): void + { + $this->content[] = \PHP_EOL; + ++$this->lines; + } + + protected function doWrite(string $message, bool $newline): void + { + // Simulate newline behavior for consistent output formatting, avoiding extra logic + if (!$newline && str_ends_with($message, \PHP_EOL)) { + $message = substr($message, 0, -\strlen(\PHP_EOL)); + $newline = true; + } + + if (!$this->isDecorated()) { + parent::doWrite($message, $newline); + + return; + } + + // Check if the previous line (last entry of `$this->content`) needs to be continued + // (i.e. does not end with a line break). In which case, it needs to be erased first. + $linesToClear = $deleteLastLine = ($lastLine = end($this->content) ?: '') && !str_ends_with($lastLine, \PHP_EOL) ? 1 : 0; + + $linesAdded = $this->addContent($message, $newline); + + if ($lineOverflow = $this->maxHeight > 0 && $this->lines > $this->maxHeight) { + // on overflow, clear the whole section and redraw again (to remove the first lines) + $linesToClear = $this->maxHeight; + } + + $erasedContent = $this->popStreamContentUntilCurrentSection($linesToClear); + + if ($lineOverflow) { + // redraw existing lines of the section + $previousLinesOfSection = \array_slice($this->content, $this->lines - $this->maxHeight, $this->maxHeight - $linesAdded); + parent::doWrite(implode('', $previousLinesOfSection), false); + } + + // if the last line was removed, re-print its content together with the new content. + // otherwise, just print the new content. + parent::doWrite($deleteLastLine ? $lastLine.$message : $message, true); + parent::doWrite($erasedContent, false); + } + + /** + * At initial stage, cursor is at the end of stream output. This method makes cursor crawl upwards until it hits + * current section. Then it erases content it crawled through. Optionally, it erases part of current section too. + */ + private function popStreamContentUntilCurrentSection(int $numberOfLinesToClearFromCurrentSection = 0): string + { + $numberOfLinesToClear = $numberOfLinesToClearFromCurrentSection; + $erasedContent = []; + + foreach ($this->sections as $section) { + if ($section === $this) { + break; + } + + $numberOfLinesToClear += $section->maxHeight ? min($section->lines, $section->maxHeight) : $section->lines; + if ('' !== $sectionContent = $section->getVisibleContent()) { + if (!str_ends_with($sectionContent, \PHP_EOL)) { + $sectionContent .= \PHP_EOL; + } + $erasedContent[] = $sectionContent; + } + } + + if ($numberOfLinesToClear > 0) { + // move cursor up n lines + parent::doWrite(sprintf("\x1b[%dA", $numberOfLinesToClear), false); + // erase to end of screen + parent::doWrite("\x1b[0J", false); + } + + return implode('', array_reverse($erasedContent)); + } + + private function getDisplayLength(string $text): int + { + return Helper::width(Helper::removeDecoration($this->getFormatter(), str_replace("\t", ' ', $text))); + } +} diff --git a/vendor/symfony/console/Output/NullOutput.php b/vendor/symfony/console/Output/NullOutput.php new file mode 100644 index 00000000..40ae3328 --- /dev/null +++ b/vendor/symfony/console/Output/NullOutput.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\NullOutputFormatter; +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * NullOutput suppresses all output. + * + * $output = new NullOutput(); + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class NullOutput implements OutputInterface +{ + private NullOutputFormatter $formatter; + + public function setFormatter(OutputFormatterInterface $formatter): void + { + // do nothing + } + + public function getFormatter(): OutputFormatterInterface + { + // to comply with the interface we must return a OutputFormatterInterface + return $this->formatter ??= new NullOutputFormatter(); + } + + public function setDecorated(bool $decorated): void + { + // do nothing + } + + public function isDecorated(): bool + { + return false; + } + + public function setVerbosity(int $level): void + { + // do nothing + } + + public function getVerbosity(): int + { + return self::VERBOSITY_QUIET; + } + + public function isQuiet(): bool + { + return true; + } + + public function isVerbose(): bool + { + return false; + } + + public function isVeryVerbose(): bool + { + return false; + } + + public function isDebug(): bool + { + return false; + } + + public function writeln(string|iterable $messages, int $options = self::OUTPUT_NORMAL): void + { + // do nothing + } + + public function write(string|iterable $messages, bool $newline = false, int $options = self::OUTPUT_NORMAL): void + { + // do nothing + } +} diff --git a/vendor/symfony/console/Output/Output.php b/vendor/symfony/console/Output/Output.php new file mode 100644 index 00000000..2bb10574 --- /dev/null +++ b/vendor/symfony/console/Output/Output.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * Base class for output classes. + * + * There are five levels of verbosity: + * + * * normal: no option passed (normal output) + * * verbose: -v (more output) + * * very verbose: -vv (highly extended output) + * * debug: -vvv (all debug output) + * * quiet: -q (no output) + * + * @author Fabien Potencier + */ +abstract class Output implements OutputInterface +{ + private int $verbosity; + private OutputFormatterInterface $formatter; + + /** + * @param int|null $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) + * @param bool $decorated Whether to decorate messages + * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) + */ + public function __construct(?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = false, ?OutputFormatterInterface $formatter = null) + { + $this->verbosity = $verbosity ?? self::VERBOSITY_NORMAL; + $this->formatter = $formatter ?? new OutputFormatter(); + $this->formatter->setDecorated($decorated); + } + + public function setFormatter(OutputFormatterInterface $formatter): void + { + $this->formatter = $formatter; + } + + public function getFormatter(): OutputFormatterInterface + { + return $this->formatter; + } + + public function setDecorated(bool $decorated): void + { + $this->formatter->setDecorated($decorated); + } + + public function isDecorated(): bool + { + return $this->formatter->isDecorated(); + } + + public function setVerbosity(int $level): void + { + $this->verbosity = $level; + } + + public function getVerbosity(): int + { + return $this->verbosity; + } + + public function isQuiet(): bool + { + return self::VERBOSITY_QUIET === $this->verbosity; + } + + public function isVerbose(): bool + { + return self::VERBOSITY_VERBOSE <= $this->verbosity; + } + + public function isVeryVerbose(): bool + { + return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity; + } + + public function isDebug(): bool + { + return self::VERBOSITY_DEBUG <= $this->verbosity; + } + + public function writeln(string|iterable $messages, int $options = self::OUTPUT_NORMAL): void + { + $this->write($messages, true, $options); + } + + public function write(string|iterable $messages, bool $newline = false, int $options = self::OUTPUT_NORMAL): void + { + if (!is_iterable($messages)) { + $messages = [$messages]; + } + + $types = self::OUTPUT_NORMAL | self::OUTPUT_RAW | self::OUTPUT_PLAIN; + $type = $types & $options ?: self::OUTPUT_NORMAL; + + $verbosities = self::VERBOSITY_QUIET | self::VERBOSITY_NORMAL | self::VERBOSITY_VERBOSE | self::VERBOSITY_VERY_VERBOSE | self::VERBOSITY_DEBUG; + $verbosity = $verbosities & $options ?: self::VERBOSITY_NORMAL; + + if ($verbosity > $this->getVerbosity()) { + return; + } + + foreach ($messages as $message) { + switch ($type) { + case OutputInterface::OUTPUT_NORMAL: + $message = $this->formatter->format($message); + break; + case OutputInterface::OUTPUT_RAW: + break; + case OutputInterface::OUTPUT_PLAIN: + $message = strip_tags($this->formatter->format($message)); + break; + } + + $this->doWrite($message ?? '', $newline); + } + } + + /** + * Writes a message to the output. + */ + abstract protected function doWrite(string $message, bool $newline): void; +} diff --git a/vendor/symfony/console/Output/OutputInterface.php b/vendor/symfony/console/Output/OutputInterface.php new file mode 100644 index 00000000..41315fbf --- /dev/null +++ b/vendor/symfony/console/Output/OutputInterface.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * OutputInterface is the interface implemented by all Output classes. + * + * @author Fabien Potencier + */ +interface OutputInterface +{ + public const VERBOSITY_QUIET = 16; + public const VERBOSITY_NORMAL = 32; + public const VERBOSITY_VERBOSE = 64; + public const VERBOSITY_VERY_VERBOSE = 128; + public const VERBOSITY_DEBUG = 256; + + public const OUTPUT_NORMAL = 1; + public const OUTPUT_RAW = 2; + public const OUTPUT_PLAIN = 4; + + /** + * Writes a message to the output. + * + * @param bool $newline Whether to add a newline + * @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), + * 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL + */ + public function write(string|iterable $messages, bool $newline = false, int $options = 0): void; + + /** + * Writes a message to the output and adds a newline at the end. + * + * @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), + * 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL + */ + public function writeln(string|iterable $messages, int $options = 0): void; + + /** + * Sets the verbosity of the output. + * + * @param self::VERBOSITY_* $level + */ + public function setVerbosity(int $level): void; + + /** + * Gets the current verbosity of the output. + * + * @return self::VERBOSITY_* + */ + public function getVerbosity(): int; + + /** + * Returns whether verbosity is quiet (-q). + */ + public function isQuiet(): bool; + + /** + * Returns whether verbosity is verbose (-v). + */ + public function isVerbose(): bool; + + /** + * Returns whether verbosity is very verbose (-vv). + */ + public function isVeryVerbose(): bool; + + /** + * Returns whether verbosity is debug (-vvv). + */ + public function isDebug(): bool; + + /** + * Sets the decorated flag. + */ + public function setDecorated(bool $decorated): void; + + /** + * Gets the decorated flag. + */ + public function isDecorated(): bool; + + public function setFormatter(OutputFormatterInterface $formatter): void; + + /** + * Returns current output formatter instance. + */ + public function getFormatter(): OutputFormatterInterface; +} diff --git a/vendor/symfony/console/Output/StreamOutput.php b/vendor/symfony/console/Output/StreamOutput.php new file mode 100644 index 00000000..84a7fa73 --- /dev/null +++ b/vendor/symfony/console/Output/StreamOutput.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * StreamOutput writes the output to a given stream. + * + * Usage: + * + * $output = new StreamOutput(fopen('php://stdout', 'w')); + * + * As `StreamOutput` can use any stream, you can also use a file: + * + * $output = new StreamOutput(fopen('/path/to/output.log', 'a', false)); + * + * @author Fabien Potencier + */ +class StreamOutput extends Output +{ + /** @var resource */ + private $stream; + + /** + * @param resource $stream A stream resource + * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) + * @param bool|null $decorated Whether to decorate messages (null for auto-guessing) + * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) + * + * @throws InvalidArgumentException When first argument is not a real stream + */ + public function __construct($stream, int $verbosity = self::VERBOSITY_NORMAL, ?bool $decorated = null, ?OutputFormatterInterface $formatter = null) + { + if (!\is_resource($stream) || 'stream' !== get_resource_type($stream)) { + throw new InvalidArgumentException('The StreamOutput class needs a stream as its first argument.'); + } + + $this->stream = $stream; + + $decorated ??= $this->hasColorSupport(); + + parent::__construct($verbosity, $decorated, $formatter); + } + + /** + * Gets the stream attached to this StreamOutput instance. + * + * @return resource + */ + public function getStream() + { + return $this->stream; + } + + protected function doWrite(string $message, bool $newline): void + { + if ($newline) { + $message .= \PHP_EOL; + } + + @fwrite($this->stream, $message); + + fflush($this->stream); + } + + /** + * Returns true if the stream supports colorization. + * + * Colorization is disabled if not supported by the stream: + * + * This is tricky on Windows, because Cygwin, Msys2 etc emulate pseudo + * terminals via named pipes, so we can only check the environment. + * + * Reference: Composer\XdebugHandler\Process::supportsColor + * https://github.com/composer/xdebug-handler + * + * @return bool true if the stream supports colorization, false otherwise + */ + protected function hasColorSupport(): bool + { + // Follow https://no-color.org/ + if (isset($_SERVER['NO_COLOR']) || false !== getenv('NO_COLOR')) { + return false; + } + + // Detect msysgit/mingw and assume this is a tty because detection + // does not work correctly, see https://github.com/composer/composer/issues/9690 + if (!@stream_isatty($this->stream) && !\in_array(strtoupper((string) getenv('MSYSTEM')), ['MINGW32', 'MINGW64'], true)) { + return false; + } + + if ('\\' === \DIRECTORY_SEPARATOR && @sapi_windows_vt100_support($this->stream)) { + return true; + } + + if ('Hyper' === getenv('TERM_PROGRAM') + || false !== getenv('COLORTERM') + || false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + ) { + return true; + } + + if ('dumb' === $term = (string) getenv('TERM')) { + return false; + } + + // See https://github.com/chalk/supports-color/blob/d4f413efaf8da045c5ab440ed418ef02dbb28bf1/index.js#L157 + return preg_match('/^((screen|xterm|vt100|vt220|putty|rxvt|ansi|cygwin|linux).*)|(.*-256(color)?(-bce)?)$/', $term); + } +} diff --git a/vendor/symfony/console/Output/TrimmedBufferOutput.php b/vendor/symfony/console/Output/TrimmedBufferOutput.php new file mode 100644 index 00000000..c1862a2b --- /dev/null +++ b/vendor/symfony/console/Output/TrimmedBufferOutput.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * A BufferedOutput that keeps only the last N chars. + * + * @author Jérémy Derussé + */ +class TrimmedBufferOutput extends Output +{ + private int $maxLength; + private string $buffer = ''; + + public function __construct(int $maxLength, ?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = false, ?OutputFormatterInterface $formatter = null) + { + if ($maxLength <= 0) { + throw new InvalidArgumentException(sprintf('"%s()" expects a strictly positive maxLength. Got %d.', __METHOD__, $maxLength)); + } + + parent::__construct($verbosity, $decorated, $formatter); + $this->maxLength = $maxLength; + } + + /** + * Empties buffer and returns its content. + */ + public function fetch(): string + { + $content = $this->buffer; + $this->buffer = ''; + + return $content; + } + + protected function doWrite(string $message, bool $newline): void + { + $this->buffer .= $message; + + if ($newline) { + $this->buffer .= \PHP_EOL; + } + + $this->buffer = substr($this->buffer, 0 - $this->maxLength); + } +} diff --git a/vendor/symfony/console/Question/ChoiceQuestion.php b/vendor/symfony/console/Question/ChoiceQuestion.php new file mode 100644 index 00000000..e449ff68 --- /dev/null +++ b/vendor/symfony/console/Question/ChoiceQuestion.php @@ -0,0 +1,177 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Question; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * Represents a choice question. + * + * @author Fabien Potencier + */ +class ChoiceQuestion extends Question +{ + private array $choices; + private bool $multiselect = false; + private string $prompt = ' > '; + private string $errorMessage = 'Value "%s" is invalid'; + + /** + * @param string $question The question to ask to the user + * @param array $choices The list of available choices + * @param mixed $default The default answer to return + */ + public function __construct(string $question, array $choices, mixed $default = null) + { + if (!$choices) { + throw new \LogicException('Choice question must have at least 1 choice available.'); + } + + parent::__construct($question, $default); + + $this->choices = $choices; + $this->setValidator($this->getDefaultValidator()); + $this->setAutocompleterValues($choices); + } + + /** + * Returns available choices. + */ + public function getChoices(): array + { + return $this->choices; + } + + /** + * Sets multiselect option. + * + * When multiselect is set to true, multiple choices can be answered. + * + * @return $this + */ + public function setMultiselect(bool $multiselect): static + { + $this->multiselect = $multiselect; + $this->setValidator($this->getDefaultValidator()); + + return $this; + } + + /** + * Returns whether the choices are multiselect. + */ + public function isMultiselect(): bool + { + return $this->multiselect; + } + + /** + * Gets the prompt for choices. + */ + public function getPrompt(): string + { + return $this->prompt; + } + + /** + * Sets the prompt for choices. + * + * @return $this + */ + public function setPrompt(string $prompt): static + { + $this->prompt = $prompt; + + return $this; + } + + /** + * Sets the error message for invalid values. + * + * The error message has a string placeholder (%s) for the invalid value. + * + * @return $this + */ + public function setErrorMessage(string $errorMessage): static + { + $this->errorMessage = $errorMessage; + $this->setValidator($this->getDefaultValidator()); + + return $this; + } + + private function getDefaultValidator(): callable + { + $choices = $this->choices; + $errorMessage = $this->errorMessage; + $multiselect = $this->multiselect; + $isAssoc = $this->isAssoc($choices); + + return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) { + if ($multiselect) { + // Check for a separated comma values + if (!preg_match('/^[^,]+(?:,[^,]+)*$/', (string) $selected, $matches)) { + throw new InvalidArgumentException(sprintf($errorMessage, $selected)); + } + + $selectedChoices = explode(',', (string) $selected); + } else { + $selectedChoices = [$selected]; + } + + if ($this->isTrimmable()) { + foreach ($selectedChoices as $k => $v) { + $selectedChoices[$k] = trim((string) $v); + } + } + + $multiselectChoices = []; + foreach ($selectedChoices as $value) { + $results = []; + foreach ($choices as $key => $choice) { + if ($choice === $value) { + $results[] = $key; + } + } + + if (\count($results) > 1) { + throw new InvalidArgumentException(sprintf('The provided answer is ambiguous. Value should be one of "%s".', implode('" or "', $results))); + } + + $result = array_search($value, $choices); + + if (!$isAssoc) { + if (false !== $result) { + $result = $choices[$result]; + } elseif (isset($choices[$value])) { + $result = $choices[$value]; + } + } elseif (false === $result && isset($choices[$value])) { + $result = $value; + } + + if (false === $result) { + throw new InvalidArgumentException(sprintf($errorMessage, $value)); + } + + // For associative choices, consistently return the key as string: + $multiselectChoices[] = $isAssoc ? (string) $result : $result; + } + + if ($multiselect) { + return $multiselectChoices; + } + + return current($multiselectChoices); + }; + } +} diff --git a/vendor/symfony/console/Question/ConfirmationQuestion.php b/vendor/symfony/console/Question/ConfirmationQuestion.php new file mode 100644 index 00000000..40eab242 --- /dev/null +++ b/vendor/symfony/console/Question/ConfirmationQuestion.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Question; + +/** + * Represents a yes/no question. + * + * @author Fabien Potencier + */ +class ConfirmationQuestion extends Question +{ + private string $trueAnswerRegex; + + /** + * @param string $question The question to ask to the user + * @param bool $default The default answer to return, true or false + * @param string $trueAnswerRegex A regex to match the "yes" answer + */ + public function __construct(string $question, bool $default = true, string $trueAnswerRegex = '/^y/i') + { + parent::__construct($question, $default); + + $this->trueAnswerRegex = $trueAnswerRegex; + $this->setNormalizer($this->getDefaultNormalizer()); + } + + /** + * Returns the default answer normalizer. + */ + private function getDefaultNormalizer(): callable + { + $default = $this->getDefault(); + $regex = $this->trueAnswerRegex; + + return function ($answer) use ($default, $regex) { + if (\is_bool($answer)) { + return $answer; + } + + $answerIsTrue = (bool) preg_match($regex, $answer); + if (false === $default) { + return $answer && $answerIsTrue; + } + + return '' === $answer || $answerIsTrue; + }; + } +} diff --git a/vendor/symfony/console/Question/Question.php b/vendor/symfony/console/Question/Question.php new file mode 100644 index 00000000..6afe8ea6 --- /dev/null +++ b/vendor/symfony/console/Question/Question.php @@ -0,0 +1,282 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Question; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; + +/** + * Represents a Question. + * + * @author Fabien Potencier + */ +class Question +{ + private string $question; + private ?int $attempts = null; + private bool $hidden = false; + private bool $hiddenFallback = true; + private ?\Closure $autocompleterCallback = null; + private ?\Closure $validator = null; + private string|int|bool|null|float $default; + private ?\Closure $normalizer = null; + private bool $trimmable = true; + private bool $multiline = false; + + /** + * @param string $question The question to ask to the user + * @param string|bool|int|float|null $default The default answer to return if the user enters nothing + */ + public function __construct(string $question, string|bool|int|float|null $default = null) + { + $this->question = $question; + $this->default = $default; + } + + /** + * Returns the question. + */ + public function getQuestion(): string + { + return $this->question; + } + + /** + * Returns the default answer. + */ + public function getDefault(): string|bool|int|float|null + { + return $this->default; + } + + /** + * Returns whether the user response accepts newline characters. + */ + public function isMultiline(): bool + { + return $this->multiline; + } + + /** + * Sets whether the user response should accept newline characters. + * + * @return $this + */ + public function setMultiline(bool $multiline): static + { + $this->multiline = $multiline; + + return $this; + } + + /** + * Returns whether the user response must be hidden. + */ + public function isHidden(): bool + { + return $this->hidden; + } + + /** + * Sets whether the user response must be hidden or not. + * + * @return $this + * + * @throws LogicException In case the autocompleter is also used + */ + public function setHidden(bool $hidden): static + { + if ($this->autocompleterCallback) { + throw new LogicException('A hidden question cannot use the autocompleter.'); + } + + $this->hidden = $hidden; + + return $this; + } + + /** + * In case the response cannot be hidden, whether to fallback on non-hidden question or not. + */ + public function isHiddenFallback(): bool + { + return $this->hiddenFallback; + } + + /** + * Sets whether to fallback on non-hidden question if the response cannot be hidden. + * + * @return $this + */ + public function setHiddenFallback(bool $fallback): static + { + $this->hiddenFallback = $fallback; + + return $this; + } + + /** + * Gets values for the autocompleter. + */ + public function getAutocompleterValues(): ?iterable + { + $callback = $this->getAutocompleterCallback(); + + return $callback ? $callback('') : null; + } + + /** + * Sets values for the autocompleter. + * + * @return $this + * + * @throws LogicException + */ + public function setAutocompleterValues(?iterable $values): static + { + if (\is_array($values)) { + $values = $this->isAssoc($values) ? array_merge(array_keys($values), array_values($values)) : array_values($values); + + $callback = static fn () => $values; + } elseif ($values instanceof \Traversable) { + $callback = static function () use ($values) { + static $valueCache; + + return $valueCache ??= iterator_to_array($values, false); + }; + } else { + $callback = null; + } + + return $this->setAutocompleterCallback($callback); + } + + /** + * Gets the callback function used for the autocompleter. + */ + public function getAutocompleterCallback(): ?callable + { + return $this->autocompleterCallback; + } + + /** + * Sets the callback function used for the autocompleter. + * + * The callback is passed the user input as argument and should return an iterable of corresponding suggestions. + * + * @return $this + */ + public function setAutocompleterCallback(?callable $callback): static + { + if ($this->hidden && null !== $callback) { + throw new LogicException('A hidden question cannot use the autocompleter.'); + } + + $this->autocompleterCallback = null === $callback ? null : $callback(...); + + return $this; + } + + /** + * Sets a validator for the question. + * + * @return $this + */ + public function setValidator(?callable $validator): static + { + $this->validator = null === $validator ? null : $validator(...); + + return $this; + } + + /** + * Gets the validator for the question. + */ + public function getValidator(): ?callable + { + return $this->validator; + } + + /** + * Sets the maximum number of attempts. + * + * Null means an unlimited number of attempts. + * + * @return $this + * + * @throws InvalidArgumentException in case the number of attempts is invalid + */ + public function setMaxAttempts(?int $attempts): static + { + if (null !== $attempts && $attempts < 1) { + throw new InvalidArgumentException('Maximum number of attempts must be a positive value.'); + } + + $this->attempts = $attempts; + + return $this; + } + + /** + * Gets the maximum number of attempts. + * + * Null means an unlimited number of attempts. + */ + public function getMaxAttempts(): ?int + { + return $this->attempts; + } + + /** + * Sets a normalizer for the response. + * + * The normalizer can be a callable (a string), a closure or a class implementing __invoke. + * + * @return $this + */ + public function setNormalizer(callable $normalizer): static + { + $this->normalizer = $normalizer(...); + + return $this; + } + + /** + * Gets the normalizer for the response. + * + * The normalizer can ba a callable (a string), a closure or a class implementing __invoke. + */ + public function getNormalizer(): ?callable + { + return $this->normalizer; + } + + protected function isAssoc(array $array): bool + { + return (bool) \count(array_filter(array_keys($array), 'is_string')); + } + + public function isTrimmable(): bool + { + return $this->trimmable; + } + + /** + * @return $this + */ + public function setTrimmable(bool $trimmable): static + { + $this->trimmable = $trimmable; + + return $this; + } +} diff --git a/vendor/symfony/console/README.md b/vendor/symfony/console/README.md new file mode 100644 index 00000000..ded2bc11 --- /dev/null +++ b/vendor/symfony/console/README.md @@ -0,0 +1,36 @@ +Console Component +================= + +The Console component eases the creation of beautiful and testable command line +interfaces. + +Sponsor +------- + +The Console component for Symfony 7.0 is [backed][1] by [Les-Tilleuls.coop][2]. + +Les-Tilleuls.coop is a team of 70+ Symfony experts who can help you design, develop and +fix your projects. They provide a wide range of professional services including development, +consulting, coaching, training and audits. They also are highly skilled in JS, Go and DevOps. +They are a worker cooperative! + +Help Symfony by [sponsoring][3] its development! + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/console.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) + +Credits +------- + +`Resources/bin/hiddeninput.exe` is a third party binary provided within this +component. Find sources and license at https://github.com/Seldaek/hidden-input. + +[1]: https://symfony.com/backers +[2]: https://les-tilleuls.coop +[3]: https://symfony.com/sponsor diff --git a/vendor/symfony/console/Resources/bin/hiddeninput.exe b/vendor/symfony/console/Resources/bin/hiddeninput.exe new file mode 100644 index 00000000..c8cf65e8 Binary files /dev/null and b/vendor/symfony/console/Resources/bin/hiddeninput.exe differ diff --git a/vendor/symfony/console/Resources/completion.bash b/vendor/symfony/console/Resources/completion.bash new file mode 100644 index 00000000..0d76eacc --- /dev/null +++ b/vendor/symfony/console/Resources/completion.bash @@ -0,0 +1,94 @@ +# This file is part of the Symfony package. +# +# (c) Fabien Potencier +# +# For the full copyright and license information, please view +# https://symfony.com/doc/current/contributing/code/license.html + +_sf_{{ COMMAND_NAME }}() { + + # Use the default completion for shell redirect operators. + for w in '>' '>>' '&>' '<'; do + if [[ $w = "${COMP_WORDS[COMP_CWORD-1]}" ]]; then + compopt -o filenames + COMPREPLY=($(compgen -f -- "${COMP_WORDS[COMP_CWORD]}")) + return 0 + fi + done + + # Use newline as only separator to allow space in completion values + IFS=$'\n' + local sf_cmd="${COMP_WORDS[0]}" + + # for an alias, get the real script behind it + sf_cmd_type=$(type -t $sf_cmd) + if [[ $sf_cmd_type == "alias" ]]; then + sf_cmd=$(alias $sf_cmd | sed -E "s/alias $sf_cmd='(.*)'/\1/") + elif [[ $sf_cmd_type == "file" ]]; then + sf_cmd=$(type -p $sf_cmd) + fi + + if [[ $sf_cmd_type != "function" && ! -x $sf_cmd ]]; then + return 1 + fi + + local cur prev words cword + _get_comp_words_by_ref -n := cur prev words cword + + local completecmd=("$sf_cmd" "_complete" "--no-interaction" "-sbash" "-c$cword" "-a{{ VERSION }}") + for w in ${words[@]}; do + w=$(printf -- '%b' "$w") + # remove quotes from typed values + quote="${w:0:1}" + if [ "$quote" == \' ]; then + w="${w%\'}" + w="${w#\'}" + elif [ "$quote" == \" ]; then + w="${w%\"}" + w="${w#\"}" + fi + # empty values are ignored + if [ ! -z "$w" ]; then + completecmd+=("-i$w") + fi + done + + local sfcomplete + if sfcomplete=$(${completecmd[@]} 2>&1); then + local quote suggestions + quote=${cur:0:1} + + # Use single quotes by default if suggestions contains backslash (FQCN) + if [ "$quote" == '' ] && [[ "$sfcomplete" =~ \\ ]]; then + quote=\' + fi + + if [ "$quote" == \' ]; then + # single quotes: no additional escaping (does not accept ' in values) + suggestions=$(for s in $sfcomplete; do printf $'%q%q%q\n' "$quote" "$s" "$quote"; done) + elif [ "$quote" == \" ]; then + # double quotes: double escaping for \ $ ` " + suggestions=$(for s in $sfcomplete; do + s=${s//\\/\\\\} + s=${s//\$/\\\$} + s=${s//\`/\\\`} + s=${s//\"/\\\"} + printf $'%q%q%q\n' "$quote" "$s" "$quote"; + done) + else + # no quotes: double escaping + suggestions=$(for s in $sfcomplete; do printf $'%q\n' $(printf '%q' "$s"); done) + fi + COMPREPLY=($(IFS=$'\n' compgen -W "$suggestions" -- $(printf -- "%q" "$cur"))) + __ltrim_colon_completions "$cur" + else + if [[ "$sfcomplete" != *"Command \"_complete\" is not defined."* ]]; then + >&2 echo + >&2 echo $sfcomplete + fi + + return 1 + fi +} + +complete -F _sf_{{ COMMAND_NAME }} {{ COMMAND_NAME }} diff --git a/vendor/symfony/console/Resources/completion.fish b/vendor/symfony/console/Resources/completion.fish new file mode 100644 index 00000000..1c34292a --- /dev/null +++ b/vendor/symfony/console/Resources/completion.fish @@ -0,0 +1,29 @@ +# This file is part of the Symfony package. +# +# (c) Fabien Potencier +# +# For the full copyright and license information, please view +# https://symfony.com/doc/current/contributing/code/license.html + +function _sf_{{ COMMAND_NAME }} + set sf_cmd (commandline -o) + set c (count (commandline -oc)) + + set completecmd "$sf_cmd[1]" "_complete" "--no-interaction" "-sfish" "-a{{ VERSION }}" + + for i in $sf_cmd + if [ $i != "" ] + set completecmd $completecmd "-i$i" + end + end + + set completecmd $completecmd "-c$c" + + set sfcomplete ($completecmd) + + for i in $sfcomplete + echo $i + end +end + +complete -c '{{ COMMAND_NAME }}' -a '(_sf_{{ COMMAND_NAME }})' -f diff --git a/vendor/symfony/console/Resources/completion.zsh b/vendor/symfony/console/Resources/completion.zsh new file mode 100644 index 00000000..ff76fe5f --- /dev/null +++ b/vendor/symfony/console/Resources/completion.zsh @@ -0,0 +1,82 @@ +#compdef {{ COMMAND_NAME }} + +# This file is part of the Symfony package. +# +# (c) Fabien Potencier +# +# For the full copyright and license information, please view +# https://symfony.com/doc/current/contributing/code/license.html + +# +# zsh completions for {{ COMMAND_NAME }} +# +# References: +# - https://github.com/spf13/cobra/blob/master/zsh_completions.go +# - https://github.com/symfony/symfony/blob/5.4/src/Symfony/Component/Console/Resources/completion.bash +# +_sf_{{ COMMAND_NAME }}() { + local lastParam flagPrefix requestComp out comp + local -a completions + + # The user could have moved the cursor backwards on the command-line. + # We need to trigger completion from the $CURRENT location, so we need + # to truncate the command-line ($words) up to the $CURRENT location. + # (We cannot use $CURSOR as its value does not work when a command is an alias.) + words=("${=words[1,CURRENT]}") lastParam=${words[-1]} + + # For zsh, when completing a flag with an = (e.g., {{ COMMAND_NAME }} -n=) + # completions must be prefixed with the flag + setopt local_options BASH_REMATCH + if [[ "${lastParam}" =~ '-.*=' ]]; then + # We are dealing with a flag with an = + flagPrefix="-P ${BASH_REMATCH}" + fi + + # Prepare the command to obtain completions + requestComp="${words[0]} ${words[1]} _complete --no-interaction -szsh -a{{ VERSION }} -c$((CURRENT-1))" i="" + for w in ${words[@]}; do + w=$(printf -- '%b' "$w") + # remove quotes from typed values + quote="${w:0:1}" + if [ "$quote" = \' ]; then + w="${w%\'}" + w="${w#\'}" + elif [ "$quote" = \" ]; then + w="${w%\"}" + w="${w#\"}" + fi + # empty values are ignored + if [ ! -z "$w" ]; then + i="${i}-i${w} " + fi + done + + # Ensure at least 1 input + if [ "${i}" = "" ]; then + requestComp="${requestComp} -i\" \"" + else + requestComp="${requestComp} ${i}" + fi + + # Use eval to handle any environment variables and such + out=$(eval ${requestComp} 2>/dev/null) + + while IFS='\n' read -r comp; do + if [ -n "$comp" ]; then + # If requested, completions are returned with a description. + # The description is preceded by a TAB character. + # For zsh's _describe, we need to use a : instead of a TAB. + # We first need to escape any : as part of the completion itself. + comp=${comp//:/\\:} + local tab=$(printf '\t') + comp=${comp//$tab/:} + completions+=${comp} + fi + done < <(printf "%s\n" "${out[@]}") + + # Let inbuilt _describe handle completions + eval _describe "completions" completions $flagPrefix + return $? +} + +compdef _sf_{{ COMMAND_NAME }} {{ COMMAND_NAME }} diff --git a/vendor/symfony/console/SignalRegistry/SignalMap.php b/vendor/symfony/console/SignalRegistry/SignalMap.php new file mode 100644 index 00000000..de419bda --- /dev/null +++ b/vendor/symfony/console/SignalRegistry/SignalMap.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\SignalRegistry; + +/** + * @author Grégoire Pineau + */ +class SignalMap +{ + private static array $map; + + public static function getSignalName(int $signal): ?string + { + if (!\extension_loaded('pcntl')) { + return null; + } + + if (!isset(self::$map)) { + $r = new \ReflectionExtension('pcntl'); + $c = $r->getConstants(); + $map = array_filter($c, fn ($k) => str_starts_with($k, 'SIG') && !str_starts_with($k, 'SIG_'), \ARRAY_FILTER_USE_KEY); + self::$map = array_flip($map); + } + + return self::$map[$signal] ?? null; + } +} diff --git a/vendor/symfony/console/SignalRegistry/SignalRegistry.php b/vendor/symfony/console/SignalRegistry/SignalRegistry.php new file mode 100644 index 00000000..ef2e5f04 --- /dev/null +++ b/vendor/symfony/console/SignalRegistry/SignalRegistry.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\SignalRegistry; + +final class SignalRegistry +{ + private array $signalHandlers = []; + + public function __construct() + { + if (\function_exists('pcntl_async_signals')) { + pcntl_async_signals(true); + } + } + + public function register(int $signal, callable $signalHandler): void + { + if (!isset($this->signalHandlers[$signal])) { + $previousCallback = pcntl_signal_get_handler($signal); + + if (\is_callable($previousCallback)) { + $this->signalHandlers[$signal][] = $previousCallback; + } + } + + $this->signalHandlers[$signal][] = $signalHandler; + + pcntl_signal($signal, $this->handle(...)); + } + + public static function isSupported(): bool + { + return \function_exists('pcntl_signal'); + } + + /** + * @internal + */ + public function handle(int $signal): void + { + $count = \count($this->signalHandlers[$signal]); + + foreach ($this->signalHandlers[$signal] as $i => $signalHandler) { + $hasNext = $i !== $count - 1; + $signalHandler($signal, $hasNext); + } + } +} diff --git a/vendor/symfony/console/SingleCommandApplication.php b/vendor/symfony/console/SingleCommandApplication.php new file mode 100644 index 00000000..ff1c1724 --- /dev/null +++ b/vendor/symfony/console/SingleCommandApplication.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Grégoire Pineau + */ +class SingleCommandApplication extends Command +{ + private string $version = 'UNKNOWN'; + private bool $autoExit = true; + private bool $running = false; + + /** + * @return $this + */ + public function setVersion(string $version): static + { + $this->version = $version; + + return $this; + } + + /** + * @final + * + * @return $this + */ + public function setAutoExit(bool $autoExit): static + { + $this->autoExit = $autoExit; + + return $this; + } + + public function run(?InputInterface $input = null, ?OutputInterface $output = null): int + { + if ($this->running) { + return parent::run($input, $output); + } + + // We use the command name as the application name + $application = new Application($this->getName() ?: 'UNKNOWN', $this->version); + $application->setAutoExit($this->autoExit); + // Fix the usage of the command displayed with "--help" + $this->setName($_SERVER['argv'][0]); + $application->add($this); + $application->setDefaultCommand($this->getName(), true); + + $this->running = true; + try { + $ret = $application->run($input, $output); + } finally { + $this->running = false; + } + + return $ret ?? 1; + } +} diff --git a/vendor/symfony/console/Style/OutputStyle.php b/vendor/symfony/console/Style/OutputStyle.php new file mode 100644 index 00000000..05076c00 --- /dev/null +++ b/vendor/symfony/console/Style/OutputStyle.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Style; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; +use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Decorates output to add console style guide helpers. + * + * @author Kevin Bond + */ +abstract class OutputStyle implements OutputInterface, StyleInterface +{ + private OutputInterface $output; + + public function __construct(OutputInterface $output) + { + $this->output = $output; + } + + public function newLine(int $count = 1): void + { + $this->output->write(str_repeat(\PHP_EOL, $count)); + } + + public function createProgressBar(int $max = 0): ProgressBar + { + return new ProgressBar($this->output, $max); + } + + public function write(string|iterable $messages, bool $newline = false, int $type = self::OUTPUT_NORMAL): void + { + $this->output->write($messages, $newline, $type); + } + + public function writeln(string|iterable $messages, int $type = self::OUTPUT_NORMAL): void + { + $this->output->writeln($messages, $type); + } + + public function setVerbosity(int $level): void + { + $this->output->setVerbosity($level); + } + + public function getVerbosity(): int + { + return $this->output->getVerbosity(); + } + + public function setDecorated(bool $decorated): void + { + $this->output->setDecorated($decorated); + } + + public function isDecorated(): bool + { + return $this->output->isDecorated(); + } + + public function setFormatter(OutputFormatterInterface $formatter): void + { + $this->output->setFormatter($formatter); + } + + public function getFormatter(): OutputFormatterInterface + { + return $this->output->getFormatter(); + } + + public function isQuiet(): bool + { + return $this->output->isQuiet(); + } + + public function isVerbose(): bool + { + return $this->output->isVerbose(); + } + + public function isVeryVerbose(): bool + { + return $this->output->isVeryVerbose(); + } + + public function isDebug(): bool + { + return $this->output->isDebug(); + } + + protected function getErrorOutput(): OutputInterface + { + if (!$this->output instanceof ConsoleOutputInterface) { + return $this->output; + } + + return $this->output->getErrorOutput(); + } +} diff --git a/vendor/symfony/console/Style/StyleInterface.php b/vendor/symfony/console/Style/StyleInterface.php new file mode 100644 index 00000000..fcc5bc77 --- /dev/null +++ b/vendor/symfony/console/Style/StyleInterface.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Style; + +/** + * Output style helpers. + * + * @author Kevin Bond + */ +interface StyleInterface +{ + /** + * Formats a command title. + */ + public function title(string $message): void; + + /** + * Formats a section title. + */ + public function section(string $message): void; + + /** + * Formats a list. + */ + public function listing(array $elements): void; + + /** + * Formats informational text. + */ + public function text(string|array $message): void; + + /** + * Formats a success result bar. + */ + public function success(string|array $message): void; + + /** + * Formats an error result bar. + */ + public function error(string|array $message): void; + + /** + * Formats an warning result bar. + */ + public function warning(string|array $message): void; + + /** + * Formats a note admonition. + */ + public function note(string|array $message): void; + + /** + * Formats a caution admonition. + */ + public function caution(string|array $message): void; + + /** + * Formats a table. + */ + public function table(array $headers, array $rows): void; + + /** + * Asks a question. + */ + public function ask(string $question, ?string $default = null, ?callable $validator = null): mixed; + + /** + * Asks a question with the user input hidden. + */ + public function askHidden(string $question, ?callable $validator = null): mixed; + + /** + * Asks for confirmation. + */ + public function confirm(string $question, bool $default = true): bool; + + /** + * Asks a choice question. + */ + public function choice(string $question, array $choices, mixed $default = null): mixed; + + /** + * Add newline(s). + */ + public function newLine(int $count = 1): void; + + /** + * Starts the progress output. + */ + public function progressStart(int $max = 0): void; + + /** + * Advances the progress output X steps. + */ + public function progressAdvance(int $step = 1): void; + + /** + * Finishes the progress output. + */ + public function progressFinish(): void; +} diff --git a/vendor/symfony/console/Style/SymfonyStyle.php b/vendor/symfony/console/Style/SymfonyStyle.php new file mode 100644 index 00000000..ca557d6c --- /dev/null +++ b/vendor/symfony/console/Style/SymfonyStyle.php @@ -0,0 +1,456 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Style; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Helper\OutputWrapper; +use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Helper\SymfonyQuestionHelper; +use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Helper\TableCell; +use Symfony\Component\Console\Helper\TableSeparator; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\ConsoleSectionOutput; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Output\TrimmedBufferOutput; +use Symfony\Component\Console\Question\ChoiceQuestion; +use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Question\Question; +use Symfony\Component\Console\Terminal; + +/** + * Output decorator helpers for the Symfony Style Guide. + * + * @author Kevin Bond + */ +class SymfonyStyle extends OutputStyle +{ + public const MAX_LINE_LENGTH = 120; + + private InputInterface $input; + private OutputInterface $output; + private SymfonyQuestionHelper $questionHelper; + private ProgressBar $progressBar; + private int $lineLength; + private TrimmedBufferOutput $bufferedOutput; + + public function __construct(InputInterface $input, OutputInterface $output) + { + $this->input = $input; + $this->bufferedOutput = new TrimmedBufferOutput(\DIRECTORY_SEPARATOR === '\\' ? 4 : 2, $output->getVerbosity(), false, clone $output->getFormatter()); + // Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not. + $width = (new Terminal())->getWidth() ?: self::MAX_LINE_LENGTH; + $this->lineLength = min($width - (int) (\DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH); + + parent::__construct($this->output = $output); + } + + /** + * Formats a message as a block of text. + */ + public function block(string|array $messages, ?string $type = null, ?string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = true): void + { + $messages = \is_array($messages) ? array_values($messages) : [$messages]; + + $this->autoPrependBlock(); + $this->writeln($this->createBlock($messages, $type, $style, $prefix, $padding, $escape)); + $this->newLine(); + } + + public function title(string $message): void + { + $this->autoPrependBlock(); + $this->writeln([ + sprintf('%s', OutputFormatter::escapeTrailingBackslash($message)), + sprintf('%s', str_repeat('=', Helper::width(Helper::removeDecoration($this->getFormatter(), $message)))), + ]); + $this->newLine(); + } + + public function section(string $message): void + { + $this->autoPrependBlock(); + $this->writeln([ + sprintf('%s', OutputFormatter::escapeTrailingBackslash($message)), + sprintf('%s', str_repeat('-', Helper::width(Helper::removeDecoration($this->getFormatter(), $message)))), + ]); + $this->newLine(); + } + + public function listing(array $elements): void + { + $this->autoPrependText(); + $elements = array_map(fn ($element) => sprintf(' * %s', $element), $elements); + + $this->writeln($elements); + $this->newLine(); + } + + public function text(string|array $message): void + { + $this->autoPrependText(); + + $messages = \is_array($message) ? array_values($message) : [$message]; + foreach ($messages as $message) { + $this->writeln(sprintf(' %s', $message)); + } + } + + /** + * Formats a command comment. + */ + public function comment(string|array $message): void + { + $this->block($message, null, null, ' // ', false, false); + } + + public function success(string|array $message): void + { + $this->block($message, 'OK', 'fg=black;bg=green', ' ', true); + } + + public function error(string|array $message): void + { + $this->block($message, 'ERROR', 'fg=white;bg=red', ' ', true); + } + + public function warning(string|array $message): void + { + $this->block($message, 'WARNING', 'fg=black;bg=yellow', ' ', true); + } + + public function note(string|array $message): void + { + $this->block($message, 'NOTE', 'fg=yellow', ' ! '); + } + + /** + * Formats an info message. + */ + public function info(string|array $message): void + { + $this->block($message, 'INFO', 'fg=green', ' ', true); + } + + public function caution(string|array $message): void + { + $this->block($message, 'CAUTION', 'fg=white;bg=red', ' ! ', true); + } + + public function table(array $headers, array $rows): void + { + $this->createTable() + ->setHeaders($headers) + ->setRows($rows) + ->render() + ; + + $this->newLine(); + } + + /** + * Formats a horizontal table. + */ + public function horizontalTable(array $headers, array $rows): void + { + $this->createTable() + ->setHorizontal(true) + ->setHeaders($headers) + ->setRows($rows) + ->render() + ; + + $this->newLine(); + } + + /** + * Formats a list of key/value horizontally. + * + * Each row can be one of: + * * 'A title' + * * ['key' => 'value'] + * * new TableSeparator() + */ + public function definitionList(string|array|TableSeparator ...$list): void + { + $headers = []; + $row = []; + foreach ($list as $value) { + if ($value instanceof TableSeparator) { + $headers[] = $value; + $row[] = $value; + continue; + } + if (\is_string($value)) { + $headers[] = new TableCell($value, ['colspan' => 2]); + $row[] = null; + continue; + } + if (!\is_array($value)) { + throw new InvalidArgumentException('Value should be an array, string, or an instance of TableSeparator.'); + } + $headers[] = key($value); + $row[] = current($value); + } + + $this->horizontalTable($headers, [$row]); + } + + public function ask(string $question, ?string $default = null, ?callable $validator = null): mixed + { + $question = new Question($question, $default); + $question->setValidator($validator); + + return $this->askQuestion($question); + } + + public function askHidden(string $question, ?callable $validator = null): mixed + { + $question = new Question($question); + + $question->setHidden(true); + $question->setValidator($validator); + + return $this->askQuestion($question); + } + + public function confirm(string $question, bool $default = true): bool + { + return $this->askQuestion(new ConfirmationQuestion($question, $default)); + } + + public function choice(string $question, array $choices, mixed $default = null, bool $multiSelect = false): mixed + { + if (null !== $default) { + $values = array_flip($choices); + $default = $values[$default] ?? $default; + } + + $questionChoice = new ChoiceQuestion($question, $choices, $default); + $questionChoice->setMultiselect($multiSelect); + + return $this->askQuestion($questionChoice); + } + + public function progressStart(int $max = 0): void + { + $this->progressBar = $this->createProgressBar($max); + $this->progressBar->start(); + } + + public function progressAdvance(int $step = 1): void + { + $this->getProgressBar()->advance($step); + } + + public function progressFinish(): void + { + $this->getProgressBar()->finish(); + $this->newLine(2); + unset($this->progressBar); + } + + public function createProgressBar(int $max = 0): ProgressBar + { + $progressBar = parent::createProgressBar($max); + + if ('\\' !== \DIRECTORY_SEPARATOR || 'Hyper' === getenv('TERM_PROGRAM')) { + $progressBar->setEmptyBarCharacter('░'); // light shade character \u2591 + $progressBar->setProgressCharacter(''); + $progressBar->setBarCharacter('▓'); // dark shade character \u2593 + } + + return $progressBar; + } + + /** + * @see ProgressBar::iterate() + * + * @template TKey + * @template TValue + * + * @param iterable $iterable + * @param int|null $max Number of steps to complete the bar (0 if indeterminate), if null it will be inferred from $iterable + * + * @return iterable + */ + public function progressIterate(iterable $iterable, ?int $max = null): iterable + { + yield from $this->createProgressBar()->iterate($iterable, $max); + + $this->newLine(2); + } + + public function askQuestion(Question $question): mixed + { + if ($this->input->isInteractive()) { + $this->autoPrependBlock(); + } + + $this->questionHelper ??= new SymfonyQuestionHelper(); + + $answer = $this->questionHelper->ask($this->input, $this, $question); + + if ($this->input->isInteractive()) { + if ($this->output instanceof ConsoleSectionOutput) { + // add the new line of the `return` to submit the input to ConsoleSectionOutput, because ConsoleSectionOutput is holding all it's lines. + // this is relevant when a `ConsoleSectionOutput::clear` is called. + $this->output->addNewLineOfInputSubmit(); + } + $this->newLine(); + $this->bufferedOutput->write("\n"); + } + + return $answer; + } + + public function writeln(string|iterable $messages, int $type = self::OUTPUT_NORMAL): void + { + if (!is_iterable($messages)) { + $messages = [$messages]; + } + + foreach ($messages as $message) { + parent::writeln($message, $type); + $this->writeBuffer($message, true, $type); + } + } + + public function write(string|iterable $messages, bool $newline = false, int $type = self::OUTPUT_NORMAL): void + { + if (!is_iterable($messages)) { + $messages = [$messages]; + } + + foreach ($messages as $message) { + parent::write($message, $newline, $type); + $this->writeBuffer($message, $newline, $type); + } + } + + public function newLine(int $count = 1): void + { + parent::newLine($count); + $this->bufferedOutput->write(str_repeat("\n", $count)); + } + + /** + * Returns a new instance which makes use of stderr if available. + */ + public function getErrorStyle(): self + { + return new self($this->input, $this->getErrorOutput()); + } + + public function createTable(): Table + { + $output = $this->output instanceof ConsoleOutputInterface ? $this->output->section() : $this->output; + $style = clone Table::getStyleDefinition('symfony-style-guide'); + $style->setCellHeaderFormat('%s'); + + return (new Table($output))->setStyle($style); + } + + private function getProgressBar(): ProgressBar + { + return $this->progressBar + ?? throw new RuntimeException('The ProgressBar is not started.'); + } + + private function autoPrependBlock(): void + { + $chars = substr(str_replace(\PHP_EOL, "\n", $this->bufferedOutput->fetch()), -2); + + if (!isset($chars[0])) { + $this->newLine(); // empty history, so we should start with a new line. + + return; + } + // Prepend new line for each non LF chars (This means no blank line was output before) + $this->newLine(2 - substr_count($chars, "\n")); + } + + private function autoPrependText(): void + { + $fetched = $this->bufferedOutput->fetch(); + // Prepend new line if last char isn't EOL: + if ($fetched && !str_ends_with($fetched, "\n")) { + $this->newLine(); + } + } + + private function writeBuffer(string $message, bool $newLine, int $type): void + { + // We need to know if the last chars are PHP_EOL + $this->bufferedOutput->write($message, $newLine, $type); + } + + private function createBlock(iterable $messages, ?string $type = null, ?string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = false): array + { + $indentLength = 0; + $prefixLength = Helper::width(Helper::removeDecoration($this->getFormatter(), $prefix)); + $lines = []; + + if (null !== $type) { + $type = sprintf('[%s] ', $type); + $indentLength = Helper::width($type); + $lineIndentation = str_repeat(' ', $indentLength); + } + + // wrap and add newlines for each element + $outputWrapper = new OutputWrapper(); + foreach ($messages as $key => $message) { + if ($escape) { + $message = OutputFormatter::escape($message); + } + + $lines = array_merge( + $lines, + explode(\PHP_EOL, $outputWrapper->wrap( + $message, + $this->lineLength - $prefixLength - $indentLength, + \PHP_EOL + )) + ); + + if (\count($messages) > 1 && $key < \count($messages) - 1) { + $lines[] = ''; + } + } + + $firstLineIndex = 0; + if ($padding && $this->isDecorated()) { + $firstLineIndex = 1; + array_unshift($lines, ''); + $lines[] = ''; + } + + foreach ($lines as $i => &$line) { + if (null !== $type) { + $line = $firstLineIndex === $i ? $type.$line : $lineIndentation.$line; + } + + $line = $prefix.$line; + $line .= str_repeat(' ', max($this->lineLength - Helper::width(Helper::removeDecoration($this->getFormatter(), $line)), 0)); + + if ($style) { + $line = sprintf('<%s>%s', $style, $line); + } + } + + return $lines; + } +} diff --git a/vendor/symfony/console/Terminal.php b/vendor/symfony/console/Terminal.php new file mode 100644 index 00000000..9eb16aa7 --- /dev/null +++ b/vendor/symfony/console/Terminal.php @@ -0,0 +1,228 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console; + +use Symfony\Component\Console\Output\AnsiColorMode; + +class Terminal +{ + public const DEFAULT_COLOR_MODE = AnsiColorMode::Ansi4; + + private static ?AnsiColorMode $colorMode = null; + private static ?int $width = null; + private static ?int $height = null; + private static ?bool $stty = null; + + /** + * About Ansi color types: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors + * For more information about true color support with terminals https://github.com/termstandard/colors/. + */ + public static function getColorMode(): AnsiColorMode + { + // Use Cache from previous run (or user forced mode) + if (null !== self::$colorMode) { + return self::$colorMode; + } + + // Try with $COLORTERM first + if (\is_string($colorterm = getenv('COLORTERM'))) { + $colorterm = strtolower($colorterm); + + if (str_contains($colorterm, 'truecolor')) { + self::setColorMode(AnsiColorMode::Ansi24); + + return self::$colorMode; + } + + if (str_contains($colorterm, '256color')) { + self::setColorMode(AnsiColorMode::Ansi8); + + return self::$colorMode; + } + } + + // Try with $TERM + if (\is_string($term = getenv('TERM'))) { + $term = strtolower($term); + + if (str_contains($term, 'truecolor')) { + self::setColorMode(AnsiColorMode::Ansi24); + + return self::$colorMode; + } + + if (str_contains($term, '256color')) { + self::setColorMode(AnsiColorMode::Ansi8); + + return self::$colorMode; + } + } + + self::setColorMode(self::DEFAULT_COLOR_MODE); + + return self::$colorMode; + } + + /** + * Force a terminal color mode rendering. + */ + public static function setColorMode(?AnsiColorMode $colorMode): void + { + self::$colorMode = $colorMode; + } + + /** + * Gets the terminal width. + */ + public function getWidth(): int + { + $width = getenv('COLUMNS'); + if (false !== $width) { + return (int) trim($width); + } + + if (null === self::$width) { + self::initDimensions(); + } + + return self::$width ?: 80; + } + + /** + * Gets the terminal height. + */ + public function getHeight(): int + { + $height = getenv('LINES'); + if (false !== $height) { + return (int) trim($height); + } + + if (null === self::$height) { + self::initDimensions(); + } + + return self::$height ?: 50; + } + + /** + * @internal + */ + public static function hasSttyAvailable(): bool + { + if (null !== self::$stty) { + return self::$stty; + } + + // skip check if shell_exec function is disabled + if (!\function_exists('shell_exec')) { + return false; + } + + return self::$stty = (bool) shell_exec('stty 2> '.('\\' === \DIRECTORY_SEPARATOR ? 'NUL' : '/dev/null')); + } + + private static function initDimensions(): void + { + if ('\\' === \DIRECTORY_SEPARATOR) { + $ansicon = getenv('ANSICON'); + if (false !== $ansicon && preg_match('/^(\d+)x(\d+)(?: \((\d+)x(\d+)\))?$/', trim($ansicon), $matches)) { + // extract [w, H] from "wxh (WxH)" + // or [w, h] from "wxh" + self::$width = (int) $matches[1]; + self::$height = isset($matches[4]) ? (int) $matches[4] : (int) $matches[2]; + } elseif (!sapi_windows_vt100_support(fopen('php://stdout', 'w')) && self::hasSttyAvailable()) { + // only use stty on Windows if the terminal does not support vt100 (e.g. Windows 7 + git-bash) + // testing for stty in a Windows 10 vt100-enabled console will implicitly disable vt100 support on STDOUT + self::initDimensionsUsingStty(); + } elseif (null !== $dimensions = self::getConsoleMode()) { + // extract [w, h] from "wxh" + self::$width = (int) $dimensions[0]; + self::$height = (int) $dimensions[1]; + } + } else { + self::initDimensionsUsingStty(); + } + } + + /** + * Initializes dimensions using the output of an stty columns line. + */ + private static function initDimensionsUsingStty(): void + { + if ($sttyString = self::getSttyColumns()) { + if (preg_match('/rows.(\d+);.columns.(\d+);/is', $sttyString, $matches)) { + // extract [w, h] from "rows h; columns w;" + self::$width = (int) $matches[2]; + self::$height = (int) $matches[1]; + } elseif (preg_match('/;.(\d+).rows;.(\d+).columns/is', $sttyString, $matches)) { + // extract [w, h] from "; h rows; w columns" + self::$width = (int) $matches[2]; + self::$height = (int) $matches[1]; + } + } + } + + /** + * Runs and parses mode CON if it's available, suppressing any error output. + * + * @return int[]|null An array composed of the width and the height or null if it could not be parsed + */ + private static function getConsoleMode(): ?array + { + $info = self::readFromProcess('mode CON'); + + if (null === $info || !preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) { + return null; + } + + return [(int) $matches[2], (int) $matches[1]]; + } + + /** + * Runs and parses stty -a if it's available, suppressing any error output. + */ + private static function getSttyColumns(): ?string + { + return self::readFromProcess(['stty', '-a']); + } + + private static function readFromProcess(string|array $command): ?string + { + if (!\function_exists('proc_open')) { + return null; + } + + $descriptorspec = [ + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], + ]; + + $cp = \function_exists('sapi_windows_cp_set') ? sapi_windows_cp_get() : 0; + + $process = proc_open($command, $descriptorspec, $pipes, null, null, ['suppress_errors' => true]); + if (!\is_resource($process)) { + return null; + } + + $info = stream_get_contents($pipes[1]); + fclose($pipes[1]); + fclose($pipes[2]); + proc_close($process); + + if ($cp) { + sapi_windows_cp_set($cp); + } + + return $info; + } +} diff --git a/vendor/symfony/console/Tester/ApplicationTester.php b/vendor/symfony/console/Tester/ApplicationTester.php new file mode 100644 index 00000000..58aee54d --- /dev/null +++ b/vendor/symfony/console/Tester/ApplicationTester.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tester; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Input\ArrayInput; + +/** + * Eases the testing of console applications. + * + * When testing an application, don't forget to disable the auto exit flag: + * + * $application = new Application(); + * $application->setAutoExit(false); + * + * @author Fabien Potencier + */ +class ApplicationTester +{ + use TesterTrait; + + private Application $application; + + public function __construct(Application $application) + { + $this->application = $application; + } + + /** + * Executes the application. + * + * Available options: + * + * * interactive: Sets the input interactive flag + * * decorated: Sets the output decorated flag + * * verbosity: Sets the output verbosity flag + * * capture_stderr_separately: Make output of stdOut and stdErr separately available + * + * @return int The command exit code + */ + public function run(array $input, array $options = []): int + { + $prevShellVerbosity = getenv('SHELL_VERBOSITY'); + + try { + $this->input = new ArrayInput($input); + if (isset($options['interactive'])) { + $this->input->setInteractive($options['interactive']); + } + + if ($this->inputs) { + $this->input->setStream(self::createStream($this->inputs)); + } + + $this->initOutput($options); + + return $this->statusCode = $this->application->run($this->input, $this->output); + } finally { + // SHELL_VERBOSITY is set by Application::configureIO so we need to unset/reset it + // to its previous value to avoid one test's verbosity to spread to the following tests + if (false === $prevShellVerbosity) { + if (\function_exists('putenv')) { + @putenv('SHELL_VERBOSITY'); + } + unset($_ENV['SHELL_VERBOSITY']); + unset($_SERVER['SHELL_VERBOSITY']); + } else { + if (\function_exists('putenv')) { + @putenv('SHELL_VERBOSITY='.$prevShellVerbosity); + } + $_ENV['SHELL_VERBOSITY'] = $prevShellVerbosity; + $_SERVER['SHELL_VERBOSITY'] = $prevShellVerbosity; + } + } + } +} diff --git a/vendor/symfony/console/Tester/CommandCompletionTester.php b/vendor/symfony/console/Tester/CommandCompletionTester.php new file mode 100644 index 00000000..a90fe52e --- /dev/null +++ b/vendor/symfony/console/Tester/CommandCompletionTester.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tester; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; + +/** + * Eases the testing of command completion. + * + * @author Jérôme Tamarelle + */ +class CommandCompletionTester +{ + private Command $command; + + public function __construct(Command $command) + { + $this->command = $command; + } + + /** + * Create completion suggestions from input tokens. + */ + public function complete(array $input): array + { + $currentIndex = \count($input); + if ('' === end($input)) { + array_pop($input); + } + array_unshift($input, $this->command->getName()); + + $completionInput = CompletionInput::fromTokens($input, $currentIndex); + $completionInput->bind($this->command->getDefinition()); + $suggestions = new CompletionSuggestions(); + + $this->command->complete($completionInput, $suggestions); + + $options = []; + foreach ($suggestions->getOptionSuggestions() as $option) { + $options[] = '--'.$option->getName(); + } + + return array_map('strval', array_merge($options, $suggestions->getValueSuggestions())); + } +} diff --git a/vendor/symfony/console/Tester/CommandTester.php b/vendor/symfony/console/Tester/CommandTester.php new file mode 100644 index 00000000..2ff813b7 --- /dev/null +++ b/vendor/symfony/console/Tester/CommandTester.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tester; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\ArrayInput; + +/** + * Eases the testing of console commands. + * + * @author Fabien Potencier + * @author Robin Chalas + */ +class CommandTester +{ + use TesterTrait; + + private Command $command; + + public function __construct(Command $command) + { + $this->command = $command; + } + + /** + * Executes the command. + * + * Available execution options: + * + * * interactive: Sets the input interactive flag + * * decorated: Sets the output decorated flag + * * verbosity: Sets the output verbosity flag + * * capture_stderr_separately: Make output of stdOut and stdErr separately available + * + * @param array $input An array of command arguments and options + * @param array $options An array of execution options + * + * @return int The command exit code + */ + public function execute(array $input, array $options = []): int + { + // set the command name automatically if the application requires + // this argument and no command name was passed + if (!isset($input['command']) + && (null !== $application = $this->command->getApplication()) + && $application->getDefinition()->hasArgument('command') + ) { + $input = array_merge(['command' => $this->command->getName()], $input); + } + + $this->input = new ArrayInput($input); + // Use an in-memory input stream even if no inputs are set so that QuestionHelper::ask() does not rely on the blocking STDIN. + $this->input->setStream(self::createStream($this->inputs)); + + if (isset($options['interactive'])) { + $this->input->setInteractive($options['interactive']); + } + + if (!isset($options['decorated'])) { + $options['decorated'] = false; + } + + $this->initOutput($options); + + return $this->statusCode = $this->command->run($this->input, $this->output); + } +} diff --git a/vendor/symfony/console/Tester/Constraint/CommandIsSuccessful.php b/vendor/symfony/console/Tester/Constraint/CommandIsSuccessful.php new file mode 100644 index 00000000..09c6194b --- /dev/null +++ b/vendor/symfony/console/Tester/Constraint/CommandIsSuccessful.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tester\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\Console\Command\Command; + +final class CommandIsSuccessful extends Constraint +{ + public function toString(): string + { + return 'is successful'; + } + + protected function matches($other): bool + { + return Command::SUCCESS === $other; + } + + protected function failureDescription($other): string + { + return 'the command '.$this->toString(); + } + + protected function additionalFailureDescription($other): string + { + $mapping = [ + Command::FAILURE => 'Command failed.', + Command::INVALID => 'Command was invalid.', + ]; + + return $mapping[$other] ?? sprintf('Command returned exit status %d.', $other); + } +} diff --git a/vendor/symfony/console/Tester/TesterTrait.php b/vendor/symfony/console/Tester/TesterTrait.php new file mode 100644 index 00000000..1ab7a70a --- /dev/null +++ b/vendor/symfony/console/Tester/TesterTrait.php @@ -0,0 +1,178 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tester; + +use PHPUnit\Framework\Assert; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Output\StreamOutput; +use Symfony\Component\Console\Tester\Constraint\CommandIsSuccessful; + +/** + * @author Amrouche Hamza + */ +trait TesterTrait +{ + private StreamOutput $output; + private array $inputs = []; + private bool $captureStreamsIndependently = false; + private InputInterface $input; + private int $statusCode; + + /** + * Gets the display returned by the last execution of the command or application. + * + * @throws \RuntimeException If it's called before the execute method + */ + public function getDisplay(bool $normalize = false): string + { + if (!isset($this->output)) { + throw new \RuntimeException('Output not initialized, did you execute the command before requesting the display?'); + } + + rewind($this->output->getStream()); + + $display = stream_get_contents($this->output->getStream()); + + if ($normalize) { + $display = str_replace(\PHP_EOL, "\n", $display); + } + + return $display; + } + + /** + * Gets the output written to STDERR by the application. + * + * @param bool $normalize Whether to normalize end of lines to \n or not + */ + public function getErrorOutput(bool $normalize = false): string + { + if (!$this->captureStreamsIndependently) { + throw new \LogicException('The error output is not available when the tester is run without "capture_stderr_separately" option set.'); + } + + rewind($this->output->getErrorOutput()->getStream()); + + $display = stream_get_contents($this->output->getErrorOutput()->getStream()); + + if ($normalize) { + $display = str_replace(\PHP_EOL, "\n", $display); + } + + return $display; + } + + /** + * Gets the input instance used by the last execution of the command or application. + */ + public function getInput(): InputInterface + { + return $this->input; + } + + /** + * Gets the output instance used by the last execution of the command or application. + */ + public function getOutput(): OutputInterface + { + return $this->output; + } + + /** + * Gets the status code returned by the last execution of the command or application. + * + * @throws \RuntimeException If it's called before the execute method + */ + public function getStatusCode(): int + { + return $this->statusCode ?? throw new \RuntimeException('Status code not initialized, did you execute the command before requesting the status code?'); + } + + public function assertCommandIsSuccessful(string $message = ''): void + { + Assert::assertThat($this->statusCode, new CommandIsSuccessful(), $message); + } + + /** + * Sets the user inputs. + * + * @param array $inputs An array of strings representing each input + * passed to the command input stream + * + * @return $this + */ + public function setInputs(array $inputs): static + { + $this->inputs = $inputs; + + return $this; + } + + /** + * Initializes the output property. + * + * Available options: + * + * * decorated: Sets the output decorated flag + * * verbosity: Sets the output verbosity flag + * * capture_stderr_separately: Make output of stdOut and stdErr separately available + */ + private function initOutput(array $options): void + { + $this->captureStreamsIndependently = $options['capture_stderr_separately'] ?? false; + if (!$this->captureStreamsIndependently) { + $this->output = new StreamOutput(fopen('php://memory', 'w', false)); + if (isset($options['decorated'])) { + $this->output->setDecorated($options['decorated']); + } + if (isset($options['verbosity'])) { + $this->output->setVerbosity($options['verbosity']); + } + } else { + $this->output = new ConsoleOutput( + $options['verbosity'] ?? ConsoleOutput::VERBOSITY_NORMAL, + $options['decorated'] ?? null + ); + + $errorOutput = new StreamOutput(fopen('php://memory', 'w', false)); + $errorOutput->setFormatter($this->output->getFormatter()); + $errorOutput->setVerbosity($this->output->getVerbosity()); + $errorOutput->setDecorated($this->output->isDecorated()); + + $reflectedOutput = new \ReflectionObject($this->output); + $strErrProperty = $reflectedOutput->getProperty('stderr'); + $strErrProperty->setValue($this->output, $errorOutput); + + $reflectedParent = $reflectedOutput->getParentClass(); + $streamProperty = $reflectedParent->getProperty('stream'); + $streamProperty->setValue($this->output, fopen('php://memory', 'w', false)); + } + } + + /** + * @return resource + */ + private static function createStream(array $inputs) + { + $stream = fopen('php://memory', 'r+', false); + + foreach ($inputs as $input) { + fwrite($stream, $input.\PHP_EOL); + } + + rewind($stream); + + return $stream; + } +} diff --git a/vendor/symfony/console/composer.json b/vendor/symfony/console/composer.json new file mode 100644 index 00000000..0ed1bd9a --- /dev/null +++ b/vendor/symfony/console/composer.json @@ -0,0 +1,54 @@ +{ + "name": "symfony/console", + "type": "library", + "description": "Eases the creation of beautiful and testable command line interfaces", + "keywords": ["console", "cli", "command-line", "terminal"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^6.4|^7.0" + }, + "require-dev": { + "symfony/config": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0", + "psr/log": "^1|^2|^3" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Console\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/deprecation-contracts/CHANGELOG.md b/vendor/symfony/deprecation-contracts/CHANGELOG.md new file mode 100644 index 00000000..7932e261 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/vendor/symfony/deprecation-contracts/LICENSE b/vendor/symfony/deprecation-contracts/LICENSE new file mode 100644 index 00000000..0ed3a246 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/deprecation-contracts/README.md b/vendor/symfony/deprecation-contracts/README.md new file mode 100644 index 00000000..9814864c --- /dev/null +++ b/vendor/symfony/deprecation-contracts/README.md @@ -0,0 +1,26 @@ +Symfony Deprecation Contracts +============================= + +A generic function and convention to trigger deprecation notices. + +This package provides a single global function named `trigger_deprecation()` that triggers silenced deprecation notices. + +By using a custom PHP error handler such as the one provided by the Symfony ErrorHandler component, +the triggered deprecations can be caught and logged for later discovery, both on dev and prod environments. + +The function requires at least 3 arguments: + - the name of the Composer package that is triggering the deprecation + - the version of the package that introduced the deprecation + - the message of the deprecation + - more arguments can be provided: they will be inserted in the message using `printf()` formatting + +Example: +```php +trigger_deprecation('symfony/blockchain', '8.9', 'Using "%s" is deprecated, use "%s" instead.', 'bitcoin', 'fabcoin'); +``` + +This will generate the following message: +`Since symfony/blockchain 8.9: Using "bitcoin" is deprecated, use "fabcoin" instead.` + +While not recommended, the deprecation notices can be completely ignored by declaring an empty +`function trigger_deprecation() {}` in your application. diff --git a/vendor/symfony/deprecation-contracts/composer.json b/vendor/symfony/deprecation-contracts/composer.json new file mode 100644 index 00000000..c6d02d87 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/composer.json @@ -0,0 +1,35 @@ +{ + "name": "symfony/deprecation-contracts", + "type": "library", + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1" + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/vendor/symfony/deprecation-contracts/function.php b/vendor/symfony/deprecation-contracts/function.php new file mode 100644 index 00000000..2d56512b --- /dev/null +++ b/vendor/symfony/deprecation-contracts/function.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (!function_exists('trigger_deprecation')) { + /** + * Triggers a silenced deprecation notice. + * + * @param string $package The name of the Composer package that is triggering the deprecation + * @param string $version The version of the package that introduced the deprecation + * @param string $message The message of the deprecation + * @param mixed ...$args Values to insert in the message using printf() formatting + * + * @author Nicolas Grekas + */ + function trigger_deprecation(string $package, string $version, string $message, mixed ...$args): void + { + @trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), \E_USER_DEPRECATED); + } +} diff --git a/vendor/symfony/event-dispatcher-contracts/CHANGELOG.md b/vendor/symfony/event-dispatcher-contracts/CHANGELOG.md new file mode 100644 index 00000000..7932e261 --- /dev/null +++ b/vendor/symfony/event-dispatcher-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/vendor/symfony/event-dispatcher-contracts/Event.php b/vendor/symfony/event-dispatcher-contracts/Event.php new file mode 100644 index 00000000..2e7f9989 --- /dev/null +++ b/vendor/symfony/event-dispatcher-contracts/Event.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\EventDispatcher; + +use Psr\EventDispatcher\StoppableEventInterface; + +/** + * Event is the base class for classes containing event data. + * + * This class contains no event data. It is used by events that do not pass + * state information to an event handler when an event is raised. + * + * You can call the method stopPropagation() to abort the execution of + * further listeners in your event listener. + * + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Bernhard Schussek + * @author Nicolas Grekas + */ +class Event implements StoppableEventInterface +{ + private bool $propagationStopped = false; + + public function isPropagationStopped(): bool + { + return $this->propagationStopped; + } + + /** + * Stops the propagation of the event to further event listeners. + * + * If multiple event listeners are connected to the same event, no + * further event listener will be triggered once any trigger calls + * stopPropagation(). + */ + public function stopPropagation(): void + { + $this->propagationStopped = true; + } +} diff --git a/vendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php b/vendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php new file mode 100644 index 00000000..610d6ac0 --- /dev/null +++ b/vendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\EventDispatcher; + +use Psr\EventDispatcher\EventDispatcherInterface as PsrEventDispatcherInterface; + +/** + * Allows providing hooks on domain-specific lifecycles by dispatching events. + */ +interface EventDispatcherInterface extends PsrEventDispatcherInterface +{ + /** + * Dispatches an event to all registered listeners. + * + * @template T of object + * + * @param T $event The event to pass to the event handlers/listeners + * @param string|null $eventName The name of the event to dispatch. If not supplied, + * the class of $event should be used instead. + * + * @return T The passed $event MUST be returned + */ + public function dispatch(object $event, string $eventName = null): object; +} diff --git a/vendor/symfony/event-dispatcher-contracts/LICENSE b/vendor/symfony/event-dispatcher-contracts/LICENSE new file mode 100644 index 00000000..7536caea --- /dev/null +++ b/vendor/symfony/event-dispatcher-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/event-dispatcher-contracts/README.md b/vendor/symfony/event-dispatcher-contracts/README.md new file mode 100644 index 00000000..332b961c --- /dev/null +++ b/vendor/symfony/event-dispatcher-contracts/README.md @@ -0,0 +1,9 @@ +Symfony EventDispatcher Contracts +================================= + +A set of abstractions extracted out of the Symfony components. + +Can be used to build on semantics that the Symfony components proved useful and +that already have battle tested implementations. + +See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/vendor/symfony/event-dispatcher-contracts/composer.json b/vendor/symfony/event-dispatcher-contracts/composer.json new file mode 100644 index 00000000..3618d53e --- /dev/null +++ b/vendor/symfony/event-dispatcher-contracts/composer.json @@ -0,0 +1,35 @@ +{ + "name": "symfony/event-dispatcher-contracts", + "type": "library", + "description": "Generic abstractions related to dispatching event", + "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "autoload": { + "psr-4": { "Symfony\\Contracts\\EventDispatcher\\": "" } + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/vendor/symfony/event-dispatcher/Attribute/AsEventListener.php b/vendor/symfony/event-dispatcher/Attribute/AsEventListener.php new file mode 100644 index 00000000..bb931b82 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Attribute/AsEventListener.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Attribute; + +/** + * Service tag to autoconfigure event listeners. + * + * @author Alexander M. Turek + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class AsEventListener +{ + public function __construct( + public ?string $event = null, + public ?string $method = null, + public int $priority = 0, + public ?string $dispatcher = null, + ) { + } +} diff --git a/vendor/symfony/event-dispatcher/CHANGELOG.md b/vendor/symfony/event-dispatcher/CHANGELOG.md new file mode 100644 index 00000000..76b2eab6 --- /dev/null +++ b/vendor/symfony/event-dispatcher/CHANGELOG.md @@ -0,0 +1,96 @@ +CHANGELOG +========= + +6.0 +--- + + * Remove `LegacyEventDispatcherProxy` + +5.4 +--- + + * Allow `#[AsEventListener]` attribute on methods + +5.3 +--- + + * Add `#[AsEventListener]` attribute for declaring listeners on PHP 8 + +5.1.0 +----- + + * The `LegacyEventDispatcherProxy` class has been deprecated. + * Added an optional `dispatcher` attribute to the listener and subscriber tags in `RegisterListenerPass`. + +5.0.0 +----- + + * The signature of the `EventDispatcherInterface::dispatch()` method has been changed to `dispatch($event, string $eventName = null): object`. + * The `Event` class has been removed in favor of `Symfony\Contracts\EventDispatcher\Event`. + * The `TraceableEventDispatcherInterface` has been removed. + * The `WrappedListener` class is now final. + +4.4.0 +----- + + * `AddEventAliasesPass` has been added, allowing applications and bundles to extend the event alias mapping used by `RegisterListenersPass`. + * Made the `event` attribute of the `kernel.event_listener` tag optional for FQCN events. + +4.3.0 +----- + + * The signature of the `EventDispatcherInterface::dispatch()` method should be updated to `dispatch($event, string $eventName = null)`, not doing so is deprecated + * deprecated the `Event` class, use `Symfony\Contracts\EventDispatcher\Event` instead + +4.1.0 +----- + + * added support for invokable event listeners tagged with `kernel.event_listener` by default + * The `TraceableEventDispatcher::getOrphanedEvents()` method has been added. + * The `TraceableEventDispatcherInterface` has been deprecated. + +4.0.0 +----- + + * removed the `ContainerAwareEventDispatcher` class + * added the `reset()` method to the `TraceableEventDispatcherInterface` + +3.4.0 +----- + + * Implementing `TraceableEventDispatcherInterface` without the `reset()` method has been deprecated. + +3.3.0 +----- + + * The ContainerAwareEventDispatcher class has been deprecated. Use EventDispatcher with closure factories instead. + +3.0.0 +----- + + * The method `getListenerPriority($eventName, $listener)` has been added to the + `EventDispatcherInterface`. + * The methods `Event::setDispatcher()`, `Event::getDispatcher()`, `Event::setName()` + and `Event::getName()` have been removed. + The event dispatcher and the event name are passed to the listener call. + +2.5.0 +----- + + * added Debug\TraceableEventDispatcher (originally in HttpKernel) + * changed Debug\TraceableEventDispatcherInterface to extend EventDispatcherInterface + * added RegisterListenersPass (originally in HttpKernel) + +2.1.0 +----- + + * added TraceableEventDispatcherInterface + * added ContainerAwareEventDispatcher + * added a reference to the EventDispatcher on the Event + * added a reference to the Event name on the event + * added fluid interface to the dispatch() method which now returns the Event + object + * added GenericEvent event class + * added the possibility for subscribers to subscribe several times for the + same event + * added ImmutableEventDispatcher diff --git a/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php new file mode 100644 index 00000000..a389acfb --- /dev/null +++ b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php @@ -0,0 +1,356 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Debug; + +use Psr\EventDispatcher\StoppableEventInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Contracts\Service\ResetInterface; + +/** + * Collects some data about event listeners. + * + * This event dispatcher delegates the dispatching to another one. + * + * @author Fabien Potencier + */ +class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterface +{ + protected ?LoggerInterface $logger; + protected Stopwatch $stopwatch; + + /** + * @var \SplObjectStorage|null + */ + private ?\SplObjectStorage $callStack = null; + private EventDispatcherInterface $dispatcher; + private array $wrappedListeners = []; + private array $orphanedEvents = []; + private ?RequestStack $requestStack; + private string $currentRequestHash = ''; + + public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, ?LoggerInterface $logger = null, ?RequestStack $requestStack = null) + { + $this->dispatcher = $dispatcher; + $this->stopwatch = $stopwatch; + $this->logger = $logger; + $this->requestStack = $requestStack; + } + + public function addListener(string $eventName, callable|array $listener, int $priority = 0): void + { + $this->dispatcher->addListener($eventName, $listener, $priority); + } + + public function addSubscriber(EventSubscriberInterface $subscriber): void + { + $this->dispatcher->addSubscriber($subscriber); + } + + public function removeListener(string $eventName, callable|array $listener): void + { + if (isset($this->wrappedListeners[$eventName])) { + foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) { + if ($wrappedListener->getWrappedListener() === $listener || ($listener instanceof \Closure && $wrappedListener->getWrappedListener() == $listener)) { + $listener = $wrappedListener; + unset($this->wrappedListeners[$eventName][$index]); + break; + } + } + } + + $this->dispatcher->removeListener($eventName, $listener); + } + + public function removeSubscriber(EventSubscriberInterface $subscriber): void + { + $this->dispatcher->removeSubscriber($subscriber); + } + + public function getListeners(?string $eventName = null): array + { + return $this->dispatcher->getListeners($eventName); + } + + public function getListenerPriority(string $eventName, callable|array $listener): ?int + { + // we might have wrapped listeners for the event (if called while dispatching) + // in that case get the priority by wrapper + if (isset($this->wrappedListeners[$eventName])) { + foreach ($this->wrappedListeners[$eventName] as $wrappedListener) { + if ($wrappedListener->getWrappedListener() === $listener || ($listener instanceof \Closure && $wrappedListener->getWrappedListener() == $listener)) { + return $this->dispatcher->getListenerPriority($eventName, $wrappedListener); + } + } + } + + return $this->dispatcher->getListenerPriority($eventName, $listener); + } + + public function hasListeners(?string $eventName = null): bool + { + return $this->dispatcher->hasListeners($eventName); + } + + public function dispatch(object $event, ?string $eventName = null): object + { + $eventName ??= $event::class; + + $this->callStack ??= new \SplObjectStorage(); + + $currentRequestHash = $this->currentRequestHash = $this->requestStack && ($request = $this->requestStack->getCurrentRequest()) ? spl_object_hash($request) : ''; + + if (null !== $this->logger && $event instanceof StoppableEventInterface && $event->isPropagationStopped()) { + $this->logger->debug(sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName)); + } + + $this->preProcess($eventName); + try { + $this->beforeDispatch($eventName, $event); + try { + $e = $this->stopwatch->start($eventName, 'section'); + try { + $this->dispatcher->dispatch($event, $eventName); + } finally { + if ($e->isStarted()) { + $e->stop(); + } + } + } finally { + $this->afterDispatch($eventName, $event); + } + } finally { + $this->currentRequestHash = $currentRequestHash; + $this->postProcess($eventName); + } + + return $event; + } + + public function getCalledListeners(?Request $request = null): array + { + if (null === $this->callStack) { + return []; + } + + $hash = $request ? spl_object_hash($request) : null; + $called = []; + foreach ($this->callStack as $listener) { + [$eventName, $requestHash] = $this->callStack->getInfo(); + if (null === $hash || $hash === $requestHash) { + $called[] = $listener->getInfo($eventName); + } + } + + return $called; + } + + public function getNotCalledListeners(?Request $request = null): array + { + try { + $allListeners = $this->dispatcher instanceof EventDispatcher ? $this->getListenersWithPriority() : $this->getListenersWithoutPriority(); + } catch (\Exception $e) { + $this->logger?->info('An exception was thrown while getting the uncalled listeners.', ['exception' => $e]); + + // unable to retrieve the uncalled listeners + return []; + } + + $hash = $request ? spl_object_hash($request) : null; + $calledListeners = []; + + if (null !== $this->callStack) { + foreach ($this->callStack as $calledListener) { + [, $requestHash] = $this->callStack->getInfo(); + + if (null === $hash || $hash === $requestHash) { + $calledListeners[] = $calledListener->getWrappedListener(); + } + } + } + + $notCalled = []; + + foreach ($allListeners as $eventName => $listeners) { + foreach ($listeners as [$listener, $priority]) { + if (!\in_array($listener, $calledListeners, true)) { + if (!$listener instanceof WrappedListener) { + $listener = new WrappedListener($listener, null, $this->stopwatch, $this, $priority); + } + $notCalled[] = $listener->getInfo($eventName); + } + } + } + + uasort($notCalled, $this->sortNotCalledListeners(...)); + + return $notCalled; + } + + public function getOrphanedEvents(?Request $request = null): array + { + if ($request) { + return $this->orphanedEvents[spl_object_hash($request)] ?? []; + } + + if (!$this->orphanedEvents) { + return []; + } + + return array_merge(...array_values($this->orphanedEvents)); + } + + public function reset(): void + { + $this->callStack = null; + $this->orphanedEvents = []; + $this->currentRequestHash = ''; + } + + /** + * Proxies all method calls to the original event dispatcher. + * + * @param string $method The method name + * @param array $arguments The method arguments + */ + public function __call(string $method, array $arguments): mixed + { + return $this->dispatcher->{$method}(...$arguments); + } + + /** + * Called before dispatching the event. + */ + protected function beforeDispatch(string $eventName, object $event): void + { + } + + /** + * Called after dispatching the event. + */ + protected function afterDispatch(string $eventName, object $event): void + { + } + + private function preProcess(string $eventName): void + { + if (!$this->dispatcher->hasListeners($eventName)) { + $this->orphanedEvents[$this->currentRequestHash][] = $eventName; + + return; + } + + foreach ($this->dispatcher->getListeners($eventName) as $listener) { + $priority = $this->getListenerPriority($eventName, $listener); + $wrappedListener = new WrappedListener($listener instanceof WrappedListener ? $listener->getWrappedListener() : $listener, null, $this->stopwatch, $this); + $this->wrappedListeners[$eventName][] = $wrappedListener; + $this->dispatcher->removeListener($eventName, $listener); + $this->dispatcher->addListener($eventName, $wrappedListener, $priority); + $this->callStack->attach($wrappedListener, [$eventName, $this->currentRequestHash]); + } + } + + private function postProcess(string $eventName): void + { + unset($this->wrappedListeners[$eventName]); + $skipped = false; + foreach ($this->dispatcher->getListeners($eventName) as $listener) { + if (!$listener instanceof WrappedListener) { // #12845: a new listener was added during dispatch. + continue; + } + // Unwrap listener + $priority = $this->getListenerPriority($eventName, $listener); + $this->dispatcher->removeListener($eventName, $listener); + $this->dispatcher->addListener($eventName, $listener->getWrappedListener(), $priority); + + if (null !== $this->logger) { + $context = ['event' => $eventName, 'listener' => $listener->getPretty()]; + } + + if ($listener->wasCalled()) { + $this->logger?->debug('Notified event "{event}" to listener "{listener}".', $context); + } else { + $this->callStack->detach($listener); + } + + if (null !== $this->logger && $skipped) { + $this->logger->debug('Listener "{listener}" was not called for event "{event}".', $context); + } + + if ($listener->stoppedPropagation()) { + $this->logger?->debug('Listener "{listener}" stopped propagation of the event "{event}".', $context); + + $skipped = true; + } + } + } + + private function sortNotCalledListeners(array $a, array $b): int + { + if (0 !== $cmp = strcmp($a['event'], $b['event'])) { + return $cmp; + } + + if (\is_int($a['priority']) && !\is_int($b['priority'])) { + return 1; + } + + if (!\is_int($a['priority']) && \is_int($b['priority'])) { + return -1; + } + + if ($a['priority'] === $b['priority']) { + return 0; + } + + if ($a['priority'] > $b['priority']) { + return -1; + } + + return 1; + } + + private function getListenersWithPriority(): array + { + $result = []; + + $allListeners = new \ReflectionProperty(EventDispatcher::class, 'listeners'); + + foreach ($allListeners->getValue($this->dispatcher) as $eventName => $listenersByPriority) { + foreach ($listenersByPriority as $priority => $listeners) { + foreach ($listeners as $listener) { + $result[$eventName][] = [$listener, $priority]; + } + } + } + + return $result; + } + + private function getListenersWithoutPriority(): array + { + $result = []; + + foreach ($this->getListeners() as $eventName => $listeners) { + foreach ($listeners as $listener) { + $result[$eventName][] = [$listener, null]; + } + } + + return $result; + } +} diff --git a/vendor/symfony/event-dispatcher/Debug/WrappedListener.php b/vendor/symfony/event-dispatcher/Debug/WrappedListener.php new file mode 100644 index 00000000..240d1d29 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Debug/WrappedListener.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Debug; + +use Psr\EventDispatcher\StoppableEventInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\VarDumper\Caster\ClassStub; + +/** + * @author Fabien Potencier + */ +final class WrappedListener +{ + private string|array|object $listener; + private ?\Closure $optimizedListener; + private string $name; + private bool $called = false; + private bool $stoppedPropagation = false; + private Stopwatch $stopwatch; + private ?EventDispatcherInterface $dispatcher; + private string $pretty; + private string $callableRef; + private ClassStub|string $stub; + private ?int $priority = null; + private static bool $hasClassStub; + + public function __construct(callable|array $listener, ?string $name, Stopwatch $stopwatch, ?EventDispatcherInterface $dispatcher = null, ?int $priority = null) + { + $this->listener = $listener; + $this->optimizedListener = $listener instanceof \Closure ? $listener : (\is_callable($listener) ? $listener(...) : null); + $this->stopwatch = $stopwatch; + $this->dispatcher = $dispatcher; + $this->priority = $priority; + + if (\is_array($listener)) { + [$this->name, $this->callableRef] = $this->parseListener($listener); + $this->pretty = $this->name.'::'.$listener[1]; + $this->callableRef .= '::'.$listener[1]; + } elseif ($listener instanceof \Closure) { + $r = new \ReflectionFunction($listener); + if (str_contains($r->name, '{closure}')) { + $this->pretty = $this->name = 'closure'; + } elseif ($class = $r->getClosureCalledClass()) { + $this->name = $class->name; + $this->pretty = $this->name.'::'.$r->name; + } else { + $this->pretty = $this->name = $r->name; + } + } elseif (\is_string($listener)) { + $this->pretty = $this->name = $listener; + } else { + $this->name = get_debug_type($listener); + $this->pretty = $this->name.'::__invoke'; + $this->callableRef = $listener::class.'::__invoke'; + } + + if (null !== $name) { + $this->name = $name; + } + + self::$hasClassStub ??= class_exists(ClassStub::class); + } + + public function getWrappedListener(): callable|array + { + return $this->listener; + } + + public function wasCalled(): bool + { + return $this->called; + } + + public function stoppedPropagation(): bool + { + return $this->stoppedPropagation; + } + + public function getPretty(): string + { + return $this->pretty; + } + + public function getInfo(string $eventName): array + { + $this->stub ??= self::$hasClassStub ? new ClassStub($this->pretty.'()', $this->callableRef ?? $this->listener) : $this->pretty.'()'; + + return [ + 'event' => $eventName, + 'priority' => $this->priority ??= $this->dispatcher?->getListenerPriority($eventName, $this->listener), + 'pretty' => $this->pretty, + 'stub' => $this->stub, + ]; + } + + public function __invoke(object $event, string $eventName, EventDispatcherInterface $dispatcher): void + { + $dispatcher = $this->dispatcher ?: $dispatcher; + + $this->called = true; + $this->priority ??= $dispatcher->getListenerPriority($eventName, $this->listener); + + $e = $this->stopwatch->start($this->name, 'event_listener'); + + try { + ($this->optimizedListener ?? $this->listener)($event, $eventName, $dispatcher); + } finally { + if ($e->isStarted()) { + $e->stop(); + } + } + + if ($event instanceof StoppableEventInterface && $event->isPropagationStopped()) { + $this->stoppedPropagation = true; + } + } + + private function parseListener(array $listener): array + { + if ($listener[0] instanceof \Closure) { + foreach ((new \ReflectionFunction($listener[0]))->getAttributes(\Closure::class) as $attribute) { + if ($name = $attribute->getArguments()['name'] ?? false) { + return [$name, $attribute->getArguments()['class'] ?? $name]; + } + } + } + + if (\is_object($listener[0])) { + return [get_debug_type($listener[0]), $listener[0]::class]; + } + + return [$listener[0], $listener[0]]; + } +} diff --git a/vendor/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php b/vendor/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php new file mode 100644 index 00000000..13b4336a --- /dev/null +++ b/vendor/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * This pass allows bundles to extend the list of event aliases. + * + * @author Alexander M. Turek + */ +class AddEventAliasesPass implements CompilerPassInterface +{ + private array $eventAliases; + + public function __construct(array $eventAliases) + { + $this->eventAliases = $eventAliases; + } + + public function process(ContainerBuilder $container): void + { + $eventAliases = $container->hasParameter('event_dispatcher.event_aliases') ? $container->getParameter('event_dispatcher.event_aliases') : []; + + $container->setParameter( + 'event_dispatcher.event_aliases', + array_merge($eventAliases, $this->eventAliases) + ); + } +} diff --git a/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php b/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php new file mode 100644 index 00000000..29a76bbc --- /dev/null +++ b/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php @@ -0,0 +1,213 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\DependencyInjection; + +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Contracts\EventDispatcher\Event; + +/** + * Compiler pass to register tagged services for an event dispatcher. + */ +class RegisterListenersPass implements CompilerPassInterface +{ + private array $hotPathEvents = []; + private array $noPreloadEvents = []; + + /** + * @return $this + */ + public function setHotPathEvents(array $hotPathEvents): static + { + $this->hotPathEvents = array_flip($hotPathEvents); + + return $this; + } + + /** + * @return $this + */ + public function setNoPreloadEvents(array $noPreloadEvents): static + { + $this->noPreloadEvents = array_flip($noPreloadEvents); + + return $this; + } + + public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('event_dispatcher') && !$container->hasAlias('event_dispatcher')) { + return; + } + + $aliases = []; + + if ($container->hasParameter('event_dispatcher.event_aliases')) { + $aliases = $container->getParameter('event_dispatcher.event_aliases'); + } + + $globalDispatcherDefinition = $container->findDefinition('event_dispatcher'); + + foreach ($container->findTaggedServiceIds('kernel.event_listener', true) as $id => $events) { + $noPreload = 0; + + foreach ($events as $event) { + $priority = $event['priority'] ?? 0; + + if (!isset($event['event'])) { + if ($container->getDefinition($id)->hasTag('kernel.event_subscriber')) { + continue; + } + + $event['method'] ??= '__invoke'; + $event['event'] = $this->getEventFromTypeDeclaration($container, $id, $event['method']); + } + + $event['event'] = $aliases[$event['event']] ?? $event['event']; + + if (!isset($event['method'])) { + $event['method'] = 'on'.preg_replace_callback([ + '/(?<=\b|_)[a-z]/i', + '/[^a-z0-9]/i', + ], fn ($matches) => strtoupper($matches[0]), $event['event']); + $event['method'] = preg_replace('/[^a-z0-9]/i', '', $event['method']); + + if (null !== ($class = $container->getDefinition($id)->getClass()) && ($r = $container->getReflectionClass($class, false)) && !$r->hasMethod($event['method'])) { + if (!$r->hasMethod('__invoke')) { + throw new InvalidArgumentException(sprintf('None of the "%s" or "__invoke" methods exist for the service "%s". Please define the "method" attribute on "kernel.event_listener" tags.', $event['method'], $id)); + } + + $event['method'] = '__invoke'; + } + } + + $dispatcherDefinition = $globalDispatcherDefinition; + if (isset($event['dispatcher'])) { + $dispatcherDefinition = $container->findDefinition($event['dispatcher']); + } + + $dispatcherDefinition->addMethodCall('addListener', [$event['event'], [new ServiceClosureArgument(new Reference($id)), $event['method']], $priority]); + + if (isset($this->hotPathEvents[$event['event']])) { + $container->getDefinition($id)->addTag('container.hot_path'); + } elseif (isset($this->noPreloadEvents[$event['event']])) { + ++$noPreload; + } + } + + if ($noPreload && \count($events) === $noPreload) { + $container->getDefinition($id)->addTag('container.no_preload'); + } + } + + $extractingDispatcher = new ExtractingEventDispatcher(); + + foreach ($container->findTaggedServiceIds('kernel.event_subscriber', true) as $id => $tags) { + $def = $container->getDefinition($id); + + // We must assume that the class value has been correctly filled, even if the service is created by a factory + $class = $def->getClass(); + + if (!$r = $container->getReflectionClass($class)) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + if (!$r->isSubclassOf(EventSubscriberInterface::class)) { + throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, EventSubscriberInterface::class)); + } + $class = $r->name; + + $dispatcherDefinitions = []; + foreach ($tags as $attributes) { + if (!isset($attributes['dispatcher']) || isset($dispatcherDefinitions[$attributes['dispatcher']])) { + continue; + } + + $dispatcherDefinitions[$attributes['dispatcher']] = $container->findDefinition($attributes['dispatcher']); + } + + if (!$dispatcherDefinitions) { + $dispatcherDefinitions = [$globalDispatcherDefinition]; + } + + $noPreload = 0; + ExtractingEventDispatcher::$aliases = $aliases; + ExtractingEventDispatcher::$subscriber = $class; + $extractingDispatcher->addSubscriber($extractingDispatcher); + foreach ($extractingDispatcher->listeners as $args) { + $args[1] = [new ServiceClosureArgument(new Reference($id)), $args[1]]; + foreach ($dispatcherDefinitions as $dispatcherDefinition) { + $dispatcherDefinition->addMethodCall('addListener', $args); + } + + if (isset($this->hotPathEvents[$args[0]])) { + $container->getDefinition($id)->addTag('container.hot_path'); + } elseif (isset($this->noPreloadEvents[$args[0]])) { + ++$noPreload; + } + } + if ($noPreload && \count($extractingDispatcher->listeners) === $noPreload) { + $container->getDefinition($id)->addTag('container.no_preload'); + } + $extractingDispatcher->listeners = []; + ExtractingEventDispatcher::$aliases = []; + } + } + + private function getEventFromTypeDeclaration(ContainerBuilder $container, string $id, string $method): string + { + if ( + null === ($class = $container->getDefinition($id)->getClass()) + || !($r = $container->getReflectionClass($class, false)) + || !$r->hasMethod($method) + || 1 > ($m = $r->getMethod($method))->getNumberOfParameters() + || !($type = $m->getParameters()[0]->getType()) instanceof \ReflectionNamedType + || $type->isBuiltin() + || Event::class === ($name = $type->getName()) + ) { + throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "kernel.event_listener" tags.', $id)); + } + + return $name; + } +} + +/** + * @internal + */ +class ExtractingEventDispatcher extends EventDispatcher implements EventSubscriberInterface +{ + public array $listeners = []; + + public static array $aliases = []; + public static string $subscriber; + + public function addListener(string $eventName, callable|array $listener, int $priority = 0): void + { + $this->listeners[] = [$eventName, $listener[1], $priority]; + } + + public static function getSubscribedEvents(): array + { + $events = []; + + foreach ([self::$subscriber, 'getSubscribedEvents']() as $eventName => $params) { + $events[self::$aliases[$eventName] ?? $eventName] = $params; + } + + return $events; + } +} diff --git a/vendor/symfony/event-dispatcher/EventDispatcher.php b/vendor/symfony/event-dispatcher/EventDispatcher.php new file mode 100644 index 00000000..43bc16b8 --- /dev/null +++ b/vendor/symfony/event-dispatcher/EventDispatcher.php @@ -0,0 +1,256 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +use Psr\EventDispatcher\StoppableEventInterface; +use Symfony\Component\EventDispatcher\Debug\WrappedListener; + +/** + * The EventDispatcherInterface is the central point of Symfony's event listener system. + * + * Listeners are registered on the manager and events are dispatched through the + * manager. + * + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Bernhard Schussek + * @author Fabien Potencier + * @author Jordi Boggiano + * @author Jordan Alliot + * @author Nicolas Grekas + */ +class EventDispatcher implements EventDispatcherInterface +{ + private array $listeners = []; + private array $sorted = []; + private array $optimized; + + public function __construct() + { + if (__CLASS__ === static::class) { + $this->optimized = []; + } + } + + public function dispatch(object $event, ?string $eventName = null): object + { + $eventName ??= $event::class; + + if (isset($this->optimized)) { + $listeners = $this->optimized[$eventName] ?? (empty($this->listeners[$eventName]) ? [] : $this->optimizeListeners($eventName)); + } else { + $listeners = $this->getListeners($eventName); + } + + if ($listeners) { + $this->callListeners($listeners, $eventName, $event); + } + + return $event; + } + + public function getListeners(?string $eventName = null): array + { + if (null !== $eventName) { + if (empty($this->listeners[$eventName])) { + return []; + } + + if (!isset($this->sorted[$eventName])) { + $this->sortListeners($eventName); + } + + return $this->sorted[$eventName]; + } + + foreach ($this->listeners as $eventName => $eventListeners) { + if (!isset($this->sorted[$eventName])) { + $this->sortListeners($eventName); + } + } + + return array_filter($this->sorted); + } + + public function getListenerPriority(string $eventName, callable|array $listener): ?int + { + if (empty($this->listeners[$eventName])) { + return null; + } + + if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { + $listener[0] = $listener[0](); + $listener[1] ??= '__invoke'; + } + + foreach ($this->listeners[$eventName] as $priority => &$listeners) { + foreach ($listeners as &$v) { + if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure && 2 >= \count($v)) { + $v[0] = $v[0](); + $v[1] ??= '__invoke'; + } + if ($v === $listener || ($listener instanceof \Closure && $v == $listener)) { + return $priority; + } + } + } + + return null; + } + + public function hasListeners(?string $eventName = null): bool + { + if (null !== $eventName) { + return !empty($this->listeners[$eventName]); + } + + foreach ($this->listeners as $eventListeners) { + if ($eventListeners) { + return true; + } + } + + return false; + } + + public function addListener(string $eventName, callable|array $listener, int $priority = 0): void + { + $this->listeners[$eventName][$priority][] = $listener; + unset($this->sorted[$eventName], $this->optimized[$eventName]); + } + + public function removeListener(string $eventName, callable|array $listener): void + { + if (empty($this->listeners[$eventName])) { + return; + } + + if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { + $listener[0] = $listener[0](); + $listener[1] ??= '__invoke'; + } + + foreach ($this->listeners[$eventName] as $priority => &$listeners) { + foreach ($listeners as $k => &$v) { + if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure && 2 >= \count($v)) { + $v[0] = $v[0](); + $v[1] ??= '__invoke'; + } + if ($v === $listener || ($listener instanceof \Closure && $v == $listener)) { + unset($listeners[$k], $this->sorted[$eventName], $this->optimized[$eventName]); + } + } + + if (!$listeners) { + unset($this->listeners[$eventName][$priority]); + } + } + } + + public function addSubscriber(EventSubscriberInterface $subscriber): void + { + foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { + if (\is_string($params)) { + $this->addListener($eventName, [$subscriber, $params]); + } elseif (\is_string($params[0])) { + $this->addListener($eventName, [$subscriber, $params[0]], $params[1] ?? 0); + } else { + foreach ($params as $listener) { + $this->addListener($eventName, [$subscriber, $listener[0]], $listener[1] ?? 0); + } + } + } + } + + public function removeSubscriber(EventSubscriberInterface $subscriber): void + { + foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { + if (\is_array($params) && \is_array($params[0])) { + foreach ($params as $listener) { + $this->removeListener($eventName, [$subscriber, $listener[0]]); + } + } else { + $this->removeListener($eventName, [$subscriber, \is_string($params) ? $params : $params[0]]); + } + } + } + + /** + * Triggers the listeners of an event. + * + * This method can be overridden to add functionality that is executed + * for each listener. + * + * @param callable[] $listeners The event listeners + * @param string $eventName The name of the event to dispatch + * @param object $event The event object to pass to the event handlers/listeners + */ + protected function callListeners(iterable $listeners, string $eventName, object $event): void + { + $stoppable = $event instanceof StoppableEventInterface; + + foreach ($listeners as $listener) { + if ($stoppable && $event->isPropagationStopped()) { + break; + } + $listener($event, $eventName, $this); + } + } + + /** + * Sorts the internal list of listeners for the given event by priority. + */ + private function sortListeners(string $eventName): void + { + krsort($this->listeners[$eventName]); + $this->sorted[$eventName] = []; + + foreach ($this->listeners[$eventName] as &$listeners) { + foreach ($listeners as &$listener) { + if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { + $listener[0] = $listener[0](); + $listener[1] ??= '__invoke'; + } + $this->sorted[$eventName][] = $listener; + } + } + } + + /** + * Optimizes the internal list of listeners for the given event by priority. + */ + private function optimizeListeners(string $eventName): array + { + krsort($this->listeners[$eventName]); + $this->optimized[$eventName] = []; + + foreach ($this->listeners[$eventName] as &$listeners) { + foreach ($listeners as &$listener) { + $closure = &$this->optimized[$eventName][]; + if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { + $closure = static function (...$args) use (&$listener, &$closure) { + if ($listener[0] instanceof \Closure) { + $listener[0] = $listener[0](); + $listener[1] ??= '__invoke'; + } + ($closure = $listener(...))(...$args); + }; + } else { + $closure = $listener instanceof WrappedListener ? $listener : $listener(...); + } + } + } + + return $this->optimized[$eventName]; + } +} diff --git a/vendor/symfony/event-dispatcher/EventDispatcherInterface.php b/vendor/symfony/event-dispatcher/EventDispatcherInterface.php new file mode 100644 index 00000000..99f8b1a0 --- /dev/null +++ b/vendor/symfony/event-dispatcher/EventDispatcherInterface.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface as ContractsEventDispatcherInterface; + +/** + * The EventDispatcherInterface is the central point of Symfony's event listener system. + * Listeners are registered on the manager and events are dispatched through the + * manager. + * + * @author Bernhard Schussek + */ +interface EventDispatcherInterface extends ContractsEventDispatcherInterface +{ + /** + * Adds an event listener that listens on the specified events. + * + * @param int $priority The higher this value, the earlier an event + * listener will be triggered in the chain (defaults to 0) + */ + public function addListener(string $eventName, callable $listener, int $priority = 0): void; + + /** + * Adds an event subscriber. + * + * The subscriber is asked for all the events it is + * interested in and added as a listener for these events. + */ + public function addSubscriber(EventSubscriberInterface $subscriber): void; + + /** + * Removes an event listener from the specified events. + */ + public function removeListener(string $eventName, callable $listener): void; + + public function removeSubscriber(EventSubscriberInterface $subscriber): void; + + /** + * Gets the listeners of a specific event or all listeners sorted by descending priority. + * + * @return array + */ + public function getListeners(?string $eventName = null): array; + + /** + * Gets the listener priority for a specific event. + * + * Returns null if the event or the listener does not exist. + */ + public function getListenerPriority(string $eventName, callable $listener): ?int; + + /** + * Checks whether an event has any registered listeners. + */ + public function hasListeners(?string $eventName = null): bool; +} diff --git a/vendor/symfony/event-dispatcher/EventSubscriberInterface.php b/vendor/symfony/event-dispatcher/EventSubscriberInterface.php new file mode 100644 index 00000000..2085e428 --- /dev/null +++ b/vendor/symfony/event-dispatcher/EventSubscriberInterface.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * An EventSubscriber knows itself what events it is interested in. + * If an EventSubscriber is added to an EventDispatcherInterface, the manager invokes + * {@link getSubscribedEvents} and registers the subscriber as a listener for all + * returned events. + * + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Bernhard Schussek + */ +interface EventSubscriberInterface +{ + /** + * Returns an array of event names this subscriber wants to listen to. + * + * The array keys are event names and the value can be: + * + * * The method name to call (priority defaults to 0) + * * An array composed of the method name to call and the priority + * * An array of arrays composed of the method names to call and respective + * priorities, or 0 if unset + * + * For instance: + * + * * ['eventName' => 'methodName'] + * * ['eventName' => ['methodName', $priority]] + * * ['eventName' => [['methodName1', $priority], ['methodName2']]] + * + * The code must not depend on runtime state as it will only be called at compile time. + * All logic depending on runtime state must be put into the individual methods handling the events. + * + * @return array> + */ + public static function getSubscribedEvents(); +} diff --git a/vendor/symfony/event-dispatcher/GenericEvent.php b/vendor/symfony/event-dispatcher/GenericEvent.php new file mode 100644 index 00000000..cb3edef8 --- /dev/null +++ b/vendor/symfony/event-dispatcher/GenericEvent.php @@ -0,0 +1,158 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +use Symfony\Contracts\EventDispatcher\Event; + +/** + * Event encapsulation class. + * + * Encapsulates events thus decoupling the observer from the subject they encapsulate. + * + * @author Drak + * + * @implements \ArrayAccess + * @implements \IteratorAggregate + */ +class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate +{ + protected mixed $subject; + protected array $arguments; + + /** + * Encapsulate an event with $subject and $arguments. + * + * @param mixed $subject The subject of the event, usually an object or a callable + * @param array $arguments Arguments to store in the event + */ + public function __construct(mixed $subject = null, array $arguments = []) + { + $this->subject = $subject; + $this->arguments = $arguments; + } + + /** + * Getter for subject property. + */ + public function getSubject(): mixed + { + return $this->subject; + } + + /** + * Get argument by key. + * + * @throws \InvalidArgumentException if key is not found + */ + public function getArgument(string $key): mixed + { + if ($this->hasArgument($key)) { + return $this->arguments[$key]; + } + + throw new \InvalidArgumentException(sprintf('Argument "%s" not found.', $key)); + } + + /** + * Add argument to event. + * + * @return $this + */ + public function setArgument(string $key, mixed $value): static + { + $this->arguments[$key] = $value; + + return $this; + } + + /** + * Getter for all arguments. + */ + public function getArguments(): array + { + return $this->arguments; + } + + /** + * Set args property. + * + * @return $this + */ + public function setArguments(array $args = []): static + { + $this->arguments = $args; + + return $this; + } + + /** + * Has argument. + */ + public function hasArgument(string $key): bool + { + return \array_key_exists($key, $this->arguments); + } + + /** + * ArrayAccess for argument getter. + * + * @param string $key Array key + * + * @throws \InvalidArgumentException if key does not exist in $this->args + */ + public function offsetGet(mixed $key): mixed + { + return $this->getArgument($key); + } + + /** + * ArrayAccess for argument setter. + * + * @param string $key Array key to set + */ + public function offsetSet(mixed $key, mixed $value): void + { + $this->setArgument($key, $value); + } + + /** + * ArrayAccess for unset argument. + * + * @param string $key Array key + */ + public function offsetUnset(mixed $key): void + { + if ($this->hasArgument($key)) { + unset($this->arguments[$key]); + } + } + + /** + * ArrayAccess has argument. + * + * @param string $key Array key + */ + public function offsetExists(mixed $key): bool + { + return $this->hasArgument($key); + } + + /** + * IteratorAggregate for iterating over the object like an array. + * + * @return \ArrayIterator + */ + public function getIterator(): \ArrayIterator + { + return new \ArrayIterator($this->arguments); + } +} diff --git a/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php b/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php new file mode 100644 index 00000000..9afa56a4 --- /dev/null +++ b/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * A read-only proxy for an event dispatcher. + * + * @author Bernhard Schussek + */ +class ImmutableEventDispatcher implements EventDispatcherInterface +{ + private EventDispatcherInterface $dispatcher; + + public function __construct(EventDispatcherInterface $dispatcher) + { + $this->dispatcher = $dispatcher; + } + + public function dispatch(object $event, ?string $eventName = null): object + { + return $this->dispatcher->dispatch($event, $eventName); + } + + public function addListener(string $eventName, callable|array $listener, int $priority = 0): never + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + public function addSubscriber(EventSubscriberInterface $subscriber): never + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + public function removeListener(string $eventName, callable|array $listener): never + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + public function removeSubscriber(EventSubscriberInterface $subscriber): never + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + public function getListeners(?string $eventName = null): array + { + return $this->dispatcher->getListeners($eventName); + } + + public function getListenerPriority(string $eventName, callable|array $listener): ?int + { + return $this->dispatcher->getListenerPriority($eventName, $listener); + } + + public function hasListeners(?string $eventName = null): bool + { + return $this->dispatcher->hasListeners($eventName); + } +} diff --git a/vendor/symfony/event-dispatcher/LICENSE b/vendor/symfony/event-dispatcher/LICENSE new file mode 100644 index 00000000..0138f8f0 --- /dev/null +++ b/vendor/symfony/event-dispatcher/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/event-dispatcher/README.md b/vendor/symfony/event-dispatcher/README.md new file mode 100644 index 00000000..dcdb68d2 --- /dev/null +++ b/vendor/symfony/event-dispatcher/README.md @@ -0,0 +1,15 @@ +EventDispatcher Component +========================= + +The EventDispatcher component provides tools that allow your application +components to communicate with each other by dispatching events and listening to +them. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/event_dispatcher.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/event-dispatcher/composer.json b/vendor/symfony/event-dispatcher/composer.json new file mode 100644 index 00000000..598bbdc5 --- /dev/null +++ b/vendor/symfony/event-dispatcher/composer.json @@ -0,0 +1,47 @@ +{ + "name": "symfony/event-dispatcher", + "type": "library", + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "require-dev": { + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/error-handler": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^6.4|^7.0", + "psr/log": "^1|^2|^3" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\EventDispatcher\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/filesystem/CHANGELOG.md b/vendor/symfony/filesystem/CHANGELOG.md new file mode 100644 index 00000000..b4bd22eb --- /dev/null +++ b/vendor/symfony/filesystem/CHANGELOG.md @@ -0,0 +1,87 @@ +CHANGELOG +========= + +7.0 +--- + + * Add argument `$lock` to `Filesystem::appendToFile()` + +5.4 +--- + + * Add `Path` class + * Add `$lock` argument to `Filesystem::appendToFile()` + +5.0.0 +----- + + * `Filesystem::dumpFile()` and `appendToFile()` don't accept arrays anymore + +4.4.0 +----- + + * support for passing a `null` value to `Filesystem::isAbsolutePath()` is deprecated and will be removed in 5.0 + * `tempnam()` now accepts a third argument `$suffix`. + +4.3.0 +----- + + * support for passing arrays to `Filesystem::dumpFile()` is deprecated and will be removed in 5.0 + * support for passing arrays to `Filesystem::appendToFile()` is deprecated and will be removed in 5.0 + +4.0.0 +----- + + * removed `LockHandler` + * Support for passing relative paths to `Filesystem::makePathRelative()` has been removed. + +3.4.0 +----- + + * support for passing relative paths to `Filesystem::makePathRelative()` is deprecated and will be removed in 4.0 + +3.3.0 +----- + + * added `appendToFile()` to append contents to existing files + +3.2.0 +----- + + * added `readlink()` as a platform independent method to read links + +3.0.0 +----- + + * removed `$mode` argument from `Filesystem::dumpFile()` + +2.8.0 +----- + + * added tempnam() a stream aware version of PHP's native tempnam() + +2.6.0 +----- + + * added LockHandler + +2.3.12 +------ + + * deprecated dumpFile() file mode argument. + +2.3.0 +----- + + * added the dumpFile() method to atomically write files + +2.2.0 +----- + + * added a delete option for the mirror() method + +2.1.0 +----- + + * 24eb396 : BC Break : mkdir() function now throws exception in case of failure instead of returning Boolean value + * created the component diff --git a/vendor/symfony/filesystem/Exception/ExceptionInterface.php b/vendor/symfony/filesystem/Exception/ExceptionInterface.php new file mode 100644 index 00000000..fc438d9f --- /dev/null +++ b/vendor/symfony/filesystem/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Exception; + +/** + * Exception interface for all exceptions thrown by the component. + * + * @author Romain Neutron + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/filesystem/Exception/FileNotFoundException.php b/vendor/symfony/filesystem/Exception/FileNotFoundException.php new file mode 100644 index 00000000..06b732b1 --- /dev/null +++ b/vendor/symfony/filesystem/Exception/FileNotFoundException.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Exception; + +/** + * Exception class thrown when a file couldn't be found. + * + * @author Fabien Potencier + * @author Christian Gärtner + */ +class FileNotFoundException extends IOException +{ + public function __construct(?string $message = null, int $code = 0, ?\Throwable $previous = null, ?string $path = null) + { + if (null === $message) { + if (null === $path) { + $message = 'File could not be found.'; + } else { + $message = sprintf('File "%s" could not be found.', $path); + } + } + + parent::__construct($message, $code, $previous, $path); + } +} diff --git a/vendor/symfony/filesystem/Exception/IOException.php b/vendor/symfony/filesystem/Exception/IOException.php new file mode 100644 index 00000000..df3a0850 --- /dev/null +++ b/vendor/symfony/filesystem/Exception/IOException.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Exception; + +/** + * Exception class thrown when a filesystem operation failure happens. + * + * @author Romain Neutron + * @author Christian Gärtner + * @author Fabien Potencier + */ +class IOException extends \RuntimeException implements IOExceptionInterface +{ + private ?string $path; + + public function __construct(string $message, int $code = 0, ?\Throwable $previous = null, ?string $path = null) + { + $this->path = $path; + + parent::__construct($message, $code, $previous); + } + + public function getPath(): ?string + { + return $this->path; + } +} diff --git a/vendor/symfony/filesystem/Exception/IOExceptionInterface.php b/vendor/symfony/filesystem/Exception/IOExceptionInterface.php new file mode 100644 index 00000000..90c71db8 --- /dev/null +++ b/vendor/symfony/filesystem/Exception/IOExceptionInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Exception; + +/** + * IOException interface for file and input/output stream related exceptions thrown by the component. + * + * @author Christian Gärtner + */ +interface IOExceptionInterface extends ExceptionInterface +{ + /** + * Returns the associated path for the exception. + */ + public function getPath(): ?string; +} diff --git a/vendor/symfony/filesystem/Exception/InvalidArgumentException.php b/vendor/symfony/filesystem/Exception/InvalidArgumentException.php new file mode 100644 index 00000000..abadc200 --- /dev/null +++ b/vendor/symfony/filesystem/Exception/InvalidArgumentException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Exception; + +/** + * @author Christian Flothmann + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/filesystem/Exception/RuntimeException.php b/vendor/symfony/filesystem/Exception/RuntimeException.php new file mode 100644 index 00000000..a7512dca --- /dev/null +++ b/vendor/symfony/filesystem/Exception/RuntimeException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Exception; + +/** + * @author Théo Fidry + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/filesystem/Filesystem.php b/vendor/symfony/filesystem/Filesystem.php new file mode 100644 index 00000000..b7fc7011 --- /dev/null +++ b/vendor/symfony/filesystem/Filesystem.php @@ -0,0 +1,739 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem; + +use Symfony\Component\Filesystem\Exception\FileNotFoundException; +use Symfony\Component\Filesystem\Exception\InvalidArgumentException; +use Symfony\Component\Filesystem\Exception\IOException; + +/** + * Provides basic utility to manipulate the file system. + * + * @author Fabien Potencier + */ +class Filesystem +{ + private static ?string $lastError = null; + + /** + * Copies a file. + * + * If the target file is older than the origin file, it's always overwritten. + * If the target file is newer, it is overwritten only when the + * $overwriteNewerFiles option is set to true. + * + * @throws FileNotFoundException When originFile doesn't exist + * @throws IOException When copy fails + */ + public function copy(string $originFile, string $targetFile, bool $overwriteNewerFiles = false): void + { + $originIsLocal = stream_is_local($originFile) || 0 === stripos($originFile, 'file://'); + if ($originIsLocal && !is_file($originFile)) { + throw new FileNotFoundException(sprintf('Failed to copy "%s" because file does not exist.', $originFile), 0, null, $originFile); + } + + $this->mkdir(\dirname($targetFile)); + + $doCopy = true; + if (!$overwriteNewerFiles && null === parse_url($originFile, \PHP_URL_HOST) && is_file($targetFile)) { + $doCopy = filemtime($originFile) > filemtime($targetFile); + } + + if ($doCopy) { + // https://bugs.php.net/64634 + if (!$source = self::box('fopen', $originFile, 'r')) { + throw new IOException(sprintf('Failed to copy "%s" to "%s" because source file could not be opened for reading: ', $originFile, $targetFile).self::$lastError, 0, null, $originFile); + } + + // Stream context created to allow files overwrite when using FTP stream wrapper - disabled by default + if (!$target = self::box('fopen', $targetFile, 'w', false, stream_context_create(['ftp' => ['overwrite' => true]]))) { + throw new IOException(sprintf('Failed to copy "%s" to "%s" because target file could not be opened for writing: ', $originFile, $targetFile).self::$lastError, 0, null, $originFile); + } + + $bytesCopied = stream_copy_to_stream($source, $target); + fclose($source); + fclose($target); + unset($source, $target); + + if (!is_file($targetFile)) { + throw new IOException(sprintf('Failed to copy "%s" to "%s".', $originFile, $targetFile), 0, null, $originFile); + } + + if ($originIsLocal) { + // Like `cp`, preserve executable permission bits + self::box('chmod', $targetFile, fileperms($targetFile) | (fileperms($originFile) & 0111)); + + if ($bytesCopied !== $bytesOrigin = filesize($originFile)) { + throw new IOException(sprintf('Failed to copy the whole content of "%s" to "%s" (%g of %g bytes copied).', $originFile, $targetFile, $bytesCopied, $bytesOrigin), 0, null, $originFile); + } + } + } + } + + /** + * Creates a directory recursively. + * + * @throws IOException On any directory creation failure + */ + public function mkdir(string|iterable $dirs, int $mode = 0777): void + { + foreach ($this->toIterable($dirs) as $dir) { + if (is_dir($dir)) { + continue; + } + + if (!self::box('mkdir', $dir, $mode, true) && !is_dir($dir)) { + throw new IOException(sprintf('Failed to create "%s": ', $dir).self::$lastError, 0, null, $dir); + } + } + } + + /** + * Checks the existence of files or directories. + */ + public function exists(string|iterable $files): bool + { + $maxPathLength = \PHP_MAXPATHLEN - 2; + + foreach ($this->toIterable($files) as $file) { + if (\strlen($file) > $maxPathLength) { + throw new IOException(sprintf('Could not check if file exist because path length exceeds %d characters.', $maxPathLength), 0, null, $file); + } + + if (!file_exists($file)) { + return false; + } + } + + return true; + } + + /** + * Sets access and modification time of file. + * + * @param int|null $time The touch time as a Unix timestamp, if not supplied the current system time is used + * @param int|null $atime The access time as a Unix timestamp, if not supplied the current system time is used + * + * @throws IOException When touch fails + */ + public function touch(string|iterable $files, ?int $time = null, ?int $atime = null): void + { + foreach ($this->toIterable($files) as $file) { + if (!($time ? self::box('touch', $file, $time, $atime) : self::box('touch', $file))) { + throw new IOException(sprintf('Failed to touch "%s": ', $file).self::$lastError, 0, null, $file); + } + } + } + + /** + * Removes files or directories. + * + * @throws IOException When removal fails + */ + public function remove(string|iterable $files): void + { + if ($files instanceof \Traversable) { + $files = iterator_to_array($files, false); + } elseif (!\is_array($files)) { + $files = [$files]; + } + + self::doRemove($files, false); + } + + private static function doRemove(array $files, bool $isRecursive): void + { + $files = array_reverse($files); + foreach ($files as $file) { + if (is_link($file)) { + // See https://bugs.php.net/52176 + if (!(self::box('unlink', $file) || '\\' !== \DIRECTORY_SEPARATOR || self::box('rmdir', $file)) && file_exists($file)) { + throw new IOException(sprintf('Failed to remove symlink "%s": ', $file).self::$lastError); + } + } elseif (is_dir($file)) { + if (!$isRecursive) { + $tmpName = \dirname(realpath($file)).'/.'.strrev(strtr(base64_encode(random_bytes(2)), '/=', '-_')); + + if (file_exists($tmpName)) { + try { + self::doRemove([$tmpName], true); + } catch (IOException) { + } + } + + if (!file_exists($tmpName) && self::box('rename', $file, $tmpName)) { + $origFile = $file; + $file = $tmpName; + } else { + $origFile = null; + } + } + + $filesystemIterator = new \FilesystemIterator($file, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS); + self::doRemove(iterator_to_array($filesystemIterator, true), true); + + if (!self::box('rmdir', $file) && file_exists($file) && !$isRecursive) { + $lastError = self::$lastError; + + if (null !== $origFile && self::box('rename', $file, $origFile)) { + $file = $origFile; + } + + throw new IOException(sprintf('Failed to remove directory "%s": ', $file).$lastError); + } + } elseif (!self::box('unlink', $file) && (str_contains(self::$lastError, 'Permission denied') || file_exists($file))) { + throw new IOException(sprintf('Failed to remove file "%s": ', $file).self::$lastError); + } + } + } + + /** + * Change mode for an array of files or directories. + * + * @param int $mode The new mode (octal) + * @param int $umask The mode mask (octal) + * @param bool $recursive Whether change the mod recursively or not + * + * @throws IOException When the change fails + */ + public function chmod(string|iterable $files, int $mode, int $umask = 0000, bool $recursive = false): void + { + foreach ($this->toIterable($files) as $file) { + if (!self::box('chmod', $file, $mode & ~$umask)) { + throw new IOException(sprintf('Failed to chmod file "%s": ', $file).self::$lastError, 0, null, $file); + } + if ($recursive && is_dir($file) && !is_link($file)) { + $this->chmod(new \FilesystemIterator($file), $mode, $umask, true); + } + } + } + + /** + * Change the owner of an array of files or directories. + * + * @param string|int $user A user name or number + * @param bool $recursive Whether change the owner recursively or not + * + * @throws IOException When the change fails + */ + public function chown(string|iterable $files, string|int $user, bool $recursive = false): void + { + foreach ($this->toIterable($files) as $file) { + if ($recursive && is_dir($file) && !is_link($file)) { + $this->chown(new \FilesystemIterator($file), $user, true); + } + if (is_link($file) && \function_exists('lchown')) { + if (!self::box('lchown', $file, $user)) { + throw new IOException(sprintf('Failed to chown file "%s": ', $file).self::$lastError, 0, null, $file); + } + } else { + if (!self::box('chown', $file, $user)) { + throw new IOException(sprintf('Failed to chown file "%s": ', $file).self::$lastError, 0, null, $file); + } + } + } + } + + /** + * Change the group of an array of files or directories. + * + * @param string|int $group A group name or number + * @param bool $recursive Whether change the group recursively or not + * + * @throws IOException When the change fails + */ + public function chgrp(string|iterable $files, string|int $group, bool $recursive = false): void + { + foreach ($this->toIterable($files) as $file) { + if ($recursive && is_dir($file) && !is_link($file)) { + $this->chgrp(new \FilesystemIterator($file), $group, true); + } + if (is_link($file) && \function_exists('lchgrp')) { + if (!self::box('lchgrp', $file, $group)) { + throw new IOException(sprintf('Failed to chgrp file "%s": ', $file).self::$lastError, 0, null, $file); + } + } else { + if (!self::box('chgrp', $file, $group)) { + throw new IOException(sprintf('Failed to chgrp file "%s": ', $file).self::$lastError, 0, null, $file); + } + } + } + } + + /** + * Renames a file or a directory. + * + * @throws IOException When target file or directory already exists + * @throws IOException When origin cannot be renamed + */ + public function rename(string $origin, string $target, bool $overwrite = false): void + { + // we check that target does not exist + if (!$overwrite && $this->isReadable($target)) { + throw new IOException(sprintf('Cannot rename because the target "%s" already exists.', $target), 0, null, $target); + } + + if (!self::box('rename', $origin, $target)) { + if (is_dir($origin)) { + // See https://bugs.php.net/54097 & https://php.net/rename#113943 + $this->mirror($origin, $target, null, ['override' => $overwrite, 'delete' => $overwrite]); + $this->remove($origin); + + return; + } + throw new IOException(sprintf('Cannot rename "%s" to "%s": ', $origin, $target).self::$lastError, 0, null, $target); + } + } + + /** + * Tells whether a file exists and is readable. + * + * @throws IOException When windows path is longer than 258 characters + */ + private function isReadable(string $filename): bool + { + $maxPathLength = \PHP_MAXPATHLEN - 2; + + if (\strlen($filename) > $maxPathLength) { + throw new IOException(sprintf('Could not check if file is readable because path length exceeds %d characters.', $maxPathLength), 0, null, $filename); + } + + return is_readable($filename); + } + + /** + * Creates a symbolic link or copy a directory. + * + * @throws IOException When symlink fails + */ + public function symlink(string $originDir, string $targetDir, bool $copyOnWindows = false): void + { + self::assertFunctionExists('symlink'); + + if ('\\' === \DIRECTORY_SEPARATOR) { + $originDir = strtr($originDir, '/', '\\'); + $targetDir = strtr($targetDir, '/', '\\'); + + if ($copyOnWindows) { + $this->mirror($originDir, $targetDir); + + return; + } + } + + $this->mkdir(\dirname($targetDir)); + + if (is_link($targetDir)) { + if (readlink($targetDir) === $originDir) { + return; + } + $this->remove($targetDir); + } + + if (!self::box('symlink', $originDir, $targetDir)) { + $this->linkException($originDir, $targetDir, 'symbolic'); + } + } + + /** + * Creates a hard link, or several hard links to a file. + * + * @param string|string[] $targetFiles The target file(s) + * + * @throws FileNotFoundException When original file is missing or not a file + * @throws IOException When link fails, including if link already exists + */ + public function hardlink(string $originFile, string|iterable $targetFiles): void + { + self::assertFunctionExists('link'); + + if (!$this->exists($originFile)) { + throw new FileNotFoundException(null, 0, null, $originFile); + } + + if (!is_file($originFile)) { + throw new FileNotFoundException(sprintf('Origin file "%s" is not a file.', $originFile)); + } + + foreach ($this->toIterable($targetFiles) as $targetFile) { + if (is_file($targetFile)) { + if (fileinode($originFile) === fileinode($targetFile)) { + continue; + } + $this->remove($targetFile); + } + + if (!self::box('link', $originFile, $targetFile)) { + $this->linkException($originFile, $targetFile, 'hard'); + } + } + } + + /** + * @param string $linkType Name of the link type, typically 'symbolic' or 'hard' + */ + private function linkException(string $origin, string $target, string $linkType): never + { + if (self::$lastError) { + if ('\\' === \DIRECTORY_SEPARATOR && str_contains(self::$lastError, 'error code(1314)')) { + throw new IOException(sprintf('Unable to create "%s" link due to error code 1314: \'A required privilege is not held by the client\'. Do you have the required Administrator-rights?', $linkType), 0, null, $target); + } + } + throw new IOException(sprintf('Failed to create "%s" link from "%s" to "%s": ', $linkType, $origin, $target).self::$lastError, 0, null, $target); + } + + /** + * Resolves links in paths. + * + * With $canonicalize = false (default) + * - if $path does not exist or is not a link, returns null + * - if $path is a link, returns the next direct target of the link without considering the existence of the target + * + * With $canonicalize = true + * - if $path does not exist, returns null + * - if $path exists, returns its absolute fully resolved final version + */ + public function readlink(string $path, bool $canonicalize = false): ?string + { + if (!$canonicalize && !is_link($path)) { + return null; + } + + if ($canonicalize) { + if (!$this->exists($path)) { + return null; + } + + return realpath($path); + } + + return readlink($path); + } + + /** + * Given an existing path, convert it to a path relative to a given starting path. + */ + public function makePathRelative(string $endPath, string $startPath): string + { + if (!$this->isAbsolutePath($startPath)) { + throw new InvalidArgumentException(sprintf('The start path "%s" is not absolute.', $startPath)); + } + + if (!$this->isAbsolutePath($endPath)) { + throw new InvalidArgumentException(sprintf('The end path "%s" is not absolute.', $endPath)); + } + + // Normalize separators on Windows + if ('\\' === \DIRECTORY_SEPARATOR) { + $endPath = str_replace('\\', '/', $endPath); + $startPath = str_replace('\\', '/', $startPath); + } + + $splitDriveLetter = fn ($path) => (\strlen($path) > 2 && ':' === $path[1] && '/' === $path[2] && ctype_alpha($path[0])) + ? [substr($path, 2), strtoupper($path[0])] + : [$path, null]; + + $splitPath = function ($path) { + $result = []; + + foreach (explode('/', trim($path, '/')) as $segment) { + if ('..' === $segment) { + array_pop($result); + } elseif ('.' !== $segment && '' !== $segment) { + $result[] = $segment; + } + } + + return $result; + }; + + [$endPath, $endDriveLetter] = $splitDriveLetter($endPath); + [$startPath, $startDriveLetter] = $splitDriveLetter($startPath); + + $startPathArr = $splitPath($startPath); + $endPathArr = $splitPath($endPath); + + if ($endDriveLetter && $startDriveLetter && $endDriveLetter != $startDriveLetter) { + // End path is on another drive, so no relative path exists + return $endDriveLetter.':/'.($endPathArr ? implode('/', $endPathArr).'/' : ''); + } + + // Find for which directory the common path stops + $index = 0; + while (isset($startPathArr[$index]) && isset($endPathArr[$index]) && $startPathArr[$index] === $endPathArr[$index]) { + ++$index; + } + + // Determine how deep the start path is relative to the common path (ie, "web/bundles" = 2 levels) + if (1 === \count($startPathArr) && '' === $startPathArr[0]) { + $depth = 0; + } else { + $depth = \count($startPathArr) - $index; + } + + // Repeated "../" for each level need to reach the common path + $traverser = str_repeat('../', $depth); + + $endPathRemainder = implode('/', \array_slice($endPathArr, $index)); + + // Construct $endPath from traversing to the common path, then to the remaining $endPath + $relativePath = $traverser.('' !== $endPathRemainder ? $endPathRemainder.'/' : ''); + + return '' === $relativePath ? './' : $relativePath; + } + + /** + * Mirrors a directory to another. + * + * Copies files and directories from the origin directory into the target directory. By default: + * + * - existing files in the target directory will be overwritten, except if they are newer (see the `override` option) + * - files in the target directory that do not exist in the source directory will not be deleted (see the `delete` option) + * + * @param \Traversable|null $iterator Iterator that filters which files and directories to copy, if null a recursive iterator is created + * @param array $options An array of boolean options + * Valid options are: + * - $options['override'] If true, target files newer than origin files are overwritten (see copy(), defaults to false) + * - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink(), defaults to false) + * - $options['delete'] Whether to delete files that are not in the source directory (defaults to false) + * + * @throws IOException When file type is unknown + */ + public function mirror(string $originDir, string $targetDir, ?\Traversable $iterator = null, array $options = []): void + { + $targetDir = rtrim($targetDir, '/\\'); + $originDir = rtrim($originDir, '/\\'); + $originDirLen = \strlen($originDir); + + if (!$this->exists($originDir)) { + throw new IOException(sprintf('The origin directory specified "%s" was not found.', $originDir), 0, null, $originDir); + } + + // Iterate in destination folder to remove obsolete entries + if ($this->exists($targetDir) && isset($options['delete']) && $options['delete']) { + $deleteIterator = $iterator; + if (null === $deleteIterator) { + $flags = \FilesystemIterator::SKIP_DOTS; + $deleteIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($targetDir, $flags), \RecursiveIteratorIterator::CHILD_FIRST); + } + $targetDirLen = \strlen($targetDir); + foreach ($deleteIterator as $file) { + $origin = $originDir.substr($file->getPathname(), $targetDirLen); + if (!$this->exists($origin)) { + $this->remove($file); + } + } + } + + $copyOnWindows = $options['copy_on_windows'] ?? false; + + if (null === $iterator) { + $flags = $copyOnWindows ? \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS : \FilesystemIterator::SKIP_DOTS; + $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($originDir, $flags), \RecursiveIteratorIterator::SELF_FIRST); + } + + $this->mkdir($targetDir); + $filesCreatedWhileMirroring = []; + + foreach ($iterator as $file) { + if ($file->getPathname() === $targetDir || $file->getRealPath() === $targetDir || isset($filesCreatedWhileMirroring[$file->getRealPath()])) { + continue; + } + + $target = $targetDir.substr($file->getPathname(), $originDirLen); + $filesCreatedWhileMirroring[$target] = true; + + if (!$copyOnWindows && is_link($file)) { + $this->symlink($file->getLinkTarget(), $target); + } elseif (is_dir($file)) { + $this->mkdir($target); + } elseif (is_file($file)) { + $this->copy($file, $target, $options['override'] ?? false); + } else { + throw new IOException(sprintf('Unable to guess "%s" file type.', $file), 0, null, $file); + } + } + } + + /** + * Returns whether the file path is an absolute path. + */ + public function isAbsolutePath(string $file): bool + { + return '' !== $file && (strspn($file, '/\\', 0, 1) + || (\strlen($file) > 3 && ctype_alpha($file[0]) + && ':' === $file[1] + && strspn($file, '/\\', 2, 1) + ) + || null !== parse_url($file, \PHP_URL_SCHEME) + ); + } + + /** + * Creates a temporary file with support for custom stream wrappers. + * + * @param string $prefix The prefix of the generated temporary filename + * Note: Windows uses only the first three characters of prefix + * @param string $suffix The suffix of the generated temporary filename + * + * @return string The new temporary filename (with path), or throw an exception on failure + */ + public function tempnam(string $dir, string $prefix, string $suffix = ''): string + { + [$scheme, $hierarchy] = $this->getSchemeAndHierarchy($dir); + + // If no scheme or scheme is "file" or "gs" (Google Cloud) create temp file in local filesystem + if ((null === $scheme || 'file' === $scheme || 'gs' === $scheme) && '' === $suffix) { + // If tempnam failed or no scheme return the filename otherwise prepend the scheme + if ($tmpFile = self::box('tempnam', $hierarchy, $prefix)) { + if (null !== $scheme && 'gs' !== $scheme) { + return $scheme.'://'.$tmpFile; + } + + return $tmpFile; + } + + throw new IOException('A temporary file could not be created: '.self::$lastError); + } + + // Loop until we create a valid temp file or have reached 10 attempts + for ($i = 0; $i < 10; ++$i) { + // Create a unique filename + $tmpFile = $dir.'/'.$prefix.uniqid(mt_rand(), true).$suffix; + + // Use fopen instead of file_exists as some streams do not support stat + // Use mode 'x+' to atomically check existence and create to avoid a TOCTOU vulnerability + if (!$handle = self::box('fopen', $tmpFile, 'x+')) { + continue; + } + + // Close the file if it was successfully opened + self::box('fclose', $handle); + + return $tmpFile; + } + + throw new IOException('A temporary file could not be created: '.self::$lastError); + } + + /** + * Atomically dumps content into a file. + * + * @param string|resource $content The data to write into the file + * + * @throws IOException if the file cannot be written to + */ + public function dumpFile(string $filename, $content): void + { + if (\is_array($content)) { + throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be string or resource, array given.', __METHOD__)); + } + + $dir = \dirname($filename); + + if (is_link($filename) && $linkTarget = $this->readlink($filename)) { + $this->dumpFile(Path::makeAbsolute($linkTarget, $dir), $content); + + return; + } + + if (!is_dir($dir)) { + $this->mkdir($dir); + } + + // Will create a temp file with 0600 access rights + // when the filesystem supports chmod. + $tmpFile = $this->tempnam($dir, basename($filename)); + + try { + if (false === self::box('file_put_contents', $tmpFile, $content)) { + throw new IOException(sprintf('Failed to write file "%s": ', $filename).self::$lastError, 0, null, $filename); + } + + self::box('chmod', $tmpFile, file_exists($filename) ? fileperms($filename) : 0666 & ~umask()); + + $this->rename($tmpFile, $filename, true); + } finally { + if (file_exists($tmpFile)) { + self::box('unlink', $tmpFile); + } + } + } + + /** + * Appends content to an existing file. + * + * @param string|resource $content The content to append + * @param bool $lock Whether the file should be locked when writing to it + * + * @throws IOException If the file is not writable + */ + public function appendToFile(string $filename, $content, bool $lock = false): void + { + if (\is_array($content)) { + throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be string or resource, array given.', __METHOD__)); + } + + $dir = \dirname($filename); + + if (!is_dir($dir)) { + $this->mkdir($dir); + } + + if (false === self::box('file_put_contents', $filename, $content, \FILE_APPEND | ($lock ? \LOCK_EX : 0))) { + throw new IOException(sprintf('Failed to write file "%s": ', $filename).self::$lastError, 0, null, $filename); + } + } + + private function toIterable(string|iterable $files): iterable + { + return is_iterable($files) ? $files : [$files]; + } + + /** + * Gets a 2-tuple of scheme (may be null) and hierarchical part of a filename (e.g. file:///tmp -> [file, tmp]). + */ + private function getSchemeAndHierarchy(string $filename): array + { + $components = explode('://', $filename, 2); + + return 2 === \count($components) ? [$components[0], $components[1]] : [null, $components[0]]; + } + + private static function assertFunctionExists(string $func): void + { + if (!\function_exists($func)) { + throw new IOException(sprintf('Unable to perform filesystem operation because the "%s()" function has been disabled.', $func)); + } + } + + private static function box(string $func, mixed ...$args): mixed + { + self::assertFunctionExists($func); + + self::$lastError = null; + set_error_handler(self::handleError(...)); + try { + return $func(...$args); + } finally { + restore_error_handler(); + } + } + + /** + * @internal + */ + public static function handleError(int $type, string $msg): void + { + self::$lastError = $msg; + } +} diff --git a/vendor/symfony/filesystem/LICENSE b/vendor/symfony/filesystem/LICENSE new file mode 100644 index 00000000..0138f8f0 --- /dev/null +++ b/vendor/symfony/filesystem/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/filesystem/Path.php b/vendor/symfony/filesystem/Path.php new file mode 100644 index 00000000..01b56172 --- /dev/null +++ b/vendor/symfony/filesystem/Path.php @@ -0,0 +1,816 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem; + +use Symfony\Component\Filesystem\Exception\InvalidArgumentException; +use Symfony\Component\Filesystem\Exception\RuntimeException; + +/** + * Contains utility methods for handling path strings. + * + * The methods in this class are able to deal with both UNIX and Windows paths + * with both forward and backward slashes. All methods return normalized parts + * containing only forward slashes and no excess "." and ".." segments. + * + * @author Bernhard Schussek + * @author Thomas Schulz + * @author Théo Fidry + */ +final class Path +{ + /** + * The number of buffer entries that triggers a cleanup operation. + */ + private const CLEANUP_THRESHOLD = 1250; + + /** + * The buffer size after the cleanup operation. + */ + private const CLEANUP_SIZE = 1000; + + /** + * Buffers input/output of {@link canonicalize()}. + * + * @var array + */ + private static array $buffer = []; + + private static int $bufferSize = 0; + + /** + * Canonicalizes the given path. + * + * During normalization, all slashes are replaced by forward slashes ("/"). + * Furthermore, all "." and ".." segments are removed as far as possible. + * ".." segments at the beginning of relative paths are not removed. + * + * ```php + * echo Path::canonicalize("\symfony\puli\..\css\style.css"); + * // => /symfony/css/style.css + * + * echo Path::canonicalize("../css/./style.css"); + * // => ../css/style.css + * ``` + * + * This method is able to deal with both UNIX and Windows paths. + */ + public static function canonicalize(string $path): string + { + if ('' === $path) { + return ''; + } + + // This method is called by many other methods in this class. Buffer + // the canonicalized paths to make up for the severe performance + // decrease. + if (isset(self::$buffer[$path])) { + return self::$buffer[$path]; + } + + // Replace "~" with user's home directory. + if ('~' === $path[0]) { + $path = self::getHomeDirectory().substr($path, 1); + } + + $path = self::normalize($path); + + [$root, $pathWithoutRoot] = self::split($path); + + $canonicalParts = self::findCanonicalParts($root, $pathWithoutRoot); + + // Add the root directory again + self::$buffer[$path] = $canonicalPath = $root.implode('/', $canonicalParts); + ++self::$bufferSize; + + // Clean up regularly to prevent memory leaks + if (self::$bufferSize > self::CLEANUP_THRESHOLD) { + self::$buffer = \array_slice(self::$buffer, -self::CLEANUP_SIZE, null, true); + self::$bufferSize = self::CLEANUP_SIZE; + } + + return $canonicalPath; + } + + /** + * Normalizes the given path. + * + * During normalization, all slashes are replaced by forward slashes ("/"). + * Contrary to {@link canonicalize()}, this method does not remove invalid + * or dot path segments. Consequently, it is much more efficient and should + * be used whenever the given path is known to be a valid, absolute system + * path. + * + * This method is able to deal with both UNIX and Windows paths. + */ + public static function normalize(string $path): string + { + return str_replace('\\', '/', $path); + } + + /** + * Returns the directory part of the path. + * + * This method is similar to PHP's dirname(), but handles various cases + * where dirname() returns a weird result: + * + * - dirname() does not accept backslashes on UNIX + * - dirname("C:/symfony") returns "C:", not "C:/" + * - dirname("C:/") returns ".", not "C:/" + * - dirname("C:") returns ".", not "C:/" + * - dirname("symfony") returns ".", not "" + * - dirname() does not canonicalize the result + * + * This method fixes these shortcomings and behaves like dirname() + * otherwise. + * + * The result is a canonical path. + * + * @return string The canonical directory part. Returns the root directory + * if the root directory is passed. Returns an empty string + * if a relative path is passed that contains no slashes. + * Returns an empty string if an empty string is passed. + */ + public static function getDirectory(string $path): string + { + if ('' === $path) { + return ''; + } + + $path = self::canonicalize($path); + + // Maintain scheme + if (false !== $schemeSeparatorPosition = strpos($path, '://')) { + $scheme = substr($path, 0, $schemeSeparatorPosition + 3); + $path = substr($path, $schemeSeparatorPosition + 3); + } else { + $scheme = ''; + } + + if (false === $dirSeparatorPosition = strrpos($path, '/')) { + return ''; + } + + // Directory equals root directory "/" + if (0 === $dirSeparatorPosition) { + return $scheme.'/'; + } + + // Directory equals Windows root "C:/" + if (2 === $dirSeparatorPosition && ctype_alpha($path[0]) && ':' === $path[1]) { + return $scheme.substr($path, 0, 3); + } + + return $scheme.substr($path, 0, $dirSeparatorPosition); + } + + /** + * Returns canonical path of the user's home directory. + * + * Supported operating systems: + * + * - UNIX + * - Windows8 and upper + * + * If your operating system or environment isn't supported, an exception is thrown. + * + * The result is a canonical path. + * + * @throws RuntimeException If your operating system or environment isn't supported + */ + public static function getHomeDirectory(): string + { + // For UNIX support + if (getenv('HOME')) { + return self::canonicalize(getenv('HOME')); + } + + // For >= Windows8 support + if (getenv('HOMEDRIVE') && getenv('HOMEPATH')) { + return self::canonicalize(getenv('HOMEDRIVE').getenv('HOMEPATH')); + } + + throw new RuntimeException("Cannot find the home directory path: Your environment or operating system isn't supported."); + } + + /** + * Returns the root directory of a path. + * + * The result is a canonical path. + * + * @return string The canonical root directory. Returns an empty string if + * the given path is relative or empty. + */ + public static function getRoot(string $path): string + { + if ('' === $path) { + return ''; + } + + // Maintain scheme + if (false !== $schemeSeparatorPosition = strpos($path, '://')) { + $scheme = substr($path, 0, $schemeSeparatorPosition + 3); + $path = substr($path, $schemeSeparatorPosition + 3); + } else { + $scheme = ''; + } + + $firstCharacter = $path[0]; + + // UNIX root "/" or "\" (Windows style) + if ('/' === $firstCharacter || '\\' === $firstCharacter) { + return $scheme.'/'; + } + + $length = \strlen($path); + + // Windows root + if ($length > 1 && ':' === $path[1] && ctype_alpha($firstCharacter)) { + // Special case: "C:" + if (2 === $length) { + return $scheme.$path.'/'; + } + + // Normal case: "C:/ or "C:\" + if ('/' === $path[2] || '\\' === $path[2]) { + return $scheme.$firstCharacter.$path[1].'/'; + } + } + + return ''; + } + + /** + * Returns the file name without the extension from a file path. + * + * @param string|null $extension if specified, only that extension is cut + * off (may contain leading dot) + */ + public static function getFilenameWithoutExtension(string $path, ?string $extension = null): string + { + if ('' === $path) { + return ''; + } + + if (null !== $extension) { + // remove extension and trailing dot + return rtrim(basename($path, $extension), '.'); + } + + return pathinfo($path, \PATHINFO_FILENAME); + } + + /** + * Returns the extension from a file path (without leading dot). + * + * @param bool $forceLowerCase forces the extension to be lower-case + */ + public static function getExtension(string $path, bool $forceLowerCase = false): string + { + if ('' === $path) { + return ''; + } + + $extension = pathinfo($path, \PATHINFO_EXTENSION); + + if ($forceLowerCase) { + $extension = self::toLower($extension); + } + + return $extension; + } + + /** + * Returns whether the path has an (or the specified) extension. + * + * @param string $path the path string + * @param string|string[]|null $extensions if null or not provided, checks if + * an extension exists, otherwise + * checks for the specified extension + * or array of extensions (with or + * without leading dot) + * @param bool $ignoreCase whether to ignore case-sensitivity + */ + public static function hasExtension(string $path, $extensions = null, bool $ignoreCase = false): bool + { + if ('' === $path) { + return false; + } + + $actualExtension = self::getExtension($path, $ignoreCase); + + // Only check if path has any extension + if ([] === $extensions || null === $extensions) { + return '' !== $actualExtension; + } + + if (\is_string($extensions)) { + $extensions = [$extensions]; + } + + foreach ($extensions as $key => $extension) { + if ($ignoreCase) { + $extension = self::toLower($extension); + } + + // remove leading '.' in extensions array + $extensions[$key] = ltrim($extension, '.'); + } + + return \in_array($actualExtension, $extensions, true); + } + + /** + * Changes the extension of a path string. + * + * @param string $path The path string with filename.ext to change. + * @param string $extension new extension (with or without leading dot) + * + * @return string the path string with new file extension + */ + public static function changeExtension(string $path, string $extension): string + { + if ('' === $path) { + return ''; + } + + $actualExtension = self::getExtension($path); + $extension = ltrim($extension, '.'); + + // No extension for paths + if ('/' === substr($path, -1)) { + return $path; + } + + // No actual extension in path + if (empty($actualExtension)) { + return $path.('.' === substr($path, -1) ? '' : '.').$extension; + } + + return substr($path, 0, -\strlen($actualExtension)).$extension; + } + + public static function isAbsolute(string $path): bool + { + if ('' === $path) { + return false; + } + + // Strip scheme + if (false !== $schemeSeparatorPosition = strpos($path, '://')) { + $path = substr($path, $schemeSeparatorPosition + 3); + } + + $firstCharacter = $path[0]; + + // UNIX root "/" or "\" (Windows style) + if ('/' === $firstCharacter || '\\' === $firstCharacter) { + return true; + } + + // Windows root + if (\strlen($path) > 1 && ctype_alpha($firstCharacter) && ':' === $path[1]) { + // Special case: "C:" + if (2 === \strlen($path)) { + return true; + } + + // Normal case: "C:/ or "C:\" + if ('/' === $path[2] || '\\' === $path[2]) { + return true; + } + } + + return false; + } + + public static function isRelative(string $path): bool + { + return !self::isAbsolute($path); + } + + /** + * Turns a relative path into an absolute path in canonical form. + * + * Usually, the relative path is appended to the given base path. Dot + * segments ("." and "..") are removed/collapsed and all slashes turned + * into forward slashes. + * + * ```php + * echo Path::makeAbsolute("../style.css", "/symfony/puli/css"); + * // => /symfony/puli/style.css + * ``` + * + * If an absolute path is passed, that path is returned unless its root + * directory is different than the one of the base path. In that case, an + * exception is thrown. + * + * ```php + * Path::makeAbsolute("/style.css", "/symfony/puli/css"); + * // => /style.css + * + * Path::makeAbsolute("C:/style.css", "C:/symfony/puli/css"); + * // => C:/style.css + * + * Path::makeAbsolute("C:/style.css", "/symfony/puli/css"); + * // InvalidArgumentException + * ``` + * + * If the base path is not an absolute path, an exception is thrown. + * + * The result is a canonical path. + * + * @param string $basePath an absolute base path + * + * @throws InvalidArgumentException if the base path is not absolute or if + * the given path is an absolute path with + * a different root than the base path + */ + public static function makeAbsolute(string $path, string $basePath): string + { + if ('' === $basePath) { + throw new InvalidArgumentException(sprintf('The base path must be a non-empty string. Got: "%s".', $basePath)); + } + + if (!self::isAbsolute($basePath)) { + throw new InvalidArgumentException(sprintf('The base path "%s" is not an absolute path.', $basePath)); + } + + if (self::isAbsolute($path)) { + return self::canonicalize($path); + } + + if (false !== $schemeSeparatorPosition = strpos($basePath, '://')) { + $scheme = substr($basePath, 0, $schemeSeparatorPosition + 3); + $basePath = substr($basePath, $schemeSeparatorPosition + 3); + } else { + $scheme = ''; + } + + return $scheme.self::canonicalize(rtrim($basePath, '/\\').'/'.$path); + } + + /** + * Turns a path into a relative path. + * + * The relative path is created relative to the given base path: + * + * ```php + * echo Path::makeRelative("/symfony/style.css", "/symfony/puli"); + * // => ../style.css + * ``` + * + * If a relative path is passed and the base path is absolute, the relative + * path is returned unchanged: + * + * ```php + * Path::makeRelative("style.css", "/symfony/puli/css"); + * // => style.css + * ``` + * + * If both paths are relative, the relative path is created with the + * assumption that both paths are relative to the same directory: + * + * ```php + * Path::makeRelative("style.css", "symfony/puli/css"); + * // => ../../../style.css + * ``` + * + * If both paths are absolute, their root directory must be the same, + * otherwise an exception is thrown: + * + * ```php + * Path::makeRelative("C:/symfony/style.css", "/symfony/puli"); + * // InvalidArgumentException + * ``` + * + * If the passed path is absolute, but the base path is not, an exception + * is thrown as well: + * + * ```php + * Path::makeRelative("/symfony/style.css", "symfony/puli"); + * // InvalidArgumentException + * ``` + * + * If the base path is not an absolute path, an exception is thrown. + * + * The result is a canonical path. + * + * @throws InvalidArgumentException if the base path is not absolute or if + * the given path has a different root + * than the base path + */ + public static function makeRelative(string $path, string $basePath): string + { + $path = self::canonicalize($path); + $basePath = self::canonicalize($basePath); + + [$root, $relativePath] = self::split($path); + [$baseRoot, $relativeBasePath] = self::split($basePath); + + // If the base path is given as absolute path and the path is already + // relative, consider it to be relative to the given absolute path + // already + if ('' === $root && '' !== $baseRoot) { + // If base path is already in its root + if ('' === $relativeBasePath) { + $relativePath = ltrim($relativePath, './\\'); + } + + return $relativePath; + } + + // If the passed path is absolute, but the base path is not, we + // cannot generate a relative path + if ('' !== $root && '' === $baseRoot) { + throw new InvalidArgumentException(sprintf('The absolute path "%s" cannot be made relative to the relative path "%s". You should provide an absolute base path instead.', $path, $basePath)); + } + + // Fail if the roots of the two paths are different + if ($baseRoot && $root !== $baseRoot) { + throw new InvalidArgumentException(sprintf('The path "%s" cannot be made relative to "%s", because they have different roots ("%s" and "%s").', $path, $basePath, $root, $baseRoot)); + } + + if ('' === $relativeBasePath) { + return $relativePath; + } + + // Build a "../../" prefix with as many "../" parts as necessary + $parts = explode('/', $relativePath); + $baseParts = explode('/', $relativeBasePath); + $dotDotPrefix = ''; + + // Once we found a non-matching part in the prefix, we need to add + // "../" parts for all remaining parts + $match = true; + + foreach ($baseParts as $index => $basePart) { + if ($match && isset($parts[$index]) && $basePart === $parts[$index]) { + unset($parts[$index]); + + continue; + } + + $match = false; + $dotDotPrefix .= '../'; + } + + return rtrim($dotDotPrefix.implode('/', $parts), '/'); + } + + /** + * Returns whether the given path is on the local filesystem. + */ + public static function isLocal(string $path): bool + { + return '' !== $path && !str_contains($path, '://'); + } + + /** + * Returns the longest common base path in canonical form of a set of paths or + * `null` if the paths are on different Windows partitions. + * + * Dot segments ("." and "..") are removed/collapsed and all slashes turned + * into forward slashes. + * + * ```php + * $basePath = Path::getLongestCommonBasePath( + * '/symfony/css/style.css', + * '/symfony/css/..' + * ); + * // => /symfony + * ``` + * + * The root is returned if no common base path can be found: + * + * ```php + * $basePath = Path::getLongestCommonBasePath( + * '/symfony/css/style.css', + * '/puli/css/..' + * ); + * // => / + * ``` + * + * If the paths are located on different Windows partitions, `null` is + * returned. + * + * ```php + * $basePath = Path::getLongestCommonBasePath( + * 'C:/symfony/css/style.css', + * 'D:/symfony/css/..' + * ); + * // => null + * ``` + */ + public static function getLongestCommonBasePath(string ...$paths): ?string + { + [$bpRoot, $basePath] = self::split(self::canonicalize(reset($paths))); + + for (next($paths); null !== key($paths) && '' !== $basePath; next($paths)) { + [$root, $path] = self::split(self::canonicalize(current($paths))); + + // If we deal with different roots (e.g. C:/ vs. D:/), it's time + // to quit + if ($root !== $bpRoot) { + return null; + } + + // Make the base path shorter until it fits into path + while (true) { + if ('.' === $basePath) { + // No more base paths + $basePath = ''; + + // next path + continue 2; + } + + // Prevent false positives for common prefixes + // see isBasePath() + if (str_starts_with($path.'/', $basePath.'/')) { + // next path + continue 2; + } + + $basePath = \dirname($basePath); + } + } + + return $bpRoot.$basePath; + } + + /** + * Joins two or more path strings into a canonical path. + */ + public static function join(string ...$paths): string + { + $finalPath = null; + $wasScheme = false; + + foreach ($paths as $path) { + if ('' === $path) { + continue; + } + + if (null === $finalPath) { + // For first part we keep slashes, like '/top', 'C:\' or 'phar://' + $finalPath = $path; + $wasScheme = str_contains($path, '://'); + continue; + } + + // Only add slash if previous part didn't end with '/' or '\' + if (!\in_array(substr($finalPath, -1), ['/', '\\'])) { + $finalPath .= '/'; + } + + // If first part included a scheme like 'phar://' we allow \current part to start with '/', otherwise trim + $finalPath .= $wasScheme ? $path : ltrim($path, '/'); + $wasScheme = false; + } + + if (null === $finalPath) { + return ''; + } + + return self::canonicalize($finalPath); + } + + /** + * Returns whether a path is a base path of another path. + * + * Dot segments ("." and "..") are removed/collapsed and all slashes turned + * into forward slashes. + * + * ```php + * Path::isBasePath('/symfony', '/symfony/css'); + * // => true + * + * Path::isBasePath('/symfony', '/symfony'); + * // => true + * + * Path::isBasePath('/symfony', '/symfony/..'); + * // => false + * + * Path::isBasePath('/symfony', '/puli'); + * // => false + * ``` + */ + public static function isBasePath(string $basePath, string $ofPath): bool + { + $basePath = self::canonicalize($basePath); + $ofPath = self::canonicalize($ofPath); + + // Append slashes to prevent false positives when two paths have + // a common prefix, for example /base/foo and /base/foobar. + // Don't append a slash for the root "/", because then that root + // won't be discovered as common prefix ("//" is not a prefix of + // "/foobar/"). + return str_starts_with($ofPath.'/', rtrim($basePath, '/').'/'); + } + + /** + * @return string[] + */ + private static function findCanonicalParts(string $root, string $pathWithoutRoot): array + { + $parts = explode('/', $pathWithoutRoot); + + $canonicalParts = []; + + // Collapse "." and "..", if possible + foreach ($parts as $part) { + if ('.' === $part || '' === $part) { + continue; + } + + // Collapse ".." with the previous part, if one exists + // Don't collapse ".." if the previous part is also ".." + if ('..' === $part && \count($canonicalParts) > 0 && '..' !== $canonicalParts[\count($canonicalParts) - 1]) { + array_pop($canonicalParts); + + continue; + } + + // Only add ".." prefixes for relative paths + if ('..' !== $part || '' === $root) { + $canonicalParts[] = $part; + } + } + + return $canonicalParts; + } + + /** + * Splits a canonical path into its root directory and the remainder. + * + * If the path has no root directory, an empty root directory will be + * returned. + * + * If the root directory is a Windows style partition, the resulting root + * will always contain a trailing slash. + * + * list ($root, $path) = Path::split("C:/symfony") + * // => ["C:/", "symfony"] + * + * list ($root, $path) = Path::split("C:") + * // => ["C:/", ""] + * + * @return array{string, string} an array with the root directory and the remaining relative path + */ + private static function split(string $path): array + { + if ('' === $path) { + return ['', '']; + } + + // Remember scheme as part of the root, if any + if (false !== $schemeSeparatorPosition = strpos($path, '://')) { + $root = substr($path, 0, $schemeSeparatorPosition + 3); + $path = substr($path, $schemeSeparatorPosition + 3); + } else { + $root = ''; + } + + $length = \strlen($path); + + // Remove and remember root directory + if (str_starts_with($path, '/')) { + $root .= '/'; + $path = $length > 1 ? substr($path, 1) : ''; + } elseif ($length > 1 && ctype_alpha($path[0]) && ':' === $path[1]) { + if (2 === $length) { + // Windows special case: "C:" + $root .= $path.'/'; + $path = ''; + } elseif ('/' === $path[2]) { + // Windows normal case: "C:/".. + $root .= substr($path, 0, 3); + $path = $length > 3 ? substr($path, 3) : ''; + } + } + + return [$root, $path]; + } + + private static function toLower(string $string): string + { + if (false !== $encoding = mb_detect_encoding($string, null, true)) { + return mb_strtolower($string, $encoding); + } + + return strtolower($string); + } + + private function __construct() + { + } +} diff --git a/vendor/symfony/filesystem/README.md b/vendor/symfony/filesystem/README.md new file mode 100644 index 00000000..f2f6d45f --- /dev/null +++ b/vendor/symfony/filesystem/README.md @@ -0,0 +1,13 @@ +Filesystem Component +==================== + +The Filesystem component provides basic utilities for the filesystem. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/filesystem.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/filesystem/composer.json b/vendor/symfony/filesystem/composer.json new file mode 100644 index 00000000..1e054b68 --- /dev/null +++ b/vendor/symfony/filesystem/composer.json @@ -0,0 +1,30 @@ +{ + "name": "symfony/filesystem", + "type": "library", + "description": "Provides basic utilities for the filesystem", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Filesystem\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/finder/CHANGELOG.md b/vendor/symfony/finder/CHANGELOG.md new file mode 100644 index 00000000..e8383024 --- /dev/null +++ b/vendor/symfony/finder/CHANGELOG.md @@ -0,0 +1,103 @@ +CHANGELOG +========= + +6.4 +--- + + * Add early directory pruning to `Finder::filter()` + +6.2 +--- + + * Add `Finder::sortByExtension()` and `Finder::sortBySize()` + * Add `Finder::sortByCaseInsensitiveName()` to sort by name with case insensitive sorting methods + +6.0 +--- + + * Remove `Comparator::setTarget()` and `Comparator::setOperator()` + +5.4.0 +----- + + * Deprecate `Comparator::setTarget()` and `Comparator::setOperator()` + * Add a constructor to `Comparator` that allows setting target and operator + * Finder's iterator has now `Symfony\Component\Finder\SplFileInfo` inner type specified + * Add recursive .gitignore files support + +5.0.0 +----- + + * added `$useNaturalSort` argument to `Finder::sortByName()` + +4.3.0 +----- + + * added Finder::ignoreVCSIgnored() to ignore files based on rules listed in .gitignore + +4.2.0 +----- + + * added $useNaturalSort option to Finder::sortByName() method + * the `Finder::sortByName()` method will have a new `$useNaturalSort` + argument in version 5.0, not defining it is deprecated + * added `Finder::reverseSorting()` to reverse the sorting + +4.0.0 +----- + + * removed `ExceptionInterface` + * removed `Symfony\Component\Finder\Iterator\FilterIterator` + +3.4.0 +----- + + * deprecated `Symfony\Component\Finder\Iterator\FilterIterator` + * added Finder::hasResults() method to check if any results were found + +3.3.0 +----- + + * added double-star matching to Glob::toRegex() + +3.0.0 +----- + + * removed deprecated classes + +2.8.0 +----- + + * deprecated adapters and related classes + +2.5.0 +----- + * added support for GLOB_BRACE in the paths passed to Finder::in() + +2.3.0 +----- + + * added a way to ignore unreadable directories (via Finder::ignoreUnreadableDirs()) + * unified the way subfolders that are not executable are handled by always throwing an AccessDeniedException exception + +2.2.0 +----- + + * added Finder::path() and Finder::notPath() methods + * added finder adapters to improve performance on specific platforms + * added support for wildcard characters (glob patterns) in the paths passed + to Finder::in() + +2.1.0 +----- + + * added Finder::sortByAccessedTime(), Finder::sortByChangedTime(), and + Finder::sortByModifiedTime() + * added Countable to Finder + * added support for an array of directories as an argument to + Finder::exclude() + * added searching based on the file content via Finder::contains() and + Finder::notContains() + * added support for the != operator in the Comparator + * [BC BREAK] filter expressions (used for file name and content) are no more + considered as regexps but glob patterns when they are enclosed in '*' or '?' diff --git a/vendor/symfony/finder/Comparator/Comparator.php b/vendor/symfony/finder/Comparator/Comparator.php new file mode 100644 index 00000000..bd685834 --- /dev/null +++ b/vendor/symfony/finder/Comparator/Comparator.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Comparator; + +/** + * @author Fabien Potencier + */ +class Comparator +{ + private string $target; + private string $operator; + + public function __construct(string $target, string $operator = '==') + { + if (!\in_array($operator, ['>', '<', '>=', '<=', '==', '!='])) { + throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator)); + } + + $this->target = $target; + $this->operator = $operator; + } + + /** + * Gets the target value. + */ + public function getTarget(): string + { + return $this->target; + } + + /** + * Gets the comparison operator. + */ + public function getOperator(): string + { + return $this->operator; + } + + /** + * Tests against the target. + */ + public function test(mixed $test): bool + { + return match ($this->operator) { + '>' => $test > $this->target, + '>=' => $test >= $this->target, + '<' => $test < $this->target, + '<=' => $test <= $this->target, + '!=' => $test != $this->target, + default => $test == $this->target, + }; + } +} diff --git a/vendor/symfony/finder/Comparator/DateComparator.php b/vendor/symfony/finder/Comparator/DateComparator.php new file mode 100644 index 00000000..e0c523d0 --- /dev/null +++ b/vendor/symfony/finder/Comparator/DateComparator.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Comparator; + +/** + * DateCompare compiles date comparisons. + * + * @author Fabien Potencier + */ +class DateComparator extends Comparator +{ + /** + * @param string $test A comparison string + * + * @throws \InvalidArgumentException If the test is not understood + */ + public function __construct(string $test) + { + if (!preg_match('#^\s*(==|!=|[<>]=?|after|since|before|until)?\s*(.+?)\s*$#i', $test, $matches)) { + throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a date test.', $test)); + } + + try { + $date = new \DateTimeImmutable($matches[2]); + $target = $date->format('U'); + } catch (\Exception) { + throw new \InvalidArgumentException(sprintf('"%s" is not a valid date.', $matches[2])); + } + + $operator = $matches[1] ?? '=='; + if ('since' === $operator || 'after' === $operator) { + $operator = '>'; + } + + if ('until' === $operator || 'before' === $operator) { + $operator = '<'; + } + + parent::__construct($target, $operator); + } +} diff --git a/vendor/symfony/finder/Comparator/NumberComparator.php b/vendor/symfony/finder/Comparator/NumberComparator.php new file mode 100644 index 00000000..dd308207 --- /dev/null +++ b/vendor/symfony/finder/Comparator/NumberComparator.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Comparator; + +/** + * NumberComparator compiles a simple comparison to an anonymous + * subroutine, which you can call with a value to be tested again. + * + * Now this would be very pointless, if NumberCompare didn't understand + * magnitudes. + * + * The target value may use magnitudes of kilobytes (k, ki), + * megabytes (m, mi), or gigabytes (g, gi). Those suffixed + * with an i use the appropriate 2**n version in accordance with the + * IEC standard: http://physics.nist.gov/cuu/Units/binary.html + * + * Based on the Perl Number::Compare module. + * + * @author Fabien Potencier PHP port + * @author Richard Clamp Perl version + * @copyright 2004-2005 Fabien Potencier + * @copyright 2002 Richard Clamp + * + * @see http://physics.nist.gov/cuu/Units/binary.html + */ +class NumberComparator extends Comparator +{ + /** + * @param string|null $test A comparison string or null + * + * @throws \InvalidArgumentException If the test is not understood + */ + public function __construct(?string $test) + { + if (null === $test || !preg_match('#^\s*(==|!=|[<>]=?)?\s*([0-9\.]+)\s*([kmg]i?)?\s*$#i', $test, $matches)) { + throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a number test.', $test ?? 'null')); + } + + $target = $matches[2]; + if (!is_numeric($target)) { + throw new \InvalidArgumentException(sprintf('Invalid number "%s".', $target)); + } + if (isset($matches[3])) { + // magnitude + switch (strtolower($matches[3])) { + case 'k': + $target *= 1000; + break; + case 'ki': + $target *= 1024; + break; + case 'm': + $target *= 1000000; + break; + case 'mi': + $target *= 1024 * 1024; + break; + case 'g': + $target *= 1000000000; + break; + case 'gi': + $target *= 1024 * 1024 * 1024; + break; + } + } + + parent::__construct($target, $matches[1] ?: '=='); + } +} diff --git a/vendor/symfony/finder/Exception/AccessDeniedException.php b/vendor/symfony/finder/Exception/AccessDeniedException.php new file mode 100644 index 00000000..ee195ea8 --- /dev/null +++ b/vendor/symfony/finder/Exception/AccessDeniedException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Exception; + +/** + * @author Jean-François Simon + */ +class AccessDeniedException extends \UnexpectedValueException +{ +} diff --git a/vendor/symfony/finder/Exception/DirectoryNotFoundException.php b/vendor/symfony/finder/Exception/DirectoryNotFoundException.php new file mode 100644 index 00000000..c6cc0f27 --- /dev/null +++ b/vendor/symfony/finder/Exception/DirectoryNotFoundException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Exception; + +/** + * @author Andreas Erhard + */ +class DirectoryNotFoundException extends \InvalidArgumentException +{ +} diff --git a/vendor/symfony/finder/Finder.php b/vendor/symfony/finder/Finder.php new file mode 100644 index 00000000..d062a60a --- /dev/null +++ b/vendor/symfony/finder/Finder.php @@ -0,0 +1,856 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder; + +use Symfony\Component\Finder\Comparator\DateComparator; +use Symfony\Component\Finder\Comparator\NumberComparator; +use Symfony\Component\Finder\Exception\DirectoryNotFoundException; +use Symfony\Component\Finder\Iterator\CustomFilterIterator; +use Symfony\Component\Finder\Iterator\DateRangeFilterIterator; +use Symfony\Component\Finder\Iterator\DepthRangeFilterIterator; +use Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator; +use Symfony\Component\Finder\Iterator\FilecontentFilterIterator; +use Symfony\Component\Finder\Iterator\FilenameFilterIterator; +use Symfony\Component\Finder\Iterator\LazyIterator; +use Symfony\Component\Finder\Iterator\SizeRangeFilterIterator; +use Symfony\Component\Finder\Iterator\SortableIterator; + +/** + * Finder allows to build rules to find files and directories. + * + * It is a thin wrapper around several specialized iterator classes. + * + * All rules may be invoked several times. + * + * All methods return the current Finder object to allow chaining: + * + * $finder = Finder::create()->files()->name('*.php')->in(__DIR__); + * + * @author Fabien Potencier + * + * @implements \IteratorAggregate + */ +class Finder implements \IteratorAggregate, \Countable +{ + public const IGNORE_VCS_FILES = 1; + public const IGNORE_DOT_FILES = 2; + public const IGNORE_VCS_IGNORED_FILES = 4; + + private int $mode = 0; + private array $names = []; + private array $notNames = []; + private array $exclude = []; + private array $filters = []; + private array $pruneFilters = []; + private array $depths = []; + private array $sizes = []; + private bool $followLinks = false; + private bool $reverseSorting = false; + private \Closure|int|false $sort = false; + private int $ignore = 0; + private array $dirs = []; + private array $dates = []; + private array $iterators = []; + private array $contains = []; + private array $notContains = []; + private array $paths = []; + private array $notPaths = []; + private bool $ignoreUnreadableDirs = false; + + private static array $vcsPatterns = ['.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg']; + + public function __construct() + { + $this->ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES; + } + + /** + * Creates a new Finder. + */ + public static function create(): static + { + return new static(); + } + + /** + * Restricts the matching to directories only. + * + * @return $this + */ + public function directories(): static + { + $this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES; + + return $this; + } + + /** + * Restricts the matching to files only. + * + * @return $this + */ + public function files(): static + { + $this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES; + + return $this; + } + + /** + * Adds tests for the directory depth. + * + * Usage: + * + * $finder->depth('> 1') // the Finder will start matching at level 1. + * $finder->depth('< 3') // the Finder will descend at most 3 levels of directories below the starting point. + * $finder->depth(['>= 1', '< 3']) + * + * @param string|int|string[]|int[] $levels The depth level expression or an array of depth levels + * + * @return $this + * + * @see DepthRangeFilterIterator + * @see NumberComparator + */ + public function depth(string|int|array $levels): static + { + foreach ((array) $levels as $level) { + $this->depths[] = new Comparator\NumberComparator($level); + } + + return $this; + } + + /** + * Adds tests for file dates (last modified). + * + * The date must be something that strtotime() is able to parse: + * + * $finder->date('since yesterday'); + * $finder->date('until 2 days ago'); + * $finder->date('> now - 2 hours'); + * $finder->date('>= 2005-10-15'); + * $finder->date(['>= 2005-10-15', '<= 2006-05-27']); + * + * @param string|string[] $dates A date range string or an array of date ranges + * + * @return $this + * + * @see strtotime + * @see DateRangeFilterIterator + * @see DateComparator + */ + public function date(string|array $dates): static + { + foreach ((array) $dates as $date) { + $this->dates[] = new Comparator\DateComparator($date); + } + + return $this; + } + + /** + * Adds rules that files must match. + * + * You can use patterns (delimited with / sign), globs or simple strings. + * + * $finder->name('/\.php$/') + * $finder->name('*.php') // same as above, without dot files + * $finder->name('test.php') + * $finder->name(['test.py', 'test.php']) + * + * @param string|string[] $patterns A pattern (a regexp, a glob, or a string) or an array of patterns + * + * @return $this + * + * @see FilenameFilterIterator + */ + public function name(string|array $patterns): static + { + $this->names = array_merge($this->names, (array) $patterns); + + return $this; + } + + /** + * Adds rules that files must not match. + * + * @param string|string[] $patterns A pattern (a regexp, a glob, or a string) or an array of patterns + * + * @return $this + * + * @see FilenameFilterIterator + */ + public function notName(string|array $patterns): static + { + $this->notNames = array_merge($this->notNames, (array) $patterns); + + return $this; + } + + /** + * Adds tests that file contents must match. + * + * Strings or PCRE patterns can be used: + * + * $finder->contains('Lorem ipsum') + * $finder->contains('/Lorem ipsum/i') + * $finder->contains(['dolor', '/ipsum/i']) + * + * @param string|string[] $patterns A pattern (string or regexp) or an array of patterns + * + * @return $this + * + * @see FilecontentFilterIterator + */ + public function contains(string|array $patterns): static + { + $this->contains = array_merge($this->contains, (array) $patterns); + + return $this; + } + + /** + * Adds tests that file contents must not match. + * + * Strings or PCRE patterns can be used: + * + * $finder->notContains('Lorem ipsum') + * $finder->notContains('/Lorem ipsum/i') + * $finder->notContains(['lorem', '/dolor/i']) + * + * @param string|string[] $patterns A pattern (string or regexp) or an array of patterns + * + * @return $this + * + * @see FilecontentFilterIterator + */ + public function notContains(string|array $patterns): static + { + $this->notContains = array_merge($this->notContains, (array) $patterns); + + return $this; + } + + /** + * Adds rules that filenames must match. + * + * You can use patterns (delimited with / sign) or simple strings. + * + * $finder->path('some/special/dir') + * $finder->path('/some\/special\/dir/') // same as above + * $finder->path(['some dir', 'another/dir']) + * + * Use only / as dirname separator. + * + * @param string|string[] $patterns A pattern (a regexp or a string) or an array of patterns + * + * @return $this + * + * @see FilenameFilterIterator + */ + public function path(string|array $patterns): static + { + $this->paths = array_merge($this->paths, (array) $patterns); + + return $this; + } + + /** + * Adds rules that filenames must not match. + * + * You can use patterns (delimited with / sign) or simple strings. + * + * $finder->notPath('some/special/dir') + * $finder->notPath('/some\/special\/dir/') // same as above + * $finder->notPath(['some/file.txt', 'another/file.log']) + * + * Use only / as dirname separator. + * + * @param string|string[] $patterns A pattern (a regexp or a string) or an array of patterns + * + * @return $this + * + * @see FilenameFilterIterator + */ + public function notPath(string|array $patterns): static + { + $this->notPaths = array_merge($this->notPaths, (array) $patterns); + + return $this; + } + + /** + * Adds tests for file sizes. + * + * $finder->size('> 10K'); + * $finder->size('<= 1Ki'); + * $finder->size(4); + * $finder->size(['> 10K', '< 20K']) + * + * @param string|int|string[]|int[] $sizes A size range string or an integer or an array of size ranges + * + * @return $this + * + * @see SizeRangeFilterIterator + * @see NumberComparator + */ + public function size(string|int|array $sizes): static + { + foreach ((array) $sizes as $size) { + $this->sizes[] = new Comparator\NumberComparator($size); + } + + return $this; + } + + /** + * Excludes directories. + * + * Directories passed as argument must be relative to the ones defined with the `in()` method. For example: + * + * $finder->in(__DIR__)->exclude('ruby'); + * + * @param string|array $dirs A directory path or an array of directories + * + * @return $this + * + * @see ExcludeDirectoryFilterIterator + */ + public function exclude(string|array $dirs): static + { + $this->exclude = array_merge($this->exclude, (array) $dirs); + + return $this; + } + + /** + * Excludes "hidden" directories and files (starting with a dot). + * + * This option is enabled by default. + * + * @return $this + * + * @see ExcludeDirectoryFilterIterator + */ + public function ignoreDotFiles(bool $ignoreDotFiles): static + { + if ($ignoreDotFiles) { + $this->ignore |= static::IGNORE_DOT_FILES; + } else { + $this->ignore &= ~static::IGNORE_DOT_FILES; + } + + return $this; + } + + /** + * Forces the finder to ignore version control directories. + * + * This option is enabled by default. + * + * @return $this + * + * @see ExcludeDirectoryFilterIterator + */ + public function ignoreVCS(bool $ignoreVCS): static + { + if ($ignoreVCS) { + $this->ignore |= static::IGNORE_VCS_FILES; + } else { + $this->ignore &= ~static::IGNORE_VCS_FILES; + } + + return $this; + } + + /** + * Forces Finder to obey .gitignore and ignore files based on rules listed there. + * + * This option is disabled by default. + * + * @return $this + */ + public function ignoreVCSIgnored(bool $ignoreVCSIgnored): static + { + if ($ignoreVCSIgnored) { + $this->ignore |= static::IGNORE_VCS_IGNORED_FILES; + } else { + $this->ignore &= ~static::IGNORE_VCS_IGNORED_FILES; + } + + return $this; + } + + /** + * Adds VCS patterns. + * + * @see ignoreVCS() + * + * @param string|string[] $pattern VCS patterns to ignore + */ + public static function addVCSPattern(string|array $pattern): void + { + foreach ((array) $pattern as $p) { + self::$vcsPatterns[] = $p; + } + + self::$vcsPatterns = array_unique(self::$vcsPatterns); + } + + /** + * Sorts files and directories by an anonymous function. + * + * The anonymous function receives two \SplFileInfo instances to compare. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sort(\Closure $closure): static + { + $this->sort = $closure; + + return $this; + } + + /** + * Sorts files and directories by extension. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByExtension(): static + { + $this->sort = Iterator\SortableIterator::SORT_BY_EXTENSION; + + return $this; + } + + /** + * Sorts files and directories by name. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByName(bool $useNaturalSort = false): static + { + $this->sort = $useNaturalSort ? Iterator\SortableIterator::SORT_BY_NAME_NATURAL : Iterator\SortableIterator::SORT_BY_NAME; + + return $this; + } + + /** + * Sorts files and directories by name case insensitive. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByCaseInsensitiveName(bool $useNaturalSort = false): static + { + $this->sort = $useNaturalSort ? Iterator\SortableIterator::SORT_BY_NAME_NATURAL_CASE_INSENSITIVE : Iterator\SortableIterator::SORT_BY_NAME_CASE_INSENSITIVE; + + return $this; + } + + /** + * Sorts files and directories by size. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortBySize(): static + { + $this->sort = Iterator\SortableIterator::SORT_BY_SIZE; + + return $this; + } + + /** + * Sorts files and directories by type (directories before files), then by name. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByType(): static + { + $this->sort = Iterator\SortableIterator::SORT_BY_TYPE; + + return $this; + } + + /** + * Sorts files and directories by the last accessed time. + * + * This is the time that the file was last accessed, read or written to. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByAccessedTime(): static + { + $this->sort = Iterator\SortableIterator::SORT_BY_ACCESSED_TIME; + + return $this; + } + + /** + * Reverses the sorting. + * + * @return $this + */ + public function reverseSorting(): static + { + $this->reverseSorting = true; + + return $this; + } + + /** + * Sorts files and directories by the last inode changed time. + * + * This is the time that the inode information was last modified (permissions, owner, group or other metadata). + * + * On Windows, since inode is not available, changed time is actually the file creation time. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByChangedTime(): static + { + $this->sort = Iterator\SortableIterator::SORT_BY_CHANGED_TIME; + + return $this; + } + + /** + * Sorts files and directories by the last modified time. + * + * This is the last time the actual contents of the file were last modified. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByModifiedTime(): static + { + $this->sort = Iterator\SortableIterator::SORT_BY_MODIFIED_TIME; + + return $this; + } + + /** + * Filters the iterator with an anonymous function. + * + * The anonymous function receives a \SplFileInfo and must return false + * to remove files. + * + * @param \Closure(SplFileInfo): bool $closure + * @param bool $prune Whether to skip traversing directories further + * + * @return $this + * + * @see CustomFilterIterator + */ + public function filter(\Closure $closure, bool $prune = false): static + { + $this->filters[] = $closure; + + if ($prune) { + $this->pruneFilters[] = $closure; + } + + return $this; + } + + /** + * Forces the following of symlinks. + * + * @return $this + */ + public function followLinks(): static + { + $this->followLinks = true; + + return $this; + } + + /** + * Tells finder to ignore unreadable directories. + * + * By default, scanning unreadable directories content throws an AccessDeniedException. + * + * @return $this + */ + public function ignoreUnreadableDirs(bool $ignore = true): static + { + $this->ignoreUnreadableDirs = $ignore; + + return $this; + } + + /** + * Searches files and directories which match defined rules. + * + * @param string|string[] $dirs A directory path or an array of directories + * + * @return $this + * + * @throws DirectoryNotFoundException if one of the directories does not exist + */ + public function in(string|array $dirs): static + { + $resolvedDirs = []; + + foreach ((array) $dirs as $dir) { + if (is_dir($dir)) { + $resolvedDirs[] = [$this->normalizeDir($dir)]; + } elseif ($glob = glob($dir, (\defined('GLOB_BRACE') ? \GLOB_BRACE : 0) | \GLOB_ONLYDIR | \GLOB_NOSORT)) { + sort($glob); + $resolvedDirs[] = array_map($this->normalizeDir(...), $glob); + } else { + throw new DirectoryNotFoundException(sprintf('The "%s" directory does not exist.', $dir)); + } + } + + $this->dirs = array_merge($this->dirs, ...$resolvedDirs); + + return $this; + } + + /** + * Returns an Iterator for the current Finder configuration. + * + * This method implements the IteratorAggregate interface. + * + * @return \Iterator + * + * @throws \LogicException if the in() method has not been called + */ + public function getIterator(): \Iterator + { + if (0 === \count($this->dirs) && 0 === \count($this->iterators)) { + throw new \LogicException('You must call one of in() or append() methods before iterating over a Finder.'); + } + + if (1 === \count($this->dirs) && 0 === \count($this->iterators)) { + $iterator = $this->searchInDirectory($this->dirs[0]); + + if ($this->sort || $this->reverseSorting) { + $iterator = (new Iterator\SortableIterator($iterator, $this->sort, $this->reverseSorting))->getIterator(); + } + + return $iterator; + } + + $iterator = new \AppendIterator(); + foreach ($this->dirs as $dir) { + $iterator->append(new \IteratorIterator(new LazyIterator(fn () => $this->searchInDirectory($dir)))); + } + + foreach ($this->iterators as $it) { + $iterator->append($it); + } + + if ($this->sort || $this->reverseSorting) { + $iterator = (new Iterator\SortableIterator($iterator, $this->sort, $this->reverseSorting))->getIterator(); + } + + return $iterator; + } + + /** + * Appends an existing set of files/directories to the finder. + * + * The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array. + * + * @return $this + * + * @throws \InvalidArgumentException when the given argument is not iterable + */ + public function append(iterable $iterator): static + { + if ($iterator instanceof \IteratorAggregate) { + $this->iterators[] = $iterator->getIterator(); + } elseif ($iterator instanceof \Iterator) { + $this->iterators[] = $iterator; + } elseif (is_iterable($iterator)) { + $it = new \ArrayIterator(); + foreach ($iterator as $file) { + $file = $file instanceof \SplFileInfo ? $file : new \SplFileInfo($file); + $it[$file->getPathname()] = $file; + } + $this->iterators[] = $it; + } else { + throw new \InvalidArgumentException('Finder::append() method wrong argument type.'); + } + + return $this; + } + + /** + * Check if any results were found. + */ + public function hasResults(): bool + { + foreach ($this->getIterator() as $_) { + return true; + } + + return false; + } + + /** + * Counts all the results collected by the iterators. + */ + public function count(): int + { + return iterator_count($this->getIterator()); + } + + private function searchInDirectory(string $dir): \Iterator + { + $exclude = $this->exclude; + $notPaths = $this->notPaths; + + if ($this->pruneFilters) { + $exclude = array_merge($exclude, $this->pruneFilters); + } + + if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) { + $exclude = array_merge($exclude, self::$vcsPatterns); + } + + if (static::IGNORE_DOT_FILES === (static::IGNORE_DOT_FILES & $this->ignore)) { + $notPaths[] = '#(^|/)\..+(/|$)#'; + } + + $minDepth = 0; + $maxDepth = \PHP_INT_MAX; + + foreach ($this->depths as $comparator) { + switch ($comparator->getOperator()) { + case '>': + $minDepth = $comparator->getTarget() + 1; + break; + case '>=': + $minDepth = $comparator->getTarget(); + break; + case '<': + $maxDepth = $comparator->getTarget() - 1; + break; + case '<=': + $maxDepth = $comparator->getTarget(); + break; + default: + $minDepth = $maxDepth = $comparator->getTarget(); + } + } + + $flags = \RecursiveDirectoryIterator::SKIP_DOTS; + + if ($this->followLinks) { + $flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS; + } + + $iterator = new Iterator\RecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs); + + if ($exclude) { + $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $exclude); + } + + $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST); + + if ($minDepth > 0 || $maxDepth < \PHP_INT_MAX) { + $iterator = new Iterator\DepthRangeFilterIterator($iterator, $minDepth, $maxDepth); + } + + if ($this->mode) { + $iterator = new Iterator\FileTypeFilterIterator($iterator, $this->mode); + } + + if ($this->names || $this->notNames) { + $iterator = new Iterator\FilenameFilterIterator($iterator, $this->names, $this->notNames); + } + + if ($this->contains || $this->notContains) { + $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains); + } + + if ($this->sizes) { + $iterator = new Iterator\SizeRangeFilterIterator($iterator, $this->sizes); + } + + if ($this->dates) { + $iterator = new Iterator\DateRangeFilterIterator($iterator, $this->dates); + } + + if ($this->filters) { + $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters); + } + + if ($this->paths || $notPaths) { + $iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $notPaths); + } + + if (static::IGNORE_VCS_IGNORED_FILES === (static::IGNORE_VCS_IGNORED_FILES & $this->ignore)) { + $iterator = new Iterator\VcsIgnoredFilterIterator($iterator, $dir); + } + + return $iterator; + } + + /** + * Normalizes given directory names by removing trailing slashes. + * + * Excluding: (s)ftp:// or ssh2.(s)ftp:// wrapper + */ + private function normalizeDir(string $dir): string + { + if ('/' === $dir) { + return $dir; + } + + $dir = rtrim($dir, '/'.\DIRECTORY_SEPARATOR); + + if (preg_match('#^(ssh2\.)?s?ftp://#', $dir)) { + $dir .= '/'; + } + + return $dir; + } +} diff --git a/vendor/symfony/finder/Gitignore.php b/vendor/symfony/finder/Gitignore.php new file mode 100644 index 00000000..bf05c5b3 --- /dev/null +++ b/vendor/symfony/finder/Gitignore.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder; + +/** + * Gitignore matches against text. + * + * @author Michael Voříšek + * @author Ahmed Abdou + */ +class Gitignore +{ + /** + * Returns a regexp which is the equivalent of the gitignore pattern. + * + * Format specification: https://git-scm.com/docs/gitignore#_pattern_format + */ + public static function toRegex(string $gitignoreFileContent): string + { + return self::buildRegex($gitignoreFileContent, false); + } + + public static function toRegexMatchingNegatedPatterns(string $gitignoreFileContent): string + { + return self::buildRegex($gitignoreFileContent, true); + } + + private static function buildRegex(string $gitignoreFileContent, bool $inverted): string + { + $gitignoreFileContent = preg_replace('~(? '['.('' !== $matches[1] ? '^' : '').str_replace('\\-', '-', $matches[2]).']', $regex); + $regex = preg_replace('~(?:(?:\\\\\*){2,}(/?))+~', '(?:(?:(?!//).(? + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder; + +/** + * Glob matches globbing patterns against text. + * + * if match_glob("foo.*", "foo.bar") echo "matched\n"; + * + * // prints foo.bar and foo.baz + * $regex = glob_to_regex("foo.*"); + * for (['foo.bar', 'foo.baz', 'foo', 'bar'] as $t) + * { + * if (/$regex/) echo "matched: $car\n"; + * } + * + * Glob implements glob(3) style matching that can be used to match + * against text, rather than fetching names from a filesystem. + * + * Based on the Perl Text::Glob module. + * + * @author Fabien Potencier PHP port + * @author Richard Clamp Perl version + * @copyright 2004-2005 Fabien Potencier + * @copyright 2002 Richard Clamp + */ +class Glob +{ + /** + * Returns a regexp which is the equivalent of the glob pattern. + */ + public static function toRegex(string $glob, bool $strictLeadingDot = true, bool $strictWildcardSlash = true, string $delimiter = '#'): string + { + $firstByte = true; + $escaping = false; + $inCurlies = 0; + $regex = ''; + $sizeGlob = \strlen($glob); + for ($i = 0; $i < $sizeGlob; ++$i) { + $car = $glob[$i]; + if ($firstByte && $strictLeadingDot && '.' !== $car) { + $regex .= '(?=[^\.])'; + } + + $firstByte = '/' === $car; + + if ($firstByte && $strictWildcardSlash && isset($glob[$i + 2]) && '**' === $glob[$i + 1].$glob[$i + 2] && (!isset($glob[$i + 3]) || '/' === $glob[$i + 3])) { + $car = '[^/]++/'; + if (!isset($glob[$i + 3])) { + $car .= '?'; + } + + if ($strictLeadingDot) { + $car = '(?=[^\.])'.$car; + } + + $car = '/(?:'.$car.')*'; + $i += 2 + isset($glob[$i + 3]); + + if ('/' === $delimiter) { + $car = str_replace('/', '\\/', $car); + } + } + + if ($delimiter === $car || '.' === $car || '(' === $car || ')' === $car || '|' === $car || '+' === $car || '^' === $car || '$' === $car) { + $regex .= "\\$car"; + } elseif ('*' === $car) { + $regex .= $escaping ? '\\*' : ($strictWildcardSlash ? '[^/]*' : '.*'); + } elseif ('?' === $car) { + $regex .= $escaping ? '\\?' : ($strictWildcardSlash ? '[^/]' : '.'); + } elseif ('{' === $car) { + $regex .= $escaping ? '\\{' : '('; + if (!$escaping) { + ++$inCurlies; + } + } elseif ('}' === $car && $inCurlies) { + $regex .= $escaping ? '}' : ')'; + if (!$escaping) { + --$inCurlies; + } + } elseif (',' === $car && $inCurlies) { + $regex .= $escaping ? ',' : '|'; + } elseif ('\\' === $car) { + if ($escaping) { + $regex .= '\\\\'; + $escaping = false; + } else { + $escaping = true; + } + + continue; + } else { + $regex .= $car; + } + $escaping = false; + } + + return $delimiter.'^'.$regex.'$'.$delimiter; + } +} diff --git a/vendor/symfony/finder/Iterator/CustomFilterIterator.php b/vendor/symfony/finder/Iterator/CustomFilterIterator.php new file mode 100644 index 00000000..82ee81d8 --- /dev/null +++ b/vendor/symfony/finder/Iterator/CustomFilterIterator.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * CustomFilterIterator filters files by applying anonymous functions. + * + * The anonymous function receives a \SplFileInfo and must return false + * to remove files. + * + * @author Fabien Potencier + * + * @extends \FilterIterator + */ +class CustomFilterIterator extends \FilterIterator +{ + private array $filters = []; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param callable[] $filters An array of PHP callbacks + * + * @throws \InvalidArgumentException + */ + public function __construct(\Iterator $iterator, array $filters) + { + foreach ($filters as $filter) { + if (!\is_callable($filter)) { + throw new \InvalidArgumentException('Invalid PHP callback.'); + } + } + $this->filters = $filters; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + */ + public function accept(): bool + { + $fileinfo = $this->current(); + + foreach ($this->filters as $filter) { + if (false === $filter($fileinfo)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/symfony/finder/Iterator/DateRangeFilterIterator.php b/vendor/symfony/finder/Iterator/DateRangeFilterIterator.php new file mode 100644 index 00000000..718d42b1 --- /dev/null +++ b/vendor/symfony/finder/Iterator/DateRangeFilterIterator.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Comparator\DateComparator; + +/** + * DateRangeFilterIterator filters out files that are not in the given date range (last modified dates). + * + * @author Fabien Potencier + * + * @extends \FilterIterator + */ +class DateRangeFilterIterator extends \FilterIterator +{ + private array $comparators = []; + + /** + * @param \Iterator $iterator + * @param DateComparator[] $comparators + */ + public function __construct(\Iterator $iterator, array $comparators) + { + $this->comparators = $comparators; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + */ + public function accept(): bool + { + $fileinfo = $this->current(); + + if (!file_exists($fileinfo->getPathname())) { + return false; + } + + $filedate = $fileinfo->getMTime(); + foreach ($this->comparators as $compare) { + if (!$compare->test($filedate)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php b/vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php new file mode 100644 index 00000000..1cddb5fa --- /dev/null +++ b/vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * DepthRangeFilterIterator limits the directory depth. + * + * @author Fabien Potencier + * + * @template-covariant TKey + * @template-covariant TValue + * + * @extends \FilterIterator + */ +class DepthRangeFilterIterator extends \FilterIterator +{ + private int $minDepth = 0; + + /** + * @param \RecursiveIteratorIterator<\RecursiveIterator> $iterator The Iterator to filter + * @param int $minDepth The min depth + * @param int $maxDepth The max depth + */ + public function __construct(\RecursiveIteratorIterator $iterator, int $minDepth = 0, int $maxDepth = \PHP_INT_MAX) + { + $this->minDepth = $minDepth; + $iterator->setMaxDepth(\PHP_INT_MAX === $maxDepth ? -1 : $maxDepth); + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + */ + public function accept(): bool + { + return $this->getInnerIterator()->getDepth() >= $this->minDepth; + } +} diff --git a/vendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php b/vendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php new file mode 100644 index 00000000..ebbc76ec --- /dev/null +++ b/vendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\SplFileInfo; + +/** + * ExcludeDirectoryFilterIterator filters out directories. + * + * @author Fabien Potencier + * + * @extends \FilterIterator + * + * @implements \RecursiveIterator + */ +class ExcludeDirectoryFilterIterator extends \FilterIterator implements \RecursiveIterator +{ + /** @var \Iterator */ + private \Iterator $iterator; + private bool $isRecursive; + /** @var array */ + private array $excludedDirs = []; + private ?string $excludedPattern = null; + /** @var list */ + private array $pruneFilters = []; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param list $directories An array of directories to exclude + */ + public function __construct(\Iterator $iterator, array $directories) + { + $this->iterator = $iterator; + $this->isRecursive = $iterator instanceof \RecursiveIterator; + $patterns = []; + foreach ($directories as $directory) { + if (!\is_string($directory)) { + if (!\is_callable($directory)) { + throw new \InvalidArgumentException('Invalid PHP callback.'); + } + + $this->pruneFilters[] = $directory; + + continue; + } + + $directory = rtrim($directory, '/'); + if (!$this->isRecursive || str_contains($directory, '/')) { + $patterns[] = preg_quote($directory, '#'); + } else { + $this->excludedDirs[$directory] = true; + } + } + if ($patterns) { + $this->excludedPattern = '#(?:^|/)(?:'.implode('|', $patterns).')(?:/|$)#'; + } + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + */ + public function accept(): bool + { + if ($this->isRecursive && isset($this->excludedDirs[$this->getFilename()]) && $this->isDir()) { + return false; + } + + if ($this->excludedPattern) { + $path = $this->isDir() ? $this->current()->getRelativePathname() : $this->current()->getRelativePath(); + $path = str_replace('\\', '/', $path); + + return !preg_match($this->excludedPattern, $path); + } + + if ($this->pruneFilters && $this->hasChildren()) { + foreach ($this->pruneFilters as $pruneFilter) { + if (!$pruneFilter($this->current())) { + return false; + } + } + } + + return true; + } + + public function hasChildren(): bool + { + return $this->isRecursive && $this->iterator->hasChildren(); + } + + public function getChildren(): self + { + $children = new self($this->iterator->getChildren(), []); + $children->excludedDirs = $this->excludedDirs; + $children->excludedPattern = $this->excludedPattern; + + return $children; + } +} diff --git a/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php b/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php new file mode 100644 index 00000000..21303781 --- /dev/null +++ b/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * FileTypeFilterIterator only keeps files, directories, or both. + * + * @author Fabien Potencier + * + * @extends \FilterIterator + */ +class FileTypeFilterIterator extends \FilterIterator +{ + public const ONLY_FILES = 1; + public const ONLY_DIRECTORIES = 2; + + private int $mode; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param int $mode The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES) + */ + public function __construct(\Iterator $iterator, int $mode) + { + $this->mode = $mode; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + */ + public function accept(): bool + { + $fileinfo = $this->current(); + if (self::ONLY_DIRECTORIES === (self::ONLY_DIRECTORIES & $this->mode) && $fileinfo->isFile()) { + return false; + } elseif (self::ONLY_FILES === (self::ONLY_FILES & $this->mode) && $fileinfo->isDir()) { + return false; + } + + return true; + } +} diff --git a/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php b/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php new file mode 100644 index 00000000..bdc71ffd --- /dev/null +++ b/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\SplFileInfo; + +/** + * FilecontentFilterIterator filters files by their contents using patterns (regexps or strings). + * + * @author Fabien Potencier + * @author Włodzimierz Gajda + * + * @extends MultiplePcreFilterIterator + */ +class FilecontentFilterIterator extends MultiplePcreFilterIterator +{ + /** + * Filters the iterator values. + */ + public function accept(): bool + { + if (!$this->matchRegexps && !$this->noMatchRegexps) { + return true; + } + + $fileinfo = $this->current(); + + if ($fileinfo->isDir() || !$fileinfo->isReadable()) { + return false; + } + + $content = $fileinfo->getContents(); + if (!$content) { + return false; + } + + return $this->isAccepted($content); + } + + /** + * Converts string to regexp if necessary. + * + * @param string $str Pattern: string or regexp + */ + protected function toRegex(string $str): string + { + return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; + } +} diff --git a/vendor/symfony/finder/Iterator/FilenameFilterIterator.php b/vendor/symfony/finder/Iterator/FilenameFilterIterator.php new file mode 100644 index 00000000..05d95358 --- /dev/null +++ b/vendor/symfony/finder/Iterator/FilenameFilterIterator.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Glob; + +/** + * FilenameFilterIterator filters files by patterns (a regexp, a glob, or a string). + * + * @author Fabien Potencier + * + * @extends MultiplePcreFilterIterator + */ +class FilenameFilterIterator extends MultiplePcreFilterIterator +{ + /** + * Filters the iterator values. + */ + public function accept(): bool + { + return $this->isAccepted($this->current()->getFilename()); + } + + /** + * Converts glob to regexp. + * + * PCRE patterns are left unchanged. + * Glob strings are transformed with Glob::toRegex(). + * + * @param string $str Pattern: glob or regexp + */ + protected function toRegex(string $str): string + { + return $this->isRegex($str) ? $str : Glob::toRegex($str); + } +} diff --git a/vendor/symfony/finder/Iterator/LazyIterator.php b/vendor/symfony/finder/Iterator/LazyIterator.php new file mode 100644 index 00000000..5b5806be --- /dev/null +++ b/vendor/symfony/finder/Iterator/LazyIterator.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * @author Jérémy Derussé + * + * @internal + */ +class LazyIterator implements \IteratorAggregate +{ + private \Closure $iteratorFactory; + + public function __construct(callable $iteratorFactory) + { + $this->iteratorFactory = $iteratorFactory(...); + } + + public function getIterator(): \Traversable + { + yield from ($this->iteratorFactory)(); + } +} diff --git a/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php b/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php new file mode 100644 index 00000000..3450c49d --- /dev/null +++ b/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * MultiplePcreFilterIterator filters files using patterns (regexps, globs or strings). + * + * @author Fabien Potencier + * + * @template-covariant TKey + * @template-covariant TValue + * + * @extends \FilterIterator + */ +abstract class MultiplePcreFilterIterator extends \FilterIterator +{ + protected array $matchRegexps = []; + protected array $noMatchRegexps = []; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param string[] $matchPatterns An array of patterns that need to match + * @param string[] $noMatchPatterns An array of patterns that need to not match + */ + public function __construct(\Iterator $iterator, array $matchPatterns, array $noMatchPatterns) + { + foreach ($matchPatterns as $pattern) { + $this->matchRegexps[] = $this->toRegex($pattern); + } + + foreach ($noMatchPatterns as $pattern) { + $this->noMatchRegexps[] = $this->toRegex($pattern); + } + + parent::__construct($iterator); + } + + /** + * Checks whether the string is accepted by the regex filters. + * + * If there is no regexps defined in the class, this method will accept the string. + * Such case can be handled by child classes before calling the method if they want to + * apply a different behavior. + */ + protected function isAccepted(string $string): bool + { + // should at least not match one rule to exclude + foreach ($this->noMatchRegexps as $regex) { + if (preg_match($regex, $string)) { + return false; + } + } + + // should at least match one rule + if ($this->matchRegexps) { + foreach ($this->matchRegexps as $regex) { + if (preg_match($regex, $string)) { + return true; + } + } + + return false; + } + + // If there is no match rules, the file is accepted + return true; + } + + /** + * Checks whether the string is a regex. + */ + protected function isRegex(string $str): bool + { + $availableModifiers = 'imsxuADUn'; + + if (preg_match('/^(.{3,}?)['.$availableModifiers.']*$/', $str, $m)) { + $start = substr($m[1], 0, 1); + $end = substr($m[1], -1); + + if ($start === $end) { + return !preg_match('/[*?[:alnum:] \\\\]/', $start); + } + + foreach ([['{', '}'], ['(', ')'], ['[', ']'], ['<', '>']] as $delimiters) { + if ($start === $delimiters[0] && $end === $delimiters[1]) { + return true; + } + } + } + + return false; + } + + /** + * Converts string into regexp. + */ + abstract protected function toRegex(string $str): string; +} diff --git a/vendor/symfony/finder/Iterator/PathFilterIterator.php b/vendor/symfony/finder/Iterator/PathFilterIterator.php new file mode 100644 index 00000000..c6d58139 --- /dev/null +++ b/vendor/symfony/finder/Iterator/PathFilterIterator.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\SplFileInfo; + +/** + * PathFilterIterator filters files by path patterns (e.g. some/special/dir). + * + * @author Fabien Potencier + * @author Włodzimierz Gajda + * + * @extends MultiplePcreFilterIterator + */ +class PathFilterIterator extends MultiplePcreFilterIterator +{ + /** + * Filters the iterator values. + */ + public function accept(): bool + { + $filename = $this->current()->getRelativePathname(); + + if ('\\' === \DIRECTORY_SEPARATOR) { + $filename = str_replace('\\', '/', $filename); + } + + return $this->isAccepted($filename); + } + + /** + * Converts strings to regexp. + * + * PCRE patterns are left unchanged. + * + * Default conversion: + * 'lorem/ipsum/dolor' ==> 'lorem\/ipsum\/dolor/' + * + * Use only / as directory separator (on Windows also). + * + * @param string $str Pattern: regexp or dirname + */ + protected function toRegex(string $str): string + { + return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; + } +} diff --git a/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php b/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php new file mode 100644 index 00000000..34cced68 --- /dev/null +++ b/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php @@ -0,0 +1,133 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Exception\AccessDeniedException; +use Symfony\Component\Finder\SplFileInfo; + +/** + * Extends the \RecursiveDirectoryIterator to support relative paths. + * + * @author Victor Berchet + * + * @extends \RecursiveDirectoryIterator + */ +class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator +{ + private bool $ignoreUnreadableDirs; + private bool $ignoreFirstRewind = true; + + // these 3 properties take part of the performance optimization to avoid redoing the same work in all iterations + private string $rootPath; + private string $subPath; + private string $directorySeparator = '/'; + + /** + * @throws \RuntimeException + */ + public function __construct(string $path, int $flags, bool $ignoreUnreadableDirs = false) + { + if ($flags & (self::CURRENT_AS_PATHNAME | self::CURRENT_AS_SELF)) { + throw new \RuntimeException('This iterator only support returning current as fileinfo.'); + } + + parent::__construct($path, $flags); + $this->ignoreUnreadableDirs = $ignoreUnreadableDirs; + $this->rootPath = $path; + if ('/' !== \DIRECTORY_SEPARATOR && !($flags & self::UNIX_PATHS)) { + $this->directorySeparator = \DIRECTORY_SEPARATOR; + } + } + + /** + * Return an instance of SplFileInfo with support for relative paths. + */ + public function current(): SplFileInfo + { + // the logic here avoids redoing the same work in all iterations + + if (!isset($this->subPath)) { + $this->subPath = $this->getSubPath(); + } + $subPathname = $this->subPath; + if ('' !== $subPathname) { + $subPathname .= $this->directorySeparator; + } + $subPathname .= $this->getFilename(); + + if ('/' !== $basePath = $this->rootPath) { + $basePath .= $this->directorySeparator; + } + + return new SplFileInfo($basePath.$subPathname, $this->subPath, $subPathname); + } + + public function hasChildren(bool $allowLinks = false): bool + { + $hasChildren = parent::hasChildren($allowLinks); + + if (!$hasChildren || !$this->ignoreUnreadableDirs) { + return $hasChildren; + } + + try { + parent::getChildren(); + + return true; + } catch (\UnexpectedValueException) { + // If directory is unreadable and finder is set to ignore it, skip children + return false; + } + } + + /** + * @throws AccessDeniedException + */ + public function getChildren(): \RecursiveDirectoryIterator + { + try { + $children = parent::getChildren(); + + if ($children instanceof self) { + // parent method will call the constructor with default arguments, so unreadable dirs won't be ignored anymore + $children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs; + + // performance optimization to avoid redoing the same work in all children + $children->rootPath = $this->rootPath; + } + + return $children; + } catch (\UnexpectedValueException $e) { + throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e); + } + } + + public function next(): void + { + $this->ignoreFirstRewind = false; + + parent::next(); + } + + public function rewind(): void + { + // some streams like FTP are not rewindable, ignore the first rewind after creation, + // as newly created DirectoryIterator does not need to be rewound + if ($this->ignoreFirstRewind) { + $this->ignoreFirstRewind = false; + + return; + } + + parent::rewind(); + } +} diff --git a/vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php b/vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php new file mode 100644 index 00000000..925830a2 --- /dev/null +++ b/vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Comparator\NumberComparator; + +/** + * SizeRangeFilterIterator filters out files that are not in the given size range. + * + * @author Fabien Potencier + * + * @extends \FilterIterator + */ +class SizeRangeFilterIterator extends \FilterIterator +{ + private array $comparators = []; + + /** + * @param \Iterator $iterator + * @param NumberComparator[] $comparators + */ + public function __construct(\Iterator $iterator, array $comparators) + { + $this->comparators = $comparators; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + */ + public function accept(): bool + { + $fileinfo = $this->current(); + if (!$fileinfo->isFile()) { + return true; + } + + $filesize = $fileinfo->getSize(); + foreach ($this->comparators as $compare) { + if (!$compare->test($filesize)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/symfony/finder/Iterator/SortableIterator.php b/vendor/symfony/finder/Iterator/SortableIterator.php new file mode 100644 index 00000000..177cd0b6 --- /dev/null +++ b/vendor/symfony/finder/Iterator/SortableIterator.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * SortableIterator applies a sort on a given Iterator. + * + * @author Fabien Potencier + * + * @implements \IteratorAggregate + */ +class SortableIterator implements \IteratorAggregate +{ + public const SORT_BY_NONE = 0; + public const SORT_BY_NAME = 1; + public const SORT_BY_TYPE = 2; + public const SORT_BY_ACCESSED_TIME = 3; + public const SORT_BY_CHANGED_TIME = 4; + public const SORT_BY_MODIFIED_TIME = 5; + public const SORT_BY_NAME_NATURAL = 6; + public const SORT_BY_NAME_CASE_INSENSITIVE = 7; + public const SORT_BY_NAME_NATURAL_CASE_INSENSITIVE = 8; + public const SORT_BY_EXTENSION = 9; + public const SORT_BY_SIZE = 10; + + /** @var \Traversable */ + private \Traversable $iterator; + private \Closure|int $sort; + + /** + * @param \Traversable $iterator + * @param int|callable $sort The sort type (SORT_BY_NAME, SORT_BY_TYPE, or a PHP callback) + * + * @throws \InvalidArgumentException + */ + public function __construct(\Traversable $iterator, int|callable $sort, bool $reverseOrder = false) + { + $this->iterator = $iterator; + $order = $reverseOrder ? -1 : 1; + + if (self::SORT_BY_NAME === $sort) { + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + } elseif (self::SORT_BY_NAME_NATURAL === $sort) { + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strnatcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + } elseif (self::SORT_BY_NAME_CASE_INSENSITIVE === $sort) { + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strcasecmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + } elseif (self::SORT_BY_NAME_NATURAL_CASE_INSENSITIVE === $sort) { + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strnatcasecmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + } elseif (self::SORT_BY_TYPE === $sort) { + $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { + if ($a->isDir() && $b->isFile()) { + return -$order; + } elseif ($a->isFile() && $b->isDir()) { + return $order; + } + + return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + }; + } elseif (self::SORT_BY_ACCESSED_TIME === $sort) { + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getATime() - $b->getATime()); + } elseif (self::SORT_BY_CHANGED_TIME === $sort) { + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getCTime() - $b->getCTime()); + } elseif (self::SORT_BY_MODIFIED_TIME === $sort) { + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getMTime() - $b->getMTime()); + } elseif (self::SORT_BY_EXTENSION === $sort) { + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strnatcmp($a->getExtension(), $b->getExtension()); + } elseif (self::SORT_BY_SIZE === $sort) { + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getSize() - $b->getSize()); + } elseif (self::SORT_BY_NONE === $sort) { + $this->sort = $order; + } elseif (\is_callable($sort)) { + $this->sort = $reverseOrder ? static fn (\SplFileInfo $a, \SplFileInfo $b) => -$sort($a, $b) : $sort(...); + } else { + throw new \InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.'); + } + } + + public function getIterator(): \Traversable + { + if (1 === $this->sort) { + return $this->iterator; + } + + $array = iterator_to_array($this->iterator, true); + + if (-1 === $this->sort) { + $array = array_reverse($array); + } else { + uasort($array, $this->sort); + } + + return new \ArrayIterator($array); + } +} diff --git a/vendor/symfony/finder/Iterator/VcsIgnoredFilterIterator.php b/vendor/symfony/finder/Iterator/VcsIgnoredFilterIterator.php new file mode 100644 index 00000000..ddd70077 --- /dev/null +++ b/vendor/symfony/finder/Iterator/VcsIgnoredFilterIterator.php @@ -0,0 +1,173 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Gitignore; + +/** + * @extends \FilterIterator + */ +final class VcsIgnoredFilterIterator extends \FilterIterator +{ + private string $baseDir; + + /** + * @var array + */ + private array $gitignoreFilesCache = []; + + /** + * @var array + */ + private array $ignoredPathsCache = []; + + /** + * @param \Iterator $iterator + */ + public function __construct(\Iterator $iterator, string $baseDir) + { + $this->baseDir = $this->normalizePath($baseDir); + + foreach ($this->parentDirectoriesUpwards($this->baseDir) as $parentDirectory) { + if (@is_dir("{$parentDirectory}/.git")) { + $this->baseDir = $parentDirectory; + break; + } + } + + parent::__construct($iterator); + } + + public function accept(): bool + { + $file = $this->current(); + + $fileRealPath = $this->normalizePath($file->getRealPath()); + + return !$this->isIgnored($fileRealPath); + } + + private function isIgnored(string $fileRealPath): bool + { + if (is_dir($fileRealPath) && !str_ends_with($fileRealPath, '/')) { + $fileRealPath .= '/'; + } + + if (isset($this->ignoredPathsCache[$fileRealPath])) { + return $this->ignoredPathsCache[$fileRealPath]; + } + + $ignored = false; + + foreach ($this->parentDirectoriesDownwards($fileRealPath) as $parentDirectory) { + if ($this->isIgnored($parentDirectory)) { + // rules in ignored directories are ignored, no need to check further. + break; + } + + $fileRelativePath = substr($fileRealPath, \strlen($parentDirectory) + 1); + + if (null === $regexps = $this->readGitignoreFile("{$parentDirectory}/.gitignore")) { + continue; + } + + [$exclusionRegex, $inclusionRegex] = $regexps; + + if (preg_match($exclusionRegex, $fileRelativePath)) { + $ignored = true; + + continue; + } + + if (preg_match($inclusionRegex, $fileRelativePath)) { + $ignored = false; + } + } + + return $this->ignoredPathsCache[$fileRealPath] = $ignored; + } + + /** + * @return list + */ + private function parentDirectoriesUpwards(string $from): array + { + $parentDirectories = []; + + $parentDirectory = $from; + + while (true) { + $newParentDirectory = \dirname($parentDirectory); + + // dirname('/') = '/' + if ($newParentDirectory === $parentDirectory) { + break; + } + + $parentDirectories[] = $parentDirectory = $newParentDirectory; + } + + return $parentDirectories; + } + + private function parentDirectoriesUpTo(string $from, string $upTo): array + { + return array_filter( + $this->parentDirectoriesUpwards($from), + static fn (string $directory): bool => str_starts_with($directory, $upTo) + ); + } + + /** + * @return list + */ + private function parentDirectoriesDownwards(string $fileRealPath): array + { + return array_reverse( + $this->parentDirectoriesUpTo($fileRealPath, $this->baseDir) + ); + } + + /** + * @return array{0: string, 1: string}|null + */ + private function readGitignoreFile(string $path): ?array + { + if (\array_key_exists($path, $this->gitignoreFilesCache)) { + return $this->gitignoreFilesCache[$path]; + } + + if (!file_exists($path)) { + return $this->gitignoreFilesCache[$path] = null; + } + + if (!is_file($path) || !is_readable($path)) { + throw new \RuntimeException("The \"ignoreVCSIgnored\" option cannot be used by the Finder as the \"{$path}\" file is not readable."); + } + + $gitignoreFileContent = file_get_contents($path); + + return $this->gitignoreFilesCache[$path] = [ + Gitignore::toRegex($gitignoreFileContent), + Gitignore::toRegexMatchingNegatedPatterns($gitignoreFileContent), + ]; + } + + private function normalizePath(string $path): string + { + if ('\\' === \DIRECTORY_SEPARATOR) { + return str_replace('\\', '/', $path); + } + + return $path; + } +} diff --git a/vendor/symfony/finder/LICENSE b/vendor/symfony/finder/LICENSE new file mode 100644 index 00000000..0138f8f0 --- /dev/null +++ b/vendor/symfony/finder/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/finder/README.md b/vendor/symfony/finder/README.md new file mode 100644 index 00000000..22bdeb9b --- /dev/null +++ b/vendor/symfony/finder/README.md @@ -0,0 +1,14 @@ +Finder Component +================ + +The Finder component finds files and directories via an intuitive fluent +interface. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/finder.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/finder/SplFileInfo.php b/vendor/symfony/finder/SplFileInfo.php new file mode 100644 index 00000000..867e8e81 --- /dev/null +++ b/vendor/symfony/finder/SplFileInfo.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder; + +/** + * Extends \SplFileInfo to support relative paths. + * + * @author Fabien Potencier + */ +class SplFileInfo extends \SplFileInfo +{ + private string $relativePath; + private string $relativePathname; + + /** + * @param string $file The file name + * @param string $relativePath The relative path + * @param string $relativePathname The relative path name + */ + public function __construct(string $file, string $relativePath, string $relativePathname) + { + parent::__construct($file); + $this->relativePath = $relativePath; + $this->relativePathname = $relativePathname; + } + + /** + * Returns the relative path. + * + * This path does not contain the file name. + */ + public function getRelativePath(): string + { + return $this->relativePath; + } + + /** + * Returns the relative path name. + * + * This path contains the file name. + */ + public function getRelativePathname(): string + { + return $this->relativePathname; + } + + public function getFilenameWithoutExtension(): string + { + $filename = $this->getFilename(); + + return pathinfo($filename, \PATHINFO_FILENAME); + } + + /** + * Returns the contents of the file. + * + * @throws \RuntimeException + */ + public function getContents(): string + { + set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); + try { + $content = file_get_contents($this->getPathname()); + } finally { + restore_error_handler(); + } + if (false === $content) { + throw new \RuntimeException($error); + } + + return $content; + } +} diff --git a/vendor/symfony/finder/composer.json b/vendor/symfony/finder/composer.json new file mode 100644 index 00000000..2b70600d --- /dev/null +++ b/vendor/symfony/finder/composer.json @@ -0,0 +1,31 @@ +{ + "name": "symfony/finder", + "type": "library", + "description": "Finds files and directories via an intuitive fluent interface", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/filesystem": "^6.4|^7.0" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Finder\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/options-resolver/CHANGELOG.md b/vendor/symfony/options-resolver/CHANGELOG.md new file mode 100644 index 00000000..f4de6d01 --- /dev/null +++ b/vendor/symfony/options-resolver/CHANGELOG.md @@ -0,0 +1,96 @@ +CHANGELOG +========= + +6.4 +--- + +* Improve message with full path on invalid type in nested option + +6.3 +--- + + * Add `OptionsResolver::setIgnoreUndefined()` and `OptionConfigurator::ignoreUndefined()` to ignore not defined options while resolving + +6.0 +--- + + * Remove `OptionsResolverIntrospector::getDeprecationMessage()` + +5.3 +--- + + * Add prototype definition for nested options + +5.1.0 +----- + + * added fluent configuration of options using `OptionResolver::define()` + * added `setInfo()` and `getInfo()` methods + * updated the signature of method `OptionsResolver::setDeprecated()` to `OptionsResolver::setDeprecation(string $option, string $package, string $version, $message)` + * deprecated `OptionsResolverIntrospector::getDeprecationMessage()`, use `OptionsResolverIntrospector::getDeprecation()` instead + +5.0.0 +----- + + * added argument `$triggerDeprecation` to `OptionsResolver::offsetGet()` + +4.3.0 +----- + + * added `OptionsResolver::addNormalizer` method + +4.2.0 +----- + + * added support for nested options definition + * added `setDeprecated` and `isDeprecated` methods + +3.4.0 +----- + + * added `OptionsResolverIntrospector` to inspect options definitions inside an `OptionsResolver` instance + * added array of types support in allowed types (e.g int[]) + +2.6.0 +----- + + * deprecated OptionsResolverInterface + * [BC BREAK] removed "array" type hint from OptionsResolverInterface methods + setRequired(), setAllowedValues(), addAllowedValues(), setAllowedTypes() and + addAllowedTypes() + * added OptionsResolver::setDefault() + * added OptionsResolver::hasDefault() + * added OptionsResolver::setNormalizer() + * added OptionsResolver::isRequired() + * added OptionsResolver::getRequiredOptions() + * added OptionsResolver::isMissing() + * added OptionsResolver::getMissingOptions() + * added OptionsResolver::setDefined() + * added OptionsResolver::isDefined() + * added OptionsResolver::getDefinedOptions() + * added OptionsResolver::remove() + * added OptionsResolver::clear() + * deprecated OptionsResolver::replaceDefaults() + * deprecated OptionsResolver::setOptional() in favor of setDefined() + * deprecated OptionsResolver::isKnown() in favor of isDefined() + * [BC BREAK] OptionsResolver::isRequired() returns true now if a required + option has a default value set + * [BC BREAK] merged Options into OptionsResolver and turned Options into an + interface + * deprecated Options::overload() (now in OptionsResolver) + * deprecated Options::set() (now in OptionsResolver) + * deprecated Options::get() (now in OptionsResolver) + * deprecated Options::has() (now in OptionsResolver) + * deprecated Options::replace() (now in OptionsResolver) + * [BC BREAK] Options::get() (now in OptionsResolver) can only be used within + lazy option/normalizer closures now + * [BC BREAK] removed Traversable interface from Options since using within + lazy option/normalizer closures resulted in exceptions + * [BC BREAK] removed Options::all() since using within lazy option/normalizer + closures resulted in exceptions + * [BC BREAK] OptionDefinitionException now extends LogicException instead of + RuntimeException + * [BC BREAK] normalizers are not executed anymore for unset options + * normalizers are executed after validating the options now + * [BC BREAK] an UndefinedOptionsException is now thrown instead of an + InvalidOptionsException when non-existing options are passed diff --git a/vendor/symfony/options-resolver/Debug/OptionsResolverIntrospector.php b/vendor/symfony/options-resolver/Debug/OptionsResolverIntrospector.php new file mode 100644 index 00000000..f55ab147 --- /dev/null +++ b/vendor/symfony/options-resolver/Debug/OptionsResolverIntrospector.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Debug; + +use Symfony\Component\OptionsResolver\Exception\NoConfigurationException; +use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * @author Maxime Steinhausser + * + * @final + */ +class OptionsResolverIntrospector +{ + private \Closure $get; + + public function __construct(OptionsResolver $optionsResolver) + { + $this->get = \Closure::bind(function ($property, $option, $message) { + /** @var OptionsResolver $this */ + if (!$this->isDefined($option)) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist.', $option)); + } + + if (!\array_key_exists($option, $this->{$property})) { + throw new NoConfigurationException($message); + } + + return $this->{$property}[$option]; + }, $optionsResolver, $optionsResolver); + } + + /** + * @throws NoConfigurationException on no configured value + */ + public function getDefault(string $option): mixed + { + return ($this->get)('defaults', $option, sprintf('No default value was set for the "%s" option.', $option)); + } + + /** + * @return \Closure[] + * + * @throws NoConfigurationException on no configured closures + */ + public function getLazyClosures(string $option): array + { + return ($this->get)('lazy', $option, sprintf('No lazy closures were set for the "%s" option.', $option)); + } + + /** + * @return string[] + * + * @throws NoConfigurationException on no configured types + */ + public function getAllowedTypes(string $option): array + { + return ($this->get)('allowedTypes', $option, sprintf('No allowed types were set for the "%s" option.', $option)); + } + + /** + * @return mixed[] + * + * @throws NoConfigurationException on no configured values + */ + public function getAllowedValues(string $option): array + { + return ($this->get)('allowedValues', $option, sprintf('No allowed values were set for the "%s" option.', $option)); + } + + /** + * @throws NoConfigurationException on no configured normalizer + */ + public function getNormalizer(string $option): \Closure + { + return current($this->getNormalizers($option)); + } + + /** + * @throws NoConfigurationException when no normalizer is configured + */ + public function getNormalizers(string $option): array + { + return ($this->get)('normalizers', $option, sprintf('No normalizer was set for the "%s" option.', $option)); + } + + /** + * @throws NoConfigurationException on no configured deprecation + */ + public function getDeprecation(string $option): array + { + return ($this->get)('deprecated', $option, sprintf('No deprecation was set for the "%s" option.', $option)); + } +} diff --git a/vendor/symfony/options-resolver/Exception/AccessException.php b/vendor/symfony/options-resolver/Exception/AccessException.php new file mode 100644 index 00000000..c12b6806 --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/AccessException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Exception; + +/** + * Thrown when trying to read an option outside of or write it inside of + * {@link \Symfony\Component\OptionsResolver\Options::resolve()}. + * + * @author Bernhard Schussek + */ +class AccessException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/options-resolver/Exception/ExceptionInterface.php b/vendor/symfony/options-resolver/Exception/ExceptionInterface.php new file mode 100644 index 00000000..ea99d050 --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Exception; + +/** + * Marker interface for all exceptions thrown by the OptionsResolver component. + * + * @author Bernhard Schussek + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/options-resolver/Exception/InvalidArgumentException.php b/vendor/symfony/options-resolver/Exception/InvalidArgumentException.php new file mode 100644 index 00000000..6d421d68 --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/InvalidArgumentException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Exception; + +/** + * Thrown when an argument is invalid. + * + * @author Bernhard Schussek + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/options-resolver/Exception/InvalidOptionsException.php b/vendor/symfony/options-resolver/Exception/InvalidOptionsException.php new file mode 100644 index 00000000..6fd4f125 --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/InvalidOptionsException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Exception; + +/** + * Thrown when the value of an option does not match its validation rules. + * + * You should make sure a valid value is passed to the option. + * + * @author Bernhard Schussek + */ +class InvalidOptionsException extends InvalidArgumentException +{ +} diff --git a/vendor/symfony/options-resolver/Exception/MissingOptionsException.php b/vendor/symfony/options-resolver/Exception/MissingOptionsException.php new file mode 100644 index 00000000..faa487f1 --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/MissingOptionsException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Exception; + +/** + * Exception thrown when a required option is missing. + * + * Add the option to the passed options array. + * + * @author Bernhard Schussek + */ +class MissingOptionsException extends InvalidArgumentException +{ +} diff --git a/vendor/symfony/options-resolver/Exception/NoConfigurationException.php b/vendor/symfony/options-resolver/Exception/NoConfigurationException.php new file mode 100644 index 00000000..6693ec14 --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/NoConfigurationException.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Exception; + +use Symfony\Component\OptionsResolver\Debug\OptionsResolverIntrospector; + +/** + * Thrown when trying to introspect an option definition property + * for which no value was configured inside the OptionsResolver instance. + * + * @see OptionsResolverIntrospector + * + * @author Maxime Steinhausser + */ +class NoConfigurationException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/options-resolver/Exception/NoSuchOptionException.php b/vendor/symfony/options-resolver/Exception/NoSuchOptionException.php new file mode 100644 index 00000000..4c3280f4 --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/NoSuchOptionException.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Exception; + +/** + * Thrown when trying to read an option that has no value set. + * + * When accessing optional options from within a lazy option or normalizer you should first + * check whether the optional option is set. You can do this with `isset($options['optional'])`. + * In contrast to the {@link UndefinedOptionsException}, this is a runtime exception that can + * occur when evaluating lazy options. + * + * @author Tobias Schultze + */ +class NoSuchOptionException extends \OutOfBoundsException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/options-resolver/Exception/OptionDefinitionException.php b/vendor/symfony/options-resolver/Exception/OptionDefinitionException.php new file mode 100644 index 00000000..e8e339d4 --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/OptionDefinitionException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Exception; + +/** + * Thrown when two lazy options have a cyclic dependency. + * + * @author Bernhard Schussek + */ +class OptionDefinitionException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/options-resolver/Exception/UndefinedOptionsException.php b/vendor/symfony/options-resolver/Exception/UndefinedOptionsException.php new file mode 100644 index 00000000..6ca3fce4 --- /dev/null +++ b/vendor/symfony/options-resolver/Exception/UndefinedOptionsException.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver\Exception; + +/** + * Exception thrown when an undefined option is passed. + * + * You should remove the options in question from your code or define them + * beforehand. + * + * @author Bernhard Schussek + */ +class UndefinedOptionsException extends InvalidArgumentException +{ +} diff --git a/vendor/symfony/options-resolver/LICENSE b/vendor/symfony/options-resolver/LICENSE new file mode 100644 index 00000000..0138f8f0 --- /dev/null +++ b/vendor/symfony/options-resolver/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/options-resolver/OptionConfigurator.php b/vendor/symfony/options-resolver/OptionConfigurator.php new file mode 100644 index 00000000..3aa37288 --- /dev/null +++ b/vendor/symfony/options-resolver/OptionConfigurator.php @@ -0,0 +1,149 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver; + +use Symfony\Component\OptionsResolver\Exception\AccessException; + +final class OptionConfigurator +{ + private string $name; + private OptionsResolver $resolver; + + public function __construct(string $name, OptionsResolver $resolver) + { + $this->name = $name; + $this->resolver = $resolver; + $this->resolver->setDefined($name); + } + + /** + * Adds allowed types for this option. + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function allowedTypes(string ...$types): static + { + $this->resolver->setAllowedTypes($this->name, $types); + + return $this; + } + + /** + * Sets allowed values for this option. + * + * @param mixed ...$values One or more acceptable values/closures + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function allowedValues(mixed ...$values): static + { + $this->resolver->setAllowedValues($this->name, $values); + + return $this; + } + + /** + * Sets the default value for this option. + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function default(mixed $value): static + { + $this->resolver->setDefault($this->name, $value); + + return $this; + } + + /** + * Defines an option configurator with the given name. + */ + public function define(string $option): self + { + return $this->resolver->define($option); + } + + /** + * Marks this option as deprecated. + * + * @param string $package The name of the composer package that is triggering the deprecation + * @param string $version The version of the package that introduced the deprecation + * @param string|\Closure $message The deprecation message to use + * + * @return $this + */ + public function deprecated(string $package, string $version, string|\Closure $message = 'The option "%name%" is deprecated.'): static + { + $this->resolver->setDeprecated($this->name, $package, $version, $message); + + return $this; + } + + /** + * Sets the normalizer for this option. + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function normalize(\Closure $normalizer): static + { + $this->resolver->setNormalizer($this->name, $normalizer); + + return $this; + } + + /** + * Marks this option as required. + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function required(): static + { + $this->resolver->setRequired($this->name); + + return $this; + } + + /** + * Sets an info message for an option. + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function info(string $info): static + { + $this->resolver->setInfo($this->name, $info); + + return $this; + } + + /** + * Sets whether ignore undefined options. + * + * @return $this + */ + public function ignoreUndefined(bool $ignore = true): static + { + $this->resolver->setIgnoreUndefined($ignore); + + return $this; + } +} diff --git a/vendor/symfony/options-resolver/Options.php b/vendor/symfony/options-resolver/Options.php new file mode 100644 index 00000000..d444ec42 --- /dev/null +++ b/vendor/symfony/options-resolver/Options.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver; + +/** + * Contains resolved option values. + * + * @author Bernhard Schussek + * @author Tobias Schultze + */ +interface Options extends \ArrayAccess, \Countable +{ +} diff --git a/vendor/symfony/options-resolver/OptionsResolver.php b/vendor/symfony/options-resolver/OptionsResolver.php new file mode 100644 index 00000000..fc378ce3 --- /dev/null +++ b/vendor/symfony/options-resolver/OptionsResolver.php @@ -0,0 +1,1317 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver; + +use Symfony\Component\OptionsResolver\Exception\AccessException; +use Symfony\Component\OptionsResolver\Exception\InvalidArgumentException; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; +use Symfony\Component\OptionsResolver\Exception\MissingOptionsException; +use Symfony\Component\OptionsResolver\Exception\NoSuchOptionException; +use Symfony\Component\OptionsResolver\Exception\OptionDefinitionException; +use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException; + +/** + * Validates options and merges them with default values. + * + * @author Bernhard Schussek + * @author Tobias Schultze + */ +class OptionsResolver implements Options +{ + private const VALIDATION_FUNCTIONS = [ + 'bool' => 'is_bool', + 'boolean' => 'is_bool', + 'int' => 'is_int', + 'integer' => 'is_int', + 'long' => 'is_int', + 'float' => 'is_float', + 'double' => 'is_float', + 'real' => 'is_float', + 'numeric' => 'is_numeric', + 'string' => 'is_string', + 'scalar' => 'is_scalar', + 'array' => 'is_array', + 'iterable' => 'is_iterable', + 'countable' => 'is_countable', + 'callable' => 'is_callable', + 'object' => 'is_object', + 'resource' => 'is_resource', + ]; + + /** + * The names of all defined options. + */ + private array $defined = []; + + /** + * The default option values. + */ + private array $defaults = []; + + /** + * A list of closure for nested options. + * + * @var \Closure[][] + */ + private array $nested = []; + + /** + * The names of required options. + */ + private array $required = []; + + /** + * The resolved option values. + */ + private array $resolved = []; + + /** + * A list of normalizer closures. + * + * @var \Closure[][] + */ + private array $normalizers = []; + + /** + * A list of accepted values for each option. + */ + private array $allowedValues = []; + + /** + * A list of accepted types for each option. + */ + private array $allowedTypes = []; + + /** + * A list of info messages for each option. + */ + private array $info = []; + + /** + * A list of closures for evaluating lazy options. + */ + private array $lazy = []; + + /** + * A list of lazy options whose closure is currently being called. + * + * This list helps detecting circular dependencies between lazy options. + */ + private array $calling = []; + + /** + * A list of deprecated options. + */ + private array $deprecated = []; + + /** + * The list of options provided by the user. + */ + private array $given = []; + + /** + * Whether the instance is locked for reading. + * + * Once locked, the options cannot be changed anymore. This is + * necessary in order to avoid inconsistencies during the resolving + * process. If any option is changed after being read, all evaluated + * lazy options that depend on this option would become invalid. + */ + private bool $locked = false; + + private array $parentsOptions = []; + + /** + * Whether the whole options definition is marked as array prototype. + */ + private ?bool $prototype = null; + + /** + * The prototype array's index that is being read. + */ + private int|string|null $prototypeIndex = null; + + /** + * Whether to ignore undefined options. + */ + private bool $ignoreUndefined = false; + + /** + * Sets the default value of a given option. + * + * If the default value should be set based on other options, you can pass + * a closure with the following signature: + * + * function (Options $options) { + * // ... + * } + * + * The closure will be evaluated when {@link resolve()} is called. The + * closure has access to the resolved values of other options through the + * passed {@link Options} instance: + * + * function (Options $options) { + * if (isset($options['port'])) { + * // ... + * } + * } + * + * If you want to access the previously set default value, add a second + * argument to the closure's signature: + * + * $options->setDefault('name', 'Default Name'); + * + * $options->setDefault('name', function (Options $options, $previousValue) { + * // 'Default Name' === $previousValue + * }); + * + * This is mostly useful if the configuration of the {@link Options} object + * is spread across different locations of your code, such as base and + * sub-classes. + * + * If you want to define nested options, you can pass a closure with the + * following signature: + * + * $options->setDefault('database', function (OptionsResolver $resolver) { + * $resolver->setDefined(['dbname', 'host', 'port', 'user', 'pass']); + * } + * + * To get access to the parent options, add a second argument to the closure's + * signature: + * + * function (OptionsResolver $resolver, Options $parent) { + * // 'default' === $parent['connection'] + * } + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function setDefault(string $option, mixed $value): static + { + // Setting is not possible once resolving starts, because then lazy + // options could manipulate the state of the object, leading to + // inconsistent results. + if ($this->locked) { + throw new AccessException('Default values cannot be set from a lazy option or normalizer.'); + } + + // If an option is a closure that should be evaluated lazily, store it + // in the "lazy" property. + if ($value instanceof \Closure) { + $reflClosure = new \ReflectionFunction($value); + $params = $reflClosure->getParameters(); + + if (isset($params[0]) && Options::class === $this->getParameterClassName($params[0])) { + // Initialize the option if no previous value exists + if (!isset($this->defaults[$option])) { + $this->defaults[$option] = null; + } + + // Ignore previous lazy options if the closure has no second parameter + if (!isset($this->lazy[$option]) || !isset($params[1])) { + $this->lazy[$option] = []; + } + + // Store closure for later evaluation + $this->lazy[$option][] = $value; + $this->defined[$option] = true; + + // Make sure the option is processed and is not nested anymore + unset($this->resolved[$option], $this->nested[$option]); + + return $this; + } + + if (isset($params[0]) && null !== ($type = $params[0]->getType()) && self::class === $type->getName() && (!isset($params[1]) || (($type = $params[1]->getType()) instanceof \ReflectionNamedType && Options::class === $type->getName()))) { + // Store closure for later evaluation + $this->nested[$option][] = $value; + $this->defaults[$option] = []; + $this->defined[$option] = true; + + // Make sure the option is processed and is not lazy anymore + unset($this->resolved[$option], $this->lazy[$option]); + + return $this; + } + } + + // This option is not lazy nor nested anymore + unset($this->lazy[$option], $this->nested[$option]); + + // Yet undefined options can be marked as resolved, because we only need + // to resolve options with lazy closures, normalizers or validation + // rules, none of which can exist for undefined options + // If the option was resolved before, update the resolved value + if (!isset($this->defined[$option]) || \array_key_exists($option, $this->resolved)) { + $this->resolved[$option] = $value; + } + + $this->defaults[$option] = $value; + $this->defined[$option] = true; + + return $this; + } + + /** + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function setDefaults(array $defaults): static + { + foreach ($defaults as $option => $value) { + $this->setDefault($option, $value); + } + + return $this; + } + + /** + * Returns whether a default value is set for an option. + * + * Returns true if {@link setDefault()} was called for this option. + * An option is also considered set if it was set to null. + */ + public function hasDefault(string $option): bool + { + return \array_key_exists($option, $this->defaults); + } + + /** + * Marks one or more options as required. + * + * @param string|string[] $optionNames One or more option names + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function setRequired(string|array $optionNames): static + { + if ($this->locked) { + throw new AccessException('Options cannot be made required from a lazy option or normalizer.'); + } + + foreach ((array) $optionNames as $option) { + $this->defined[$option] = true; + $this->required[$option] = true; + } + + return $this; + } + + /** + * Returns whether an option is required. + * + * An option is required if it was passed to {@link setRequired()}. + */ + public function isRequired(string $option): bool + { + return isset($this->required[$option]); + } + + /** + * Returns the names of all required options. + * + * @return string[] + * + * @see isRequired() + */ + public function getRequiredOptions(): array + { + return array_keys($this->required); + } + + /** + * Returns whether an option is missing a default value. + * + * An option is missing if it was passed to {@link setRequired()}, but not + * to {@link setDefault()}. This option must be passed explicitly to + * {@link resolve()}, otherwise an exception will be thrown. + */ + public function isMissing(string $option): bool + { + return isset($this->required[$option]) && !\array_key_exists($option, $this->defaults); + } + + /** + * Returns the names of all options missing a default value. + * + * @return string[] + */ + public function getMissingOptions(): array + { + return array_keys(array_diff_key($this->required, $this->defaults)); + } + + /** + * Defines a valid option name. + * + * Defines an option name without setting a default value. The option will + * be accepted when passed to {@link resolve()}. When not passed, the + * option will not be included in the resolved options. + * + * @param string|string[] $optionNames One or more option names + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function setDefined(string|array $optionNames): static + { + if ($this->locked) { + throw new AccessException('Options cannot be defined from a lazy option or normalizer.'); + } + + foreach ((array) $optionNames as $option) { + $this->defined[$option] = true; + } + + return $this; + } + + /** + * Returns whether an option is defined. + * + * Returns true for any option passed to {@link setDefault()}, + * {@link setRequired()} or {@link setDefined()}. + */ + public function isDefined(string $option): bool + { + return isset($this->defined[$option]); + } + + /** + * Returns the names of all defined options. + * + * @return string[] + * + * @see isDefined() + */ + public function getDefinedOptions(): array + { + return array_keys($this->defined); + } + + public function isNested(string $option): bool + { + return isset($this->nested[$option]); + } + + /** + * Deprecates an option, allowed types or values. + * + * Instead of passing the message, you may also pass a closure with the + * following signature: + * + * function (Options $options, $value): string { + * // ... + * } + * + * The closure receives the value as argument and should return a string. + * Return an empty string to ignore the option deprecation. + * + * The closure is invoked when {@link resolve()} is called. The parameter + * passed to the closure is the value of the option after validating it + * and before normalizing it. + * + * @param string $package The name of the composer package that is triggering the deprecation + * @param string $version The version of the package that introduced the deprecation + * @param string|\Closure $message The deprecation message to use + * + * @return $this + */ + public function setDeprecated(string $option, string $package, string $version, string|\Closure $message = 'The option "%name%" is deprecated.'): static + { + if ($this->locked) { + throw new AccessException('Options cannot be deprecated from a lazy option or normalizer.'); + } + + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist, defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined)))); + } + + if (!\is_string($message) && !$message instanceof \Closure) { + throw new InvalidArgumentException(sprintf('Invalid type for deprecation message argument, expected string or \Closure, but got "%s".', get_debug_type($message))); + } + + // ignore if empty string + if ('' === $message) { + return $this; + } + + $this->deprecated[$option] = [ + 'package' => $package, + 'version' => $version, + 'message' => $message, + ]; + + // Make sure the option is processed + unset($this->resolved[$option]); + + return $this; + } + + public function isDeprecated(string $option): bool + { + return isset($this->deprecated[$option]); + } + + /** + * Sets the normalizer for an option. + * + * The normalizer should be a closure with the following signature: + * + * function (Options $options, $value) { + * // ... + * } + * + * The closure is invoked when {@link resolve()} is called. The closure + * has access to the resolved values of other options through the passed + * {@link Options} instance. + * + * The second parameter passed to the closure is the value of + * the option. + * + * The resolved option value is set to the return value of the closure. + * + * @return $this + * + * @throws UndefinedOptionsException If the option is undefined + * @throws AccessException If called from a lazy option or normalizer + */ + public function setNormalizer(string $option, \Closure $normalizer): static + { + if ($this->locked) { + throw new AccessException('Normalizers cannot be set from a lazy option or normalizer.'); + } + + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined)))); + } + + $this->normalizers[$option] = [$normalizer]; + + // Make sure the option is processed + unset($this->resolved[$option]); + + return $this; + } + + /** + * Adds a normalizer for an option. + * + * The normalizer should be a closure with the following signature: + * + * function (Options $options, $value): mixed { + * // ... + * } + * + * The closure is invoked when {@link resolve()} is called. The closure + * has access to the resolved values of other options through the passed + * {@link Options} instance. + * + * The second parameter passed to the closure is the value of + * the option. + * + * The resolved option value is set to the return value of the closure. + * + * @return $this + * + * @throws UndefinedOptionsException If the option is undefined + * @throws AccessException If called from a lazy option or normalizer + */ + public function addNormalizer(string $option, \Closure $normalizer, bool $forcePrepend = false): static + { + if ($this->locked) { + throw new AccessException('Normalizers cannot be set from a lazy option or normalizer.'); + } + + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined)))); + } + + if ($forcePrepend) { + $this->normalizers[$option] ??= []; + array_unshift($this->normalizers[$option], $normalizer); + } else { + $this->normalizers[$option][] = $normalizer; + } + + // Make sure the option is processed + unset($this->resolved[$option]); + + return $this; + } + + /** + * Sets allowed values for an option. + * + * Instead of passing values, you may also pass a closures with the + * following signature: + * + * function ($value) { + * // return true or false + * } + * + * The closure receives the value as argument and should return true to + * accept the value and false to reject the value. + * + * @param mixed $allowedValues One or more acceptable values/closures + * + * @return $this + * + * @throws UndefinedOptionsException If the option is undefined + * @throws AccessException If called from a lazy option or normalizer + */ + public function setAllowedValues(string $option, mixed $allowedValues): static + { + if ($this->locked) { + throw new AccessException('Allowed values cannot be set from a lazy option or normalizer.'); + } + + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined)))); + } + + $this->allowedValues[$option] = \is_array($allowedValues) ? $allowedValues : [$allowedValues]; + + // Make sure the option is processed + unset($this->resolved[$option]); + + return $this; + } + + /** + * Adds allowed values for an option. + * + * The values are merged with the allowed values defined previously. + * + * Instead of passing values, you may also pass a closures with the + * following signature: + * + * function ($value) { + * // return true or false + * } + * + * The closure receives the value as argument and should return true to + * accept the value and false to reject the value. + * + * @param mixed $allowedValues One or more acceptable values/closures + * + * @return $this + * + * @throws UndefinedOptionsException If the option is undefined + * @throws AccessException If called from a lazy option or normalizer + */ + public function addAllowedValues(string $option, mixed $allowedValues): static + { + if ($this->locked) { + throw new AccessException('Allowed values cannot be added from a lazy option or normalizer.'); + } + + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined)))); + } + + if (!\is_array($allowedValues)) { + $allowedValues = [$allowedValues]; + } + + if (!isset($this->allowedValues[$option])) { + $this->allowedValues[$option] = $allowedValues; + } else { + $this->allowedValues[$option] = array_merge($this->allowedValues[$option], $allowedValues); + } + + // Make sure the option is processed + unset($this->resolved[$option]); + + return $this; + } + + /** + * Sets allowed types for an option. + * + * Any type for which a corresponding is_() function exists is + * acceptable. Additionally, fully-qualified class or interface names may + * be passed. + * + * @param string|string[] $allowedTypes One or more accepted types + * + * @return $this + * + * @throws UndefinedOptionsException If the option is undefined + * @throws AccessException If called from a lazy option or normalizer + */ + public function setAllowedTypes(string $option, string|array $allowedTypes): static + { + if ($this->locked) { + throw new AccessException('Allowed types cannot be set from a lazy option or normalizer.'); + } + + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined)))); + } + + $this->allowedTypes[$option] = (array) $allowedTypes; + + // Make sure the option is processed + unset($this->resolved[$option]); + + return $this; + } + + /** + * Adds allowed types for an option. + * + * The types are merged with the allowed types defined previously. + * + * Any type for which a corresponding is_() function exists is + * acceptable. Additionally, fully-qualified class or interface names may + * be passed. + * + * @param string|string[] $allowedTypes One or more accepted types + * + * @return $this + * + * @throws UndefinedOptionsException If the option is undefined + * @throws AccessException If called from a lazy option or normalizer + */ + public function addAllowedTypes(string $option, string|array $allowedTypes): static + { + if ($this->locked) { + throw new AccessException('Allowed types cannot be added from a lazy option or normalizer.'); + } + + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined)))); + } + + if (!isset($this->allowedTypes[$option])) { + $this->allowedTypes[$option] = (array) $allowedTypes; + } else { + $this->allowedTypes[$option] = array_merge($this->allowedTypes[$option], (array) $allowedTypes); + } + + // Make sure the option is processed + unset($this->resolved[$option]); + + return $this; + } + + /** + * Defines an option configurator with the given name. + */ + public function define(string $option): OptionConfigurator + { + if (isset($this->defined[$option])) { + throw new OptionDefinitionException(sprintf('The option "%s" is already defined.', $option)); + } + + return new OptionConfigurator($option, $this); + } + + /** + * Sets an info message for an option. + * + * @return $this + * + * @throws UndefinedOptionsException If the option is undefined + * @throws AccessException If called from a lazy option or normalizer + */ + public function setInfo(string $option, string $info): static + { + if ($this->locked) { + throw new AccessException('The Info message cannot be set from a lazy option or normalizer.'); + } + + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined)))); + } + + $this->info[$option] = $info; + + return $this; + } + + /** + * Gets the info message for an option. + */ + public function getInfo(string $option): ?string + { + if (!isset($this->defined[$option])) { + throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined)))); + } + + return $this->info[$option] ?? null; + } + + /** + * Marks the whole options definition as array prototype. + * + * @return $this + * + * @throws AccessException If called from a lazy option, a normalizer or a root definition + */ + public function setPrototype(bool $prototype): static + { + if ($this->locked) { + throw new AccessException('The prototype property cannot be set from a lazy option or normalizer.'); + } + + if (null === $this->prototype && $prototype) { + throw new AccessException('The prototype property cannot be set from a root definition.'); + } + + $this->prototype = $prototype; + + return $this; + } + + public function isPrototype(): bool + { + return $this->prototype ?? false; + } + + /** + * Removes the option with the given name. + * + * Undefined options are ignored. + * + * @param string|string[] $optionNames One or more option names + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function remove(string|array $optionNames): static + { + if ($this->locked) { + throw new AccessException('Options cannot be removed from a lazy option or normalizer.'); + } + + foreach ((array) $optionNames as $option) { + unset($this->defined[$option], $this->defaults[$option], $this->required[$option], $this->resolved[$option]); + unset($this->lazy[$option], $this->normalizers[$option], $this->allowedTypes[$option], $this->allowedValues[$option], $this->info[$option]); + } + + return $this; + } + + /** + * Removes all options. + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function clear(): static + { + if ($this->locked) { + throw new AccessException('Options cannot be cleared from a lazy option or normalizer.'); + } + + $this->defined = []; + $this->defaults = []; + $this->nested = []; + $this->required = []; + $this->resolved = []; + $this->lazy = []; + $this->normalizers = []; + $this->allowedTypes = []; + $this->allowedValues = []; + $this->deprecated = []; + $this->info = []; + + return $this; + } + + /** + * Merges options with the default values stored in the container and + * validates them. + * + * Exceptions are thrown if: + * + * - Undefined options are passed; + * - Required options are missing; + * - Options have invalid types; + * - Options have invalid values. + * + * @throws UndefinedOptionsException If an option name is undefined + * @throws InvalidOptionsException If an option doesn't fulfill the + * specified validation rules + * @throws MissingOptionsException If a required option is missing + * @throws OptionDefinitionException If there is a cyclic dependency between + * lazy options and/or normalizers + * @throws NoSuchOptionException If a lazy option reads an unavailable option + * @throws AccessException If called from a lazy option or normalizer + */ + public function resolve(array $options = []): array + { + if ($this->locked) { + throw new AccessException('Options cannot be resolved from a lazy option or normalizer.'); + } + + // Allow this method to be called multiple times + $clone = clone $this; + + // Make sure that no unknown options are passed + $diff = $this->ignoreUndefined ? [] : array_diff_key($options, $clone->defined); + + if (\count($diff) > 0) { + ksort($clone->defined); + ksort($diff); + + throw new UndefinedOptionsException(sprintf((\count($diff) > 1 ? 'The options "%s" do not exist.' : 'The option "%s" does not exist.').' Defined options are: "%s".', $this->formatOptions(array_keys($diff)), implode('", "', array_keys($clone->defined)))); + } + + // Override options set by the user + foreach ($options as $option => $value) { + if ($this->ignoreUndefined && !isset($clone->defined[$option])) { + continue; + } + + $clone->given[$option] = true; + $clone->defaults[$option] = $value; + unset($clone->resolved[$option], $clone->lazy[$option]); + } + + // Check whether any required option is missing + $diff = array_diff_key($clone->required, $clone->defaults); + + if (\count($diff) > 0) { + ksort($diff); + + throw new MissingOptionsException(sprintf(\count($diff) > 1 ? 'The required options "%s" are missing.' : 'The required option "%s" is missing.', $this->formatOptions(array_keys($diff)))); + } + + // Lock the container + $clone->locked = true; + + // Now process the individual options. Use offsetGet(), which resolves + // the option itself and any options that the option depends on + foreach ($clone->defaults as $option => $_) { + $clone->offsetGet($option); + } + + return $clone->resolved; + } + + /** + * Returns the resolved value of an option. + * + * @param bool $triggerDeprecation Whether to trigger the deprecation or not (true by default) + * + * @throws AccessException If accessing this method outside of + * {@link resolve()} + * @throws NoSuchOptionException If the option is not set + * @throws InvalidOptionsException If the option doesn't fulfill the + * specified validation rules + * @throws OptionDefinitionException If there is a cyclic dependency between + * lazy options and/or normalizers + */ + public function offsetGet(mixed $option, bool $triggerDeprecation = true): mixed + { + if (!$this->locked) { + throw new AccessException('Array access is only supported within closures of lazy options and normalizers.'); + } + + // Shortcut for resolved options + if (isset($this->resolved[$option]) || \array_key_exists($option, $this->resolved)) { + if ($triggerDeprecation && isset($this->deprecated[$option]) && (isset($this->given[$option]) || $this->calling) && \is_string($this->deprecated[$option]['message'])) { + trigger_deprecation($this->deprecated[$option]['package'], $this->deprecated[$option]['version'], strtr($this->deprecated[$option]['message'], ['%name%' => $option])); + } + + return $this->resolved[$option]; + } + + // Check whether the option is set at all + if (!isset($this->defaults[$option]) && !\array_key_exists($option, $this->defaults)) { + if (!isset($this->defined[$option])) { + throw new NoSuchOptionException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined)))); + } + + throw new NoSuchOptionException(sprintf('The optional option "%s" has no value set. You should make sure it is set with "isset" before reading it.', $this->formatOptions([$option]))); + } + + $value = $this->defaults[$option]; + + // Resolve the option if it is a nested definition + if (isset($this->nested[$option])) { + // If the closure is already being called, we have a cyclic dependency + if (isset($this->calling[$option])) { + throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', $this->formatOptions(array_keys($this->calling)))); + } + + if (!\is_array($value)) { + throw new InvalidOptionsException(sprintf('The nested option "%s" with value %s is expected to be of type array, but is of type "%s".', $this->formatOptions([$option]), $this->formatValue($value), get_debug_type($value))); + } + + // The following section must be protected from cyclic calls. + $this->calling[$option] = true; + try { + $resolver = new self(); + $resolver->prototype = false; + $resolver->parentsOptions = $this->parentsOptions; + $resolver->parentsOptions[] = $option; + foreach ($this->nested[$option] as $closure) { + $closure($resolver, $this); + } + + if ($resolver->prototype) { + $values = []; + foreach ($value as $index => $prototypeValue) { + if (!\is_array($prototypeValue)) { + throw new InvalidOptionsException(sprintf('The value of the option "%s" is expected to be of type array of array, but is of type array of "%s".', $this->formatOptions([$option]), get_debug_type($prototypeValue))); + } + + $resolver->prototypeIndex = $index; + $values[$index] = $resolver->resolve($prototypeValue); + } + $value = $values; + } else { + $value = $resolver->resolve($value); + } + } finally { + $resolver->prototypeIndex = null; + unset($this->calling[$option]); + } + } + + // Resolve the option if the default value is lazily evaluated + if (isset($this->lazy[$option])) { + // If the closure is already being called, we have a cyclic + // dependency + if (isset($this->calling[$option])) { + throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', $this->formatOptions(array_keys($this->calling)))); + } + + // The following section must be protected from cyclic + // calls. Set $calling for the current $option to detect a cyclic + // dependency + // BEGIN + $this->calling[$option] = true; + try { + foreach ($this->lazy[$option] as $closure) { + $value = $closure($this, $value); + } + } finally { + unset($this->calling[$option]); + } + // END + } + + // Validate the type of the resolved option + if (isset($this->allowedTypes[$option])) { + $valid = true; + $invalidTypes = []; + + foreach ($this->allowedTypes[$option] as $type) { + if ($valid = $this->verifyTypes($type, $value, $invalidTypes)) { + break; + } + } + + if (!$valid) { + $fmtActualValue = $this->formatValue($value); + $fmtAllowedTypes = implode('" or "', $this->allowedTypes[$option]); + $fmtProvidedTypes = implode('|', array_keys($invalidTypes)); + $allowedContainsArrayType = \count(array_filter($this->allowedTypes[$option], static fn ($item) => str_ends_with($item, '[]'))) > 0; + + if (\is_array($value) && $allowedContainsArrayType) { + throw new InvalidOptionsException(sprintf('The option "%s" with value %s is expected to be of type "%s", but one of the elements is of type "%s".', $this->formatOptions([$option]), $fmtActualValue, $fmtAllowedTypes, $fmtProvidedTypes)); + } + + throw new InvalidOptionsException(sprintf('The option "%s" with value %s is expected to be of type "%s", but is of type "%s".', $this->formatOptions([$option]), $fmtActualValue, $fmtAllowedTypes, $fmtProvidedTypes)); + } + } + + // Validate the value of the resolved option + if (isset($this->allowedValues[$option])) { + $success = false; + $printableAllowedValues = []; + + foreach ($this->allowedValues[$option] as $allowedValue) { + if ($allowedValue instanceof \Closure) { + if ($allowedValue($value)) { + $success = true; + break; + } + + // Don't include closures in the exception message + continue; + } + + if ($value === $allowedValue) { + $success = true; + break; + } + + $printableAllowedValues[] = $allowedValue; + } + + if (!$success) { + $message = sprintf( + 'The option "%s" with value %s is invalid.', + $this->formatOptions([$option]), + $this->formatValue($value) + ); + + if (\count($printableAllowedValues) > 0) { + $message .= sprintf( + ' Accepted values are: %s.', + $this->formatValues($printableAllowedValues) + ); + } + + if (isset($this->info[$option])) { + $message .= sprintf(' Info: %s.', $this->info[$option]); + } + + throw new InvalidOptionsException($message); + } + } + + // Check whether the option is deprecated + // and it is provided by the user or is being called from a lazy evaluation + if ($triggerDeprecation && isset($this->deprecated[$option]) && (isset($this->given[$option]) || ($this->calling && \is_string($this->deprecated[$option]['message'])))) { + $deprecation = $this->deprecated[$option]; + $message = $this->deprecated[$option]['message']; + + if ($message instanceof \Closure) { + // If the closure is already being called, we have a cyclic dependency + if (isset($this->calling[$option])) { + throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', $this->formatOptions(array_keys($this->calling)))); + } + + $this->calling[$option] = true; + try { + if (!\is_string($message = $message($this, $value))) { + throw new InvalidOptionsException(sprintf('Invalid type for deprecation message, expected string but got "%s", return an empty string to ignore.', get_debug_type($message))); + } + } finally { + unset($this->calling[$option]); + } + } + + if ('' !== $message) { + trigger_deprecation($deprecation['package'], $deprecation['version'], strtr($message, ['%name%' => $option])); + } + } + + // Normalize the validated option + if (isset($this->normalizers[$option])) { + // If the closure is already being called, we have a cyclic + // dependency + if (isset($this->calling[$option])) { + throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', $this->formatOptions(array_keys($this->calling)))); + } + + // The following section must be protected from cyclic + // calls. Set $calling for the current $option to detect a cyclic + // dependency + // BEGIN + $this->calling[$option] = true; + try { + foreach ($this->normalizers[$option] as $normalizer) { + $value = $normalizer($this, $value); + } + } finally { + unset($this->calling[$option]); + } + // END + } + + // Mark as resolved + $this->resolved[$option] = $value; + + return $value; + } + + private function verifyTypes(string $type, mixed $value, array &$invalidTypes, int $level = 0): bool + { + if (\is_array($value) && str_ends_with($type, '[]')) { + $type = substr($type, 0, -2); + $valid = true; + + foreach ($value as $val) { + if (!$this->verifyTypes($type, $val, $invalidTypes, $level + 1)) { + $valid = false; + } + } + + return $valid; + } + + if (('null' === $type && null === $value) || (isset(self::VALIDATION_FUNCTIONS[$type]) ? self::VALIDATION_FUNCTIONS[$type]($value) : $value instanceof $type)) { + return true; + } + + if (!$invalidTypes || $level > 0) { + $invalidTypes[get_debug_type($value)] = true; + } + + return false; + } + + /** + * Returns whether a resolved option with the given name exists. + * + * @throws AccessException If accessing this method outside of {@link resolve()} + * + * @see \ArrayAccess::offsetExists() + */ + public function offsetExists(mixed $option): bool + { + if (!$this->locked) { + throw new AccessException('Array access is only supported within closures of lazy options and normalizers.'); + } + + return \array_key_exists($option, $this->defaults); + } + + /** + * Not supported. + * + * @throws AccessException + */ + public function offsetSet(mixed $option, mixed $value): void + { + throw new AccessException('Setting options via array access is not supported. Use setDefault() instead.'); + } + + /** + * Not supported. + * + * @throws AccessException + */ + public function offsetUnset(mixed $option): void + { + throw new AccessException('Removing options via array access is not supported. Use remove() instead.'); + } + + /** + * Returns the number of set options. + * + * This may be only a subset of the defined options. + * + * @throws AccessException If accessing this method outside of {@link resolve()} + * + * @see \Countable::count() + */ + public function count(): int + { + if (!$this->locked) { + throw new AccessException('Counting is only supported within closures of lazy options and normalizers.'); + } + + return \count($this->defaults); + } + + /** + * Sets whether ignore undefined options. + * + * @return $this + */ + public function setIgnoreUndefined(bool $ignore = true): static + { + $this->ignoreUndefined = $ignore; + + return $this; + } + + /** + * Returns a string representation of the value. + * + * This method returns the equivalent PHP tokens for most scalar types + * (i.e. "false" for false, "1" for 1 etc.). Strings are always wrapped + * in double quotes ("). + */ + private function formatValue(mixed $value): string + { + if (\is_object($value)) { + return $value::class; + } + + if (\is_array($value)) { + return 'array'; + } + + if (\is_string($value)) { + return '"'.$value.'"'; + } + + if (\is_resource($value)) { + return 'resource'; + } + + if (null === $value) { + return 'null'; + } + + if (false === $value) { + return 'false'; + } + + if (true === $value) { + return 'true'; + } + + return (string) $value; + } + + /** + * Returns a string representation of a list of values. + * + * Each of the values is converted to a string using + * {@link formatValue()}. The values are then concatenated with commas. + * + * @see formatValue() + */ + private function formatValues(array $values): string + { + foreach ($values as $key => $value) { + $values[$key] = $this->formatValue($value); + } + + return implode(', ', $values); + } + + private function formatOptions(array $options): string + { + if ($this->parentsOptions) { + $prefix = array_shift($this->parentsOptions); + if ($this->parentsOptions) { + $prefix .= sprintf('[%s]', implode('][', $this->parentsOptions)); + } + + if ($this->prototype && null !== $this->prototypeIndex) { + $prefix .= sprintf('[%s]', $this->prototypeIndex); + } + + $options = array_map(static fn (string $option): string => sprintf('%s[%s]', $prefix, $option), $options); + } + + return implode('", "', $options); + } + + private function getParameterClassName(\ReflectionParameter $parameter): ?string + { + if (!($type = $parameter->getType()) instanceof \ReflectionNamedType || $type->isBuiltin()) { + return null; + } + + return $type->getName(); + } +} diff --git a/vendor/symfony/options-resolver/README.md b/vendor/symfony/options-resolver/README.md new file mode 100644 index 00000000..c63b9005 --- /dev/null +++ b/vendor/symfony/options-resolver/README.md @@ -0,0 +1,15 @@ +OptionsResolver Component +========================= + +The OptionsResolver component is `array_replace` on steroids. It allows you to +create an options system with required options, defaults, validation (type, +value), normalization and more. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/options_resolver.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/options-resolver/composer.json b/vendor/symfony/options-resolver/composer.json new file mode 100644 index 00000000..e70640d6 --- /dev/null +++ b/vendor/symfony/options-resolver/composer.json @@ -0,0 +1,29 @@ +{ + "name": "symfony/options-resolver", + "type": "library", + "description": "Provides an improved replacement for the array_replace PHP function", + "keywords": ["options", "config", "configuration"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\OptionsResolver\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/polyfill-ctype/Ctype.php b/vendor/symfony/polyfill-ctype/Ctype.php new file mode 100644 index 00000000..ba75a2c9 --- /dev/null +++ b/vendor/symfony/polyfill-ctype/Ctype.php @@ -0,0 +1,232 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Ctype; + +/** + * Ctype implementation through regex. + * + * @internal + * + * @author Gert de Pagter + */ +final class Ctype +{ + /** + * Returns TRUE if every character in text is either a letter or a digit, FALSE otherwise. + * + * @see https://php.net/ctype-alnum + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_alnum($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z0-9]/', $text); + } + + /** + * Returns TRUE if every character in text is a letter, FALSE otherwise. + * + * @see https://php.net/ctype-alpha + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_alpha($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z]/', $text); + } + + /** + * Returns TRUE if every character in text is a control character from the current locale, FALSE otherwise. + * + * @see https://php.net/ctype-cntrl + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_cntrl($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^\x00-\x1f\x7f]/', $text); + } + + /** + * Returns TRUE if every character in the string text is a decimal digit, FALSE otherwise. + * + * @see https://php.net/ctype-digit + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_digit($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^0-9]/', $text); + } + + /** + * Returns TRUE if every character in text is printable and actually creates visible output (no white space), FALSE otherwise. + * + * @see https://php.net/ctype-graph + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_graph($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^!-~]/', $text); + } + + /** + * Returns TRUE if every character in text is a lowercase letter. + * + * @see https://php.net/ctype-lower + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_lower($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^a-z]/', $text); + } + + /** + * Returns TRUE if every character in text will actually create output (including blanks). Returns FALSE if text contains control characters or characters that do not have any output or control function at all. + * + * @see https://php.net/ctype-print + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_print($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^ -~]/', $text); + } + + /** + * Returns TRUE if every character in text is printable, but neither letter, digit or blank, FALSE otherwise. + * + * @see https://php.net/ctype-punct + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_punct($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^!-\/\:-@\[-`\{-~]/', $text); + } + + /** + * Returns TRUE if every character in text creates some sort of white space, FALSE otherwise. Besides the blank character this also includes tab, vertical tab, line feed, carriage return and form feed characters. + * + * @see https://php.net/ctype-space + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_space($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^\s]/', $text); + } + + /** + * Returns TRUE if every character in text is an uppercase letter. + * + * @see https://php.net/ctype-upper + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_upper($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Z]/', $text); + } + + /** + * Returns TRUE if every character in text is a hexadecimal 'digit', that is a decimal digit or a character from [A-Fa-f] , FALSE otherwise. + * + * @see https://php.net/ctype-xdigit + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_xdigit($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Fa-f0-9]/', $text); + } + + /** + * Converts integers to their char versions according to normal ctype behaviour, if needed. + * + * If an integer between -128 and 255 inclusive is provided, + * it is interpreted as the ASCII value of a single character + * (negative values have 256 added in order to allow characters in the Extended ASCII range). + * Any other integer is interpreted as a string containing the decimal digits of the integer. + * + * @param mixed $int + * @param string $function + * + * @return mixed + */ + private static function convert_int_to_char_for_ctype($int, $function) + { + if (!\is_int($int)) { + return $int; + } + + if ($int < -128 || $int > 255) { + return (string) $int; + } + + if (\PHP_VERSION_ID >= 80100) { + @trigger_error($function.'(): Argument of type int will be interpreted as string in the future', \E_USER_DEPRECATED); + } + + if ($int < 0) { + $int += 256; + } + + return \chr($int); + } +} diff --git a/vendor/symfony/polyfill-ctype/LICENSE b/vendor/symfony/polyfill-ctype/LICENSE new file mode 100644 index 00000000..7536caea --- /dev/null +++ b/vendor/symfony/polyfill-ctype/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/polyfill-ctype/README.md b/vendor/symfony/polyfill-ctype/README.md new file mode 100644 index 00000000..b144d03c --- /dev/null +++ b/vendor/symfony/polyfill-ctype/README.md @@ -0,0 +1,12 @@ +Symfony Polyfill / Ctype +======================== + +This component provides `ctype_*` functions to users who run php versions without the ctype extension. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-ctype/bootstrap.php b/vendor/symfony/polyfill-ctype/bootstrap.php new file mode 100644 index 00000000..d54524b3 --- /dev/null +++ b/vendor/symfony/polyfill-ctype/bootstrap.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Ctype as p; + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +if (!function_exists('ctype_alnum')) { + function ctype_alnum($text) { return p\Ctype::ctype_alnum($text); } +} +if (!function_exists('ctype_alpha')) { + function ctype_alpha($text) { return p\Ctype::ctype_alpha($text); } +} +if (!function_exists('ctype_cntrl')) { + function ctype_cntrl($text) { return p\Ctype::ctype_cntrl($text); } +} +if (!function_exists('ctype_digit')) { + function ctype_digit($text) { return p\Ctype::ctype_digit($text); } +} +if (!function_exists('ctype_graph')) { + function ctype_graph($text) { return p\Ctype::ctype_graph($text); } +} +if (!function_exists('ctype_lower')) { + function ctype_lower($text) { return p\Ctype::ctype_lower($text); } +} +if (!function_exists('ctype_print')) { + function ctype_print($text) { return p\Ctype::ctype_print($text); } +} +if (!function_exists('ctype_punct')) { + function ctype_punct($text) { return p\Ctype::ctype_punct($text); } +} +if (!function_exists('ctype_space')) { + function ctype_space($text) { return p\Ctype::ctype_space($text); } +} +if (!function_exists('ctype_upper')) { + function ctype_upper($text) { return p\Ctype::ctype_upper($text); } +} +if (!function_exists('ctype_xdigit')) { + function ctype_xdigit($text) { return p\Ctype::ctype_xdigit($text); } +} diff --git a/vendor/symfony/polyfill-ctype/bootstrap80.php b/vendor/symfony/polyfill-ctype/bootstrap80.php new file mode 100644 index 00000000..ab2f8611 --- /dev/null +++ b/vendor/symfony/polyfill-ctype/bootstrap80.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Ctype as p; + +if (!function_exists('ctype_alnum')) { + function ctype_alnum(mixed $text): bool { return p\Ctype::ctype_alnum($text); } +} +if (!function_exists('ctype_alpha')) { + function ctype_alpha(mixed $text): bool { return p\Ctype::ctype_alpha($text); } +} +if (!function_exists('ctype_cntrl')) { + function ctype_cntrl(mixed $text): bool { return p\Ctype::ctype_cntrl($text); } +} +if (!function_exists('ctype_digit')) { + function ctype_digit(mixed $text): bool { return p\Ctype::ctype_digit($text); } +} +if (!function_exists('ctype_graph')) { + function ctype_graph(mixed $text): bool { return p\Ctype::ctype_graph($text); } +} +if (!function_exists('ctype_lower')) { + function ctype_lower(mixed $text): bool { return p\Ctype::ctype_lower($text); } +} +if (!function_exists('ctype_print')) { + function ctype_print(mixed $text): bool { return p\Ctype::ctype_print($text); } +} +if (!function_exists('ctype_punct')) { + function ctype_punct(mixed $text): bool { return p\Ctype::ctype_punct($text); } +} +if (!function_exists('ctype_space')) { + function ctype_space(mixed $text): bool { return p\Ctype::ctype_space($text); } +} +if (!function_exists('ctype_upper')) { + function ctype_upper(mixed $text): bool { return p\Ctype::ctype_upper($text); } +} +if (!function_exists('ctype_xdigit')) { + function ctype_xdigit(mixed $text): bool { return p\Ctype::ctype_xdigit($text); } +} diff --git a/vendor/symfony/polyfill-ctype/composer.json b/vendor/symfony/polyfill-ctype/composer.json new file mode 100644 index 00000000..b222fdab --- /dev/null +++ b/vendor/symfony/polyfill-ctype/composer.json @@ -0,0 +1,38 @@ +{ + "name": "symfony/polyfill-ctype", + "type": "library", + "description": "Symfony polyfill for ctype functions", + "keywords": ["polyfill", "compatibility", "portable", "ctype"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Ctype\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/vendor/symfony/polyfill-intl-grapheme/Grapheme.php b/vendor/symfony/polyfill-intl-grapheme/Grapheme.php new file mode 100644 index 00000000..5373f168 --- /dev/null +++ b/vendor/symfony/polyfill-intl-grapheme/Grapheme.php @@ -0,0 +1,247 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Grapheme; + +\define('SYMFONY_GRAPHEME_CLUSTER_RX', ((float) \PCRE_VERSION < 10 ? (float) \PCRE_VERSION >= 8.32 : (float) \PCRE_VERSION >= 10.39) ? '\X' : Grapheme::GRAPHEME_CLUSTER_RX); + +/** + * Partial intl implementation in pure PHP. + * + * Implemented: + * - grapheme_extract - Extract a sequence of grapheme clusters from a text buffer, which must be encoded in UTF-8 + * - grapheme_stripos - Find position (in grapheme units) of first occurrence of a case-insensitive string + * - grapheme_stristr - Returns part of haystack string from the first occurrence of case-insensitive needle to the end of haystack + * - grapheme_strlen - Get string length in grapheme units + * - grapheme_strpos - Find position (in grapheme units) of first occurrence of a string + * - grapheme_strripos - Find position (in grapheme units) of last occurrence of a case-insensitive string + * - grapheme_strrpos - Find position (in grapheme units) of last occurrence of a string + * - grapheme_strstr - Returns part of haystack string from the first occurrence of needle to the end of haystack + * - grapheme_substr - Return part of a string + * + * @author Nicolas Grekas + * + * @internal + */ +final class Grapheme +{ + // (CRLF|([ZWNJ-ZWJ]|T+|L*(LV?V+|LV|LVT)T*|L+|[^Control])[Extend]*|[Control]) + // This regular expression is a work around for http://bugs.exim.org/1279 + public const GRAPHEME_CLUSTER_RX = '(?:\r\n|(?:[ -~\x{200C}\x{200D}]|[ᆨ-ᇹ]+|[ᄀ-ᅟ]*(?:[가개갸걔거게겨계고과괘괴교구궈궤귀규그긔기까깨꺄꺠꺼께껴꼐꼬꽈꽤꾀꾜꾸꿔꿰뀌뀨끄끠끼나내냐냬너네녀녜노놔놰뇌뇨누눠눼뉘뉴느늬니다대댜댸더데뎌뎨도돠돼되됴두둬뒈뒤듀드듸디따때땨떄떠떼뗘뗴또똬뙈뙤뚀뚜뚸뛔뛰뜌뜨띄띠라래랴럐러레려례로롸뢔뢰료루뤄뤠뤼류르릐리마매먀먜머메며몌모뫄뫠뫼묘무뭐뭬뮈뮤므믜미바배뱌뱨버베벼볘보봐봬뵈뵤부붜붸뷔뷰브븨비빠빼뺘뺴뻐뻬뼈뼤뽀뽜뽸뾔뾰뿌뿨쀄쀠쀼쁘쁴삐사새샤섀서세셔셰소솨쇄쇠쇼수숴쉐쉬슈스싀시싸쌔쌰썌써쎄쎠쎼쏘쏴쐐쐬쑈쑤쒀쒜쒸쓔쓰씌씨아애야얘어에여예오와왜외요우워웨위유으의이자재쟈쟤저제져졔조좌좨죄죠주줘줴쥐쥬즈즤지짜째쨔쨰쩌쩨쪄쪠쪼쫘쫴쬐쬬쭈쭤쮀쮜쮸쯔쯰찌차채챠챼처체쳐쳬초촤쵀최쵸추춰췌취츄츠츼치카캐캬컈커케켜켸코콰쾌쾨쿄쿠쿼퀘퀴큐크킈키타태탸턔터테텨톄토톼퇘퇴툐투퉈퉤튀튜트틔티파패퍄퍠퍼페펴폐포퐈퐤푀표푸풔풰퓌퓨프픠피하해햐햬허헤혀혜호화홰회효후훠훼휘휴흐희히]?[ᅠ-ᆢ]+|[가-힣])[ᆨ-ᇹ]*|[ᄀ-ᅟ]+|[^\p{Cc}\p{Cf}\p{Zl}\p{Zp}])[\p{Mn}\p{Me}\x{09BE}\x{09D7}\x{0B3E}\x{0B57}\x{0BBE}\x{0BD7}\x{0CC2}\x{0CD5}\x{0CD6}\x{0D3E}\x{0D57}\x{0DCF}\x{0DDF}\x{200C}\x{200D}\x{1D165}\x{1D16E}-\x{1D172}]*|[\p{Cc}\p{Cf}\p{Zl}\p{Zp}])'; + + private const CASE_FOLD = [ + ['µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"], + ['μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'], + ]; + + public static function grapheme_extract($s, $size, $type = \GRAPHEME_EXTR_COUNT, $start = 0, &$next = 0) + { + if (0 > $start) { + $start = \strlen($s) + $start; + } + + if (!\is_scalar($s)) { + $hasError = false; + set_error_handler(function () use (&$hasError) { $hasError = true; }); + $next = substr($s, $start); + restore_error_handler(); + if ($hasError) { + substr($s, $start); + $s = ''; + } else { + $s = $next; + } + } else { + $s = substr($s, $start); + } + $size = (int) $size; + $type = (int) $type; + $start = (int) $start; + + if (\GRAPHEME_EXTR_COUNT !== $type && \GRAPHEME_EXTR_MAXBYTES !== $type && \GRAPHEME_EXTR_MAXCHARS !== $type) { + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError('grapheme_extract(): Argument #3 ($type) must be one of GRAPHEME_EXTR_COUNT, GRAPHEME_EXTR_MAXBYTES, or GRAPHEME_EXTR_MAXCHARS'); + } + + if (!isset($s[0]) || 0 > $size || 0 > $start) { + return false; + } + if (0 === $size) { + return ''; + } + + $next = $start; + + $s = preg_split('/('.SYMFONY_GRAPHEME_CLUSTER_RX.')/u', "\r\n".$s, $size + 1, \PREG_SPLIT_NO_EMPTY | \PREG_SPLIT_DELIM_CAPTURE); + + if (!isset($s[1])) { + return false; + } + + $i = 1; + $ret = ''; + + do { + if (\GRAPHEME_EXTR_COUNT === $type) { + --$size; + } elseif (\GRAPHEME_EXTR_MAXBYTES === $type) { + $size -= \strlen($s[$i]); + } else { + $size -= iconv_strlen($s[$i], 'UTF-8//IGNORE'); + } + + if ($size >= 0) { + $ret .= $s[$i]; + } + } while (isset($s[++$i]) && $size > 0); + + $next += \strlen($ret); + + return $ret; + } + + public static function grapheme_strlen($s) + { + preg_replace('/'.SYMFONY_GRAPHEME_CLUSTER_RX.'/u', '', $s, -1, $len); + + return 0 === $len && '' !== $s ? null : $len; + } + + public static function grapheme_substr($s, $start, $len = null) + { + if (null === $len) { + $len = 2147483647; + } + + preg_match_all('/'.SYMFONY_GRAPHEME_CLUSTER_RX.'/u', $s, $s); + + $slen = \count($s[0]); + $start = (int) $start; + + if (0 > $start) { + $start += $slen; + } + if (0 > $start) { + if (\PHP_VERSION_ID < 80000) { + return false; + } + + $start = 0; + } + if ($start >= $slen) { + return \PHP_VERSION_ID >= 80000 ? '' : false; + } + + $rem = $slen - $start; + + if (0 > $len) { + $len += $rem; + } + if (0 === $len) { + return ''; + } + if (0 > $len) { + return \PHP_VERSION_ID >= 80000 ? '' : false; + } + if ($len > $rem) { + $len = $rem; + } + + return implode('', \array_slice($s[0], $start, $len)); + } + + public static function grapheme_strpos($s, $needle, $offset = 0) + { + return self::grapheme_position($s, $needle, $offset, 0); + } + + public static function grapheme_stripos($s, $needle, $offset = 0) + { + return self::grapheme_position($s, $needle, $offset, 1); + } + + public static function grapheme_strrpos($s, $needle, $offset = 0) + { + return self::grapheme_position($s, $needle, $offset, 2); + } + + public static function grapheme_strripos($s, $needle, $offset = 0) + { + return self::grapheme_position($s, $needle, $offset, 3); + } + + public static function grapheme_stristr($s, $needle, $beforeNeedle = false) + { + return mb_stristr($s, $needle, $beforeNeedle, 'UTF-8'); + } + + public static function grapheme_strstr($s, $needle, $beforeNeedle = false) + { + return mb_strstr($s, $needle, $beforeNeedle, 'UTF-8'); + } + + private static function grapheme_position($s, $needle, $offset, $mode) + { + $needle = (string) $needle; + if (80000 > \PHP_VERSION_ID && !preg_match('/./us', $needle)) { + return false; + } + $s = (string) $s; + if (!preg_match('/./us', $s)) { + return false; + } + if ($offset > 0) { + $s = self::grapheme_substr($s, $offset); + } elseif ($offset < 0) { + if (2 > $mode) { + $offset += self::grapheme_strlen($s); + $s = self::grapheme_substr($s, $offset); + if (0 > $offset) { + $offset = 0; + } + } elseif (0 > $offset += self::grapheme_strlen($needle)) { + $s = self::grapheme_substr($s, 0, $offset); + $offset = 0; + } else { + $offset = 0; + } + } + + // As UTF-8 is self-synchronizing, and we have ensured the strings are valid UTF-8, + // we can use normal binary string functions here. For case-insensitive searches, + // case fold the strings first. + $caseInsensitive = $mode & 1; + $reverse = $mode & 2; + if ($caseInsensitive) { + // Use the same case folding mode as mbstring does for mb_stripos(). + // Stick to SIMPLE case folding to avoid changing the length of the string, which + // might result in offsets being shifted. + $mode = \defined('MB_CASE_FOLD_SIMPLE') ? \MB_CASE_FOLD_SIMPLE : \MB_CASE_LOWER; + $s = mb_convert_case($s, $mode, 'UTF-8'); + $needle = mb_convert_case($needle, $mode, 'UTF-8'); + + if (!\defined('MB_CASE_FOLD_SIMPLE')) { + $s = str_replace(self::CASE_FOLD[0], self::CASE_FOLD[1], $s); + $needle = str_replace(self::CASE_FOLD[0], self::CASE_FOLD[1], $needle); + } + } + if ($reverse) { + $needlePos = strrpos($s, $needle); + } else { + $needlePos = strpos($s, $needle); + } + + return false !== $needlePos ? self::grapheme_strlen(substr($s, 0, $needlePos)) + $offset : false; + } +} diff --git a/vendor/symfony/polyfill-intl-grapheme/LICENSE b/vendor/symfony/polyfill-intl-grapheme/LICENSE new file mode 100644 index 00000000..6e3afce6 --- /dev/null +++ b/vendor/symfony/polyfill-intl-grapheme/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/polyfill-intl-grapheme/README.md b/vendor/symfony/polyfill-intl-grapheme/README.md new file mode 100644 index 00000000..f55d92c5 --- /dev/null +++ b/vendor/symfony/polyfill-intl-grapheme/README.md @@ -0,0 +1,31 @@ +Symfony Polyfill / Intl: Grapheme +================================= + +This component provides a partial, native PHP implementation of the +[Grapheme functions](https://php.net/intl.grapheme) from the +[Intl](https://php.net/intl) extension. + +- [`grapheme_extract`](https://php.net/grapheme_extract): Extract a sequence of grapheme + clusters from a text buffer, which must be encoded in UTF-8 +- [`grapheme_stripos`](https://php.net/grapheme_stripos): Find position (in grapheme units) + of first occurrence of a case-insensitive string +- [`grapheme_stristr`](https://php.net/grapheme_stristr): Returns part of haystack string + from the first occurrence of case-insensitive needle to the end of haystack +- [`grapheme_strlen`](https://php.net/grapheme_strlen): Get string length in grapheme units +- [`grapheme_strpos`](https://php.net/grapheme_strpos): Find position (in grapheme units) + of first occurrence of a string +- [`grapheme_strripos`](https://php.net/grapheme_strripos): Find position (in grapheme units) + of last occurrence of a case-insensitive string +- [`grapheme_strrpos`](https://php.net/grapheme_strrpos): Find position (in grapheme units) + of last occurrence of a string +- [`grapheme_strstr`](https://php.net/grapheme_strstr): Returns part of haystack string from + the first occurrence of needle to the end of haystack +- [`grapheme_substr`](https://php.net/grapheme_substr): Return part of a string + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-intl-grapheme/bootstrap.php b/vendor/symfony/polyfill-intl-grapheme/bootstrap.php new file mode 100644 index 00000000..a9ea03c7 --- /dev/null +++ b/vendor/symfony/polyfill-intl-grapheme/bootstrap.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Intl\Grapheme as p; + +if (extension_loaded('intl')) { + return; +} + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +if (!defined('GRAPHEME_EXTR_COUNT')) { + define('GRAPHEME_EXTR_COUNT', 0); +} +if (!defined('GRAPHEME_EXTR_MAXBYTES')) { + define('GRAPHEME_EXTR_MAXBYTES', 1); +} +if (!defined('GRAPHEME_EXTR_MAXCHARS')) { + define('GRAPHEME_EXTR_MAXCHARS', 2); +} + +if (!function_exists('grapheme_extract')) { + function grapheme_extract($haystack, $size, $type = 0, $start = 0, &$next = 0) { return p\Grapheme::grapheme_extract($haystack, $size, $type, $start, $next); } +} +if (!function_exists('grapheme_stripos')) { + function grapheme_stripos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_stripos($haystack, $needle, $offset); } +} +if (!function_exists('grapheme_stristr')) { + function grapheme_stristr($haystack, $needle, $beforeNeedle = false) { return p\Grapheme::grapheme_stristr($haystack, $needle, $beforeNeedle); } +} +if (!function_exists('grapheme_strlen')) { + function grapheme_strlen($input) { return p\Grapheme::grapheme_strlen($input); } +} +if (!function_exists('grapheme_strpos')) { + function grapheme_strpos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_strpos($haystack, $needle, $offset); } +} +if (!function_exists('grapheme_strripos')) { + function grapheme_strripos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_strripos($haystack, $needle, $offset); } +} +if (!function_exists('grapheme_strrpos')) { + function grapheme_strrpos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_strrpos($haystack, $needle, $offset); } +} +if (!function_exists('grapheme_strstr')) { + function grapheme_strstr($haystack, $needle, $beforeNeedle = false) { return p\Grapheme::grapheme_strstr($haystack, $needle, $beforeNeedle); } +} +if (!function_exists('grapheme_substr')) { + function grapheme_substr($string, $offset, $length = null) { return p\Grapheme::grapheme_substr($string, $offset, $length); } +} diff --git a/vendor/symfony/polyfill-intl-grapheme/bootstrap80.php b/vendor/symfony/polyfill-intl-grapheme/bootstrap80.php new file mode 100644 index 00000000..b8c07867 --- /dev/null +++ b/vendor/symfony/polyfill-intl-grapheme/bootstrap80.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Intl\Grapheme as p; + +if (!defined('GRAPHEME_EXTR_COUNT')) { + define('GRAPHEME_EXTR_COUNT', 0); +} +if (!defined('GRAPHEME_EXTR_MAXBYTES')) { + define('GRAPHEME_EXTR_MAXBYTES', 1); +} +if (!defined('GRAPHEME_EXTR_MAXCHARS')) { + define('GRAPHEME_EXTR_MAXCHARS', 2); +} + +if (!function_exists('grapheme_extract')) { + function grapheme_extract(?string $haystack, ?int $size, ?int $type = GRAPHEME_EXTR_COUNT, ?int $offset = 0, &$next = null): string|false { return p\Grapheme::grapheme_extract((string) $haystack, (int) $size, (int) $type, (int) $offset, $next); } +} +if (!function_exists('grapheme_stripos')) { + function grapheme_stripos(?string $haystack, ?string $needle, ?int $offset = 0): int|false { return p\Grapheme::grapheme_stripos((string) $haystack, (string) $needle, (int) $offset); } +} +if (!function_exists('grapheme_stristr')) { + function grapheme_stristr(?string $haystack, ?string $needle, ?bool $beforeNeedle = false): string|false { return p\Grapheme::grapheme_stristr((string) $haystack, (string) $needle, (bool) $beforeNeedle); } +} +if (!function_exists('grapheme_strlen')) { + function grapheme_strlen(?string $string): int|false|null { return p\Grapheme::grapheme_strlen((string) $string); } +} +if (!function_exists('grapheme_strpos')) { + function grapheme_strpos(?string $haystack, ?string $needle, ?int $offset = 0): int|false { return p\Grapheme::grapheme_strpos((string) $haystack, (string) $needle, (int) $offset); } +} +if (!function_exists('grapheme_strripos')) { + function grapheme_strripos(?string $haystack, ?string $needle, ?int $offset = 0): int|false { return p\Grapheme::grapheme_strripos((string) $haystack, (string) $needle, (int) $offset); } +} +if (!function_exists('grapheme_strrpos')) { + function grapheme_strrpos(?string $haystack, ?string $needle, ?int $offset = 0): int|false { return p\Grapheme::grapheme_strrpos((string) $haystack, (string) $needle, (int) $offset); } +} +if (!function_exists('grapheme_strstr')) { + function grapheme_strstr(?string $haystack, ?string $needle, ?bool $beforeNeedle = false): string|false { return p\Grapheme::grapheme_strstr((string) $haystack, (string) $needle, (bool) $beforeNeedle); } +} +if (!function_exists('grapheme_substr')) { + function grapheme_substr(?string $string, ?int $offset, ?int $length = null): string|false { return p\Grapheme::grapheme_substr((string) $string, (int) $offset, $length); } +} diff --git a/vendor/symfony/polyfill-intl-grapheme/composer.json b/vendor/symfony/polyfill-intl-grapheme/composer.json new file mode 100644 index 00000000..a20d3faa --- /dev/null +++ b/vendor/symfony/polyfill-intl-grapheme/composer.json @@ -0,0 +1,35 @@ +{ + "name": "symfony/polyfill-intl-grapheme", + "type": "library", + "description": "Symfony polyfill for intl's grapheme_* functions", + "keywords": ["polyfill", "shim", "compatibility", "portable", "intl", "grapheme"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Intl\\Grapheme\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-intl": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/vendor/symfony/polyfill-intl-normalizer/LICENSE b/vendor/symfony/polyfill-intl-normalizer/LICENSE new file mode 100644 index 00000000..6e3afce6 --- /dev/null +++ b/vendor/symfony/polyfill-intl-normalizer/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/polyfill-intl-normalizer/Normalizer.php b/vendor/symfony/polyfill-intl-normalizer/Normalizer.php new file mode 100644 index 00000000..81704ab3 --- /dev/null +++ b/vendor/symfony/polyfill-intl-normalizer/Normalizer.php @@ -0,0 +1,310 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Normalizer; + +/** + * Normalizer is a PHP fallback implementation of the Normalizer class provided by the intl extension. + * + * It has been validated with Unicode 6.3 Normalization Conformance Test. + * See http://www.unicode.org/reports/tr15/ for detailed info about Unicode normalizations. + * + * @author Nicolas Grekas + * + * @internal + */ +class Normalizer +{ + public const FORM_D = \Normalizer::FORM_D; + public const FORM_KD = \Normalizer::FORM_KD; + public const FORM_C = \Normalizer::FORM_C; + public const FORM_KC = \Normalizer::FORM_KC; + public const NFD = \Normalizer::NFD; + public const NFKD = \Normalizer::NFKD; + public const NFC = \Normalizer::NFC; + public const NFKC = \Normalizer::NFKC; + + private static $C; + private static $D; + private static $KD; + private static $cC; + private static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; + private static $ASCII = "\x20\x65\x69\x61\x73\x6E\x74\x72\x6F\x6C\x75\x64\x5D\x5B\x63\x6D\x70\x27\x0A\x67\x7C\x68\x76\x2E\x66\x62\x2C\x3A\x3D\x2D\x71\x31\x30\x43\x32\x2A\x79\x78\x29\x28\x4C\x39\x41\x53\x2F\x50\x22\x45\x6A\x4D\x49\x6B\x33\x3E\x35\x54\x3C\x44\x34\x7D\x42\x7B\x38\x46\x77\x52\x36\x37\x55\x47\x4E\x3B\x4A\x7A\x56\x23\x48\x4F\x57\x5F\x26\x21\x4B\x3F\x58\x51\x25\x59\x5C\x09\x5A\x2B\x7E\x5E\x24\x40\x60\x7F\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"; + + public static function isNormalized(string $s, int $form = self::FORM_C) + { + if (!\in_array($form, [self::NFD, self::NFKD, self::NFC, self::NFKC])) { + return false; + } + if (!isset($s[strspn($s, self::$ASCII)])) { + return true; + } + if (self::NFC == $form && preg_match('//u', $s) && !preg_match('/[^\x00-\x{2FF}]/u', $s)) { + return true; + } + + return self::normalize($s, $form) === $s; + } + + public static function normalize(string $s, int $form = self::FORM_C) + { + if (!preg_match('//u', $s)) { + return false; + } + + switch ($form) { + case self::NFC: $C = true; $K = false; break; + case self::NFD: $C = false; $K = false; break; + case self::NFKC: $C = true; $K = true; break; + case self::NFKD: $C = false; $K = true; break; + default: + if (\defined('Normalizer::NONE') && \Normalizer::NONE == $form) { + return $s; + } + + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError('normalizer_normalize(): Argument #2 ($form) must be a a valid normalization form'); + } + + if ('' === $s) { + return ''; + } + + if ($K && null === self::$KD) { + self::$KD = self::getData('compatibilityDecomposition'); + } + + if (null === self::$D) { + self::$D = self::getData('canonicalDecomposition'); + self::$cC = self::getData('combiningClass'); + } + + if (null !== $mbEncoding = (2 /* MB_OVERLOAD_STRING */ & (int) \ini_get('mbstring.func_overload')) ? mb_internal_encoding() : null) { + mb_internal_encoding('8bit'); + } + + $r = self::decompose($s, $K); + + if ($C) { + if (null === self::$C) { + self::$C = self::getData('canonicalComposition'); + } + + $r = self::recompose($r); + } + if (null !== $mbEncoding) { + mb_internal_encoding($mbEncoding); + } + + return $r; + } + + private static function recompose($s) + { + $ASCII = self::$ASCII; + $compMap = self::$C; + $combClass = self::$cC; + $ulenMask = self::$ulenMask; + + $result = $tail = ''; + + $i = $s[0] < "\x80" ? 1 : $ulenMask[$s[0] & "\xF0"]; + $len = \strlen($s); + + $lastUchr = substr($s, 0, $i); + $lastUcls = isset($combClass[$lastUchr]) ? 256 : 0; + + while ($i < $len) { + if ($s[$i] < "\x80") { + // ASCII chars + + if ($tail) { + $lastUchr .= $tail; + $tail = ''; + } + + if ($j = strspn($s, $ASCII, $i + 1)) { + $lastUchr .= substr($s, $i, $j); + $i += $j; + } + + $result .= $lastUchr; + $lastUchr = $s[$i]; + $lastUcls = 0; + ++$i; + continue; + } + + $ulen = $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + + if ($lastUchr < "\xE1\x84\x80" || "\xE1\x84\x92" < $lastUchr + || $uchr < "\xE1\x85\xA1" || "\xE1\x85\xB5" < $uchr + || $lastUcls) { + // Table lookup and combining chars composition + + $ucls = $combClass[$uchr] ?? 0; + + if (isset($compMap[$lastUchr.$uchr]) && (!$lastUcls || $lastUcls < $ucls)) { + $lastUchr = $compMap[$lastUchr.$uchr]; + } elseif ($lastUcls = $ucls) { + $tail .= $uchr; + } else { + if ($tail) { + $lastUchr .= $tail; + $tail = ''; + } + + $result .= $lastUchr; + $lastUchr = $uchr; + } + } else { + // Hangul chars + + $L = \ord($lastUchr[2]) - 0x80; + $V = \ord($uchr[2]) - 0xA1; + $T = 0; + + $uchr = substr($s, $i + $ulen, 3); + + if ("\xE1\x86\xA7" <= $uchr && $uchr <= "\xE1\x87\x82") { + $T = \ord($uchr[2]) - 0xA7; + 0 > $T && $T += 0x40; + $ulen += 3; + } + + $L = 0xAC00 + ($L * 21 + $V) * 28 + $T; + $lastUchr = \chr(0xE0 | $L >> 12).\chr(0x80 | $L >> 6 & 0x3F).\chr(0x80 | $L & 0x3F); + } + + $i += $ulen; + } + + return $result.$lastUchr.$tail; + } + + private static function decompose($s, $c) + { + $result = ''; + + $ASCII = self::$ASCII; + $decompMap = self::$D; + $combClass = self::$cC; + $ulenMask = self::$ulenMask; + if ($c) { + $compatMap = self::$KD; + } + + $c = []; + $i = 0; + $len = \strlen($s); + + while ($i < $len) { + if ($s[$i] < "\x80") { + // ASCII chars + + if ($c) { + ksort($c); + $result .= implode('', $c); + $c = []; + } + + $j = 1 + strspn($s, $ASCII, $i + 1); + $result .= substr($s, $i, $j); + $i += $j; + continue; + } + + $ulen = $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + + if ($uchr < "\xEA\xB0\x80" || "\xED\x9E\xA3" < $uchr) { + // Table lookup + + if ($uchr !== $j = $compatMap[$uchr] ?? ($decompMap[$uchr] ?? $uchr)) { + $uchr = $j; + + $j = \strlen($uchr); + $ulen = $uchr[0] < "\x80" ? 1 : $ulenMask[$uchr[0] & "\xF0"]; + + if ($ulen != $j) { + // Put trailing chars in $s + + $j -= $ulen; + $i -= $j; + + if (0 > $i) { + $s = str_repeat(' ', -$i).$s; + $len -= $i; + $i = 0; + } + + while ($j--) { + $s[$i + $j] = $uchr[$ulen + $j]; + } + + $uchr = substr($uchr, 0, $ulen); + } + } + if (isset($combClass[$uchr])) { + // Combining chars, for sorting + + if (!isset($c[$combClass[$uchr]])) { + $c[$combClass[$uchr]] = ''; + } + $c[$combClass[$uchr]] .= $uchr; + continue; + } + } else { + // Hangul chars + + $uchr = unpack('C*', $uchr); + $j = (($uchr[1] - 224) << 12) + (($uchr[2] - 128) << 6) + $uchr[3] - 0xAC80; + + $uchr = "\xE1\x84".\chr(0x80 + (int) ($j / 588)) + ."\xE1\x85".\chr(0xA1 + (int) (($j % 588) / 28)); + + if ($j %= 28) { + $uchr .= $j < 25 + ? ("\xE1\x86".\chr(0xA7 + $j)) + : ("\xE1\x87".\chr(0x67 + $j)); + } + } + if ($c) { + ksort($c); + $result .= implode('', $c); + $c = []; + } + + $result .= $uchr; + } + + if ($c) { + ksort($c); + $result .= implode('', $c); + } + + return $result; + } + + private static function getData($file) + { + if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) { + return require $file; + } + + return false; + } +} diff --git a/vendor/symfony/polyfill-intl-normalizer/README.md b/vendor/symfony/polyfill-intl-normalizer/README.md new file mode 100644 index 00000000..b9b762e8 --- /dev/null +++ b/vendor/symfony/polyfill-intl-normalizer/README.md @@ -0,0 +1,14 @@ +Symfony Polyfill / Intl: Normalizer +=================================== + +This component provides a fallback implementation for the +[`Normalizer`](https://php.net/Normalizer) class provided +by the [Intl](https://php.net/intl) extension. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php b/vendor/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php new file mode 100644 index 00000000..0fdfc890 --- /dev/null +++ b/vendor/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php @@ -0,0 +1,17 @@ + 'À', + 'Á' => 'Á', + 'Â' => 'Â', + 'Ã' => 'Ã', + 'Ä' => 'Ä', + 'Å' => 'Å', + 'Ç' => 'Ç', + 'È' => 'È', + 'É' => 'É', + 'Ê' => 'Ê', + 'Ë' => 'Ë', + 'Ì' => 'Ì', + 'Í' => 'Í', + 'Î' => 'Î', + 'Ï' => 'Ï', + 'Ñ' => 'Ñ', + 'Ò' => 'Ò', + 'Ó' => 'Ó', + 'Ô' => 'Ô', + 'Õ' => 'Õ', + 'Ö' => 'Ö', + 'Ù' => 'Ù', + 'Ú' => 'Ú', + 'Û' => 'Û', + 'Ü' => 'Ü', + 'Ý' => 'Ý', + 'à' => 'à', + 'á' => 'á', + 'â' => 'â', + 'ã' => 'ã', + 'ä' => 'ä', + 'å' => 'å', + 'ç' => 'ç', + 'è' => 'è', + 'é' => 'é', + 'ê' => 'ê', + 'ë' => 'ë', + 'ì' => 'ì', + 'í' => 'í', + 'î' => 'î', + 'ï' => 'ï', + 'ñ' => 'ñ', + 'ò' => 'ò', + 'ó' => 'ó', + 'ô' => 'ô', + 'õ' => 'õ', + 'ö' => 'ö', + 'ù' => 'ù', + 'ú' => 'ú', + 'û' => 'û', + 'ü' => 'ü', + 'ý' => 'ý', + 'ÿ' => 'ÿ', + 'Ā' => 'Ā', + 'ā' => 'ā', + 'Ă' => 'Ă', + 'ă' => 'ă', + 'Ą' => 'Ą', + 'ą' => 'ą', + 'Ć' => 'Ć', + 'ć' => 'ć', + 'Ĉ' => 'Ĉ', + 'ĉ' => 'ĉ', + 'Ċ' => 'Ċ', + 'ċ' => 'ċ', + 'Č' => 'Č', + 'č' => 'č', + 'Ď' => 'Ď', + 'ď' => 'ď', + 'Ē' => 'Ē', + 'ē' => 'ē', + 'Ĕ' => 'Ĕ', + 'ĕ' => 'ĕ', + 'Ė' => 'Ė', + 'ė' => 'ė', + 'Ę' => 'Ę', + 'ę' => 'ę', + 'Ě' => 'Ě', + 'ě' => 'ě', + 'Ĝ' => 'Ĝ', + 'ĝ' => 'ĝ', + 'Ğ' => 'Ğ', + 'ğ' => 'ğ', + 'Ġ' => 'Ġ', + 'ġ' => 'ġ', + 'Ģ' => 'Ģ', + 'ģ' => 'ģ', + 'Ĥ' => 'Ĥ', + 'ĥ' => 'ĥ', + 'Ĩ' => 'Ĩ', + 'ĩ' => 'ĩ', + 'Ī' => 'Ī', + 'ī' => 'ī', + 'Ĭ' => 'Ĭ', + 'ĭ' => 'ĭ', + 'Į' => 'Į', + 'į' => 'į', + 'İ' => 'İ', + 'Ĵ' => 'Ĵ', + 'ĵ' => 'ĵ', + 'Ķ' => 'Ķ', + 'ķ' => 'ķ', + 'Ĺ' => 'Ĺ', + 'ĺ' => 'ĺ', + 'Ļ' => 'Ļ', + 'ļ' => 'ļ', + 'Ľ' => 'Ľ', + 'ľ' => 'ľ', + 'Ń' => 'Ń', + 'ń' => 'ń', + 'Ņ' => 'Ņ', + 'ņ' => 'ņ', + 'Ň' => 'Ň', + 'ň' => 'ň', + 'Ō' => 'Ō', + 'ō' => 'ō', + 'Ŏ' => 'Ŏ', + 'ŏ' => 'ŏ', + 'Ő' => 'Ő', + 'ő' => 'ő', + 'Ŕ' => 'Ŕ', + 'ŕ' => 'ŕ', + 'Ŗ' => 'Ŗ', + 'ŗ' => 'ŗ', + 'Ř' => 'Ř', + 'ř' => 'ř', + 'Ś' => 'Ś', + 'ś' => 'ś', + 'Ŝ' => 'Ŝ', + 'ŝ' => 'ŝ', + 'Ş' => 'Ş', + 'ş' => 'ş', + 'Š' => 'Š', + 'š' => 'š', + 'Ţ' => 'Ţ', + 'ţ' => 'ţ', + 'Ť' => 'Ť', + 'ť' => 'ť', + 'Ũ' => 'Ũ', + 'ũ' => 'ũ', + 'Ū' => 'Ū', + 'ū' => 'ū', + 'Ŭ' => 'Ŭ', + 'ŭ' => 'ŭ', + 'Ů' => 'Ů', + 'ů' => 'ů', + 'Ű' => 'Ű', + 'ű' => 'ű', + 'Ų' => 'Ų', + 'ų' => 'ų', + 'Ŵ' => 'Ŵ', + 'ŵ' => 'ŵ', + 'Ŷ' => 'Ŷ', + 'ŷ' => 'ŷ', + 'Ÿ' => 'Ÿ', + 'Ź' => 'Ź', + 'ź' => 'ź', + 'Ż' => 'Ż', + 'ż' => 'ż', + 'Ž' => 'Ž', + 'ž' => 'ž', + 'Ơ' => 'Ơ', + 'ơ' => 'ơ', + 'Ư' => 'Ư', + 'ư' => 'ư', + 'Ǎ' => 'Ǎ', + 'ǎ' => 'ǎ', + 'Ǐ' => 'Ǐ', + 'ǐ' => 'ǐ', + 'Ǒ' => 'Ǒ', + 'ǒ' => 'ǒ', + 'Ǔ' => 'Ǔ', + 'ǔ' => 'ǔ', + 'Ǖ' => 'Ǖ', + 'ǖ' => 'ǖ', + 'Ǘ' => 'Ǘ', + 'ǘ' => 'ǘ', + 'Ǚ' => 'Ǚ', + 'ǚ' => 'ǚ', + 'Ǜ' => 'Ǜ', + 'ǜ' => 'ǜ', + 'Ǟ' => 'Ǟ', + 'ǟ' => 'ǟ', + 'Ǡ' => 'Ǡ', + 'ǡ' => 'ǡ', + 'Ǣ' => 'Ǣ', + 'ǣ' => 'ǣ', + 'Ǧ' => 'Ǧ', + 'ǧ' => 'ǧ', + 'Ǩ' => 'Ǩ', + 'ǩ' => 'ǩ', + 'Ǫ' => 'Ǫ', + 'ǫ' => 'ǫ', + 'Ǭ' => 'Ǭ', + 'ǭ' => 'ǭ', + 'Ǯ' => 'Ǯ', + 'ǯ' => 'ǯ', + 'ǰ' => 'ǰ', + 'Ǵ' => 'Ǵ', + 'ǵ' => 'ǵ', + 'Ǹ' => 'Ǹ', + 'ǹ' => 'ǹ', + 'Ǻ' => 'Ǻ', + 'ǻ' => 'ǻ', + 'Ǽ' => 'Ǽ', + 'ǽ' => 'ǽ', + 'Ǿ' => 'Ǿ', + 'ǿ' => 'ǿ', + 'Ȁ' => 'Ȁ', + 'ȁ' => 'ȁ', + 'Ȃ' => 'Ȃ', + 'ȃ' => 'ȃ', + 'Ȅ' => 'Ȅ', + 'ȅ' => 'ȅ', + 'Ȇ' => 'Ȇ', + 'ȇ' => 'ȇ', + 'Ȉ' => 'Ȉ', + 'ȉ' => 'ȉ', + 'Ȋ' => 'Ȋ', + 'ȋ' => 'ȋ', + 'Ȍ' => 'Ȍ', + 'ȍ' => 'ȍ', + 'Ȏ' => 'Ȏ', + 'ȏ' => 'ȏ', + 'Ȑ' => 'Ȑ', + 'ȑ' => 'ȑ', + 'Ȓ' => 'Ȓ', + 'ȓ' => 'ȓ', + 'Ȕ' => 'Ȕ', + 'ȕ' => 'ȕ', + 'Ȗ' => 'Ȗ', + 'ȗ' => 'ȗ', + 'Ș' => 'Ș', + 'ș' => 'ș', + 'Ț' => 'Ț', + 'ț' => 'ț', + 'Ȟ' => 'Ȟ', + 'ȟ' => 'ȟ', + 'Ȧ' => 'Ȧ', + 'ȧ' => 'ȧ', + 'Ȩ' => 'Ȩ', + 'ȩ' => 'ȩ', + 'Ȫ' => 'Ȫ', + 'ȫ' => 'ȫ', + 'Ȭ' => 'Ȭ', + 'ȭ' => 'ȭ', + 'Ȯ' => 'Ȯ', + 'ȯ' => 'ȯ', + 'Ȱ' => 'Ȱ', + 'ȱ' => 'ȱ', + 'Ȳ' => 'Ȳ', + 'ȳ' => 'ȳ', + '΅' => '΅', + 'Ά' => 'Ά', + 'Έ' => 'Έ', + 'Ή' => 'Ή', + 'Ί' => 'Ί', + 'Ό' => 'Ό', + 'Ύ' => 'Ύ', + 'Ώ' => 'Ώ', + 'ΐ' => 'ΐ', + 'Ϊ' => 'Ϊ', + 'Ϋ' => 'Ϋ', + 'ά' => 'ά', + 'έ' => 'έ', + 'ή' => 'ή', + 'ί' => 'ί', + 'ΰ' => 'ΰ', + 'ϊ' => 'ϊ', + 'ϋ' => 'ϋ', + 'ό' => 'ό', + 'ύ' => 'ύ', + 'ώ' => 'ώ', + 'ϓ' => 'ϓ', + 'ϔ' => 'ϔ', + 'Ѐ' => 'Ѐ', + 'Ё' => 'Ё', + 'Ѓ' => 'Ѓ', + 'Ї' => 'Ї', + 'Ќ' => 'Ќ', + 'Ѝ' => 'Ѝ', + 'Ў' => 'Ў', + 'Й' => 'Й', + 'й' => 'й', + 'ѐ' => 'ѐ', + 'ё' => 'ё', + 'ѓ' => 'ѓ', + 'ї' => 'ї', + 'ќ' => 'ќ', + 'ѝ' => 'ѝ', + 'ў' => 'ў', + 'Ѷ' => 'Ѷ', + 'ѷ' => 'ѷ', + 'Ӂ' => 'Ӂ', + 'ӂ' => 'ӂ', + 'Ӑ' => 'Ӑ', + 'ӑ' => 'ӑ', + 'Ӓ' => 'Ӓ', + 'ӓ' => 'ӓ', + 'Ӗ' => 'Ӗ', + 'ӗ' => 'ӗ', + 'Ӛ' => 'Ӛ', + 'ӛ' => 'ӛ', + 'Ӝ' => 'Ӝ', + 'ӝ' => 'ӝ', + 'Ӟ' => 'Ӟ', + 'ӟ' => 'ӟ', + 'Ӣ' => 'Ӣ', + 'ӣ' => 'ӣ', + 'Ӥ' => 'Ӥ', + 'ӥ' => 'ӥ', + 'Ӧ' => 'Ӧ', + 'ӧ' => 'ӧ', + 'Ӫ' => 'Ӫ', + 'ӫ' => 'ӫ', + 'Ӭ' => 'Ӭ', + 'ӭ' => 'ӭ', + 'Ӯ' => 'Ӯ', + 'ӯ' => 'ӯ', + 'Ӱ' => 'Ӱ', + 'ӱ' => 'ӱ', + 'Ӳ' => 'Ӳ', + 'ӳ' => 'ӳ', + 'Ӵ' => 'Ӵ', + 'ӵ' => 'ӵ', + 'Ӹ' => 'Ӹ', + 'ӹ' => 'ӹ', + 'آ' => 'آ', + 'أ' => 'أ', + 'ؤ' => 'ؤ', + 'إ' => 'إ', + 'ئ' => 'ئ', + 'ۀ' => 'ۀ', + 'ۂ' => 'ۂ', + 'ۓ' => 'ۓ', + 'ऩ' => 'ऩ', + 'ऱ' => 'ऱ', + 'ऴ' => 'ऴ', + 'ো' => 'ো', + 'ৌ' => 'ৌ', + 'ୈ' => 'ୈ', + 'ୋ' => 'ୋ', + 'ୌ' => 'ୌ', + 'ஔ' => 'ஔ', + 'ொ' => 'ொ', + 'ோ' => 'ோ', + 'ௌ' => 'ௌ', + 'ై' => 'ై', + 'ೀ' => 'ೀ', + 'ೇ' => 'ೇ', + 'ೈ' => 'ೈ', + 'ೊ' => 'ೊ', + 'ೋ' => 'ೋ', + 'ൊ' => 'ൊ', + 'ോ' => 'ോ', + 'ൌ' => 'ൌ', + 'ේ' => 'ේ', + 'ො' => 'ො', + 'ෝ' => 'ෝ', + 'ෞ' => 'ෞ', + 'ဦ' => 'ဦ', + 'ᬆ' => 'ᬆ', + 'ᬈ' => 'ᬈ', + 'ᬊ' => 'ᬊ', + 'ᬌ' => 'ᬌ', + 'ᬎ' => 'ᬎ', + 'ᬒ' => 'ᬒ', + 'ᬻ' => 'ᬻ', + 'ᬽ' => 'ᬽ', + 'ᭀ' => 'ᭀ', + 'ᭁ' => 'ᭁ', + 'ᭃ' => 'ᭃ', + 'Ḁ' => 'Ḁ', + 'ḁ' => 'ḁ', + 'Ḃ' => 'Ḃ', + 'ḃ' => 'ḃ', + 'Ḅ' => 'Ḅ', + 'ḅ' => 'ḅ', + 'Ḇ' => 'Ḇ', + 'ḇ' => 'ḇ', + 'Ḉ' => 'Ḉ', + 'ḉ' => 'ḉ', + 'Ḋ' => 'Ḋ', + 'ḋ' => 'ḋ', + 'Ḍ' => 'Ḍ', + 'ḍ' => 'ḍ', + 'Ḏ' => 'Ḏ', + 'ḏ' => 'ḏ', + 'Ḑ' => 'Ḑ', + 'ḑ' => 'ḑ', + 'Ḓ' => 'Ḓ', + 'ḓ' => 'ḓ', + 'Ḕ' => 'Ḕ', + 'ḕ' => 'ḕ', + 'Ḗ' => 'Ḗ', + 'ḗ' => 'ḗ', + 'Ḙ' => 'Ḙ', + 'ḙ' => 'ḙ', + 'Ḛ' => 'Ḛ', + 'ḛ' => 'ḛ', + 'Ḝ' => 'Ḝ', + 'ḝ' => 'ḝ', + 'Ḟ' => 'Ḟ', + 'ḟ' => 'ḟ', + 'Ḡ' => 'Ḡ', + 'ḡ' => 'ḡ', + 'Ḣ' => 'Ḣ', + 'ḣ' => 'ḣ', + 'Ḥ' => 'Ḥ', + 'ḥ' => 'ḥ', + 'Ḧ' => 'Ḧ', + 'ḧ' => 'ḧ', + 'Ḩ' => 'Ḩ', + 'ḩ' => 'ḩ', + 'Ḫ' => 'Ḫ', + 'ḫ' => 'ḫ', + 'Ḭ' => 'Ḭ', + 'ḭ' => 'ḭ', + 'Ḯ' => 'Ḯ', + 'ḯ' => 'ḯ', + 'Ḱ' => 'Ḱ', + 'ḱ' => 'ḱ', + 'Ḳ' => 'Ḳ', + 'ḳ' => 'ḳ', + 'Ḵ' => 'Ḵ', + 'ḵ' => 'ḵ', + 'Ḷ' => 'Ḷ', + 'ḷ' => 'ḷ', + 'Ḹ' => 'Ḹ', + 'ḹ' => 'ḹ', + 'Ḻ' => 'Ḻ', + 'ḻ' => 'ḻ', + 'Ḽ' => 'Ḽ', + 'ḽ' => 'ḽ', + 'Ḿ' => 'Ḿ', + 'ḿ' => 'ḿ', + 'Ṁ' => 'Ṁ', + 'ṁ' => 'ṁ', + 'Ṃ' => 'Ṃ', + 'ṃ' => 'ṃ', + 'Ṅ' => 'Ṅ', + 'ṅ' => 'ṅ', + 'Ṇ' => 'Ṇ', + 'ṇ' => 'ṇ', + 'Ṉ' => 'Ṉ', + 'ṉ' => 'ṉ', + 'Ṋ' => 'Ṋ', + 'ṋ' => 'ṋ', + 'Ṍ' => 'Ṍ', + 'ṍ' => 'ṍ', + 'Ṏ' => 'Ṏ', + 'ṏ' => 'ṏ', + 'Ṑ' => 'Ṑ', + 'ṑ' => 'ṑ', + 'Ṓ' => 'Ṓ', + 'ṓ' => 'ṓ', + 'Ṕ' => 'Ṕ', + 'ṕ' => 'ṕ', + 'Ṗ' => 'Ṗ', + 'ṗ' => 'ṗ', + 'Ṙ' => 'Ṙ', + 'ṙ' => 'ṙ', + 'Ṛ' => 'Ṛ', + 'ṛ' => 'ṛ', + 'Ṝ' => 'Ṝ', + 'ṝ' => 'ṝ', + 'Ṟ' => 'Ṟ', + 'ṟ' => 'ṟ', + 'Ṡ' => 'Ṡ', + 'ṡ' => 'ṡ', + 'Ṣ' => 'Ṣ', + 'ṣ' => 'ṣ', + 'Ṥ' => 'Ṥ', + 'ṥ' => 'ṥ', + 'Ṧ' => 'Ṧ', + 'ṧ' => 'ṧ', + 'Ṩ' => 'Ṩ', + 'ṩ' => 'ṩ', + 'Ṫ' => 'Ṫ', + 'ṫ' => 'ṫ', + 'Ṭ' => 'Ṭ', + 'ṭ' => 'ṭ', + 'Ṯ' => 'Ṯ', + 'ṯ' => 'ṯ', + 'Ṱ' => 'Ṱ', + 'ṱ' => 'ṱ', + 'Ṳ' => 'Ṳ', + 'ṳ' => 'ṳ', + 'Ṵ' => 'Ṵ', + 'ṵ' => 'ṵ', + 'Ṷ' => 'Ṷ', + 'ṷ' => 'ṷ', + 'Ṹ' => 'Ṹ', + 'ṹ' => 'ṹ', + 'Ṻ' => 'Ṻ', + 'ṻ' => 'ṻ', + 'Ṽ' => 'Ṽ', + 'ṽ' => 'ṽ', + 'Ṿ' => 'Ṿ', + 'ṿ' => 'ṿ', + 'Ẁ' => 'Ẁ', + 'ẁ' => 'ẁ', + 'Ẃ' => 'Ẃ', + 'ẃ' => 'ẃ', + 'Ẅ' => 'Ẅ', + 'ẅ' => 'ẅ', + 'Ẇ' => 'Ẇ', + 'ẇ' => 'ẇ', + 'Ẉ' => 'Ẉ', + 'ẉ' => 'ẉ', + 'Ẋ' => 'Ẋ', + 'ẋ' => 'ẋ', + 'Ẍ' => 'Ẍ', + 'ẍ' => 'ẍ', + 'Ẏ' => 'Ẏ', + 'ẏ' => 'ẏ', + 'Ẑ' => 'Ẑ', + 'ẑ' => 'ẑ', + 'Ẓ' => 'Ẓ', + 'ẓ' => 'ẓ', + 'Ẕ' => 'Ẕ', + 'ẕ' => 'ẕ', + 'ẖ' => 'ẖ', + 'ẗ' => 'ẗ', + 'ẘ' => 'ẘ', + 'ẙ' => 'ẙ', + 'ẛ' => 'ẛ', + 'Ạ' => 'Ạ', + 'ạ' => 'ạ', + 'Ả' => 'Ả', + 'ả' => 'ả', + 'Ấ' => 'Ấ', + 'ấ' => 'ấ', + 'Ầ' => 'Ầ', + 'ầ' => 'ầ', + 'Ẩ' => 'Ẩ', + 'ẩ' => 'ẩ', + 'Ẫ' => 'Ẫ', + 'ẫ' => 'ẫ', + 'Ậ' => 'Ậ', + 'ậ' => 'ậ', + 'Ắ' => 'Ắ', + 'ắ' => 'ắ', + 'Ằ' => 'Ằ', + 'ằ' => 'ằ', + 'Ẳ' => 'Ẳ', + 'ẳ' => 'ẳ', + 'Ẵ' => 'Ẵ', + 'ẵ' => 'ẵ', + 'Ặ' => 'Ặ', + 'ặ' => 'ặ', + 'Ẹ' => 'Ẹ', + 'ẹ' => 'ẹ', + 'Ẻ' => 'Ẻ', + 'ẻ' => 'ẻ', + 'Ẽ' => 'Ẽ', + 'ẽ' => 'ẽ', + 'Ế' => 'Ế', + 'ế' => 'ế', + 'Ề' => 'Ề', + 'ề' => 'ề', + 'Ể' => 'Ể', + 'ể' => 'ể', + 'Ễ' => 'Ễ', + 'ễ' => 'ễ', + 'Ệ' => 'Ệ', + 'ệ' => 'ệ', + 'Ỉ' => 'Ỉ', + 'ỉ' => 'ỉ', + 'Ị' => 'Ị', + 'ị' => 'ị', + 'Ọ' => 'Ọ', + 'ọ' => 'ọ', + 'Ỏ' => 'Ỏ', + 'ỏ' => 'ỏ', + 'Ố' => 'Ố', + 'ố' => 'ố', + 'Ồ' => 'Ồ', + 'ồ' => 'ồ', + 'Ổ' => 'Ổ', + 'ổ' => 'ổ', + 'Ỗ' => 'Ỗ', + 'ỗ' => 'ỗ', + 'Ộ' => 'Ộ', + 'ộ' => 'ộ', + 'Ớ' => 'Ớ', + 'ớ' => 'ớ', + 'Ờ' => 'Ờ', + 'ờ' => 'ờ', + 'Ở' => 'Ở', + 'ở' => 'ở', + 'Ỡ' => 'Ỡ', + 'ỡ' => 'ỡ', + 'Ợ' => 'Ợ', + 'ợ' => 'ợ', + 'Ụ' => 'Ụ', + 'ụ' => 'ụ', + 'Ủ' => 'Ủ', + 'ủ' => 'ủ', + 'Ứ' => 'Ứ', + 'ứ' => 'ứ', + 'Ừ' => 'Ừ', + 'ừ' => 'ừ', + 'Ử' => 'Ử', + 'ử' => 'ử', + 'Ữ' => 'Ữ', + 'ữ' => 'ữ', + 'Ự' => 'Ự', + 'ự' => 'ự', + 'Ỳ' => 'Ỳ', + 'ỳ' => 'ỳ', + 'Ỵ' => 'Ỵ', + 'ỵ' => 'ỵ', + 'Ỷ' => 'Ỷ', + 'ỷ' => 'ỷ', + 'Ỹ' => 'Ỹ', + 'ỹ' => 'ỹ', + 'ἀ' => 'ἀ', + 'ἁ' => 'ἁ', + 'ἂ' => 'ἂ', + 'ἃ' => 'ἃ', + 'ἄ' => 'ἄ', + 'ἅ' => 'ἅ', + 'ἆ' => 'ἆ', + 'ἇ' => 'ἇ', + 'Ἀ' => 'Ἀ', + 'Ἁ' => 'Ἁ', + 'Ἂ' => 'Ἂ', + 'Ἃ' => 'Ἃ', + 'Ἄ' => 'Ἄ', + 'Ἅ' => 'Ἅ', + 'Ἆ' => 'Ἆ', + 'Ἇ' => 'Ἇ', + 'ἐ' => 'ἐ', + 'ἑ' => 'ἑ', + 'ἒ' => 'ἒ', + 'ἓ' => 'ἓ', + 'ἔ' => 'ἔ', + 'ἕ' => 'ἕ', + 'Ἐ' => 'Ἐ', + 'Ἑ' => 'Ἑ', + 'Ἒ' => 'Ἒ', + 'Ἓ' => 'Ἓ', + 'Ἔ' => 'Ἔ', + 'Ἕ' => 'Ἕ', + 'ἠ' => 'ἠ', + 'ἡ' => 'ἡ', + 'ἢ' => 'ἢ', + 'ἣ' => 'ἣ', + 'ἤ' => 'ἤ', + 'ἥ' => 'ἥ', + 'ἦ' => 'ἦ', + 'ἧ' => 'ἧ', + 'Ἠ' => 'Ἠ', + 'Ἡ' => 'Ἡ', + 'Ἢ' => 'Ἢ', + 'Ἣ' => 'Ἣ', + 'Ἤ' => 'Ἤ', + 'Ἥ' => 'Ἥ', + 'Ἦ' => 'Ἦ', + 'Ἧ' => 'Ἧ', + 'ἰ' => 'ἰ', + 'ἱ' => 'ἱ', + 'ἲ' => 'ἲ', + 'ἳ' => 'ἳ', + 'ἴ' => 'ἴ', + 'ἵ' => 'ἵ', + 'ἶ' => 'ἶ', + 'ἷ' => 'ἷ', + 'Ἰ' => 'Ἰ', + 'Ἱ' => 'Ἱ', + 'Ἲ' => 'Ἲ', + 'Ἳ' => 'Ἳ', + 'Ἴ' => 'Ἴ', + 'Ἵ' => 'Ἵ', + 'Ἶ' => 'Ἶ', + 'Ἷ' => 'Ἷ', + 'ὀ' => 'ὀ', + 'ὁ' => 'ὁ', + 'ὂ' => 'ὂ', + 'ὃ' => 'ὃ', + 'ὄ' => 'ὄ', + 'ὅ' => 'ὅ', + 'Ὀ' => 'Ὀ', + 'Ὁ' => 'Ὁ', + 'Ὂ' => 'Ὂ', + 'Ὃ' => 'Ὃ', + 'Ὄ' => 'Ὄ', + 'Ὅ' => 'Ὅ', + 'ὐ' => 'ὐ', + 'ὑ' => 'ὑ', + 'ὒ' => 'ὒ', + 'ὓ' => 'ὓ', + 'ὔ' => 'ὔ', + 'ὕ' => 'ὕ', + 'ὖ' => 'ὖ', + 'ὗ' => 'ὗ', + 'Ὑ' => 'Ὑ', + 'Ὓ' => 'Ὓ', + 'Ὕ' => 'Ὕ', + 'Ὗ' => 'Ὗ', + 'ὠ' => 'ὠ', + 'ὡ' => 'ὡ', + 'ὢ' => 'ὢ', + 'ὣ' => 'ὣ', + 'ὤ' => 'ὤ', + 'ὥ' => 'ὥ', + 'ὦ' => 'ὦ', + 'ὧ' => 'ὧ', + 'Ὠ' => 'Ὠ', + 'Ὡ' => 'Ὡ', + 'Ὢ' => 'Ὢ', + 'Ὣ' => 'Ὣ', + 'Ὤ' => 'Ὤ', + 'Ὥ' => 'Ὥ', + 'Ὦ' => 'Ὦ', + 'Ὧ' => 'Ὧ', + 'ὰ' => 'ὰ', + 'ὲ' => 'ὲ', + 'ὴ' => 'ὴ', + 'ὶ' => 'ὶ', + 'ὸ' => 'ὸ', + 'ὺ' => 'ὺ', + 'ὼ' => 'ὼ', + 'ᾀ' => 'ᾀ', + 'ᾁ' => 'ᾁ', + 'ᾂ' => 'ᾂ', + 'ᾃ' => 'ᾃ', + 'ᾄ' => 'ᾄ', + 'ᾅ' => 'ᾅ', + 'ᾆ' => 'ᾆ', + 'ᾇ' => 'ᾇ', + 'ᾈ' => 'ᾈ', + 'ᾉ' => 'ᾉ', + 'ᾊ' => 'ᾊ', + 'ᾋ' => 'ᾋ', + 'ᾌ' => 'ᾌ', + 'ᾍ' => 'ᾍ', + 'ᾎ' => 'ᾎ', + 'ᾏ' => 'ᾏ', + 'ᾐ' => 'ᾐ', + 'ᾑ' => 'ᾑ', + 'ᾒ' => 'ᾒ', + 'ᾓ' => 'ᾓ', + 'ᾔ' => 'ᾔ', + 'ᾕ' => 'ᾕ', + 'ᾖ' => 'ᾖ', + 'ᾗ' => 'ᾗ', + 'ᾘ' => 'ᾘ', + 'ᾙ' => 'ᾙ', + 'ᾚ' => 'ᾚ', + 'ᾛ' => 'ᾛ', + 'ᾜ' => 'ᾜ', + 'ᾝ' => 'ᾝ', + 'ᾞ' => 'ᾞ', + 'ᾟ' => 'ᾟ', + 'ᾠ' => 'ᾠ', + 'ᾡ' => 'ᾡ', + 'ᾢ' => 'ᾢ', + 'ᾣ' => 'ᾣ', + 'ᾤ' => 'ᾤ', + 'ᾥ' => 'ᾥ', + 'ᾦ' => 'ᾦ', + 'ᾧ' => 'ᾧ', + 'ᾨ' => 'ᾨ', + 'ᾩ' => 'ᾩ', + 'ᾪ' => 'ᾪ', + 'ᾫ' => 'ᾫ', + 'ᾬ' => 'ᾬ', + 'ᾭ' => 'ᾭ', + 'ᾮ' => 'ᾮ', + 'ᾯ' => 'ᾯ', + 'ᾰ' => 'ᾰ', + 'ᾱ' => 'ᾱ', + 'ᾲ' => 'ᾲ', + 'ᾳ' => 'ᾳ', + 'ᾴ' => 'ᾴ', + 'ᾶ' => 'ᾶ', + 'ᾷ' => 'ᾷ', + 'Ᾰ' => 'Ᾰ', + 'Ᾱ' => 'Ᾱ', + 'Ὰ' => 'Ὰ', + 'ᾼ' => 'ᾼ', + '῁' => '῁', + 'ῂ' => 'ῂ', + 'ῃ' => 'ῃ', + 'ῄ' => 'ῄ', + 'ῆ' => 'ῆ', + 'ῇ' => 'ῇ', + 'Ὲ' => 'Ὲ', + 'Ὴ' => 'Ὴ', + 'ῌ' => 'ῌ', + '῍' => '῍', + '῎' => '῎', + '῏' => '῏', + 'ῐ' => 'ῐ', + 'ῑ' => 'ῑ', + 'ῒ' => 'ῒ', + 'ῖ' => 'ῖ', + 'ῗ' => 'ῗ', + 'Ῐ' => 'Ῐ', + 'Ῑ' => 'Ῑ', + 'Ὶ' => 'Ὶ', + '῝' => '῝', + '῞' => '῞', + '῟' => '῟', + 'ῠ' => 'ῠ', + 'ῡ' => 'ῡ', + 'ῢ' => 'ῢ', + 'ῤ' => 'ῤ', + 'ῥ' => 'ῥ', + 'ῦ' => 'ῦ', + 'ῧ' => 'ῧ', + 'Ῠ' => 'Ῠ', + 'Ῡ' => 'Ῡ', + 'Ὺ' => 'Ὺ', + 'Ῥ' => 'Ῥ', + '῭' => '῭', + 'ῲ' => 'ῲ', + 'ῳ' => 'ῳ', + 'ῴ' => 'ῴ', + 'ῶ' => 'ῶ', + 'ῷ' => 'ῷ', + 'Ὸ' => 'Ὸ', + 'Ὼ' => 'Ὼ', + 'ῼ' => 'ῼ', + '↚' => '↚', + '↛' => '↛', + '↮' => '↮', + '⇍' => '⇍', + '⇎' => '⇎', + '⇏' => '⇏', + '∄' => '∄', + '∉' => '∉', + '∌' => '∌', + '∤' => '∤', + '∦' => '∦', + '≁' => '≁', + '≄' => '≄', + '≇' => '≇', + '≉' => '≉', + '≠' => '≠', + '≢' => '≢', + '≭' => '≭', + '≮' => '≮', + '≯' => '≯', + '≰' => '≰', + '≱' => '≱', + '≴' => '≴', + '≵' => '≵', + '≸' => '≸', + '≹' => '≹', + '⊀' => '⊀', + '⊁' => '⊁', + '⊄' => '⊄', + '⊅' => '⊅', + '⊈' => '⊈', + '⊉' => '⊉', + '⊬' => '⊬', + '⊭' => '⊭', + '⊮' => '⊮', + '⊯' => '⊯', + '⋠' => '⋠', + '⋡' => '⋡', + '⋢' => '⋢', + '⋣' => '⋣', + '⋪' => '⋪', + '⋫' => '⋫', + '⋬' => '⋬', + '⋭' => '⋭', + 'が' => 'が', + 'ぎ' => 'ぎ', + 'ぐ' => 'ぐ', + 'げ' => 'げ', + 'ご' => 'ご', + 'ざ' => 'ざ', + 'じ' => 'じ', + 'ず' => 'ず', + 'ぜ' => 'ぜ', + 'ぞ' => 'ぞ', + 'だ' => 'だ', + 'ぢ' => 'ぢ', + 'づ' => 'づ', + 'で' => 'で', + 'ど' => 'ど', + 'ば' => 'ば', + 'ぱ' => 'ぱ', + 'び' => 'び', + 'ぴ' => 'ぴ', + 'ぶ' => 'ぶ', + 'ぷ' => 'ぷ', + 'べ' => 'べ', + 'ぺ' => 'ぺ', + 'ぼ' => 'ぼ', + 'ぽ' => 'ぽ', + 'ゔ' => 'ゔ', + 'ゞ' => 'ゞ', + 'ガ' => 'ガ', + 'ギ' => 'ギ', + 'グ' => 'グ', + 'ゲ' => 'ゲ', + 'ゴ' => 'ゴ', + 'ザ' => 'ザ', + 'ジ' => 'ジ', + 'ズ' => 'ズ', + 'ゼ' => 'ゼ', + 'ゾ' => 'ゾ', + 'ダ' => 'ダ', + 'ヂ' => 'ヂ', + 'ヅ' => 'ヅ', + 'デ' => 'デ', + 'ド' => 'ド', + 'バ' => 'バ', + 'パ' => 'パ', + 'ビ' => 'ビ', + 'ピ' => 'ピ', + 'ブ' => 'ブ', + 'プ' => 'プ', + 'ベ' => 'ベ', + 'ペ' => 'ペ', + 'ボ' => 'ボ', + 'ポ' => 'ポ', + 'ヴ' => 'ヴ', + 'ヷ' => 'ヷ', + 'ヸ' => 'ヸ', + 'ヹ' => 'ヹ', + 'ヺ' => 'ヺ', + 'ヾ' => 'ヾ', + '𑂚' => '𑂚', + '𑂜' => '𑂜', + '𑂫' => '𑂫', + '𑄮' => '𑄮', + '𑄯' => '𑄯', + '𑍋' => '𑍋', + '𑍌' => '𑍌', + '𑒻' => '𑒻', + '𑒼' => '𑒼', + '𑒾' => '𑒾', + '𑖺' => '𑖺', + '𑖻' => '𑖻', + '𑤸' => '𑤸', +); diff --git a/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalDecomposition.php b/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalDecomposition.php new file mode 100644 index 00000000..5a3e8e09 --- /dev/null +++ b/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalDecomposition.php @@ -0,0 +1,2065 @@ + 'À', + 'Á' => 'Á', + 'Â' => 'Â', + 'Ã' => 'Ã', + 'Ä' => 'Ä', + 'Å' => 'Å', + 'Ç' => 'Ç', + 'È' => 'È', + 'É' => 'É', + 'Ê' => 'Ê', + 'Ë' => 'Ë', + 'Ì' => 'Ì', + 'Í' => 'Í', + 'Î' => 'Î', + 'Ï' => 'Ï', + 'Ñ' => 'Ñ', + 'Ò' => 'Ò', + 'Ó' => 'Ó', + 'Ô' => 'Ô', + 'Õ' => 'Õ', + 'Ö' => 'Ö', + 'Ù' => 'Ù', + 'Ú' => 'Ú', + 'Û' => 'Û', + 'Ü' => 'Ü', + 'Ý' => 'Ý', + 'à' => 'à', + 'á' => 'á', + 'â' => 'â', + 'ã' => 'ã', + 'ä' => 'ä', + 'å' => 'å', + 'ç' => 'ç', + 'è' => 'è', + 'é' => 'é', + 'ê' => 'ê', + 'ë' => 'ë', + 'ì' => 'ì', + 'í' => 'í', + 'î' => 'î', + 'ï' => 'ï', + 'ñ' => 'ñ', + 'ò' => 'ò', + 'ó' => 'ó', + 'ô' => 'ô', + 'õ' => 'õ', + 'ö' => 'ö', + 'ù' => 'ù', + 'ú' => 'ú', + 'û' => 'û', + 'ü' => 'ü', + 'ý' => 'ý', + 'ÿ' => 'ÿ', + 'Ā' => 'Ā', + 'ā' => 'ā', + 'Ă' => 'Ă', + 'ă' => 'ă', + 'Ą' => 'Ą', + 'ą' => 'ą', + 'Ć' => 'Ć', + 'ć' => 'ć', + 'Ĉ' => 'Ĉ', + 'ĉ' => 'ĉ', + 'Ċ' => 'Ċ', + 'ċ' => 'ċ', + 'Č' => 'Č', + 'č' => 'č', + 'Ď' => 'Ď', + 'ď' => 'ď', + 'Ē' => 'Ē', + 'ē' => 'ē', + 'Ĕ' => 'Ĕ', + 'ĕ' => 'ĕ', + 'Ė' => 'Ė', + 'ė' => 'ė', + 'Ę' => 'Ę', + 'ę' => 'ę', + 'Ě' => 'Ě', + 'ě' => 'ě', + 'Ĝ' => 'Ĝ', + 'ĝ' => 'ĝ', + 'Ğ' => 'Ğ', + 'ğ' => 'ğ', + 'Ġ' => 'Ġ', + 'ġ' => 'ġ', + 'Ģ' => 'Ģ', + 'ģ' => 'ģ', + 'Ĥ' => 'Ĥ', + 'ĥ' => 'ĥ', + 'Ĩ' => 'Ĩ', + 'ĩ' => 'ĩ', + 'Ī' => 'Ī', + 'ī' => 'ī', + 'Ĭ' => 'Ĭ', + 'ĭ' => 'ĭ', + 'Į' => 'Į', + 'į' => 'į', + 'İ' => 'İ', + 'Ĵ' => 'Ĵ', + 'ĵ' => 'ĵ', + 'Ķ' => 'Ķ', + 'ķ' => 'ķ', + 'Ĺ' => 'Ĺ', + 'ĺ' => 'ĺ', + 'Ļ' => 'Ļ', + 'ļ' => 'ļ', + 'Ľ' => 'Ľ', + 'ľ' => 'ľ', + 'Ń' => 'Ń', + 'ń' => 'ń', + 'Ņ' => 'Ņ', + 'ņ' => 'ņ', + 'Ň' => 'Ň', + 'ň' => 'ň', + 'Ō' => 'Ō', + 'ō' => 'ō', + 'Ŏ' => 'Ŏ', + 'ŏ' => 'ŏ', + 'Ő' => 'Ő', + 'ő' => 'ő', + 'Ŕ' => 'Ŕ', + 'ŕ' => 'ŕ', + 'Ŗ' => 'Ŗ', + 'ŗ' => 'ŗ', + 'Ř' => 'Ř', + 'ř' => 'ř', + 'Ś' => 'Ś', + 'ś' => 'ś', + 'Ŝ' => 'Ŝ', + 'ŝ' => 'ŝ', + 'Ş' => 'Ş', + 'ş' => 'ş', + 'Š' => 'Š', + 'š' => 'š', + 'Ţ' => 'Ţ', + 'ţ' => 'ţ', + 'Ť' => 'Ť', + 'ť' => 'ť', + 'Ũ' => 'Ũ', + 'ũ' => 'ũ', + 'Ū' => 'Ū', + 'ū' => 'ū', + 'Ŭ' => 'Ŭ', + 'ŭ' => 'ŭ', + 'Ů' => 'Ů', + 'ů' => 'ů', + 'Ű' => 'Ű', + 'ű' => 'ű', + 'Ų' => 'Ų', + 'ų' => 'ų', + 'Ŵ' => 'Ŵ', + 'ŵ' => 'ŵ', + 'Ŷ' => 'Ŷ', + 'ŷ' => 'ŷ', + 'Ÿ' => 'Ÿ', + 'Ź' => 'Ź', + 'ź' => 'ź', + 'Ż' => 'Ż', + 'ż' => 'ż', + 'Ž' => 'Ž', + 'ž' => 'ž', + 'Ơ' => 'Ơ', + 'ơ' => 'ơ', + 'Ư' => 'Ư', + 'ư' => 'ư', + 'Ǎ' => 'Ǎ', + 'ǎ' => 'ǎ', + 'Ǐ' => 'Ǐ', + 'ǐ' => 'ǐ', + 'Ǒ' => 'Ǒ', + 'ǒ' => 'ǒ', + 'Ǔ' => 'Ǔ', + 'ǔ' => 'ǔ', + 'Ǖ' => 'Ǖ', + 'ǖ' => 'ǖ', + 'Ǘ' => 'Ǘ', + 'ǘ' => 'ǘ', + 'Ǚ' => 'Ǚ', + 'ǚ' => 'ǚ', + 'Ǜ' => 'Ǜ', + 'ǜ' => 'ǜ', + 'Ǟ' => 'Ǟ', + 'ǟ' => 'ǟ', + 'Ǡ' => 'Ǡ', + 'ǡ' => 'ǡ', + 'Ǣ' => 'Ǣ', + 'ǣ' => 'ǣ', + 'Ǧ' => 'Ǧ', + 'ǧ' => 'ǧ', + 'Ǩ' => 'Ǩ', + 'ǩ' => 'ǩ', + 'Ǫ' => 'Ǫ', + 'ǫ' => 'ǫ', + 'Ǭ' => 'Ǭ', + 'ǭ' => 'ǭ', + 'Ǯ' => 'Ǯ', + 'ǯ' => 'ǯ', + 'ǰ' => 'ǰ', + 'Ǵ' => 'Ǵ', + 'ǵ' => 'ǵ', + 'Ǹ' => 'Ǹ', + 'ǹ' => 'ǹ', + 'Ǻ' => 'Ǻ', + 'ǻ' => 'ǻ', + 'Ǽ' => 'Ǽ', + 'ǽ' => 'ǽ', + 'Ǿ' => 'Ǿ', + 'ǿ' => 'ǿ', + 'Ȁ' => 'Ȁ', + 'ȁ' => 'ȁ', + 'Ȃ' => 'Ȃ', + 'ȃ' => 'ȃ', + 'Ȅ' => 'Ȅ', + 'ȅ' => 'ȅ', + 'Ȇ' => 'Ȇ', + 'ȇ' => 'ȇ', + 'Ȉ' => 'Ȉ', + 'ȉ' => 'ȉ', + 'Ȋ' => 'Ȋ', + 'ȋ' => 'ȋ', + 'Ȍ' => 'Ȍ', + 'ȍ' => 'ȍ', + 'Ȏ' => 'Ȏ', + 'ȏ' => 'ȏ', + 'Ȑ' => 'Ȑ', + 'ȑ' => 'ȑ', + 'Ȓ' => 'Ȓ', + 'ȓ' => 'ȓ', + 'Ȕ' => 'Ȕ', + 'ȕ' => 'ȕ', + 'Ȗ' => 'Ȗ', + 'ȗ' => 'ȗ', + 'Ș' => 'Ș', + 'ș' => 'ș', + 'Ț' => 'Ț', + 'ț' => 'ț', + 'Ȟ' => 'Ȟ', + 'ȟ' => 'ȟ', + 'Ȧ' => 'Ȧ', + 'ȧ' => 'ȧ', + 'Ȩ' => 'Ȩ', + 'ȩ' => 'ȩ', + 'Ȫ' => 'Ȫ', + 'ȫ' => 'ȫ', + 'Ȭ' => 'Ȭ', + 'ȭ' => 'ȭ', + 'Ȯ' => 'Ȯ', + 'ȯ' => 'ȯ', + 'Ȱ' => 'Ȱ', + 'ȱ' => 'ȱ', + 'Ȳ' => 'Ȳ', + 'ȳ' => 'ȳ', + '̀' => '̀', + '́' => '́', + '̓' => '̓', + '̈́' => '̈́', + 'ʹ' => 'ʹ', + ';' => ';', + '΅' => '΅', + 'Ά' => 'Ά', + '·' => '·', + 'Έ' => 'Έ', + 'Ή' => 'Ή', + 'Ί' => 'Ί', + 'Ό' => 'Ό', + 'Ύ' => 'Ύ', + 'Ώ' => 'Ώ', + 'ΐ' => 'ΐ', + 'Ϊ' => 'Ϊ', + 'Ϋ' => 'Ϋ', + 'ά' => 'ά', + 'έ' => 'έ', + 'ή' => 'ή', + 'ί' => 'ί', + 'ΰ' => 'ΰ', + 'ϊ' => 'ϊ', + 'ϋ' => 'ϋ', + 'ό' => 'ό', + 'ύ' => 'ύ', + 'ώ' => 'ώ', + 'ϓ' => 'ϓ', + 'ϔ' => 'ϔ', + 'Ѐ' => 'Ѐ', + 'Ё' => 'Ё', + 'Ѓ' => 'Ѓ', + 'Ї' => 'Ї', + 'Ќ' => 'Ќ', + 'Ѝ' => 'Ѝ', + 'Ў' => 'Ў', + 'Й' => 'Й', + 'й' => 'й', + 'ѐ' => 'ѐ', + 'ё' => 'ё', + 'ѓ' => 'ѓ', + 'ї' => 'ї', + 'ќ' => 'ќ', + 'ѝ' => 'ѝ', + 'ў' => 'ў', + 'Ѷ' => 'Ѷ', + 'ѷ' => 'ѷ', + 'Ӂ' => 'Ӂ', + 'ӂ' => 'ӂ', + 'Ӑ' => 'Ӑ', + 'ӑ' => 'ӑ', + 'Ӓ' => 'Ӓ', + 'ӓ' => 'ӓ', + 'Ӗ' => 'Ӗ', + 'ӗ' => 'ӗ', + 'Ӛ' => 'Ӛ', + 'ӛ' => 'ӛ', + 'Ӝ' => 'Ӝ', + 'ӝ' => 'ӝ', + 'Ӟ' => 'Ӟ', + 'ӟ' => 'ӟ', + 'Ӣ' => 'Ӣ', + 'ӣ' => 'ӣ', + 'Ӥ' => 'Ӥ', + 'ӥ' => 'ӥ', + 'Ӧ' => 'Ӧ', + 'ӧ' => 'ӧ', + 'Ӫ' => 'Ӫ', + 'ӫ' => 'ӫ', + 'Ӭ' => 'Ӭ', + 'ӭ' => 'ӭ', + 'Ӯ' => 'Ӯ', + 'ӯ' => 'ӯ', + 'Ӱ' => 'Ӱ', + 'ӱ' => 'ӱ', + 'Ӳ' => 'Ӳ', + 'ӳ' => 'ӳ', + 'Ӵ' => 'Ӵ', + 'ӵ' => 'ӵ', + 'Ӹ' => 'Ӹ', + 'ӹ' => 'ӹ', + 'آ' => 'آ', + 'أ' => 'أ', + 'ؤ' => 'ؤ', + 'إ' => 'إ', + 'ئ' => 'ئ', + 'ۀ' => 'ۀ', + 'ۂ' => 'ۂ', + 'ۓ' => 'ۓ', + 'ऩ' => 'ऩ', + 'ऱ' => 'ऱ', + 'ऴ' => 'ऴ', + 'क़' => 'क़', + 'ख़' => 'ख़', + 'ग़' => 'ग़', + 'ज़' => 'ज़', + 'ड़' => 'ड़', + 'ढ़' => 'ढ़', + 'फ़' => 'फ़', + 'य़' => 'य़', + 'ো' => 'ো', + 'ৌ' => 'ৌ', + 'ড়' => 'ড়', + 'ঢ়' => 'ঢ়', + 'য়' => 'য়', + 'ਲ਼' => 'ਲ਼', + 'ਸ਼' => 'ਸ਼', + 'ਖ਼' => 'ਖ਼', + 'ਗ਼' => 'ਗ਼', + 'ਜ਼' => 'ਜ਼', + 'ਫ਼' => 'ਫ਼', + 'ୈ' => 'ୈ', + 'ୋ' => 'ୋ', + 'ୌ' => 'ୌ', + 'ଡ଼' => 'ଡ଼', + 'ଢ଼' => 'ଢ଼', + 'ஔ' => 'ஔ', + 'ொ' => 'ொ', + 'ோ' => 'ோ', + 'ௌ' => 'ௌ', + 'ై' => 'ై', + 'ೀ' => 'ೀ', + 'ೇ' => 'ೇ', + 'ೈ' => 'ೈ', + 'ೊ' => 'ೊ', + 'ೋ' => 'ೋ', + 'ൊ' => 'ൊ', + 'ോ' => 'ോ', + 'ൌ' => 'ൌ', + 'ේ' => 'ේ', + 'ො' => 'ො', + 'ෝ' => 'ෝ', + 'ෞ' => 'ෞ', + 'གྷ' => 'གྷ', + 'ཌྷ' => 'ཌྷ', + 'དྷ' => 'དྷ', + 'བྷ' => 'བྷ', + 'ཛྷ' => 'ཛྷ', + 'ཀྵ' => 'ཀྵ', + 'ཱི' => 'ཱི', + 'ཱུ' => 'ཱུ', + 'ྲྀ' => 'ྲྀ', + 'ླྀ' => 'ླྀ', + 'ཱྀ' => 'ཱྀ', + 'ྒྷ' => 'ྒྷ', + 'ྜྷ' => 'ྜྷ', + 'ྡྷ' => 'ྡྷ', + 'ྦྷ' => 'ྦྷ', + 'ྫྷ' => 'ྫྷ', + 'ྐྵ' => 'ྐྵ', + 'ဦ' => 'ဦ', + 'ᬆ' => 'ᬆ', + 'ᬈ' => 'ᬈ', + 'ᬊ' => 'ᬊ', + 'ᬌ' => 'ᬌ', + 'ᬎ' => 'ᬎ', + 'ᬒ' => 'ᬒ', + 'ᬻ' => 'ᬻ', + 'ᬽ' => 'ᬽ', + 'ᭀ' => 'ᭀ', + 'ᭁ' => 'ᭁ', + 'ᭃ' => 'ᭃ', + 'Ḁ' => 'Ḁ', + 'ḁ' => 'ḁ', + 'Ḃ' => 'Ḃ', + 'ḃ' => 'ḃ', + 'Ḅ' => 'Ḅ', + 'ḅ' => 'ḅ', + 'Ḇ' => 'Ḇ', + 'ḇ' => 'ḇ', + 'Ḉ' => 'Ḉ', + 'ḉ' => 'ḉ', + 'Ḋ' => 'Ḋ', + 'ḋ' => 'ḋ', + 'Ḍ' => 'Ḍ', + 'ḍ' => 'ḍ', + 'Ḏ' => 'Ḏ', + 'ḏ' => 'ḏ', + 'Ḑ' => 'Ḑ', + 'ḑ' => 'ḑ', + 'Ḓ' => 'Ḓ', + 'ḓ' => 'ḓ', + 'Ḕ' => 'Ḕ', + 'ḕ' => 'ḕ', + 'Ḗ' => 'Ḗ', + 'ḗ' => 'ḗ', + 'Ḙ' => 'Ḙ', + 'ḙ' => 'ḙ', + 'Ḛ' => 'Ḛ', + 'ḛ' => 'ḛ', + 'Ḝ' => 'Ḝ', + 'ḝ' => 'ḝ', + 'Ḟ' => 'Ḟ', + 'ḟ' => 'ḟ', + 'Ḡ' => 'Ḡ', + 'ḡ' => 'ḡ', + 'Ḣ' => 'Ḣ', + 'ḣ' => 'ḣ', + 'Ḥ' => 'Ḥ', + 'ḥ' => 'ḥ', + 'Ḧ' => 'Ḧ', + 'ḧ' => 'ḧ', + 'Ḩ' => 'Ḩ', + 'ḩ' => 'ḩ', + 'Ḫ' => 'Ḫ', + 'ḫ' => 'ḫ', + 'Ḭ' => 'Ḭ', + 'ḭ' => 'ḭ', + 'Ḯ' => 'Ḯ', + 'ḯ' => 'ḯ', + 'Ḱ' => 'Ḱ', + 'ḱ' => 'ḱ', + 'Ḳ' => 'Ḳ', + 'ḳ' => 'ḳ', + 'Ḵ' => 'Ḵ', + 'ḵ' => 'ḵ', + 'Ḷ' => 'Ḷ', + 'ḷ' => 'ḷ', + 'Ḹ' => 'Ḹ', + 'ḹ' => 'ḹ', + 'Ḻ' => 'Ḻ', + 'ḻ' => 'ḻ', + 'Ḽ' => 'Ḽ', + 'ḽ' => 'ḽ', + 'Ḿ' => 'Ḿ', + 'ḿ' => 'ḿ', + 'Ṁ' => 'Ṁ', + 'ṁ' => 'ṁ', + 'Ṃ' => 'Ṃ', + 'ṃ' => 'ṃ', + 'Ṅ' => 'Ṅ', + 'ṅ' => 'ṅ', + 'Ṇ' => 'Ṇ', + 'ṇ' => 'ṇ', + 'Ṉ' => 'Ṉ', + 'ṉ' => 'ṉ', + 'Ṋ' => 'Ṋ', + 'ṋ' => 'ṋ', + 'Ṍ' => 'Ṍ', + 'ṍ' => 'ṍ', + 'Ṏ' => 'Ṏ', + 'ṏ' => 'ṏ', + 'Ṑ' => 'Ṑ', + 'ṑ' => 'ṑ', + 'Ṓ' => 'Ṓ', + 'ṓ' => 'ṓ', + 'Ṕ' => 'Ṕ', + 'ṕ' => 'ṕ', + 'Ṗ' => 'Ṗ', + 'ṗ' => 'ṗ', + 'Ṙ' => 'Ṙ', + 'ṙ' => 'ṙ', + 'Ṛ' => 'Ṛ', + 'ṛ' => 'ṛ', + 'Ṝ' => 'Ṝ', + 'ṝ' => 'ṝ', + 'Ṟ' => 'Ṟ', + 'ṟ' => 'ṟ', + 'Ṡ' => 'Ṡ', + 'ṡ' => 'ṡ', + 'Ṣ' => 'Ṣ', + 'ṣ' => 'ṣ', + 'Ṥ' => 'Ṥ', + 'ṥ' => 'ṥ', + 'Ṧ' => 'Ṧ', + 'ṧ' => 'ṧ', + 'Ṩ' => 'Ṩ', + 'ṩ' => 'ṩ', + 'Ṫ' => 'Ṫ', + 'ṫ' => 'ṫ', + 'Ṭ' => 'Ṭ', + 'ṭ' => 'ṭ', + 'Ṯ' => 'Ṯ', + 'ṯ' => 'ṯ', + 'Ṱ' => 'Ṱ', + 'ṱ' => 'ṱ', + 'Ṳ' => 'Ṳ', + 'ṳ' => 'ṳ', + 'Ṵ' => 'Ṵ', + 'ṵ' => 'ṵ', + 'Ṷ' => 'Ṷ', + 'ṷ' => 'ṷ', + 'Ṹ' => 'Ṹ', + 'ṹ' => 'ṹ', + 'Ṻ' => 'Ṻ', + 'ṻ' => 'ṻ', + 'Ṽ' => 'Ṽ', + 'ṽ' => 'ṽ', + 'Ṿ' => 'Ṿ', + 'ṿ' => 'ṿ', + 'Ẁ' => 'Ẁ', + 'ẁ' => 'ẁ', + 'Ẃ' => 'Ẃ', + 'ẃ' => 'ẃ', + 'Ẅ' => 'Ẅ', + 'ẅ' => 'ẅ', + 'Ẇ' => 'Ẇ', + 'ẇ' => 'ẇ', + 'Ẉ' => 'Ẉ', + 'ẉ' => 'ẉ', + 'Ẋ' => 'Ẋ', + 'ẋ' => 'ẋ', + 'Ẍ' => 'Ẍ', + 'ẍ' => 'ẍ', + 'Ẏ' => 'Ẏ', + 'ẏ' => 'ẏ', + 'Ẑ' => 'Ẑ', + 'ẑ' => 'ẑ', + 'Ẓ' => 'Ẓ', + 'ẓ' => 'ẓ', + 'Ẕ' => 'Ẕ', + 'ẕ' => 'ẕ', + 'ẖ' => 'ẖ', + 'ẗ' => 'ẗ', + 'ẘ' => 'ẘ', + 'ẙ' => 'ẙ', + 'ẛ' => 'ẛ', + 'Ạ' => 'Ạ', + 'ạ' => 'ạ', + 'Ả' => 'Ả', + 'ả' => 'ả', + 'Ấ' => 'Ấ', + 'ấ' => 'ấ', + 'Ầ' => 'Ầ', + 'ầ' => 'ầ', + 'Ẩ' => 'Ẩ', + 'ẩ' => 'ẩ', + 'Ẫ' => 'Ẫ', + 'ẫ' => 'ẫ', + 'Ậ' => 'Ậ', + 'ậ' => 'ậ', + 'Ắ' => 'Ắ', + 'ắ' => 'ắ', + 'Ằ' => 'Ằ', + 'ằ' => 'ằ', + 'Ẳ' => 'Ẳ', + 'ẳ' => 'ẳ', + 'Ẵ' => 'Ẵ', + 'ẵ' => 'ẵ', + 'Ặ' => 'Ặ', + 'ặ' => 'ặ', + 'Ẹ' => 'Ẹ', + 'ẹ' => 'ẹ', + 'Ẻ' => 'Ẻ', + 'ẻ' => 'ẻ', + 'Ẽ' => 'Ẽ', + 'ẽ' => 'ẽ', + 'Ế' => 'Ế', + 'ế' => 'ế', + 'Ề' => 'Ề', + 'ề' => 'ề', + 'Ể' => 'Ể', + 'ể' => 'ể', + 'Ễ' => 'Ễ', + 'ễ' => 'ễ', + 'Ệ' => 'Ệ', + 'ệ' => 'ệ', + 'Ỉ' => 'Ỉ', + 'ỉ' => 'ỉ', + 'Ị' => 'Ị', + 'ị' => 'ị', + 'Ọ' => 'Ọ', + 'ọ' => 'ọ', + 'Ỏ' => 'Ỏ', + 'ỏ' => 'ỏ', + 'Ố' => 'Ố', + 'ố' => 'ố', + 'Ồ' => 'Ồ', + 'ồ' => 'ồ', + 'Ổ' => 'Ổ', + 'ổ' => 'ổ', + 'Ỗ' => 'Ỗ', + 'ỗ' => 'ỗ', + 'Ộ' => 'Ộ', + 'ộ' => 'ộ', + 'Ớ' => 'Ớ', + 'ớ' => 'ớ', + 'Ờ' => 'Ờ', + 'ờ' => 'ờ', + 'Ở' => 'Ở', + 'ở' => 'ở', + 'Ỡ' => 'Ỡ', + 'ỡ' => 'ỡ', + 'Ợ' => 'Ợ', + 'ợ' => 'ợ', + 'Ụ' => 'Ụ', + 'ụ' => 'ụ', + 'Ủ' => 'Ủ', + 'ủ' => 'ủ', + 'Ứ' => 'Ứ', + 'ứ' => 'ứ', + 'Ừ' => 'Ừ', + 'ừ' => 'ừ', + 'Ử' => 'Ử', + 'ử' => 'ử', + 'Ữ' => 'Ữ', + 'ữ' => 'ữ', + 'Ự' => 'Ự', + 'ự' => 'ự', + 'Ỳ' => 'Ỳ', + 'ỳ' => 'ỳ', + 'Ỵ' => 'Ỵ', + 'ỵ' => 'ỵ', + 'Ỷ' => 'Ỷ', + 'ỷ' => 'ỷ', + 'Ỹ' => 'Ỹ', + 'ỹ' => 'ỹ', + 'ἀ' => 'ἀ', + 'ἁ' => 'ἁ', + 'ἂ' => 'ἂ', + 'ἃ' => 'ἃ', + 'ἄ' => 'ἄ', + 'ἅ' => 'ἅ', + 'ἆ' => 'ἆ', + 'ἇ' => 'ἇ', + 'Ἀ' => 'Ἀ', + 'Ἁ' => 'Ἁ', + 'Ἂ' => 'Ἂ', + 'Ἃ' => 'Ἃ', + 'Ἄ' => 'Ἄ', + 'Ἅ' => 'Ἅ', + 'Ἆ' => 'Ἆ', + 'Ἇ' => 'Ἇ', + 'ἐ' => 'ἐ', + 'ἑ' => 'ἑ', + 'ἒ' => 'ἒ', + 'ἓ' => 'ἓ', + 'ἔ' => 'ἔ', + 'ἕ' => 'ἕ', + 'Ἐ' => 'Ἐ', + 'Ἑ' => 'Ἑ', + 'Ἒ' => 'Ἒ', + 'Ἓ' => 'Ἓ', + 'Ἔ' => 'Ἔ', + 'Ἕ' => 'Ἕ', + 'ἠ' => 'ἠ', + 'ἡ' => 'ἡ', + 'ἢ' => 'ἢ', + 'ἣ' => 'ἣ', + 'ἤ' => 'ἤ', + 'ἥ' => 'ἥ', + 'ἦ' => 'ἦ', + 'ἧ' => 'ἧ', + 'Ἠ' => 'Ἠ', + 'Ἡ' => 'Ἡ', + 'Ἢ' => 'Ἢ', + 'Ἣ' => 'Ἣ', + 'Ἤ' => 'Ἤ', + 'Ἥ' => 'Ἥ', + 'Ἦ' => 'Ἦ', + 'Ἧ' => 'Ἧ', + 'ἰ' => 'ἰ', + 'ἱ' => 'ἱ', + 'ἲ' => 'ἲ', + 'ἳ' => 'ἳ', + 'ἴ' => 'ἴ', + 'ἵ' => 'ἵ', + 'ἶ' => 'ἶ', + 'ἷ' => 'ἷ', + 'Ἰ' => 'Ἰ', + 'Ἱ' => 'Ἱ', + 'Ἲ' => 'Ἲ', + 'Ἳ' => 'Ἳ', + 'Ἴ' => 'Ἴ', + 'Ἵ' => 'Ἵ', + 'Ἶ' => 'Ἶ', + 'Ἷ' => 'Ἷ', + 'ὀ' => 'ὀ', + 'ὁ' => 'ὁ', + 'ὂ' => 'ὂ', + 'ὃ' => 'ὃ', + 'ὄ' => 'ὄ', + 'ὅ' => 'ὅ', + 'Ὀ' => 'Ὀ', + 'Ὁ' => 'Ὁ', + 'Ὂ' => 'Ὂ', + 'Ὃ' => 'Ὃ', + 'Ὄ' => 'Ὄ', + 'Ὅ' => 'Ὅ', + 'ὐ' => 'ὐ', + 'ὑ' => 'ὑ', + 'ὒ' => 'ὒ', + 'ὓ' => 'ὓ', + 'ὔ' => 'ὔ', + 'ὕ' => 'ὕ', + 'ὖ' => 'ὖ', + 'ὗ' => 'ὗ', + 'Ὑ' => 'Ὑ', + 'Ὓ' => 'Ὓ', + 'Ὕ' => 'Ὕ', + 'Ὗ' => 'Ὗ', + 'ὠ' => 'ὠ', + 'ὡ' => 'ὡ', + 'ὢ' => 'ὢ', + 'ὣ' => 'ὣ', + 'ὤ' => 'ὤ', + 'ὥ' => 'ὥ', + 'ὦ' => 'ὦ', + 'ὧ' => 'ὧ', + 'Ὠ' => 'Ὠ', + 'Ὡ' => 'Ὡ', + 'Ὢ' => 'Ὢ', + 'Ὣ' => 'Ὣ', + 'Ὤ' => 'Ὤ', + 'Ὥ' => 'Ὥ', + 'Ὦ' => 'Ὦ', + 'Ὧ' => 'Ὧ', + 'ὰ' => 'ὰ', + 'ά' => 'ά', + 'ὲ' => 'ὲ', + 'έ' => 'έ', + 'ὴ' => 'ὴ', + 'ή' => 'ή', + 'ὶ' => 'ὶ', + 'ί' => 'ί', + 'ὸ' => 'ὸ', + 'ό' => 'ό', + 'ὺ' => 'ὺ', + 'ύ' => 'ύ', + 'ὼ' => 'ὼ', + 'ώ' => 'ώ', + 'ᾀ' => 'ᾀ', + 'ᾁ' => 'ᾁ', + 'ᾂ' => 'ᾂ', + 'ᾃ' => 'ᾃ', + 'ᾄ' => 'ᾄ', + 'ᾅ' => 'ᾅ', + 'ᾆ' => 'ᾆ', + 'ᾇ' => 'ᾇ', + 'ᾈ' => 'ᾈ', + 'ᾉ' => 'ᾉ', + 'ᾊ' => 'ᾊ', + 'ᾋ' => 'ᾋ', + 'ᾌ' => 'ᾌ', + 'ᾍ' => 'ᾍ', + 'ᾎ' => 'ᾎ', + 'ᾏ' => 'ᾏ', + 'ᾐ' => 'ᾐ', + 'ᾑ' => 'ᾑ', + 'ᾒ' => 'ᾒ', + 'ᾓ' => 'ᾓ', + 'ᾔ' => 'ᾔ', + 'ᾕ' => 'ᾕ', + 'ᾖ' => 'ᾖ', + 'ᾗ' => 'ᾗ', + 'ᾘ' => 'ᾘ', + 'ᾙ' => 'ᾙ', + 'ᾚ' => 'ᾚ', + 'ᾛ' => 'ᾛ', + 'ᾜ' => 'ᾜ', + 'ᾝ' => 'ᾝ', + 'ᾞ' => 'ᾞ', + 'ᾟ' => 'ᾟ', + 'ᾠ' => 'ᾠ', + 'ᾡ' => 'ᾡ', + 'ᾢ' => 'ᾢ', + 'ᾣ' => 'ᾣ', + 'ᾤ' => 'ᾤ', + 'ᾥ' => 'ᾥ', + 'ᾦ' => 'ᾦ', + 'ᾧ' => 'ᾧ', + 'ᾨ' => 'ᾨ', + 'ᾩ' => 'ᾩ', + 'ᾪ' => 'ᾪ', + 'ᾫ' => 'ᾫ', + 'ᾬ' => 'ᾬ', + 'ᾭ' => 'ᾭ', + 'ᾮ' => 'ᾮ', + 'ᾯ' => 'ᾯ', + 'ᾰ' => 'ᾰ', + 'ᾱ' => 'ᾱ', + 'ᾲ' => 'ᾲ', + 'ᾳ' => 'ᾳ', + 'ᾴ' => 'ᾴ', + 'ᾶ' => 'ᾶ', + 'ᾷ' => 'ᾷ', + 'Ᾰ' => 'Ᾰ', + 'Ᾱ' => 'Ᾱ', + 'Ὰ' => 'Ὰ', + 'Ά' => 'Ά', + 'ᾼ' => 'ᾼ', + 'ι' => 'ι', + '῁' => '῁', + 'ῂ' => 'ῂ', + 'ῃ' => 'ῃ', + 'ῄ' => 'ῄ', + 'ῆ' => 'ῆ', + 'ῇ' => 'ῇ', + 'Ὲ' => 'Ὲ', + 'Έ' => 'Έ', + 'Ὴ' => 'Ὴ', + 'Ή' => 'Ή', + 'ῌ' => 'ῌ', + '῍' => '῍', + '῎' => '῎', + '῏' => '῏', + 'ῐ' => 'ῐ', + 'ῑ' => 'ῑ', + 'ῒ' => 'ῒ', + 'ΐ' => 'ΐ', + 'ῖ' => 'ῖ', + 'ῗ' => 'ῗ', + 'Ῐ' => 'Ῐ', + 'Ῑ' => 'Ῑ', + 'Ὶ' => 'Ὶ', + 'Ί' => 'Ί', + '῝' => '῝', + '῞' => '῞', + '῟' => '῟', + 'ῠ' => 'ῠ', + 'ῡ' => 'ῡ', + 'ῢ' => 'ῢ', + 'ΰ' => 'ΰ', + 'ῤ' => 'ῤ', + 'ῥ' => 'ῥ', + 'ῦ' => 'ῦ', + 'ῧ' => 'ῧ', + 'Ῠ' => 'Ῠ', + 'Ῡ' => 'Ῡ', + 'Ὺ' => 'Ὺ', + 'Ύ' => 'Ύ', + 'Ῥ' => 'Ῥ', + '῭' => '῭', + '΅' => '΅', + '`' => '`', + 'ῲ' => 'ῲ', + 'ῳ' => 'ῳ', + 'ῴ' => 'ῴ', + 'ῶ' => 'ῶ', + 'ῷ' => 'ῷ', + 'Ὸ' => 'Ὸ', + 'Ό' => 'Ό', + 'Ὼ' => 'Ὼ', + 'Ώ' => 'Ώ', + 'ῼ' => 'ῼ', + '´' => '´', + ' ' => ' ', + ' ' => ' ', + 'Ω' => 'Ω', + 'K' => 'K', + 'Å' => 'Å', + '↚' => '↚', + '↛' => '↛', + '↮' => '↮', + '⇍' => '⇍', + '⇎' => '⇎', + '⇏' => '⇏', + '∄' => '∄', + '∉' => '∉', + '∌' => '∌', + '∤' => '∤', + '∦' => '∦', + '≁' => '≁', + '≄' => '≄', + '≇' => '≇', + '≉' => '≉', + '≠' => '≠', + '≢' => '≢', + '≭' => '≭', + '≮' => '≮', + '≯' => '≯', + '≰' => '≰', + '≱' => '≱', + '≴' => '≴', + '≵' => '≵', + '≸' => '≸', + '≹' => '≹', + '⊀' => '⊀', + '⊁' => '⊁', + '⊄' => '⊄', + '⊅' => '⊅', + '⊈' => '⊈', + '⊉' => '⊉', + '⊬' => '⊬', + '⊭' => '⊭', + '⊮' => '⊮', + '⊯' => '⊯', + '⋠' => '⋠', + '⋡' => '⋡', + '⋢' => '⋢', + '⋣' => '⋣', + '⋪' => '⋪', + '⋫' => '⋫', + '⋬' => '⋬', + '⋭' => '⋭', + '〈' => '〈', + '〉' => '〉', + '⫝̸' => '⫝̸', + 'が' => 'が', + 'ぎ' => 'ぎ', + 'ぐ' => 'ぐ', + 'げ' => 'げ', + 'ご' => 'ご', + 'ざ' => 'ざ', + 'じ' => 'じ', + 'ず' => 'ず', + 'ぜ' => 'ぜ', + 'ぞ' => 'ぞ', + 'だ' => 'だ', + 'ぢ' => 'ぢ', + 'づ' => 'づ', + 'で' => 'で', + 'ど' => 'ど', + 'ば' => 'ば', + 'ぱ' => 'ぱ', + 'び' => 'び', + 'ぴ' => 'ぴ', + 'ぶ' => 'ぶ', + 'ぷ' => 'ぷ', + 'べ' => 'べ', + 'ぺ' => 'ぺ', + 'ぼ' => 'ぼ', + 'ぽ' => 'ぽ', + 'ゔ' => 'ゔ', + 'ゞ' => 'ゞ', + 'ガ' => 'ガ', + 'ギ' => 'ギ', + 'グ' => 'グ', + 'ゲ' => 'ゲ', + 'ゴ' => 'ゴ', + 'ザ' => 'ザ', + 'ジ' => 'ジ', + 'ズ' => 'ズ', + 'ゼ' => 'ゼ', + 'ゾ' => 'ゾ', + 'ダ' => 'ダ', + 'ヂ' => 'ヂ', + 'ヅ' => 'ヅ', + 'デ' => 'デ', + 'ド' => 'ド', + 'バ' => 'バ', + 'パ' => 'パ', + 'ビ' => 'ビ', + 'ピ' => 'ピ', + 'ブ' => 'ブ', + 'プ' => 'プ', + 'ベ' => 'ベ', + 'ペ' => 'ペ', + 'ボ' => 'ボ', + 'ポ' => 'ポ', + 'ヴ' => 'ヴ', + 'ヷ' => 'ヷ', + 'ヸ' => 'ヸ', + 'ヹ' => 'ヹ', + 'ヺ' => 'ヺ', + 'ヾ' => 'ヾ', + '豈' => '豈', + '更' => '更', + '車' => '車', + '賈' => '賈', + '滑' => '滑', + '串' => '串', + '句' => '句', + '龜' => '龜', + '龜' => '龜', + '契' => '契', + '金' => '金', + '喇' => '喇', + '奈' => '奈', + '懶' => '懶', + '癩' => '癩', + '羅' => '羅', + '蘿' => '蘿', + '螺' => '螺', + '裸' => '裸', + '邏' => '邏', + '樂' => '樂', + '洛' => '洛', + '烙' => '烙', + '珞' => '珞', + '落' => '落', + '酪' => '酪', + '駱' => '駱', + '亂' => '亂', + '卵' => '卵', + '欄' => '欄', + '爛' => '爛', + '蘭' => '蘭', + '鸞' => '鸞', + '嵐' => '嵐', + '濫' => '濫', + '藍' => '藍', + '襤' => '襤', + '拉' => '拉', + '臘' => '臘', + '蠟' => '蠟', + '廊' => '廊', + '朗' => '朗', + '浪' => '浪', + '狼' => '狼', + '郎' => '郎', + '來' => '來', + '冷' => '冷', + '勞' => '勞', + '擄' => '擄', + '櫓' => '櫓', + '爐' => '爐', + '盧' => '盧', + '老' => '老', + '蘆' => '蘆', + '虜' => '虜', + '路' => '路', + '露' => '露', + '魯' => '魯', + '鷺' => '鷺', + '碌' => '碌', + '祿' => '祿', + '綠' => '綠', + '菉' => '菉', + '錄' => '錄', + '鹿' => '鹿', + '論' => '論', + '壟' => '壟', + '弄' => '弄', + '籠' => '籠', + '聾' => '聾', + '牢' => '牢', + '磊' => '磊', + '賂' => '賂', + '雷' => '雷', + '壘' => '壘', + '屢' => '屢', + '樓' => '樓', + '淚' => '淚', + '漏' => '漏', + '累' => '累', + '縷' => '縷', + '陋' => '陋', + '勒' => '勒', + '肋' => '肋', + '凜' => '凜', + '凌' => '凌', + '稜' => '稜', + '綾' => '綾', + '菱' => '菱', + '陵' => '陵', + '讀' => '讀', + '拏' => '拏', + '樂' => '樂', + '諾' => '諾', + '丹' => '丹', + '寧' => '寧', + '怒' => '怒', + '率' => '率', + '異' => '異', + '北' => '北', + '磻' => '磻', + '便' => '便', + '復' => '復', + '不' => '不', + '泌' => '泌', + '數' => '數', + '索' => '索', + '參' => '參', + '塞' => '塞', + '省' => '省', + '葉' => '葉', + '說' => '說', + '殺' => '殺', + '辰' => '辰', + '沈' => '沈', + '拾' => '拾', + '若' => '若', + '掠' => '掠', + '略' => '略', + '亮' => '亮', + '兩' => '兩', + '凉' => '凉', + '梁' => '梁', + '糧' => '糧', + '良' => '良', + '諒' => '諒', + '量' => '量', + '勵' => '勵', + '呂' => '呂', + '女' => '女', + '廬' => '廬', + '旅' => '旅', + '濾' => '濾', + '礪' => '礪', + '閭' => '閭', + '驪' => '驪', + '麗' => '麗', + '黎' => '黎', + '力' => '力', + '曆' => '曆', + '歷' => '歷', + '轢' => '轢', + '年' => '年', + '憐' => '憐', + '戀' => '戀', + '撚' => '撚', + '漣' => '漣', + '煉' => '煉', + '璉' => '璉', + '秊' => '秊', + '練' => '練', + '聯' => '聯', + '輦' => '輦', + '蓮' => '蓮', + '連' => '連', + '鍊' => '鍊', + '列' => '列', + '劣' => '劣', + '咽' => '咽', + '烈' => '烈', + '裂' => '裂', + '說' => '說', + '廉' => '廉', + '念' => '念', + '捻' => '捻', + '殮' => '殮', + '簾' => '簾', + '獵' => '獵', + '令' => '令', + '囹' => '囹', + '寧' => '寧', + '嶺' => '嶺', + '怜' => '怜', + '玲' => '玲', + '瑩' => '瑩', + '羚' => '羚', + '聆' => '聆', + '鈴' => '鈴', + '零' => '零', + '靈' => '靈', + '領' => '領', + '例' => '例', + '禮' => '禮', + '醴' => '醴', + '隸' => '隸', + '惡' => '惡', + '了' => '了', + '僚' => '僚', + '寮' => '寮', + '尿' => '尿', + '料' => '料', + '樂' => '樂', + '燎' => '燎', + '療' => '療', + '蓼' => '蓼', + '遼' => '遼', + '龍' => '龍', + '暈' => '暈', + '阮' => '阮', + '劉' => '劉', + '杻' => '杻', + '柳' => '柳', + '流' => '流', + '溜' => '溜', + '琉' => '琉', + '留' => '留', + '硫' => '硫', + '紐' => '紐', + '類' => '類', + '六' => '六', + '戮' => '戮', + '陸' => '陸', + '倫' => '倫', + '崙' => '崙', + '淪' => '淪', + '輪' => '輪', + '律' => '律', + '慄' => '慄', + '栗' => '栗', + '率' => '率', + '隆' => '隆', + '利' => '利', + '吏' => '吏', + '履' => '履', + '易' => '易', + '李' => '李', + '梨' => '梨', + '泥' => '泥', + '理' => '理', + '痢' => '痢', + '罹' => '罹', + '裏' => '裏', + '裡' => '裡', + '里' => '里', + '離' => '離', + '匿' => '匿', + '溺' => '溺', + '吝' => '吝', + '燐' => '燐', + '璘' => '璘', + '藺' => '藺', + '隣' => '隣', + '鱗' => '鱗', + '麟' => '麟', + '林' => '林', + '淋' => '淋', + '臨' => '臨', + '立' => '立', + '笠' => '笠', + '粒' => '粒', + '狀' => '狀', + '炙' => '炙', + '識' => '識', + '什' => '什', + '茶' => '茶', + '刺' => '刺', + '切' => '切', + '度' => '度', + '拓' => '拓', + '糖' => '糖', + '宅' => '宅', + '洞' => '洞', + '暴' => '暴', + '輻' => '輻', + '行' => '行', + '降' => '降', + '見' => '見', + '廓' => '廓', + '兀' => '兀', + '嗀' => '嗀', + '塚' => '塚', + '晴' => '晴', + '凞' => '凞', + '猪' => '猪', + '益' => '益', + '礼' => '礼', + '神' => '神', + '祥' => '祥', + '福' => '福', + '靖' => '靖', + '精' => '精', + '羽' => '羽', + '蘒' => '蘒', + '諸' => '諸', + '逸' => '逸', + '都' => '都', + '飯' => '飯', + '飼' => '飼', + '館' => '館', + '鶴' => '鶴', + '郞' => '郞', + '隷' => '隷', + '侮' => '侮', + '僧' => '僧', + '免' => '免', + '勉' => '勉', + '勤' => '勤', + '卑' => '卑', + '喝' => '喝', + '嘆' => '嘆', + '器' => '器', + '塀' => '塀', + '墨' => '墨', + '層' => '層', + '屮' => '屮', + '悔' => '悔', + '慨' => '慨', + '憎' => '憎', + '懲' => '懲', + '敏' => '敏', + '既' => '既', + '暑' => '暑', + '梅' => '梅', + '海' => '海', + '渚' => '渚', + '漢' => '漢', + '煮' => '煮', + '爫' => '爫', + '琢' => '琢', + '碑' => '碑', + '社' => '社', + '祉' => '祉', + '祈' => '祈', + '祐' => '祐', + '祖' => '祖', + '祝' => '祝', + '禍' => '禍', + '禎' => '禎', + '穀' => '穀', + '突' => '突', + '節' => '節', + '練' => '練', + '縉' => '縉', + '繁' => '繁', + '署' => '署', + '者' => '者', + '臭' => '臭', + '艹' => '艹', + '艹' => '艹', + '著' => '著', + '褐' => '褐', + '視' => '視', + '謁' => '謁', + '謹' => '謹', + '賓' => '賓', + '贈' => '贈', + '辶' => '辶', + '逸' => '逸', + '難' => '難', + '響' => '響', + '頻' => '頻', + '恵' => '恵', + '𤋮' => '𤋮', + '舘' => '舘', + '並' => '並', + '况' => '况', + '全' => '全', + '侀' => '侀', + '充' => '充', + '冀' => '冀', + '勇' => '勇', + '勺' => '勺', + '喝' => '喝', + '啕' => '啕', + '喙' => '喙', + '嗢' => '嗢', + '塚' => '塚', + '墳' => '墳', + '奄' => '奄', + '奔' => '奔', + '婢' => '婢', + '嬨' => '嬨', + '廒' => '廒', + '廙' => '廙', + '彩' => '彩', + '徭' => '徭', + '惘' => '惘', + '慎' => '慎', + '愈' => '愈', + '憎' => '憎', + '慠' => '慠', + '懲' => '懲', + '戴' => '戴', + '揄' => '揄', + '搜' => '搜', + '摒' => '摒', + '敖' => '敖', + '晴' => '晴', + '朗' => '朗', + '望' => '望', + '杖' => '杖', + '歹' => '歹', + '殺' => '殺', + '流' => '流', + '滛' => '滛', + '滋' => '滋', + '漢' => '漢', + '瀞' => '瀞', + '煮' => '煮', + '瞧' => '瞧', + '爵' => '爵', + '犯' => '犯', + '猪' => '猪', + '瑱' => '瑱', + '甆' => '甆', + '画' => '画', + '瘝' => '瘝', + '瘟' => '瘟', + '益' => '益', + '盛' => '盛', + '直' => '直', + '睊' => '睊', + '着' => '着', + '磌' => '磌', + '窱' => '窱', + '節' => '節', + '类' => '类', + '絛' => '絛', + '練' => '練', + '缾' => '缾', + '者' => '者', + '荒' => '荒', + '華' => '華', + '蝹' => '蝹', + '襁' => '襁', + '覆' => '覆', + '視' => '視', + '調' => '調', + '諸' => '諸', + '請' => '請', + '謁' => '謁', + '諾' => '諾', + '諭' => '諭', + '謹' => '謹', + '變' => '變', + '贈' => '贈', + '輸' => '輸', + '遲' => '遲', + '醙' => '醙', + '鉶' => '鉶', + '陼' => '陼', + '難' => '難', + '靖' => '靖', + '韛' => '韛', + '響' => '響', + '頋' => '頋', + '頻' => '頻', + '鬒' => '鬒', + '龜' => '龜', + '𢡊' => '𢡊', + '𢡄' => '𢡄', + '𣏕' => '𣏕', + '㮝' => '㮝', + '䀘' => '䀘', + '䀹' => '䀹', + '𥉉' => '𥉉', + '𥳐' => '𥳐', + '𧻓' => '𧻓', + '齃' => '齃', + '龎' => '龎', + 'יִ' => 'יִ', + 'ײַ' => 'ײַ', + 'שׁ' => 'שׁ', + 'שׂ' => 'שׂ', + 'שּׁ' => 'שּׁ', + 'שּׂ' => 'שּׂ', + 'אַ' => 'אַ', + 'אָ' => 'אָ', + 'אּ' => 'אּ', + 'בּ' => 'בּ', + 'גּ' => 'גּ', + 'דּ' => 'דּ', + 'הּ' => 'הּ', + 'וּ' => 'וּ', + 'זּ' => 'זּ', + 'טּ' => 'טּ', + 'יּ' => 'יּ', + 'ךּ' => 'ךּ', + 'כּ' => 'כּ', + 'לּ' => 'לּ', + 'מּ' => 'מּ', + 'נּ' => 'נּ', + 'סּ' => 'סּ', + 'ףּ' => 'ףּ', + 'פּ' => 'פּ', + 'צּ' => 'צּ', + 'קּ' => 'קּ', + 'רּ' => 'רּ', + 'שּ' => 'שּ', + 'תּ' => 'תּ', + 'וֹ' => 'וֹ', + 'בֿ' => 'בֿ', + 'כֿ' => 'כֿ', + 'פֿ' => 'פֿ', + '𑂚' => '𑂚', + '𑂜' => '𑂜', + '𑂫' => '𑂫', + '𑄮' => '𑄮', + '𑄯' => '𑄯', + '𑍋' => '𑍋', + '𑍌' => '𑍌', + '𑒻' => '𑒻', + '𑒼' => '𑒼', + '𑒾' => '𑒾', + '𑖺' => '𑖺', + '𑖻' => '𑖻', + '𑤸' => '𑤸', + '𝅗𝅥' => '𝅗𝅥', + '𝅘𝅥' => '𝅘𝅥', + '𝅘𝅥𝅮' => '𝅘𝅥𝅮', + '𝅘𝅥𝅯' => '𝅘𝅥𝅯', + '𝅘𝅥𝅰' => '𝅘𝅥𝅰', + '𝅘𝅥𝅱' => '𝅘𝅥𝅱', + '𝅘𝅥𝅲' => '𝅘𝅥𝅲', + '𝆹𝅥' => '𝆹𝅥', + '𝆺𝅥' => '𝆺𝅥', + '𝆹𝅥𝅮' => '𝆹𝅥𝅮', + '𝆺𝅥𝅮' => '𝆺𝅥𝅮', + '𝆹𝅥𝅯' => '𝆹𝅥𝅯', + '𝆺𝅥𝅯' => '𝆺𝅥𝅯', + '丽' => '丽', + '丸' => '丸', + '乁' => '乁', + '𠄢' => '𠄢', + '你' => '你', + '侮' => '侮', + '侻' => '侻', + '倂' => '倂', + '偺' => '偺', + '備' => '備', + '僧' => '僧', + '像' => '像', + '㒞' => '㒞', + '𠘺' => '𠘺', + '免' => '免', + '兔' => '兔', + '兤' => '兤', + '具' => '具', + '𠔜' => '𠔜', + '㒹' => '㒹', + '內' => '內', + '再' => '再', + '𠕋' => '𠕋', + '冗' => '冗', + '冤' => '冤', + '仌' => '仌', + '冬' => '冬', + '况' => '况', + '𩇟' => '𩇟', + '凵' => '凵', + '刃' => '刃', + '㓟' => '㓟', + '刻' => '刻', + '剆' => '剆', + '割' => '割', + '剷' => '剷', + '㔕' => '㔕', + '勇' => '勇', + '勉' => '勉', + '勤' => '勤', + '勺' => '勺', + '包' => '包', + '匆' => '匆', + '北' => '北', + '卉' => '卉', + '卑' => '卑', + '博' => '博', + '即' => '即', + '卽' => '卽', + '卿' => '卿', + '卿' => '卿', + '卿' => '卿', + '𠨬' => '𠨬', + '灰' => '灰', + '及' => '及', + '叟' => '叟', + '𠭣' => '𠭣', + '叫' => '叫', + '叱' => '叱', + '吆' => '吆', + '咞' => '咞', + '吸' => '吸', + '呈' => '呈', + '周' => '周', + '咢' => '咢', + '哶' => '哶', + '唐' => '唐', + '啓' => '啓', + '啣' => '啣', + '善' => '善', + '善' => '善', + '喙' => '喙', + '喫' => '喫', + '喳' => '喳', + '嗂' => '嗂', + '圖' => '圖', + '嘆' => '嘆', + '圗' => '圗', + '噑' => '噑', + '噴' => '噴', + '切' => '切', + '壮' => '壮', + '城' => '城', + '埴' => '埴', + '堍' => '堍', + '型' => '型', + '堲' => '堲', + '報' => '報', + '墬' => '墬', + '𡓤' => '𡓤', + '売' => '売', + '壷' => '壷', + '夆' => '夆', + '多' => '多', + '夢' => '夢', + '奢' => '奢', + '𡚨' => '𡚨', + '𡛪' => '𡛪', + '姬' => '姬', + '娛' => '娛', + '娧' => '娧', + '姘' => '姘', + '婦' => '婦', + '㛮' => '㛮', + '㛼' => '㛼', + '嬈' => '嬈', + '嬾' => '嬾', + '嬾' => '嬾', + '𡧈' => '𡧈', + '寃' => '寃', + '寘' => '寘', + '寧' => '寧', + '寳' => '寳', + '𡬘' => '𡬘', + '寿' => '寿', + '将' => '将', + '当' => '当', + '尢' => '尢', + '㞁' => '㞁', + '屠' => '屠', + '屮' => '屮', + '峀' => '峀', + '岍' => '岍', + '𡷤' => '𡷤', + '嵃' => '嵃', + '𡷦' => '𡷦', + '嵮' => '嵮', + '嵫' => '嵫', + '嵼' => '嵼', + '巡' => '巡', + '巢' => '巢', + '㠯' => '㠯', + '巽' => '巽', + '帨' => '帨', + '帽' => '帽', + '幩' => '幩', + '㡢' => '㡢', + '𢆃' => '𢆃', + '㡼' => '㡼', + '庰' => '庰', + '庳' => '庳', + '庶' => '庶', + '廊' => '廊', + '𪎒' => '𪎒', + '廾' => '廾', + '𢌱' => '𢌱', + '𢌱' => '𢌱', + '舁' => '舁', + '弢' => '弢', + '弢' => '弢', + '㣇' => '㣇', + '𣊸' => '𣊸', + '𦇚' => '𦇚', + '形' => '形', + '彫' => '彫', + '㣣' => '㣣', + '徚' => '徚', + '忍' => '忍', + '志' => '志', + '忹' => '忹', + '悁' => '悁', + '㤺' => '㤺', + '㤜' => '㤜', + '悔' => '悔', + '𢛔' => '𢛔', + '惇' => '惇', + '慈' => '慈', + '慌' => '慌', + '慎' => '慎', + '慌' => '慌', + '慺' => '慺', + '憎' => '憎', + '憲' => '憲', + '憤' => '憤', + '憯' => '憯', + '懞' => '懞', + '懲' => '懲', + '懶' => '懶', + '成' => '成', + '戛' => '戛', + '扝' => '扝', + '抱' => '抱', + '拔' => '拔', + '捐' => '捐', + '𢬌' => '𢬌', + '挽' => '挽', + '拼' => '拼', + '捨' => '捨', + '掃' => '掃', + '揤' => '揤', + '𢯱' => '𢯱', + '搢' => '搢', + '揅' => '揅', + '掩' => '掩', + '㨮' => '㨮', + '摩' => '摩', + '摾' => '摾', + '撝' => '撝', + '摷' => '摷', + '㩬' => '㩬', + '敏' => '敏', + '敬' => '敬', + '𣀊' => '𣀊', + '旣' => '旣', + '書' => '書', + '晉' => '晉', + '㬙' => '㬙', + '暑' => '暑', + '㬈' => '㬈', + '㫤' => '㫤', + '冒' => '冒', + '冕' => '冕', + '最' => '最', + '暜' => '暜', + '肭' => '肭', + '䏙' => '䏙', + '朗' => '朗', + '望' => '望', + '朡' => '朡', + '杞' => '杞', + '杓' => '杓', + '𣏃' => '𣏃', + '㭉' => '㭉', + '柺' => '柺', + '枅' => '枅', + '桒' => '桒', + '梅' => '梅', + '𣑭' => '𣑭', + '梎' => '梎', + '栟' => '栟', + '椔' => '椔', + '㮝' => '㮝', + '楂' => '楂', + '榣' => '榣', + '槪' => '槪', + '檨' => '檨', + '𣚣' => '𣚣', + '櫛' => '櫛', + '㰘' => '㰘', + '次' => '次', + '𣢧' => '𣢧', + '歔' => '歔', + '㱎' => '㱎', + '歲' => '歲', + '殟' => '殟', + '殺' => '殺', + '殻' => '殻', + '𣪍' => '𣪍', + '𡴋' => '𡴋', + '𣫺' => '𣫺', + '汎' => '汎', + '𣲼' => '𣲼', + '沿' => '沿', + '泍' => '泍', + '汧' => '汧', + '洖' => '洖', + '派' => '派', + '海' => '海', + '流' => '流', + '浩' => '浩', + '浸' => '浸', + '涅' => '涅', + '𣴞' => '𣴞', + '洴' => '洴', + '港' => '港', + '湮' => '湮', + '㴳' => '㴳', + '滋' => '滋', + '滇' => '滇', + '𣻑' => '𣻑', + '淹' => '淹', + '潮' => '潮', + '𣽞' => '𣽞', + '𣾎' => '𣾎', + '濆' => '濆', + '瀹' => '瀹', + '瀞' => '瀞', + '瀛' => '瀛', + '㶖' => '㶖', + '灊' => '灊', + '災' => '災', + '灷' => '灷', + '炭' => '炭', + '𠔥' => '𠔥', + '煅' => '煅', + '𤉣' => '𤉣', + '熜' => '熜', + '𤎫' => '𤎫', + '爨' => '爨', + '爵' => '爵', + '牐' => '牐', + '𤘈' => '𤘈', + '犀' => '犀', + '犕' => '犕', + '𤜵' => '𤜵', + '𤠔' => '𤠔', + '獺' => '獺', + '王' => '王', + '㺬' => '㺬', + '玥' => '玥', + '㺸' => '㺸', + '㺸' => '㺸', + '瑇' => '瑇', + '瑜' => '瑜', + '瑱' => '瑱', + '璅' => '璅', + '瓊' => '瓊', + '㼛' => '㼛', + '甤' => '甤', + '𤰶' => '𤰶', + '甾' => '甾', + '𤲒' => '𤲒', + '異' => '異', + '𢆟' => '𢆟', + '瘐' => '瘐', + '𤾡' => '𤾡', + '𤾸' => '𤾸', + '𥁄' => '𥁄', + '㿼' => '㿼', + '䀈' => '䀈', + '直' => '直', + '𥃳' => '𥃳', + '𥃲' => '𥃲', + '𥄙' => '𥄙', + '𥄳' => '𥄳', + '眞' => '眞', + '真' => '真', + '真' => '真', + '睊' => '睊', + '䀹' => '䀹', + '瞋' => '瞋', + '䁆' => '䁆', + '䂖' => '䂖', + '𥐝' => '𥐝', + '硎' => '硎', + '碌' => '碌', + '磌' => '磌', + '䃣' => '䃣', + '𥘦' => '𥘦', + '祖' => '祖', + '𥚚' => '𥚚', + '𥛅' => '𥛅', + '福' => '福', + '秫' => '秫', + '䄯' => '䄯', + '穀' => '穀', + '穊' => '穊', + '穏' => '穏', + '𥥼' => '𥥼', + '𥪧' => '𥪧', + '𥪧' => '𥪧', + '竮' => '竮', + '䈂' => '䈂', + '𥮫' => '𥮫', + '篆' => '篆', + '築' => '築', + '䈧' => '䈧', + '𥲀' => '𥲀', + '糒' => '糒', + '䊠' => '䊠', + '糨' => '糨', + '糣' => '糣', + '紀' => '紀', + '𥾆' => '𥾆', + '絣' => '絣', + '䌁' => '䌁', + '緇' => '緇', + '縂' => '縂', + '繅' => '繅', + '䌴' => '䌴', + '𦈨' => '𦈨', + '𦉇' => '𦉇', + '䍙' => '䍙', + '𦋙' => '𦋙', + '罺' => '罺', + '𦌾' => '𦌾', + '羕' => '羕', + '翺' => '翺', + '者' => '者', + '𦓚' => '𦓚', + '𦔣' => '𦔣', + '聠' => '聠', + '𦖨' => '𦖨', + '聰' => '聰', + '𣍟' => '𣍟', + '䏕' => '䏕', + '育' => '育', + '脃' => '脃', + '䐋' => '䐋', + '脾' => '脾', + '媵' => '媵', + '𦞧' => '𦞧', + '𦞵' => '𦞵', + '𣎓' => '𣎓', + '𣎜' => '𣎜', + '舁' => '舁', + '舄' => '舄', + '辞' => '辞', + '䑫' => '䑫', + '芑' => '芑', + '芋' => '芋', + '芝' => '芝', + '劳' => '劳', + '花' => '花', + '芳' => '芳', + '芽' => '芽', + '苦' => '苦', + '𦬼' => '𦬼', + '若' => '若', + '茝' => '茝', + '荣' => '荣', + '莭' => '莭', + '茣' => '茣', + '莽' => '莽', + '菧' => '菧', + '著' => '著', + '荓' => '荓', + '菊' => '菊', + '菌' => '菌', + '菜' => '菜', + '𦰶' => '𦰶', + '𦵫' => '𦵫', + '𦳕' => '𦳕', + '䔫' => '䔫', + '蓱' => '蓱', + '蓳' => '蓳', + '蔖' => '蔖', + '𧏊' => '𧏊', + '蕤' => '蕤', + '𦼬' => '𦼬', + '䕝' => '䕝', + '䕡' => '䕡', + '𦾱' => '𦾱', + '𧃒' => '𧃒', + '䕫' => '䕫', + '虐' => '虐', + '虜' => '虜', + '虧' => '虧', + '虩' => '虩', + '蚩' => '蚩', + '蚈' => '蚈', + '蜎' => '蜎', + '蛢' => '蛢', + '蝹' => '蝹', + '蜨' => '蜨', + '蝫' => '蝫', + '螆' => '螆', + '䗗' => '䗗', + '蟡' => '蟡', + '蠁' => '蠁', + '䗹' => '䗹', + '衠' => '衠', + '衣' => '衣', + '𧙧' => '𧙧', + '裗' => '裗', + '裞' => '裞', + '䘵' => '䘵', + '裺' => '裺', + '㒻' => '㒻', + '𧢮' => '𧢮', + '𧥦' => '𧥦', + '䚾' => '䚾', + '䛇' => '䛇', + '誠' => '誠', + '諭' => '諭', + '變' => '變', + '豕' => '豕', + '𧲨' => '𧲨', + '貫' => '貫', + '賁' => '賁', + '贛' => '贛', + '起' => '起', + '𧼯' => '𧼯', + '𠠄' => '𠠄', + '跋' => '跋', + '趼' => '趼', + '跰' => '跰', + '𠣞' => '𠣞', + '軔' => '軔', + '輸' => '輸', + '𨗒' => '𨗒', + '𨗭' => '𨗭', + '邔' => '邔', + '郱' => '郱', + '鄑' => '鄑', + '𨜮' => '𨜮', + '鄛' => '鄛', + '鈸' => '鈸', + '鋗' => '鋗', + '鋘' => '鋘', + '鉼' => '鉼', + '鏹' => '鏹', + '鐕' => '鐕', + '𨯺' => '𨯺', + '開' => '開', + '䦕' => '䦕', + '閷' => '閷', + '𨵷' => '𨵷', + '䧦' => '䧦', + '雃' => '雃', + '嶲' => '嶲', + '霣' => '霣', + '𩅅' => '𩅅', + '𩈚' => '𩈚', + '䩮' => '䩮', + '䩶' => '䩶', + '韠' => '韠', + '𩐊' => '𩐊', + '䪲' => '䪲', + '𩒖' => '𩒖', + '頋' => '頋', + '頋' => '頋', + '頩' => '頩', + '𩖶' => '𩖶', + '飢' => '飢', + '䬳' => '䬳', + '餩' => '餩', + '馧' => '馧', + '駂' => '駂', + '駾' => '駾', + '䯎' => '䯎', + '𩬰' => '𩬰', + '鬒' => '鬒', + '鱀' => '鱀', + '鳽' => '鳽', + '䳎' => '䳎', + '䳭' => '䳭', + '鵧' => '鵧', + '𪃎' => '𪃎', + '䳸' => '䳸', + '𪄅' => '𪄅', + '𪈎' => '𪈎', + '𪊑' => '𪊑', + '麻' => '麻', + '䵖' => '䵖', + '黹' => '黹', + '黾' => '黾', + '鼅' => '鼅', + '鼏' => '鼏', + '鼖' => '鼖', + '鼻' => '鼻', + '𪘀' => '𪘀', +); diff --git a/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/combiningClass.php b/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/combiningClass.php new file mode 100644 index 00000000..ec90f36e --- /dev/null +++ b/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/combiningClass.php @@ -0,0 +1,876 @@ + 230, + '́' => 230, + '̂' => 230, + '̃' => 230, + '̄' => 230, + '̅' => 230, + '̆' => 230, + '̇' => 230, + '̈' => 230, + '̉' => 230, + '̊' => 230, + '̋' => 230, + '̌' => 230, + '̍' => 230, + '̎' => 230, + '̏' => 230, + '̐' => 230, + '̑' => 230, + '̒' => 230, + '̓' => 230, + '̔' => 230, + '̕' => 232, + '̖' => 220, + '̗' => 220, + '̘' => 220, + '̙' => 220, + '̚' => 232, + '̛' => 216, + '̜' => 220, + '̝' => 220, + '̞' => 220, + '̟' => 220, + '̠' => 220, + '̡' => 202, + '̢' => 202, + '̣' => 220, + '̤' => 220, + '̥' => 220, + '̦' => 220, + '̧' => 202, + '̨' => 202, + '̩' => 220, + '̪' => 220, + '̫' => 220, + '̬' => 220, + '̭' => 220, + '̮' => 220, + '̯' => 220, + '̰' => 220, + '̱' => 220, + '̲' => 220, + '̳' => 220, + '̴' => 1, + '̵' => 1, + '̶' => 1, + '̷' => 1, + '̸' => 1, + '̹' => 220, + '̺' => 220, + '̻' => 220, + '̼' => 220, + '̽' => 230, + '̾' => 230, + '̿' => 230, + '̀' => 230, + '́' => 230, + '͂' => 230, + '̓' => 230, + '̈́' => 230, + 'ͅ' => 240, + '͆' => 230, + '͇' => 220, + '͈' => 220, + '͉' => 220, + '͊' => 230, + '͋' => 230, + '͌' => 230, + '͍' => 220, + '͎' => 220, + '͐' => 230, + '͑' => 230, + '͒' => 230, + '͓' => 220, + '͔' => 220, + '͕' => 220, + '͖' => 220, + '͗' => 230, + '͘' => 232, + '͙' => 220, + '͚' => 220, + '͛' => 230, + '͜' => 233, + '͝' => 234, + '͞' => 234, + '͟' => 233, + '͠' => 234, + '͡' => 234, + '͢' => 233, + 'ͣ' => 230, + 'ͤ' => 230, + 'ͥ' => 230, + 'ͦ' => 230, + 'ͧ' => 230, + 'ͨ' => 230, + 'ͩ' => 230, + 'ͪ' => 230, + 'ͫ' => 230, + 'ͬ' => 230, + 'ͭ' => 230, + 'ͮ' => 230, + 'ͯ' => 230, + '҃' => 230, + '҄' => 230, + '҅' => 230, + '҆' => 230, + '҇' => 230, + '֑' => 220, + '֒' => 230, + '֓' => 230, + '֔' => 230, + '֕' => 230, + '֖' => 220, + '֗' => 230, + '֘' => 230, + '֙' => 230, + '֚' => 222, + '֛' => 220, + '֜' => 230, + '֝' => 230, + '֞' => 230, + '֟' => 230, + '֠' => 230, + '֡' => 230, + '֢' => 220, + '֣' => 220, + '֤' => 220, + '֥' => 220, + '֦' => 220, + '֧' => 220, + '֨' => 230, + '֩' => 230, + '֪' => 220, + '֫' => 230, + '֬' => 230, + '֭' => 222, + '֮' => 228, + '֯' => 230, + 'ְ' => 10, + 'ֱ' => 11, + 'ֲ' => 12, + 'ֳ' => 13, + 'ִ' => 14, + 'ֵ' => 15, + 'ֶ' => 16, + 'ַ' => 17, + 'ָ' => 18, + 'ֹ' => 19, + 'ֺ' => 19, + 'ֻ' => 20, + 'ּ' => 21, + 'ֽ' => 22, + 'ֿ' => 23, + 'ׁ' => 24, + 'ׂ' => 25, + 'ׄ' => 230, + 'ׅ' => 220, + 'ׇ' => 18, + 'ؐ' => 230, + 'ؑ' => 230, + 'ؒ' => 230, + 'ؓ' => 230, + 'ؔ' => 230, + 'ؕ' => 230, + 'ؖ' => 230, + 'ؗ' => 230, + 'ؘ' => 30, + 'ؙ' => 31, + 'ؚ' => 32, + 'ً' => 27, + 'ٌ' => 28, + 'ٍ' => 29, + 'َ' => 30, + 'ُ' => 31, + 'ِ' => 32, + 'ّ' => 33, + 'ْ' => 34, + 'ٓ' => 230, + 'ٔ' => 230, + 'ٕ' => 220, + 'ٖ' => 220, + 'ٗ' => 230, + '٘' => 230, + 'ٙ' => 230, + 'ٚ' => 230, + 'ٛ' => 230, + 'ٜ' => 220, + 'ٝ' => 230, + 'ٞ' => 230, + 'ٟ' => 220, + 'ٰ' => 35, + 'ۖ' => 230, + 'ۗ' => 230, + 'ۘ' => 230, + 'ۙ' => 230, + 'ۚ' => 230, + 'ۛ' => 230, + 'ۜ' => 230, + '۟' => 230, + '۠' => 230, + 'ۡ' => 230, + 'ۢ' => 230, + 'ۣ' => 220, + 'ۤ' => 230, + 'ۧ' => 230, + 'ۨ' => 230, + '۪' => 220, + '۫' => 230, + '۬' => 230, + 'ۭ' => 220, + 'ܑ' => 36, + 'ܰ' => 230, + 'ܱ' => 220, + 'ܲ' => 230, + 'ܳ' => 230, + 'ܴ' => 220, + 'ܵ' => 230, + 'ܶ' => 230, + 'ܷ' => 220, + 'ܸ' => 220, + 'ܹ' => 220, + 'ܺ' => 230, + 'ܻ' => 220, + 'ܼ' => 220, + 'ܽ' => 230, + 'ܾ' => 220, + 'ܿ' => 230, + '݀' => 230, + '݁' => 230, + '݂' => 220, + '݃' => 230, + '݄' => 220, + '݅' => 230, + '݆' => 220, + '݇' => 230, + '݈' => 220, + '݉' => 230, + '݊' => 230, + '߫' => 230, + '߬' => 230, + '߭' => 230, + '߮' => 230, + '߯' => 230, + '߰' => 230, + '߱' => 230, + '߲' => 220, + '߳' => 230, + '߽' => 220, + 'ࠖ' => 230, + 'ࠗ' => 230, + '࠘' => 230, + '࠙' => 230, + 'ࠛ' => 230, + 'ࠜ' => 230, + 'ࠝ' => 230, + 'ࠞ' => 230, + 'ࠟ' => 230, + 'ࠠ' => 230, + 'ࠡ' => 230, + 'ࠢ' => 230, + 'ࠣ' => 230, + 'ࠥ' => 230, + 'ࠦ' => 230, + 'ࠧ' => 230, + 'ࠩ' => 230, + 'ࠪ' => 230, + 'ࠫ' => 230, + 'ࠬ' => 230, + '࠭' => 230, + '࡙' => 220, + '࡚' => 220, + '࡛' => 220, + '࣓' => 220, + 'ࣔ' => 230, + 'ࣕ' => 230, + 'ࣖ' => 230, + 'ࣗ' => 230, + 'ࣘ' => 230, + 'ࣙ' => 230, + 'ࣚ' => 230, + 'ࣛ' => 230, + 'ࣜ' => 230, + 'ࣝ' => 230, + 'ࣞ' => 230, + 'ࣟ' => 230, + '࣠' => 230, + '࣡' => 230, + 'ࣣ' => 220, + 'ࣤ' => 230, + 'ࣥ' => 230, + 'ࣦ' => 220, + 'ࣧ' => 230, + 'ࣨ' => 230, + 'ࣩ' => 220, + '࣪' => 230, + '࣫' => 230, + '࣬' => 230, + '࣭' => 220, + '࣮' => 220, + '࣯' => 220, + 'ࣰ' => 27, + 'ࣱ' => 28, + 'ࣲ' => 29, + 'ࣳ' => 230, + 'ࣴ' => 230, + 'ࣵ' => 230, + 'ࣶ' => 220, + 'ࣷ' => 230, + 'ࣸ' => 230, + 'ࣹ' => 220, + 'ࣺ' => 220, + 'ࣻ' => 230, + 'ࣼ' => 230, + 'ࣽ' => 230, + 'ࣾ' => 230, + 'ࣿ' => 230, + '़' => 7, + '्' => 9, + '॑' => 230, + '॒' => 220, + '॓' => 230, + '॔' => 230, + '়' => 7, + '্' => 9, + '৾' => 230, + '਼' => 7, + '੍' => 9, + '઼' => 7, + '્' => 9, + '଼' => 7, + '୍' => 9, + '்' => 9, + '్' => 9, + 'ౕ' => 84, + 'ౖ' => 91, + '಼' => 7, + '್' => 9, + '഻' => 9, + '഼' => 9, + '്' => 9, + '්' => 9, + 'ุ' => 103, + 'ู' => 103, + 'ฺ' => 9, + '่' => 107, + '้' => 107, + '๊' => 107, + '๋' => 107, + 'ຸ' => 118, + 'ູ' => 118, + '຺' => 9, + '່' => 122, + '້' => 122, + '໊' => 122, + '໋' => 122, + '༘' => 220, + '༙' => 220, + '༵' => 220, + '༷' => 220, + '༹' => 216, + 'ཱ' => 129, + 'ི' => 130, + 'ུ' => 132, + 'ེ' => 130, + 'ཻ' => 130, + 'ོ' => 130, + 'ཽ' => 130, + 'ྀ' => 130, + 'ྂ' => 230, + 'ྃ' => 230, + '྄' => 9, + '྆' => 230, + '྇' => 230, + '࿆' => 220, + '့' => 7, + '္' => 9, + '်' => 9, + 'ႍ' => 220, + '፝' => 230, + '፞' => 230, + '፟' => 230, + '᜔' => 9, + '᜴' => 9, + '្' => 9, + '៝' => 230, + 'ᢩ' => 228, + '᤹' => 222, + '᤺' => 230, + '᤻' => 220, + 'ᨗ' => 230, + 'ᨘ' => 220, + '᩠' => 9, + '᩵' => 230, + '᩶' => 230, + '᩷' => 230, + '᩸' => 230, + '᩹' => 230, + '᩺' => 230, + '᩻' => 230, + '᩼' => 230, + '᩿' => 220, + '᪰' => 230, + '᪱' => 230, + '᪲' => 230, + '᪳' => 230, + '᪴' => 230, + '᪵' => 220, + '᪶' => 220, + '᪷' => 220, + '᪸' => 220, + '᪹' => 220, + '᪺' => 220, + '᪻' => 230, + '᪼' => 230, + '᪽' => 220, + 'ᪿ' => 220, + 'ᫀ' => 220, + '᬴' => 7, + '᭄' => 9, + '᭫' => 230, + '᭬' => 220, + '᭭' => 230, + '᭮' => 230, + '᭯' => 230, + '᭰' => 230, + '᭱' => 230, + '᭲' => 230, + '᭳' => 230, + '᮪' => 9, + '᮫' => 9, + '᯦' => 7, + '᯲' => 9, + '᯳' => 9, + '᰷' => 7, + '᳐' => 230, + '᳑' => 230, + '᳒' => 230, + '᳔' => 1, + '᳕' => 220, + '᳖' => 220, + '᳗' => 220, + '᳘' => 220, + '᳙' => 220, + '᳚' => 230, + '᳛' => 230, + '᳜' => 220, + '᳝' => 220, + '᳞' => 220, + '᳟' => 220, + '᳠' => 230, + '᳢' => 1, + '᳣' => 1, + '᳤' => 1, + '᳥' => 1, + '᳦' => 1, + '᳧' => 1, + '᳨' => 1, + '᳭' => 220, + '᳴' => 230, + '᳸' => 230, + '᳹' => 230, + '᷀' => 230, + '᷁' => 230, + '᷂' => 220, + '᷃' => 230, + '᷄' => 230, + '᷅' => 230, + '᷆' => 230, + '᷇' => 230, + '᷈' => 230, + '᷉' => 230, + '᷊' => 220, + '᷋' => 230, + '᷌' => 230, + '᷍' => 234, + '᷎' => 214, + '᷏' => 220, + '᷐' => 202, + '᷑' => 230, + '᷒' => 230, + 'ᷓ' => 230, + 'ᷔ' => 230, + 'ᷕ' => 230, + 'ᷖ' => 230, + 'ᷗ' => 230, + 'ᷘ' => 230, + 'ᷙ' => 230, + 'ᷚ' => 230, + 'ᷛ' => 230, + 'ᷜ' => 230, + 'ᷝ' => 230, + 'ᷞ' => 230, + 'ᷟ' => 230, + 'ᷠ' => 230, + 'ᷡ' => 230, + 'ᷢ' => 230, + 'ᷣ' => 230, + 'ᷤ' => 230, + 'ᷥ' => 230, + 'ᷦ' => 230, + 'ᷧ' => 230, + 'ᷨ' => 230, + 'ᷩ' => 230, + 'ᷪ' => 230, + 'ᷫ' => 230, + 'ᷬ' => 230, + 'ᷭ' => 230, + 'ᷮ' => 230, + 'ᷯ' => 230, + 'ᷰ' => 230, + 'ᷱ' => 230, + 'ᷲ' => 230, + 'ᷳ' => 230, + 'ᷴ' => 230, + '᷵' => 230, + '᷶' => 232, + '᷷' => 228, + '᷸' => 228, + '᷹' => 220, + '᷻' => 230, + '᷼' => 233, + '᷽' => 220, + '᷾' => 230, + '᷿' => 220, + '⃐' => 230, + '⃑' => 230, + '⃒' => 1, + '⃓' => 1, + '⃔' => 230, + '⃕' => 230, + '⃖' => 230, + '⃗' => 230, + '⃘' => 1, + '⃙' => 1, + '⃚' => 1, + '⃛' => 230, + '⃜' => 230, + '⃡' => 230, + '⃥' => 1, + '⃦' => 1, + '⃧' => 230, + '⃨' => 220, + '⃩' => 230, + '⃪' => 1, + '⃫' => 1, + '⃬' => 220, + '⃭' => 220, + '⃮' => 220, + '⃯' => 220, + '⃰' => 230, + '⳯' => 230, + '⳰' => 230, + '⳱' => 230, + '⵿' => 9, + 'ⷠ' => 230, + 'ⷡ' => 230, + 'ⷢ' => 230, + 'ⷣ' => 230, + 'ⷤ' => 230, + 'ⷥ' => 230, + 'ⷦ' => 230, + 'ⷧ' => 230, + 'ⷨ' => 230, + 'ⷩ' => 230, + 'ⷪ' => 230, + 'ⷫ' => 230, + 'ⷬ' => 230, + 'ⷭ' => 230, + 'ⷮ' => 230, + 'ⷯ' => 230, + 'ⷰ' => 230, + 'ⷱ' => 230, + 'ⷲ' => 230, + 'ⷳ' => 230, + 'ⷴ' => 230, + 'ⷵ' => 230, + 'ⷶ' => 230, + 'ⷷ' => 230, + 'ⷸ' => 230, + 'ⷹ' => 230, + 'ⷺ' => 230, + 'ⷻ' => 230, + 'ⷼ' => 230, + 'ⷽ' => 230, + 'ⷾ' => 230, + 'ⷿ' => 230, + '〪' => 218, + '〫' => 228, + '〬' => 232, + '〭' => 222, + '〮' => 224, + '〯' => 224, + '゙' => 8, + '゚' => 8, + '꙯' => 230, + 'ꙴ' => 230, + 'ꙵ' => 230, + 'ꙶ' => 230, + 'ꙷ' => 230, + 'ꙸ' => 230, + 'ꙹ' => 230, + 'ꙺ' => 230, + 'ꙻ' => 230, + '꙼' => 230, + '꙽' => 230, + 'ꚞ' => 230, + 'ꚟ' => 230, + '꛰' => 230, + '꛱' => 230, + '꠆' => 9, + '꠬' => 9, + '꣄' => 9, + '꣠' => 230, + '꣡' => 230, + '꣢' => 230, + '꣣' => 230, + '꣤' => 230, + '꣥' => 230, + '꣦' => 230, + '꣧' => 230, + '꣨' => 230, + '꣩' => 230, + '꣪' => 230, + '꣫' => 230, + '꣬' => 230, + '꣭' => 230, + '꣮' => 230, + '꣯' => 230, + '꣰' => 230, + '꣱' => 230, + '꤫' => 220, + '꤬' => 220, + '꤭' => 220, + '꥓' => 9, + '꦳' => 7, + '꧀' => 9, + 'ꪰ' => 230, + 'ꪲ' => 230, + 'ꪳ' => 230, + 'ꪴ' => 220, + 'ꪷ' => 230, + 'ꪸ' => 230, + 'ꪾ' => 230, + '꪿' => 230, + '꫁' => 230, + '꫶' => 9, + '꯭' => 9, + 'ﬞ' => 26, + '︠' => 230, + '︡' => 230, + '︢' => 230, + '︣' => 230, + '︤' => 230, + '︥' => 230, + '︦' => 230, + '︧' => 220, + '︨' => 220, + '︩' => 220, + '︪' => 220, + '︫' => 220, + '︬' => 220, + '︭' => 220, + '︮' => 230, + '︯' => 230, + '𐇽' => 220, + '𐋠' => 220, + '𐍶' => 230, + '𐍷' => 230, + '𐍸' => 230, + '𐍹' => 230, + '𐍺' => 230, + '𐨍' => 220, + '𐨏' => 230, + '𐨸' => 230, + '𐨹' => 1, + '𐨺' => 220, + '𐨿' => 9, + '𐫥' => 230, + '𐫦' => 220, + '𐴤' => 230, + '𐴥' => 230, + '𐴦' => 230, + '𐴧' => 230, + '𐺫' => 230, + '𐺬' => 230, + '𐽆' => 220, + '𐽇' => 220, + '𐽈' => 230, + '𐽉' => 230, + '𐽊' => 230, + '𐽋' => 220, + '𐽌' => 230, + '𐽍' => 220, + '𐽎' => 220, + '𐽏' => 220, + '𐽐' => 220, + '𑁆' => 9, + '𑁿' => 9, + '𑂹' => 9, + '𑂺' => 7, + '𑄀' => 230, + '𑄁' => 230, + '𑄂' => 230, + '𑄳' => 9, + '𑄴' => 9, + '𑅳' => 7, + '𑇀' => 9, + '𑇊' => 7, + '𑈵' => 9, + '𑈶' => 7, + '𑋩' => 7, + '𑋪' => 9, + '𑌻' => 7, + '𑌼' => 7, + '𑍍' => 9, + '𑍦' => 230, + '𑍧' => 230, + '𑍨' => 230, + '𑍩' => 230, + '𑍪' => 230, + '𑍫' => 230, + '𑍬' => 230, + '𑍰' => 230, + '𑍱' => 230, + '𑍲' => 230, + '𑍳' => 230, + '𑍴' => 230, + '𑑂' => 9, + '𑑆' => 7, + '𑑞' => 230, + '𑓂' => 9, + '𑓃' => 7, + '𑖿' => 9, + '𑗀' => 7, + '𑘿' => 9, + '𑚶' => 9, + '𑚷' => 7, + '𑜫' => 9, + '𑠹' => 9, + '𑠺' => 7, + '𑤽' => 9, + '𑤾' => 9, + '𑥃' => 7, + '𑧠' => 9, + '𑨴' => 9, + '𑩇' => 9, + '𑪙' => 9, + '𑰿' => 9, + '𑵂' => 7, + '𑵄' => 9, + '𑵅' => 9, + '𑶗' => 9, + '𖫰' => 1, + '𖫱' => 1, + '𖫲' => 1, + '𖫳' => 1, + '𖫴' => 1, + '𖬰' => 230, + '𖬱' => 230, + '𖬲' => 230, + '𖬳' => 230, + '𖬴' => 230, + '𖬵' => 230, + '𖬶' => 230, + '𖿰' => 6, + '𖿱' => 6, + '𛲞' => 1, + '𝅥' => 216, + '𝅦' => 216, + '𝅧' => 1, + '𝅨' => 1, + '𝅩' => 1, + '𝅭' => 226, + '𝅮' => 216, + '𝅯' => 216, + '𝅰' => 216, + '𝅱' => 216, + '𝅲' => 216, + '𝅻' => 220, + '𝅼' => 220, + '𝅽' => 220, + '𝅾' => 220, + '𝅿' => 220, + '𝆀' => 220, + '𝆁' => 220, + '𝆂' => 220, + '𝆅' => 230, + '𝆆' => 230, + '𝆇' => 230, + '𝆈' => 230, + '𝆉' => 230, + '𝆊' => 220, + '𝆋' => 220, + '𝆪' => 230, + '𝆫' => 230, + '𝆬' => 230, + '𝆭' => 230, + '𝉂' => 230, + '𝉃' => 230, + '𝉄' => 230, + '𞀀' => 230, + '𞀁' => 230, + '𞀂' => 230, + '𞀃' => 230, + '𞀄' => 230, + '𞀅' => 230, + '𞀆' => 230, + '𞀈' => 230, + '𞀉' => 230, + '𞀊' => 230, + '𞀋' => 230, + '𞀌' => 230, + '𞀍' => 230, + '𞀎' => 230, + '𞀏' => 230, + '𞀐' => 230, + '𞀑' => 230, + '𞀒' => 230, + '𞀓' => 230, + '𞀔' => 230, + '𞀕' => 230, + '𞀖' => 230, + '𞀗' => 230, + '𞀘' => 230, + '𞀛' => 230, + '𞀜' => 230, + '𞀝' => 230, + '𞀞' => 230, + '𞀟' => 230, + '𞀠' => 230, + '𞀡' => 230, + '𞀣' => 230, + '𞀤' => 230, + '𞀦' => 230, + '𞀧' => 230, + '𞀨' => 230, + '𞀩' => 230, + '𞀪' => 230, + '𞄰' => 230, + '𞄱' => 230, + '𞄲' => 230, + '𞄳' => 230, + '𞄴' => 230, + '𞄵' => 230, + '𞄶' => 230, + '𞋬' => 230, + '𞋭' => 230, + '𞋮' => 230, + '𞋯' => 230, + '𞣐' => 220, + '𞣑' => 220, + '𞣒' => 220, + '𞣓' => 220, + '𞣔' => 220, + '𞣕' => 220, + '𞣖' => 220, + '𞥄' => 230, + '𞥅' => 230, + '𞥆' => 230, + '𞥇' => 230, + '𞥈' => 230, + '𞥉' => 230, + '𞥊' => 7, +); diff --git a/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/compatibilityDecomposition.php b/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/compatibilityDecomposition.php new file mode 100644 index 00000000..15749028 --- /dev/null +++ b/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/compatibilityDecomposition.php @@ -0,0 +1,3695 @@ + ' ', + '¨' => ' ̈', + 'ª' => 'a', + '¯' => ' ̄', + '²' => '2', + '³' => '3', + '´' => ' ́', + 'µ' => 'μ', + '¸' => ' ̧', + '¹' => '1', + 'º' => 'o', + '¼' => '1⁄4', + '½' => '1⁄2', + '¾' => '3⁄4', + 'IJ' => 'IJ', + 'ij' => 'ij', + 'Ŀ' => 'L·', + 'ŀ' => 'l·', + 'ʼn' => 'ʼn', + 'ſ' => 's', + 'DŽ' => 'DŽ', + 'Dž' => 'Dž', + 'dž' => 'dž', + 'LJ' => 'LJ', + 'Lj' => 'Lj', + 'lj' => 'lj', + 'NJ' => 'NJ', + 'Nj' => 'Nj', + 'nj' => 'nj', + 'DZ' => 'DZ', + 'Dz' => 'Dz', + 'dz' => 'dz', + 'ʰ' => 'h', + 'ʱ' => 'ɦ', + 'ʲ' => 'j', + 'ʳ' => 'r', + 'ʴ' => 'ɹ', + 'ʵ' => 'ɻ', + 'ʶ' => 'ʁ', + 'ʷ' => 'w', + 'ʸ' => 'y', + '˘' => ' ̆', + '˙' => ' ̇', + '˚' => ' ̊', + '˛' => ' ̨', + '˜' => ' ̃', + '˝' => ' ̋', + 'ˠ' => 'ɣ', + 'ˡ' => 'l', + 'ˢ' => 's', + 'ˣ' => 'x', + 'ˤ' => 'ʕ', + 'ͺ' => ' ͅ', + '΄' => ' ́', + '΅' => ' ̈́', + 'ϐ' => 'β', + 'ϑ' => 'θ', + 'ϒ' => 'Υ', + 'ϓ' => 'Ύ', + 'ϔ' => 'Ϋ', + 'ϕ' => 'φ', + 'ϖ' => 'π', + 'ϰ' => 'κ', + 'ϱ' => 'ρ', + 'ϲ' => 'ς', + 'ϴ' => 'Θ', + 'ϵ' => 'ε', + 'Ϲ' => 'Σ', + 'և' => 'եւ', + 'ٵ' => 'اٴ', + 'ٶ' => 'وٴ', + 'ٷ' => 'ۇٴ', + 'ٸ' => 'يٴ', + 'ำ' => 'ํา', + 'ຳ' => 'ໍາ', + 'ໜ' => 'ຫນ', + 'ໝ' => 'ຫມ', + '༌' => '་', + 'ཷ' => 'ྲཱྀ', + 'ཹ' => 'ླཱྀ', + 'ჼ' => 'ნ', + 'ᴬ' => 'A', + 'ᴭ' => 'Æ', + 'ᴮ' => 'B', + 'ᴰ' => 'D', + 'ᴱ' => 'E', + 'ᴲ' => 'Ǝ', + 'ᴳ' => 'G', + 'ᴴ' => 'H', + 'ᴵ' => 'I', + 'ᴶ' => 'J', + 'ᴷ' => 'K', + 'ᴸ' => 'L', + 'ᴹ' => 'M', + 'ᴺ' => 'N', + 'ᴼ' => 'O', + 'ᴽ' => 'Ȣ', + 'ᴾ' => 'P', + 'ᴿ' => 'R', + 'ᵀ' => 'T', + 'ᵁ' => 'U', + 'ᵂ' => 'W', + 'ᵃ' => 'a', + 'ᵄ' => 'ɐ', + 'ᵅ' => 'ɑ', + 'ᵆ' => 'ᴂ', + 'ᵇ' => 'b', + 'ᵈ' => 'd', + 'ᵉ' => 'e', + 'ᵊ' => 'ə', + 'ᵋ' => 'ɛ', + 'ᵌ' => 'ɜ', + 'ᵍ' => 'g', + 'ᵏ' => 'k', + 'ᵐ' => 'm', + 'ᵑ' => 'ŋ', + 'ᵒ' => 'o', + 'ᵓ' => 'ɔ', + 'ᵔ' => 'ᴖ', + 'ᵕ' => 'ᴗ', + 'ᵖ' => 'p', + 'ᵗ' => 't', + 'ᵘ' => 'u', + 'ᵙ' => 'ᴝ', + 'ᵚ' => 'ɯ', + 'ᵛ' => 'v', + 'ᵜ' => 'ᴥ', + 'ᵝ' => 'β', + 'ᵞ' => 'γ', + 'ᵟ' => 'δ', + 'ᵠ' => 'φ', + 'ᵡ' => 'χ', + 'ᵢ' => 'i', + 'ᵣ' => 'r', + 'ᵤ' => 'u', + 'ᵥ' => 'v', + 'ᵦ' => 'β', + 'ᵧ' => 'γ', + 'ᵨ' => 'ρ', + 'ᵩ' => 'φ', + 'ᵪ' => 'χ', + 'ᵸ' => 'н', + 'ᶛ' => 'ɒ', + 'ᶜ' => 'c', + 'ᶝ' => 'ɕ', + 'ᶞ' => 'ð', + 'ᶟ' => 'ɜ', + 'ᶠ' => 'f', + 'ᶡ' => 'ɟ', + 'ᶢ' => 'ɡ', + 'ᶣ' => 'ɥ', + 'ᶤ' => 'ɨ', + 'ᶥ' => 'ɩ', + 'ᶦ' => 'ɪ', + 'ᶧ' => 'ᵻ', + 'ᶨ' => 'ʝ', + 'ᶩ' => 'ɭ', + 'ᶪ' => 'ᶅ', + 'ᶫ' => 'ʟ', + 'ᶬ' => 'ɱ', + 'ᶭ' => 'ɰ', + 'ᶮ' => 'ɲ', + 'ᶯ' => 'ɳ', + 'ᶰ' => 'ɴ', + 'ᶱ' => 'ɵ', + 'ᶲ' => 'ɸ', + 'ᶳ' => 'ʂ', + 'ᶴ' => 'ʃ', + 'ᶵ' => 'ƫ', + 'ᶶ' => 'ʉ', + 'ᶷ' => 'ʊ', + 'ᶸ' => 'ᴜ', + 'ᶹ' => 'ʋ', + 'ᶺ' => 'ʌ', + 'ᶻ' => 'z', + 'ᶼ' => 'ʐ', + 'ᶽ' => 'ʑ', + 'ᶾ' => 'ʒ', + 'ᶿ' => 'θ', + 'ẚ' => 'aʾ', + 'ẛ' => 'ṡ', + '᾽' => ' ̓', + '᾿' => ' ̓', + '῀' => ' ͂', + '῁' => ' ̈͂', + '῍' => ' ̓̀', + '῎' => ' ̓́', + '῏' => ' ̓͂', + '῝' => ' ̔̀', + '῞' => ' ̔́', + '῟' => ' ̔͂', + '῭' => ' ̈̀', + '΅' => ' ̈́', + '´' => ' ́', + '῾' => ' ̔', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + '‑' => '‐', + '‗' => ' ̳', + '․' => '.', + '‥' => '..', + '…' => '...', + ' ' => ' ', + '″' => '′′', + '‴' => '′′′', + '‶' => '‵‵', + '‷' => '‵‵‵', + '‼' => '!!', + '‾' => ' ̅', + '⁇' => '??', + '⁈' => '?!', + '⁉' => '!?', + '⁗' => '′′′′', + ' ' => ' ', + '⁰' => '0', + 'ⁱ' => 'i', + '⁴' => '4', + '⁵' => '5', + '⁶' => '6', + '⁷' => '7', + '⁸' => '8', + '⁹' => '9', + '⁺' => '+', + '⁻' => '−', + '⁼' => '=', + '⁽' => '(', + '⁾' => ')', + 'ⁿ' => 'n', + '₀' => '0', + '₁' => '1', + '₂' => '2', + '₃' => '3', + '₄' => '4', + '₅' => '5', + '₆' => '6', + '₇' => '7', + '₈' => '8', + '₉' => '9', + '₊' => '+', + '₋' => '−', + '₌' => '=', + '₍' => '(', + '₎' => ')', + 'ₐ' => 'a', + 'ₑ' => 'e', + 'ₒ' => 'o', + 'ₓ' => 'x', + 'ₔ' => 'ə', + 'ₕ' => 'h', + 'ₖ' => 'k', + 'ₗ' => 'l', + 'ₘ' => 'm', + 'ₙ' => 'n', + 'ₚ' => 'p', + 'ₛ' => 's', + 'ₜ' => 't', + '₨' => 'Rs', + '℀' => 'a/c', + '℁' => 'a/s', + 'ℂ' => 'C', + '℃' => '°C', + '℅' => 'c/o', + '℆' => 'c/u', + 'ℇ' => 'Ɛ', + '℉' => '°F', + 'ℊ' => 'g', + 'ℋ' => 'H', + 'ℌ' => 'H', + 'ℍ' => 'H', + 'ℎ' => 'h', + 'ℏ' => 'ħ', + 'ℐ' => 'I', + 'ℑ' => 'I', + 'ℒ' => 'L', + 'ℓ' => 'l', + 'ℕ' => 'N', + '№' => 'No', + 'ℙ' => 'P', + 'ℚ' => 'Q', + 'ℛ' => 'R', + 'ℜ' => 'R', + 'ℝ' => 'R', + '℠' => 'SM', + '℡' => 'TEL', + '™' => 'TM', + 'ℤ' => 'Z', + 'ℨ' => 'Z', + 'ℬ' => 'B', + 'ℭ' => 'C', + 'ℯ' => 'e', + 'ℰ' => 'E', + 'ℱ' => 'F', + 'ℳ' => 'M', + 'ℴ' => 'o', + 'ℵ' => 'א', + 'ℶ' => 'ב', + 'ℷ' => 'ג', + 'ℸ' => 'ד', + 'ℹ' => 'i', + '℻' => 'FAX', + 'ℼ' => 'π', + 'ℽ' => 'γ', + 'ℾ' => 'Γ', + 'ℿ' => 'Π', + '⅀' => '∑', + 'ⅅ' => 'D', + 'ⅆ' => 'd', + 'ⅇ' => 'e', + 'ⅈ' => 'i', + 'ⅉ' => 'j', + '⅐' => '1⁄7', + '⅑' => '1⁄9', + '⅒' => '1⁄10', + '⅓' => '1⁄3', + '⅔' => '2⁄3', + '⅕' => '1⁄5', + '⅖' => '2⁄5', + '⅗' => '3⁄5', + '⅘' => '4⁄5', + '⅙' => '1⁄6', + '⅚' => '5⁄6', + '⅛' => '1⁄8', + '⅜' => '3⁄8', + '⅝' => '5⁄8', + '⅞' => '7⁄8', + '⅟' => '1⁄', + 'Ⅰ' => 'I', + 'Ⅱ' => 'II', + 'Ⅲ' => 'III', + 'Ⅳ' => 'IV', + 'Ⅴ' => 'V', + 'Ⅵ' => 'VI', + 'Ⅶ' => 'VII', + 'Ⅷ' => 'VIII', + 'Ⅸ' => 'IX', + 'Ⅹ' => 'X', + 'Ⅺ' => 'XI', + 'Ⅻ' => 'XII', + 'Ⅼ' => 'L', + 'Ⅽ' => 'C', + 'Ⅾ' => 'D', + 'Ⅿ' => 'M', + 'ⅰ' => 'i', + 'ⅱ' => 'ii', + 'ⅲ' => 'iii', + 'ⅳ' => 'iv', + 'ⅴ' => 'v', + 'ⅵ' => 'vi', + 'ⅶ' => 'vii', + 'ⅷ' => 'viii', + 'ⅸ' => 'ix', + 'ⅹ' => 'x', + 'ⅺ' => 'xi', + 'ⅻ' => 'xii', + 'ⅼ' => 'l', + 'ⅽ' => 'c', + 'ⅾ' => 'd', + 'ⅿ' => 'm', + '↉' => '0⁄3', + '∬' => '∫∫', + '∭' => '∫∫∫', + '∯' => '∮∮', + '∰' => '∮∮∮', + '①' => '1', + '②' => '2', + '③' => '3', + '④' => '4', + '⑤' => '5', + '⑥' => '6', + '⑦' => '7', + '⑧' => '8', + '⑨' => '9', + '⑩' => '10', + '⑪' => '11', + '⑫' => '12', + '⑬' => '13', + '⑭' => '14', + '⑮' => '15', + '⑯' => '16', + '⑰' => '17', + '⑱' => '18', + '⑲' => '19', + '⑳' => '20', + '⑴' => '(1)', + '⑵' => '(2)', + '⑶' => '(3)', + '⑷' => '(4)', + '⑸' => '(5)', + '⑹' => '(6)', + '⑺' => '(7)', + '⑻' => '(8)', + '⑼' => '(9)', + '⑽' => '(10)', + '⑾' => '(11)', + '⑿' => '(12)', + '⒀' => '(13)', + '⒁' => '(14)', + '⒂' => '(15)', + '⒃' => '(16)', + '⒄' => '(17)', + '⒅' => '(18)', + '⒆' => '(19)', + '⒇' => '(20)', + '⒈' => '1.', + '⒉' => '2.', + '⒊' => '3.', + '⒋' => '4.', + '⒌' => '5.', + '⒍' => '6.', + '⒎' => '7.', + '⒏' => '8.', + '⒐' => '9.', + '⒑' => '10.', + '⒒' => '11.', + '⒓' => '12.', + '⒔' => '13.', + '⒕' => '14.', + '⒖' => '15.', + '⒗' => '16.', + '⒘' => '17.', + '⒙' => '18.', + '⒚' => '19.', + '⒛' => '20.', + '⒜' => '(a)', + '⒝' => '(b)', + '⒞' => '(c)', + '⒟' => '(d)', + '⒠' => '(e)', + '⒡' => '(f)', + '⒢' => '(g)', + '⒣' => '(h)', + '⒤' => '(i)', + '⒥' => '(j)', + '⒦' => '(k)', + '⒧' => '(l)', + '⒨' => '(m)', + '⒩' => '(n)', + '⒪' => '(o)', + '⒫' => '(p)', + '⒬' => '(q)', + '⒭' => '(r)', + '⒮' => '(s)', + '⒯' => '(t)', + '⒰' => '(u)', + '⒱' => '(v)', + '⒲' => '(w)', + '⒳' => '(x)', + '⒴' => '(y)', + '⒵' => '(z)', + 'Ⓐ' => 'A', + 'Ⓑ' => 'B', + 'Ⓒ' => 'C', + 'Ⓓ' => 'D', + 'Ⓔ' => 'E', + 'Ⓕ' => 'F', + 'Ⓖ' => 'G', + 'Ⓗ' => 'H', + 'Ⓘ' => 'I', + 'Ⓙ' => 'J', + 'Ⓚ' => 'K', + 'Ⓛ' => 'L', + 'Ⓜ' => 'M', + 'Ⓝ' => 'N', + 'Ⓞ' => 'O', + 'Ⓟ' => 'P', + 'Ⓠ' => 'Q', + 'Ⓡ' => 'R', + 'Ⓢ' => 'S', + 'Ⓣ' => 'T', + 'Ⓤ' => 'U', + 'Ⓥ' => 'V', + 'Ⓦ' => 'W', + 'Ⓧ' => 'X', + 'Ⓨ' => 'Y', + 'Ⓩ' => 'Z', + 'ⓐ' => 'a', + 'ⓑ' => 'b', + 'ⓒ' => 'c', + 'ⓓ' => 'd', + 'ⓔ' => 'e', + 'ⓕ' => 'f', + 'ⓖ' => 'g', + 'ⓗ' => 'h', + 'ⓘ' => 'i', + 'ⓙ' => 'j', + 'ⓚ' => 'k', + 'ⓛ' => 'l', + 'ⓜ' => 'm', + 'ⓝ' => 'n', + 'ⓞ' => 'o', + 'ⓟ' => 'p', + 'ⓠ' => 'q', + 'ⓡ' => 'r', + 'ⓢ' => 's', + 'ⓣ' => 't', + 'ⓤ' => 'u', + 'ⓥ' => 'v', + 'ⓦ' => 'w', + 'ⓧ' => 'x', + 'ⓨ' => 'y', + 'ⓩ' => 'z', + '⓪' => '0', + '⨌' => '∫∫∫∫', + '⩴' => '::=', + '⩵' => '==', + '⩶' => '===', + 'ⱼ' => 'j', + 'ⱽ' => 'V', + 'ⵯ' => 'ⵡ', + '⺟' => '母', + '⻳' => '龟', + '⼀' => '一', + '⼁' => '丨', + '⼂' => '丶', + '⼃' => '丿', + '⼄' => '乙', + '⼅' => '亅', + '⼆' => '二', + '⼇' => '亠', + '⼈' => '人', + '⼉' => '儿', + '⼊' => '入', + '⼋' => '八', + '⼌' => '冂', + '⼍' => '冖', + '⼎' => '冫', + '⼏' => '几', + '⼐' => '凵', + '⼑' => '刀', + '⼒' => '力', + '⼓' => '勹', + '⼔' => '匕', + '⼕' => '匚', + '⼖' => '匸', + '⼗' => '十', + '⼘' => '卜', + '⼙' => '卩', + '⼚' => '厂', + '⼛' => '厶', + '⼜' => '又', + '⼝' => '口', + '⼞' => '囗', + '⼟' => '土', + '⼠' => '士', + '⼡' => '夂', + '⼢' => '夊', + '⼣' => '夕', + '⼤' => '大', + '⼥' => '女', + '⼦' => '子', + '⼧' => '宀', + '⼨' => '寸', + '⼩' => '小', + '⼪' => '尢', + '⼫' => '尸', + '⼬' => '屮', + '⼭' => '山', + '⼮' => '巛', + '⼯' => '工', + '⼰' => '己', + '⼱' => '巾', + '⼲' => '干', + '⼳' => '幺', + '⼴' => '广', + '⼵' => '廴', + '⼶' => '廾', + '⼷' => '弋', + '⼸' => '弓', + '⼹' => '彐', + '⼺' => '彡', + '⼻' => '彳', + '⼼' => '心', + '⼽' => '戈', + '⼾' => '戶', + '⼿' => '手', + '⽀' => '支', + '⽁' => '攴', + '⽂' => '文', + '⽃' => '斗', + '⽄' => '斤', + '⽅' => '方', + '⽆' => '无', + '⽇' => '日', + '⽈' => '曰', + '⽉' => '月', + '⽊' => '木', + '⽋' => '欠', + '⽌' => '止', + '⽍' => '歹', + '⽎' => '殳', + '⽏' => '毋', + '⽐' => '比', + '⽑' => '毛', + '⽒' => '氏', + '⽓' => '气', + '⽔' => '水', + '⽕' => '火', + '⽖' => '爪', + '⽗' => '父', + '⽘' => '爻', + '⽙' => '爿', + '⽚' => '片', + '⽛' => '牙', + '⽜' => '牛', + '⽝' => '犬', + '⽞' => '玄', + '⽟' => '玉', + '⽠' => '瓜', + '⽡' => '瓦', + '⽢' => '甘', + '⽣' => '生', + '⽤' => '用', + '⽥' => '田', + '⽦' => '疋', + '⽧' => '疒', + '⽨' => '癶', + '⽩' => '白', + '⽪' => '皮', + '⽫' => '皿', + '⽬' => '目', + '⽭' => '矛', + '⽮' => '矢', + '⽯' => '石', + '⽰' => '示', + '⽱' => '禸', + '⽲' => '禾', + '⽳' => '穴', + '⽴' => '立', + '⽵' => '竹', + '⽶' => '米', + '⽷' => '糸', + '⽸' => '缶', + '⽹' => '网', + '⽺' => '羊', + '⽻' => '羽', + '⽼' => '老', + '⽽' => '而', + '⽾' => '耒', + '⽿' => '耳', + '⾀' => '聿', + '⾁' => '肉', + '⾂' => '臣', + '⾃' => '自', + '⾄' => '至', + '⾅' => '臼', + '⾆' => '舌', + '⾇' => '舛', + '⾈' => '舟', + '⾉' => '艮', + '⾊' => '色', + '⾋' => '艸', + '⾌' => '虍', + '⾍' => '虫', + '⾎' => '血', + '⾏' => '行', + '⾐' => '衣', + '⾑' => '襾', + '⾒' => '見', + '⾓' => '角', + '⾔' => '言', + '⾕' => '谷', + '⾖' => '豆', + '⾗' => '豕', + '⾘' => '豸', + '⾙' => '貝', + '⾚' => '赤', + '⾛' => '走', + '⾜' => '足', + '⾝' => '身', + '⾞' => '車', + '⾟' => '辛', + '⾠' => '辰', + '⾡' => '辵', + '⾢' => '邑', + '⾣' => '酉', + '⾤' => '釆', + '⾥' => '里', + '⾦' => '金', + '⾧' => '長', + '⾨' => '門', + '⾩' => '阜', + '⾪' => '隶', + '⾫' => '隹', + '⾬' => '雨', + '⾭' => '靑', + '⾮' => '非', + '⾯' => '面', + '⾰' => '革', + '⾱' => '韋', + '⾲' => '韭', + '⾳' => '音', + '⾴' => '頁', + '⾵' => '風', + '⾶' => '飛', + '⾷' => '食', + '⾸' => '首', + '⾹' => '香', + '⾺' => '馬', + '⾻' => '骨', + '⾼' => '高', + '⾽' => '髟', + '⾾' => '鬥', + '⾿' => '鬯', + '⿀' => '鬲', + '⿁' => '鬼', + '⿂' => '魚', + '⿃' => '鳥', + '⿄' => '鹵', + '⿅' => '鹿', + '⿆' => '麥', + '⿇' => '麻', + '⿈' => '黃', + '⿉' => '黍', + '⿊' => '黑', + '⿋' => '黹', + '⿌' => '黽', + '⿍' => '鼎', + '⿎' => '鼓', + '⿏' => '鼠', + '⿐' => '鼻', + '⿑' => '齊', + '⿒' => '齒', + '⿓' => '龍', + '⿔' => '龜', + '⿕' => '龠', + ' ' => ' ', + '〶' => '〒', + '〸' => '十', + '〹' => '卄', + '〺' => '卅', + '゛' => ' ゙', + '゜' => ' ゚', + 'ゟ' => 'より', + 'ヿ' => 'コト', + 'ㄱ' => 'ᄀ', + 'ㄲ' => 'ᄁ', + 'ㄳ' => 'ᆪ', + 'ㄴ' => 'ᄂ', + 'ㄵ' => 'ᆬ', + 'ㄶ' => 'ᆭ', + 'ㄷ' => 'ᄃ', + 'ㄸ' => 'ᄄ', + 'ㄹ' => 'ᄅ', + 'ㄺ' => 'ᆰ', + 'ㄻ' => 'ᆱ', + 'ㄼ' => 'ᆲ', + 'ㄽ' => 'ᆳ', + 'ㄾ' => 'ᆴ', + 'ㄿ' => 'ᆵ', + 'ㅀ' => 'ᄚ', + 'ㅁ' => 'ᄆ', + 'ㅂ' => 'ᄇ', + 'ㅃ' => 'ᄈ', + 'ㅄ' => 'ᄡ', + 'ㅅ' => 'ᄉ', + 'ㅆ' => 'ᄊ', + 'ㅇ' => 'ᄋ', + 'ㅈ' => 'ᄌ', + 'ㅉ' => 'ᄍ', + 'ㅊ' => 'ᄎ', + 'ㅋ' => 'ᄏ', + 'ㅌ' => 'ᄐ', + 'ㅍ' => 'ᄑ', + 'ㅎ' => 'ᄒ', + 'ㅏ' => 'ᅡ', + 'ㅐ' => 'ᅢ', + 'ㅑ' => 'ᅣ', + 'ㅒ' => 'ᅤ', + 'ㅓ' => 'ᅥ', + 'ㅔ' => 'ᅦ', + 'ㅕ' => 'ᅧ', + 'ㅖ' => 'ᅨ', + 'ㅗ' => 'ᅩ', + 'ㅘ' => 'ᅪ', + 'ㅙ' => 'ᅫ', + 'ㅚ' => 'ᅬ', + 'ㅛ' => 'ᅭ', + 'ㅜ' => 'ᅮ', + 'ㅝ' => 'ᅯ', + 'ㅞ' => 'ᅰ', + 'ㅟ' => 'ᅱ', + 'ㅠ' => 'ᅲ', + 'ㅡ' => 'ᅳ', + 'ㅢ' => 'ᅴ', + 'ㅣ' => 'ᅵ', + 'ㅤ' => 'ᅠ', + 'ㅥ' => 'ᄔ', + 'ㅦ' => 'ᄕ', + 'ㅧ' => 'ᇇ', + 'ㅨ' => 'ᇈ', + 'ㅩ' => 'ᇌ', + 'ㅪ' => 'ᇎ', + 'ㅫ' => 'ᇓ', + 'ㅬ' => 'ᇗ', + 'ㅭ' => 'ᇙ', + 'ㅮ' => 'ᄜ', + 'ㅯ' => 'ᇝ', + 'ㅰ' => 'ᇟ', + 'ㅱ' => 'ᄝ', + 'ㅲ' => 'ᄞ', + 'ㅳ' => 'ᄠ', + 'ㅴ' => 'ᄢ', + 'ㅵ' => 'ᄣ', + 'ㅶ' => 'ᄧ', + 'ㅷ' => 'ᄩ', + 'ㅸ' => 'ᄫ', + 'ㅹ' => 'ᄬ', + 'ㅺ' => 'ᄭ', + 'ㅻ' => 'ᄮ', + 'ㅼ' => 'ᄯ', + 'ㅽ' => 'ᄲ', + 'ㅾ' => 'ᄶ', + 'ㅿ' => 'ᅀ', + 'ㆀ' => 'ᅇ', + 'ㆁ' => 'ᅌ', + 'ㆂ' => 'ᇱ', + 'ㆃ' => 'ᇲ', + 'ㆄ' => 'ᅗ', + 'ㆅ' => 'ᅘ', + 'ㆆ' => 'ᅙ', + 'ㆇ' => 'ᆄ', + 'ㆈ' => 'ᆅ', + 'ㆉ' => 'ᆈ', + 'ㆊ' => 'ᆑ', + 'ㆋ' => 'ᆒ', + 'ㆌ' => 'ᆔ', + 'ㆍ' => 'ᆞ', + 'ㆎ' => 'ᆡ', + '㆒' => '一', + '㆓' => '二', + '㆔' => '三', + '㆕' => '四', + '㆖' => '上', + '㆗' => '中', + '㆘' => '下', + '㆙' => '甲', + '㆚' => '乙', + '㆛' => '丙', + '㆜' => '丁', + '㆝' => '天', + '㆞' => '地', + '㆟' => '人', + '㈀' => '(ᄀ)', + '㈁' => '(ᄂ)', + '㈂' => '(ᄃ)', + '㈃' => '(ᄅ)', + '㈄' => '(ᄆ)', + '㈅' => '(ᄇ)', + '㈆' => '(ᄉ)', + '㈇' => '(ᄋ)', + '㈈' => '(ᄌ)', + '㈉' => '(ᄎ)', + '㈊' => '(ᄏ)', + '㈋' => '(ᄐ)', + '㈌' => '(ᄑ)', + '㈍' => '(ᄒ)', + '㈎' => '(가)', + '㈏' => '(나)', + '㈐' => '(다)', + '㈑' => '(라)', + '㈒' => '(마)', + '㈓' => '(바)', + '㈔' => '(사)', + '㈕' => '(아)', + '㈖' => '(자)', + '㈗' => '(차)', + '㈘' => '(카)', + '㈙' => '(타)', + '㈚' => '(파)', + '㈛' => '(하)', + '㈜' => '(주)', + '㈝' => '(오전)', + '㈞' => '(오후)', + '㈠' => '(一)', + '㈡' => '(二)', + '㈢' => '(三)', + '㈣' => '(四)', + '㈤' => '(五)', + '㈥' => '(六)', + '㈦' => '(七)', + '㈧' => '(八)', + '㈨' => '(九)', + '㈩' => '(十)', + '㈪' => '(月)', + '㈫' => '(火)', + '㈬' => '(水)', + '㈭' => '(木)', + '㈮' => '(金)', + '㈯' => '(土)', + '㈰' => '(日)', + '㈱' => '(株)', + '㈲' => '(有)', + '㈳' => '(社)', + '㈴' => '(名)', + '㈵' => '(特)', + '㈶' => '(財)', + '㈷' => '(祝)', + '㈸' => '(労)', + '㈹' => '(代)', + '㈺' => '(呼)', + '㈻' => '(学)', + '㈼' => '(監)', + '㈽' => '(企)', + '㈾' => '(資)', + '㈿' => '(協)', + '㉀' => '(祭)', + '㉁' => '(休)', + '㉂' => '(自)', + '㉃' => '(至)', + '㉄' => '問', + '㉅' => '幼', + '㉆' => '文', + '㉇' => '箏', + '㉐' => 'PTE', + '㉑' => '21', + '㉒' => '22', + '㉓' => '23', + '㉔' => '24', + '㉕' => '25', + '㉖' => '26', + '㉗' => '27', + '㉘' => '28', + '㉙' => '29', + '㉚' => '30', + '㉛' => '31', + '㉜' => '32', + '㉝' => '33', + '㉞' => '34', + '㉟' => '35', + '㉠' => 'ᄀ', + '㉡' => 'ᄂ', + '㉢' => 'ᄃ', + '㉣' => 'ᄅ', + '㉤' => 'ᄆ', + '㉥' => 'ᄇ', + '㉦' => 'ᄉ', + '㉧' => 'ᄋ', + '㉨' => 'ᄌ', + '㉩' => 'ᄎ', + '㉪' => 'ᄏ', + '㉫' => 'ᄐ', + '㉬' => 'ᄑ', + '㉭' => 'ᄒ', + '㉮' => '가', + '㉯' => '나', + '㉰' => '다', + '㉱' => '라', + '㉲' => '마', + '㉳' => '바', + '㉴' => '사', + '㉵' => '아', + '㉶' => '자', + '㉷' => '차', + '㉸' => '카', + '㉹' => '타', + '㉺' => '파', + '㉻' => '하', + '㉼' => '참고', + '㉽' => '주의', + '㉾' => '우', + '㊀' => '一', + '㊁' => '二', + '㊂' => '三', + '㊃' => '四', + '㊄' => '五', + '㊅' => '六', + '㊆' => '七', + '㊇' => '八', + '㊈' => '九', + '㊉' => '十', + '㊊' => '月', + '㊋' => '火', + '㊌' => '水', + '㊍' => '木', + '㊎' => '金', + '㊏' => '土', + '㊐' => '日', + '㊑' => '株', + '㊒' => '有', + '㊓' => '社', + '㊔' => '名', + '㊕' => '特', + '㊖' => '財', + '㊗' => '祝', + '㊘' => '労', + '㊙' => '秘', + '㊚' => '男', + '㊛' => '女', + '㊜' => '適', + '㊝' => '優', + '㊞' => '印', + '㊟' => '注', + '㊠' => '項', + '㊡' => '休', + '㊢' => '写', + '㊣' => '正', + '㊤' => '上', + '㊥' => '中', + '㊦' => '下', + '㊧' => '左', + '㊨' => '右', + '㊩' => '医', + '㊪' => '宗', + '㊫' => '学', + '㊬' => '監', + '㊭' => '企', + '㊮' => '資', + '㊯' => '協', + '㊰' => '夜', + '㊱' => '36', + '㊲' => '37', + '㊳' => '38', + '㊴' => '39', + '㊵' => '40', + '㊶' => '41', + '㊷' => '42', + '㊸' => '43', + '㊹' => '44', + '㊺' => '45', + '㊻' => '46', + '㊼' => '47', + '㊽' => '48', + '㊾' => '49', + '㊿' => '50', + '㋀' => '1月', + '㋁' => '2月', + '㋂' => '3月', + '㋃' => '4月', + '㋄' => '5月', + '㋅' => '6月', + '㋆' => '7月', + '㋇' => '8月', + '㋈' => '9月', + '㋉' => '10月', + '㋊' => '11月', + '㋋' => '12月', + '㋌' => 'Hg', + '㋍' => 'erg', + '㋎' => 'eV', + '㋏' => 'LTD', + '㋐' => 'ア', + '㋑' => 'イ', + '㋒' => 'ウ', + '㋓' => 'エ', + '㋔' => 'オ', + '㋕' => 'カ', + '㋖' => 'キ', + '㋗' => 'ク', + '㋘' => 'ケ', + '㋙' => 'コ', + '㋚' => 'サ', + '㋛' => 'シ', + '㋜' => 'ス', + '㋝' => 'セ', + '㋞' => 'ソ', + '㋟' => 'タ', + '㋠' => 'チ', + '㋡' => 'ツ', + '㋢' => 'テ', + '㋣' => 'ト', + '㋤' => 'ナ', + '㋥' => 'ニ', + '㋦' => 'ヌ', + '㋧' => 'ネ', + '㋨' => 'ノ', + '㋩' => 'ハ', + '㋪' => 'ヒ', + '㋫' => 'フ', + '㋬' => 'ヘ', + '㋭' => 'ホ', + '㋮' => 'マ', + '㋯' => 'ミ', + '㋰' => 'ム', + '㋱' => 'メ', + '㋲' => 'モ', + '㋳' => 'ヤ', + '㋴' => 'ユ', + '㋵' => 'ヨ', + '㋶' => 'ラ', + '㋷' => 'リ', + '㋸' => 'ル', + '㋹' => 'レ', + '㋺' => 'ロ', + '㋻' => 'ワ', + '㋼' => 'ヰ', + '㋽' => 'ヱ', + '㋾' => 'ヲ', + '㋿' => '令和', + '㌀' => 'アパート', + '㌁' => 'アルファ', + '㌂' => 'アンペア', + '㌃' => 'アール', + '㌄' => 'イニング', + '㌅' => 'インチ', + '㌆' => 'ウォン', + '㌇' => 'エスクード', + '㌈' => 'エーカー', + '㌉' => 'オンス', + '㌊' => 'オーム', + '㌋' => 'カイリ', + '㌌' => 'カラット', + '㌍' => 'カロリー', + '㌎' => 'ガロン', + '㌏' => 'ガンマ', + '㌐' => 'ギガ', + '㌑' => 'ギニー', + '㌒' => 'キュリー', + '㌓' => 'ギルダー', + '㌔' => 'キロ', + '㌕' => 'キログラム', + '㌖' => 'キロメートル', + '㌗' => 'キロワット', + '㌘' => 'グラム', + '㌙' => 'グラムトン', + '㌚' => 'クルゼイロ', + '㌛' => 'クローネ', + '㌜' => 'ケース', + '㌝' => 'コルナ', + '㌞' => 'コーポ', + '㌟' => 'サイクル', + '㌠' => 'サンチーム', + '㌡' => 'シリング', + '㌢' => 'センチ', + '㌣' => 'セント', + '㌤' => 'ダース', + '㌥' => 'デシ', + '㌦' => 'ドル', + '㌧' => 'トン', + '㌨' => 'ナノ', + '㌩' => 'ノット', + '㌪' => 'ハイツ', + '㌫' => 'パーセント', + '㌬' => 'パーツ', + '㌭' => 'バーレル', + '㌮' => 'ピアストル', + '㌯' => 'ピクル', + '㌰' => 'ピコ', + '㌱' => 'ビル', + '㌲' => 'ファラッド', + '㌳' => 'フィート', + '㌴' => 'ブッシェル', + '㌵' => 'フラン', + '㌶' => 'ヘクタール', + '㌷' => 'ペソ', + '㌸' => 'ペニヒ', + '㌹' => 'ヘルツ', + '㌺' => 'ペンス', + '㌻' => 'ページ', + '㌼' => 'ベータ', + '㌽' => 'ポイント', + '㌾' => 'ボルト', + '㌿' => 'ホン', + '㍀' => 'ポンド', + '㍁' => 'ホール', + '㍂' => 'ホーン', + '㍃' => 'マイクロ', + '㍄' => 'マイル', + '㍅' => 'マッハ', + '㍆' => 'マルク', + '㍇' => 'マンション', + '㍈' => 'ミクロン', + '㍉' => 'ミリ', + '㍊' => 'ミリバール', + '㍋' => 'メガ', + '㍌' => 'メガトン', + '㍍' => 'メートル', + '㍎' => 'ヤード', + '㍏' => 'ヤール', + '㍐' => 'ユアン', + '㍑' => 'リットル', + '㍒' => 'リラ', + '㍓' => 'ルピー', + '㍔' => 'ルーブル', + '㍕' => 'レム', + '㍖' => 'レントゲン', + '㍗' => 'ワット', + '㍘' => '0点', + '㍙' => '1点', + '㍚' => '2点', + '㍛' => '3点', + '㍜' => '4点', + '㍝' => '5点', + '㍞' => '6点', + '㍟' => '7点', + '㍠' => '8点', + '㍡' => '9点', + '㍢' => '10点', + '㍣' => '11点', + '㍤' => '12点', + '㍥' => '13点', + '㍦' => '14点', + '㍧' => '15点', + '㍨' => '16点', + '㍩' => '17点', + '㍪' => '18点', + '㍫' => '19点', + '㍬' => '20点', + '㍭' => '21点', + '㍮' => '22点', + '㍯' => '23点', + '㍰' => '24点', + '㍱' => 'hPa', + '㍲' => 'da', + '㍳' => 'AU', + '㍴' => 'bar', + '㍵' => 'oV', + '㍶' => 'pc', + '㍷' => 'dm', + '㍸' => 'dm2', + '㍹' => 'dm3', + '㍺' => 'IU', + '㍻' => '平成', + '㍼' => '昭和', + '㍽' => '大正', + '㍾' => '明治', + '㍿' => '株式会社', + '㎀' => 'pA', + '㎁' => 'nA', + '㎂' => 'μA', + '㎃' => 'mA', + '㎄' => 'kA', + '㎅' => 'KB', + '㎆' => 'MB', + '㎇' => 'GB', + '㎈' => 'cal', + '㎉' => 'kcal', + '㎊' => 'pF', + '㎋' => 'nF', + '㎌' => 'μF', + '㎍' => 'μg', + '㎎' => 'mg', + '㎏' => 'kg', + '㎐' => 'Hz', + '㎑' => 'kHz', + '㎒' => 'MHz', + '㎓' => 'GHz', + '㎔' => 'THz', + '㎕' => 'μl', + '㎖' => 'ml', + '㎗' => 'dl', + '㎘' => 'kl', + '㎙' => 'fm', + '㎚' => 'nm', + '㎛' => 'μm', + '㎜' => 'mm', + '㎝' => 'cm', + '㎞' => 'km', + '㎟' => 'mm2', + '㎠' => 'cm2', + '㎡' => 'm2', + '㎢' => 'km2', + '㎣' => 'mm3', + '㎤' => 'cm3', + '㎥' => 'm3', + '㎦' => 'km3', + '㎧' => 'm∕s', + '㎨' => 'm∕s2', + '㎩' => 'Pa', + '㎪' => 'kPa', + '㎫' => 'MPa', + '㎬' => 'GPa', + '㎭' => 'rad', + '㎮' => 'rad∕s', + '㎯' => 'rad∕s2', + '㎰' => 'ps', + '㎱' => 'ns', + '㎲' => 'μs', + '㎳' => 'ms', + '㎴' => 'pV', + '㎵' => 'nV', + '㎶' => 'μV', + '㎷' => 'mV', + '㎸' => 'kV', + '㎹' => 'MV', + '㎺' => 'pW', + '㎻' => 'nW', + '㎼' => 'μW', + '㎽' => 'mW', + '㎾' => 'kW', + '㎿' => 'MW', + '㏀' => 'kΩ', + '㏁' => 'MΩ', + '㏂' => 'a.m.', + '㏃' => 'Bq', + '㏄' => 'cc', + '㏅' => 'cd', + '㏆' => 'C∕kg', + '㏇' => 'Co.', + '㏈' => 'dB', + '㏉' => 'Gy', + '㏊' => 'ha', + '㏋' => 'HP', + '㏌' => 'in', + '㏍' => 'KK', + '㏎' => 'KM', + '㏏' => 'kt', + '㏐' => 'lm', + '㏑' => 'ln', + '㏒' => 'log', + '㏓' => 'lx', + '㏔' => 'mb', + '㏕' => 'mil', + '㏖' => 'mol', + '㏗' => 'PH', + '㏘' => 'p.m.', + '㏙' => 'PPM', + '㏚' => 'PR', + '㏛' => 'sr', + '㏜' => 'Sv', + '㏝' => 'Wb', + '㏞' => 'V∕m', + '㏟' => 'A∕m', + '㏠' => '1日', + '㏡' => '2日', + '㏢' => '3日', + '㏣' => '4日', + '㏤' => '5日', + '㏥' => '6日', + '㏦' => '7日', + '㏧' => '8日', + '㏨' => '9日', + '㏩' => '10日', + '㏪' => '11日', + '㏫' => '12日', + '㏬' => '13日', + '㏭' => '14日', + '㏮' => '15日', + '㏯' => '16日', + '㏰' => '17日', + '㏱' => '18日', + '㏲' => '19日', + '㏳' => '20日', + '㏴' => '21日', + '㏵' => '22日', + '㏶' => '23日', + '㏷' => '24日', + '㏸' => '25日', + '㏹' => '26日', + '㏺' => '27日', + '㏻' => '28日', + '㏼' => '29日', + '㏽' => '30日', + '㏾' => '31日', + '㏿' => 'gal', + 'ꚜ' => 'ъ', + 'ꚝ' => 'ь', + 'ꝰ' => 'ꝯ', + 'ꟸ' => 'Ħ', + 'ꟹ' => 'œ', + 'ꭜ' => 'ꜧ', + 'ꭝ' => 'ꬷ', + 'ꭞ' => 'ɫ', + 'ꭟ' => 'ꭒ', + 'ꭩ' => 'ʍ', + 'ff' => 'ff', + 'fi' => 'fi', + 'fl' => 'fl', + 'ffi' => 'ffi', + 'ffl' => 'ffl', + 'ſt' => 'st', + 'st' => 'st', + 'ﬓ' => 'մն', + 'ﬔ' => 'մե', + 'ﬕ' => 'մի', + 'ﬖ' => 'վն', + 'ﬗ' => 'մխ', + 'ﬠ' => 'ע', + 'ﬡ' => 'א', + 'ﬢ' => 'ד', + 'ﬣ' => 'ה', + 'ﬤ' => 'כ', + 'ﬥ' => 'ל', + 'ﬦ' => 'ם', + 'ﬧ' => 'ר', + 'ﬨ' => 'ת', + '﬩' => '+', + 'ﭏ' => 'אל', + 'ﭐ' => 'ٱ', + 'ﭑ' => 'ٱ', + 'ﭒ' => 'ٻ', + 'ﭓ' => 'ٻ', + 'ﭔ' => 'ٻ', + 'ﭕ' => 'ٻ', + 'ﭖ' => 'پ', + 'ﭗ' => 'پ', + 'ﭘ' => 'پ', + 'ﭙ' => 'پ', + 'ﭚ' => 'ڀ', + 'ﭛ' => 'ڀ', + 'ﭜ' => 'ڀ', + 'ﭝ' => 'ڀ', + 'ﭞ' => 'ٺ', + 'ﭟ' => 'ٺ', + 'ﭠ' => 'ٺ', + 'ﭡ' => 'ٺ', + 'ﭢ' => 'ٿ', + 'ﭣ' => 'ٿ', + 'ﭤ' => 'ٿ', + 'ﭥ' => 'ٿ', + 'ﭦ' => 'ٹ', + 'ﭧ' => 'ٹ', + 'ﭨ' => 'ٹ', + 'ﭩ' => 'ٹ', + 'ﭪ' => 'ڤ', + 'ﭫ' => 'ڤ', + 'ﭬ' => 'ڤ', + 'ﭭ' => 'ڤ', + 'ﭮ' => 'ڦ', + 'ﭯ' => 'ڦ', + 'ﭰ' => 'ڦ', + 'ﭱ' => 'ڦ', + 'ﭲ' => 'ڄ', + 'ﭳ' => 'ڄ', + 'ﭴ' => 'ڄ', + 'ﭵ' => 'ڄ', + 'ﭶ' => 'ڃ', + 'ﭷ' => 'ڃ', + 'ﭸ' => 'ڃ', + 'ﭹ' => 'ڃ', + 'ﭺ' => 'چ', + 'ﭻ' => 'چ', + 'ﭼ' => 'چ', + 'ﭽ' => 'چ', + 'ﭾ' => 'ڇ', + 'ﭿ' => 'ڇ', + 'ﮀ' => 'ڇ', + 'ﮁ' => 'ڇ', + 'ﮂ' => 'ڍ', + 'ﮃ' => 'ڍ', + 'ﮄ' => 'ڌ', + 'ﮅ' => 'ڌ', + 'ﮆ' => 'ڎ', + 'ﮇ' => 'ڎ', + 'ﮈ' => 'ڈ', + 'ﮉ' => 'ڈ', + 'ﮊ' => 'ژ', + 'ﮋ' => 'ژ', + 'ﮌ' => 'ڑ', + 'ﮍ' => 'ڑ', + 'ﮎ' => 'ک', + 'ﮏ' => 'ک', + 'ﮐ' => 'ک', + 'ﮑ' => 'ک', + 'ﮒ' => 'گ', + 'ﮓ' => 'گ', + 'ﮔ' => 'گ', + 'ﮕ' => 'گ', + 'ﮖ' => 'ڳ', + 'ﮗ' => 'ڳ', + 'ﮘ' => 'ڳ', + 'ﮙ' => 'ڳ', + 'ﮚ' => 'ڱ', + 'ﮛ' => 'ڱ', + 'ﮜ' => 'ڱ', + 'ﮝ' => 'ڱ', + 'ﮞ' => 'ں', + 'ﮟ' => 'ں', + 'ﮠ' => 'ڻ', + 'ﮡ' => 'ڻ', + 'ﮢ' => 'ڻ', + 'ﮣ' => 'ڻ', + 'ﮤ' => 'ۀ', + 'ﮥ' => 'ۀ', + 'ﮦ' => 'ہ', + 'ﮧ' => 'ہ', + 'ﮨ' => 'ہ', + 'ﮩ' => 'ہ', + 'ﮪ' => 'ھ', + 'ﮫ' => 'ھ', + 'ﮬ' => 'ھ', + 'ﮭ' => 'ھ', + 'ﮮ' => 'ے', + 'ﮯ' => 'ے', + 'ﮰ' => 'ۓ', + 'ﮱ' => 'ۓ', + 'ﯓ' => 'ڭ', + 'ﯔ' => 'ڭ', + 'ﯕ' => 'ڭ', + 'ﯖ' => 'ڭ', + 'ﯗ' => 'ۇ', + 'ﯘ' => 'ۇ', + 'ﯙ' => 'ۆ', + 'ﯚ' => 'ۆ', + 'ﯛ' => 'ۈ', + 'ﯜ' => 'ۈ', + 'ﯝ' => 'ۇٴ', + 'ﯞ' => 'ۋ', + 'ﯟ' => 'ۋ', + 'ﯠ' => 'ۅ', + 'ﯡ' => 'ۅ', + 'ﯢ' => 'ۉ', + 'ﯣ' => 'ۉ', + 'ﯤ' => 'ې', + 'ﯥ' => 'ې', + 'ﯦ' => 'ې', + 'ﯧ' => 'ې', + 'ﯨ' => 'ى', + 'ﯩ' => 'ى', + 'ﯪ' => 'ئا', + 'ﯫ' => 'ئا', + 'ﯬ' => 'ئە', + 'ﯭ' => 'ئە', + 'ﯮ' => 'ئو', + 'ﯯ' => 'ئو', + 'ﯰ' => 'ئۇ', + 'ﯱ' => 'ئۇ', + 'ﯲ' => 'ئۆ', + 'ﯳ' => 'ئۆ', + 'ﯴ' => 'ئۈ', + 'ﯵ' => 'ئۈ', + 'ﯶ' => 'ئې', + 'ﯷ' => 'ئې', + 'ﯸ' => 'ئې', + 'ﯹ' => 'ئى', + 'ﯺ' => 'ئى', + 'ﯻ' => 'ئى', + 'ﯼ' => 'ی', + 'ﯽ' => 'ی', + 'ﯾ' => 'ی', + 'ﯿ' => 'ی', + 'ﰀ' => 'ئج', + 'ﰁ' => 'ئح', + 'ﰂ' => 'ئم', + 'ﰃ' => 'ئى', + 'ﰄ' => 'ئي', + 'ﰅ' => 'بج', + 'ﰆ' => 'بح', + 'ﰇ' => 'بخ', + 'ﰈ' => 'بم', + 'ﰉ' => 'بى', + 'ﰊ' => 'بي', + 'ﰋ' => 'تج', + 'ﰌ' => 'تح', + 'ﰍ' => 'تخ', + 'ﰎ' => 'تم', + 'ﰏ' => 'تى', + 'ﰐ' => 'تي', + 'ﰑ' => 'ثج', + 'ﰒ' => 'ثم', + 'ﰓ' => 'ثى', + 'ﰔ' => 'ثي', + 'ﰕ' => 'جح', + 'ﰖ' => 'جم', + 'ﰗ' => 'حج', + 'ﰘ' => 'حم', + 'ﰙ' => 'خج', + 'ﰚ' => 'خح', + 'ﰛ' => 'خم', + 'ﰜ' => 'سج', + 'ﰝ' => 'سح', + 'ﰞ' => 'سخ', + 'ﰟ' => 'سم', + 'ﰠ' => 'صح', + 'ﰡ' => 'صم', + 'ﰢ' => 'ضج', + 'ﰣ' => 'ضح', + 'ﰤ' => 'ضخ', + 'ﰥ' => 'ضم', + 'ﰦ' => 'طح', + 'ﰧ' => 'طم', + 'ﰨ' => 'ظم', + 'ﰩ' => 'عج', + 'ﰪ' => 'عم', + 'ﰫ' => 'غج', + 'ﰬ' => 'غم', + 'ﰭ' => 'فج', + 'ﰮ' => 'فح', + 'ﰯ' => 'فخ', + 'ﰰ' => 'فم', + 'ﰱ' => 'فى', + 'ﰲ' => 'في', + 'ﰳ' => 'قح', + 'ﰴ' => 'قم', + 'ﰵ' => 'قى', + 'ﰶ' => 'قي', + 'ﰷ' => 'كا', + 'ﰸ' => 'كج', + 'ﰹ' => 'كح', + 'ﰺ' => 'كخ', + 'ﰻ' => 'كل', + 'ﰼ' => 'كم', + 'ﰽ' => 'كى', + 'ﰾ' => 'كي', + 'ﰿ' => 'لج', + 'ﱀ' => 'لح', + 'ﱁ' => 'لخ', + 'ﱂ' => 'لم', + 'ﱃ' => 'لى', + 'ﱄ' => 'لي', + 'ﱅ' => 'مج', + 'ﱆ' => 'مح', + 'ﱇ' => 'مخ', + 'ﱈ' => 'مم', + 'ﱉ' => 'مى', + 'ﱊ' => 'مي', + 'ﱋ' => 'نج', + 'ﱌ' => 'نح', + 'ﱍ' => 'نخ', + 'ﱎ' => 'نم', + 'ﱏ' => 'نى', + 'ﱐ' => 'ني', + 'ﱑ' => 'هج', + 'ﱒ' => 'هم', + 'ﱓ' => 'هى', + 'ﱔ' => 'هي', + 'ﱕ' => 'يج', + 'ﱖ' => 'يح', + 'ﱗ' => 'يخ', + 'ﱘ' => 'يم', + 'ﱙ' => 'يى', + 'ﱚ' => 'يي', + 'ﱛ' => 'ذٰ', + 'ﱜ' => 'رٰ', + 'ﱝ' => 'ىٰ', + 'ﱞ' => ' ٌّ', + 'ﱟ' => ' ٍّ', + 'ﱠ' => ' َّ', + 'ﱡ' => ' ُّ', + 'ﱢ' => ' ِّ', + 'ﱣ' => ' ّٰ', + 'ﱤ' => 'ئر', + 'ﱥ' => 'ئز', + 'ﱦ' => 'ئم', + 'ﱧ' => 'ئن', + 'ﱨ' => 'ئى', + 'ﱩ' => 'ئي', + 'ﱪ' => 'بر', + 'ﱫ' => 'بز', + 'ﱬ' => 'بم', + 'ﱭ' => 'بن', + 'ﱮ' => 'بى', + 'ﱯ' => 'بي', + 'ﱰ' => 'تر', + 'ﱱ' => 'تز', + 'ﱲ' => 'تم', + 'ﱳ' => 'تن', + 'ﱴ' => 'تى', + 'ﱵ' => 'تي', + 'ﱶ' => 'ثر', + 'ﱷ' => 'ثز', + 'ﱸ' => 'ثم', + 'ﱹ' => 'ثن', + 'ﱺ' => 'ثى', + 'ﱻ' => 'ثي', + 'ﱼ' => 'فى', + 'ﱽ' => 'في', + 'ﱾ' => 'قى', + 'ﱿ' => 'قي', + 'ﲀ' => 'كا', + 'ﲁ' => 'كل', + 'ﲂ' => 'كم', + 'ﲃ' => 'كى', + 'ﲄ' => 'كي', + 'ﲅ' => 'لم', + 'ﲆ' => 'لى', + 'ﲇ' => 'لي', + 'ﲈ' => 'ما', + 'ﲉ' => 'مم', + 'ﲊ' => 'نر', + 'ﲋ' => 'نز', + 'ﲌ' => 'نم', + 'ﲍ' => 'نن', + 'ﲎ' => 'نى', + 'ﲏ' => 'ني', + 'ﲐ' => 'ىٰ', + 'ﲑ' => 'ير', + 'ﲒ' => 'يز', + 'ﲓ' => 'يم', + 'ﲔ' => 'ين', + 'ﲕ' => 'يى', + 'ﲖ' => 'يي', + 'ﲗ' => 'ئج', + 'ﲘ' => 'ئح', + 'ﲙ' => 'ئخ', + 'ﲚ' => 'ئم', + 'ﲛ' => 'ئه', + 'ﲜ' => 'بج', + 'ﲝ' => 'بح', + 'ﲞ' => 'بخ', + 'ﲟ' => 'بم', + 'ﲠ' => 'به', + 'ﲡ' => 'تج', + 'ﲢ' => 'تح', + 'ﲣ' => 'تخ', + 'ﲤ' => 'تم', + 'ﲥ' => 'ته', + 'ﲦ' => 'ثم', + 'ﲧ' => 'جح', + 'ﲨ' => 'جم', + 'ﲩ' => 'حج', + 'ﲪ' => 'حم', + 'ﲫ' => 'خج', + 'ﲬ' => 'خم', + 'ﲭ' => 'سج', + 'ﲮ' => 'سح', + 'ﲯ' => 'سخ', + 'ﲰ' => 'سم', + 'ﲱ' => 'صح', + 'ﲲ' => 'صخ', + 'ﲳ' => 'صم', + 'ﲴ' => 'ضج', + 'ﲵ' => 'ضح', + 'ﲶ' => 'ضخ', + 'ﲷ' => 'ضم', + 'ﲸ' => 'طح', + 'ﲹ' => 'ظم', + 'ﲺ' => 'عج', + 'ﲻ' => 'عم', + 'ﲼ' => 'غج', + 'ﲽ' => 'غم', + 'ﲾ' => 'فج', + 'ﲿ' => 'فح', + 'ﳀ' => 'فخ', + 'ﳁ' => 'فم', + 'ﳂ' => 'قح', + 'ﳃ' => 'قم', + 'ﳄ' => 'كج', + 'ﳅ' => 'كح', + 'ﳆ' => 'كخ', + 'ﳇ' => 'كل', + 'ﳈ' => 'كم', + 'ﳉ' => 'لج', + 'ﳊ' => 'لح', + 'ﳋ' => 'لخ', + 'ﳌ' => 'لم', + 'ﳍ' => 'له', + 'ﳎ' => 'مج', + 'ﳏ' => 'مح', + 'ﳐ' => 'مخ', + 'ﳑ' => 'مم', + 'ﳒ' => 'نج', + 'ﳓ' => 'نح', + 'ﳔ' => 'نخ', + 'ﳕ' => 'نم', + 'ﳖ' => 'نه', + 'ﳗ' => 'هج', + 'ﳘ' => 'هم', + 'ﳙ' => 'هٰ', + 'ﳚ' => 'يج', + 'ﳛ' => 'يح', + 'ﳜ' => 'يخ', + 'ﳝ' => 'يم', + 'ﳞ' => 'يه', + 'ﳟ' => 'ئم', + 'ﳠ' => 'ئه', + 'ﳡ' => 'بم', + 'ﳢ' => 'به', + 'ﳣ' => 'تم', + 'ﳤ' => 'ته', + 'ﳥ' => 'ثم', + 'ﳦ' => 'ثه', + 'ﳧ' => 'سم', + 'ﳨ' => 'سه', + 'ﳩ' => 'شم', + 'ﳪ' => 'شه', + 'ﳫ' => 'كل', + 'ﳬ' => 'كم', + 'ﳭ' => 'لم', + 'ﳮ' => 'نم', + 'ﳯ' => 'نه', + 'ﳰ' => 'يم', + 'ﳱ' => 'يه', + 'ﳲ' => 'ـَّ', + 'ﳳ' => 'ـُّ', + 'ﳴ' => 'ـِّ', + 'ﳵ' => 'طى', + 'ﳶ' => 'طي', + 'ﳷ' => 'عى', + 'ﳸ' => 'عي', + 'ﳹ' => 'غى', + 'ﳺ' => 'غي', + 'ﳻ' => 'سى', + 'ﳼ' => 'سي', + 'ﳽ' => 'شى', + 'ﳾ' => 'شي', + 'ﳿ' => 'حى', + 'ﴀ' => 'حي', + 'ﴁ' => 'جى', + 'ﴂ' => 'جي', + 'ﴃ' => 'خى', + 'ﴄ' => 'خي', + 'ﴅ' => 'صى', + 'ﴆ' => 'صي', + 'ﴇ' => 'ضى', + 'ﴈ' => 'ضي', + 'ﴉ' => 'شج', + 'ﴊ' => 'شح', + 'ﴋ' => 'شخ', + 'ﴌ' => 'شم', + 'ﴍ' => 'شر', + 'ﴎ' => 'سر', + 'ﴏ' => 'صر', + 'ﴐ' => 'ضر', + 'ﴑ' => 'طى', + 'ﴒ' => 'طي', + 'ﴓ' => 'عى', + 'ﴔ' => 'عي', + 'ﴕ' => 'غى', + 'ﴖ' => 'غي', + 'ﴗ' => 'سى', + 'ﴘ' => 'سي', + 'ﴙ' => 'شى', + 'ﴚ' => 'شي', + 'ﴛ' => 'حى', + 'ﴜ' => 'حي', + 'ﴝ' => 'جى', + 'ﴞ' => 'جي', + 'ﴟ' => 'خى', + 'ﴠ' => 'خي', + 'ﴡ' => 'صى', + 'ﴢ' => 'صي', + 'ﴣ' => 'ضى', + 'ﴤ' => 'ضي', + 'ﴥ' => 'شج', + 'ﴦ' => 'شح', + 'ﴧ' => 'شخ', + 'ﴨ' => 'شم', + 'ﴩ' => 'شر', + 'ﴪ' => 'سر', + 'ﴫ' => 'صر', + 'ﴬ' => 'ضر', + 'ﴭ' => 'شج', + 'ﴮ' => 'شح', + 'ﴯ' => 'شخ', + 'ﴰ' => 'شم', + 'ﴱ' => 'سه', + 'ﴲ' => 'شه', + 'ﴳ' => 'طم', + 'ﴴ' => 'سج', + 'ﴵ' => 'سح', + 'ﴶ' => 'سخ', + 'ﴷ' => 'شج', + 'ﴸ' => 'شح', + 'ﴹ' => 'شخ', + 'ﴺ' => 'طم', + 'ﴻ' => 'ظم', + 'ﴼ' => 'اً', + 'ﴽ' => 'اً', + 'ﵐ' => 'تجم', + 'ﵑ' => 'تحج', + 'ﵒ' => 'تحج', + 'ﵓ' => 'تحم', + 'ﵔ' => 'تخم', + 'ﵕ' => 'تمج', + 'ﵖ' => 'تمح', + 'ﵗ' => 'تمخ', + 'ﵘ' => 'جمح', + 'ﵙ' => 'جمح', + 'ﵚ' => 'حمي', + 'ﵛ' => 'حمى', + 'ﵜ' => 'سحج', + 'ﵝ' => 'سجح', + 'ﵞ' => 'سجى', + 'ﵟ' => 'سمح', + 'ﵠ' => 'سمح', + 'ﵡ' => 'سمج', + 'ﵢ' => 'سمم', + 'ﵣ' => 'سمم', + 'ﵤ' => 'صحح', + 'ﵥ' => 'صحح', + 'ﵦ' => 'صمم', + 'ﵧ' => 'شحم', + 'ﵨ' => 'شحم', + 'ﵩ' => 'شجي', + 'ﵪ' => 'شمخ', + 'ﵫ' => 'شمخ', + 'ﵬ' => 'شمم', + 'ﵭ' => 'شمم', + 'ﵮ' => 'ضحى', + 'ﵯ' => 'ضخم', + 'ﵰ' => 'ضخم', + 'ﵱ' => 'طمح', + 'ﵲ' => 'طمح', + 'ﵳ' => 'طمم', + 'ﵴ' => 'طمي', + 'ﵵ' => 'عجم', + 'ﵶ' => 'عمم', + 'ﵷ' => 'عمم', + 'ﵸ' => 'عمى', + 'ﵹ' => 'غمم', + 'ﵺ' => 'غمي', + 'ﵻ' => 'غمى', + 'ﵼ' => 'فخم', + 'ﵽ' => 'فخم', + 'ﵾ' => 'قمح', + 'ﵿ' => 'قمم', + 'ﶀ' => 'لحم', + 'ﶁ' => 'لحي', + 'ﶂ' => 'لحى', + 'ﶃ' => 'لجج', + 'ﶄ' => 'لجج', + 'ﶅ' => 'لخم', + 'ﶆ' => 'لخم', + 'ﶇ' => 'لمح', + 'ﶈ' => 'لمح', + 'ﶉ' => 'محج', + 'ﶊ' => 'محم', + 'ﶋ' => 'محي', + 'ﶌ' => 'مجح', + 'ﶍ' => 'مجم', + 'ﶎ' => 'مخج', + 'ﶏ' => 'مخم', + 'ﶒ' => 'مجخ', + 'ﶓ' => 'همج', + 'ﶔ' => 'همم', + 'ﶕ' => 'نحم', + 'ﶖ' => 'نحى', + 'ﶗ' => 'نجم', + 'ﶘ' => 'نجم', + 'ﶙ' => 'نجى', + 'ﶚ' => 'نمي', + 'ﶛ' => 'نمى', + 'ﶜ' => 'يمم', + 'ﶝ' => 'يمم', + 'ﶞ' => 'بخي', + 'ﶟ' => 'تجي', + 'ﶠ' => 'تجى', + 'ﶡ' => 'تخي', + 'ﶢ' => 'تخى', + 'ﶣ' => 'تمي', + 'ﶤ' => 'تمى', + 'ﶥ' => 'جمي', + 'ﶦ' => 'جحى', + 'ﶧ' => 'جمى', + 'ﶨ' => 'سخى', + 'ﶩ' => 'صحي', + 'ﶪ' => 'شحي', + 'ﶫ' => 'ضحي', + 'ﶬ' => 'لجي', + 'ﶭ' => 'لمي', + 'ﶮ' => 'يحي', + 'ﶯ' => 'يجي', + 'ﶰ' => 'يمي', + 'ﶱ' => 'ممي', + 'ﶲ' => 'قمي', + 'ﶳ' => 'نحي', + 'ﶴ' => 'قمح', + 'ﶵ' => 'لحم', + 'ﶶ' => 'عمي', + 'ﶷ' => 'كمي', + 'ﶸ' => 'نجح', + 'ﶹ' => 'مخي', + 'ﶺ' => 'لجم', + 'ﶻ' => 'كمم', + 'ﶼ' => 'لجم', + 'ﶽ' => 'نجح', + 'ﶾ' => 'جحي', + 'ﶿ' => 'حجي', + 'ﷀ' => 'مجي', + 'ﷁ' => 'فمي', + 'ﷂ' => 'بحي', + 'ﷃ' => 'كمم', + 'ﷄ' => 'عجم', + 'ﷅ' => 'صمم', + 'ﷆ' => 'سخي', + 'ﷇ' => 'نجي', + 'ﷰ' => 'صلے', + 'ﷱ' => 'قلے', + 'ﷲ' => 'الله', + 'ﷳ' => 'اكبر', + 'ﷴ' => 'محمد', + 'ﷵ' => 'صلعم', + 'ﷶ' => 'رسول', + 'ﷷ' => 'عليه', + 'ﷸ' => 'وسلم', + 'ﷹ' => 'صلى', + 'ﷺ' => 'صلى الله عليه وسلم', + 'ﷻ' => 'جل جلاله', + '﷼' => 'ریال', + '︐' => ',', + '︑' => '、', + '︒' => '。', + '︓' => ':', + '︔' => ';', + '︕' => '!', + '︖' => '?', + '︗' => '〖', + '︘' => '〗', + '︙' => '...', + '︰' => '..', + '︱' => '—', + '︲' => '–', + '︳' => '_', + '︴' => '_', + '︵' => '(', + '︶' => ')', + '︷' => '{', + '︸' => '}', + '︹' => '〔', + '︺' => '〕', + '︻' => '【', + '︼' => '】', + '︽' => '《', + '︾' => '》', + '︿' => '〈', + '﹀' => '〉', + '﹁' => '「', + '﹂' => '」', + '﹃' => '『', + '﹄' => '』', + '﹇' => '[', + '﹈' => ']', + '﹉' => ' ̅', + '﹊' => ' ̅', + '﹋' => ' ̅', + '﹌' => ' ̅', + '﹍' => '_', + '﹎' => '_', + '﹏' => '_', + '﹐' => ',', + '﹑' => '、', + '﹒' => '.', + '﹔' => ';', + '﹕' => ':', + '﹖' => '?', + '﹗' => '!', + '﹘' => '—', + '﹙' => '(', + '﹚' => ')', + '﹛' => '{', + '﹜' => '}', + '﹝' => '〔', + '﹞' => '〕', + '﹟' => '#', + '﹠' => '&', + '﹡' => '*', + '﹢' => '+', + '﹣' => '-', + '﹤' => '<', + '﹥' => '>', + '﹦' => '=', + '﹨' => '\\', + '﹩' => '$', + '﹪' => '%', + '﹫' => '@', + 'ﹰ' => ' ً', + 'ﹱ' => 'ـً', + 'ﹲ' => ' ٌ', + 'ﹴ' => ' ٍ', + 'ﹶ' => ' َ', + 'ﹷ' => 'ـَ', + 'ﹸ' => ' ُ', + 'ﹹ' => 'ـُ', + 'ﹺ' => ' ِ', + 'ﹻ' => 'ـِ', + 'ﹼ' => ' ّ', + 'ﹽ' => 'ـّ', + 'ﹾ' => ' ْ', + 'ﹿ' => 'ـْ', + 'ﺀ' => 'ء', + 'ﺁ' => 'آ', + 'ﺂ' => 'آ', + 'ﺃ' => 'أ', + 'ﺄ' => 'أ', + 'ﺅ' => 'ؤ', + 'ﺆ' => 'ؤ', + 'ﺇ' => 'إ', + 'ﺈ' => 'إ', + 'ﺉ' => 'ئ', + 'ﺊ' => 'ئ', + 'ﺋ' => 'ئ', + 'ﺌ' => 'ئ', + 'ﺍ' => 'ا', + 'ﺎ' => 'ا', + 'ﺏ' => 'ب', + 'ﺐ' => 'ب', + 'ﺑ' => 'ب', + 'ﺒ' => 'ب', + 'ﺓ' => 'ة', + 'ﺔ' => 'ة', + 'ﺕ' => 'ت', + 'ﺖ' => 'ت', + 'ﺗ' => 'ت', + 'ﺘ' => 'ت', + 'ﺙ' => 'ث', + 'ﺚ' => 'ث', + 'ﺛ' => 'ث', + 'ﺜ' => 'ث', + 'ﺝ' => 'ج', + 'ﺞ' => 'ج', + 'ﺟ' => 'ج', + 'ﺠ' => 'ج', + 'ﺡ' => 'ح', + 'ﺢ' => 'ح', + 'ﺣ' => 'ح', + 'ﺤ' => 'ح', + 'ﺥ' => 'خ', + 'ﺦ' => 'خ', + 'ﺧ' => 'خ', + 'ﺨ' => 'خ', + 'ﺩ' => 'د', + 'ﺪ' => 'د', + 'ﺫ' => 'ذ', + 'ﺬ' => 'ذ', + 'ﺭ' => 'ر', + 'ﺮ' => 'ر', + 'ﺯ' => 'ز', + 'ﺰ' => 'ز', + 'ﺱ' => 'س', + 'ﺲ' => 'س', + 'ﺳ' => 'س', + 'ﺴ' => 'س', + 'ﺵ' => 'ش', + 'ﺶ' => 'ش', + 'ﺷ' => 'ش', + 'ﺸ' => 'ش', + 'ﺹ' => 'ص', + 'ﺺ' => 'ص', + 'ﺻ' => 'ص', + 'ﺼ' => 'ص', + 'ﺽ' => 'ض', + 'ﺾ' => 'ض', + 'ﺿ' => 'ض', + 'ﻀ' => 'ض', + 'ﻁ' => 'ط', + 'ﻂ' => 'ط', + 'ﻃ' => 'ط', + 'ﻄ' => 'ط', + 'ﻅ' => 'ظ', + 'ﻆ' => 'ظ', + 'ﻇ' => 'ظ', + 'ﻈ' => 'ظ', + 'ﻉ' => 'ع', + 'ﻊ' => 'ع', + 'ﻋ' => 'ع', + 'ﻌ' => 'ع', + 'ﻍ' => 'غ', + 'ﻎ' => 'غ', + 'ﻏ' => 'غ', + 'ﻐ' => 'غ', + 'ﻑ' => 'ف', + 'ﻒ' => 'ف', + 'ﻓ' => 'ف', + 'ﻔ' => 'ف', + 'ﻕ' => 'ق', + 'ﻖ' => 'ق', + 'ﻗ' => 'ق', + 'ﻘ' => 'ق', + 'ﻙ' => 'ك', + 'ﻚ' => 'ك', + 'ﻛ' => 'ك', + 'ﻜ' => 'ك', + 'ﻝ' => 'ل', + 'ﻞ' => 'ل', + 'ﻟ' => 'ل', + 'ﻠ' => 'ل', + 'ﻡ' => 'م', + 'ﻢ' => 'م', + 'ﻣ' => 'م', + 'ﻤ' => 'م', + 'ﻥ' => 'ن', + 'ﻦ' => 'ن', + 'ﻧ' => 'ن', + 'ﻨ' => 'ن', + 'ﻩ' => 'ه', + 'ﻪ' => 'ه', + 'ﻫ' => 'ه', + 'ﻬ' => 'ه', + 'ﻭ' => 'و', + 'ﻮ' => 'و', + 'ﻯ' => 'ى', + 'ﻰ' => 'ى', + 'ﻱ' => 'ي', + 'ﻲ' => 'ي', + 'ﻳ' => 'ي', + 'ﻴ' => 'ي', + 'ﻵ' => 'لآ', + 'ﻶ' => 'لآ', + 'ﻷ' => 'لأ', + 'ﻸ' => 'لأ', + 'ﻹ' => 'لإ', + 'ﻺ' => 'لإ', + 'ﻻ' => 'لا', + 'ﻼ' => 'لا', + '!' => '!', + '"' => '"', + '#' => '#', + '$' => '$', + '%' => '%', + '&' => '&', + ''' => '\'', + '(' => '(', + ')' => ')', + '*' => '*', + '+' => '+', + ',' => ',', + '-' => '-', + '.' => '.', + '/' => '/', + '0' => '0', + '1' => '1', + '2' => '2', + '3' => '3', + '4' => '4', + '5' => '5', + '6' => '6', + '7' => '7', + '8' => '8', + '9' => '9', + ':' => ':', + ';' => ';', + '<' => '<', + '=' => '=', + '>' => '>', + '?' => '?', + '@' => '@', + 'A' => 'A', + 'B' => 'B', + 'C' => 'C', + 'D' => 'D', + 'E' => 'E', + 'F' => 'F', + 'G' => 'G', + 'H' => 'H', + 'I' => 'I', + 'J' => 'J', + 'K' => 'K', + 'L' => 'L', + 'M' => 'M', + 'N' => 'N', + 'O' => 'O', + 'P' => 'P', + 'Q' => 'Q', + 'R' => 'R', + 'S' => 'S', + 'T' => 'T', + 'U' => 'U', + 'V' => 'V', + 'W' => 'W', + 'X' => 'X', + 'Y' => 'Y', + 'Z' => 'Z', + '[' => '[', + '\' => '\\', + ']' => ']', + '^' => '^', + '_' => '_', + '`' => '`', + 'a' => 'a', + 'b' => 'b', + 'c' => 'c', + 'd' => 'd', + 'e' => 'e', + 'f' => 'f', + 'g' => 'g', + 'h' => 'h', + 'i' => 'i', + 'j' => 'j', + 'k' => 'k', + 'l' => 'l', + 'm' => 'm', + 'n' => 'n', + 'o' => 'o', + 'p' => 'p', + 'q' => 'q', + 'r' => 'r', + 's' => 's', + 't' => 't', + 'u' => 'u', + 'v' => 'v', + 'w' => 'w', + 'x' => 'x', + 'y' => 'y', + 'z' => 'z', + '{' => '{', + '|' => '|', + '}' => '}', + '~' => '~', + '⦅' => '⦅', + '⦆' => '⦆', + '。' => '。', + '「' => '「', + '」' => '」', + '、' => '、', + '・' => '・', + 'ヲ' => 'ヲ', + 'ァ' => 'ァ', + 'ィ' => 'ィ', + 'ゥ' => 'ゥ', + 'ェ' => 'ェ', + 'ォ' => 'ォ', + 'ャ' => 'ャ', + 'ュ' => 'ュ', + 'ョ' => 'ョ', + 'ッ' => 'ッ', + 'ー' => 'ー', + 'ア' => 'ア', + 'イ' => 'イ', + 'ウ' => 'ウ', + 'エ' => 'エ', + 'オ' => 'オ', + 'カ' => 'カ', + 'キ' => 'キ', + 'ク' => 'ク', + 'ケ' => 'ケ', + 'コ' => 'コ', + 'サ' => 'サ', + 'シ' => 'シ', + 'ス' => 'ス', + 'セ' => 'セ', + 'ソ' => 'ソ', + 'タ' => 'タ', + 'チ' => 'チ', + 'ツ' => 'ツ', + 'テ' => 'テ', + 'ト' => 'ト', + 'ナ' => 'ナ', + 'ニ' => 'ニ', + 'ヌ' => 'ヌ', + 'ネ' => 'ネ', + 'ノ' => 'ノ', + 'ハ' => 'ハ', + 'ヒ' => 'ヒ', + 'フ' => 'フ', + 'ヘ' => 'ヘ', + 'ホ' => 'ホ', + 'マ' => 'マ', + 'ミ' => 'ミ', + 'ム' => 'ム', + 'メ' => 'メ', + 'モ' => 'モ', + 'ヤ' => 'ヤ', + 'ユ' => 'ユ', + 'ヨ' => 'ヨ', + 'ラ' => 'ラ', + 'リ' => 'リ', + 'ル' => 'ル', + 'レ' => 'レ', + 'ロ' => 'ロ', + 'ワ' => 'ワ', + 'ン' => 'ン', + '゙' => '゙', + '゚' => '゚', + 'ᅠ' => 'ᅠ', + 'ᄀ' => 'ᄀ', + 'ᄁ' => 'ᄁ', + 'ᆪ' => 'ᆪ', + 'ᄂ' => 'ᄂ', + 'ᆬ' => 'ᆬ', + 'ᆭ' => 'ᆭ', + 'ᄃ' => 'ᄃ', + 'ᄄ' => 'ᄄ', + 'ᄅ' => 'ᄅ', + 'ᆰ' => 'ᆰ', + 'ᆱ' => 'ᆱ', + 'ᆲ' => 'ᆲ', + 'ᆳ' => 'ᆳ', + 'ᆴ' => 'ᆴ', + 'ᆵ' => 'ᆵ', + 'ᄚ' => 'ᄚ', + 'ᄆ' => 'ᄆ', + 'ᄇ' => 'ᄇ', + 'ᄈ' => 'ᄈ', + 'ᄡ' => 'ᄡ', + 'ᄉ' => 'ᄉ', + 'ᄊ' => 'ᄊ', + 'ᄋ' => 'ᄋ', + 'ᄌ' => 'ᄌ', + 'ᄍ' => 'ᄍ', + 'ᄎ' => 'ᄎ', + 'ᄏ' => 'ᄏ', + 'ᄐ' => 'ᄐ', + 'ᄑ' => 'ᄑ', + 'ᄒ' => 'ᄒ', + 'ᅡ' => 'ᅡ', + 'ᅢ' => 'ᅢ', + 'ᅣ' => 'ᅣ', + 'ᅤ' => 'ᅤ', + 'ᅥ' => 'ᅥ', + 'ᅦ' => 'ᅦ', + 'ᅧ' => 'ᅧ', + 'ᅨ' => 'ᅨ', + 'ᅩ' => 'ᅩ', + 'ᅪ' => 'ᅪ', + 'ᅫ' => 'ᅫ', + 'ᅬ' => 'ᅬ', + 'ᅭ' => 'ᅭ', + 'ᅮ' => 'ᅮ', + 'ᅯ' => 'ᅯ', + 'ᅰ' => 'ᅰ', + 'ᅱ' => 'ᅱ', + 'ᅲ' => 'ᅲ', + 'ᅳ' => 'ᅳ', + 'ᅴ' => 'ᅴ', + 'ᅵ' => 'ᅵ', + '¢' => '¢', + '£' => '£', + '¬' => '¬', + ' ̄' => ' ̄', + '¦' => '¦', + '¥' => '¥', + '₩' => '₩', + '│' => '│', + '←' => '←', + '↑' => '↑', + '→' => '→', + '↓' => '↓', + '■' => '■', + '○' => '○', + '𝐀' => 'A', + '𝐁' => 'B', + '𝐂' => 'C', + '𝐃' => 'D', + '𝐄' => 'E', + '𝐅' => 'F', + '𝐆' => 'G', + '𝐇' => 'H', + '𝐈' => 'I', + '𝐉' => 'J', + '𝐊' => 'K', + '𝐋' => 'L', + '𝐌' => 'M', + '𝐍' => 'N', + '𝐎' => 'O', + '𝐏' => 'P', + '𝐐' => 'Q', + '𝐑' => 'R', + '𝐒' => 'S', + '𝐓' => 'T', + '𝐔' => 'U', + '𝐕' => 'V', + '𝐖' => 'W', + '𝐗' => 'X', + '𝐘' => 'Y', + '𝐙' => 'Z', + '𝐚' => 'a', + '𝐛' => 'b', + '𝐜' => 'c', + '𝐝' => 'd', + '𝐞' => 'e', + '𝐟' => 'f', + '𝐠' => 'g', + '𝐡' => 'h', + '𝐢' => 'i', + '𝐣' => 'j', + '𝐤' => 'k', + '𝐥' => 'l', + '𝐦' => 'm', + '𝐧' => 'n', + '𝐨' => 'o', + '𝐩' => 'p', + '𝐪' => 'q', + '𝐫' => 'r', + '𝐬' => 's', + '𝐭' => 't', + '𝐮' => 'u', + '𝐯' => 'v', + '𝐰' => 'w', + '𝐱' => 'x', + '𝐲' => 'y', + '𝐳' => 'z', + '𝐴' => 'A', + '𝐵' => 'B', + '𝐶' => 'C', + '𝐷' => 'D', + '𝐸' => 'E', + '𝐹' => 'F', + '𝐺' => 'G', + '𝐻' => 'H', + '𝐼' => 'I', + '𝐽' => 'J', + '𝐾' => 'K', + '𝐿' => 'L', + '𝑀' => 'M', + '𝑁' => 'N', + '𝑂' => 'O', + '𝑃' => 'P', + '𝑄' => 'Q', + '𝑅' => 'R', + '𝑆' => 'S', + '𝑇' => 'T', + '𝑈' => 'U', + '𝑉' => 'V', + '𝑊' => 'W', + '𝑋' => 'X', + '𝑌' => 'Y', + '𝑍' => 'Z', + '𝑎' => 'a', + '𝑏' => 'b', + '𝑐' => 'c', + '𝑑' => 'd', + '𝑒' => 'e', + '𝑓' => 'f', + '𝑔' => 'g', + '𝑖' => 'i', + '𝑗' => 'j', + '𝑘' => 'k', + '𝑙' => 'l', + '𝑚' => 'm', + '𝑛' => 'n', + '𝑜' => 'o', + '𝑝' => 'p', + '𝑞' => 'q', + '𝑟' => 'r', + '𝑠' => 's', + '𝑡' => 't', + '𝑢' => 'u', + '𝑣' => 'v', + '𝑤' => 'w', + '𝑥' => 'x', + '𝑦' => 'y', + '𝑧' => 'z', + '𝑨' => 'A', + '𝑩' => 'B', + '𝑪' => 'C', + '𝑫' => 'D', + '𝑬' => 'E', + '𝑭' => 'F', + '𝑮' => 'G', + '𝑯' => 'H', + '𝑰' => 'I', + '𝑱' => 'J', + '𝑲' => 'K', + '𝑳' => 'L', + '𝑴' => 'M', + '𝑵' => 'N', + '𝑶' => 'O', + '𝑷' => 'P', + '𝑸' => 'Q', + '𝑹' => 'R', + '𝑺' => 'S', + '𝑻' => 'T', + '𝑼' => 'U', + '𝑽' => 'V', + '𝑾' => 'W', + '𝑿' => 'X', + '𝒀' => 'Y', + '𝒁' => 'Z', + '𝒂' => 'a', + '𝒃' => 'b', + '𝒄' => 'c', + '𝒅' => 'd', + '𝒆' => 'e', + '𝒇' => 'f', + '𝒈' => 'g', + '𝒉' => 'h', + '𝒊' => 'i', + '𝒋' => 'j', + '𝒌' => 'k', + '𝒍' => 'l', + '𝒎' => 'm', + '𝒏' => 'n', + '𝒐' => 'o', + '𝒑' => 'p', + '𝒒' => 'q', + '𝒓' => 'r', + '𝒔' => 's', + '𝒕' => 't', + '𝒖' => 'u', + '𝒗' => 'v', + '𝒘' => 'w', + '𝒙' => 'x', + '𝒚' => 'y', + '𝒛' => 'z', + '𝒜' => 'A', + '𝒞' => 'C', + '𝒟' => 'D', + '𝒢' => 'G', + '𝒥' => 'J', + '𝒦' => 'K', + '𝒩' => 'N', + '𝒪' => 'O', + '𝒫' => 'P', + '𝒬' => 'Q', + '𝒮' => 'S', + '𝒯' => 'T', + '𝒰' => 'U', + '𝒱' => 'V', + '𝒲' => 'W', + '𝒳' => 'X', + '𝒴' => 'Y', + '𝒵' => 'Z', + '𝒶' => 'a', + '𝒷' => 'b', + '𝒸' => 'c', + '𝒹' => 'd', + '𝒻' => 'f', + '𝒽' => 'h', + '𝒾' => 'i', + '𝒿' => 'j', + '𝓀' => 'k', + '𝓁' => 'l', + '𝓂' => 'm', + '𝓃' => 'n', + '𝓅' => 'p', + '𝓆' => 'q', + '𝓇' => 'r', + '𝓈' => 's', + '𝓉' => 't', + '𝓊' => 'u', + '𝓋' => 'v', + '𝓌' => 'w', + '𝓍' => 'x', + '𝓎' => 'y', + '𝓏' => 'z', + '𝓐' => 'A', + '𝓑' => 'B', + '𝓒' => 'C', + '𝓓' => 'D', + '𝓔' => 'E', + '𝓕' => 'F', + '𝓖' => 'G', + '𝓗' => 'H', + '𝓘' => 'I', + '𝓙' => 'J', + '𝓚' => 'K', + '𝓛' => 'L', + '𝓜' => 'M', + '𝓝' => 'N', + '𝓞' => 'O', + '𝓟' => 'P', + '𝓠' => 'Q', + '𝓡' => 'R', + '𝓢' => 'S', + '𝓣' => 'T', + '𝓤' => 'U', + '𝓥' => 'V', + '𝓦' => 'W', + '𝓧' => 'X', + '𝓨' => 'Y', + '𝓩' => 'Z', + '𝓪' => 'a', + '𝓫' => 'b', + '𝓬' => 'c', + '𝓭' => 'd', + '𝓮' => 'e', + '𝓯' => 'f', + '𝓰' => 'g', + '𝓱' => 'h', + '𝓲' => 'i', + '𝓳' => 'j', + '𝓴' => 'k', + '𝓵' => 'l', + '𝓶' => 'm', + '𝓷' => 'n', + '𝓸' => 'o', + '𝓹' => 'p', + '𝓺' => 'q', + '𝓻' => 'r', + '𝓼' => 's', + '𝓽' => 't', + '𝓾' => 'u', + '𝓿' => 'v', + '𝔀' => 'w', + '𝔁' => 'x', + '𝔂' => 'y', + '𝔃' => 'z', + '𝔄' => 'A', + '𝔅' => 'B', + '𝔇' => 'D', + '𝔈' => 'E', + '𝔉' => 'F', + '𝔊' => 'G', + '𝔍' => 'J', + '𝔎' => 'K', + '𝔏' => 'L', + '𝔐' => 'M', + '𝔑' => 'N', + '𝔒' => 'O', + '𝔓' => 'P', + '𝔔' => 'Q', + '𝔖' => 'S', + '𝔗' => 'T', + '𝔘' => 'U', + '𝔙' => 'V', + '𝔚' => 'W', + '𝔛' => 'X', + '𝔜' => 'Y', + '𝔞' => 'a', + '𝔟' => 'b', + '𝔠' => 'c', + '𝔡' => 'd', + '𝔢' => 'e', + '𝔣' => 'f', + '𝔤' => 'g', + '𝔥' => 'h', + '𝔦' => 'i', + '𝔧' => 'j', + '𝔨' => 'k', + '𝔩' => 'l', + '𝔪' => 'm', + '𝔫' => 'n', + '𝔬' => 'o', + '𝔭' => 'p', + '𝔮' => 'q', + '𝔯' => 'r', + '𝔰' => 's', + '𝔱' => 't', + '𝔲' => 'u', + '𝔳' => 'v', + '𝔴' => 'w', + '𝔵' => 'x', + '𝔶' => 'y', + '𝔷' => 'z', + '𝔸' => 'A', + '𝔹' => 'B', + '𝔻' => 'D', + '𝔼' => 'E', + '𝔽' => 'F', + '𝔾' => 'G', + '𝕀' => 'I', + '𝕁' => 'J', + '𝕂' => 'K', + '𝕃' => 'L', + '𝕄' => 'M', + '𝕆' => 'O', + '𝕊' => 'S', + '𝕋' => 'T', + '𝕌' => 'U', + '𝕍' => 'V', + '𝕎' => 'W', + '𝕏' => 'X', + '𝕐' => 'Y', + '𝕒' => 'a', + '𝕓' => 'b', + '𝕔' => 'c', + '𝕕' => 'd', + '𝕖' => 'e', + '𝕗' => 'f', + '𝕘' => 'g', + '𝕙' => 'h', + '𝕚' => 'i', + '𝕛' => 'j', + '𝕜' => 'k', + '𝕝' => 'l', + '𝕞' => 'm', + '𝕟' => 'n', + '𝕠' => 'o', + '𝕡' => 'p', + '𝕢' => 'q', + '𝕣' => 'r', + '𝕤' => 's', + '𝕥' => 't', + '𝕦' => 'u', + '𝕧' => 'v', + '𝕨' => 'w', + '𝕩' => 'x', + '𝕪' => 'y', + '𝕫' => 'z', + '𝕬' => 'A', + '𝕭' => 'B', + '𝕮' => 'C', + '𝕯' => 'D', + '𝕰' => 'E', + '𝕱' => 'F', + '𝕲' => 'G', + '𝕳' => 'H', + '𝕴' => 'I', + '𝕵' => 'J', + '𝕶' => 'K', + '𝕷' => 'L', + '𝕸' => 'M', + '𝕹' => 'N', + '𝕺' => 'O', + '𝕻' => 'P', + '𝕼' => 'Q', + '𝕽' => 'R', + '𝕾' => 'S', + '𝕿' => 'T', + '𝖀' => 'U', + '𝖁' => 'V', + '𝖂' => 'W', + '𝖃' => 'X', + '𝖄' => 'Y', + '𝖅' => 'Z', + '𝖆' => 'a', + '𝖇' => 'b', + '𝖈' => 'c', + '𝖉' => 'd', + '𝖊' => 'e', + '𝖋' => 'f', + '𝖌' => 'g', + '𝖍' => 'h', + '𝖎' => 'i', + '𝖏' => 'j', + '𝖐' => 'k', + '𝖑' => 'l', + '𝖒' => 'm', + '𝖓' => 'n', + '𝖔' => 'o', + '𝖕' => 'p', + '𝖖' => 'q', + '𝖗' => 'r', + '𝖘' => 's', + '𝖙' => 't', + '𝖚' => 'u', + '𝖛' => 'v', + '𝖜' => 'w', + '𝖝' => 'x', + '𝖞' => 'y', + '𝖟' => 'z', + '𝖠' => 'A', + '𝖡' => 'B', + '𝖢' => 'C', + '𝖣' => 'D', + '𝖤' => 'E', + '𝖥' => 'F', + '𝖦' => 'G', + '𝖧' => 'H', + '𝖨' => 'I', + '𝖩' => 'J', + '𝖪' => 'K', + '𝖫' => 'L', + '𝖬' => 'M', + '𝖭' => 'N', + '𝖮' => 'O', + '𝖯' => 'P', + '𝖰' => 'Q', + '𝖱' => 'R', + '𝖲' => 'S', + '𝖳' => 'T', + '𝖴' => 'U', + '𝖵' => 'V', + '𝖶' => 'W', + '𝖷' => 'X', + '𝖸' => 'Y', + '𝖹' => 'Z', + '𝖺' => 'a', + '𝖻' => 'b', + '𝖼' => 'c', + '𝖽' => 'd', + '𝖾' => 'e', + '𝖿' => 'f', + '𝗀' => 'g', + '𝗁' => 'h', + '𝗂' => 'i', + '𝗃' => 'j', + '𝗄' => 'k', + '𝗅' => 'l', + '𝗆' => 'm', + '𝗇' => 'n', + '𝗈' => 'o', + '𝗉' => 'p', + '𝗊' => 'q', + '𝗋' => 'r', + '𝗌' => 's', + '𝗍' => 't', + '𝗎' => 'u', + '𝗏' => 'v', + '𝗐' => 'w', + '𝗑' => 'x', + '𝗒' => 'y', + '𝗓' => 'z', + '𝗔' => 'A', + '𝗕' => 'B', + '𝗖' => 'C', + '𝗗' => 'D', + '𝗘' => 'E', + '𝗙' => 'F', + '𝗚' => 'G', + '𝗛' => 'H', + '𝗜' => 'I', + '𝗝' => 'J', + '𝗞' => 'K', + '𝗟' => 'L', + '𝗠' => 'M', + '𝗡' => 'N', + '𝗢' => 'O', + '𝗣' => 'P', + '𝗤' => 'Q', + '𝗥' => 'R', + '𝗦' => 'S', + '𝗧' => 'T', + '𝗨' => 'U', + '𝗩' => 'V', + '𝗪' => 'W', + '𝗫' => 'X', + '𝗬' => 'Y', + '𝗭' => 'Z', + '𝗮' => 'a', + '𝗯' => 'b', + '𝗰' => 'c', + '𝗱' => 'd', + '𝗲' => 'e', + '𝗳' => 'f', + '𝗴' => 'g', + '𝗵' => 'h', + '𝗶' => 'i', + '𝗷' => 'j', + '𝗸' => 'k', + '𝗹' => 'l', + '𝗺' => 'm', + '𝗻' => 'n', + '𝗼' => 'o', + '𝗽' => 'p', + '𝗾' => 'q', + '𝗿' => 'r', + '𝘀' => 's', + '𝘁' => 't', + '𝘂' => 'u', + '𝘃' => 'v', + '𝘄' => 'w', + '𝘅' => 'x', + '𝘆' => 'y', + '𝘇' => 'z', + '𝘈' => 'A', + '𝘉' => 'B', + '𝘊' => 'C', + '𝘋' => 'D', + '𝘌' => 'E', + '𝘍' => 'F', + '𝘎' => 'G', + '𝘏' => 'H', + '𝘐' => 'I', + '𝘑' => 'J', + '𝘒' => 'K', + '𝘓' => 'L', + '𝘔' => 'M', + '𝘕' => 'N', + '𝘖' => 'O', + '𝘗' => 'P', + '𝘘' => 'Q', + '𝘙' => 'R', + '𝘚' => 'S', + '𝘛' => 'T', + '𝘜' => 'U', + '𝘝' => 'V', + '𝘞' => 'W', + '𝘟' => 'X', + '𝘠' => 'Y', + '𝘡' => 'Z', + '𝘢' => 'a', + '𝘣' => 'b', + '𝘤' => 'c', + '𝘥' => 'd', + '𝘦' => 'e', + '𝘧' => 'f', + '𝘨' => 'g', + '𝘩' => 'h', + '𝘪' => 'i', + '𝘫' => 'j', + '𝘬' => 'k', + '𝘭' => 'l', + '𝘮' => 'm', + '𝘯' => 'n', + '𝘰' => 'o', + '𝘱' => 'p', + '𝘲' => 'q', + '𝘳' => 'r', + '𝘴' => 's', + '𝘵' => 't', + '𝘶' => 'u', + '𝘷' => 'v', + '𝘸' => 'w', + '𝘹' => 'x', + '𝘺' => 'y', + '𝘻' => 'z', + '𝘼' => 'A', + '𝘽' => 'B', + '𝘾' => 'C', + '𝘿' => 'D', + '𝙀' => 'E', + '𝙁' => 'F', + '𝙂' => 'G', + '𝙃' => 'H', + '𝙄' => 'I', + '𝙅' => 'J', + '𝙆' => 'K', + '𝙇' => 'L', + '𝙈' => 'M', + '𝙉' => 'N', + '𝙊' => 'O', + '𝙋' => 'P', + '𝙌' => 'Q', + '𝙍' => 'R', + '𝙎' => 'S', + '𝙏' => 'T', + '𝙐' => 'U', + '𝙑' => 'V', + '𝙒' => 'W', + '𝙓' => 'X', + '𝙔' => 'Y', + '𝙕' => 'Z', + '𝙖' => 'a', + '𝙗' => 'b', + '𝙘' => 'c', + '𝙙' => 'd', + '𝙚' => 'e', + '𝙛' => 'f', + '𝙜' => 'g', + '𝙝' => 'h', + '𝙞' => 'i', + '𝙟' => 'j', + '𝙠' => 'k', + '𝙡' => 'l', + '𝙢' => 'm', + '𝙣' => 'n', + '𝙤' => 'o', + '𝙥' => 'p', + '𝙦' => 'q', + '𝙧' => 'r', + '𝙨' => 's', + '𝙩' => 't', + '𝙪' => 'u', + '𝙫' => 'v', + '𝙬' => 'w', + '𝙭' => 'x', + '𝙮' => 'y', + '𝙯' => 'z', + '𝙰' => 'A', + '𝙱' => 'B', + '𝙲' => 'C', + '𝙳' => 'D', + '𝙴' => 'E', + '𝙵' => 'F', + '𝙶' => 'G', + '𝙷' => 'H', + '𝙸' => 'I', + '𝙹' => 'J', + '𝙺' => 'K', + '𝙻' => 'L', + '𝙼' => 'M', + '𝙽' => 'N', + '𝙾' => 'O', + '𝙿' => 'P', + '𝚀' => 'Q', + '𝚁' => 'R', + '𝚂' => 'S', + '𝚃' => 'T', + '𝚄' => 'U', + '𝚅' => 'V', + '𝚆' => 'W', + '𝚇' => 'X', + '𝚈' => 'Y', + '𝚉' => 'Z', + '𝚊' => 'a', + '𝚋' => 'b', + '𝚌' => 'c', + '𝚍' => 'd', + '𝚎' => 'e', + '𝚏' => 'f', + '𝚐' => 'g', + '𝚑' => 'h', + '𝚒' => 'i', + '𝚓' => 'j', + '𝚔' => 'k', + '𝚕' => 'l', + '𝚖' => 'm', + '𝚗' => 'n', + '𝚘' => 'o', + '𝚙' => 'p', + '𝚚' => 'q', + '𝚛' => 'r', + '𝚜' => 's', + '𝚝' => 't', + '𝚞' => 'u', + '𝚟' => 'v', + '𝚠' => 'w', + '𝚡' => 'x', + '𝚢' => 'y', + '𝚣' => 'z', + '𝚤' => 'ı', + '𝚥' => 'ȷ', + '𝚨' => 'Α', + '𝚩' => 'Β', + '𝚪' => 'Γ', + '𝚫' => 'Δ', + '𝚬' => 'Ε', + '𝚭' => 'Ζ', + '𝚮' => 'Η', + '𝚯' => 'Θ', + '𝚰' => 'Ι', + '𝚱' => 'Κ', + '𝚲' => 'Λ', + '𝚳' => 'Μ', + '𝚴' => 'Ν', + '𝚵' => 'Ξ', + '𝚶' => 'Ο', + '𝚷' => 'Π', + '𝚸' => 'Ρ', + '𝚹' => 'Θ', + '𝚺' => 'Σ', + '𝚻' => 'Τ', + '𝚼' => 'Υ', + '𝚽' => 'Φ', + '𝚾' => 'Χ', + '𝚿' => 'Ψ', + '𝛀' => 'Ω', + '𝛁' => '∇', + '𝛂' => 'α', + '𝛃' => 'β', + '𝛄' => 'γ', + '𝛅' => 'δ', + '𝛆' => 'ε', + '𝛇' => 'ζ', + '𝛈' => 'η', + '𝛉' => 'θ', + '𝛊' => 'ι', + '𝛋' => 'κ', + '𝛌' => 'λ', + '𝛍' => 'μ', + '𝛎' => 'ν', + '𝛏' => 'ξ', + '𝛐' => 'ο', + '𝛑' => 'π', + '𝛒' => 'ρ', + '𝛓' => 'ς', + '𝛔' => 'σ', + '𝛕' => 'τ', + '𝛖' => 'υ', + '𝛗' => 'φ', + '𝛘' => 'χ', + '𝛙' => 'ψ', + '𝛚' => 'ω', + '𝛛' => '∂', + '𝛜' => 'ε', + '𝛝' => 'θ', + '𝛞' => 'κ', + '𝛟' => 'φ', + '𝛠' => 'ρ', + '𝛡' => 'π', + '𝛢' => 'Α', + '𝛣' => 'Β', + '𝛤' => 'Γ', + '𝛥' => 'Δ', + '𝛦' => 'Ε', + '𝛧' => 'Ζ', + '𝛨' => 'Η', + '𝛩' => 'Θ', + '𝛪' => 'Ι', + '𝛫' => 'Κ', + '𝛬' => 'Λ', + '𝛭' => 'Μ', + '𝛮' => 'Ν', + '𝛯' => 'Ξ', + '𝛰' => 'Ο', + '𝛱' => 'Π', + '𝛲' => 'Ρ', + '𝛳' => 'Θ', + '𝛴' => 'Σ', + '𝛵' => 'Τ', + '𝛶' => 'Υ', + '𝛷' => 'Φ', + '𝛸' => 'Χ', + '𝛹' => 'Ψ', + '𝛺' => 'Ω', + '𝛻' => '∇', + '𝛼' => 'α', + '𝛽' => 'β', + '𝛾' => 'γ', + '𝛿' => 'δ', + '𝜀' => 'ε', + '𝜁' => 'ζ', + '𝜂' => 'η', + '𝜃' => 'θ', + '𝜄' => 'ι', + '𝜅' => 'κ', + '𝜆' => 'λ', + '𝜇' => 'μ', + '𝜈' => 'ν', + '𝜉' => 'ξ', + '𝜊' => 'ο', + '𝜋' => 'π', + '𝜌' => 'ρ', + '𝜍' => 'ς', + '𝜎' => 'σ', + '𝜏' => 'τ', + '𝜐' => 'υ', + '𝜑' => 'φ', + '𝜒' => 'χ', + '𝜓' => 'ψ', + '𝜔' => 'ω', + '𝜕' => '∂', + '𝜖' => 'ε', + '𝜗' => 'θ', + '𝜘' => 'κ', + '𝜙' => 'φ', + '𝜚' => 'ρ', + '𝜛' => 'π', + '𝜜' => 'Α', + '𝜝' => 'Β', + '𝜞' => 'Γ', + '𝜟' => 'Δ', + '𝜠' => 'Ε', + '𝜡' => 'Ζ', + '𝜢' => 'Η', + '𝜣' => 'Θ', + '𝜤' => 'Ι', + '𝜥' => 'Κ', + '𝜦' => 'Λ', + '𝜧' => 'Μ', + '𝜨' => 'Ν', + '𝜩' => 'Ξ', + '𝜪' => 'Ο', + '𝜫' => 'Π', + '𝜬' => 'Ρ', + '𝜭' => 'Θ', + '𝜮' => 'Σ', + '𝜯' => 'Τ', + '𝜰' => 'Υ', + '𝜱' => 'Φ', + '𝜲' => 'Χ', + '𝜳' => 'Ψ', + '𝜴' => 'Ω', + '𝜵' => '∇', + '𝜶' => 'α', + '𝜷' => 'β', + '𝜸' => 'γ', + '𝜹' => 'δ', + '𝜺' => 'ε', + '𝜻' => 'ζ', + '𝜼' => 'η', + '𝜽' => 'θ', + '𝜾' => 'ι', + '𝜿' => 'κ', + '𝝀' => 'λ', + '𝝁' => 'μ', + '𝝂' => 'ν', + '𝝃' => 'ξ', + '𝝄' => 'ο', + '𝝅' => 'π', + '𝝆' => 'ρ', + '𝝇' => 'ς', + '𝝈' => 'σ', + '𝝉' => 'τ', + '𝝊' => 'υ', + '𝝋' => 'φ', + '𝝌' => 'χ', + '𝝍' => 'ψ', + '𝝎' => 'ω', + '𝝏' => '∂', + '𝝐' => 'ε', + '𝝑' => 'θ', + '𝝒' => 'κ', + '𝝓' => 'φ', + '𝝔' => 'ρ', + '𝝕' => 'π', + '𝝖' => 'Α', + '𝝗' => 'Β', + '𝝘' => 'Γ', + '𝝙' => 'Δ', + '𝝚' => 'Ε', + '𝝛' => 'Ζ', + '𝝜' => 'Η', + '𝝝' => 'Θ', + '𝝞' => 'Ι', + '𝝟' => 'Κ', + '𝝠' => 'Λ', + '𝝡' => 'Μ', + '𝝢' => 'Ν', + '𝝣' => 'Ξ', + '𝝤' => 'Ο', + '𝝥' => 'Π', + '𝝦' => 'Ρ', + '𝝧' => 'Θ', + '𝝨' => 'Σ', + '𝝩' => 'Τ', + '𝝪' => 'Υ', + '𝝫' => 'Φ', + '𝝬' => 'Χ', + '𝝭' => 'Ψ', + '𝝮' => 'Ω', + '𝝯' => '∇', + '𝝰' => 'α', + '𝝱' => 'β', + '𝝲' => 'γ', + '𝝳' => 'δ', + '𝝴' => 'ε', + '𝝵' => 'ζ', + '𝝶' => 'η', + '𝝷' => 'θ', + '𝝸' => 'ι', + '𝝹' => 'κ', + '𝝺' => 'λ', + '𝝻' => 'μ', + '𝝼' => 'ν', + '𝝽' => 'ξ', + '𝝾' => 'ο', + '𝝿' => 'π', + '𝞀' => 'ρ', + '𝞁' => 'ς', + '𝞂' => 'σ', + '𝞃' => 'τ', + '𝞄' => 'υ', + '𝞅' => 'φ', + '𝞆' => 'χ', + '𝞇' => 'ψ', + '𝞈' => 'ω', + '𝞉' => '∂', + '𝞊' => 'ε', + '𝞋' => 'θ', + '𝞌' => 'κ', + '𝞍' => 'φ', + '𝞎' => 'ρ', + '𝞏' => 'π', + '𝞐' => 'Α', + '𝞑' => 'Β', + '𝞒' => 'Γ', + '𝞓' => 'Δ', + '𝞔' => 'Ε', + '𝞕' => 'Ζ', + '𝞖' => 'Η', + '𝞗' => 'Θ', + '𝞘' => 'Ι', + '𝞙' => 'Κ', + '𝞚' => 'Λ', + '𝞛' => 'Μ', + '𝞜' => 'Ν', + '𝞝' => 'Ξ', + '𝞞' => 'Ο', + '𝞟' => 'Π', + '𝞠' => 'Ρ', + '𝞡' => 'Θ', + '𝞢' => 'Σ', + '𝞣' => 'Τ', + '𝞤' => 'Υ', + '𝞥' => 'Φ', + '𝞦' => 'Χ', + '𝞧' => 'Ψ', + '𝞨' => 'Ω', + '𝞩' => '∇', + '𝞪' => 'α', + '𝞫' => 'β', + '𝞬' => 'γ', + '𝞭' => 'δ', + '𝞮' => 'ε', + '𝞯' => 'ζ', + '𝞰' => 'η', + '𝞱' => 'θ', + '𝞲' => 'ι', + '𝞳' => 'κ', + '𝞴' => 'λ', + '𝞵' => 'μ', + '𝞶' => 'ν', + '𝞷' => 'ξ', + '𝞸' => 'ο', + '𝞹' => 'π', + '𝞺' => 'ρ', + '𝞻' => 'ς', + '𝞼' => 'σ', + '𝞽' => 'τ', + '𝞾' => 'υ', + '𝞿' => 'φ', + '𝟀' => 'χ', + '𝟁' => 'ψ', + '𝟂' => 'ω', + '𝟃' => '∂', + '𝟄' => 'ε', + '𝟅' => 'θ', + '𝟆' => 'κ', + '𝟇' => 'φ', + '𝟈' => 'ρ', + '𝟉' => 'π', + '𝟊' => 'Ϝ', + '𝟋' => 'ϝ', + '𝟎' => '0', + '𝟏' => '1', + '𝟐' => '2', + '𝟑' => '3', + '𝟒' => '4', + '𝟓' => '5', + '𝟔' => '6', + '𝟕' => '7', + '𝟖' => '8', + '𝟗' => '9', + '𝟘' => '0', + '𝟙' => '1', + '𝟚' => '2', + '𝟛' => '3', + '𝟜' => '4', + '𝟝' => '5', + '𝟞' => '6', + '𝟟' => '7', + '𝟠' => '8', + '𝟡' => '9', + '𝟢' => '0', + '𝟣' => '1', + '𝟤' => '2', + '𝟥' => '3', + '𝟦' => '4', + '𝟧' => '5', + '𝟨' => '6', + '𝟩' => '7', + '𝟪' => '8', + '𝟫' => '9', + '𝟬' => '0', + '𝟭' => '1', + '𝟮' => '2', + '𝟯' => '3', + '𝟰' => '4', + '𝟱' => '5', + '𝟲' => '6', + '𝟳' => '7', + '𝟴' => '8', + '𝟵' => '9', + '𝟶' => '0', + '𝟷' => '1', + '𝟸' => '2', + '𝟹' => '3', + '𝟺' => '4', + '𝟻' => '5', + '𝟼' => '6', + '𝟽' => '7', + '𝟾' => '8', + '𝟿' => '9', + '𞸀' => 'ا', + '𞸁' => 'ب', + '𞸂' => 'ج', + '𞸃' => 'د', + '𞸅' => 'و', + '𞸆' => 'ز', + '𞸇' => 'ح', + '𞸈' => 'ط', + '𞸉' => 'ي', + '𞸊' => 'ك', + '𞸋' => 'ل', + '𞸌' => 'م', + '𞸍' => 'ن', + '𞸎' => 'س', + '𞸏' => 'ع', + '𞸐' => 'ف', + '𞸑' => 'ص', + '𞸒' => 'ق', + '𞸓' => 'ر', + '𞸔' => 'ش', + '𞸕' => 'ت', + '𞸖' => 'ث', + '𞸗' => 'خ', + '𞸘' => 'ذ', + '𞸙' => 'ض', + '𞸚' => 'ظ', + '𞸛' => 'غ', + '𞸜' => 'ٮ', + '𞸝' => 'ں', + '𞸞' => 'ڡ', + '𞸟' => 'ٯ', + '𞸡' => 'ب', + '𞸢' => 'ج', + '𞸤' => 'ه', + '𞸧' => 'ح', + '𞸩' => 'ي', + '𞸪' => 'ك', + '𞸫' => 'ل', + '𞸬' => 'م', + '𞸭' => 'ن', + '𞸮' => 'س', + '𞸯' => 'ع', + '𞸰' => 'ف', + '𞸱' => 'ص', + '𞸲' => 'ق', + '𞸴' => 'ش', + '𞸵' => 'ت', + '𞸶' => 'ث', + '𞸷' => 'خ', + '𞸹' => 'ض', + '𞸻' => 'غ', + '𞹂' => 'ج', + '𞹇' => 'ح', + '𞹉' => 'ي', + '𞹋' => 'ل', + '𞹍' => 'ن', + '𞹎' => 'س', + '𞹏' => 'ع', + '𞹑' => 'ص', + '𞹒' => 'ق', + '𞹔' => 'ش', + '𞹗' => 'خ', + '𞹙' => 'ض', + '𞹛' => 'غ', + '𞹝' => 'ں', + '𞹟' => 'ٯ', + '𞹡' => 'ب', + '𞹢' => 'ج', + '𞹤' => 'ه', + '𞹧' => 'ح', + '𞹨' => 'ط', + '𞹩' => 'ي', + '𞹪' => 'ك', + '𞹬' => 'م', + '𞹭' => 'ن', + '𞹮' => 'س', + '𞹯' => 'ع', + '𞹰' => 'ف', + '𞹱' => 'ص', + '𞹲' => 'ق', + '𞹴' => 'ش', + '𞹵' => 'ت', + '𞹶' => 'ث', + '𞹷' => 'خ', + '𞹹' => 'ض', + '𞹺' => 'ظ', + '𞹻' => 'غ', + '𞹼' => 'ٮ', + '𞹾' => 'ڡ', + '𞺀' => 'ا', + '𞺁' => 'ب', + '𞺂' => 'ج', + '𞺃' => 'د', + '𞺄' => 'ه', + '𞺅' => 'و', + '𞺆' => 'ز', + '𞺇' => 'ح', + '𞺈' => 'ط', + '𞺉' => 'ي', + '𞺋' => 'ل', + '𞺌' => 'م', + '𞺍' => 'ن', + '𞺎' => 'س', + '𞺏' => 'ع', + '𞺐' => 'ف', + '𞺑' => 'ص', + '𞺒' => 'ق', + '𞺓' => 'ر', + '𞺔' => 'ش', + '𞺕' => 'ت', + '𞺖' => 'ث', + '𞺗' => 'خ', + '𞺘' => 'ذ', + '𞺙' => 'ض', + '𞺚' => 'ظ', + '𞺛' => 'غ', + '𞺡' => 'ب', + '𞺢' => 'ج', + '𞺣' => 'د', + '𞺥' => 'و', + '𞺦' => 'ز', + '𞺧' => 'ح', + '𞺨' => 'ط', + '𞺩' => 'ي', + '𞺫' => 'ل', + '𞺬' => 'م', + '𞺭' => 'ن', + '𞺮' => 'س', + '𞺯' => 'ع', + '𞺰' => 'ف', + '𞺱' => 'ص', + '𞺲' => 'ق', + '𞺳' => 'ر', + '𞺴' => 'ش', + '𞺵' => 'ت', + '𞺶' => 'ث', + '𞺷' => 'خ', + '𞺸' => 'ذ', + '𞺹' => 'ض', + '𞺺' => 'ظ', + '𞺻' => 'غ', + '🄀' => '0.', + '🄁' => '0,', + '🄂' => '1,', + '🄃' => '2,', + '🄄' => '3,', + '🄅' => '4,', + '🄆' => '5,', + '🄇' => '6,', + '🄈' => '7,', + '🄉' => '8,', + '🄊' => '9,', + '🄐' => '(A)', + '🄑' => '(B)', + '🄒' => '(C)', + '🄓' => '(D)', + '🄔' => '(E)', + '🄕' => '(F)', + '🄖' => '(G)', + '🄗' => '(H)', + '🄘' => '(I)', + '🄙' => '(J)', + '🄚' => '(K)', + '🄛' => '(L)', + '🄜' => '(M)', + '🄝' => '(N)', + '🄞' => '(O)', + '🄟' => '(P)', + '🄠' => '(Q)', + '🄡' => '(R)', + '🄢' => '(S)', + '🄣' => '(T)', + '🄤' => '(U)', + '🄥' => '(V)', + '🄦' => '(W)', + '🄧' => '(X)', + '🄨' => '(Y)', + '🄩' => '(Z)', + '🄪' => '〔S〕', + '🄫' => 'C', + '🄬' => 'R', + '🄭' => 'CD', + '🄮' => 'WZ', + '🄰' => 'A', + '🄱' => 'B', + '🄲' => 'C', + '🄳' => 'D', + '🄴' => 'E', + '🄵' => 'F', + '🄶' => 'G', + '🄷' => 'H', + '🄸' => 'I', + '🄹' => 'J', + '🄺' => 'K', + '🄻' => 'L', + '🄼' => 'M', + '🄽' => 'N', + '🄾' => 'O', + '🄿' => 'P', + '🅀' => 'Q', + '🅁' => 'R', + '🅂' => 'S', + '🅃' => 'T', + '🅄' => 'U', + '🅅' => 'V', + '🅆' => 'W', + '🅇' => 'X', + '🅈' => 'Y', + '🅉' => 'Z', + '🅊' => 'HV', + '🅋' => 'MV', + '🅌' => 'SD', + '🅍' => 'SS', + '🅎' => 'PPV', + '🅏' => 'WC', + '🅪' => 'MC', + '🅫' => 'MD', + '🅬' => 'MR', + '🆐' => 'DJ', + '🈀' => 'ほか', + '🈁' => 'ココ', + '🈂' => 'サ', + '🈐' => '手', + '🈑' => '字', + '🈒' => '双', + '🈓' => 'デ', + '🈔' => '二', + '🈕' => '多', + '🈖' => '解', + '🈗' => '天', + '🈘' => '交', + '🈙' => '映', + '🈚' => '無', + '🈛' => '料', + '🈜' => '前', + '🈝' => '後', + '🈞' => '再', + '🈟' => '新', + '🈠' => '初', + '🈡' => '終', + '🈢' => '生', + '🈣' => '販', + '🈤' => '声', + '🈥' => '吹', + '🈦' => '演', + '🈧' => '投', + '🈨' => '捕', + '🈩' => '一', + '🈪' => '三', + '🈫' => '遊', + '🈬' => '左', + '🈭' => '中', + '🈮' => '右', + '🈯' => '指', + '🈰' => '走', + '🈱' => '打', + '🈲' => '禁', + '🈳' => '空', + '🈴' => '合', + '🈵' => '満', + '🈶' => '有', + '🈷' => '月', + '🈸' => '申', + '🈹' => '割', + '🈺' => '営', + '🈻' => '配', + '🉀' => '〔本〕', + '🉁' => '〔三〕', + '🉂' => '〔二〕', + '🉃' => '〔安〕', + '🉄' => '〔点〕', + '🉅' => '〔打〕', + '🉆' => '〔盗〕', + '🉇' => '〔勝〕', + '🉈' => '〔敗〕', + '🉐' => '得', + '🉑' => '可', + '🯰' => '0', + '🯱' => '1', + '🯲' => '2', + '🯳' => '3', + '🯴' => '4', + '🯵' => '5', + '🯶' => '6', + '🯷' => '7', + '🯸' => '8', + '🯹' => '9', +); diff --git a/vendor/symfony/polyfill-intl-normalizer/bootstrap.php b/vendor/symfony/polyfill-intl-normalizer/bootstrap.php new file mode 100644 index 00000000..3608e5c0 --- /dev/null +++ b/vendor/symfony/polyfill-intl-normalizer/bootstrap.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Intl\Normalizer as p; + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +if (!function_exists('normalizer_is_normalized')) { + function normalizer_is_normalized($string, $form = p\Normalizer::FORM_C) { return p\Normalizer::isNormalized($string, $form); } +} +if (!function_exists('normalizer_normalize')) { + function normalizer_normalize($string, $form = p\Normalizer::FORM_C) { return p\Normalizer::normalize($string, $form); } +} diff --git a/vendor/symfony/polyfill-intl-normalizer/bootstrap80.php b/vendor/symfony/polyfill-intl-normalizer/bootstrap80.php new file mode 100644 index 00000000..e36d1a94 --- /dev/null +++ b/vendor/symfony/polyfill-intl-normalizer/bootstrap80.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Intl\Normalizer as p; + +if (!function_exists('normalizer_is_normalized')) { + function normalizer_is_normalized(?string $string, ?int $form = p\Normalizer::FORM_C): bool { return p\Normalizer::isNormalized((string) $string, (int) $form); } +} +if (!function_exists('normalizer_normalize')) { + function normalizer_normalize(?string $string, ?int $form = p\Normalizer::FORM_C): string|false { return p\Normalizer::normalize((string) $string, (int) $form); } +} diff --git a/vendor/symfony/polyfill-intl-normalizer/composer.json b/vendor/symfony/polyfill-intl-normalizer/composer.json new file mode 100644 index 00000000..1b93573a --- /dev/null +++ b/vendor/symfony/polyfill-intl-normalizer/composer.json @@ -0,0 +1,36 @@ +{ + "name": "symfony/polyfill-intl-normalizer", + "type": "library", + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "keywords": ["polyfill", "shim", "compatibility", "portable", "intl", "normalizer"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Intl\\Normalizer\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "suggest": { + "ext-intl": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/vendor/symfony/polyfill-mbstring/LICENSE b/vendor/symfony/polyfill-mbstring/LICENSE new file mode 100644 index 00000000..6e3afce6 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/polyfill-mbstring/Mbstring.php b/vendor/symfony/polyfill-mbstring/Mbstring.php new file mode 100644 index 00000000..2e0b9694 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Mbstring.php @@ -0,0 +1,947 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Mbstring; + +/** + * Partial mbstring implementation in PHP, iconv based, UTF-8 centric. + * + * Implemented: + * - mb_chr - Returns a specific character from its Unicode code point + * - mb_convert_encoding - Convert character encoding + * - mb_convert_variables - Convert character code in variable(s) + * - mb_decode_mimeheader - Decode string in MIME header field + * - mb_encode_mimeheader - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED + * - mb_decode_numericentity - Decode HTML numeric string reference to character + * - mb_encode_numericentity - Encode character to HTML numeric string reference + * - mb_convert_case - Perform case folding on a string + * - mb_detect_encoding - Detect character encoding + * - mb_get_info - Get internal settings of mbstring + * - mb_http_input - Detect HTTP input character encoding + * - mb_http_output - Set/Get HTTP output character encoding + * - mb_internal_encoding - Set/Get internal character encoding + * - mb_list_encodings - Returns an array of all supported encodings + * - mb_ord - Returns the Unicode code point of a character + * - mb_output_handler - Callback function converts character encoding in output buffer + * - mb_scrub - Replaces ill-formed byte sequences with substitute characters + * - mb_strlen - Get string length + * - mb_strpos - Find position of first occurrence of string in a string + * - mb_strrpos - Find position of last occurrence of a string in a string + * - mb_str_split - Convert a string to an array + * - mb_strtolower - Make a string lowercase + * - mb_strtoupper - Make a string uppercase + * - mb_substitute_character - Set/Get substitution character + * - mb_substr - Get part of string + * - mb_stripos - Finds position of first occurrence of a string within another, case insensitive + * - mb_stristr - Finds first occurrence of a string within another, case insensitive + * - mb_strrchr - Finds the last occurrence of a character in a string within another + * - mb_strrichr - Finds the last occurrence of a character in a string within another, case insensitive + * - mb_strripos - Finds position of last occurrence of a string within another, case insensitive + * - mb_strstr - Finds first occurrence of a string within another + * - mb_strwidth - Return width of string + * - mb_substr_count - Count the number of substring occurrences + * + * Not implemented: + * - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more) + * - mb_ereg_* - Regular expression with multibyte support + * - mb_parse_str - Parse GET/POST/COOKIE data and set global variable + * - mb_preferred_mime_name - Get MIME charset string + * - mb_regex_encoding - Returns current encoding for multibyte regex as string + * - mb_regex_set_options - Set/Get the default options for mbregex functions + * - mb_send_mail - Send encoded mail + * - mb_split - Split multibyte string using regular expression + * - mb_strcut - Get part of string + * - mb_strimwidth - Get truncated string with specified width + * + * @author Nicolas Grekas + * + * @internal + */ +final class Mbstring +{ + public const MB_CASE_FOLD = \PHP_INT_MAX; + + private const SIMPLE_CASE_FOLD = [ + ['µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"], + ['μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'], + ]; + + private static $encodingList = ['ASCII', 'UTF-8']; + private static $language = 'neutral'; + private static $internalEncoding = 'UTF-8'; + + public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null) + { + if (\is_array($fromEncoding) || (null !== $fromEncoding && false !== strpos($fromEncoding, ','))) { + $fromEncoding = self::mb_detect_encoding($s, $fromEncoding); + } else { + $fromEncoding = self::getEncoding($fromEncoding); + } + + $toEncoding = self::getEncoding($toEncoding); + + if ('BASE64' === $fromEncoding) { + $s = base64_decode($s); + $fromEncoding = $toEncoding; + } + + if ('BASE64' === $toEncoding) { + return base64_encode($s); + } + + if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) { + if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) { + $fromEncoding = 'Windows-1252'; + } + if ('UTF-8' !== $fromEncoding) { + $s = iconv($fromEncoding, 'UTF-8//IGNORE', $s); + } + + return preg_replace_callback('/[\x80-\xFF]+/', [__CLASS__, 'html_encoding_callback'], $s); + } + + if ('HTML-ENTITIES' === $fromEncoding) { + $s = html_entity_decode($s, \ENT_COMPAT, 'UTF-8'); + $fromEncoding = 'UTF-8'; + } + + return iconv($fromEncoding, $toEncoding.'//IGNORE', $s); + } + + public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars) + { + $ok = true; + array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) { + if (false === $v = self::mb_convert_encoding($v, $toEncoding, $fromEncoding)) { + $ok = false; + } + }); + + return $ok ? $fromEncoding : false; + } + + public static function mb_decode_mimeheader($s) + { + return iconv_mime_decode($s, 2, self::$internalEncoding); + } + + public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null) + { + trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', \E_USER_WARNING); + } + + public static function mb_decode_numericentity($s, $convmap, $encoding = null) + { + if (null !== $s && !\is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { + trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) { + return false; + } + + if (null !== $encoding && !\is_scalar($encoding)) { + trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return ''; // Instead of null (cf. mb_encode_numericentity). + } + + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + $cnt = floor(\count($convmap) / 4) * 4; + + for ($i = 0; $i < $cnt; $i += 4) { + // collector_decode_htmlnumericentity ignores $convmap[$i + 3] + $convmap[$i] += $convmap[$i + 2]; + $convmap[$i + 1] += $convmap[$i + 2]; + } + + $s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))(?!&);?/', function (array $m) use ($cnt, $convmap) { + $c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1]; + for ($i = 0; $i < $cnt; $i += 4) { + if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) { + return self::mb_chr($c - $convmap[$i + 2]); + } + } + + return $m[0]; + }, $s); + + if (null === $encoding) { + return $s; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $s); + } + + public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false) + { + if (null !== $s && !\is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { + trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) { + return false; + } + + if (null !== $encoding && !\is_scalar($encoding)) { + trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; // Instead of '' (cf. mb_decode_numericentity). + } + + if (null !== $is_hex && !\is_scalar($is_hex)) { + trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; + + $cnt = floor(\count($convmap) / 4) * 4; + $i = 0; + $len = \strlen($s); + $result = ''; + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + $c = self::mb_ord($uchr); + + for ($j = 0; $j < $cnt; $j += 4) { + if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) { + $cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3]; + $result .= $is_hex ? sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';'; + continue 2; + } + } + $result .= $uchr; + } + + if (null === $encoding) { + return $result; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $result); + } + + public static function mb_convert_case($s, $mode, $encoding = null) + { + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + if (\MB_CASE_TITLE == $mode) { + static $titleRegexp = null; + if (null === $titleRegexp) { + $titleRegexp = self::getData('titleCaseRegexp'); + } + $s = preg_replace_callback($titleRegexp, [__CLASS__, 'title_case'], $s); + } else { + if (\MB_CASE_UPPER == $mode) { + static $upper = null; + if (null === $upper) { + $upper = self::getData('upperCase'); + } + $map = $upper; + } else { + if (self::MB_CASE_FOLD === $mode) { + static $caseFolding = null; + if (null === $caseFolding) { + $caseFolding = self::getData('caseFolding'); + } + $s = strtr($s, $caseFolding); + } + + static $lower = null; + if (null === $lower) { + $lower = self::getData('lowerCase'); + } + $map = $lower; + } + + static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; + + $i = 0; + $len = \strlen($s); + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + + if (isset($map[$uchr])) { + $uchr = $map[$uchr]; + $nlen = \strlen($uchr); + + if ($nlen == $ulen) { + $nlen = $i; + do { + $s[--$nlen] = $uchr[--$ulen]; + } while ($ulen); + } else { + $s = substr_replace($s, $uchr, $i - $ulen, $ulen); + $len += $nlen - $ulen; + $i += $nlen - $ulen; + } + } + } + } + + if (null === $encoding) { + return $s; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $s); + } + + public static function mb_internal_encoding($encoding = null) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + $normalizedEncoding = self::getEncoding($encoding); + + if ('UTF-8' === $normalizedEncoding || false !== @iconv($normalizedEncoding, $normalizedEncoding, ' ')) { + self::$internalEncoding = $normalizedEncoding; + + return true; + } + + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError(sprintf('Argument #1 ($encoding) must be a valid encoding, "%s" given', $encoding)); + } + + public static function mb_language($lang = null) + { + if (null === $lang) { + return self::$language; + } + + switch ($normalizedLang = strtolower($lang)) { + case 'uni': + case 'neutral': + self::$language = $normalizedLang; + + return true; + } + + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError(sprintf('Argument #1 ($language) must be a valid language, "%s" given', $lang)); + } + + public static function mb_list_encodings() + { + return ['UTF-8']; + } + + public static function mb_encoding_aliases($encoding) + { + switch (strtoupper($encoding)) { + case 'UTF8': + case 'UTF-8': + return ['utf8']; + } + + return false; + } + + public static function mb_check_encoding($var = null, $encoding = null) + { + if (PHP_VERSION_ID < 70200 && \is_array($var)) { + trigger_error('mb_check_encoding() expects parameter 1 to be string, array given', \E_USER_WARNING); + + return null; + } + + if (null === $encoding) { + if (null === $var) { + return false; + } + $encoding = self::$internalEncoding; + } + + if (!\is_array($var)) { + return self::mb_detect_encoding($var, [$encoding]) || false !== @iconv($encoding, $encoding, $var); + } + + foreach ($var as $key => $value) { + if (!self::mb_check_encoding($key, $encoding)) { + return false; + } + if (!self::mb_check_encoding($value, $encoding)) { + return false; + } + } + + return true; + + } + + public static function mb_detect_encoding($str, $encodingList = null, $strict = false) + { + if (null === $encodingList) { + $encodingList = self::$encodingList; + } else { + if (!\is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + } + + foreach ($encodingList as $enc) { + switch ($enc) { + case 'ASCII': + if (!preg_match('/[\x80-\xFF]/', $str)) { + return $enc; + } + break; + + case 'UTF8': + case 'UTF-8': + if (preg_match('//u', $str)) { + return 'UTF-8'; + } + break; + + default: + if (0 === strncmp($enc, 'ISO-8859-', 9)) { + return $enc; + } + } + } + + return false; + } + + public static function mb_detect_order($encodingList = null) + { + if (null === $encodingList) { + return self::$encodingList; + } + + if (!\is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + + foreach ($encodingList as $enc) { + switch ($enc) { + default: + if (strncmp($enc, 'ISO-8859-', 9)) { + return false; + } + // no break + case 'ASCII': + case 'UTF8': + case 'UTF-8': + } + } + + self::$encodingList = $encodingList; + + return true; + } + + public static function mb_strlen($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return \strlen($s); + } + + return @iconv_strlen($s, $encoding); + } + + public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strpos($haystack, $needle, $offset); + } + + $needle = (string) $needle; + if ('' === $needle) { + if (80000 > \PHP_VERSION_ID) { + trigger_error(__METHOD__.': Empty delimiter', \E_USER_WARNING); + + return false; + } + + return 0; + } + + return iconv_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strrpos($haystack, $needle, $offset); + } + + if ($offset != (int) $offset) { + $offset = 0; + } elseif ($offset = (int) $offset) { + if ($offset < 0) { + if (0 > $offset += self::mb_strlen($needle)) { + $haystack = self::mb_substr($haystack, 0, $offset, $encoding); + } + $offset = 0; + } else { + $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding); + } + } + + $pos = '' !== $needle || 80000 > \PHP_VERSION_ID + ? iconv_strrpos($haystack, $needle, $encoding) + : self::mb_strlen($haystack, $encoding); + + return false !== $pos ? $offset + $pos : false; + } + + public static function mb_str_split($string, $split_length = 1, $encoding = null) + { + if (null !== $string && !\is_scalar($string) && !(\is_object($string) && method_exists($string, '__toString'))) { + trigger_error('mb_str_split() expects parameter 1 to be string, '.\gettype($string).' given', \E_USER_WARNING); + + return null; + } + + if (1 > $split_length = (int) $split_length) { + if (80000 > \PHP_VERSION_ID) { + trigger_error('The length of each segment must be greater than zero', \E_USER_WARNING); + + return false; + } + + throw new \ValueError('Argument #2 ($length) must be greater than 0'); + } + + if (null === $encoding) { + $encoding = mb_internal_encoding(); + } + + if ('UTF-8' === $encoding = self::getEncoding($encoding)) { + $rx = '/('; + while (65535 < $split_length) { + $rx .= '.{65535}'; + $split_length -= 65535; + } + $rx .= '.{'.$split_length.'})/us'; + + return preg_split($rx, $string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY); + } + + $result = []; + $length = mb_strlen($string, $encoding); + + for ($i = 0; $i < $length; $i += $split_length) { + $result[] = mb_substr($string, $i, $split_length, $encoding); + } + + return $result; + } + + public static function mb_strtolower($s, $encoding = null) + { + return self::mb_convert_case($s, \MB_CASE_LOWER, $encoding); + } + + public static function mb_strtoupper($s, $encoding = null) + { + return self::mb_convert_case($s, \MB_CASE_UPPER, $encoding); + } + + public static function mb_substitute_character($c = null) + { + if (null === $c) { + return 'none'; + } + if (0 === strcasecmp($c, 'none')) { + return true; + } + if (80000 > \PHP_VERSION_ID) { + return false; + } + if (\is_int($c) || 'long' === $c || 'entity' === $c) { + return false; + } + + throw new \ValueError('Argument #1 ($substitute_character) must be "none", "long", "entity" or a valid codepoint'); + } + + public static function mb_substr($s, $start, $length = null, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return (string) substr($s, $start, null === $length ? 2147483647 : $length); + } + + if ($start < 0) { + $start = iconv_strlen($s, $encoding) + $start; + if ($start < 0) { + $start = 0; + } + } + + if (null === $length) { + $length = 2147483647; + } elseif ($length < 0) { + $length = iconv_strlen($s, $encoding) + $length - $start; + if ($length < 0) { + return ''; + } + } + + return (string) iconv_substr($s, $start, $length, $encoding); + } + + public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) + { + [$haystack, $needle] = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], [ + self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding), + self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding), + ]); + + return self::mb_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_stristr($haystack, $needle, $part = false, $encoding = null) + { + $pos = self::mb_stripos($haystack, $needle, 0, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + $pos = strrpos($haystack, $needle); + } else { + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = iconv_strrpos($haystack, $needle, $encoding); + } + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null) + { + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = self::mb_strripos($haystack, $needle, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) + { + $haystack = self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding); + $needle = self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding); + + $haystack = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $haystack); + $needle = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $needle); + + return self::mb_strrpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strstr($haystack, $needle, $part = false, $encoding = null) + { + $pos = strpos($haystack, $needle); + if (false === $pos) { + return false; + } + if ($part) { + return substr($haystack, 0, $pos); + } + + return substr($haystack, $pos); + } + + public static function mb_get_info($type = 'all') + { + $info = [ + 'internal_encoding' => self::$internalEncoding, + 'http_output' => 'pass', + 'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)', + 'func_overload' => 0, + 'func_overload_list' => 'no overload', + 'mail_charset' => 'UTF-8', + 'mail_header_encoding' => 'BASE64', + 'mail_body_encoding' => 'BASE64', + 'illegal_chars' => 0, + 'encoding_translation' => 'Off', + 'language' => self::$language, + 'detect_order' => self::$encodingList, + 'substitute_character' => 'none', + 'strict_detection' => 'Off', + ]; + + if ('all' === $type) { + return $info; + } + if (isset($info[$type])) { + return $info[$type]; + } + + return false; + } + + public static function mb_http_input($type = '') + { + return false; + } + + public static function mb_http_output($encoding = null) + { + return null !== $encoding ? 'pass' === $encoding : 'pass'; + } + + public static function mb_strwidth($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + + if ('UTF-8' !== $encoding) { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + $s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide); + + return ($wide << 1) + iconv_strlen($s, 'UTF-8'); + } + + public static function mb_substr_count($haystack, $needle, $encoding = null) + { + return substr_count($haystack, $needle); + } + + public static function mb_output_handler($contents, $status) + { + return $contents; + } + + public static function mb_chr($code, $encoding = null) + { + if (0x80 > $code %= 0x200000) { + $s = \chr($code); + } elseif (0x800 > $code) { + $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F); + } elseif (0x10000 > $code) { + $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } else { + $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } + + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, $encoding, 'UTF-8'); + } + + return $s; + } + + public static function mb_ord($s, $encoding = null) + { + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, 'UTF-8', $encoding); + } + + if (1 === \strlen($s)) { + return \ord($s); + } + + $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0; + if (0xF0 <= $code) { + return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80; + } + if (0xE0 <= $code) { + return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80; + } + if (0xC0 <= $code) { + return (($code - 0xC0) << 6) + $s[2] - 0x80; + } + + return $code; + } + + public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, string $encoding = null): string + { + if (!\in_array($pad_type, [\STR_PAD_RIGHT, \STR_PAD_LEFT, \STR_PAD_BOTH], true)) { + throw new \ValueError('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH'); + } + + if (null === $encoding) { + $encoding = self::mb_internal_encoding(); + } + + try { + $validEncoding = @self::mb_check_encoding('', $encoding); + } catch (\ValueError $e) { + throw new \ValueError(sprintf('mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given', $encoding)); + } + + // BC for PHP 7.3 and lower + if (!$validEncoding) { + throw new \ValueError(sprintf('mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given', $encoding)); + } + + if (self::mb_strlen($pad_string, $encoding) <= 0) { + throw new \ValueError('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string'); + } + + $paddingRequired = $length - self::mb_strlen($string, $encoding); + + if ($paddingRequired < 1) { + return $string; + } + + switch ($pad_type) { + case \STR_PAD_LEFT: + return self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding).$string; + case \STR_PAD_RIGHT: + return $string.self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding); + default: + $leftPaddingLength = floor($paddingRequired / 2); + $rightPaddingLength = $paddingRequired - $leftPaddingLength; + + return self::mb_substr(str_repeat($pad_string, $leftPaddingLength), 0, $leftPaddingLength, $encoding).$string.self::mb_substr(str_repeat($pad_string, $rightPaddingLength), 0, $rightPaddingLength, $encoding); + } + } + + private static function getSubpart($pos, $part, $haystack, $encoding) + { + if (false === $pos) { + return false; + } + if ($part) { + return self::mb_substr($haystack, 0, $pos, $encoding); + } + + return self::mb_substr($haystack, $pos, null, $encoding); + } + + private static function html_encoding_callback(array $m) + { + $i = 1; + $entities = ''; + $m = unpack('C*', htmlentities($m[0], \ENT_COMPAT, 'UTF-8')); + + while (isset($m[$i])) { + if (0x80 > $m[$i]) { + $entities .= \chr($m[$i++]); + continue; + } + if (0xF0 <= $m[$i]) { + $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } elseif (0xE0 <= $m[$i]) { + $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } else { + $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80; + } + + $entities .= '&#'.$c.';'; + } + + return $entities; + } + + private static function title_case(array $s) + { + return self::mb_convert_case($s[1], \MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], \MB_CASE_LOWER, 'UTF-8'); + } + + private static function getData($file) + { + if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) { + return require $file; + } + + return false; + } + + private static function getEncoding($encoding) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + if ('UTF-8' === $encoding) { + return 'UTF-8'; + } + + $encoding = strtoupper($encoding); + + if ('8BIT' === $encoding || 'BINARY' === $encoding) { + return 'CP850'; + } + + if ('UTF8' === $encoding) { + return 'UTF-8'; + } + + return $encoding; + } +} diff --git a/vendor/symfony/polyfill-mbstring/README.md b/vendor/symfony/polyfill-mbstring/README.md new file mode 100644 index 00000000..478b40da --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/README.md @@ -0,0 +1,13 @@ +Symfony Polyfill / Mbstring +=========================== + +This component provides a partial, native PHP implementation for the +[Mbstring](https://php.net/mbstring) extension. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/caseFolding.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/caseFolding.php new file mode 100644 index 00000000..512bba0b --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/caseFolding.php @@ -0,0 +1,119 @@ + 'i̇', + 'µ' => 'μ', + 'ſ' => 's', + 'ͅ' => 'ι', + 'ς' => 'σ', + 'ϐ' => 'β', + 'ϑ' => 'θ', + 'ϕ' => 'φ', + 'ϖ' => 'π', + 'ϰ' => 'κ', + 'ϱ' => 'ρ', + 'ϵ' => 'ε', + 'ẛ' => 'ṡ', + 'ι' => 'ι', + 'ß' => 'ss', + 'ʼn' => 'ʼn', + 'ǰ' => 'ǰ', + 'ΐ' => 'ΐ', + 'ΰ' => 'ΰ', + 'և' => 'եւ', + 'ẖ' => 'ẖ', + 'ẗ' => 'ẗ', + 'ẘ' => 'ẘ', + 'ẙ' => 'ẙ', + 'ẚ' => 'aʾ', + 'ẞ' => 'ss', + 'ὐ' => 'ὐ', + 'ὒ' => 'ὒ', + 'ὔ' => 'ὔ', + 'ὖ' => 'ὖ', + 'ᾀ' => 'ἀι', + 'ᾁ' => 'ἁι', + 'ᾂ' => 'ἂι', + 'ᾃ' => 'ἃι', + 'ᾄ' => 'ἄι', + 'ᾅ' => 'ἅι', + 'ᾆ' => 'ἆι', + 'ᾇ' => 'ἇι', + 'ᾈ' => 'ἀι', + 'ᾉ' => 'ἁι', + 'ᾊ' => 'ἂι', + 'ᾋ' => 'ἃι', + 'ᾌ' => 'ἄι', + 'ᾍ' => 'ἅι', + 'ᾎ' => 'ἆι', + 'ᾏ' => 'ἇι', + 'ᾐ' => 'ἠι', + 'ᾑ' => 'ἡι', + 'ᾒ' => 'ἢι', + 'ᾓ' => 'ἣι', + 'ᾔ' => 'ἤι', + 'ᾕ' => 'ἥι', + 'ᾖ' => 'ἦι', + 'ᾗ' => 'ἧι', + 'ᾘ' => 'ἠι', + 'ᾙ' => 'ἡι', + 'ᾚ' => 'ἢι', + 'ᾛ' => 'ἣι', + 'ᾜ' => 'ἤι', + 'ᾝ' => 'ἥι', + 'ᾞ' => 'ἦι', + 'ᾟ' => 'ἧι', + 'ᾠ' => 'ὠι', + 'ᾡ' => 'ὡι', + 'ᾢ' => 'ὢι', + 'ᾣ' => 'ὣι', + 'ᾤ' => 'ὤι', + 'ᾥ' => 'ὥι', + 'ᾦ' => 'ὦι', + 'ᾧ' => 'ὧι', + 'ᾨ' => 'ὠι', + 'ᾩ' => 'ὡι', + 'ᾪ' => 'ὢι', + 'ᾫ' => 'ὣι', + 'ᾬ' => 'ὤι', + 'ᾭ' => 'ὥι', + 'ᾮ' => 'ὦι', + 'ᾯ' => 'ὧι', + 'ᾲ' => 'ὰι', + 'ᾳ' => 'αι', + 'ᾴ' => 'άι', + 'ᾶ' => 'ᾶ', + 'ᾷ' => 'ᾶι', + 'ᾼ' => 'αι', + 'ῂ' => 'ὴι', + 'ῃ' => 'ηι', + 'ῄ' => 'ήι', + 'ῆ' => 'ῆ', + 'ῇ' => 'ῆι', + 'ῌ' => 'ηι', + 'ῒ' => 'ῒ', + 'ῖ' => 'ῖ', + 'ῗ' => 'ῗ', + 'ῢ' => 'ῢ', + 'ῤ' => 'ῤ', + 'ῦ' => 'ῦ', + 'ῧ' => 'ῧ', + 'ῲ' => 'ὼι', + 'ῳ' => 'ωι', + 'ῴ' => 'ώι', + 'ῶ' => 'ῶ', + 'ῷ' => 'ῶι', + 'ῼ' => 'ωι', + 'ff' => 'ff', + 'fi' => 'fi', + 'fl' => 'fl', + 'ffi' => 'ffi', + 'ffl' => 'ffl', + 'ſt' => 'st', + 'st' => 'st', + 'ﬓ' => 'մն', + 'ﬔ' => 'մե', + 'ﬕ' => 'մի', + 'ﬖ' => 'վն', + 'ﬗ' => 'մխ', +]; diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php new file mode 100644 index 00000000..fac60b08 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php @@ -0,0 +1,1397 @@ + 'a', + 'B' => 'b', + 'C' => 'c', + 'D' => 'd', + 'E' => 'e', + 'F' => 'f', + 'G' => 'g', + 'H' => 'h', + 'I' => 'i', + 'J' => 'j', + 'K' => 'k', + 'L' => 'l', + 'M' => 'm', + 'N' => 'n', + 'O' => 'o', + 'P' => 'p', + 'Q' => 'q', + 'R' => 'r', + 'S' => 's', + 'T' => 't', + 'U' => 'u', + 'V' => 'v', + 'W' => 'w', + 'X' => 'x', + 'Y' => 'y', + 'Z' => 'z', + 'À' => 'à', + 'Á' => 'á', + 'Â' => 'â', + 'Ã' => 'ã', + 'Ä' => 'ä', + 'Å' => 'å', + 'Æ' => 'æ', + 'Ç' => 'ç', + 'È' => 'è', + 'É' => 'é', + 'Ê' => 'ê', + 'Ë' => 'ë', + 'Ì' => 'ì', + 'Í' => 'í', + 'Î' => 'î', + 'Ï' => 'ï', + 'Ð' => 'ð', + 'Ñ' => 'ñ', + 'Ò' => 'ò', + 'Ó' => 'ó', + 'Ô' => 'ô', + 'Õ' => 'õ', + 'Ö' => 'ö', + 'Ø' => 'ø', + 'Ù' => 'ù', + 'Ú' => 'ú', + 'Û' => 'û', + 'Ü' => 'ü', + 'Ý' => 'ý', + 'Þ' => 'þ', + 'Ā' => 'ā', + 'Ă' => 'ă', + 'Ą' => 'ą', + 'Ć' => 'ć', + 'Ĉ' => 'ĉ', + 'Ċ' => 'ċ', + 'Č' => 'č', + 'Ď' => 'ď', + 'Đ' => 'đ', + 'Ē' => 'ē', + 'Ĕ' => 'ĕ', + 'Ė' => 'ė', + 'Ę' => 'ę', + 'Ě' => 'ě', + 'Ĝ' => 'ĝ', + 'Ğ' => 'ğ', + 'Ġ' => 'ġ', + 'Ģ' => 'ģ', + 'Ĥ' => 'ĥ', + 'Ħ' => 'ħ', + 'Ĩ' => 'ĩ', + 'Ī' => 'ī', + 'Ĭ' => 'ĭ', + 'Į' => 'į', + 'İ' => 'i̇', + 'IJ' => 'ij', + 'Ĵ' => 'ĵ', + 'Ķ' => 'ķ', + 'Ĺ' => 'ĺ', + 'Ļ' => 'ļ', + 'Ľ' => 'ľ', + 'Ŀ' => 'ŀ', + 'Ł' => 'ł', + 'Ń' => 'ń', + 'Ņ' => 'ņ', + 'Ň' => 'ň', + 'Ŋ' => 'ŋ', + 'Ō' => 'ō', + 'Ŏ' => 'ŏ', + 'Ő' => 'ő', + 'Œ' => 'œ', + 'Ŕ' => 'ŕ', + 'Ŗ' => 'ŗ', + 'Ř' => 'ř', + 'Ś' => 'ś', + 'Ŝ' => 'ŝ', + 'Ş' => 'ş', + 'Š' => 'š', + 'Ţ' => 'ţ', + 'Ť' => 'ť', + 'Ŧ' => 'ŧ', + 'Ũ' => 'ũ', + 'Ū' => 'ū', + 'Ŭ' => 'ŭ', + 'Ů' => 'ů', + 'Ű' => 'ű', + 'Ų' => 'ų', + 'Ŵ' => 'ŵ', + 'Ŷ' => 'ŷ', + 'Ÿ' => 'ÿ', + 'Ź' => 'ź', + 'Ż' => 'ż', + 'Ž' => 'ž', + 'Ɓ' => 'ɓ', + 'Ƃ' => 'ƃ', + 'Ƅ' => 'ƅ', + 'Ɔ' => 'ɔ', + 'Ƈ' => 'ƈ', + 'Ɖ' => 'ɖ', + 'Ɗ' => 'ɗ', + 'Ƌ' => 'ƌ', + 'Ǝ' => 'ǝ', + 'Ə' => 'ə', + 'Ɛ' => 'ɛ', + 'Ƒ' => 'ƒ', + 'Ɠ' => 'ɠ', + 'Ɣ' => 'ɣ', + 'Ɩ' => 'ɩ', + 'Ɨ' => 'ɨ', + 'Ƙ' => 'ƙ', + 'Ɯ' => 'ɯ', + 'Ɲ' => 'ɲ', + 'Ɵ' => 'ɵ', + 'Ơ' => 'ơ', + 'Ƣ' => 'ƣ', + 'Ƥ' => 'ƥ', + 'Ʀ' => 'ʀ', + 'Ƨ' => 'ƨ', + 'Ʃ' => 'ʃ', + 'Ƭ' => 'ƭ', + 'Ʈ' => 'ʈ', + 'Ư' => 'ư', + 'Ʊ' => 'ʊ', + 'Ʋ' => 'ʋ', + 'Ƴ' => 'ƴ', + 'Ƶ' => 'ƶ', + 'Ʒ' => 'ʒ', + 'Ƹ' => 'ƹ', + 'Ƽ' => 'ƽ', + 'DŽ' => 'dž', + 'Dž' => 'dž', + 'LJ' => 'lj', + 'Lj' => 'lj', + 'NJ' => 'nj', + 'Nj' => 'nj', + 'Ǎ' => 'ǎ', + 'Ǐ' => 'ǐ', + 'Ǒ' => 'ǒ', + 'Ǔ' => 'ǔ', + 'Ǖ' => 'ǖ', + 'Ǘ' => 'ǘ', + 'Ǚ' => 'ǚ', + 'Ǜ' => 'ǜ', + 'Ǟ' => 'ǟ', + 'Ǡ' => 'ǡ', + 'Ǣ' => 'ǣ', + 'Ǥ' => 'ǥ', + 'Ǧ' => 'ǧ', + 'Ǩ' => 'ǩ', + 'Ǫ' => 'ǫ', + 'Ǭ' => 'ǭ', + 'Ǯ' => 'ǯ', + 'DZ' => 'dz', + 'Dz' => 'dz', + 'Ǵ' => 'ǵ', + 'Ƕ' => 'ƕ', + 'Ƿ' => 'ƿ', + 'Ǹ' => 'ǹ', + 'Ǻ' => 'ǻ', + 'Ǽ' => 'ǽ', + 'Ǿ' => 'ǿ', + 'Ȁ' => 'ȁ', + 'Ȃ' => 'ȃ', + 'Ȅ' => 'ȅ', + 'Ȇ' => 'ȇ', + 'Ȉ' => 'ȉ', + 'Ȋ' => 'ȋ', + 'Ȍ' => 'ȍ', + 'Ȏ' => 'ȏ', + 'Ȑ' => 'ȑ', + 'Ȓ' => 'ȓ', + 'Ȕ' => 'ȕ', + 'Ȗ' => 'ȗ', + 'Ș' => 'ș', + 'Ț' => 'ț', + 'Ȝ' => 'ȝ', + 'Ȟ' => 'ȟ', + 'Ƞ' => 'ƞ', + 'Ȣ' => 'ȣ', + 'Ȥ' => 'ȥ', + 'Ȧ' => 'ȧ', + 'Ȩ' => 'ȩ', + 'Ȫ' => 'ȫ', + 'Ȭ' => 'ȭ', + 'Ȯ' => 'ȯ', + 'Ȱ' => 'ȱ', + 'Ȳ' => 'ȳ', + 'Ⱥ' => 'ⱥ', + 'Ȼ' => 'ȼ', + 'Ƚ' => 'ƚ', + 'Ⱦ' => 'ⱦ', + 'Ɂ' => 'ɂ', + 'Ƀ' => 'ƀ', + 'Ʉ' => 'ʉ', + 'Ʌ' => 'ʌ', + 'Ɇ' => 'ɇ', + 'Ɉ' => 'ɉ', + 'Ɋ' => 'ɋ', + 'Ɍ' => 'ɍ', + 'Ɏ' => 'ɏ', + 'Ͱ' => 'ͱ', + 'Ͳ' => 'ͳ', + 'Ͷ' => 'ͷ', + 'Ϳ' => 'ϳ', + 'Ά' => 'ά', + 'Έ' => 'έ', + 'Ή' => 'ή', + 'Ί' => 'ί', + 'Ό' => 'ό', + 'Ύ' => 'ύ', + 'Ώ' => 'ώ', + 'Α' => 'α', + 'Β' => 'β', + 'Γ' => 'γ', + 'Δ' => 'δ', + 'Ε' => 'ε', + 'Ζ' => 'ζ', + 'Η' => 'η', + 'Θ' => 'θ', + 'Ι' => 'ι', + 'Κ' => 'κ', + 'Λ' => 'λ', + 'Μ' => 'μ', + 'Ν' => 'ν', + 'Ξ' => 'ξ', + 'Ο' => 'ο', + 'Π' => 'π', + 'Ρ' => 'ρ', + 'Σ' => 'σ', + 'Τ' => 'τ', + 'Υ' => 'υ', + 'Φ' => 'φ', + 'Χ' => 'χ', + 'Ψ' => 'ψ', + 'Ω' => 'ω', + 'Ϊ' => 'ϊ', + 'Ϋ' => 'ϋ', + 'Ϗ' => 'ϗ', + 'Ϙ' => 'ϙ', + 'Ϛ' => 'ϛ', + 'Ϝ' => 'ϝ', + 'Ϟ' => 'ϟ', + 'Ϡ' => 'ϡ', + 'Ϣ' => 'ϣ', + 'Ϥ' => 'ϥ', + 'Ϧ' => 'ϧ', + 'Ϩ' => 'ϩ', + 'Ϫ' => 'ϫ', + 'Ϭ' => 'ϭ', + 'Ϯ' => 'ϯ', + 'ϴ' => 'θ', + 'Ϸ' => 'ϸ', + 'Ϲ' => 'ϲ', + 'Ϻ' => 'ϻ', + 'Ͻ' => 'ͻ', + 'Ͼ' => 'ͼ', + 'Ͽ' => 'ͽ', + 'Ѐ' => 'ѐ', + 'Ё' => 'ё', + 'Ђ' => 'ђ', + 'Ѓ' => 'ѓ', + 'Є' => 'є', + 'Ѕ' => 'ѕ', + 'І' => 'і', + 'Ї' => 'ї', + 'Ј' => 'ј', + 'Љ' => 'љ', + 'Њ' => 'њ', + 'Ћ' => 'ћ', + 'Ќ' => 'ќ', + 'Ѝ' => 'ѝ', + 'Ў' => 'ў', + 'Џ' => 'џ', + 'А' => 'а', + 'Б' => 'б', + 'В' => 'в', + 'Г' => 'г', + 'Д' => 'д', + 'Е' => 'е', + 'Ж' => 'ж', + 'З' => 'з', + 'И' => 'и', + 'Й' => 'й', + 'К' => 'к', + 'Л' => 'л', + 'М' => 'м', + 'Н' => 'н', + 'О' => 'о', + 'П' => 'п', + 'Р' => 'р', + 'С' => 'с', + 'Т' => 'т', + 'У' => 'у', + 'Ф' => 'ф', + 'Х' => 'х', + 'Ц' => 'ц', + 'Ч' => 'ч', + 'Ш' => 'ш', + 'Щ' => 'щ', + 'Ъ' => 'ъ', + 'Ы' => 'ы', + 'Ь' => 'ь', + 'Э' => 'э', + 'Ю' => 'ю', + 'Я' => 'я', + 'Ѡ' => 'ѡ', + 'Ѣ' => 'ѣ', + 'Ѥ' => 'ѥ', + 'Ѧ' => 'ѧ', + 'Ѩ' => 'ѩ', + 'Ѫ' => 'ѫ', + 'Ѭ' => 'ѭ', + 'Ѯ' => 'ѯ', + 'Ѱ' => 'ѱ', + 'Ѳ' => 'ѳ', + 'Ѵ' => 'ѵ', + 'Ѷ' => 'ѷ', + 'Ѹ' => 'ѹ', + 'Ѻ' => 'ѻ', + 'Ѽ' => 'ѽ', + 'Ѿ' => 'ѿ', + 'Ҁ' => 'ҁ', + 'Ҋ' => 'ҋ', + 'Ҍ' => 'ҍ', + 'Ҏ' => 'ҏ', + 'Ґ' => 'ґ', + 'Ғ' => 'ғ', + 'Ҕ' => 'ҕ', + 'Җ' => 'җ', + 'Ҙ' => 'ҙ', + 'Қ' => 'қ', + 'Ҝ' => 'ҝ', + 'Ҟ' => 'ҟ', + 'Ҡ' => 'ҡ', + 'Ң' => 'ң', + 'Ҥ' => 'ҥ', + 'Ҧ' => 'ҧ', + 'Ҩ' => 'ҩ', + 'Ҫ' => 'ҫ', + 'Ҭ' => 'ҭ', + 'Ү' => 'ү', + 'Ұ' => 'ұ', + 'Ҳ' => 'ҳ', + 'Ҵ' => 'ҵ', + 'Ҷ' => 'ҷ', + 'Ҹ' => 'ҹ', + 'Һ' => 'һ', + 'Ҽ' => 'ҽ', + 'Ҿ' => 'ҿ', + 'Ӏ' => 'ӏ', + 'Ӂ' => 'ӂ', + 'Ӄ' => 'ӄ', + 'Ӆ' => 'ӆ', + 'Ӈ' => 'ӈ', + 'Ӊ' => 'ӊ', + 'Ӌ' => 'ӌ', + 'Ӎ' => 'ӎ', + 'Ӑ' => 'ӑ', + 'Ӓ' => 'ӓ', + 'Ӕ' => 'ӕ', + 'Ӗ' => 'ӗ', + 'Ә' => 'ә', + 'Ӛ' => 'ӛ', + 'Ӝ' => 'ӝ', + 'Ӟ' => 'ӟ', + 'Ӡ' => 'ӡ', + 'Ӣ' => 'ӣ', + 'Ӥ' => 'ӥ', + 'Ӧ' => 'ӧ', + 'Ө' => 'ө', + 'Ӫ' => 'ӫ', + 'Ӭ' => 'ӭ', + 'Ӯ' => 'ӯ', + 'Ӱ' => 'ӱ', + 'Ӳ' => 'ӳ', + 'Ӵ' => 'ӵ', + 'Ӷ' => 'ӷ', + 'Ӹ' => 'ӹ', + 'Ӻ' => 'ӻ', + 'Ӽ' => 'ӽ', + 'Ӿ' => 'ӿ', + 'Ԁ' => 'ԁ', + 'Ԃ' => 'ԃ', + 'Ԅ' => 'ԅ', + 'Ԇ' => 'ԇ', + 'Ԉ' => 'ԉ', + 'Ԋ' => 'ԋ', + 'Ԍ' => 'ԍ', + 'Ԏ' => 'ԏ', + 'Ԑ' => 'ԑ', + 'Ԓ' => 'ԓ', + 'Ԕ' => 'ԕ', + 'Ԗ' => 'ԗ', + 'Ԙ' => 'ԙ', + 'Ԛ' => 'ԛ', + 'Ԝ' => 'ԝ', + 'Ԟ' => 'ԟ', + 'Ԡ' => 'ԡ', + 'Ԣ' => 'ԣ', + 'Ԥ' => 'ԥ', + 'Ԧ' => 'ԧ', + 'Ԩ' => 'ԩ', + 'Ԫ' => 'ԫ', + 'Ԭ' => 'ԭ', + 'Ԯ' => 'ԯ', + 'Ա' => 'ա', + 'Բ' => 'բ', + 'Գ' => 'գ', + 'Դ' => 'դ', + 'Ե' => 'ե', + 'Զ' => 'զ', + 'Է' => 'է', + 'Ը' => 'ը', + 'Թ' => 'թ', + 'Ժ' => 'ժ', + 'Ի' => 'ի', + 'Լ' => 'լ', + 'Խ' => 'խ', + 'Ծ' => 'ծ', + 'Կ' => 'կ', + 'Հ' => 'հ', + 'Ձ' => 'ձ', + 'Ղ' => 'ղ', + 'Ճ' => 'ճ', + 'Մ' => 'մ', + 'Յ' => 'յ', + 'Ն' => 'ն', + 'Շ' => 'շ', + 'Ո' => 'ո', + 'Չ' => 'չ', + 'Պ' => 'պ', + 'Ջ' => 'ջ', + 'Ռ' => 'ռ', + 'Ս' => 'ս', + 'Վ' => 'վ', + 'Տ' => 'տ', + 'Ր' => 'ր', + 'Ց' => 'ց', + 'Ւ' => 'ւ', + 'Փ' => 'փ', + 'Ք' => 'ք', + 'Օ' => 'օ', + 'Ֆ' => 'ֆ', + 'Ⴀ' => 'ⴀ', + 'Ⴁ' => 'ⴁ', + 'Ⴂ' => 'ⴂ', + 'Ⴃ' => 'ⴃ', + 'Ⴄ' => 'ⴄ', + 'Ⴅ' => 'ⴅ', + 'Ⴆ' => 'ⴆ', + 'Ⴇ' => 'ⴇ', + 'Ⴈ' => 'ⴈ', + 'Ⴉ' => 'ⴉ', + 'Ⴊ' => 'ⴊ', + 'Ⴋ' => 'ⴋ', + 'Ⴌ' => 'ⴌ', + 'Ⴍ' => 'ⴍ', + 'Ⴎ' => 'ⴎ', + 'Ⴏ' => 'ⴏ', + 'Ⴐ' => 'ⴐ', + 'Ⴑ' => 'ⴑ', + 'Ⴒ' => 'ⴒ', + 'Ⴓ' => 'ⴓ', + 'Ⴔ' => 'ⴔ', + 'Ⴕ' => 'ⴕ', + 'Ⴖ' => 'ⴖ', + 'Ⴗ' => 'ⴗ', + 'Ⴘ' => 'ⴘ', + 'Ⴙ' => 'ⴙ', + 'Ⴚ' => 'ⴚ', + 'Ⴛ' => 'ⴛ', + 'Ⴜ' => 'ⴜ', + 'Ⴝ' => 'ⴝ', + 'Ⴞ' => 'ⴞ', + 'Ⴟ' => 'ⴟ', + 'Ⴠ' => 'ⴠ', + 'Ⴡ' => 'ⴡ', + 'Ⴢ' => 'ⴢ', + 'Ⴣ' => 'ⴣ', + 'Ⴤ' => 'ⴤ', + 'Ⴥ' => 'ⴥ', + 'Ⴧ' => 'ⴧ', + 'Ⴭ' => 'ⴭ', + 'Ꭰ' => 'ꭰ', + 'Ꭱ' => 'ꭱ', + 'Ꭲ' => 'ꭲ', + 'Ꭳ' => 'ꭳ', + 'Ꭴ' => 'ꭴ', + 'Ꭵ' => 'ꭵ', + 'Ꭶ' => 'ꭶ', + 'Ꭷ' => 'ꭷ', + 'Ꭸ' => 'ꭸ', + 'Ꭹ' => 'ꭹ', + 'Ꭺ' => 'ꭺ', + 'Ꭻ' => 'ꭻ', + 'Ꭼ' => 'ꭼ', + 'Ꭽ' => 'ꭽ', + 'Ꭾ' => 'ꭾ', + 'Ꭿ' => 'ꭿ', + 'Ꮀ' => 'ꮀ', + 'Ꮁ' => 'ꮁ', + 'Ꮂ' => 'ꮂ', + 'Ꮃ' => 'ꮃ', + 'Ꮄ' => 'ꮄ', + 'Ꮅ' => 'ꮅ', + 'Ꮆ' => 'ꮆ', + 'Ꮇ' => 'ꮇ', + 'Ꮈ' => 'ꮈ', + 'Ꮉ' => 'ꮉ', + 'Ꮊ' => 'ꮊ', + 'Ꮋ' => 'ꮋ', + 'Ꮌ' => 'ꮌ', + 'Ꮍ' => 'ꮍ', + 'Ꮎ' => 'ꮎ', + 'Ꮏ' => 'ꮏ', + 'Ꮐ' => 'ꮐ', + 'Ꮑ' => 'ꮑ', + 'Ꮒ' => 'ꮒ', + 'Ꮓ' => 'ꮓ', + 'Ꮔ' => 'ꮔ', + 'Ꮕ' => 'ꮕ', + 'Ꮖ' => 'ꮖ', + 'Ꮗ' => 'ꮗ', + 'Ꮘ' => 'ꮘ', + 'Ꮙ' => 'ꮙ', + 'Ꮚ' => 'ꮚ', + 'Ꮛ' => 'ꮛ', + 'Ꮜ' => 'ꮜ', + 'Ꮝ' => 'ꮝ', + 'Ꮞ' => 'ꮞ', + 'Ꮟ' => 'ꮟ', + 'Ꮠ' => 'ꮠ', + 'Ꮡ' => 'ꮡ', + 'Ꮢ' => 'ꮢ', + 'Ꮣ' => 'ꮣ', + 'Ꮤ' => 'ꮤ', + 'Ꮥ' => 'ꮥ', + 'Ꮦ' => 'ꮦ', + 'Ꮧ' => 'ꮧ', + 'Ꮨ' => 'ꮨ', + 'Ꮩ' => 'ꮩ', + 'Ꮪ' => 'ꮪ', + 'Ꮫ' => 'ꮫ', + 'Ꮬ' => 'ꮬ', + 'Ꮭ' => 'ꮭ', + 'Ꮮ' => 'ꮮ', + 'Ꮯ' => 'ꮯ', + 'Ꮰ' => 'ꮰ', + 'Ꮱ' => 'ꮱ', + 'Ꮲ' => 'ꮲ', + 'Ꮳ' => 'ꮳ', + 'Ꮴ' => 'ꮴ', + 'Ꮵ' => 'ꮵ', + 'Ꮶ' => 'ꮶ', + 'Ꮷ' => 'ꮷ', + 'Ꮸ' => 'ꮸ', + 'Ꮹ' => 'ꮹ', + 'Ꮺ' => 'ꮺ', + 'Ꮻ' => 'ꮻ', + 'Ꮼ' => 'ꮼ', + 'Ꮽ' => 'ꮽ', + 'Ꮾ' => 'ꮾ', + 'Ꮿ' => 'ꮿ', + 'Ᏸ' => 'ᏸ', + 'Ᏹ' => 'ᏹ', + 'Ᏺ' => 'ᏺ', + 'Ᏻ' => 'ᏻ', + 'Ᏼ' => 'ᏼ', + 'Ᏽ' => 'ᏽ', + 'Ა' => 'ა', + 'Ბ' => 'ბ', + 'Გ' => 'გ', + 'Დ' => 'დ', + 'Ე' => 'ე', + 'Ვ' => 'ვ', + 'Ზ' => 'ზ', + 'Თ' => 'თ', + 'Ი' => 'ი', + 'Კ' => 'კ', + 'Ლ' => 'ლ', + 'Მ' => 'მ', + 'Ნ' => 'ნ', + 'Ო' => 'ო', + 'Პ' => 'პ', + 'Ჟ' => 'ჟ', + 'Რ' => 'რ', + 'Ს' => 'ს', + 'Ტ' => 'ტ', + 'Უ' => 'უ', + 'Ფ' => 'ფ', + 'Ქ' => 'ქ', + 'Ღ' => 'ღ', + 'Ყ' => 'ყ', + 'Შ' => 'შ', + 'Ჩ' => 'ჩ', + 'Ც' => 'ც', + 'Ძ' => 'ძ', + 'Წ' => 'წ', + 'Ჭ' => 'ჭ', + 'Ხ' => 'ხ', + 'Ჯ' => 'ჯ', + 'Ჰ' => 'ჰ', + 'Ჱ' => 'ჱ', + 'Ჲ' => 'ჲ', + 'Ჳ' => 'ჳ', + 'Ჴ' => 'ჴ', + 'Ჵ' => 'ჵ', + 'Ჶ' => 'ჶ', + 'Ჷ' => 'ჷ', + 'Ჸ' => 'ჸ', + 'Ჹ' => 'ჹ', + 'Ჺ' => 'ჺ', + 'Ჽ' => 'ჽ', + 'Ჾ' => 'ჾ', + 'Ჿ' => 'ჿ', + 'Ḁ' => 'ḁ', + 'Ḃ' => 'ḃ', + 'Ḅ' => 'ḅ', + 'Ḇ' => 'ḇ', + 'Ḉ' => 'ḉ', + 'Ḋ' => 'ḋ', + 'Ḍ' => 'ḍ', + 'Ḏ' => 'ḏ', + 'Ḑ' => 'ḑ', + 'Ḓ' => 'ḓ', + 'Ḕ' => 'ḕ', + 'Ḗ' => 'ḗ', + 'Ḙ' => 'ḙ', + 'Ḛ' => 'ḛ', + 'Ḝ' => 'ḝ', + 'Ḟ' => 'ḟ', + 'Ḡ' => 'ḡ', + 'Ḣ' => 'ḣ', + 'Ḥ' => 'ḥ', + 'Ḧ' => 'ḧ', + 'Ḩ' => 'ḩ', + 'Ḫ' => 'ḫ', + 'Ḭ' => 'ḭ', + 'Ḯ' => 'ḯ', + 'Ḱ' => 'ḱ', + 'Ḳ' => 'ḳ', + 'Ḵ' => 'ḵ', + 'Ḷ' => 'ḷ', + 'Ḹ' => 'ḹ', + 'Ḻ' => 'ḻ', + 'Ḽ' => 'ḽ', + 'Ḿ' => 'ḿ', + 'Ṁ' => 'ṁ', + 'Ṃ' => 'ṃ', + 'Ṅ' => 'ṅ', + 'Ṇ' => 'ṇ', + 'Ṉ' => 'ṉ', + 'Ṋ' => 'ṋ', + 'Ṍ' => 'ṍ', + 'Ṏ' => 'ṏ', + 'Ṑ' => 'ṑ', + 'Ṓ' => 'ṓ', + 'Ṕ' => 'ṕ', + 'Ṗ' => 'ṗ', + 'Ṙ' => 'ṙ', + 'Ṛ' => 'ṛ', + 'Ṝ' => 'ṝ', + 'Ṟ' => 'ṟ', + 'Ṡ' => 'ṡ', + 'Ṣ' => 'ṣ', + 'Ṥ' => 'ṥ', + 'Ṧ' => 'ṧ', + 'Ṩ' => 'ṩ', + 'Ṫ' => 'ṫ', + 'Ṭ' => 'ṭ', + 'Ṯ' => 'ṯ', + 'Ṱ' => 'ṱ', + 'Ṳ' => 'ṳ', + 'Ṵ' => 'ṵ', + 'Ṷ' => 'ṷ', + 'Ṹ' => 'ṹ', + 'Ṻ' => 'ṻ', + 'Ṽ' => 'ṽ', + 'Ṿ' => 'ṿ', + 'Ẁ' => 'ẁ', + 'Ẃ' => 'ẃ', + 'Ẅ' => 'ẅ', + 'Ẇ' => 'ẇ', + 'Ẉ' => 'ẉ', + 'Ẋ' => 'ẋ', + 'Ẍ' => 'ẍ', + 'Ẏ' => 'ẏ', + 'Ẑ' => 'ẑ', + 'Ẓ' => 'ẓ', + 'Ẕ' => 'ẕ', + 'ẞ' => 'ß', + 'Ạ' => 'ạ', + 'Ả' => 'ả', + 'Ấ' => 'ấ', + 'Ầ' => 'ầ', + 'Ẩ' => 'ẩ', + 'Ẫ' => 'ẫ', + 'Ậ' => 'ậ', + 'Ắ' => 'ắ', + 'Ằ' => 'ằ', + 'Ẳ' => 'ẳ', + 'Ẵ' => 'ẵ', + 'Ặ' => 'ặ', + 'Ẹ' => 'ẹ', + 'Ẻ' => 'ẻ', + 'Ẽ' => 'ẽ', + 'Ế' => 'ế', + 'Ề' => 'ề', + 'Ể' => 'ể', + 'Ễ' => 'ễ', + 'Ệ' => 'ệ', + 'Ỉ' => 'ỉ', + 'Ị' => 'ị', + 'Ọ' => 'ọ', + 'Ỏ' => 'ỏ', + 'Ố' => 'ố', + 'Ồ' => 'ồ', + 'Ổ' => 'ổ', + 'Ỗ' => 'ỗ', + 'Ộ' => 'ộ', + 'Ớ' => 'ớ', + 'Ờ' => 'ờ', + 'Ở' => 'ở', + 'Ỡ' => 'ỡ', + 'Ợ' => 'ợ', + 'Ụ' => 'ụ', + 'Ủ' => 'ủ', + 'Ứ' => 'ứ', + 'Ừ' => 'ừ', + 'Ử' => 'ử', + 'Ữ' => 'ữ', + 'Ự' => 'ự', + 'Ỳ' => 'ỳ', + 'Ỵ' => 'ỵ', + 'Ỷ' => 'ỷ', + 'Ỹ' => 'ỹ', + 'Ỻ' => 'ỻ', + 'Ỽ' => 'ỽ', + 'Ỿ' => 'ỿ', + 'Ἀ' => 'ἀ', + 'Ἁ' => 'ἁ', + 'Ἂ' => 'ἂ', + 'Ἃ' => 'ἃ', + 'Ἄ' => 'ἄ', + 'Ἅ' => 'ἅ', + 'Ἆ' => 'ἆ', + 'Ἇ' => 'ἇ', + 'Ἐ' => 'ἐ', + 'Ἑ' => 'ἑ', + 'Ἒ' => 'ἒ', + 'Ἓ' => 'ἓ', + 'Ἔ' => 'ἔ', + 'Ἕ' => 'ἕ', + 'Ἠ' => 'ἠ', + 'Ἡ' => 'ἡ', + 'Ἢ' => 'ἢ', + 'Ἣ' => 'ἣ', + 'Ἤ' => 'ἤ', + 'Ἥ' => 'ἥ', + 'Ἦ' => 'ἦ', + 'Ἧ' => 'ἧ', + 'Ἰ' => 'ἰ', + 'Ἱ' => 'ἱ', + 'Ἲ' => 'ἲ', + 'Ἳ' => 'ἳ', + 'Ἴ' => 'ἴ', + 'Ἵ' => 'ἵ', + 'Ἶ' => 'ἶ', + 'Ἷ' => 'ἷ', + 'Ὀ' => 'ὀ', + 'Ὁ' => 'ὁ', + 'Ὂ' => 'ὂ', + 'Ὃ' => 'ὃ', + 'Ὄ' => 'ὄ', + 'Ὅ' => 'ὅ', + 'Ὑ' => 'ὑ', + 'Ὓ' => 'ὓ', + 'Ὕ' => 'ὕ', + 'Ὗ' => 'ὗ', + 'Ὠ' => 'ὠ', + 'Ὡ' => 'ὡ', + 'Ὢ' => 'ὢ', + 'Ὣ' => 'ὣ', + 'Ὤ' => 'ὤ', + 'Ὥ' => 'ὥ', + 'Ὦ' => 'ὦ', + 'Ὧ' => 'ὧ', + 'ᾈ' => 'ᾀ', + 'ᾉ' => 'ᾁ', + 'ᾊ' => 'ᾂ', + 'ᾋ' => 'ᾃ', + 'ᾌ' => 'ᾄ', + 'ᾍ' => 'ᾅ', + 'ᾎ' => 'ᾆ', + 'ᾏ' => 'ᾇ', + 'ᾘ' => 'ᾐ', + 'ᾙ' => 'ᾑ', + 'ᾚ' => 'ᾒ', + 'ᾛ' => 'ᾓ', + 'ᾜ' => 'ᾔ', + 'ᾝ' => 'ᾕ', + 'ᾞ' => 'ᾖ', + 'ᾟ' => 'ᾗ', + 'ᾨ' => 'ᾠ', + 'ᾩ' => 'ᾡ', + 'ᾪ' => 'ᾢ', + 'ᾫ' => 'ᾣ', + 'ᾬ' => 'ᾤ', + 'ᾭ' => 'ᾥ', + 'ᾮ' => 'ᾦ', + 'ᾯ' => 'ᾧ', + 'Ᾰ' => 'ᾰ', + 'Ᾱ' => 'ᾱ', + 'Ὰ' => 'ὰ', + 'Ά' => 'ά', + 'ᾼ' => 'ᾳ', + 'Ὲ' => 'ὲ', + 'Έ' => 'έ', + 'Ὴ' => 'ὴ', + 'Ή' => 'ή', + 'ῌ' => 'ῃ', + 'Ῐ' => 'ῐ', + 'Ῑ' => 'ῑ', + 'Ὶ' => 'ὶ', + 'Ί' => 'ί', + 'Ῠ' => 'ῠ', + 'Ῡ' => 'ῡ', + 'Ὺ' => 'ὺ', + 'Ύ' => 'ύ', + 'Ῥ' => 'ῥ', + 'Ὸ' => 'ὸ', + 'Ό' => 'ό', + 'Ὼ' => 'ὼ', + 'Ώ' => 'ώ', + 'ῼ' => 'ῳ', + 'Ω' => 'ω', + 'K' => 'k', + 'Å' => 'å', + 'Ⅎ' => 'ⅎ', + 'Ⅰ' => 'ⅰ', + 'Ⅱ' => 'ⅱ', + 'Ⅲ' => 'ⅲ', + 'Ⅳ' => 'ⅳ', + 'Ⅴ' => 'ⅴ', + 'Ⅵ' => 'ⅵ', + 'Ⅶ' => 'ⅶ', + 'Ⅷ' => 'ⅷ', + 'Ⅸ' => 'ⅸ', + 'Ⅹ' => 'ⅹ', + 'Ⅺ' => 'ⅺ', + 'Ⅻ' => 'ⅻ', + 'Ⅼ' => 'ⅼ', + 'Ⅽ' => 'ⅽ', + 'Ⅾ' => 'ⅾ', + 'Ⅿ' => 'ⅿ', + 'Ↄ' => 'ↄ', + 'Ⓐ' => 'ⓐ', + 'Ⓑ' => 'ⓑ', + 'Ⓒ' => 'ⓒ', + 'Ⓓ' => 'ⓓ', + 'Ⓔ' => 'ⓔ', + 'Ⓕ' => 'ⓕ', + 'Ⓖ' => 'ⓖ', + 'Ⓗ' => 'ⓗ', + 'Ⓘ' => 'ⓘ', + 'Ⓙ' => 'ⓙ', + 'Ⓚ' => 'ⓚ', + 'Ⓛ' => 'ⓛ', + 'Ⓜ' => 'ⓜ', + 'Ⓝ' => 'ⓝ', + 'Ⓞ' => 'ⓞ', + 'Ⓟ' => 'ⓟ', + 'Ⓠ' => 'ⓠ', + 'Ⓡ' => 'ⓡ', + 'Ⓢ' => 'ⓢ', + 'Ⓣ' => 'ⓣ', + 'Ⓤ' => 'ⓤ', + 'Ⓥ' => 'ⓥ', + 'Ⓦ' => 'ⓦ', + 'Ⓧ' => 'ⓧ', + 'Ⓨ' => 'ⓨ', + 'Ⓩ' => 'ⓩ', + 'Ⰰ' => 'ⰰ', + 'Ⰱ' => 'ⰱ', + 'Ⰲ' => 'ⰲ', + 'Ⰳ' => 'ⰳ', + 'Ⰴ' => 'ⰴ', + 'Ⰵ' => 'ⰵ', + 'Ⰶ' => 'ⰶ', + 'Ⰷ' => 'ⰷ', + 'Ⰸ' => 'ⰸ', + 'Ⰹ' => 'ⰹ', + 'Ⰺ' => 'ⰺ', + 'Ⰻ' => 'ⰻ', + 'Ⰼ' => 'ⰼ', + 'Ⰽ' => 'ⰽ', + 'Ⰾ' => 'ⰾ', + 'Ⰿ' => 'ⰿ', + 'Ⱀ' => 'ⱀ', + 'Ⱁ' => 'ⱁ', + 'Ⱂ' => 'ⱂ', + 'Ⱃ' => 'ⱃ', + 'Ⱄ' => 'ⱄ', + 'Ⱅ' => 'ⱅ', + 'Ⱆ' => 'ⱆ', + 'Ⱇ' => 'ⱇ', + 'Ⱈ' => 'ⱈ', + 'Ⱉ' => 'ⱉ', + 'Ⱊ' => 'ⱊ', + 'Ⱋ' => 'ⱋ', + 'Ⱌ' => 'ⱌ', + 'Ⱍ' => 'ⱍ', + 'Ⱎ' => 'ⱎ', + 'Ⱏ' => 'ⱏ', + 'Ⱐ' => 'ⱐ', + 'Ⱑ' => 'ⱑ', + 'Ⱒ' => 'ⱒ', + 'Ⱓ' => 'ⱓ', + 'Ⱔ' => 'ⱔ', + 'Ⱕ' => 'ⱕ', + 'Ⱖ' => 'ⱖ', + 'Ⱗ' => 'ⱗ', + 'Ⱘ' => 'ⱘ', + 'Ⱙ' => 'ⱙ', + 'Ⱚ' => 'ⱚ', + 'Ⱛ' => 'ⱛ', + 'Ⱜ' => 'ⱜ', + 'Ⱝ' => 'ⱝ', + 'Ⱞ' => 'ⱞ', + 'Ⱡ' => 'ⱡ', + 'Ɫ' => 'ɫ', + 'Ᵽ' => 'ᵽ', + 'Ɽ' => 'ɽ', + 'Ⱨ' => 'ⱨ', + 'Ⱪ' => 'ⱪ', + 'Ⱬ' => 'ⱬ', + 'Ɑ' => 'ɑ', + 'Ɱ' => 'ɱ', + 'Ɐ' => 'ɐ', + 'Ɒ' => 'ɒ', + 'Ⱳ' => 'ⱳ', + 'Ⱶ' => 'ⱶ', + 'Ȿ' => 'ȿ', + 'Ɀ' => 'ɀ', + 'Ⲁ' => 'ⲁ', + 'Ⲃ' => 'ⲃ', + 'Ⲅ' => 'ⲅ', + 'Ⲇ' => 'ⲇ', + 'Ⲉ' => 'ⲉ', + 'Ⲋ' => 'ⲋ', + 'Ⲍ' => 'ⲍ', + 'Ⲏ' => 'ⲏ', + 'Ⲑ' => 'ⲑ', + 'Ⲓ' => 'ⲓ', + 'Ⲕ' => 'ⲕ', + 'Ⲗ' => 'ⲗ', + 'Ⲙ' => 'ⲙ', + 'Ⲛ' => 'ⲛ', + 'Ⲝ' => 'ⲝ', + 'Ⲟ' => 'ⲟ', + 'Ⲡ' => 'ⲡ', + 'Ⲣ' => 'ⲣ', + 'Ⲥ' => 'ⲥ', + 'Ⲧ' => 'ⲧ', + 'Ⲩ' => 'ⲩ', + 'Ⲫ' => 'ⲫ', + 'Ⲭ' => 'ⲭ', + 'Ⲯ' => 'ⲯ', + 'Ⲱ' => 'ⲱ', + 'Ⲳ' => 'ⲳ', + 'Ⲵ' => 'ⲵ', + 'Ⲷ' => 'ⲷ', + 'Ⲹ' => 'ⲹ', + 'Ⲻ' => 'ⲻ', + 'Ⲽ' => 'ⲽ', + 'Ⲿ' => 'ⲿ', + 'Ⳁ' => 'ⳁ', + 'Ⳃ' => 'ⳃ', + 'Ⳅ' => 'ⳅ', + 'Ⳇ' => 'ⳇ', + 'Ⳉ' => 'ⳉ', + 'Ⳋ' => 'ⳋ', + 'Ⳍ' => 'ⳍ', + 'Ⳏ' => 'ⳏ', + 'Ⳑ' => 'ⳑ', + 'Ⳓ' => 'ⳓ', + 'Ⳕ' => 'ⳕ', + 'Ⳗ' => 'ⳗ', + 'Ⳙ' => 'ⳙ', + 'Ⳛ' => 'ⳛ', + 'Ⳝ' => 'ⳝ', + 'Ⳟ' => 'ⳟ', + 'Ⳡ' => 'ⳡ', + 'Ⳣ' => 'ⳣ', + 'Ⳬ' => 'ⳬ', + 'Ⳮ' => 'ⳮ', + 'Ⳳ' => 'ⳳ', + 'Ꙁ' => 'ꙁ', + 'Ꙃ' => 'ꙃ', + 'Ꙅ' => 'ꙅ', + 'Ꙇ' => 'ꙇ', + 'Ꙉ' => 'ꙉ', + 'Ꙋ' => 'ꙋ', + 'Ꙍ' => 'ꙍ', + 'Ꙏ' => 'ꙏ', + 'Ꙑ' => 'ꙑ', + 'Ꙓ' => 'ꙓ', + 'Ꙕ' => 'ꙕ', + 'Ꙗ' => 'ꙗ', + 'Ꙙ' => 'ꙙ', + 'Ꙛ' => 'ꙛ', + 'Ꙝ' => 'ꙝ', + 'Ꙟ' => 'ꙟ', + 'Ꙡ' => 'ꙡ', + 'Ꙣ' => 'ꙣ', + 'Ꙥ' => 'ꙥ', + 'Ꙧ' => 'ꙧ', + 'Ꙩ' => 'ꙩ', + 'Ꙫ' => 'ꙫ', + 'Ꙭ' => 'ꙭ', + 'Ꚁ' => 'ꚁ', + 'Ꚃ' => 'ꚃ', + 'Ꚅ' => 'ꚅ', + 'Ꚇ' => 'ꚇ', + 'Ꚉ' => 'ꚉ', + 'Ꚋ' => 'ꚋ', + 'Ꚍ' => 'ꚍ', + 'Ꚏ' => 'ꚏ', + 'Ꚑ' => 'ꚑ', + 'Ꚓ' => 'ꚓ', + 'Ꚕ' => 'ꚕ', + 'Ꚗ' => 'ꚗ', + 'Ꚙ' => 'ꚙ', + 'Ꚛ' => 'ꚛ', + 'Ꜣ' => 'ꜣ', + 'Ꜥ' => 'ꜥ', + 'Ꜧ' => 'ꜧ', + 'Ꜩ' => 'ꜩ', + 'Ꜫ' => 'ꜫ', + 'Ꜭ' => 'ꜭ', + 'Ꜯ' => 'ꜯ', + 'Ꜳ' => 'ꜳ', + 'Ꜵ' => 'ꜵ', + 'Ꜷ' => 'ꜷ', + 'Ꜹ' => 'ꜹ', + 'Ꜻ' => 'ꜻ', + 'Ꜽ' => 'ꜽ', + 'Ꜿ' => 'ꜿ', + 'Ꝁ' => 'ꝁ', + 'Ꝃ' => 'ꝃ', + 'Ꝅ' => 'ꝅ', + 'Ꝇ' => 'ꝇ', + 'Ꝉ' => 'ꝉ', + 'Ꝋ' => 'ꝋ', + 'Ꝍ' => 'ꝍ', + 'Ꝏ' => 'ꝏ', + 'Ꝑ' => 'ꝑ', + 'Ꝓ' => 'ꝓ', + 'Ꝕ' => 'ꝕ', + 'Ꝗ' => 'ꝗ', + 'Ꝙ' => 'ꝙ', + 'Ꝛ' => 'ꝛ', + 'Ꝝ' => 'ꝝ', + 'Ꝟ' => 'ꝟ', + 'Ꝡ' => 'ꝡ', + 'Ꝣ' => 'ꝣ', + 'Ꝥ' => 'ꝥ', + 'Ꝧ' => 'ꝧ', + 'Ꝩ' => 'ꝩ', + 'Ꝫ' => 'ꝫ', + 'Ꝭ' => 'ꝭ', + 'Ꝯ' => 'ꝯ', + 'Ꝺ' => 'ꝺ', + 'Ꝼ' => 'ꝼ', + 'Ᵹ' => 'ᵹ', + 'Ꝿ' => 'ꝿ', + 'Ꞁ' => 'ꞁ', + 'Ꞃ' => 'ꞃ', + 'Ꞅ' => 'ꞅ', + 'Ꞇ' => 'ꞇ', + 'Ꞌ' => 'ꞌ', + 'Ɥ' => 'ɥ', + 'Ꞑ' => 'ꞑ', + 'Ꞓ' => 'ꞓ', + 'Ꞗ' => 'ꞗ', + 'Ꞙ' => 'ꞙ', + 'Ꞛ' => 'ꞛ', + 'Ꞝ' => 'ꞝ', + 'Ꞟ' => 'ꞟ', + 'Ꞡ' => 'ꞡ', + 'Ꞣ' => 'ꞣ', + 'Ꞥ' => 'ꞥ', + 'Ꞧ' => 'ꞧ', + 'Ꞩ' => 'ꞩ', + 'Ɦ' => 'ɦ', + 'Ɜ' => 'ɜ', + 'Ɡ' => 'ɡ', + 'Ɬ' => 'ɬ', + 'Ɪ' => 'ɪ', + 'Ʞ' => 'ʞ', + 'Ʇ' => 'ʇ', + 'Ʝ' => 'ʝ', + 'Ꭓ' => 'ꭓ', + 'Ꞵ' => 'ꞵ', + 'Ꞷ' => 'ꞷ', + 'Ꞹ' => 'ꞹ', + 'Ꞻ' => 'ꞻ', + 'Ꞽ' => 'ꞽ', + 'Ꞿ' => 'ꞿ', + 'Ꟃ' => 'ꟃ', + 'Ꞔ' => 'ꞔ', + 'Ʂ' => 'ʂ', + 'Ᶎ' => 'ᶎ', + 'Ꟈ' => 'ꟈ', + 'Ꟊ' => 'ꟊ', + 'Ꟶ' => 'ꟶ', + 'A' => 'a', + 'B' => 'b', + 'C' => 'c', + 'D' => 'd', + 'E' => 'e', + 'F' => 'f', + 'G' => 'g', + 'H' => 'h', + 'I' => 'i', + 'J' => 'j', + 'K' => 'k', + 'L' => 'l', + 'M' => 'm', + 'N' => 'n', + 'O' => 'o', + 'P' => 'p', + 'Q' => 'q', + 'R' => 'r', + 'S' => 's', + 'T' => 't', + 'U' => 'u', + 'V' => 'v', + 'W' => 'w', + 'X' => 'x', + 'Y' => 'y', + 'Z' => 'z', + '𐐀' => '𐐨', + '𐐁' => '𐐩', + '𐐂' => '𐐪', + '𐐃' => '𐐫', + '𐐄' => '𐐬', + '𐐅' => '𐐭', + '𐐆' => '𐐮', + '𐐇' => '𐐯', + '𐐈' => '𐐰', + '𐐉' => '𐐱', + '𐐊' => '𐐲', + '𐐋' => '𐐳', + '𐐌' => '𐐴', + '𐐍' => '𐐵', + '𐐎' => '𐐶', + '𐐏' => '𐐷', + '𐐐' => '𐐸', + '𐐑' => '𐐹', + '𐐒' => '𐐺', + '𐐓' => '𐐻', + '𐐔' => '𐐼', + '𐐕' => '𐐽', + '𐐖' => '𐐾', + '𐐗' => '𐐿', + '𐐘' => '𐑀', + '𐐙' => '𐑁', + '𐐚' => '𐑂', + '𐐛' => '𐑃', + '𐐜' => '𐑄', + '𐐝' => '𐑅', + '𐐞' => '𐑆', + '𐐟' => '𐑇', + '𐐠' => '𐑈', + '𐐡' => '𐑉', + '𐐢' => '𐑊', + '𐐣' => '𐑋', + '𐐤' => '𐑌', + '𐐥' => '𐑍', + '𐐦' => '𐑎', + '𐐧' => '𐑏', + '𐒰' => '𐓘', + '𐒱' => '𐓙', + '𐒲' => '𐓚', + '𐒳' => '𐓛', + '𐒴' => '𐓜', + '𐒵' => '𐓝', + '𐒶' => '𐓞', + '𐒷' => '𐓟', + '𐒸' => '𐓠', + '𐒹' => '𐓡', + '𐒺' => '𐓢', + '𐒻' => '𐓣', + '𐒼' => '𐓤', + '𐒽' => '𐓥', + '𐒾' => '𐓦', + '𐒿' => '𐓧', + '𐓀' => '𐓨', + '𐓁' => '𐓩', + '𐓂' => '𐓪', + '𐓃' => '𐓫', + '𐓄' => '𐓬', + '𐓅' => '𐓭', + '𐓆' => '𐓮', + '𐓇' => '𐓯', + '𐓈' => '𐓰', + '𐓉' => '𐓱', + '𐓊' => '𐓲', + '𐓋' => '𐓳', + '𐓌' => '𐓴', + '𐓍' => '𐓵', + '𐓎' => '𐓶', + '𐓏' => '𐓷', + '𐓐' => '𐓸', + '𐓑' => '𐓹', + '𐓒' => '𐓺', + '𐓓' => '𐓻', + '𐲀' => '𐳀', + '𐲁' => '𐳁', + '𐲂' => '𐳂', + '𐲃' => '𐳃', + '𐲄' => '𐳄', + '𐲅' => '𐳅', + '𐲆' => '𐳆', + '𐲇' => '𐳇', + '𐲈' => '𐳈', + '𐲉' => '𐳉', + '𐲊' => '𐳊', + '𐲋' => '𐳋', + '𐲌' => '𐳌', + '𐲍' => '𐳍', + '𐲎' => '𐳎', + '𐲏' => '𐳏', + '𐲐' => '𐳐', + '𐲑' => '𐳑', + '𐲒' => '𐳒', + '𐲓' => '𐳓', + '𐲔' => '𐳔', + '𐲕' => '𐳕', + '𐲖' => '𐳖', + '𐲗' => '𐳗', + '𐲘' => '𐳘', + '𐲙' => '𐳙', + '𐲚' => '𐳚', + '𐲛' => '𐳛', + '𐲜' => '𐳜', + '𐲝' => '𐳝', + '𐲞' => '𐳞', + '𐲟' => '𐳟', + '𐲠' => '𐳠', + '𐲡' => '𐳡', + '𐲢' => '𐳢', + '𐲣' => '𐳣', + '𐲤' => '𐳤', + '𐲥' => '𐳥', + '𐲦' => '𐳦', + '𐲧' => '𐳧', + '𐲨' => '𐳨', + '𐲩' => '𐳩', + '𐲪' => '𐳪', + '𐲫' => '𐳫', + '𐲬' => '𐳬', + '𐲭' => '𐳭', + '𐲮' => '𐳮', + '𐲯' => '𐳯', + '𐲰' => '𐳰', + '𐲱' => '𐳱', + '𐲲' => '𐳲', + '𑢠' => '𑣀', + '𑢡' => '𑣁', + '𑢢' => '𑣂', + '𑢣' => '𑣃', + '𑢤' => '𑣄', + '𑢥' => '𑣅', + '𑢦' => '𑣆', + '𑢧' => '𑣇', + '𑢨' => '𑣈', + '𑢩' => '𑣉', + '𑢪' => '𑣊', + '𑢫' => '𑣋', + '𑢬' => '𑣌', + '𑢭' => '𑣍', + '𑢮' => '𑣎', + '𑢯' => '𑣏', + '𑢰' => '𑣐', + '𑢱' => '𑣑', + '𑢲' => '𑣒', + '𑢳' => '𑣓', + '𑢴' => '𑣔', + '𑢵' => '𑣕', + '𑢶' => '𑣖', + '𑢷' => '𑣗', + '𑢸' => '𑣘', + '𑢹' => '𑣙', + '𑢺' => '𑣚', + '𑢻' => '𑣛', + '𑢼' => '𑣜', + '𑢽' => '𑣝', + '𑢾' => '𑣞', + '𑢿' => '𑣟', + '𖹀' => '𖹠', + '𖹁' => '𖹡', + '𖹂' => '𖹢', + '𖹃' => '𖹣', + '𖹄' => '𖹤', + '𖹅' => '𖹥', + '𖹆' => '𖹦', + '𖹇' => '𖹧', + '𖹈' => '𖹨', + '𖹉' => '𖹩', + '𖹊' => '𖹪', + '𖹋' => '𖹫', + '𖹌' => '𖹬', + '𖹍' => '𖹭', + '𖹎' => '𖹮', + '𖹏' => '𖹯', + '𖹐' => '𖹰', + '𖹑' => '𖹱', + '𖹒' => '𖹲', + '𖹓' => '𖹳', + '𖹔' => '𖹴', + '𖹕' => '𖹵', + '𖹖' => '𖹶', + '𖹗' => '𖹷', + '𖹘' => '𖹸', + '𖹙' => '𖹹', + '𖹚' => '𖹺', + '𖹛' => '𖹻', + '𖹜' => '𖹼', + '𖹝' => '𖹽', + '𖹞' => '𖹾', + '𖹟' => '𖹿', + '𞤀' => '𞤢', + '𞤁' => '𞤣', + '𞤂' => '𞤤', + '𞤃' => '𞤥', + '𞤄' => '𞤦', + '𞤅' => '𞤧', + '𞤆' => '𞤨', + '𞤇' => '𞤩', + '𞤈' => '𞤪', + '𞤉' => '𞤫', + '𞤊' => '𞤬', + '𞤋' => '𞤭', + '𞤌' => '𞤮', + '𞤍' => '𞤯', + '𞤎' => '𞤰', + '𞤏' => '𞤱', + '𞤐' => '𞤲', + '𞤑' => '𞤳', + '𞤒' => '𞤴', + '𞤓' => '𞤵', + '𞤔' => '𞤶', + '𞤕' => '𞤷', + '𞤖' => '𞤸', + '𞤗' => '𞤹', + '𞤘' => '𞤺', + '𞤙' => '𞤻', + '𞤚' => '𞤼', + '𞤛' => '𞤽', + '𞤜' => '𞤾', + '𞤝' => '𞤿', + '𞤞' => '𞥀', + '𞤟' => '𞥁', + '𞤠' => '𞥂', + '𞤡' => '𞥃', +); diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php new file mode 100644 index 00000000..2a8f6e73 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php @@ -0,0 +1,5 @@ + 'A', + 'b' => 'B', + 'c' => 'C', + 'd' => 'D', + 'e' => 'E', + 'f' => 'F', + 'g' => 'G', + 'h' => 'H', + 'i' => 'I', + 'j' => 'J', + 'k' => 'K', + 'l' => 'L', + 'm' => 'M', + 'n' => 'N', + 'o' => 'O', + 'p' => 'P', + 'q' => 'Q', + 'r' => 'R', + 's' => 'S', + 't' => 'T', + 'u' => 'U', + 'v' => 'V', + 'w' => 'W', + 'x' => 'X', + 'y' => 'Y', + 'z' => 'Z', + 'µ' => 'Μ', + 'à' => 'À', + 'á' => 'Á', + 'â' => 'Â', + 'ã' => 'Ã', + 'ä' => 'Ä', + 'å' => 'Å', + 'æ' => 'Æ', + 'ç' => 'Ç', + 'è' => 'È', + 'é' => 'É', + 'ê' => 'Ê', + 'ë' => 'Ë', + 'ì' => 'Ì', + 'í' => 'Í', + 'î' => 'Î', + 'ï' => 'Ï', + 'ð' => 'Ð', + 'ñ' => 'Ñ', + 'ò' => 'Ò', + 'ó' => 'Ó', + 'ô' => 'Ô', + 'õ' => 'Õ', + 'ö' => 'Ö', + 'ø' => 'Ø', + 'ù' => 'Ù', + 'ú' => 'Ú', + 'û' => 'Û', + 'ü' => 'Ü', + 'ý' => 'Ý', + 'þ' => 'Þ', + 'ÿ' => 'Ÿ', + 'ā' => 'Ā', + 'ă' => 'Ă', + 'ą' => 'Ą', + 'ć' => 'Ć', + 'ĉ' => 'Ĉ', + 'ċ' => 'Ċ', + 'č' => 'Č', + 'ď' => 'Ď', + 'đ' => 'Đ', + 'ē' => 'Ē', + 'ĕ' => 'Ĕ', + 'ė' => 'Ė', + 'ę' => 'Ę', + 'ě' => 'Ě', + 'ĝ' => 'Ĝ', + 'ğ' => 'Ğ', + 'ġ' => 'Ġ', + 'ģ' => 'Ģ', + 'ĥ' => 'Ĥ', + 'ħ' => 'Ħ', + 'ĩ' => 'Ĩ', + 'ī' => 'Ī', + 'ĭ' => 'Ĭ', + 'į' => 'Į', + 'ı' => 'I', + 'ij' => 'IJ', + 'ĵ' => 'Ĵ', + 'ķ' => 'Ķ', + 'ĺ' => 'Ĺ', + 'ļ' => 'Ļ', + 'ľ' => 'Ľ', + 'ŀ' => 'Ŀ', + 'ł' => 'Ł', + 'ń' => 'Ń', + 'ņ' => 'Ņ', + 'ň' => 'Ň', + 'ŋ' => 'Ŋ', + 'ō' => 'Ō', + 'ŏ' => 'Ŏ', + 'ő' => 'Ő', + 'œ' => 'Œ', + 'ŕ' => 'Ŕ', + 'ŗ' => 'Ŗ', + 'ř' => 'Ř', + 'ś' => 'Ś', + 'ŝ' => 'Ŝ', + 'ş' => 'Ş', + 'š' => 'Š', + 'ţ' => 'Ţ', + 'ť' => 'Ť', + 'ŧ' => 'Ŧ', + 'ũ' => 'Ũ', + 'ū' => 'Ū', + 'ŭ' => 'Ŭ', + 'ů' => 'Ů', + 'ű' => 'Ű', + 'ų' => 'Ų', + 'ŵ' => 'Ŵ', + 'ŷ' => 'Ŷ', + 'ź' => 'Ź', + 'ż' => 'Ż', + 'ž' => 'Ž', + 'ſ' => 'S', + 'ƀ' => 'Ƀ', + 'ƃ' => 'Ƃ', + 'ƅ' => 'Ƅ', + 'ƈ' => 'Ƈ', + 'ƌ' => 'Ƌ', + 'ƒ' => 'Ƒ', + 'ƕ' => 'Ƕ', + 'ƙ' => 'Ƙ', + 'ƚ' => 'Ƚ', + 'ƞ' => 'Ƞ', + 'ơ' => 'Ơ', + 'ƣ' => 'Ƣ', + 'ƥ' => 'Ƥ', + 'ƨ' => 'Ƨ', + 'ƭ' => 'Ƭ', + 'ư' => 'Ư', + 'ƴ' => 'Ƴ', + 'ƶ' => 'Ƶ', + 'ƹ' => 'Ƹ', + 'ƽ' => 'Ƽ', + 'ƿ' => 'Ƿ', + 'Dž' => 'DŽ', + 'dž' => 'DŽ', + 'Lj' => 'LJ', + 'lj' => 'LJ', + 'Nj' => 'NJ', + 'nj' => 'NJ', + 'ǎ' => 'Ǎ', + 'ǐ' => 'Ǐ', + 'ǒ' => 'Ǒ', + 'ǔ' => 'Ǔ', + 'ǖ' => 'Ǖ', + 'ǘ' => 'Ǘ', + 'ǚ' => 'Ǚ', + 'ǜ' => 'Ǜ', + 'ǝ' => 'Ǝ', + 'ǟ' => 'Ǟ', + 'ǡ' => 'Ǡ', + 'ǣ' => 'Ǣ', + 'ǥ' => 'Ǥ', + 'ǧ' => 'Ǧ', + 'ǩ' => 'Ǩ', + 'ǫ' => 'Ǫ', + 'ǭ' => 'Ǭ', + 'ǯ' => 'Ǯ', + 'Dz' => 'DZ', + 'dz' => 'DZ', + 'ǵ' => 'Ǵ', + 'ǹ' => 'Ǹ', + 'ǻ' => 'Ǻ', + 'ǽ' => 'Ǽ', + 'ǿ' => 'Ǿ', + 'ȁ' => 'Ȁ', + 'ȃ' => 'Ȃ', + 'ȅ' => 'Ȅ', + 'ȇ' => 'Ȇ', + 'ȉ' => 'Ȉ', + 'ȋ' => 'Ȋ', + 'ȍ' => 'Ȍ', + 'ȏ' => 'Ȏ', + 'ȑ' => 'Ȑ', + 'ȓ' => 'Ȓ', + 'ȕ' => 'Ȕ', + 'ȗ' => 'Ȗ', + 'ș' => 'Ș', + 'ț' => 'Ț', + 'ȝ' => 'Ȝ', + 'ȟ' => 'Ȟ', + 'ȣ' => 'Ȣ', + 'ȥ' => 'Ȥ', + 'ȧ' => 'Ȧ', + 'ȩ' => 'Ȩ', + 'ȫ' => 'Ȫ', + 'ȭ' => 'Ȭ', + 'ȯ' => 'Ȯ', + 'ȱ' => 'Ȱ', + 'ȳ' => 'Ȳ', + 'ȼ' => 'Ȼ', + 'ȿ' => 'Ȿ', + 'ɀ' => 'Ɀ', + 'ɂ' => 'Ɂ', + 'ɇ' => 'Ɇ', + 'ɉ' => 'Ɉ', + 'ɋ' => 'Ɋ', + 'ɍ' => 'Ɍ', + 'ɏ' => 'Ɏ', + 'ɐ' => 'Ɐ', + 'ɑ' => 'Ɑ', + 'ɒ' => 'Ɒ', + 'ɓ' => 'Ɓ', + 'ɔ' => 'Ɔ', + 'ɖ' => 'Ɖ', + 'ɗ' => 'Ɗ', + 'ə' => 'Ə', + 'ɛ' => 'Ɛ', + 'ɜ' => 'Ɜ', + 'ɠ' => 'Ɠ', + 'ɡ' => 'Ɡ', + 'ɣ' => 'Ɣ', + 'ɥ' => 'Ɥ', + 'ɦ' => 'Ɦ', + 'ɨ' => 'Ɨ', + 'ɩ' => 'Ɩ', + 'ɪ' => 'Ɪ', + 'ɫ' => 'Ɫ', + 'ɬ' => 'Ɬ', + 'ɯ' => 'Ɯ', + 'ɱ' => 'Ɱ', + 'ɲ' => 'Ɲ', + 'ɵ' => 'Ɵ', + 'ɽ' => 'Ɽ', + 'ʀ' => 'Ʀ', + 'ʂ' => 'Ʂ', + 'ʃ' => 'Ʃ', + 'ʇ' => 'Ʇ', + 'ʈ' => 'Ʈ', + 'ʉ' => 'Ʉ', + 'ʊ' => 'Ʊ', + 'ʋ' => 'Ʋ', + 'ʌ' => 'Ʌ', + 'ʒ' => 'Ʒ', + 'ʝ' => 'Ʝ', + 'ʞ' => 'Ʞ', + 'ͅ' => 'Ι', + 'ͱ' => 'Ͱ', + 'ͳ' => 'Ͳ', + 'ͷ' => 'Ͷ', + 'ͻ' => 'Ͻ', + 'ͼ' => 'Ͼ', + 'ͽ' => 'Ͽ', + 'ά' => 'Ά', + 'έ' => 'Έ', + 'ή' => 'Ή', + 'ί' => 'Ί', + 'α' => 'Α', + 'β' => 'Β', + 'γ' => 'Γ', + 'δ' => 'Δ', + 'ε' => 'Ε', + 'ζ' => 'Ζ', + 'η' => 'Η', + 'θ' => 'Θ', + 'ι' => 'Ι', + 'κ' => 'Κ', + 'λ' => 'Λ', + 'μ' => 'Μ', + 'ν' => 'Ν', + 'ξ' => 'Ξ', + 'ο' => 'Ο', + 'π' => 'Π', + 'ρ' => 'Ρ', + 'ς' => 'Σ', + 'σ' => 'Σ', + 'τ' => 'Τ', + 'υ' => 'Υ', + 'φ' => 'Φ', + 'χ' => 'Χ', + 'ψ' => 'Ψ', + 'ω' => 'Ω', + 'ϊ' => 'Ϊ', + 'ϋ' => 'Ϋ', + 'ό' => 'Ό', + 'ύ' => 'Ύ', + 'ώ' => 'Ώ', + 'ϐ' => 'Β', + 'ϑ' => 'Θ', + 'ϕ' => 'Φ', + 'ϖ' => 'Π', + 'ϗ' => 'Ϗ', + 'ϙ' => 'Ϙ', + 'ϛ' => 'Ϛ', + 'ϝ' => 'Ϝ', + 'ϟ' => 'Ϟ', + 'ϡ' => 'Ϡ', + 'ϣ' => 'Ϣ', + 'ϥ' => 'Ϥ', + 'ϧ' => 'Ϧ', + 'ϩ' => 'Ϩ', + 'ϫ' => 'Ϫ', + 'ϭ' => 'Ϭ', + 'ϯ' => 'Ϯ', + 'ϰ' => 'Κ', + 'ϱ' => 'Ρ', + 'ϲ' => 'Ϲ', + 'ϳ' => 'Ϳ', + 'ϵ' => 'Ε', + 'ϸ' => 'Ϸ', + 'ϻ' => 'Ϻ', + 'а' => 'А', + 'б' => 'Б', + 'в' => 'В', + 'г' => 'Г', + 'д' => 'Д', + 'е' => 'Е', + 'ж' => 'Ж', + 'з' => 'З', + 'и' => 'И', + 'й' => 'Й', + 'к' => 'К', + 'л' => 'Л', + 'м' => 'М', + 'н' => 'Н', + 'о' => 'О', + 'п' => 'П', + 'р' => 'Р', + 'с' => 'С', + 'т' => 'Т', + 'у' => 'У', + 'ф' => 'Ф', + 'х' => 'Х', + 'ц' => 'Ц', + 'ч' => 'Ч', + 'ш' => 'Ш', + 'щ' => 'Щ', + 'ъ' => 'Ъ', + 'ы' => 'Ы', + 'ь' => 'Ь', + 'э' => 'Э', + 'ю' => 'Ю', + 'я' => 'Я', + 'ѐ' => 'Ѐ', + 'ё' => 'Ё', + 'ђ' => 'Ђ', + 'ѓ' => 'Ѓ', + 'є' => 'Є', + 'ѕ' => 'Ѕ', + 'і' => 'І', + 'ї' => 'Ї', + 'ј' => 'Ј', + 'љ' => 'Љ', + 'њ' => 'Њ', + 'ћ' => 'Ћ', + 'ќ' => 'Ќ', + 'ѝ' => 'Ѝ', + 'ў' => 'Ў', + 'џ' => 'Џ', + 'ѡ' => 'Ѡ', + 'ѣ' => 'Ѣ', + 'ѥ' => 'Ѥ', + 'ѧ' => 'Ѧ', + 'ѩ' => 'Ѩ', + 'ѫ' => 'Ѫ', + 'ѭ' => 'Ѭ', + 'ѯ' => 'Ѯ', + 'ѱ' => 'Ѱ', + 'ѳ' => 'Ѳ', + 'ѵ' => 'Ѵ', + 'ѷ' => 'Ѷ', + 'ѹ' => 'Ѹ', + 'ѻ' => 'Ѻ', + 'ѽ' => 'Ѽ', + 'ѿ' => 'Ѿ', + 'ҁ' => 'Ҁ', + 'ҋ' => 'Ҋ', + 'ҍ' => 'Ҍ', + 'ҏ' => 'Ҏ', + 'ґ' => 'Ґ', + 'ғ' => 'Ғ', + 'ҕ' => 'Ҕ', + 'җ' => 'Җ', + 'ҙ' => 'Ҙ', + 'қ' => 'Қ', + 'ҝ' => 'Ҝ', + 'ҟ' => 'Ҟ', + 'ҡ' => 'Ҡ', + 'ң' => 'Ң', + 'ҥ' => 'Ҥ', + 'ҧ' => 'Ҧ', + 'ҩ' => 'Ҩ', + 'ҫ' => 'Ҫ', + 'ҭ' => 'Ҭ', + 'ү' => 'Ү', + 'ұ' => 'Ұ', + 'ҳ' => 'Ҳ', + 'ҵ' => 'Ҵ', + 'ҷ' => 'Ҷ', + 'ҹ' => 'Ҹ', + 'һ' => 'Һ', + 'ҽ' => 'Ҽ', + 'ҿ' => 'Ҿ', + 'ӂ' => 'Ӂ', + 'ӄ' => 'Ӄ', + 'ӆ' => 'Ӆ', + 'ӈ' => 'Ӈ', + 'ӊ' => 'Ӊ', + 'ӌ' => 'Ӌ', + 'ӎ' => 'Ӎ', + 'ӏ' => 'Ӏ', + 'ӑ' => 'Ӑ', + 'ӓ' => 'Ӓ', + 'ӕ' => 'Ӕ', + 'ӗ' => 'Ӗ', + 'ә' => 'Ә', + 'ӛ' => 'Ӛ', + 'ӝ' => 'Ӝ', + 'ӟ' => 'Ӟ', + 'ӡ' => 'Ӡ', + 'ӣ' => 'Ӣ', + 'ӥ' => 'Ӥ', + 'ӧ' => 'Ӧ', + 'ө' => 'Ө', + 'ӫ' => 'Ӫ', + 'ӭ' => 'Ӭ', + 'ӯ' => 'Ӯ', + 'ӱ' => 'Ӱ', + 'ӳ' => 'Ӳ', + 'ӵ' => 'Ӵ', + 'ӷ' => 'Ӷ', + 'ӹ' => 'Ӹ', + 'ӻ' => 'Ӻ', + 'ӽ' => 'Ӽ', + 'ӿ' => 'Ӿ', + 'ԁ' => 'Ԁ', + 'ԃ' => 'Ԃ', + 'ԅ' => 'Ԅ', + 'ԇ' => 'Ԇ', + 'ԉ' => 'Ԉ', + 'ԋ' => 'Ԋ', + 'ԍ' => 'Ԍ', + 'ԏ' => 'Ԏ', + 'ԑ' => 'Ԑ', + 'ԓ' => 'Ԓ', + 'ԕ' => 'Ԕ', + 'ԗ' => 'Ԗ', + 'ԙ' => 'Ԙ', + 'ԛ' => 'Ԛ', + 'ԝ' => 'Ԝ', + 'ԟ' => 'Ԟ', + 'ԡ' => 'Ԡ', + 'ԣ' => 'Ԣ', + 'ԥ' => 'Ԥ', + 'ԧ' => 'Ԧ', + 'ԩ' => 'Ԩ', + 'ԫ' => 'Ԫ', + 'ԭ' => 'Ԭ', + 'ԯ' => 'Ԯ', + 'ա' => 'Ա', + 'բ' => 'Բ', + 'գ' => 'Գ', + 'դ' => 'Դ', + 'ե' => 'Ե', + 'զ' => 'Զ', + 'է' => 'Է', + 'ը' => 'Ը', + 'թ' => 'Թ', + 'ժ' => 'Ժ', + 'ի' => 'Ի', + 'լ' => 'Լ', + 'խ' => 'Խ', + 'ծ' => 'Ծ', + 'կ' => 'Կ', + 'հ' => 'Հ', + 'ձ' => 'Ձ', + 'ղ' => 'Ղ', + 'ճ' => 'Ճ', + 'մ' => 'Մ', + 'յ' => 'Յ', + 'ն' => 'Ն', + 'շ' => 'Շ', + 'ո' => 'Ո', + 'չ' => 'Չ', + 'պ' => 'Պ', + 'ջ' => 'Ջ', + 'ռ' => 'Ռ', + 'ս' => 'Ս', + 'վ' => 'Վ', + 'տ' => 'Տ', + 'ր' => 'Ր', + 'ց' => 'Ց', + 'ւ' => 'Ւ', + 'փ' => 'Փ', + 'ք' => 'Ք', + 'օ' => 'Օ', + 'ֆ' => 'Ֆ', + 'ა' => 'Ა', + 'ბ' => 'Ბ', + 'გ' => 'Გ', + 'დ' => 'Დ', + 'ე' => 'Ე', + 'ვ' => 'Ვ', + 'ზ' => 'Ზ', + 'თ' => 'Თ', + 'ი' => 'Ი', + 'კ' => 'Კ', + 'ლ' => 'Ლ', + 'მ' => 'Მ', + 'ნ' => 'Ნ', + 'ო' => 'Ო', + 'პ' => 'Პ', + 'ჟ' => 'Ჟ', + 'რ' => 'Რ', + 'ს' => 'Ს', + 'ტ' => 'Ტ', + 'უ' => 'Უ', + 'ფ' => 'Ფ', + 'ქ' => 'Ქ', + 'ღ' => 'Ღ', + 'ყ' => 'Ყ', + 'შ' => 'Შ', + 'ჩ' => 'Ჩ', + 'ც' => 'Ც', + 'ძ' => 'Ძ', + 'წ' => 'Წ', + 'ჭ' => 'Ჭ', + 'ხ' => 'Ხ', + 'ჯ' => 'Ჯ', + 'ჰ' => 'Ჰ', + 'ჱ' => 'Ჱ', + 'ჲ' => 'Ჲ', + 'ჳ' => 'Ჳ', + 'ჴ' => 'Ჴ', + 'ჵ' => 'Ჵ', + 'ჶ' => 'Ჶ', + 'ჷ' => 'Ჷ', + 'ჸ' => 'Ჸ', + 'ჹ' => 'Ჹ', + 'ჺ' => 'Ჺ', + 'ჽ' => 'Ჽ', + 'ჾ' => 'Ჾ', + 'ჿ' => 'Ჿ', + 'ᏸ' => 'Ᏸ', + 'ᏹ' => 'Ᏹ', + 'ᏺ' => 'Ᏺ', + 'ᏻ' => 'Ᏻ', + 'ᏼ' => 'Ᏼ', + 'ᏽ' => 'Ᏽ', + 'ᲀ' => 'В', + 'ᲁ' => 'Д', + 'ᲂ' => 'О', + 'ᲃ' => 'С', + 'ᲄ' => 'Т', + 'ᲅ' => 'Т', + 'ᲆ' => 'Ъ', + 'ᲇ' => 'Ѣ', + 'ᲈ' => 'Ꙋ', + 'ᵹ' => 'Ᵹ', + 'ᵽ' => 'Ᵽ', + 'ᶎ' => 'Ᶎ', + 'ḁ' => 'Ḁ', + 'ḃ' => 'Ḃ', + 'ḅ' => 'Ḅ', + 'ḇ' => 'Ḇ', + 'ḉ' => 'Ḉ', + 'ḋ' => 'Ḋ', + 'ḍ' => 'Ḍ', + 'ḏ' => 'Ḏ', + 'ḑ' => 'Ḑ', + 'ḓ' => 'Ḓ', + 'ḕ' => 'Ḕ', + 'ḗ' => 'Ḗ', + 'ḙ' => 'Ḙ', + 'ḛ' => 'Ḛ', + 'ḝ' => 'Ḝ', + 'ḟ' => 'Ḟ', + 'ḡ' => 'Ḡ', + 'ḣ' => 'Ḣ', + 'ḥ' => 'Ḥ', + 'ḧ' => 'Ḧ', + 'ḩ' => 'Ḩ', + 'ḫ' => 'Ḫ', + 'ḭ' => 'Ḭ', + 'ḯ' => 'Ḯ', + 'ḱ' => 'Ḱ', + 'ḳ' => 'Ḳ', + 'ḵ' => 'Ḵ', + 'ḷ' => 'Ḷ', + 'ḹ' => 'Ḹ', + 'ḻ' => 'Ḻ', + 'ḽ' => 'Ḽ', + 'ḿ' => 'Ḿ', + 'ṁ' => 'Ṁ', + 'ṃ' => 'Ṃ', + 'ṅ' => 'Ṅ', + 'ṇ' => 'Ṇ', + 'ṉ' => 'Ṉ', + 'ṋ' => 'Ṋ', + 'ṍ' => 'Ṍ', + 'ṏ' => 'Ṏ', + 'ṑ' => 'Ṑ', + 'ṓ' => 'Ṓ', + 'ṕ' => 'Ṕ', + 'ṗ' => 'Ṗ', + 'ṙ' => 'Ṙ', + 'ṛ' => 'Ṛ', + 'ṝ' => 'Ṝ', + 'ṟ' => 'Ṟ', + 'ṡ' => 'Ṡ', + 'ṣ' => 'Ṣ', + 'ṥ' => 'Ṥ', + 'ṧ' => 'Ṧ', + 'ṩ' => 'Ṩ', + 'ṫ' => 'Ṫ', + 'ṭ' => 'Ṭ', + 'ṯ' => 'Ṯ', + 'ṱ' => 'Ṱ', + 'ṳ' => 'Ṳ', + 'ṵ' => 'Ṵ', + 'ṷ' => 'Ṷ', + 'ṹ' => 'Ṹ', + 'ṻ' => 'Ṻ', + 'ṽ' => 'Ṽ', + 'ṿ' => 'Ṿ', + 'ẁ' => 'Ẁ', + 'ẃ' => 'Ẃ', + 'ẅ' => 'Ẅ', + 'ẇ' => 'Ẇ', + 'ẉ' => 'Ẉ', + 'ẋ' => 'Ẋ', + 'ẍ' => 'Ẍ', + 'ẏ' => 'Ẏ', + 'ẑ' => 'Ẑ', + 'ẓ' => 'Ẓ', + 'ẕ' => 'Ẕ', + 'ẛ' => 'Ṡ', + 'ạ' => 'Ạ', + 'ả' => 'Ả', + 'ấ' => 'Ấ', + 'ầ' => 'Ầ', + 'ẩ' => 'Ẩ', + 'ẫ' => 'Ẫ', + 'ậ' => 'Ậ', + 'ắ' => 'Ắ', + 'ằ' => 'Ằ', + 'ẳ' => 'Ẳ', + 'ẵ' => 'Ẵ', + 'ặ' => 'Ặ', + 'ẹ' => 'Ẹ', + 'ẻ' => 'Ẻ', + 'ẽ' => 'Ẽ', + 'ế' => 'Ế', + 'ề' => 'Ề', + 'ể' => 'Ể', + 'ễ' => 'Ễ', + 'ệ' => 'Ệ', + 'ỉ' => 'Ỉ', + 'ị' => 'Ị', + 'ọ' => 'Ọ', + 'ỏ' => 'Ỏ', + 'ố' => 'Ố', + 'ồ' => 'Ồ', + 'ổ' => 'Ổ', + 'ỗ' => 'Ỗ', + 'ộ' => 'Ộ', + 'ớ' => 'Ớ', + 'ờ' => 'Ờ', + 'ở' => 'Ở', + 'ỡ' => 'Ỡ', + 'ợ' => 'Ợ', + 'ụ' => 'Ụ', + 'ủ' => 'Ủ', + 'ứ' => 'Ứ', + 'ừ' => 'Ừ', + 'ử' => 'Ử', + 'ữ' => 'Ữ', + 'ự' => 'Ự', + 'ỳ' => 'Ỳ', + 'ỵ' => 'Ỵ', + 'ỷ' => 'Ỷ', + 'ỹ' => 'Ỹ', + 'ỻ' => 'Ỻ', + 'ỽ' => 'Ỽ', + 'ỿ' => 'Ỿ', + 'ἀ' => 'Ἀ', + 'ἁ' => 'Ἁ', + 'ἂ' => 'Ἂ', + 'ἃ' => 'Ἃ', + 'ἄ' => 'Ἄ', + 'ἅ' => 'Ἅ', + 'ἆ' => 'Ἆ', + 'ἇ' => 'Ἇ', + 'ἐ' => 'Ἐ', + 'ἑ' => 'Ἑ', + 'ἒ' => 'Ἒ', + 'ἓ' => 'Ἓ', + 'ἔ' => 'Ἔ', + 'ἕ' => 'Ἕ', + 'ἠ' => 'Ἠ', + 'ἡ' => 'Ἡ', + 'ἢ' => 'Ἢ', + 'ἣ' => 'Ἣ', + 'ἤ' => 'Ἤ', + 'ἥ' => 'Ἥ', + 'ἦ' => 'Ἦ', + 'ἧ' => 'Ἧ', + 'ἰ' => 'Ἰ', + 'ἱ' => 'Ἱ', + 'ἲ' => 'Ἲ', + 'ἳ' => 'Ἳ', + 'ἴ' => 'Ἴ', + 'ἵ' => 'Ἵ', + 'ἶ' => 'Ἶ', + 'ἷ' => 'Ἷ', + 'ὀ' => 'Ὀ', + 'ὁ' => 'Ὁ', + 'ὂ' => 'Ὂ', + 'ὃ' => 'Ὃ', + 'ὄ' => 'Ὄ', + 'ὅ' => 'Ὅ', + 'ὑ' => 'Ὑ', + 'ὓ' => 'Ὓ', + 'ὕ' => 'Ὕ', + 'ὗ' => 'Ὗ', + 'ὠ' => 'Ὠ', + 'ὡ' => 'Ὡ', + 'ὢ' => 'Ὢ', + 'ὣ' => 'Ὣ', + 'ὤ' => 'Ὤ', + 'ὥ' => 'Ὥ', + 'ὦ' => 'Ὦ', + 'ὧ' => 'Ὧ', + 'ὰ' => 'Ὰ', + 'ά' => 'Ά', + 'ὲ' => 'Ὲ', + 'έ' => 'Έ', + 'ὴ' => 'Ὴ', + 'ή' => 'Ή', + 'ὶ' => 'Ὶ', + 'ί' => 'Ί', + 'ὸ' => 'Ὸ', + 'ό' => 'Ό', + 'ὺ' => 'Ὺ', + 'ύ' => 'Ύ', + 'ὼ' => 'Ὼ', + 'ώ' => 'Ώ', + 'ᾀ' => 'ἈΙ', + 'ᾁ' => 'ἉΙ', + 'ᾂ' => 'ἊΙ', + 'ᾃ' => 'ἋΙ', + 'ᾄ' => 'ἌΙ', + 'ᾅ' => 'ἍΙ', + 'ᾆ' => 'ἎΙ', + 'ᾇ' => 'ἏΙ', + 'ᾐ' => 'ἨΙ', + 'ᾑ' => 'ἩΙ', + 'ᾒ' => 'ἪΙ', + 'ᾓ' => 'ἫΙ', + 'ᾔ' => 'ἬΙ', + 'ᾕ' => 'ἭΙ', + 'ᾖ' => 'ἮΙ', + 'ᾗ' => 'ἯΙ', + 'ᾠ' => 'ὨΙ', + 'ᾡ' => 'ὩΙ', + 'ᾢ' => 'ὪΙ', + 'ᾣ' => 'ὫΙ', + 'ᾤ' => 'ὬΙ', + 'ᾥ' => 'ὭΙ', + 'ᾦ' => 'ὮΙ', + 'ᾧ' => 'ὯΙ', + 'ᾰ' => 'Ᾰ', + 'ᾱ' => 'Ᾱ', + 'ᾳ' => 'ΑΙ', + 'ι' => 'Ι', + 'ῃ' => 'ΗΙ', + 'ῐ' => 'Ῐ', + 'ῑ' => 'Ῑ', + 'ῠ' => 'Ῠ', + 'ῡ' => 'Ῡ', + 'ῥ' => 'Ῥ', + 'ῳ' => 'ΩΙ', + 'ⅎ' => 'Ⅎ', + 'ⅰ' => 'Ⅰ', + 'ⅱ' => 'Ⅱ', + 'ⅲ' => 'Ⅲ', + 'ⅳ' => 'Ⅳ', + 'ⅴ' => 'Ⅴ', + 'ⅵ' => 'Ⅵ', + 'ⅶ' => 'Ⅶ', + 'ⅷ' => 'Ⅷ', + 'ⅸ' => 'Ⅸ', + 'ⅹ' => 'Ⅹ', + 'ⅺ' => 'Ⅺ', + 'ⅻ' => 'Ⅻ', + 'ⅼ' => 'Ⅼ', + 'ⅽ' => 'Ⅽ', + 'ⅾ' => 'Ⅾ', + 'ⅿ' => 'Ⅿ', + 'ↄ' => 'Ↄ', + 'ⓐ' => 'Ⓐ', + 'ⓑ' => 'Ⓑ', + 'ⓒ' => 'Ⓒ', + 'ⓓ' => 'Ⓓ', + 'ⓔ' => 'Ⓔ', + 'ⓕ' => 'Ⓕ', + 'ⓖ' => 'Ⓖ', + 'ⓗ' => 'Ⓗ', + 'ⓘ' => 'Ⓘ', + 'ⓙ' => 'Ⓙ', + 'ⓚ' => 'Ⓚ', + 'ⓛ' => 'Ⓛ', + 'ⓜ' => 'Ⓜ', + 'ⓝ' => 'Ⓝ', + 'ⓞ' => 'Ⓞ', + 'ⓟ' => 'Ⓟ', + 'ⓠ' => 'Ⓠ', + 'ⓡ' => 'Ⓡ', + 'ⓢ' => 'Ⓢ', + 'ⓣ' => 'Ⓣ', + 'ⓤ' => 'Ⓤ', + 'ⓥ' => 'Ⓥ', + 'ⓦ' => 'Ⓦ', + 'ⓧ' => 'Ⓧ', + 'ⓨ' => 'Ⓨ', + 'ⓩ' => 'Ⓩ', + 'ⰰ' => 'Ⰰ', + 'ⰱ' => 'Ⰱ', + 'ⰲ' => 'Ⰲ', + 'ⰳ' => 'Ⰳ', + 'ⰴ' => 'Ⰴ', + 'ⰵ' => 'Ⰵ', + 'ⰶ' => 'Ⰶ', + 'ⰷ' => 'Ⰷ', + 'ⰸ' => 'Ⰸ', + 'ⰹ' => 'Ⰹ', + 'ⰺ' => 'Ⰺ', + 'ⰻ' => 'Ⰻ', + 'ⰼ' => 'Ⰼ', + 'ⰽ' => 'Ⰽ', + 'ⰾ' => 'Ⰾ', + 'ⰿ' => 'Ⰿ', + 'ⱀ' => 'Ⱀ', + 'ⱁ' => 'Ⱁ', + 'ⱂ' => 'Ⱂ', + 'ⱃ' => 'Ⱃ', + 'ⱄ' => 'Ⱄ', + 'ⱅ' => 'Ⱅ', + 'ⱆ' => 'Ⱆ', + 'ⱇ' => 'Ⱇ', + 'ⱈ' => 'Ⱈ', + 'ⱉ' => 'Ⱉ', + 'ⱊ' => 'Ⱊ', + 'ⱋ' => 'Ⱋ', + 'ⱌ' => 'Ⱌ', + 'ⱍ' => 'Ⱍ', + 'ⱎ' => 'Ⱎ', + 'ⱏ' => 'Ⱏ', + 'ⱐ' => 'Ⱐ', + 'ⱑ' => 'Ⱑ', + 'ⱒ' => 'Ⱒ', + 'ⱓ' => 'Ⱓ', + 'ⱔ' => 'Ⱔ', + 'ⱕ' => 'Ⱕ', + 'ⱖ' => 'Ⱖ', + 'ⱗ' => 'Ⱗ', + 'ⱘ' => 'Ⱘ', + 'ⱙ' => 'Ⱙ', + 'ⱚ' => 'Ⱚ', + 'ⱛ' => 'Ⱛ', + 'ⱜ' => 'Ⱜ', + 'ⱝ' => 'Ⱝ', + 'ⱞ' => 'Ⱞ', + 'ⱡ' => 'Ⱡ', + 'ⱥ' => 'Ⱥ', + 'ⱦ' => 'Ⱦ', + 'ⱨ' => 'Ⱨ', + 'ⱪ' => 'Ⱪ', + 'ⱬ' => 'Ⱬ', + 'ⱳ' => 'Ⱳ', + 'ⱶ' => 'Ⱶ', + 'ⲁ' => 'Ⲁ', + 'ⲃ' => 'Ⲃ', + 'ⲅ' => 'Ⲅ', + 'ⲇ' => 'Ⲇ', + 'ⲉ' => 'Ⲉ', + 'ⲋ' => 'Ⲋ', + 'ⲍ' => 'Ⲍ', + 'ⲏ' => 'Ⲏ', + 'ⲑ' => 'Ⲑ', + 'ⲓ' => 'Ⲓ', + 'ⲕ' => 'Ⲕ', + 'ⲗ' => 'Ⲗ', + 'ⲙ' => 'Ⲙ', + 'ⲛ' => 'Ⲛ', + 'ⲝ' => 'Ⲝ', + 'ⲟ' => 'Ⲟ', + 'ⲡ' => 'Ⲡ', + 'ⲣ' => 'Ⲣ', + 'ⲥ' => 'Ⲥ', + 'ⲧ' => 'Ⲧ', + 'ⲩ' => 'Ⲩ', + 'ⲫ' => 'Ⲫ', + 'ⲭ' => 'Ⲭ', + 'ⲯ' => 'Ⲯ', + 'ⲱ' => 'Ⲱ', + 'ⲳ' => 'Ⲳ', + 'ⲵ' => 'Ⲵ', + 'ⲷ' => 'Ⲷ', + 'ⲹ' => 'Ⲹ', + 'ⲻ' => 'Ⲻ', + 'ⲽ' => 'Ⲽ', + 'ⲿ' => 'Ⲿ', + 'ⳁ' => 'Ⳁ', + 'ⳃ' => 'Ⳃ', + 'ⳅ' => 'Ⳅ', + 'ⳇ' => 'Ⳇ', + 'ⳉ' => 'Ⳉ', + 'ⳋ' => 'Ⳋ', + 'ⳍ' => 'Ⳍ', + 'ⳏ' => 'Ⳏ', + 'ⳑ' => 'Ⳑ', + 'ⳓ' => 'Ⳓ', + 'ⳕ' => 'Ⳕ', + 'ⳗ' => 'Ⳗ', + 'ⳙ' => 'Ⳙ', + 'ⳛ' => 'Ⳛ', + 'ⳝ' => 'Ⳝ', + 'ⳟ' => 'Ⳟ', + 'ⳡ' => 'Ⳡ', + 'ⳣ' => 'Ⳣ', + 'ⳬ' => 'Ⳬ', + 'ⳮ' => 'Ⳮ', + 'ⳳ' => 'Ⳳ', + 'ⴀ' => 'Ⴀ', + 'ⴁ' => 'Ⴁ', + 'ⴂ' => 'Ⴂ', + 'ⴃ' => 'Ⴃ', + 'ⴄ' => 'Ⴄ', + 'ⴅ' => 'Ⴅ', + 'ⴆ' => 'Ⴆ', + 'ⴇ' => 'Ⴇ', + 'ⴈ' => 'Ⴈ', + 'ⴉ' => 'Ⴉ', + 'ⴊ' => 'Ⴊ', + 'ⴋ' => 'Ⴋ', + 'ⴌ' => 'Ⴌ', + 'ⴍ' => 'Ⴍ', + 'ⴎ' => 'Ⴎ', + 'ⴏ' => 'Ⴏ', + 'ⴐ' => 'Ⴐ', + 'ⴑ' => 'Ⴑ', + 'ⴒ' => 'Ⴒ', + 'ⴓ' => 'Ⴓ', + 'ⴔ' => 'Ⴔ', + 'ⴕ' => 'Ⴕ', + 'ⴖ' => 'Ⴖ', + 'ⴗ' => 'Ⴗ', + 'ⴘ' => 'Ⴘ', + 'ⴙ' => 'Ⴙ', + 'ⴚ' => 'Ⴚ', + 'ⴛ' => 'Ⴛ', + 'ⴜ' => 'Ⴜ', + 'ⴝ' => 'Ⴝ', + 'ⴞ' => 'Ⴞ', + 'ⴟ' => 'Ⴟ', + 'ⴠ' => 'Ⴠ', + 'ⴡ' => 'Ⴡ', + 'ⴢ' => 'Ⴢ', + 'ⴣ' => 'Ⴣ', + 'ⴤ' => 'Ⴤ', + 'ⴥ' => 'Ⴥ', + 'ⴧ' => 'Ⴧ', + 'ⴭ' => 'Ⴭ', + 'ꙁ' => 'Ꙁ', + 'ꙃ' => 'Ꙃ', + 'ꙅ' => 'Ꙅ', + 'ꙇ' => 'Ꙇ', + 'ꙉ' => 'Ꙉ', + 'ꙋ' => 'Ꙋ', + 'ꙍ' => 'Ꙍ', + 'ꙏ' => 'Ꙏ', + 'ꙑ' => 'Ꙑ', + 'ꙓ' => 'Ꙓ', + 'ꙕ' => 'Ꙕ', + 'ꙗ' => 'Ꙗ', + 'ꙙ' => 'Ꙙ', + 'ꙛ' => 'Ꙛ', + 'ꙝ' => 'Ꙝ', + 'ꙟ' => 'Ꙟ', + 'ꙡ' => 'Ꙡ', + 'ꙣ' => 'Ꙣ', + 'ꙥ' => 'Ꙥ', + 'ꙧ' => 'Ꙧ', + 'ꙩ' => 'Ꙩ', + 'ꙫ' => 'Ꙫ', + 'ꙭ' => 'Ꙭ', + 'ꚁ' => 'Ꚁ', + 'ꚃ' => 'Ꚃ', + 'ꚅ' => 'Ꚅ', + 'ꚇ' => 'Ꚇ', + 'ꚉ' => 'Ꚉ', + 'ꚋ' => 'Ꚋ', + 'ꚍ' => 'Ꚍ', + 'ꚏ' => 'Ꚏ', + 'ꚑ' => 'Ꚑ', + 'ꚓ' => 'Ꚓ', + 'ꚕ' => 'Ꚕ', + 'ꚗ' => 'Ꚗ', + 'ꚙ' => 'Ꚙ', + 'ꚛ' => 'Ꚛ', + 'ꜣ' => 'Ꜣ', + 'ꜥ' => 'Ꜥ', + 'ꜧ' => 'Ꜧ', + 'ꜩ' => 'Ꜩ', + 'ꜫ' => 'Ꜫ', + 'ꜭ' => 'Ꜭ', + 'ꜯ' => 'Ꜯ', + 'ꜳ' => 'Ꜳ', + 'ꜵ' => 'Ꜵ', + 'ꜷ' => 'Ꜷ', + 'ꜹ' => 'Ꜹ', + 'ꜻ' => 'Ꜻ', + 'ꜽ' => 'Ꜽ', + 'ꜿ' => 'Ꜿ', + 'ꝁ' => 'Ꝁ', + 'ꝃ' => 'Ꝃ', + 'ꝅ' => 'Ꝅ', + 'ꝇ' => 'Ꝇ', + 'ꝉ' => 'Ꝉ', + 'ꝋ' => 'Ꝋ', + 'ꝍ' => 'Ꝍ', + 'ꝏ' => 'Ꝏ', + 'ꝑ' => 'Ꝑ', + 'ꝓ' => 'Ꝓ', + 'ꝕ' => 'Ꝕ', + 'ꝗ' => 'Ꝗ', + 'ꝙ' => 'Ꝙ', + 'ꝛ' => 'Ꝛ', + 'ꝝ' => 'Ꝝ', + 'ꝟ' => 'Ꝟ', + 'ꝡ' => 'Ꝡ', + 'ꝣ' => 'Ꝣ', + 'ꝥ' => 'Ꝥ', + 'ꝧ' => 'Ꝧ', + 'ꝩ' => 'Ꝩ', + 'ꝫ' => 'Ꝫ', + 'ꝭ' => 'Ꝭ', + 'ꝯ' => 'Ꝯ', + 'ꝺ' => 'Ꝺ', + 'ꝼ' => 'Ꝼ', + 'ꝿ' => 'Ꝿ', + 'ꞁ' => 'Ꞁ', + 'ꞃ' => 'Ꞃ', + 'ꞅ' => 'Ꞅ', + 'ꞇ' => 'Ꞇ', + 'ꞌ' => 'Ꞌ', + 'ꞑ' => 'Ꞑ', + 'ꞓ' => 'Ꞓ', + 'ꞔ' => 'Ꞔ', + 'ꞗ' => 'Ꞗ', + 'ꞙ' => 'Ꞙ', + 'ꞛ' => 'Ꞛ', + 'ꞝ' => 'Ꞝ', + 'ꞟ' => 'Ꞟ', + 'ꞡ' => 'Ꞡ', + 'ꞣ' => 'Ꞣ', + 'ꞥ' => 'Ꞥ', + 'ꞧ' => 'Ꞧ', + 'ꞩ' => 'Ꞩ', + 'ꞵ' => 'Ꞵ', + 'ꞷ' => 'Ꞷ', + 'ꞹ' => 'Ꞹ', + 'ꞻ' => 'Ꞻ', + 'ꞽ' => 'Ꞽ', + 'ꞿ' => 'Ꞿ', + 'ꟃ' => 'Ꟃ', + 'ꟈ' => 'Ꟈ', + 'ꟊ' => 'Ꟊ', + 'ꟶ' => 'Ꟶ', + 'ꭓ' => 'Ꭓ', + 'ꭰ' => 'Ꭰ', + 'ꭱ' => 'Ꭱ', + 'ꭲ' => 'Ꭲ', + 'ꭳ' => 'Ꭳ', + 'ꭴ' => 'Ꭴ', + 'ꭵ' => 'Ꭵ', + 'ꭶ' => 'Ꭶ', + 'ꭷ' => 'Ꭷ', + 'ꭸ' => 'Ꭸ', + 'ꭹ' => 'Ꭹ', + 'ꭺ' => 'Ꭺ', + 'ꭻ' => 'Ꭻ', + 'ꭼ' => 'Ꭼ', + 'ꭽ' => 'Ꭽ', + 'ꭾ' => 'Ꭾ', + 'ꭿ' => 'Ꭿ', + 'ꮀ' => 'Ꮀ', + 'ꮁ' => 'Ꮁ', + 'ꮂ' => 'Ꮂ', + 'ꮃ' => 'Ꮃ', + 'ꮄ' => 'Ꮄ', + 'ꮅ' => 'Ꮅ', + 'ꮆ' => 'Ꮆ', + 'ꮇ' => 'Ꮇ', + 'ꮈ' => 'Ꮈ', + 'ꮉ' => 'Ꮉ', + 'ꮊ' => 'Ꮊ', + 'ꮋ' => 'Ꮋ', + 'ꮌ' => 'Ꮌ', + 'ꮍ' => 'Ꮍ', + 'ꮎ' => 'Ꮎ', + 'ꮏ' => 'Ꮏ', + 'ꮐ' => 'Ꮐ', + 'ꮑ' => 'Ꮑ', + 'ꮒ' => 'Ꮒ', + 'ꮓ' => 'Ꮓ', + 'ꮔ' => 'Ꮔ', + 'ꮕ' => 'Ꮕ', + 'ꮖ' => 'Ꮖ', + 'ꮗ' => 'Ꮗ', + 'ꮘ' => 'Ꮘ', + 'ꮙ' => 'Ꮙ', + 'ꮚ' => 'Ꮚ', + 'ꮛ' => 'Ꮛ', + 'ꮜ' => 'Ꮜ', + 'ꮝ' => 'Ꮝ', + 'ꮞ' => 'Ꮞ', + 'ꮟ' => 'Ꮟ', + 'ꮠ' => 'Ꮠ', + 'ꮡ' => 'Ꮡ', + 'ꮢ' => 'Ꮢ', + 'ꮣ' => 'Ꮣ', + 'ꮤ' => 'Ꮤ', + 'ꮥ' => 'Ꮥ', + 'ꮦ' => 'Ꮦ', + 'ꮧ' => 'Ꮧ', + 'ꮨ' => 'Ꮨ', + 'ꮩ' => 'Ꮩ', + 'ꮪ' => 'Ꮪ', + 'ꮫ' => 'Ꮫ', + 'ꮬ' => 'Ꮬ', + 'ꮭ' => 'Ꮭ', + 'ꮮ' => 'Ꮮ', + 'ꮯ' => 'Ꮯ', + 'ꮰ' => 'Ꮰ', + 'ꮱ' => 'Ꮱ', + 'ꮲ' => 'Ꮲ', + 'ꮳ' => 'Ꮳ', + 'ꮴ' => 'Ꮴ', + 'ꮵ' => 'Ꮵ', + 'ꮶ' => 'Ꮶ', + 'ꮷ' => 'Ꮷ', + 'ꮸ' => 'Ꮸ', + 'ꮹ' => 'Ꮹ', + 'ꮺ' => 'Ꮺ', + 'ꮻ' => 'Ꮻ', + 'ꮼ' => 'Ꮼ', + 'ꮽ' => 'Ꮽ', + 'ꮾ' => 'Ꮾ', + 'ꮿ' => 'Ꮿ', + 'a' => 'A', + 'b' => 'B', + 'c' => 'C', + 'd' => 'D', + 'e' => 'E', + 'f' => 'F', + 'g' => 'G', + 'h' => 'H', + 'i' => 'I', + 'j' => 'J', + 'k' => 'K', + 'l' => 'L', + 'm' => 'M', + 'n' => 'N', + 'o' => 'O', + 'p' => 'P', + 'q' => 'Q', + 'r' => 'R', + 's' => 'S', + 't' => 'T', + 'u' => 'U', + 'v' => 'V', + 'w' => 'W', + 'x' => 'X', + 'y' => 'Y', + 'z' => 'Z', + '𐐨' => '𐐀', + '𐐩' => '𐐁', + '𐐪' => '𐐂', + '𐐫' => '𐐃', + '𐐬' => '𐐄', + '𐐭' => '𐐅', + '𐐮' => '𐐆', + '𐐯' => '𐐇', + '𐐰' => '𐐈', + '𐐱' => '𐐉', + '𐐲' => '𐐊', + '𐐳' => '𐐋', + '𐐴' => '𐐌', + '𐐵' => '𐐍', + '𐐶' => '𐐎', + '𐐷' => '𐐏', + '𐐸' => '𐐐', + '𐐹' => '𐐑', + '𐐺' => '𐐒', + '𐐻' => '𐐓', + '𐐼' => '𐐔', + '𐐽' => '𐐕', + '𐐾' => '𐐖', + '𐐿' => '𐐗', + '𐑀' => '𐐘', + '𐑁' => '𐐙', + '𐑂' => '𐐚', + '𐑃' => '𐐛', + '𐑄' => '𐐜', + '𐑅' => '𐐝', + '𐑆' => '𐐞', + '𐑇' => '𐐟', + '𐑈' => '𐐠', + '𐑉' => '𐐡', + '𐑊' => '𐐢', + '𐑋' => '𐐣', + '𐑌' => '𐐤', + '𐑍' => '𐐥', + '𐑎' => '𐐦', + '𐑏' => '𐐧', + '𐓘' => '𐒰', + '𐓙' => '𐒱', + '𐓚' => '𐒲', + '𐓛' => '𐒳', + '𐓜' => '𐒴', + '𐓝' => '𐒵', + '𐓞' => '𐒶', + '𐓟' => '𐒷', + '𐓠' => '𐒸', + '𐓡' => '𐒹', + '𐓢' => '𐒺', + '𐓣' => '𐒻', + '𐓤' => '𐒼', + '𐓥' => '𐒽', + '𐓦' => '𐒾', + '𐓧' => '𐒿', + '𐓨' => '𐓀', + '𐓩' => '𐓁', + '𐓪' => '𐓂', + '𐓫' => '𐓃', + '𐓬' => '𐓄', + '𐓭' => '𐓅', + '𐓮' => '𐓆', + '𐓯' => '𐓇', + '𐓰' => '𐓈', + '𐓱' => '𐓉', + '𐓲' => '𐓊', + '𐓳' => '𐓋', + '𐓴' => '𐓌', + '𐓵' => '𐓍', + '𐓶' => '𐓎', + '𐓷' => '𐓏', + '𐓸' => '𐓐', + '𐓹' => '𐓑', + '𐓺' => '𐓒', + '𐓻' => '𐓓', + '𐳀' => '𐲀', + '𐳁' => '𐲁', + '𐳂' => '𐲂', + '𐳃' => '𐲃', + '𐳄' => '𐲄', + '𐳅' => '𐲅', + '𐳆' => '𐲆', + '𐳇' => '𐲇', + '𐳈' => '𐲈', + '𐳉' => '𐲉', + '𐳊' => '𐲊', + '𐳋' => '𐲋', + '𐳌' => '𐲌', + '𐳍' => '𐲍', + '𐳎' => '𐲎', + '𐳏' => '𐲏', + '𐳐' => '𐲐', + '𐳑' => '𐲑', + '𐳒' => '𐲒', + '𐳓' => '𐲓', + '𐳔' => '𐲔', + '𐳕' => '𐲕', + '𐳖' => '𐲖', + '𐳗' => '𐲗', + '𐳘' => '𐲘', + '𐳙' => '𐲙', + '𐳚' => '𐲚', + '𐳛' => '𐲛', + '𐳜' => '𐲜', + '𐳝' => '𐲝', + '𐳞' => '𐲞', + '𐳟' => '𐲟', + '𐳠' => '𐲠', + '𐳡' => '𐲡', + '𐳢' => '𐲢', + '𐳣' => '𐲣', + '𐳤' => '𐲤', + '𐳥' => '𐲥', + '𐳦' => '𐲦', + '𐳧' => '𐲧', + '𐳨' => '𐲨', + '𐳩' => '𐲩', + '𐳪' => '𐲪', + '𐳫' => '𐲫', + '𐳬' => '𐲬', + '𐳭' => '𐲭', + '𐳮' => '𐲮', + '𐳯' => '𐲯', + '𐳰' => '𐲰', + '𐳱' => '𐲱', + '𐳲' => '𐲲', + '𑣀' => '𑢠', + '𑣁' => '𑢡', + '𑣂' => '𑢢', + '𑣃' => '𑢣', + '𑣄' => '𑢤', + '𑣅' => '𑢥', + '𑣆' => '𑢦', + '𑣇' => '𑢧', + '𑣈' => '𑢨', + '𑣉' => '𑢩', + '𑣊' => '𑢪', + '𑣋' => '𑢫', + '𑣌' => '𑢬', + '𑣍' => '𑢭', + '𑣎' => '𑢮', + '𑣏' => '𑢯', + '𑣐' => '𑢰', + '𑣑' => '𑢱', + '𑣒' => '𑢲', + '𑣓' => '𑢳', + '𑣔' => '𑢴', + '𑣕' => '𑢵', + '𑣖' => '𑢶', + '𑣗' => '𑢷', + '𑣘' => '𑢸', + '𑣙' => '𑢹', + '𑣚' => '𑢺', + '𑣛' => '𑢻', + '𑣜' => '𑢼', + '𑣝' => '𑢽', + '𑣞' => '𑢾', + '𑣟' => '𑢿', + '𖹠' => '𖹀', + '𖹡' => '𖹁', + '𖹢' => '𖹂', + '𖹣' => '𖹃', + '𖹤' => '𖹄', + '𖹥' => '𖹅', + '𖹦' => '𖹆', + '𖹧' => '𖹇', + '𖹨' => '𖹈', + '𖹩' => '𖹉', + '𖹪' => '𖹊', + '𖹫' => '𖹋', + '𖹬' => '𖹌', + '𖹭' => '𖹍', + '𖹮' => '𖹎', + '𖹯' => '𖹏', + '𖹰' => '𖹐', + '𖹱' => '𖹑', + '𖹲' => '𖹒', + '𖹳' => '𖹓', + '𖹴' => '𖹔', + '𖹵' => '𖹕', + '𖹶' => '𖹖', + '𖹷' => '𖹗', + '𖹸' => '𖹘', + '𖹹' => '𖹙', + '𖹺' => '𖹚', + '𖹻' => '𖹛', + '𖹼' => '𖹜', + '𖹽' => '𖹝', + '𖹾' => '𖹞', + '𖹿' => '𖹟', + '𞤢' => '𞤀', + '𞤣' => '𞤁', + '𞤤' => '𞤂', + '𞤥' => '𞤃', + '𞤦' => '𞤄', + '𞤧' => '𞤅', + '𞤨' => '𞤆', + '𞤩' => '𞤇', + '𞤪' => '𞤈', + '𞤫' => '𞤉', + '𞤬' => '𞤊', + '𞤭' => '𞤋', + '𞤮' => '𞤌', + '𞤯' => '𞤍', + '𞤰' => '𞤎', + '𞤱' => '𞤏', + '𞤲' => '𞤐', + '𞤳' => '𞤑', + '𞤴' => '𞤒', + '𞤵' => '𞤓', + '𞤶' => '𞤔', + '𞤷' => '𞤕', + '𞤸' => '𞤖', + '𞤹' => '𞤗', + '𞤺' => '𞤘', + '𞤻' => '𞤙', + '𞤼' => '𞤚', + '𞤽' => '𞤛', + '𞤾' => '𞤜', + '𞤿' => '𞤝', + '𞥀' => '𞤞', + '𞥁' => '𞤟', + '𞥂' => '𞤠', + '𞥃' => '𞤡', + 'ß' => 'SS', + 'ff' => 'FF', + 'fi' => 'FI', + 'fl' => 'FL', + 'ffi' => 'FFI', + 'ffl' => 'FFL', + 'ſt' => 'ST', + 'st' => 'ST', + 'և' => 'ԵՒ', + 'ﬓ' => 'ՄՆ', + 'ﬔ' => 'ՄԵ', + 'ﬕ' => 'ՄԻ', + 'ﬖ' => 'ՎՆ', + 'ﬗ' => 'ՄԽ', + 'ʼn' => 'ʼN', + 'ΐ' => 'Ϊ́', + 'ΰ' => 'Ϋ́', + 'ǰ' => 'J̌', + 'ẖ' => 'H̱', + 'ẗ' => 'T̈', + 'ẘ' => 'W̊', + 'ẙ' => 'Y̊', + 'ẚ' => 'Aʾ', + 'ὐ' => 'Υ̓', + 'ὒ' => 'Υ̓̀', + 'ὔ' => 'Υ̓́', + 'ὖ' => 'Υ̓͂', + 'ᾶ' => 'Α͂', + 'ῆ' => 'Η͂', + 'ῒ' => 'Ϊ̀', + 'ΐ' => 'Ϊ́', + 'ῖ' => 'Ι͂', + 'ῗ' => 'Ϊ͂', + 'ῢ' => 'Ϋ̀', + 'ΰ' => 'Ϋ́', + 'ῤ' => 'Ρ̓', + 'ῦ' => 'Υ͂', + 'ῧ' => 'Ϋ͂', + 'ῶ' => 'Ω͂', + 'ᾈ' => 'ἈΙ', + 'ᾉ' => 'ἉΙ', + 'ᾊ' => 'ἊΙ', + 'ᾋ' => 'ἋΙ', + 'ᾌ' => 'ἌΙ', + 'ᾍ' => 'ἍΙ', + 'ᾎ' => 'ἎΙ', + 'ᾏ' => 'ἏΙ', + 'ᾘ' => 'ἨΙ', + 'ᾙ' => 'ἩΙ', + 'ᾚ' => 'ἪΙ', + 'ᾛ' => 'ἫΙ', + 'ᾜ' => 'ἬΙ', + 'ᾝ' => 'ἭΙ', + 'ᾞ' => 'ἮΙ', + 'ᾟ' => 'ἯΙ', + 'ᾨ' => 'ὨΙ', + 'ᾩ' => 'ὩΙ', + 'ᾪ' => 'ὪΙ', + 'ᾫ' => 'ὫΙ', + 'ᾬ' => 'ὬΙ', + 'ᾭ' => 'ὭΙ', + 'ᾮ' => 'ὮΙ', + 'ᾯ' => 'ὯΙ', + 'ᾼ' => 'ΑΙ', + 'ῌ' => 'ΗΙ', + 'ῼ' => 'ΩΙ', + 'ᾲ' => 'ᾺΙ', + 'ᾴ' => 'ΆΙ', + 'ῂ' => 'ῊΙ', + 'ῄ' => 'ΉΙ', + 'ῲ' => 'ῺΙ', + 'ῴ' => 'ΏΙ', + 'ᾷ' => 'Α͂Ι', + 'ῇ' => 'Η͂Ι', + 'ῷ' => 'Ω͂Ι', +); diff --git a/vendor/symfony/polyfill-mbstring/bootstrap.php b/vendor/symfony/polyfill-mbstring/bootstrap.php new file mode 100644 index 00000000..ecf1a035 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/bootstrap.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Mbstring as p; + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +if (!function_exists('mb_convert_encoding')) { + function mb_convert_encoding($string, $to_encoding, $from_encoding = null) { return p\Mbstring::mb_convert_encoding($string, $to_encoding, $from_encoding); } +} +if (!function_exists('mb_decode_mimeheader')) { + function mb_decode_mimeheader($string) { return p\Mbstring::mb_decode_mimeheader($string); } +} +if (!function_exists('mb_encode_mimeheader')) { + function mb_encode_mimeheader($string, $charset = null, $transfer_encoding = null, $newline = "\r\n", $indent = 0) { return p\Mbstring::mb_encode_mimeheader($string, $charset, $transfer_encoding, $newline, $indent); } +} +if (!function_exists('mb_decode_numericentity')) { + function mb_decode_numericentity($string, $map, $encoding = null) { return p\Mbstring::mb_decode_numericentity($string, $map, $encoding); } +} +if (!function_exists('mb_encode_numericentity')) { + function mb_encode_numericentity($string, $map, $encoding = null, $hex = false) { return p\Mbstring::mb_encode_numericentity($string, $map, $encoding, $hex); } +} +if (!function_exists('mb_convert_case')) { + function mb_convert_case($string, $mode, $encoding = null) { return p\Mbstring::mb_convert_case($string, $mode, $encoding); } +} +if (!function_exists('mb_internal_encoding')) { + function mb_internal_encoding($encoding = null) { return p\Mbstring::mb_internal_encoding($encoding); } +} +if (!function_exists('mb_language')) { + function mb_language($language = null) { return p\Mbstring::mb_language($language); } +} +if (!function_exists('mb_list_encodings')) { + function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); } +} +if (!function_exists('mb_encoding_aliases')) { + function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); } +} +if (!function_exists('mb_check_encoding')) { + function mb_check_encoding($value = null, $encoding = null) { return p\Mbstring::mb_check_encoding($value, $encoding); } +} +if (!function_exists('mb_detect_encoding')) { + function mb_detect_encoding($string, $encodings = null, $strict = false) { return p\Mbstring::mb_detect_encoding($string, $encodings, $strict); } +} +if (!function_exists('mb_detect_order')) { + function mb_detect_order($encoding = null) { return p\Mbstring::mb_detect_order($encoding); } +} +if (!function_exists('mb_parse_str')) { + function mb_parse_str($string, &$result = []) { parse_str($string, $result); return (bool) $result; } +} +if (!function_exists('mb_strlen')) { + function mb_strlen($string, $encoding = null) { return p\Mbstring::mb_strlen($string, $encoding); } +} +if (!function_exists('mb_strpos')) { + function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strpos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strtolower')) { + function mb_strtolower($string, $encoding = null) { return p\Mbstring::mb_strtolower($string, $encoding); } +} +if (!function_exists('mb_strtoupper')) { + function mb_strtoupper($string, $encoding = null) { return p\Mbstring::mb_strtoupper($string, $encoding); } +} +if (!function_exists('mb_substitute_character')) { + function mb_substitute_character($substitute_character = null) { return p\Mbstring::mb_substitute_character($substitute_character); } +} +if (!function_exists('mb_substr')) { + function mb_substr($string, $start, $length = 2147483647, $encoding = null) { return p\Mbstring::mb_substr($string, $start, $length, $encoding); } +} +if (!function_exists('mb_stripos')) { + function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_stripos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_stristr')) { + function mb_stristr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_stristr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strrchr')) { + function mb_strrchr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrchr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strrichr')) { + function mb_strrichr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrichr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strripos')) { + function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strripos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strrpos')) { + function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strrpos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strstr')) { + function mb_strstr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strstr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_get_info')) { + function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); } +} +if (!function_exists('mb_http_output')) { + function mb_http_output($encoding = null) { return p\Mbstring::mb_http_output($encoding); } +} +if (!function_exists('mb_strwidth')) { + function mb_strwidth($string, $encoding = null) { return p\Mbstring::mb_strwidth($string, $encoding); } +} +if (!function_exists('mb_substr_count')) { + function mb_substr_count($haystack, $needle, $encoding = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $encoding); } +} +if (!function_exists('mb_output_handler')) { + function mb_output_handler($string, $status) { return p\Mbstring::mb_output_handler($string, $status); } +} +if (!function_exists('mb_http_input')) { + function mb_http_input($type = null) { return p\Mbstring::mb_http_input($type); } +} + +if (!function_exists('mb_convert_variables')) { + function mb_convert_variables($to_encoding, $from_encoding, &...$vars) { return p\Mbstring::mb_convert_variables($to_encoding, $from_encoding, ...$vars); } +} + +if (!function_exists('mb_ord')) { + function mb_ord($string, $encoding = null) { return p\Mbstring::mb_ord($string, $encoding); } +} +if (!function_exists('mb_chr')) { + function mb_chr($codepoint, $encoding = null) { return p\Mbstring::mb_chr($codepoint, $encoding); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub($string, $encoding = null) { $encoding = null === $encoding ? mb_internal_encoding() : $encoding; return mb_convert_encoding($string, $encoding, $encoding); } +} +if (!function_exists('mb_str_split')) { + function mb_str_split($string, $length = 1, $encoding = null) { return p\Mbstring::mb_str_split($string, $length, $encoding); } +} + +if (!function_exists('mb_str_pad')) { + function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Mbstring::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); } +} + +if (extension_loaded('mbstring')) { + return; +} + +if (!defined('MB_CASE_UPPER')) { + define('MB_CASE_UPPER', 0); +} +if (!defined('MB_CASE_LOWER')) { + define('MB_CASE_LOWER', 1); +} +if (!defined('MB_CASE_TITLE')) { + define('MB_CASE_TITLE', 2); +} diff --git a/vendor/symfony/polyfill-mbstring/bootstrap80.php b/vendor/symfony/polyfill-mbstring/bootstrap80.php new file mode 100644 index 00000000..2f9fb5b4 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/bootstrap80.php @@ -0,0 +1,147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Mbstring as p; + +if (!function_exists('mb_convert_encoding')) { + function mb_convert_encoding(array|string|null $string, ?string $to_encoding, array|string|null $from_encoding = null): array|string|false { return p\Mbstring::mb_convert_encoding($string ?? '', (string) $to_encoding, $from_encoding); } +} +if (!function_exists('mb_decode_mimeheader')) { + function mb_decode_mimeheader(?string $string): string { return p\Mbstring::mb_decode_mimeheader((string) $string); } +} +if (!function_exists('mb_encode_mimeheader')) { + function mb_encode_mimeheader(?string $string, ?string $charset = null, ?string $transfer_encoding = null, ?string $newline = "\r\n", ?int $indent = 0): string { return p\Mbstring::mb_encode_mimeheader((string) $string, $charset, $transfer_encoding, (string) $newline, (int) $indent); } +} +if (!function_exists('mb_decode_numericentity')) { + function mb_decode_numericentity(?string $string, array $map, ?string $encoding = null): string { return p\Mbstring::mb_decode_numericentity((string) $string, $map, $encoding); } +} +if (!function_exists('mb_encode_numericentity')) { + function mb_encode_numericentity(?string $string, array $map, ?string $encoding = null, ?bool $hex = false): string { return p\Mbstring::mb_encode_numericentity((string) $string, $map, $encoding, (bool) $hex); } +} +if (!function_exists('mb_convert_case')) { + function mb_convert_case(?string $string, ?int $mode, ?string $encoding = null): string { return p\Mbstring::mb_convert_case((string) $string, (int) $mode, $encoding); } +} +if (!function_exists('mb_internal_encoding')) { + function mb_internal_encoding(?string $encoding = null): string|bool { return p\Mbstring::mb_internal_encoding($encoding); } +} +if (!function_exists('mb_language')) { + function mb_language(?string $language = null): string|bool { return p\Mbstring::mb_language($language); } +} +if (!function_exists('mb_list_encodings')) { + function mb_list_encodings(): array { return p\Mbstring::mb_list_encodings(); } +} +if (!function_exists('mb_encoding_aliases')) { + function mb_encoding_aliases(?string $encoding): array { return p\Mbstring::mb_encoding_aliases((string) $encoding); } +} +if (!function_exists('mb_check_encoding')) { + function mb_check_encoding(array|string|null $value = null, ?string $encoding = null): bool { return p\Mbstring::mb_check_encoding($value, $encoding); } +} +if (!function_exists('mb_detect_encoding')) { + function mb_detect_encoding(?string $string, array|string|null $encodings = null, ?bool $strict = false): string|false { return p\Mbstring::mb_detect_encoding((string) $string, $encodings, (bool) $strict); } +} +if (!function_exists('mb_detect_order')) { + function mb_detect_order(array|string|null $encoding = null): array|bool { return p\Mbstring::mb_detect_order($encoding); } +} +if (!function_exists('mb_parse_str')) { + function mb_parse_str(?string $string, &$result = []): bool { parse_str((string) $string, $result); return (bool) $result; } +} +if (!function_exists('mb_strlen')) { + function mb_strlen(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strlen((string) $string, $encoding); } +} +if (!function_exists('mb_strpos')) { + function mb_strpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strpos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strtolower')) { + function mb_strtolower(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtolower((string) $string, $encoding); } +} +if (!function_exists('mb_strtoupper')) { + function mb_strtoupper(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtoupper((string) $string, $encoding); } +} +if (!function_exists('mb_substitute_character')) { + function mb_substitute_character(string|int|null $substitute_character = null): string|int|bool { return p\Mbstring::mb_substitute_character($substitute_character); } +} +if (!function_exists('mb_substr')) { + function mb_substr(?string $string, ?int $start, ?int $length = null, ?string $encoding = null): string { return p\Mbstring::mb_substr((string) $string, (int) $start, $length, $encoding); } +} +if (!function_exists('mb_stripos')) { + function mb_stripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_stripos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_stristr')) { + function mb_stristr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_stristr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strrchr')) { + function mb_strrchr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrchr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strrichr')) { + function mb_strrichr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrichr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strripos')) { + function mb_strripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strripos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strrpos')) { + function mb_strrpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strrpos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strstr')) { + function mb_strstr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strstr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_get_info')) { + function mb_get_info(?string $type = 'all'): array|string|int|false { return p\Mbstring::mb_get_info((string) $type); } +} +if (!function_exists('mb_http_output')) { + function mb_http_output(?string $encoding = null): string|bool { return p\Mbstring::mb_http_output($encoding); } +} +if (!function_exists('mb_strwidth')) { + function mb_strwidth(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strwidth((string) $string, $encoding); } +} +if (!function_exists('mb_substr_count')) { + function mb_substr_count(?string $haystack, ?string $needle, ?string $encoding = null): int { return p\Mbstring::mb_substr_count((string) $haystack, (string) $needle, $encoding); } +} +if (!function_exists('mb_output_handler')) { + function mb_output_handler(?string $string, ?int $status): string { return p\Mbstring::mb_output_handler((string) $string, (int) $status); } +} +if (!function_exists('mb_http_input')) { + function mb_http_input(?string $type = null): array|string|false { return p\Mbstring::mb_http_input($type); } +} + +if (!function_exists('mb_convert_variables')) { + function mb_convert_variables(?string $to_encoding, array|string|null $from_encoding, mixed &$var, mixed &...$vars): string|false { return p\Mbstring::mb_convert_variables((string) $to_encoding, $from_encoding ?? '', $var, ...$vars); } +} + +if (!function_exists('mb_ord')) { + function mb_ord(?string $string, ?string $encoding = null): int|false { return p\Mbstring::mb_ord((string) $string, $encoding); } +} +if (!function_exists('mb_chr')) { + function mb_chr(?int $codepoint, ?string $encoding = null): string|false { return p\Mbstring::mb_chr((int) $codepoint, $encoding); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub(?string $string, ?string $encoding = null): string { $encoding ??= mb_internal_encoding(); return mb_convert_encoding((string) $string, $encoding, $encoding); } +} +if (!function_exists('mb_str_split')) { + function mb_str_split(?string $string, ?int $length = 1, ?string $encoding = null): array { return p\Mbstring::mb_str_split((string) $string, (int) $length, $encoding); } +} + +if (!function_exists('mb_str_pad')) { + function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Mbstring::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); } +} + +if (extension_loaded('mbstring')) { + return; +} + +if (!defined('MB_CASE_UPPER')) { + define('MB_CASE_UPPER', 0); +} +if (!defined('MB_CASE_LOWER')) { + define('MB_CASE_LOWER', 1); +} +if (!defined('MB_CASE_TITLE')) { + define('MB_CASE_TITLE', 2); +} diff --git a/vendor/symfony/polyfill-mbstring/composer.json b/vendor/symfony/polyfill-mbstring/composer.json new file mode 100644 index 00000000..bd99d4b9 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/composer.json @@ -0,0 +1,38 @@ +{ + "name": "symfony/polyfill-mbstring", + "type": "library", + "description": "Symfony polyfill for the Mbstring extension", + "keywords": ["polyfill", "shim", "compatibility", "portable", "mbstring"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Mbstring\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/vendor/symfony/polyfill-php80/LICENSE b/vendor/symfony/polyfill-php80/LICENSE new file mode 100644 index 00000000..0ed3a246 --- /dev/null +++ b/vendor/symfony/polyfill-php80/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/polyfill-php80/Php80.php b/vendor/symfony/polyfill-php80/Php80.php new file mode 100644 index 00000000..362dd1a9 --- /dev/null +++ b/vendor/symfony/polyfill-php80/Php80.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php80; + +/** + * @author Ion Bazan + * @author Nico Oelgart + * @author Nicolas Grekas + * + * @internal + */ +final class Php80 +{ + public static function fdiv(float $dividend, float $divisor): float + { + return @($dividend / $divisor); + } + + public static function get_debug_type($value): string + { + switch (true) { + case null === $value: return 'null'; + case \is_bool($value): return 'bool'; + case \is_string($value): return 'string'; + case \is_array($value): return 'array'; + case \is_int($value): return 'int'; + case \is_float($value): return 'float'; + case \is_object($value): break; + case $value instanceof \__PHP_Incomplete_Class: return '__PHP_Incomplete_Class'; + default: + if (null === $type = @get_resource_type($value)) { + return 'unknown'; + } + + if ('Unknown' === $type) { + $type = 'closed'; + } + + return "resource ($type)"; + } + + $class = \get_class($value); + + if (false === strpos($class, '@')) { + return $class; + } + + return (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous'; + } + + public static function get_resource_id($res): int + { + if (!\is_resource($res) && null === @get_resource_type($res)) { + throw new \TypeError(sprintf('Argument 1 passed to get_resource_id() must be of the type resource, %s given', get_debug_type($res))); + } + + return (int) $res; + } + + public static function preg_last_error_msg(): string + { + switch (preg_last_error()) { + case \PREG_INTERNAL_ERROR: + return 'Internal error'; + case \PREG_BAD_UTF8_ERROR: + return 'Malformed UTF-8 characters, possibly incorrectly encoded'; + case \PREG_BAD_UTF8_OFFSET_ERROR: + return 'The offset did not correspond to the beginning of a valid UTF-8 code point'; + case \PREG_BACKTRACK_LIMIT_ERROR: + return 'Backtrack limit exhausted'; + case \PREG_RECURSION_LIMIT_ERROR: + return 'Recursion limit exhausted'; + case \PREG_JIT_STACKLIMIT_ERROR: + return 'JIT stack limit exhausted'; + case \PREG_NO_ERROR: + return 'No error'; + default: + return 'Unknown error'; + } + } + + public static function str_contains(string $haystack, string $needle): bool + { + return '' === $needle || false !== strpos($haystack, $needle); + } + + public static function str_starts_with(string $haystack, string $needle): bool + { + return 0 === strncmp($haystack, $needle, \strlen($needle)); + } + + public static function str_ends_with(string $haystack, string $needle): bool + { + if ('' === $needle || $needle === $haystack) { + return true; + } + + if ('' === $haystack) { + return false; + } + + $needleLength = \strlen($needle); + + return $needleLength <= \strlen($haystack) && 0 === substr_compare($haystack, $needle, -$needleLength); + } +} diff --git a/vendor/symfony/polyfill-php80/PhpToken.php b/vendor/symfony/polyfill-php80/PhpToken.php new file mode 100644 index 00000000..fe6e6910 --- /dev/null +++ b/vendor/symfony/polyfill-php80/PhpToken.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php80; + +/** + * @author Fedonyuk Anton + * + * @internal + */ +class PhpToken implements \Stringable +{ + /** + * @var int + */ + public $id; + + /** + * @var string + */ + public $text; + + /** + * @var int + */ + public $line; + + /** + * @var int + */ + public $pos; + + public function __construct(int $id, string $text, int $line = -1, int $position = -1) + { + $this->id = $id; + $this->text = $text; + $this->line = $line; + $this->pos = $position; + } + + public function getTokenName(): ?string + { + if ('UNKNOWN' === $name = token_name($this->id)) { + $name = \strlen($this->text) > 1 || \ord($this->text) < 32 ? null : $this->text; + } + + return $name; + } + + /** + * @param int|string|array $kind + */ + public function is($kind): bool + { + foreach ((array) $kind as $value) { + if (\in_array($value, [$this->id, $this->text], true)) { + return true; + } + } + + return false; + } + + public function isIgnorable(): bool + { + return \in_array($this->id, [\T_WHITESPACE, \T_COMMENT, \T_DOC_COMMENT, \T_OPEN_TAG], true); + } + + public function __toString(): string + { + return (string) $this->text; + } + + /** + * @return static[] + */ + public static function tokenize(string $code, int $flags = 0): array + { + $line = 1; + $position = 0; + $tokens = token_get_all($code, $flags); + foreach ($tokens as $index => $token) { + if (\is_string($token)) { + $id = \ord($token); + $text = $token; + } else { + [$id, $text, $line] = $token; + } + $tokens[$index] = new static($id, $text, $line, $position); + $position += \strlen($text); + } + + return $tokens; + } +} diff --git a/vendor/symfony/polyfill-php80/README.md b/vendor/symfony/polyfill-php80/README.md new file mode 100644 index 00000000..3816c559 --- /dev/null +++ b/vendor/symfony/polyfill-php80/README.md @@ -0,0 +1,25 @@ +Symfony Polyfill / Php80 +======================== + +This component provides features added to PHP 8.0 core: + +- [`Stringable`](https://php.net/stringable) interface +- [`fdiv`](https://php.net/fdiv) +- [`ValueError`](https://php.net/valueerror) class +- [`UnhandledMatchError`](https://php.net/unhandledmatcherror) class +- `FILTER_VALIDATE_BOOL` constant +- [`get_debug_type`](https://php.net/get_debug_type) +- [`PhpToken`](https://php.net/phptoken) class +- [`preg_last_error_msg`](https://php.net/preg_last_error_msg) +- [`str_contains`](https://php.net/str_contains) +- [`str_starts_with`](https://php.net/str_starts_with) +- [`str_ends_with`](https://php.net/str_ends_with) +- [`get_resource_id`](https://php.net/get_resource_id) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php b/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php new file mode 100644 index 00000000..2b955423 --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#[Attribute(Attribute::TARGET_CLASS)] +final class Attribute +{ + public const TARGET_CLASS = 1; + public const TARGET_FUNCTION = 2; + public const TARGET_METHOD = 4; + public const TARGET_PROPERTY = 8; + public const TARGET_CLASS_CONSTANT = 16; + public const TARGET_PARAMETER = 32; + public const TARGET_ALL = 63; + public const IS_REPEATABLE = 64; + + /** @var int */ + public $flags; + + public function __construct(int $flags = self::TARGET_ALL) + { + $this->flags = $flags; + } +} diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php b/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php new file mode 100644 index 00000000..bd1212f6 --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000 && extension_loaded('tokenizer')) { + class PhpToken extends Symfony\Polyfill\Php80\PhpToken + { + } +} diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php b/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php new file mode 100644 index 00000000..7c62d750 --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000) { + interface Stringable + { + /** + * @return string + */ + public function __toString(); + } +} diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php b/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php new file mode 100644 index 00000000..01c6c6c8 --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000) { + class UnhandledMatchError extends Error + { + } +} diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php b/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php new file mode 100644 index 00000000..783dbc28 --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000) { + class ValueError extends Error + { + } +} diff --git a/vendor/symfony/polyfill-php80/bootstrap.php b/vendor/symfony/polyfill-php80/bootstrap.php new file mode 100644 index 00000000..e5f7dbc1 --- /dev/null +++ b/vendor/symfony/polyfill-php80/bootstrap.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php80 as p; + +if (\PHP_VERSION_ID >= 80000) { + return; +} + +if (!defined('FILTER_VALIDATE_BOOL') && defined('FILTER_VALIDATE_BOOLEAN')) { + define('FILTER_VALIDATE_BOOL', \FILTER_VALIDATE_BOOLEAN); +} + +if (!function_exists('fdiv')) { + function fdiv(float $num1, float $num2): float { return p\Php80::fdiv($num1, $num2); } +} +if (!function_exists('preg_last_error_msg')) { + function preg_last_error_msg(): string { return p\Php80::preg_last_error_msg(); } +} +if (!function_exists('str_contains')) { + function str_contains(?string $haystack, ?string $needle): bool { return p\Php80::str_contains($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('str_starts_with')) { + function str_starts_with(?string $haystack, ?string $needle): bool { return p\Php80::str_starts_with($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('str_ends_with')) { + function str_ends_with(?string $haystack, ?string $needle): bool { return p\Php80::str_ends_with($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('get_debug_type')) { + function get_debug_type($value): string { return p\Php80::get_debug_type($value); } +} +if (!function_exists('get_resource_id')) { + function get_resource_id($resource): int { return p\Php80::get_resource_id($resource); } +} diff --git a/vendor/symfony/polyfill-php80/composer.json b/vendor/symfony/polyfill-php80/composer.json new file mode 100644 index 00000000..46ccde20 --- /dev/null +++ b/vendor/symfony/polyfill-php80/composer.json @@ -0,0 +1,37 @@ +{ + "name": "symfony/polyfill-php80", + "type": "library", + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "keywords": ["polyfill", "shim", "compatibility", "portable"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php80\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/vendor/symfony/polyfill-php81/LICENSE b/vendor/symfony/polyfill-php81/LICENSE new file mode 100644 index 00000000..99c6bdf3 --- /dev/null +++ b/vendor/symfony/polyfill-php81/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2021-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/polyfill-php81/Php81.php b/vendor/symfony/polyfill-php81/Php81.php new file mode 100644 index 00000000..f0507b76 --- /dev/null +++ b/vendor/symfony/polyfill-php81/Php81.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php81; + +/** + * @author Nicolas Grekas + * + * @internal + */ +final class Php81 +{ + public static function array_is_list(array $array): bool + { + if ([] === $array || $array === array_values($array)) { + return true; + } + + $nextKey = -1; + + foreach ($array as $k => $v) { + if ($k !== ++$nextKey) { + return false; + } + } + + return true; + } +} diff --git a/vendor/symfony/polyfill-php81/README.md b/vendor/symfony/polyfill-php81/README.md new file mode 100644 index 00000000..c07ef782 --- /dev/null +++ b/vendor/symfony/polyfill-php81/README.md @@ -0,0 +1,18 @@ +Symfony Polyfill / Php81 +======================== + +This component provides features added to PHP 8.1 core: + +- [`array_is_list`](https://php.net/array_is_list) +- [`enum_exists`](https://php.net/enum-exists) +- [`MYSQLI_REFRESH_REPLICA`](https://php.net/mysqli.constants#constantmysqli-refresh-replica) constant +- [`ReturnTypeWillChange`](https://wiki.php.net/rfc/internal_method_return_types) +- [`CURLStringFile`](https://php.net/CURLStringFile) (but only if PHP >= 7.4 is used) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php b/vendor/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php new file mode 100644 index 00000000..eb5952ee --- /dev/null +++ b/vendor/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID >= 70400 && extension_loaded('curl')) { + /** + * @property string $data + */ + class CURLStringFile extends CURLFile + { + private $data; + + public function __construct(string $data, string $postname, string $mime = 'application/octet-stream') + { + $this->data = $data; + parent::__construct('data://application/octet-stream;base64,'.base64_encode($data), $mime, $postname); + } + + public function __set(string $name, $value): void + { + if ('data' !== $name) { + $this->$name = $value; + + return; + } + + if (is_object($value) ? !method_exists($value, '__toString') : !is_scalar($value)) { + throw new \TypeError('Cannot assign '.gettype($value).' to property CURLStringFile::$data of type string'); + } + + $this->name = 'data://application/octet-stream;base64,'.base64_encode($value); + } + + public function __isset(string $name): bool + { + return isset($this->$name); + } + + public function &__get(string $name) + { + return $this->$name; + } + } +} diff --git a/vendor/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php b/vendor/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php new file mode 100644 index 00000000..cb7720a8 --- /dev/null +++ b/vendor/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80100) { + #[Attribute(Attribute::TARGET_METHOD)] + final class ReturnTypeWillChange + { + public function __construct() + { + } + } +} diff --git a/vendor/symfony/polyfill-php81/bootstrap.php b/vendor/symfony/polyfill-php81/bootstrap.php new file mode 100644 index 00000000..9f872e02 --- /dev/null +++ b/vendor/symfony/polyfill-php81/bootstrap.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php81 as p; + +if (\PHP_VERSION_ID >= 80100) { + return; +} + +if (defined('MYSQLI_REFRESH_SLAVE') && !defined('MYSQLI_REFRESH_REPLICA')) { + define('MYSQLI_REFRESH_REPLICA', 64); +} + +if (!function_exists('array_is_list')) { + function array_is_list(array $array): bool { return p\Php81::array_is_list($array); } +} + +if (!function_exists('enum_exists')) { + function enum_exists(string $enum, bool $autoload = true): bool { return $autoload && class_exists($enum) && false; } +} diff --git a/vendor/symfony/polyfill-php81/composer.json b/vendor/symfony/polyfill-php81/composer.json new file mode 100644 index 00000000..381af79a --- /dev/null +++ b/vendor/symfony/polyfill-php81/composer.json @@ -0,0 +1,33 @@ +{ + "name": "symfony/polyfill-php81", + "type": "library", + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "keywords": ["polyfill", "shim", "compatibility", "portable"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php81\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/vendor/symfony/process/CHANGELOG.md b/vendor/symfony/process/CHANGELOG.md new file mode 100644 index 00000000..e26819b5 --- /dev/null +++ b/vendor/symfony/process/CHANGELOG.md @@ -0,0 +1,124 @@ +CHANGELOG +========= + +6.4 +--- + + * Add `PhpSubprocess` to handle PHP subprocesses that take over the + configuration from their parent + * Add `RunProcessMessage` and `RunProcessMessageHandler` + * Support using `Process::findExecutable()` independently of `open_basedir` + +5.2.0 +----- + + * added `Process::setOptions()` to set `Process` specific options + * added option `create_new_console` to allow a subprocess to continue + to run after the main script exited, both on Linux and on Windows + +5.1.0 +----- + + * added `Process::getStartTime()` to retrieve the start time of the process as float + +5.0.0 +----- + + * removed `Process::inheritEnvironmentVariables()` + * removed `PhpProcess::setPhpBinary()` + * `Process` must be instantiated with a command array, use `Process::fromShellCommandline()` when the command should be parsed by the shell + * removed `Process::setCommandLine()` + +4.4.0 +----- + + * deprecated `Process::inheritEnvironmentVariables()`: env variables are always inherited. + * added `Process::getLastOutputTime()` method + +4.2.0 +----- + + * added the `Process::fromShellCommandline()` to run commands in a shell wrapper + * deprecated passing a command as string when creating a `Process` instance + * deprecated the `Process::setCommandline()` and the `PhpProcess::setPhpBinary()` methods + * added the `Process::waitUntil()` method to wait for the process only for a + specific output, then continue the normal execution of your application + +4.1.0 +----- + + * added the `Process::isTtySupported()` method that allows to check for TTY support + * made `PhpExecutableFinder` look for the `PHP_BINARY` env var when searching the php binary + * added the `ProcessSignaledException` class to properly catch signaled process errors + +4.0.0 +----- + + * environment variables will always be inherited + * added a second `array $env = []` argument to the `start()`, `run()`, + `mustRun()`, and `restart()` methods of the `Process` class + * added a second `array $env = []` argument to the `start()` method of the + `PhpProcess` class + * the `ProcessUtils::escapeArgument()` method has been removed + * the `areEnvironmentVariablesInherited()`, `getOptions()`, and `setOptions()` + methods of the `Process` class have been removed + * support for passing `proc_open()` options has been removed + * removed the `ProcessBuilder` class, use the `Process` class instead + * removed the `getEnhanceWindowsCompatibility()` and `setEnhanceWindowsCompatibility()` methods of the `Process` class + * passing a not existing working directory to the constructor of the `Symfony\Component\Process\Process` class is not + supported anymore + +3.4.0 +----- + + * deprecated the ProcessBuilder class + * deprecated calling `Process::start()` without setting a valid working directory beforehand (via `setWorkingDirectory()` or constructor) + +3.3.0 +----- + + * added command line arrays in the `Process` class + * added `$env` argument to `Process::start()`, `run()`, `mustRun()` and `restart()` methods + * deprecated the `ProcessUtils::escapeArgument()` method + * deprecated not inheriting environment variables + * deprecated configuring `proc_open()` options + * deprecated configuring enhanced Windows compatibility + * deprecated configuring enhanced sigchild compatibility + +2.5.0 +----- + + * added support for PTY mode + * added the convenience method "mustRun" + * deprecation: Process::setStdin() is deprecated in favor of Process::setInput() + * deprecation: Process::getStdin() is deprecated in favor of Process::getInput() + * deprecation: Process::setInput() and ProcessBuilder::setInput() do not accept non-scalar types + +2.4.0 +----- + + * added the ability to define an idle timeout + +2.3.0 +----- + + * added ProcessUtils::escapeArgument() to fix the bug in escapeshellarg() function on Windows + * added Process::signal() + * added Process::getPid() + * added support for a TTY mode + +2.2.0 +----- + + * added ProcessBuilder::setArguments() to reset the arguments on a builder + * added a way to retrieve the standard and error output incrementally + * added Process:restart() + +2.1.0 +----- + + * added support for non-blocking processes (start(), wait(), isRunning(), stop()) + * enhanced Windows compatibility + * added Process::getExitCodeText() that returns a string representation for + the exit code returned by the process + * added ProcessBuilder diff --git a/vendor/symfony/process/Exception/ExceptionInterface.php b/vendor/symfony/process/Exception/ExceptionInterface.php new file mode 100644 index 00000000..bd4a6040 --- /dev/null +++ b/vendor/symfony/process/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +/** + * Marker Interface for the Process Component. + * + * @author Johannes M. Schmitt + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/process/Exception/InvalidArgumentException.php b/vendor/symfony/process/Exception/InvalidArgumentException.php new file mode 100644 index 00000000..926ee211 --- /dev/null +++ b/vendor/symfony/process/Exception/InvalidArgumentException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +/** + * InvalidArgumentException for the Process Component. + * + * @author Romain Neutron + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/process/Exception/LogicException.php b/vendor/symfony/process/Exception/LogicException.php new file mode 100644 index 00000000..be3d490d --- /dev/null +++ b/vendor/symfony/process/Exception/LogicException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +/** + * LogicException for the Process Component. + * + * @author Romain Neutron + */ +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/process/Exception/ProcessFailedException.php b/vendor/symfony/process/Exception/ProcessFailedException.php new file mode 100644 index 00000000..499809ee --- /dev/null +++ b/vendor/symfony/process/Exception/ProcessFailedException.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +use Symfony\Component\Process\Process; + +/** + * Exception for failed processes. + * + * @author Johannes M. Schmitt + */ +class ProcessFailedException extends RuntimeException +{ + private Process $process; + + public function __construct(Process $process) + { + if ($process->isSuccessful()) { + throw new InvalidArgumentException('Expected a failed process, but the given process was successful.'); + } + + $error = sprintf('The command "%s" failed.'."\n\nExit Code: %s(%s)\n\nWorking directory: %s", + $process->getCommandLine(), + $process->getExitCode(), + $process->getExitCodeText(), + $process->getWorkingDirectory() + ); + + if (!$process->isOutputDisabled()) { + $error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s", + $process->getOutput(), + $process->getErrorOutput() + ); + } + + parent::__construct($error); + + $this->process = $process; + } + + public function getProcess(): Process + { + return $this->process; + } +} diff --git a/vendor/symfony/process/Exception/ProcessSignaledException.php b/vendor/symfony/process/Exception/ProcessSignaledException.php new file mode 100644 index 00000000..0fed8ac3 --- /dev/null +++ b/vendor/symfony/process/Exception/ProcessSignaledException.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +use Symfony\Component\Process\Process; + +/** + * Exception that is thrown when a process has been signaled. + * + * @author Sullivan Senechal + */ +final class ProcessSignaledException extends RuntimeException +{ + private Process $process; + + public function __construct(Process $process) + { + $this->process = $process; + + parent::__construct(sprintf('The process has been signaled with signal "%s".', $process->getTermSignal())); + } + + public function getProcess(): Process + { + return $this->process; + } + + public function getSignal(): int + { + return $this->getProcess()->getTermSignal(); + } +} diff --git a/vendor/symfony/process/Exception/ProcessTimedOutException.php b/vendor/symfony/process/Exception/ProcessTimedOutException.php new file mode 100644 index 00000000..252e1112 --- /dev/null +++ b/vendor/symfony/process/Exception/ProcessTimedOutException.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +use Symfony\Component\Process\Process; + +/** + * Exception that is thrown when a process times out. + * + * @author Johannes M. Schmitt + */ +class ProcessTimedOutException extends RuntimeException +{ + public const TYPE_GENERAL = 1; + public const TYPE_IDLE = 2; + + private Process $process; + private int $timeoutType; + + public function __construct(Process $process, int $timeoutType) + { + $this->process = $process; + $this->timeoutType = $timeoutType; + + parent::__construct(sprintf( + 'The process "%s" exceeded the timeout of %s seconds.', + $process->getCommandLine(), + $this->getExceededTimeout() + )); + } + + public function getProcess(): Process + { + return $this->process; + } + + public function isGeneralTimeout(): bool + { + return self::TYPE_GENERAL === $this->timeoutType; + } + + public function isIdleTimeout(): bool + { + return self::TYPE_IDLE === $this->timeoutType; + } + + public function getExceededTimeout(): ?float + { + return match ($this->timeoutType) { + self::TYPE_GENERAL => $this->process->getTimeout(), + self::TYPE_IDLE => $this->process->getIdleTimeout(), + default => throw new \LogicException(sprintf('Unknown timeout type "%d".', $this->timeoutType)), + }; + } +} diff --git a/vendor/symfony/process/Exception/RunProcessFailedException.php b/vendor/symfony/process/Exception/RunProcessFailedException.php new file mode 100644 index 00000000..e7219d35 --- /dev/null +++ b/vendor/symfony/process/Exception/RunProcessFailedException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +use Symfony\Component\Process\Messenger\RunProcessContext; + +/** + * @author Kevin Bond + */ +final class RunProcessFailedException extends RuntimeException +{ + public function __construct(ProcessFailedException $exception, public readonly RunProcessContext $context) + { + parent::__construct($exception->getMessage(), $exception->getCode()); + } +} diff --git a/vendor/symfony/process/Exception/RuntimeException.php b/vendor/symfony/process/Exception/RuntimeException.php new file mode 100644 index 00000000..adead253 --- /dev/null +++ b/vendor/symfony/process/Exception/RuntimeException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +/** + * RuntimeException for the Process Component. + * + * @author Johannes M. Schmitt + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/process/ExecutableFinder.php b/vendor/symfony/process/ExecutableFinder.php new file mode 100644 index 00000000..ceb7a558 --- /dev/null +++ b/vendor/symfony/process/ExecutableFinder.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +/** + * Generic executable finder. + * + * @author Fabien Potencier + * @author Johannes M. Schmitt + */ +class ExecutableFinder +{ + private array $suffixes = ['.exe', '.bat', '.cmd', '.com']; + + /** + * Replaces default suffixes of executable. + */ + public function setSuffixes(array $suffixes): void + { + $this->suffixes = $suffixes; + } + + /** + * Adds new possible suffix to check for executable. + */ + public function addSuffix(string $suffix): void + { + $this->suffixes[] = $suffix; + } + + /** + * Finds an executable by name. + * + * @param string $name The executable name (without the extension) + * @param string|null $default The default to return if no executable is found + * @param array $extraDirs Additional dirs to check into + */ + public function find(string $name, ?string $default = null, array $extraDirs = []): ?string + { + $dirs = array_merge( + explode(\PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')), + $extraDirs + ); + + $suffixes = ['']; + if ('\\' === \DIRECTORY_SEPARATOR) { + $pathExt = getenv('PATHEXT'); + $suffixes = array_merge($pathExt ? explode(\PATH_SEPARATOR, $pathExt) : $this->suffixes, $suffixes); + } + foreach ($suffixes as $suffix) { + foreach ($dirs as $dir) { + if (@is_file($file = $dir.\DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === \DIRECTORY_SEPARATOR || @is_executable($file))) { + return $file; + } + + if (!@is_dir($dir) && basename($dir) === $name.$suffix && @is_executable($dir)) { + return $dir; + } + } + } + + $command = '\\' === \DIRECTORY_SEPARATOR ? 'where' : 'command -v --'; + if (\function_exists('exec') && ($executablePath = strtok(@exec($command.' '.escapeshellarg($name)), \PHP_EOL)) && @is_executable($executablePath)) { + return $executablePath; + } + + return $default; + } +} diff --git a/vendor/symfony/process/InputStream.php b/vendor/symfony/process/InputStream.php new file mode 100644 index 00000000..cd91029e --- /dev/null +++ b/vendor/symfony/process/InputStream.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +use Symfony\Component\Process\Exception\RuntimeException; + +/** + * Provides a way to continuously write to the input of a Process until the InputStream is closed. + * + * @author Nicolas Grekas + * + * @implements \IteratorAggregate + */ +class InputStream implements \IteratorAggregate +{ + private ?\Closure $onEmpty = null; + private array $input = []; + private bool $open = true; + + /** + * Sets a callback that is called when the write buffer becomes empty. + */ + public function onEmpty(?callable $onEmpty = null): void + { + $this->onEmpty = null !== $onEmpty ? $onEmpty(...) : null; + } + + /** + * Appends an input to the write buffer. + * + * @param resource|string|int|float|bool|\Traversable|null $input The input to append as scalar, + * stream resource or \Traversable + */ + public function write(mixed $input): void + { + if (null === $input) { + return; + } + if ($this->isClosed()) { + throw new RuntimeException(sprintf('"%s" is closed.', static::class)); + } + $this->input[] = ProcessUtils::validateInput(__METHOD__, $input); + } + + /** + * Closes the write buffer. + */ + public function close(): void + { + $this->open = false; + } + + /** + * Tells whether the write buffer is closed or not. + */ + public function isClosed(): bool + { + return !$this->open; + } + + public function getIterator(): \Traversable + { + $this->open = true; + + while ($this->open || $this->input) { + if (!$this->input) { + yield ''; + continue; + } + $current = array_shift($this->input); + + if ($current instanceof \Iterator) { + yield from $current; + } else { + yield $current; + } + if (!$this->input && $this->open && null !== $onEmpty = $this->onEmpty) { + $this->write($onEmpty($this)); + } + } + } +} diff --git a/vendor/symfony/process/LICENSE b/vendor/symfony/process/LICENSE new file mode 100644 index 00000000..0138f8f0 --- /dev/null +++ b/vendor/symfony/process/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/process/Messenger/RunProcessContext.php b/vendor/symfony/process/Messenger/RunProcessContext.php new file mode 100644 index 00000000..b5ade072 --- /dev/null +++ b/vendor/symfony/process/Messenger/RunProcessContext.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Messenger; + +use Symfony\Component\Process\Process; + +/** + * @author Kevin Bond + */ +final class RunProcessContext +{ + public readonly ?int $exitCode; + public readonly ?string $output; + public readonly ?string $errorOutput; + + public function __construct( + public readonly RunProcessMessage $message, + Process $process, + ) { + $this->exitCode = $process->getExitCode(); + $this->output = $process->isOutputDisabled() ? null : $process->getOutput(); + $this->errorOutput = $process->isOutputDisabled() ? null : $process->getErrorOutput(); + } +} diff --git a/vendor/symfony/process/Messenger/RunProcessMessage.php b/vendor/symfony/process/Messenger/RunProcessMessage.php new file mode 100644 index 00000000..b2c33fe3 --- /dev/null +++ b/vendor/symfony/process/Messenger/RunProcessMessage.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Messenger; + +/** + * @author Kevin Bond + */ +class RunProcessMessage implements \Stringable +{ + public function __construct( + public readonly array $command, + public readonly ?string $cwd = null, + public readonly ?array $env = null, + public readonly mixed $input = null, + public readonly ?float $timeout = 60.0, + ) { + } + + public function __toString(): string + { + return implode(' ', $this->command); + } +} diff --git a/vendor/symfony/process/Messenger/RunProcessMessageHandler.php b/vendor/symfony/process/Messenger/RunProcessMessageHandler.php new file mode 100644 index 00000000..41c1934c --- /dev/null +++ b/vendor/symfony/process/Messenger/RunProcessMessageHandler.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Messenger; + +use Symfony\Component\Process\Exception\ProcessFailedException; +use Symfony\Component\Process\Exception\RunProcessFailedException; +use Symfony\Component\Process\Process; + +/** + * @author Kevin Bond + */ +final class RunProcessMessageHandler +{ + public function __invoke(RunProcessMessage $message): RunProcessContext + { + $process = new Process($message->command, $message->cwd, $message->env, $message->input, $message->timeout); + + try { + return new RunProcessContext($message, $process->mustRun()); + } catch (ProcessFailedException $e) { + throw new RunProcessFailedException($e, new RunProcessContext($message, $e->getProcess())); + } + } +} diff --git a/vendor/symfony/process/PhpExecutableFinder.php b/vendor/symfony/process/PhpExecutableFinder.php new file mode 100644 index 00000000..4a882e0f --- /dev/null +++ b/vendor/symfony/process/PhpExecutableFinder.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +/** + * An executable finder specifically designed for the PHP executable. + * + * @author Fabien Potencier + * @author Johannes M. Schmitt + */ +class PhpExecutableFinder +{ + private ExecutableFinder $executableFinder; + + public function __construct() + { + $this->executableFinder = new ExecutableFinder(); + } + + /** + * Finds The PHP executable. + */ + public function find(bool $includeArgs = true): string|false + { + if ($php = getenv('PHP_BINARY')) { + if (!is_executable($php)) { + $command = '\\' === \DIRECTORY_SEPARATOR ? 'where' : 'command -v --'; + if (\function_exists('exec') && $php = strtok(exec($command.' '.escapeshellarg($php)), \PHP_EOL)) { + if (!is_executable($php)) { + return false; + } + } else { + return false; + } + } + + if (@is_dir($php)) { + return false; + } + + return $php; + } + + $args = $this->findArguments(); + $args = $includeArgs && $args ? ' '.implode(' ', $args) : ''; + + // PHP_BINARY return the current sapi executable + if (\PHP_BINARY && \in_array(\PHP_SAPI, ['cli', 'cli-server', 'phpdbg'], true)) { + return \PHP_BINARY.$args; + } + + if ($php = getenv('PHP_PATH')) { + if (!@is_executable($php) || @is_dir($php)) { + return false; + } + + return $php; + } + + if ($php = getenv('PHP_PEAR_PHP_BIN')) { + if (@is_executable($php) && !@is_dir($php)) { + return $php; + } + } + + if (@is_executable($php = \PHP_BINDIR.('\\' === \DIRECTORY_SEPARATOR ? '\\php.exe' : '/php')) && !@is_dir($php)) { + return $php; + } + + $dirs = [\PHP_BINDIR]; + if ('\\' === \DIRECTORY_SEPARATOR) { + $dirs[] = 'C:\xampp\php\\'; + } + + return $this->executableFinder->find('php', false, $dirs); + } + + /** + * Finds the PHP executable arguments. + */ + public function findArguments(): array + { + $arguments = []; + if ('phpdbg' === \PHP_SAPI) { + $arguments[] = '-qrr'; + } + + return $arguments; + } +} diff --git a/vendor/symfony/process/PhpProcess.php b/vendor/symfony/process/PhpProcess.php new file mode 100644 index 00000000..01d88954 --- /dev/null +++ b/vendor/symfony/process/PhpProcess.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +use Symfony\Component\Process\Exception\LogicException; +use Symfony\Component\Process\Exception\RuntimeException; + +/** + * PhpProcess runs a PHP script in an independent process. + * + * $p = new PhpProcess(''); + * $p->run(); + * print $p->getOutput()."\n"; + * + * @author Fabien Potencier + */ +class PhpProcess extends Process +{ + /** + * @param string $script The PHP script to run (as a string) + * @param string|null $cwd The working directory or null to use the working dir of the current PHP process + * @param array|null $env The environment variables or null to use the same environment as the current PHP process + * @param int $timeout The timeout in seconds + * @param array|null $php Path to the PHP binary to use with any additional arguments + */ + public function __construct(string $script, ?string $cwd = null, ?array $env = null, int $timeout = 60, ?array $php = null) + { + if (null === $php) { + $executableFinder = new PhpExecutableFinder(); + $php = $executableFinder->find(false); + $php = false === $php ? null : array_merge([$php], $executableFinder->findArguments()); + } + if ('phpdbg' === \PHP_SAPI) { + $file = tempnam(sys_get_temp_dir(), 'dbg'); + file_put_contents($file, $script); + register_shutdown_function('unlink', $file); + $php[] = $file; + $script = null; + } + + parent::__construct($php, $cwd, $env, $script, $timeout); + } + + public static function fromShellCommandline(string $command, ?string $cwd = null, ?array $env = null, mixed $input = null, ?float $timeout = 60): static + { + throw new LogicException(sprintf('The "%s()" method cannot be called when using "%s".', __METHOD__, self::class)); + } + + public function start(?callable $callback = null, array $env = []): void + { + if (null === $this->getCommandLine()) { + throw new RuntimeException('Unable to find the PHP executable.'); + } + + parent::start($callback, $env); + } +} diff --git a/vendor/symfony/process/PhpSubprocess.php b/vendor/symfony/process/PhpSubprocess.php new file mode 100644 index 00000000..a97f8b26 --- /dev/null +++ b/vendor/symfony/process/PhpSubprocess.php @@ -0,0 +1,164 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +use Symfony\Component\Process\Exception\LogicException; +use Symfony\Component\Process\Exception\RuntimeException; + +/** + * PhpSubprocess runs a PHP command as a subprocess while keeping the original php.ini settings. + * + * For this, it generates a temporary php.ini file taking over all the current settings and disables + * loading additional .ini files. Basically, your command gets prefixed using "php -n -c /tmp/temp.ini". + * + * Given your php.ini contains "memory_limit=-1" and you have a "MemoryTest.php" with the following content: + * + * run(); + * print $p->getOutput()."\n"; + * + * This will output "string(2) "-1", because the process is started with the default php.ini settings. + * + * $p = new PhpSubprocess(['MemoryTest.php'], null, null, 60, ['php', '-d', 'memory_limit=256M']); + * $p->run(); + * print $p->getOutput()."\n"; + * + * This will output "string(4) "256M"", because the process is started with the temporarily created php.ini settings. + * + * @author Yanick Witschi + * @author Partially copied and heavily inspired from composer/xdebug-handler by John Stevenson + */ +class PhpSubprocess extends Process +{ + /** + * @param array $command The command to run and its arguments listed as separate entries. They will automatically + * get prefixed with the PHP binary + * @param string|null $cwd The working directory or null to use the working dir of the current PHP process + * @param array|null $env The environment variables or null to use the same environment as the current PHP process + * @param int $timeout The timeout in seconds + * @param array|null $php Path to the PHP binary to use with any additional arguments + */ + public function __construct(array $command, ?string $cwd = null, ?array $env = null, int $timeout = 60, ?array $php = null) + { + if (null === $php) { + $executableFinder = new PhpExecutableFinder(); + $php = $executableFinder->find(false); + $php = false === $php ? null : array_merge([$php], $executableFinder->findArguments()); + } + + if (null === $php) { + throw new RuntimeException('Unable to find PHP binary.'); + } + + $tmpIni = $this->writeTmpIni($this->getAllIniFiles(), sys_get_temp_dir()); + + $php = array_merge($php, ['-n', '-c', $tmpIni]); + register_shutdown_function('unlink', $tmpIni); + + $command = array_merge($php, $command); + + parent::__construct($command, $cwd, $env, null, $timeout); + } + + public static function fromShellCommandline(string $command, ?string $cwd = null, ?array $env = null, mixed $input = null, ?float $timeout = 60): static + { + throw new LogicException(sprintf('The "%s()" method cannot be called when using "%s".', __METHOD__, self::class)); + } + + public function start(?callable $callback = null, array $env = []): void + { + if (null === $this->getCommandLine()) { + throw new RuntimeException('Unable to find the PHP executable.'); + } + + parent::start($callback, $env); + } + + private function writeTmpIni(array $iniFiles, string $tmpDir): string + { + if (false === $tmpfile = @tempnam($tmpDir, '')) { + throw new RuntimeException('Unable to create temporary ini file.'); + } + + // $iniFiles has at least one item and it may be empty + if ('' === $iniFiles[0]) { + array_shift($iniFiles); + } + + $content = ''; + + foreach ($iniFiles as $file) { + // Check for inaccessible ini files + if (($data = @file_get_contents($file)) === false) { + throw new RuntimeException('Unable to read ini: '.$file); + } + // Check and remove directives after HOST and PATH sections + if (preg_match('/^\s*\[(?:PATH|HOST)\s*=/mi', $data, $matches)) { + $data = substr($data, 0, $matches[0][1]); + } + + $content .= $data."\n"; + } + + // Merge loaded settings into our ini content, if it is valid + $config = parse_ini_string($content); + $loaded = ini_get_all(null, false); + + if (false === $config || false === $loaded) { + throw new RuntimeException('Unable to parse ini data.'); + } + + $content .= $this->mergeLoadedConfig($loaded, $config); + + // Work-around for https://bugs.php.net/bug.php?id=75932 + $content .= "opcache.enable_cli=0\n"; + + if (false === @file_put_contents($tmpfile, $content)) { + throw new RuntimeException('Unable to write temporary ini file.'); + } + + return $tmpfile; + } + + private function mergeLoadedConfig(array $loadedConfig, array $iniConfig): string + { + $content = ''; + + foreach ($loadedConfig as $name => $value) { + if (!\is_string($value)) { + continue; + } + + if (!isset($iniConfig[$name]) || $iniConfig[$name] !== $value) { + // Double-quote escape each value + $content .= $name.'="'.addcslashes($value, '\\"')."\"\n"; + } + } + + return $content; + } + + private function getAllIniFiles(): array + { + $paths = [(string) php_ini_loaded_file()]; + + if (false !== $scanned = php_ini_scanned_files()) { + $paths = array_merge($paths, array_map('trim', explode(',', $scanned))); + } + + return $paths; + } +} diff --git a/vendor/symfony/process/Pipes/AbstractPipes.php b/vendor/symfony/process/Pipes/AbstractPipes.php new file mode 100644 index 00000000..cbbb7277 --- /dev/null +++ b/vendor/symfony/process/Pipes/AbstractPipes.php @@ -0,0 +1,176 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Pipes; + +use Symfony\Component\Process\Exception\InvalidArgumentException; + +/** + * @author Romain Neutron + * + * @internal + */ +abstract class AbstractPipes implements PipesInterface +{ + public array $pipes = []; + + private string $inputBuffer = ''; + /** @var resource|string|\Iterator */ + private $input; + private bool $blocked = true; + private ?string $lastError = null; + + /** + * @param resource|string|\Iterator $input + */ + public function __construct($input) + { + if (\is_resource($input) || $input instanceof \Iterator) { + $this->input = $input; + } else { + $this->inputBuffer = (string) $input; + } + } + + public function close(): void + { + foreach ($this->pipes as $pipe) { + if (\is_resource($pipe)) { + fclose($pipe); + } + } + $this->pipes = []; + } + + /** + * Returns true if a system call has been interrupted. + */ + protected function hasSystemCallBeenInterrupted(): bool + { + $lastError = $this->lastError; + $this->lastError = null; + + // stream_select returns false when the `select` system call is interrupted by an incoming signal + return null !== $lastError && false !== stripos($lastError, 'interrupted system call'); + } + + /** + * Unblocks streams. + */ + protected function unblock(): void + { + if (!$this->blocked) { + return; + } + + foreach ($this->pipes as $pipe) { + stream_set_blocking($pipe, 0); + } + if (\is_resource($this->input)) { + stream_set_blocking($this->input, 0); + } + + $this->blocked = false; + } + + /** + * Writes input to stdin. + * + * @throws InvalidArgumentException When an input iterator yields a non supported value + */ + protected function write(): ?array + { + if (!isset($this->pipes[0])) { + return null; + } + $input = $this->input; + + if ($input instanceof \Iterator) { + if (!$input->valid()) { + $input = null; + } elseif (\is_resource($input = $input->current())) { + stream_set_blocking($input, 0); + } elseif (!isset($this->inputBuffer[0])) { + if (!\is_string($input)) { + if (!\is_scalar($input)) { + throw new InvalidArgumentException(sprintf('"%s" yielded a value of type "%s", but only scalars and stream resources are supported.', get_debug_type($this->input), get_debug_type($input))); + } + $input = (string) $input; + } + $this->inputBuffer = $input; + $this->input->next(); + $input = null; + } else { + $input = null; + } + } + + $r = $e = []; + $w = [$this->pipes[0]]; + + // let's have a look if something changed in streams + if (false === @stream_select($r, $w, $e, 0, 0)) { + return null; + } + + foreach ($w as $stdin) { + if (isset($this->inputBuffer[0])) { + $written = fwrite($stdin, $this->inputBuffer); + $this->inputBuffer = substr($this->inputBuffer, $written); + if (isset($this->inputBuffer[0])) { + return [$this->pipes[0]]; + } + } + + if ($input) { + while (true) { + $data = fread($input, self::CHUNK_SIZE); + if (!isset($data[0])) { + break; + } + $written = fwrite($stdin, $data); + $data = substr($data, $written); + if (isset($data[0])) { + $this->inputBuffer = $data; + + return [$this->pipes[0]]; + } + } + if (feof($input)) { + if ($this->input instanceof \Iterator) { + $this->input->next(); + } else { + $this->input = null; + } + } + } + } + + // no input to read on resource, buffer is empty + if (!isset($this->inputBuffer[0]) && !($this->input instanceof \Iterator ? $this->input->valid() : $this->input)) { + $this->input = null; + fclose($this->pipes[0]); + unset($this->pipes[0]); + } elseif (!$w) { + return [$this->pipes[0]]; + } + + return null; + } + + /** + * @internal + */ + public function handleError(int $type, string $msg): void + { + $this->lastError = $msg; + } +} diff --git a/vendor/symfony/process/Pipes/PipesInterface.php b/vendor/symfony/process/Pipes/PipesInterface.php new file mode 100644 index 00000000..967f8de7 --- /dev/null +++ b/vendor/symfony/process/Pipes/PipesInterface.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Pipes; + +/** + * PipesInterface manages descriptors and pipes for the use of proc_open. + * + * @author Romain Neutron + * + * @internal + */ +interface PipesInterface +{ + public const CHUNK_SIZE = 16384; + + /** + * Returns an array of descriptors for the use of proc_open. + */ + public function getDescriptors(): array; + + /** + * Returns an array of filenames indexed by their related stream in case these pipes use temporary files. + * + * @return string[] + */ + public function getFiles(): array; + + /** + * Reads data in file handles and pipes. + * + * @param bool $blocking Whether to use blocking calls or not + * @param bool $close Whether to close pipes if they've reached EOF + * + * @return string[] An array of read data indexed by their fd + */ + public function readAndWrite(bool $blocking, bool $close = false): array; + + /** + * Returns if the current state has open file handles or pipes. + */ + public function areOpen(): bool; + + /** + * Returns if pipes are able to read output. + */ + public function haveReadSupport(): bool; + + /** + * Closes file handles and pipes. + */ + public function close(): void; +} diff --git a/vendor/symfony/process/Pipes/UnixPipes.php b/vendor/symfony/process/Pipes/UnixPipes.php new file mode 100644 index 00000000..7bd0db0e --- /dev/null +++ b/vendor/symfony/process/Pipes/UnixPipes.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Pipes; + +use Symfony\Component\Process\Process; + +/** + * UnixPipes implementation uses unix pipes as handles. + * + * @author Romain Neutron + * + * @internal + */ +class UnixPipes extends AbstractPipes +{ + private ?bool $ttyMode; + private bool $ptyMode; + private bool $haveReadSupport; + + public function __construct(?bool $ttyMode, bool $ptyMode, mixed $input, bool $haveReadSupport) + { + $this->ttyMode = $ttyMode; + $this->ptyMode = $ptyMode; + $this->haveReadSupport = $haveReadSupport; + + parent::__construct($input); + } + + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup(): void + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + $this->close(); + } + + public function getDescriptors(): array + { + if (!$this->haveReadSupport) { + $nullstream = fopen('/dev/null', 'c'); + + return [ + ['pipe', 'r'], + $nullstream, + $nullstream, + ]; + } + + if ($this->ttyMode) { + return [ + ['file', '/dev/tty', 'r'], + ['file', '/dev/tty', 'w'], + ['file', '/dev/tty', 'w'], + ]; + } + + if ($this->ptyMode && Process::isPtySupported()) { + return [ + ['pty'], + ['pty'], + ['pty'], + ]; + } + + return [ + ['pipe', 'r'], + ['pipe', 'w'], // stdout + ['pipe', 'w'], // stderr + ]; + } + + public function getFiles(): array + { + return []; + } + + public function readAndWrite(bool $blocking, bool $close = false): array + { + $this->unblock(); + $w = $this->write(); + + $read = $e = []; + $r = $this->pipes; + unset($r[0]); + + // let's have a look if something changed in streams + set_error_handler($this->handleError(...)); + if (($r || $w) && false === stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) { + restore_error_handler(); + // if a system call has been interrupted, forget about it, let's try again + // otherwise, an error occurred, let's reset pipes + if (!$this->hasSystemCallBeenInterrupted()) { + $this->pipes = []; + } + + return $read; + } + restore_error_handler(); + + foreach ($r as $pipe) { + // prior PHP 5.4 the array passed to stream_select is modified and + // lose key association, we have to find back the key + $read[$type = array_search($pipe, $this->pipes, true)] = ''; + + do { + $data = @fread($pipe, self::CHUNK_SIZE); + $read[$type] .= $data; + } while (isset($data[0]) && ($close || isset($data[self::CHUNK_SIZE - 1]))); + + if (!isset($read[$type][0])) { + unset($read[$type]); + } + + if ($close && feof($pipe)) { + fclose($pipe); + unset($this->pipes[$type]); + } + } + + return $read; + } + + public function haveReadSupport(): bool + { + return $this->haveReadSupport; + } + + public function areOpen(): bool + { + return (bool) $this->pipes; + } +} diff --git a/vendor/symfony/process/Pipes/WindowsPipes.php b/vendor/symfony/process/Pipes/WindowsPipes.php new file mode 100644 index 00000000..8033442a --- /dev/null +++ b/vendor/symfony/process/Pipes/WindowsPipes.php @@ -0,0 +1,186 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Pipes; + +use Symfony\Component\Process\Exception\RuntimeException; +use Symfony\Component\Process\Process; + +/** + * WindowsPipes implementation uses temporary files as handles. + * + * @see https://bugs.php.net/51800 + * @see https://bugs.php.net/65650 + * + * @author Romain Neutron + * + * @internal + */ +class WindowsPipes extends AbstractPipes +{ + private array $files = []; + private array $fileHandles = []; + private array $lockHandles = []; + private array $readBytes = [ + Process::STDOUT => 0, + Process::STDERR => 0, + ]; + private bool $haveReadSupport; + + public function __construct(mixed $input, bool $haveReadSupport) + { + $this->haveReadSupport = $haveReadSupport; + + if ($this->haveReadSupport) { + // Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big. + // Workaround for this problem is to use temporary files instead of pipes on Windows platform. + // + // @see https://bugs.php.net/51800 + $pipes = [ + Process::STDOUT => Process::OUT, + Process::STDERR => Process::ERR, + ]; + $tmpDir = sys_get_temp_dir(); + $lastError = 'unknown reason'; + set_error_handler(function ($type, $msg) use (&$lastError) { $lastError = $msg; }); + for ($i = 0;; ++$i) { + foreach ($pipes as $pipe => $name) { + $file = sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name); + + if (!$h = fopen($file.'.lock', 'w')) { + if (file_exists($file.'.lock')) { + continue 2; + } + restore_error_handler(); + throw new RuntimeException('A temporary file could not be opened to write the process output: '.$lastError); + } + if (!flock($h, \LOCK_EX | \LOCK_NB)) { + continue 2; + } + if (isset($this->lockHandles[$pipe])) { + flock($this->lockHandles[$pipe], \LOCK_UN); + fclose($this->lockHandles[$pipe]); + } + $this->lockHandles[$pipe] = $h; + + if (!($h = fopen($file, 'w')) || !fclose($h) || !$h = fopen($file, 'r')) { + flock($this->lockHandles[$pipe], \LOCK_UN); + fclose($this->lockHandles[$pipe]); + unset($this->lockHandles[$pipe]); + continue 2; + } + $this->fileHandles[$pipe] = $h; + $this->files[$pipe] = $file; + } + break; + } + restore_error_handler(); + } + + parent::__construct($input); + } + + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup(): void + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + $this->close(); + } + + public function getDescriptors(): array + { + if (!$this->haveReadSupport) { + $nullstream = fopen('NUL', 'c'); + + return [ + ['pipe', 'r'], + $nullstream, + $nullstream, + ]; + } + + // We're not using pipe on Windows platform as it hangs (https://bugs.php.net/51800) + // We're not using file handles as it can produce corrupted output https://bugs.php.net/65650 + // So we redirect output within the commandline and pass the nul device to the process + return [ + ['pipe', 'r'], + ['file', 'NUL', 'w'], + ['file', 'NUL', 'w'], + ]; + } + + public function getFiles(): array + { + return $this->files; + } + + public function readAndWrite(bool $blocking, bool $close = false): array + { + $this->unblock(); + $w = $this->write(); + $read = $r = $e = []; + + if ($blocking) { + if ($w) { + @stream_select($r, $w, $e, 0, Process::TIMEOUT_PRECISION * 1E6); + } elseif ($this->fileHandles) { + usleep((int) (Process::TIMEOUT_PRECISION * 1E6)); + } + } + foreach ($this->fileHandles as $type => $fileHandle) { + $data = stream_get_contents($fileHandle, -1, $this->readBytes[$type]); + + if (isset($data[0])) { + $this->readBytes[$type] += \strlen($data); + $read[$type] = $data; + } + if ($close) { + ftruncate($fileHandle, 0); + fclose($fileHandle); + flock($this->lockHandles[$type], \LOCK_UN); + fclose($this->lockHandles[$type]); + unset($this->fileHandles[$type], $this->lockHandles[$type]); + } + } + + return $read; + } + + public function haveReadSupport(): bool + { + return $this->haveReadSupport; + } + + public function areOpen(): bool + { + return $this->pipes && $this->fileHandles; + } + + public function close(): void + { + parent::close(); + foreach ($this->fileHandles as $type => $handle) { + ftruncate($handle, 0); + fclose($handle); + flock($this->lockHandles[$type], \LOCK_UN); + fclose($this->lockHandles[$type]); + } + $this->fileHandles = $this->lockHandles = []; + } +} diff --git a/vendor/symfony/process/Process.php b/vendor/symfony/process/Process.php new file mode 100644 index 00000000..1aaf730d --- /dev/null +++ b/vendor/symfony/process/Process.php @@ -0,0 +1,1599 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +use Symfony\Component\Process\Exception\InvalidArgumentException; +use Symfony\Component\Process\Exception\LogicException; +use Symfony\Component\Process\Exception\ProcessFailedException; +use Symfony\Component\Process\Exception\ProcessSignaledException; +use Symfony\Component\Process\Exception\ProcessTimedOutException; +use Symfony\Component\Process\Exception\RuntimeException; +use Symfony\Component\Process\Pipes\UnixPipes; +use Symfony\Component\Process\Pipes\WindowsPipes; + +/** + * Process is a thin wrapper around proc_* functions to easily + * start independent PHP processes. + * + * @author Fabien Potencier + * @author Romain Neutron + * + * @implements \IteratorAggregate + */ +class Process implements \IteratorAggregate +{ + public const ERR = 'err'; + public const OUT = 'out'; + + public const STATUS_READY = 'ready'; + public const STATUS_STARTED = 'started'; + public const STATUS_TERMINATED = 'terminated'; + + public const STDIN = 0; + public const STDOUT = 1; + public const STDERR = 2; + + // Timeout Precision in seconds. + public const TIMEOUT_PRECISION = 0.2; + + public const ITER_NON_BLOCKING = 1; // By default, iterating over outputs is a blocking call, use this flag to make it non-blocking + public const ITER_KEEP_OUTPUT = 2; // By default, outputs are cleared while iterating, use this flag to keep them in memory + public const ITER_SKIP_OUT = 4; // Use this flag to skip STDOUT while iterating + public const ITER_SKIP_ERR = 8; // Use this flag to skip STDERR while iterating + + private ?\Closure $callback = null; + private array|string $commandline; + private ?string $cwd; + private array $env = []; + /** @var resource|string|\Iterator|null */ + private $input; + private ?float $starttime = null; + private ?float $lastOutputTime = null; + private ?float $timeout = null; + private ?float $idleTimeout = null; + private ?int $exitcode = null; + private array $fallbackStatus = []; + private array $processInformation; + private bool $outputDisabled = false; + /** @var resource */ + private $stdout; + /** @var resource */ + private $stderr; + /** @var resource|null */ + private $process; + private string $status = self::STATUS_READY; + private int $incrementalOutputOffset = 0; + private int $incrementalErrorOutputOffset = 0; + private bool $tty = false; + private bool $pty; + private array $options = ['suppress_errors' => true, 'bypass_shell' => true]; + + private WindowsPipes|UnixPipes $processPipes; + + private ?int $latestSignal = null; + private ?int $cachedExitCode = null; + + private static ?bool $sigchild = null; + + /** + * Exit codes translation table. + * + * User-defined errors must use exit codes in the 64-113 range. + */ + public static array $exitCodes = [ + 0 => 'OK', + 1 => 'General error', + 2 => 'Misuse of shell builtins', + + 126 => 'Invoked command cannot execute', + 127 => 'Command not found', + 128 => 'Invalid exit argument', + + // signals + 129 => 'Hangup', + 130 => 'Interrupt', + 131 => 'Quit and dump core', + 132 => 'Illegal instruction', + 133 => 'Trace/breakpoint trap', + 134 => 'Process aborted', + 135 => 'Bus error: "access to undefined portion of memory object"', + 136 => 'Floating point exception: "erroneous arithmetic operation"', + 137 => 'Kill (terminate immediately)', + 138 => 'User-defined 1', + 139 => 'Segmentation violation', + 140 => 'User-defined 2', + 141 => 'Write to pipe with no one reading', + 142 => 'Signal raised by alarm', + 143 => 'Termination (request to terminate)', + // 144 - not defined + 145 => 'Child process terminated, stopped (or continued*)', + 146 => 'Continue if stopped', + 147 => 'Stop executing temporarily', + 148 => 'Terminal stop signal', + 149 => 'Background process attempting to read from tty ("in")', + 150 => 'Background process attempting to write to tty ("out")', + 151 => 'Urgent data available on socket', + 152 => 'CPU time limit exceeded', + 153 => 'File size limit exceeded', + 154 => 'Signal raised by timer counting virtual time: "virtual timer expired"', + 155 => 'Profiling timer expired', + // 156 - not defined + 157 => 'Pollable event', + // 158 - not defined + 159 => 'Bad syscall', + ]; + + /** + * @param array $command The command to run and its arguments listed as separate entries + * @param string|null $cwd The working directory or null to use the working dir of the current PHP process + * @param array|null $env The environment variables or null to use the same environment as the current PHP process + * @param mixed $input The input as stream resource, scalar or \Traversable, or null for no input + * @param int|float|null $timeout The timeout in seconds or null to disable + * + * @throws LogicException When proc_open is not installed + */ + public function __construct(array $command, ?string $cwd = null, ?array $env = null, mixed $input = null, ?float $timeout = 60) + { + if (!\function_exists('proc_open')) { + throw new LogicException('The Process class relies on proc_open, which is not available on your PHP installation.'); + } + + $this->commandline = $command; + $this->cwd = $cwd; + + // on Windows, if the cwd changed via chdir(), proc_open defaults to the dir where PHP was started + // on Gnu/Linux, PHP builds with --enable-maintainer-zts are also affected + // @see : https://bugs.php.net/51800 + // @see : https://bugs.php.net/50524 + if (null === $this->cwd && (\defined('ZEND_THREAD_SAFE') || '\\' === \DIRECTORY_SEPARATOR)) { + $this->cwd = getcwd(); + } + if (null !== $env) { + $this->setEnv($env); + } + + $this->setInput($input); + $this->setTimeout($timeout); + $this->pty = false; + } + + /** + * Creates a Process instance as a command-line to be run in a shell wrapper. + * + * Command-lines are parsed by the shell of your OS (/bin/sh on Unix-like, cmd.exe on Windows.) + * This allows using e.g. pipes or conditional execution. In this mode, signals are sent to the + * shell wrapper and not to your commands. + * + * In order to inject dynamic values into command-lines, we strongly recommend using placeholders. + * This will save escaping values, which is not portable nor secure anyway: + * + * $process = Process::fromShellCommandline('my_command "${:MY_VAR}"'); + * $process->run(null, ['MY_VAR' => $theValue]); + * + * @param string $command The command line to pass to the shell of the OS + * @param string|null $cwd The working directory or null to use the working dir of the current PHP process + * @param array|null $env The environment variables or null to use the same environment as the current PHP process + * @param mixed $input The input as stream resource, scalar or \Traversable, or null for no input + * @param int|float|null $timeout The timeout in seconds or null to disable + * + * @throws LogicException When proc_open is not installed + */ + public static function fromShellCommandline(string $command, ?string $cwd = null, ?array $env = null, mixed $input = null, ?float $timeout = 60): static + { + $process = new static([], $cwd, $env, $input, $timeout); + $process->commandline = $command; + + return $process; + } + + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup(): void + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + if ($this->options['create_new_console'] ?? false) { + $this->processPipes->close(); + } else { + $this->stop(0); + } + } + + public function __clone() + { + $this->resetProcessData(); + } + + /** + * Runs the process. + * + * The callback receives the type of output (out or err) and + * some bytes from the output in real-time. It allows to have feedback + * from the independent process during execution. + * + * The STDOUT and STDERR are also available after the process is finished + * via the getOutput() and getErrorOutput() methods. + * + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * + * @return int The exit status code + * + * @throws RuntimeException When process can't be launched + * @throws RuntimeException When process is already running + * @throws ProcessTimedOutException When process timed out + * @throws ProcessSignaledException When process stopped after receiving signal + * @throws LogicException In case a callback is provided and output has been disabled + * + * @final + */ + public function run(?callable $callback = null, array $env = []): int + { + $this->start($callback, $env); + + return $this->wait(); + } + + /** + * Runs the process. + * + * This is identical to run() except that an exception is thrown if the process + * exits with a non-zero exit code. + * + * @return $this + * + * @throws ProcessFailedException if the process didn't terminate successfully + * + * @final + */ + public function mustRun(?callable $callback = null, array $env = []): static + { + if (0 !== $this->run($callback, $env)) { + throw new ProcessFailedException($this); + } + + return $this; + } + + /** + * Starts the process and returns after writing the input to STDIN. + * + * This method blocks until all STDIN data is sent to the process then it + * returns while the process runs in the background. + * + * The termination of the process can be awaited with wait(). + * + * The callback receives the type of output (out or err) and some bytes from + * the output in real-time while writing the standard input to the process. + * It allows to have feedback from the independent process during execution. + * + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * + * @throws RuntimeException When process can't be launched + * @throws RuntimeException When process is already running + * @throws LogicException In case a callback is provided and output has been disabled + */ + public function start(?callable $callback = null, array $env = []): void + { + if ($this->isRunning()) { + throw new RuntimeException('Process is already running.'); + } + + $this->resetProcessData(); + $this->starttime = $this->lastOutputTime = microtime(true); + $this->callback = $this->buildCallback($callback); + $descriptors = $this->getDescriptors(null !== $callback); + + if ($this->env) { + $env += '\\' === \DIRECTORY_SEPARATOR ? array_diff_ukey($this->env, $env, 'strcasecmp') : $this->env; + } + + $env += '\\' === \DIRECTORY_SEPARATOR ? array_diff_ukey($this->getDefaultEnv(), $env, 'strcasecmp') : $this->getDefaultEnv(); + + if (\is_array($commandline = $this->commandline)) { + $commandline = implode(' ', array_map($this->escapeArgument(...), $commandline)); + + if ('\\' !== \DIRECTORY_SEPARATOR) { + // exec is mandatory to deal with sending a signal to the process + $commandline = 'exec '.$commandline; + } + } else { + $commandline = $this->replacePlaceholders($commandline, $env); + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + $commandline = $this->prepareWindowsCommandLine($commandline, $env); + } elseif ($this->isSigchildEnabled()) { + // last exit code is output on the fourth pipe and caught to work around --enable-sigchild + $descriptors[3] = ['pipe', 'w']; + + // See https://unix.stackexchange.com/questions/71205/background-process-pipe-input + $commandline = '{ ('.$commandline.') <&3 3<&- 3>/dev/null & } 3<&0;'; + $commandline .= 'pid=$!; echo $pid >&3; wait $pid 2>/dev/null; code=$?; echo $code >&3; exit $code'; + } + + $envPairs = []; + foreach ($env as $k => $v) { + if (false !== $v && false === \in_array($k, ['argc', 'argv', 'ARGC', 'ARGV'], true)) { + $envPairs[] = $k.'='.$v; + } + } + + if (!is_dir($this->cwd)) { + throw new RuntimeException(sprintf('The provided cwd "%s" does not exist.', $this->cwd)); + } + + $process = @proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $this->options); + + if (!\is_resource($process)) { + throw new RuntimeException('Unable to launch a new process.'); + } + $this->process = $process; + $this->status = self::STATUS_STARTED; + + if (isset($descriptors[3])) { + $this->fallbackStatus['pid'] = (int) fgets($this->processPipes->pipes[3]); + } + + if ($this->tty) { + return; + } + + $this->updateStatus(false); + $this->checkTimeout(); + } + + /** + * Restarts the process. + * + * Be warned that the process is cloned before being started. + * + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * + * @throws RuntimeException When process can't be launched + * @throws RuntimeException When process is already running + * + * @see start() + * + * @final + */ + public function restart(?callable $callback = null, array $env = []): static + { + if ($this->isRunning()) { + throw new RuntimeException('Process is already running.'); + } + + $process = clone $this; + $process->start($callback, $env); + + return $process; + } + + /** + * Waits for the process to terminate. + * + * The callback receives the type of output (out or err) and some bytes + * from the output in real-time while writing the standard input to the process. + * It allows to have feedback from the independent process during execution. + * + * @param callable|null $callback A valid PHP callback + * + * @return int The exitcode of the process + * + * @throws ProcessTimedOutException When process timed out + * @throws ProcessSignaledException When process stopped after receiving signal + * @throws LogicException When process is not yet started + */ + public function wait(?callable $callback = null): int + { + $this->requireProcessIsStarted(__FUNCTION__); + + $this->updateStatus(false); + + if (null !== $callback) { + if (!$this->processPipes->haveReadSupport()) { + $this->stop(0); + throw new LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::wait".'); + } + $this->callback = $this->buildCallback($callback); + } + + do { + $this->checkTimeout(); + $running = $this->isRunning() && ('\\' === \DIRECTORY_SEPARATOR || $this->processPipes->areOpen()); + $this->readPipes($running, '\\' !== \DIRECTORY_SEPARATOR || !$running); + } while ($running); + + while ($this->isRunning()) { + $this->checkTimeout(); + usleep(1000); + } + + if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) { + throw new ProcessSignaledException($this); + } + + return $this->exitcode; + } + + /** + * Waits until the callback returns true. + * + * The callback receives the type of output (out or err) and some bytes + * from the output in real-time while writing the standard input to the process. + * It allows to have feedback from the independent process during execution. + * + * @throws RuntimeException When process timed out + * @throws LogicException When process is not yet started + * @throws ProcessTimedOutException In case the timeout was reached + */ + public function waitUntil(callable $callback): bool + { + $this->requireProcessIsStarted(__FUNCTION__); + $this->updateStatus(false); + + if (!$this->processPipes->haveReadSupport()) { + $this->stop(0); + throw new LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::waitUntil".'); + } + $callback = $this->buildCallback($callback); + + $ready = false; + while (true) { + $this->checkTimeout(); + $running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen(); + $output = $this->processPipes->readAndWrite($running, '\\' !== \DIRECTORY_SEPARATOR || !$running); + + foreach ($output as $type => $data) { + if (3 !== $type) { + $ready = $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data) || $ready; + } elseif (!isset($this->fallbackStatus['signaled'])) { + $this->fallbackStatus['exitcode'] = (int) $data; + } + } + if ($ready) { + return true; + } + if (!$running) { + return false; + } + + usleep(1000); + } + } + + /** + * Returns the Pid (process identifier), if applicable. + * + * @return int|null The process id if running, null otherwise + */ + public function getPid(): ?int + { + return $this->isRunning() ? $this->processInformation['pid'] : null; + } + + /** + * Sends a POSIX signal to the process. + * + * @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants) + * + * @return $this + * + * @throws LogicException In case the process is not running + * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed + * @throws RuntimeException In case of failure + */ + public function signal(int $signal): static + { + $this->doSignal($signal, true); + + return $this; + } + + /** + * Disables fetching output and error output from the underlying process. + * + * @return $this + * + * @throws RuntimeException In case the process is already running + * @throws LogicException if an idle timeout is set + */ + public function disableOutput(): static + { + if ($this->isRunning()) { + throw new RuntimeException('Disabling output while the process is running is not possible.'); + } + if (null !== $this->idleTimeout) { + throw new LogicException('Output cannot be disabled while an idle timeout is set.'); + } + + $this->outputDisabled = true; + + return $this; + } + + /** + * Enables fetching output and error output from the underlying process. + * + * @return $this + * + * @throws RuntimeException In case the process is already running + */ + public function enableOutput(): static + { + if ($this->isRunning()) { + throw new RuntimeException('Enabling output while the process is running is not possible.'); + } + + $this->outputDisabled = false; + + return $this; + } + + /** + * Returns true in case the output is disabled, false otherwise. + */ + public function isOutputDisabled(): bool + { + return $this->outputDisabled; + } + + /** + * Returns the current output of the process (STDOUT). + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + */ + public function getOutput(): string + { + $this->readPipesForOutput(__FUNCTION__); + + if (false === $ret = stream_get_contents($this->stdout, -1, 0)) { + return ''; + } + + return $ret; + } + + /** + * Returns the output incrementally. + * + * In comparison with the getOutput method which always return the whole + * output, this one returns the new output since the last call. + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + */ + public function getIncrementalOutput(): string + { + $this->readPipesForOutput(__FUNCTION__); + + $latest = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset); + $this->incrementalOutputOffset = ftell($this->stdout); + + if (false === $latest) { + return ''; + } + + return $latest; + } + + /** + * Returns an iterator to the output of the process, with the output type as keys (Process::OUT/ERR). + * + * @param int $flags A bit field of Process::ITER_* flags + * + * @return \Generator + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + */ + public function getIterator(int $flags = 0): \Generator + { + $this->readPipesForOutput(__FUNCTION__, false); + + $clearOutput = !(self::ITER_KEEP_OUTPUT & $flags); + $blocking = !(self::ITER_NON_BLOCKING & $flags); + $yieldOut = !(self::ITER_SKIP_OUT & $flags); + $yieldErr = !(self::ITER_SKIP_ERR & $flags); + + while (null !== $this->callback || ($yieldOut && !feof($this->stdout)) || ($yieldErr && !feof($this->stderr))) { + if ($yieldOut) { + $out = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset); + + if (isset($out[0])) { + if ($clearOutput) { + $this->clearOutput(); + } else { + $this->incrementalOutputOffset = ftell($this->stdout); + } + + yield self::OUT => $out; + } + } + + if ($yieldErr) { + $err = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset); + + if (isset($err[0])) { + if ($clearOutput) { + $this->clearErrorOutput(); + } else { + $this->incrementalErrorOutputOffset = ftell($this->stderr); + } + + yield self::ERR => $err; + } + } + + if (!$blocking && !isset($out[0]) && !isset($err[0])) { + yield self::OUT => ''; + } + + $this->checkTimeout(); + $this->readPipesForOutput(__FUNCTION__, $blocking); + } + } + + /** + * Clears the process output. + * + * @return $this + */ + public function clearOutput(): static + { + ftruncate($this->stdout, 0); + fseek($this->stdout, 0); + $this->incrementalOutputOffset = 0; + + return $this; + } + + /** + * Returns the current error output of the process (STDERR). + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + */ + public function getErrorOutput(): string + { + $this->readPipesForOutput(__FUNCTION__); + + if (false === $ret = stream_get_contents($this->stderr, -1, 0)) { + return ''; + } + + return $ret; + } + + /** + * Returns the errorOutput incrementally. + * + * In comparison with the getErrorOutput method which always return the + * whole error output, this one returns the new error output since the last + * call. + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + */ + public function getIncrementalErrorOutput(): string + { + $this->readPipesForOutput(__FUNCTION__); + + $latest = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset); + $this->incrementalErrorOutputOffset = ftell($this->stderr); + + if (false === $latest) { + return ''; + } + + return $latest; + } + + /** + * Clears the process output. + * + * @return $this + */ + public function clearErrorOutput(): static + { + ftruncate($this->stderr, 0); + fseek($this->stderr, 0); + $this->incrementalErrorOutputOffset = 0; + + return $this; + } + + /** + * Returns the exit code returned by the process. + * + * @return int|null The exit status code, null if the Process is not terminated + */ + public function getExitCode(): ?int + { + $this->updateStatus(false); + + return $this->exitcode; + } + + /** + * Returns a string representation for the exit code returned by the process. + * + * This method relies on the Unix exit code status standardization + * and might not be relevant for other operating systems. + * + * @return string|null A string representation for the exit status code, null if the Process is not terminated + * + * @see http://tldp.org/LDP/abs/html/exitcodes.html + * @see http://en.wikipedia.org/wiki/Unix_signal + */ + public function getExitCodeText(): ?string + { + if (null === $exitcode = $this->getExitCode()) { + return null; + } + + return self::$exitCodes[$exitcode] ?? 'Unknown error'; + } + + /** + * Checks if the process ended successfully. + */ + public function isSuccessful(): bool + { + return 0 === $this->getExitCode(); + } + + /** + * Returns true if the child process has been terminated by an uncaught signal. + * + * It always returns false on Windows. + * + * @throws LogicException In case the process is not terminated + */ + public function hasBeenSignaled(): bool + { + $this->requireProcessIsTerminated(__FUNCTION__); + + return $this->processInformation['signaled']; + } + + /** + * Returns the number of the signal that caused the child process to terminate its execution. + * + * It is only meaningful if hasBeenSignaled() returns true. + * + * @throws RuntimeException In case --enable-sigchild is activated + * @throws LogicException In case the process is not terminated + */ + public function getTermSignal(): int + { + $this->requireProcessIsTerminated(__FUNCTION__); + + if ($this->isSigchildEnabled() && -1 === $this->processInformation['termsig']) { + throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal cannot be retrieved.'); + } + + return $this->processInformation['termsig']; + } + + /** + * Returns true if the child process has been stopped by a signal. + * + * It always returns false on Windows. + * + * @throws LogicException In case the process is not terminated + */ + public function hasBeenStopped(): bool + { + $this->requireProcessIsTerminated(__FUNCTION__); + + return $this->processInformation['stopped']; + } + + /** + * Returns the number of the signal that caused the child process to stop its execution. + * + * It is only meaningful if hasBeenStopped() returns true. + * + * @throws LogicException In case the process is not terminated + */ + public function getStopSignal(): int + { + $this->requireProcessIsTerminated(__FUNCTION__); + + return $this->processInformation['stopsig']; + } + + /** + * Checks if the process is currently running. + */ + public function isRunning(): bool + { + if (self::STATUS_STARTED !== $this->status) { + return false; + } + + $this->updateStatus(false); + + return $this->processInformation['running']; + } + + /** + * Checks if the process has been started with no regard to the current state. + */ + public function isStarted(): bool + { + return self::STATUS_READY != $this->status; + } + + /** + * Checks if the process is terminated. + */ + public function isTerminated(): bool + { + $this->updateStatus(false); + + return self::STATUS_TERMINATED == $this->status; + } + + /** + * Gets the process status. + * + * The status is one of: ready, started, terminated. + */ + public function getStatus(): string + { + $this->updateStatus(false); + + return $this->status; + } + + /** + * Stops the process. + * + * @param int|float $timeout The timeout in seconds + * @param int|null $signal A POSIX signal to send in case the process has not stop at timeout, default is SIGKILL (9) + * + * @return int|null The exit-code of the process or null if it's not running + */ + public function stop(float $timeout = 10, ?int $signal = null): ?int + { + $timeoutMicro = microtime(true) + $timeout; + if ($this->isRunning()) { + // given SIGTERM may not be defined and that "proc_terminate" uses the constant value and not the constant itself, we use the same here + $this->doSignal(15, false); + do { + usleep(1000); + } while ($this->isRunning() && microtime(true) < $timeoutMicro); + + if ($this->isRunning()) { + // Avoid exception here: process is supposed to be running, but it might have stopped just + // after this line. In any case, let's silently discard the error, we cannot do anything. + $this->doSignal($signal ?: 9, false); + } + } + + if ($this->isRunning()) { + if (isset($this->fallbackStatus['pid'])) { + unset($this->fallbackStatus['pid']); + + return $this->stop(0, $signal); + } + $this->close(); + } + + return $this->exitcode; + } + + /** + * Adds a line to the STDOUT stream. + * + * @internal + */ + public function addOutput(string $line): void + { + $this->lastOutputTime = microtime(true); + + fseek($this->stdout, 0, \SEEK_END); + fwrite($this->stdout, $line); + fseek($this->stdout, $this->incrementalOutputOffset); + } + + /** + * Adds a line to the STDERR stream. + * + * @internal + */ + public function addErrorOutput(string $line): void + { + $this->lastOutputTime = microtime(true); + + fseek($this->stderr, 0, \SEEK_END); + fwrite($this->stderr, $line); + fseek($this->stderr, $this->incrementalErrorOutputOffset); + } + + /** + * Gets the last output time in seconds. + */ + public function getLastOutputTime(): ?float + { + return $this->lastOutputTime; + } + + /** + * Gets the command line to be executed. + */ + public function getCommandLine(): string + { + return \is_array($this->commandline) ? implode(' ', array_map($this->escapeArgument(...), $this->commandline)) : $this->commandline; + } + + /** + * Gets the process timeout in seconds (max. runtime). + */ + public function getTimeout(): ?float + { + return $this->timeout; + } + + /** + * Gets the process idle timeout in seconds (max. time since last output). + */ + public function getIdleTimeout(): ?float + { + return $this->idleTimeout; + } + + /** + * Sets the process timeout (max. runtime) in seconds. + * + * To disable the timeout, set this value to null. + * + * @return $this + * + * @throws InvalidArgumentException if the timeout is negative + */ + public function setTimeout(?float $timeout): static + { + $this->timeout = $this->validateTimeout($timeout); + + return $this; + } + + /** + * Sets the process idle timeout (max. time since last output) in seconds. + * + * To disable the timeout, set this value to null. + * + * @return $this + * + * @throws LogicException if the output is disabled + * @throws InvalidArgumentException if the timeout is negative + */ + public function setIdleTimeout(?float $timeout): static + { + if (null !== $timeout && $this->outputDisabled) { + throw new LogicException('Idle timeout cannot be set while the output is disabled.'); + } + + $this->idleTimeout = $this->validateTimeout($timeout); + + return $this; + } + + /** + * Enables or disables the TTY mode. + * + * @return $this + * + * @throws RuntimeException In case the TTY mode is not supported + */ + public function setTty(bool $tty): static + { + if ('\\' === \DIRECTORY_SEPARATOR && $tty) { + throw new RuntimeException('TTY mode is not supported on Windows platform.'); + } + + if ($tty && !self::isTtySupported()) { + throw new RuntimeException('TTY mode requires /dev/tty to be read/writable.'); + } + + $this->tty = $tty; + + return $this; + } + + /** + * Checks if the TTY mode is enabled. + */ + public function isTty(): bool + { + return $this->tty; + } + + /** + * Sets PTY mode. + * + * @return $this + */ + public function setPty(bool $bool): static + { + $this->pty = $bool; + + return $this; + } + + /** + * Returns PTY state. + */ + public function isPty(): bool + { + return $this->pty; + } + + /** + * Gets the working directory. + */ + public function getWorkingDirectory(): ?string + { + if (null === $this->cwd) { + // getcwd() will return false if any one of the parent directories does not have + // the readable or search mode set, even if the current directory does + return getcwd() ?: null; + } + + return $this->cwd; + } + + /** + * Sets the current working directory. + * + * @return $this + */ + public function setWorkingDirectory(string $cwd): static + { + $this->cwd = $cwd; + + return $this; + } + + /** + * Gets the environment variables. + */ + public function getEnv(): array + { + return $this->env; + } + + /** + * Sets the environment variables. + * + * @param array $env The new environment variables + * + * @return $this + */ + public function setEnv(array $env): static + { + $this->env = $env; + + return $this; + } + + /** + * Gets the Process input. + * + * @return resource|string|\Iterator|null + */ + public function getInput() + { + return $this->input; + } + + /** + * Sets the input. + * + * This content will be passed to the underlying process standard input. + * + * @param string|resource|\Traversable|self|null $input The content + * + * @return $this + * + * @throws LogicException In case the process is running + */ + public function setInput(mixed $input): static + { + if ($this->isRunning()) { + throw new LogicException('Input cannot be set while the process is running.'); + } + + $this->input = ProcessUtils::validateInput(__METHOD__, $input); + + return $this; + } + + /** + * Performs a check between the timeout definition and the time the process started. + * + * In case you run a background process (with the start method), you should + * trigger this method regularly to ensure the process timeout + * + * @throws ProcessTimedOutException In case the timeout was reached + */ + public function checkTimeout(): void + { + if (self::STATUS_STARTED !== $this->status) { + return; + } + + if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) { + $this->stop(0); + + throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_GENERAL); + } + + if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) { + $this->stop(0); + + throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_IDLE); + } + } + + /** + * @throws LogicException in case process is not started + */ + public function getStartTime(): float + { + if (!$this->isStarted()) { + throw new LogicException('Start time is only available after process start.'); + } + + return $this->starttime; + } + + /** + * Defines options to pass to the underlying proc_open(). + * + * @see https://php.net/proc_open for the options supported by PHP. + * + * Enabling the "create_new_console" option allows a subprocess to continue + * to run after the main process exited, on both Windows and *nix + */ + public function setOptions(array $options): void + { + if ($this->isRunning()) { + throw new RuntimeException('Setting options while the process is running is not possible.'); + } + + $defaultOptions = $this->options; + $existingOptions = ['blocking_pipes', 'create_process_group', 'create_new_console']; + + foreach ($options as $key => $value) { + if (!\in_array($key, $existingOptions)) { + $this->options = $defaultOptions; + throw new LogicException(sprintf('Invalid option "%s" passed to "%s()". Supported options are "%s".', $key, __METHOD__, implode('", "', $existingOptions))); + } + $this->options[$key] = $value; + } + } + + /** + * Returns whether TTY is supported on the current operating system. + */ + public static function isTtySupported(): bool + { + static $isTtySupported; + + return $isTtySupported ??= ('/' === \DIRECTORY_SEPARATOR && stream_isatty(\STDOUT)); + } + + /** + * Returns whether PTY is supported on the current operating system. + */ + public static function isPtySupported(): bool + { + static $result; + + if (null !== $result) { + return $result; + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + return $result = false; + } + + return $result = (bool) @proc_open('echo 1 >/dev/null', [['pty'], ['pty'], ['pty']], $pipes); + } + + /** + * Creates the descriptors needed by the proc_open. + */ + private function getDescriptors(bool $hasCallback): array + { + if ($this->input instanceof \Iterator) { + $this->input->rewind(); + } + if ('\\' === \DIRECTORY_SEPARATOR) { + $this->processPipes = new WindowsPipes($this->input, !$this->outputDisabled || $hasCallback); + } else { + $this->processPipes = new UnixPipes($this->isTty(), $this->isPty(), $this->input, !$this->outputDisabled || $hasCallback); + } + + return $this->processPipes->getDescriptors(); + } + + /** + * Builds up the callback used by wait(). + * + * The callbacks adds all occurred output to the specific buffer and calls + * the user callback (if present) with the received output. + * + * @param callable|null $callback The user defined PHP callback + */ + protected function buildCallback(?callable $callback = null): \Closure + { + if ($this->outputDisabled) { + return fn ($type, $data): bool => null !== $callback && $callback($type, $data); + } + + $out = self::OUT; + + return function ($type, $data) use ($callback, $out): bool { + if ($out == $type) { + $this->addOutput($data); + } else { + $this->addErrorOutput($data); + } + + return null !== $callback && $callback($type, $data); + }; + } + + /** + * Updates the status of the process, reads pipes. + * + * @param bool $blocking Whether to use a blocking read call + */ + protected function updateStatus(bool $blocking): void + { + if (self::STATUS_STARTED !== $this->status) { + return; + } + + $this->processInformation = proc_get_status($this->process); + $running = $this->processInformation['running']; + + // In PHP < 8.3, "proc_get_status" only returns the correct exit status on the first call. + // Subsequent calls return -1 as the process is discarded. This workaround caches the first + // retrieved exit status for consistent results in later calls, mimicking PHP 8.3 behavior. + if (\PHP_VERSION_ID < 80300) { + if (!isset($this->cachedExitCode) && !$running && -1 !== $this->processInformation['exitcode']) { + $this->cachedExitCode = $this->processInformation['exitcode']; + } + + if (isset($this->cachedExitCode) && !$running && -1 === $this->processInformation['exitcode']) { + $this->processInformation['exitcode'] = $this->cachedExitCode; + } + } + + $this->readPipes($running && $blocking, '\\' !== \DIRECTORY_SEPARATOR || !$running); + + if ($this->fallbackStatus && $this->isSigchildEnabled()) { + $this->processInformation = $this->fallbackStatus + $this->processInformation; + } + + if (!$running) { + $this->close(); + } + } + + /** + * Returns whether PHP has been compiled with the '--enable-sigchild' option or not. + */ + protected function isSigchildEnabled(): bool + { + if (null !== self::$sigchild) { + return self::$sigchild; + } + + if (!\function_exists('phpinfo')) { + return self::$sigchild = false; + } + + ob_start(); + phpinfo(\INFO_GENERAL); + + return self::$sigchild = str_contains(ob_get_clean(), '--enable-sigchild'); + } + + /** + * Reads pipes for the freshest output. + * + * @param string $caller The name of the method that needs fresh outputs + * @param bool $blocking Whether to use blocking calls or not + * + * @throws LogicException in case output has been disabled or process is not started + */ + private function readPipesForOutput(string $caller, bool $blocking = false): void + { + if ($this->outputDisabled) { + throw new LogicException('Output has been disabled.'); + } + + $this->requireProcessIsStarted($caller); + + $this->updateStatus($blocking); + } + + /** + * Validates and returns the filtered timeout. + * + * @throws InvalidArgumentException if the given timeout is a negative number + */ + private function validateTimeout(?float $timeout): ?float + { + $timeout = (float) $timeout; + + if (0.0 === $timeout) { + $timeout = null; + } elseif ($timeout < 0) { + throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.'); + } + + return $timeout; + } + + /** + * Reads pipes, executes callback. + * + * @param bool $blocking Whether to use blocking calls or not + * @param bool $close Whether to close file handles or not + */ + private function readPipes(bool $blocking, bool $close): void + { + $result = $this->processPipes->readAndWrite($blocking, $close); + + $callback = $this->callback; + foreach ($result as $type => $data) { + if (3 !== $type) { + $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data); + } elseif (!isset($this->fallbackStatus['signaled'])) { + $this->fallbackStatus['exitcode'] = (int) $data; + } + } + } + + /** + * Closes process resource, closes file handles, sets the exitcode. + * + * @return int The exitcode + */ + private function close(): int + { + $this->processPipes->close(); + if (\is_resource($this->process)) { + proc_close($this->process); + } + $this->exitcode = $this->processInformation['exitcode']; + $this->status = self::STATUS_TERMINATED; + + if (-1 === $this->exitcode) { + if ($this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) { + // if process has been signaled, no exitcode but a valid termsig, apply Unix convention + $this->exitcode = 128 + $this->processInformation['termsig']; + } elseif ($this->isSigchildEnabled()) { + $this->processInformation['signaled'] = true; + $this->processInformation['termsig'] = -1; + } + } + + // Free memory from self-reference callback created by buildCallback + // Doing so in other contexts like __destruct or by garbage collector is ineffective + // Now pipes are closed, so the callback is no longer necessary + $this->callback = null; + + return $this->exitcode; + } + + /** + * Resets data related to the latest run of the process. + */ + private function resetProcessData(): void + { + $this->starttime = null; + $this->callback = null; + $this->exitcode = null; + $this->fallbackStatus = []; + $this->processInformation = []; + $this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+'); + $this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+'); + $this->process = null; + $this->latestSignal = null; + $this->status = self::STATUS_READY; + $this->incrementalOutputOffset = 0; + $this->incrementalErrorOutputOffset = 0; + } + + /** + * Sends a POSIX signal to the process. + * + * @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants) + * @param bool $throwException Whether to throw exception in case signal failed + * + * @throws LogicException In case the process is not running + * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed + * @throws RuntimeException In case of failure + */ + private function doSignal(int $signal, bool $throwException): bool + { + if (null === $pid = $this->getPid()) { + if ($throwException) { + throw new LogicException('Cannot send signal on a non running process.'); + } + + return false; + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + exec(sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode); + if ($exitCode && $this->isRunning()) { + if ($throwException) { + throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output))); + } + + return false; + } + } else { + if (!$this->isSigchildEnabled()) { + $ok = @proc_terminate($this->process, $signal); + } elseif (\function_exists('posix_kill')) { + $ok = @posix_kill($pid, $signal); + } elseif ($ok = proc_open(sprintf('kill -%d %d', $signal, $pid), [2 => ['pipe', 'w']], $pipes)) { + $ok = false === fgets($pipes[2]); + } + if (!$ok) { + if ($throwException) { + throw new RuntimeException(sprintf('Error while sending signal "%s".', $signal)); + } + + return false; + } + } + + $this->latestSignal = $signal; + $this->fallbackStatus['signaled'] = true; + $this->fallbackStatus['exitcode'] = -1; + $this->fallbackStatus['termsig'] = $this->latestSignal; + + return true; + } + + private function prepareWindowsCommandLine(string $cmd, array &$env): string + { + $uid = uniqid('', true); + $cmd = preg_replace_callback( + '/"(?:( + [^"%!^]*+ + (?: + (?: !LF! | "(?:\^[%!^])?+" ) + [^"%!^]*+ + )++ + ) | [^"]*+ )"/x', + function ($m) use (&$env, $uid) { + static $varCount = 0; + static $varCache = []; + if (!isset($m[1])) { + return $m[0]; + } + if (isset($varCache[$m[0]])) { + return $varCache[$m[0]]; + } + if (str_contains($value = $m[1], "\0")) { + $value = str_replace("\0", '?', $value); + } + if (false === strpbrk($value, "\"%!\n")) { + return '"'.$value.'"'; + } + + $value = str_replace(['!LF!', '"^!"', '"^%"', '"^^"', '""'], ["\n", '!', '%', '^', '"'], $value); + $value = '"'.preg_replace('/(\\\\*)"/', '$1$1\\"', $value).'"'; + $var = $uid.++$varCount; + + $env[$var] = $value; + + return $varCache[$m[0]] = '!'.$var.'!'; + }, + $cmd + ); + + $cmd = 'cmd /V:ON /E:ON /D /C ('.str_replace("\n", ' ', $cmd).')'; + foreach ($this->processPipes->getFiles() as $offset => $filename) { + $cmd .= ' '.$offset.'>"'.$filename.'"'; + } + + return $cmd; + } + + /** + * Ensures the process is running or terminated, throws a LogicException if the process has a not started. + * + * @throws LogicException if the process has not run + */ + private function requireProcessIsStarted(string $functionName): void + { + if (!$this->isStarted()) { + throw new LogicException(sprintf('Process must be started before calling "%s()".', $functionName)); + } + } + + /** + * Ensures the process is terminated, throws a LogicException if the process has a status different than "terminated". + * + * @throws LogicException if the process is not yet terminated + */ + private function requireProcessIsTerminated(string $functionName): void + { + if (!$this->isTerminated()) { + throw new LogicException(sprintf('Process must be terminated before calling "%s()".', $functionName)); + } + } + + /** + * Escapes a string to be used as a shell argument. + */ + private function escapeArgument(?string $argument): string + { + if ('' === $argument || null === $argument) { + return '""'; + } + if ('\\' !== \DIRECTORY_SEPARATOR) { + return "'".str_replace("'", "'\\''", $argument)."'"; + } + if (str_contains($argument, "\0")) { + $argument = str_replace("\0", '?', $argument); + } + if (!preg_match('/[\/()%!^"<>&|\s]/', $argument)) { + return $argument; + } + $argument = preg_replace('/(\\\\+)$/', '$1$1', $argument); + + return '"'.str_replace(['"', '^', '%', '!', "\n"], ['""', '"^^"', '"^%"', '"^!"', '!LF!'], $argument).'"'; + } + + private function replacePlaceholders(string $commandline, array $env): string + { + return preg_replace_callback('/"\$\{:([_a-zA-Z]++[_a-zA-Z0-9]*+)\}"/', function ($matches) use ($commandline, $env) { + if (!isset($env[$matches[1]]) || false === $env[$matches[1]]) { + throw new InvalidArgumentException(sprintf('Command line is missing a value for parameter "%s": ', $matches[1]).$commandline); + } + + return $this->escapeArgument($env[$matches[1]]); + }, $commandline); + } + + private function getDefaultEnv(): array + { + $env = getenv(); + $env = ('\\' === \DIRECTORY_SEPARATOR ? array_intersect_ukey($env, $_SERVER, 'strcasecmp') : array_intersect_key($env, $_SERVER)) ?: $env; + + return $_ENV + ('\\' === \DIRECTORY_SEPARATOR ? array_diff_ukey($env, $_ENV, 'strcasecmp') : $env); + } +} diff --git a/vendor/symfony/process/ProcessUtils.php b/vendor/symfony/process/ProcessUtils.php new file mode 100644 index 00000000..092c5ccf --- /dev/null +++ b/vendor/symfony/process/ProcessUtils.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +use Symfony\Component\Process\Exception\InvalidArgumentException; + +/** + * ProcessUtils is a bunch of utility methods. + * + * This class contains static methods only and is not meant to be instantiated. + * + * @author Martin Hasoň + */ +class ProcessUtils +{ + /** + * This class should not be instantiated. + */ + private function __construct() + { + } + + /** + * Validates and normalizes a Process input. + * + * @param string $caller The name of method call that validates the input + * @param mixed $input The input to validate + * + * @throws InvalidArgumentException In case the input is not valid + */ + public static function validateInput(string $caller, mixed $input): mixed + { + if (null !== $input) { + if (\is_resource($input)) { + return $input; + } + if (\is_scalar($input)) { + return (string) $input; + } + if ($input instanceof Process) { + return $input->getIterator($input::ITER_SKIP_ERR); + } + if ($input instanceof \Iterator) { + return $input; + } + if ($input instanceof \Traversable) { + return new \IteratorIterator($input); + } + + throw new InvalidArgumentException(sprintf('"%s" only accepts strings, Traversable objects or stream resources.', $caller)); + } + + return $input; + } +} diff --git a/vendor/symfony/process/README.md b/vendor/symfony/process/README.md new file mode 100644 index 00000000..afce5e45 --- /dev/null +++ b/vendor/symfony/process/README.md @@ -0,0 +1,13 @@ +Process Component +================= + +The Process component executes commands in sub-processes. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/process.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/process/composer.json b/vendor/symfony/process/composer.json new file mode 100644 index 00000000..dda5575e --- /dev/null +++ b/vendor/symfony/process/composer.json @@ -0,0 +1,28 @@ +{ + "name": "symfony/process", + "type": "library", + "description": "Executes commands in sub-processes", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Process\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/service-contracts/Attribute/Required.php b/vendor/symfony/service-contracts/Attribute/Required.php new file mode 100644 index 00000000..9df85118 --- /dev/null +++ b/vendor/symfony/service-contracts/Attribute/Required.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service\Attribute; + +/** + * A required dependency. + * + * This attribute indicates that a property holds a required dependency. The annotated property or method should be + * considered during the instantiation process of the containing class. + * + * @author Alexander M. Turek + */ +#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] +final class Required +{ +} diff --git a/vendor/symfony/service-contracts/Attribute/SubscribedService.php b/vendor/symfony/service-contracts/Attribute/SubscribedService.php new file mode 100644 index 00000000..d98e1dfd --- /dev/null +++ b/vendor/symfony/service-contracts/Attribute/SubscribedService.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service\Attribute; + +use Symfony\Contracts\Service\ServiceSubscriberInterface; +use Symfony\Contracts\Service\ServiceSubscriberTrait; + +/** + * For use as the return value for {@see ServiceSubscriberInterface}. + * + * @example new SubscribedService('http_client', HttpClientInterface::class, false, new Target('githubApi')) + * + * Use with {@see ServiceSubscriberTrait} to mark a method's return type + * as a subscribed service. + * + * @author Kevin Bond + */ +#[\Attribute(\Attribute::TARGET_METHOD)] +final class SubscribedService +{ + /** @var object[] */ + public array $attributes; + + /** + * @param string|null $key The key to use for the service + * @param class-string|null $type The service class + * @param bool $nullable Whether the service is optional + * @param object|object[] $attributes One or more dependency injection attributes to use + */ + public function __construct( + public ?string $key = null, + public ?string $type = null, + public bool $nullable = false, + array|object $attributes = [], + ) { + $this->attributes = \is_array($attributes) ? $attributes : [$attributes]; + } +} diff --git a/vendor/symfony/service-contracts/CHANGELOG.md b/vendor/symfony/service-contracts/CHANGELOG.md new file mode 100644 index 00000000..7932e261 --- /dev/null +++ b/vendor/symfony/service-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/vendor/symfony/service-contracts/LICENSE b/vendor/symfony/service-contracts/LICENSE new file mode 100644 index 00000000..7536caea --- /dev/null +++ b/vendor/symfony/service-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/service-contracts/README.md b/vendor/symfony/service-contracts/README.md new file mode 100644 index 00000000..42841a57 --- /dev/null +++ b/vendor/symfony/service-contracts/README.md @@ -0,0 +1,9 @@ +Symfony Service Contracts +========================= + +A set of abstractions extracted out of the Symfony components. + +Can be used to build on semantics that the Symfony components proved useful and +that already have battle tested implementations. + +See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/vendor/symfony/service-contracts/ResetInterface.php b/vendor/symfony/service-contracts/ResetInterface.php new file mode 100644 index 00000000..a4f389b0 --- /dev/null +++ b/vendor/symfony/service-contracts/ResetInterface.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +/** + * Provides a way to reset an object to its initial state. + * + * When calling the "reset()" method on an object, it should be put back to its + * initial state. This usually means clearing any internal buffers and forwarding + * the call to internal dependencies. All properties of the object should be put + * back to the same state it had when it was first ready to use. + * + * This method could be called, for example, to recycle objects that are used as + * services, so that they can be used to handle several requests in the same + * process loop (note that we advise making your services stateless instead of + * implementing this interface when possible.) + */ +interface ResetInterface +{ + /** + * @return void + */ + public function reset(); +} diff --git a/vendor/symfony/service-contracts/ServiceLocatorTrait.php b/vendor/symfony/service-contracts/ServiceLocatorTrait.php new file mode 100644 index 00000000..b62ec3e5 --- /dev/null +++ b/vendor/symfony/service-contracts/ServiceLocatorTrait.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(ContainerExceptionInterface::class); +class_exists(NotFoundExceptionInterface::class); + +/** + * A trait to help implement ServiceProviderInterface. + * + * @author Robin Chalas + * @author Nicolas Grekas + */ +trait ServiceLocatorTrait +{ + private array $factories; + private array $loading = []; + private array $providedTypes; + + /** + * @param array $factories + */ + public function __construct(array $factories) + { + $this->factories = $factories; + } + + public function has(string $id): bool + { + return isset($this->factories[$id]); + } + + public function get(string $id): mixed + { + if (!isset($this->factories[$id])) { + throw $this->createNotFoundException($id); + } + + if (isset($this->loading[$id])) { + $ids = array_values($this->loading); + $ids = \array_slice($this->loading, array_search($id, $ids)); + $ids[] = $id; + + throw $this->createCircularReferenceException($id, $ids); + } + + $this->loading[$id] = $id; + try { + return $this->factories[$id]($this); + } finally { + unset($this->loading[$id]); + } + } + + public function getProvidedServices(): array + { + if (!isset($this->providedTypes)) { + $this->providedTypes = []; + + foreach ($this->factories as $name => $factory) { + if (!\is_callable($factory)) { + $this->providedTypes[$name] = '?'; + } else { + $type = (new \ReflectionFunction($factory))->getReturnType(); + + $this->providedTypes[$name] = $type ? ($type->allowsNull() ? '?' : '').($type instanceof \ReflectionNamedType ? $type->getName() : $type) : '?'; + } + } + } + + return $this->providedTypes; + } + + private function createNotFoundException(string $id): NotFoundExceptionInterface + { + if (!$alternatives = array_keys($this->factories)) { + $message = 'is empty...'; + } else { + $last = array_pop($alternatives); + if ($alternatives) { + $message = sprintf('only knows about the "%s" and "%s" services.', implode('", "', $alternatives), $last); + } else { + $message = sprintf('only knows about the "%s" service.', $last); + } + } + + if ($this->loading) { + $message = sprintf('The service "%s" has a dependency on a non-existent service "%s". This locator %s', end($this->loading), $id, $message); + } else { + $message = sprintf('Service "%s" not found: the current service locator %s', $id, $message); + } + + return new class($message) extends \InvalidArgumentException implements NotFoundExceptionInterface { + }; + } + + private function createCircularReferenceException(string $id, array $path): ContainerExceptionInterface + { + return new class(sprintf('Circular reference detected for service "%s", path: "%s".', $id, implode(' -> ', $path))) extends \RuntimeException implements ContainerExceptionInterface { + }; + } +} diff --git a/vendor/symfony/service-contracts/ServiceProviderInterface.php b/vendor/symfony/service-contracts/ServiceProviderInterface.php new file mode 100644 index 00000000..2e71f00c --- /dev/null +++ b/vendor/symfony/service-contracts/ServiceProviderInterface.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Psr\Container\ContainerInterface; + +/** + * A ServiceProviderInterface exposes the identifiers and the types of services provided by a container. + * + * @author Nicolas Grekas + * @author Mateusz Sip + * + * @template-covariant T of mixed + */ +interface ServiceProviderInterface extends ContainerInterface +{ + /** + * @return T + */ + public function get(string $id): mixed; + + public function has(string $id): bool; + + /** + * Returns an associative array of service types keyed by the identifiers provided by the current container. + * + * Examples: + * + * * ['logger' => 'Psr\Log\LoggerInterface'] means the object provides a service named "logger" that implements Psr\Log\LoggerInterface + * * ['foo' => '?'] means the container provides service name "foo" of unspecified type + * * ['bar' => '?Bar\Baz'] means the container provides a service "bar" of type Bar\Baz|null + * + * @return array The provided service types, keyed by service names + */ + public function getProvidedServices(): array; +} diff --git a/vendor/symfony/service-contracts/ServiceSubscriberInterface.php b/vendor/symfony/service-contracts/ServiceSubscriberInterface.php new file mode 100644 index 00000000..3da19169 --- /dev/null +++ b/vendor/symfony/service-contracts/ServiceSubscriberInterface.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Symfony\Contracts\Service\Attribute\SubscribedService; + +/** + * A ServiceSubscriber exposes its dependencies via the static {@link getSubscribedServices} method. + * + * The getSubscribedServices method returns an array of service types required by such instances, + * optionally keyed by the service names used internally. Service types that start with an interrogation + * mark "?" are optional, while the other ones are mandatory service dependencies. + * + * The injected service locators SHOULD NOT allow access to any other services not specified by the method. + * + * It is expected that ServiceSubscriber instances consume PSR-11-based service locators internally. + * This interface does not dictate any injection method for these service locators, although constructor + * injection is recommended. + * + * @author Nicolas Grekas + */ +interface ServiceSubscriberInterface +{ + /** + * Returns an array of service types (or {@see SubscribedService} objects) required + * by such instances, optionally keyed by the service names used internally. + * + * For mandatory dependencies: + * + * * ['logger' => 'Psr\Log\LoggerInterface'] means the objects use the "logger" name + * internally to fetch a service which must implement Psr\Log\LoggerInterface. + * * ['loggers' => 'Psr\Log\LoggerInterface[]'] means the objects use the "loggers" name + * internally to fetch an iterable of Psr\Log\LoggerInterface instances. + * * ['Psr\Log\LoggerInterface'] is a shortcut for + * * ['Psr\Log\LoggerInterface' => 'Psr\Log\LoggerInterface'] + * + * otherwise: + * + * * ['logger' => '?Psr\Log\LoggerInterface'] denotes an optional dependency + * * ['loggers' => '?Psr\Log\LoggerInterface[]'] denotes an optional iterable dependency + * * ['?Psr\Log\LoggerInterface'] is a shortcut for + * * ['Psr\Log\LoggerInterface' => '?Psr\Log\LoggerInterface'] + * + * additionally, an array of {@see SubscribedService}'s can be returned: + * + * * [new SubscribedService('logger', Psr\Log\LoggerInterface::class)] + * * [new SubscribedService(type: Psr\Log\LoggerInterface::class, nullable: true)] + * * [new SubscribedService('http_client', HttpClientInterface::class, attributes: new Target('githubApi'))] + * + * @return string[]|SubscribedService[] The required service types, optionally keyed by service names + */ + public static function getSubscribedServices(): array; +} diff --git a/vendor/symfony/service-contracts/ServiceSubscriberTrait.php b/vendor/symfony/service-contracts/ServiceSubscriberTrait.php new file mode 100644 index 00000000..f3b450cd --- /dev/null +++ b/vendor/symfony/service-contracts/ServiceSubscriberTrait.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Psr\Container\ContainerInterface; +use Symfony\Contracts\Service\Attribute\Required; +use Symfony\Contracts\Service\Attribute\SubscribedService; + +/** + * Implementation of ServiceSubscriberInterface that determines subscribed services from + * method return types. Service ids are available as "ClassName::methodName". + * + * @author Kevin Bond + */ +trait ServiceSubscriberTrait +{ + /** @var ContainerInterface */ + protected $container; + + public static function getSubscribedServices(): array + { + $services = method_exists(get_parent_class(self::class) ?: '', __FUNCTION__) ? parent::getSubscribedServices() : []; + + foreach ((new \ReflectionClass(self::class))->getMethods() as $method) { + if (self::class !== $method->getDeclaringClass()->name) { + continue; + } + + if (!$attribute = $method->getAttributes(SubscribedService::class)[0] ?? null) { + continue; + } + + if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) { + throw new \LogicException(sprintf('Cannot use "%s" on method "%s::%s()" (can only be used on non-static, non-abstract methods with no parameters).', SubscribedService::class, self::class, $method->name)); + } + + if (!$returnType = $method->getReturnType()) { + throw new \LogicException(sprintf('Cannot use "%s" on methods without a return type in "%s::%s()".', SubscribedService::class, $method->name, self::class)); + } + + /* @var SubscribedService $attribute */ + $attribute = $attribute->newInstance(); + $attribute->key ??= self::class.'::'.$method->name; + $attribute->type ??= $returnType instanceof \ReflectionNamedType ? $returnType->getName() : (string) $returnType; + $attribute->nullable = $returnType->allowsNull(); + + if ($attribute->attributes) { + $services[] = $attribute; + } else { + $services[$attribute->key] = ($attribute->nullable ? '?' : '').$attribute->type; + } + } + + return $services; + } + + #[Required] + public function setContainer(ContainerInterface $container): ?ContainerInterface + { + $ret = null; + if (method_exists(get_parent_class(self::class) ?: '', __FUNCTION__)) { + $ret = parent::setContainer($container); + } + + $this->container = $container; + + return $ret; + } +} diff --git a/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php b/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php new file mode 100644 index 00000000..07d12b4a --- /dev/null +++ b/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service\Test; + +class_alias(ServiceLocatorTestCase::class, ServiceLocatorTest::class); + +if (false) { + /** + * @deprecated since PHPUnit 9.6 + */ + class ServiceLocatorTest + { + } +} diff --git a/vendor/symfony/service-contracts/Test/ServiceLocatorTestCase.php b/vendor/symfony/service-contracts/Test/ServiceLocatorTestCase.php new file mode 100644 index 00000000..65a3fe33 --- /dev/null +++ b/vendor/symfony/service-contracts/Test/ServiceLocatorTestCase.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service\Test; + +use PHPUnit\Framework\TestCase; +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\ContainerInterface; +use Psr\Container\NotFoundExceptionInterface; +use Symfony\Contracts\Service\ServiceLocatorTrait; + +abstract class ServiceLocatorTestCase extends TestCase +{ + protected function getServiceLocator(array $factories): ContainerInterface + { + return new class($factories) implements ContainerInterface { + use ServiceLocatorTrait; + }; + } + + public function testHas() + { + $locator = $this->getServiceLocator([ + 'foo' => fn () => 'bar', + 'bar' => fn () => 'baz', + fn () => 'dummy', + ]); + + $this->assertTrue($locator->has('foo')); + $this->assertTrue($locator->has('bar')); + $this->assertFalse($locator->has('dummy')); + } + + public function testGet() + { + $locator = $this->getServiceLocator([ + 'foo' => fn () => 'bar', + 'bar' => fn () => 'baz', + ]); + + $this->assertSame('bar', $locator->get('foo')); + $this->assertSame('baz', $locator->get('bar')); + } + + public function testGetDoesNotMemoize() + { + $i = 0; + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$i) { + ++$i; + + return 'bar'; + }, + ]); + + $this->assertSame('bar', $locator->get('foo')); + $this->assertSame('bar', $locator->get('foo')); + $this->assertSame(2, $i); + } + + public function testThrowsOnUndefinedInternalService() + { + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$locator) { return $locator->get('bar'); }, + ]); + + if (!$this->getExpectedException()) { + $this->expectException(NotFoundExceptionInterface::class); + $this->expectExceptionMessage('The service "foo" has a dependency on a non-existent service "bar". This locator only knows about the "foo" service.'); + } + + $locator->get('foo'); + } + + public function testThrowsOnCircularReference() + { + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$locator) { return $locator->get('bar'); }, + 'bar' => function () use (&$locator) { return $locator->get('baz'); }, + 'baz' => function () use (&$locator) { return $locator->get('bar'); }, + ]); + + $this->expectException(ContainerExceptionInterface::class); + $this->expectExceptionMessage('Circular reference detected for service "bar", path: "bar -> baz -> bar".'); + + $locator->get('foo'); + } +} diff --git a/vendor/symfony/service-contracts/composer.json b/vendor/symfony/service-contracts/composer.json new file mode 100644 index 00000000..32bb8a31 --- /dev/null +++ b/vendor/symfony/service-contracts/composer.json @@ -0,0 +1,41 @@ +{ + "name": "symfony/service-contracts", + "type": "library", + "description": "Generic abstractions related to writing services", + "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "autoload": { + "psr-4": { "Symfony\\Contracts\\Service\\": "" }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/vendor/symfony/stopwatch/CHANGELOG.md b/vendor/symfony/stopwatch/CHANGELOG.md new file mode 100644 index 00000000..f2fd7d0f --- /dev/null +++ b/vendor/symfony/stopwatch/CHANGELOG.md @@ -0,0 +1,24 @@ +CHANGELOG +========= + +5.2 +--- + + * Add `name` argument to the `StopWatchEvent` constructor, accessible via a new `StopwatchEvent::getName()` + +5.0.0 +----- + + * Removed support for passing `null` as 1st (`$id`) argument of `Section::get()` method, pass a valid child section identifier instead. + +4.4.0 +----- + + * Deprecated passing `null` as 1st (`$id`) argument of `Section::get()` method, pass a valid child section identifier instead. + +3.4.0 +----- + + * added the `Stopwatch::reset()` method + * allowed to measure sub-millisecond times by introducing an argument to the + constructor of `Stopwatch` diff --git a/vendor/symfony/stopwatch/LICENSE b/vendor/symfony/stopwatch/LICENSE new file mode 100644 index 00000000..0138f8f0 --- /dev/null +++ b/vendor/symfony/stopwatch/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/stopwatch/README.md b/vendor/symfony/stopwatch/README.md new file mode 100644 index 00000000..13a9dfa5 --- /dev/null +++ b/vendor/symfony/stopwatch/README.md @@ -0,0 +1,42 @@ +Stopwatch Component +=================== + +The Stopwatch component provides a way to profile code. + +Getting Started +--------------- + +``` +$ composer require symfony/stopwatch +``` + +```php +use Symfony\Component\Stopwatch\Stopwatch; + +$stopwatch = new Stopwatch(); + +// optionally group events into sections (e.g. phases of the execution) +$stopwatch->openSection(); + +// starts event named 'eventName' +$stopwatch->start('eventName'); + +// ... run your code here + +// optionally, start a new "lap" time +$stopwatch->lap('foo'); + +// ... run your code here + +$event = $stopwatch->stop('eventName'); + +$stopwatch->stopSection('phase_1'); +``` + +Resources +--------- + + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/stopwatch/Section.php b/vendor/symfony/stopwatch/Section.php new file mode 100644 index 00000000..2a34db16 --- /dev/null +++ b/vendor/symfony/stopwatch/Section.php @@ -0,0 +1,157 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Stopwatch; + +/** + * Stopwatch section. + * + * @author Fabien Potencier + */ +class Section +{ + /** + * @var StopwatchEvent[] + */ + private array $events = []; + + private ?float $origin; + private bool $morePrecision; + private ?string $id = null; + + /** + * @var Section[] + */ + private array $children = []; + + /** + * @param float|null $origin Set the origin of the events in this section, use null to set their origin to their start time + * @param bool $morePrecision If true, time is stored as float to keep the original microsecond precision + */ + public function __construct(?float $origin = null, bool $morePrecision = false) + { + $this->origin = $origin; + $this->morePrecision = $morePrecision; + } + + /** + * Returns the child section. + */ + public function get(string $id): ?self + { + foreach ($this->children as $child) { + if ($id === $child->getId()) { + return $child; + } + } + + return null; + } + + /** + * Creates or re-opens a child section. + * + * @param string|null $id Null to create a new section, the identifier to re-open an existing one + */ + public function open(?string $id): self + { + if (null === $id || null === $session = $this->get($id)) { + $session = $this->children[] = new self(microtime(true) * 1000, $this->morePrecision); + } + + return $session; + } + + public function getId(): ?string + { + return $this->id; + } + + /** + * Sets the session identifier. + * + * @return $this + */ + public function setId(string $id): static + { + $this->id = $id; + + return $this; + } + + /** + * Starts an event. + */ + public function startEvent(string $name, ?string $category): StopwatchEvent + { + if (!isset($this->events[$name])) { + $this->events[$name] = new StopwatchEvent($this->origin ?: microtime(true) * 1000, $category, $this->morePrecision, $name); + } + + return $this->events[$name]->start(); + } + + /** + * Checks if the event was started. + */ + public function isEventStarted(string $name): bool + { + return isset($this->events[$name]) && $this->events[$name]->isStarted(); + } + + /** + * Stops an event. + * + * @throws \LogicException When the event has not been started + */ + public function stopEvent(string $name): StopwatchEvent + { + if (!isset($this->events[$name])) { + throw new \LogicException(sprintf('Event "%s" is not started.', $name)); + } + + return $this->events[$name]->stop(); + } + + /** + * Stops then restarts an event. + * + * @throws \LogicException When the event has not been started + */ + public function lap(string $name): StopwatchEvent + { + return $this->stopEvent($name)->start(); + } + + /** + * Returns a specific event by name. + * + * @throws \LogicException When the event is not known + */ + public function getEvent(string $name): StopwatchEvent + { + if (!isset($this->events[$name])) { + throw new \LogicException(sprintf('Event "%s" is not known.', $name)); + } + + return $this->events[$name]; + } + + /** + * Returns the events from this section. + * + * @return StopwatchEvent[] + */ + public function getEvents(): array + { + return $this->events; + } +} diff --git a/vendor/symfony/stopwatch/Stopwatch.php b/vendor/symfony/stopwatch/Stopwatch.php new file mode 100644 index 00000000..b50ca1b3 --- /dev/null +++ b/vendor/symfony/stopwatch/Stopwatch.php @@ -0,0 +1,153 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Stopwatch; + +use Symfony\Contracts\Service\ResetInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(Section::class); + +/** + * Stopwatch provides a way to profile code. + * + * @author Fabien Potencier + */ +class Stopwatch implements ResetInterface +{ + private bool $morePrecision; + + /** + * @var Section[] + */ + private array $sections; + + /** + * @var Section[] + */ + private array $activeSections; + + /** + * @param bool $morePrecision If true, time is stored as float to keep the original microsecond precision + */ + public function __construct(bool $morePrecision = false) + { + $this->morePrecision = $morePrecision; + $this->reset(); + } + + /** + * @return Section[] + */ + public function getSections(): array + { + return $this->sections; + } + + /** + * Creates a new section or re-opens an existing section. + * + * @param string|null $id The id of the session to re-open, null to create a new one + * + * @throws \LogicException When the section to re-open is not reachable + */ + public function openSection(?string $id = null): void + { + $current = end($this->activeSections); + + if (null !== $id && null === $current->get($id)) { + throw new \LogicException(sprintf('The section "%s" has been started at an other level and cannot be opened.', $id)); + } + + $this->start('__section__.child', 'section'); + $this->activeSections[] = $current->open($id); + $this->start('__section__'); + } + + /** + * Stops the last started section. + * + * The id parameter is used to retrieve the events from this section. + * + * @see getSectionEvents() + * + * @throws \LogicException When there's no started section to be stopped + */ + public function stopSection(string $id): void + { + $this->stop('__section__'); + + if (1 == \count($this->activeSections)) { + throw new \LogicException('There is no started section to stop.'); + } + + $this->sections[$id] = array_pop($this->activeSections)->setId($id); + $this->stop('__section__.child'); + } + + /** + * Starts an event. + */ + public function start(string $name, ?string $category = null): StopwatchEvent + { + return end($this->activeSections)->startEvent($name, $category); + } + + /** + * Checks if the event was started. + */ + public function isStarted(string $name): bool + { + return end($this->activeSections)->isEventStarted($name); + } + + /** + * Stops an event. + */ + public function stop(string $name): StopwatchEvent + { + return end($this->activeSections)->stopEvent($name); + } + + /** + * Stops then restarts an event. + */ + public function lap(string $name): StopwatchEvent + { + return end($this->activeSections)->stopEvent($name)->start(); + } + + /** + * Returns a specific event by name. + */ + public function getEvent(string $name): StopwatchEvent + { + return end($this->activeSections)->getEvent($name); + } + + /** + * Gets all events for a given section. + * + * @return StopwatchEvent[] + */ + public function getSectionEvents(string $id): array + { + return isset($this->sections[$id]) ? $this->sections[$id]->getEvents() : []; + } + + /** + * Resets the stopwatch to its original state. + */ + public function reset(): void + { + $this->sections = $this->activeSections = ['__root__' => new Section(null, $this->morePrecision)]; + } +} diff --git a/vendor/symfony/stopwatch/StopwatchEvent.php b/vendor/symfony/stopwatch/StopwatchEvent.php new file mode 100644 index 00000000..841bb3f4 --- /dev/null +++ b/vendor/symfony/stopwatch/StopwatchEvent.php @@ -0,0 +1,228 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Stopwatch; + +/** + * Represents an Event managed by Stopwatch. + * + * @author Fabien Potencier + */ +class StopwatchEvent +{ + /** + * @var StopwatchPeriod[] + */ + private array $periods = []; + + private float $origin; + private string $category; + private bool $morePrecision; + + /** + * @var float[] + */ + private array $started = []; + + private string $name; + + /** + * @param float $origin The origin time in milliseconds + * @param string|null $category The event category or null to use the default + * @param bool $morePrecision If true, time is stored as float to keep the original microsecond precision + * @param string|null $name The event name or null to define the name as default + * + * @throws \InvalidArgumentException When the raw time is not valid + */ + public function __construct(float $origin, ?string $category = null, bool $morePrecision = false, ?string $name = null) + { + $this->origin = $this->formatTime($origin); + $this->category = \is_string($category) ? $category : 'default'; + $this->morePrecision = $morePrecision; + $this->name = $name ?? 'default'; + } + + /** + * Gets the category. + */ + public function getCategory(): string + { + return $this->category; + } + + /** + * Gets the origin in milliseconds. + */ + public function getOrigin(): float + { + return $this->origin; + } + + /** + * Starts a new event period. + * + * @return $this + */ + public function start(): static + { + $this->started[] = $this->getNow(); + + return $this; + } + + /** + * Stops the last started event period. + * + * @return $this + * + * @throws \LogicException When stop() is called without a matching call to start() + */ + public function stop(): static + { + if (!\count($this->started)) { + throw new \LogicException('stop() called but start() has not been called before.'); + } + + $this->periods[] = new StopwatchPeriod(array_pop($this->started), $this->getNow(), $this->morePrecision); + + return $this; + } + + /** + * Checks if the event was started. + */ + public function isStarted(): bool + { + return !empty($this->started); + } + + /** + * Stops the current period and then starts a new one. + * + * @return $this + */ + public function lap(): static + { + return $this->stop()->start(); + } + + /** + * Stops all non already stopped periods. + */ + public function ensureStopped(): void + { + while (\count($this->started)) { + $this->stop(); + } + } + + /** + * Gets all event periods. + * + * @return StopwatchPeriod[] + */ + public function getPeriods(): array + { + return $this->periods; + } + + /** + * Gets the relative time of the start of the first period in milliseconds. + */ + public function getStartTime(): int|float + { + if (isset($this->periods[0])) { + return $this->periods[0]->getStartTime(); + } + + if ($this->started) { + return $this->started[0]; + } + + return 0; + } + + /** + * Gets the relative time of the end of the last period in milliseconds. + */ + public function getEndTime(): int|float + { + $count = \count($this->periods); + + return $count ? $this->periods[$count - 1]->getEndTime() : 0; + } + + /** + * Gets the duration of the events in milliseconds (including all periods). + */ + public function getDuration(): int|float + { + $periods = $this->periods; + $left = \count($this->started); + + for ($i = $left - 1; $i >= 0; --$i) { + $periods[] = new StopwatchPeriod($this->started[$i], $this->getNow(), $this->morePrecision); + } + + $total = 0; + foreach ($periods as $period) { + $total += $period->getDuration(); + } + + return $total; + } + + /** + * Gets the max memory usage of all periods in bytes. + */ + public function getMemory(): int + { + $memory = 0; + foreach ($this->periods as $period) { + if ($period->getMemory() > $memory) { + $memory = $period->getMemory(); + } + } + + return $memory; + } + + /** + * Return the current time relative to origin in milliseconds. + */ + protected function getNow(): float + { + return $this->formatTime(microtime(true) * 1000 - $this->origin); + } + + /** + * Formats a time. + * + * @throws \InvalidArgumentException When the raw time is not valid + */ + private function formatTime(float $time): float + { + return round($time, 1); + } + + /** + * Gets the event name. + */ + public function getName(): string + { + return $this->name; + } + + public function __toString(): string + { + return sprintf('%s/%s: %.2F MiB - %d ms', $this->getCategory(), $this->getName(), $this->getMemory() / 1024 / 1024, $this->getDuration()); + } +} diff --git a/vendor/symfony/stopwatch/StopwatchPeriod.php b/vendor/symfony/stopwatch/StopwatchPeriod.php new file mode 100644 index 00000000..41e8dccb --- /dev/null +++ b/vendor/symfony/stopwatch/StopwatchPeriod.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Stopwatch; + +/** + * Represents an Period for an Event. + * + * @author Fabien Potencier + */ +class StopwatchPeriod +{ + private int|float $start; + private int|float $end; + private int $memory; + + /** + * @param int|float $start The relative time of the start of the period (in milliseconds) + * @param int|float $end The relative time of the end of the period (in milliseconds) + * @param bool $morePrecision If true, time is stored as float to keep the original microsecond precision + */ + public function __construct(int|float $start, int|float $end, bool $morePrecision = false) + { + $this->start = $morePrecision ? (float) $start : (int) $start; + $this->end = $morePrecision ? (float) $end : (int) $end; + $this->memory = memory_get_usage(true); + } + + /** + * Gets the relative time of the start of the period in milliseconds. + */ + public function getStartTime(): int|float + { + return $this->start; + } + + /** + * Gets the relative time of the end of the period in milliseconds. + */ + public function getEndTime(): int|float + { + return $this->end; + } + + /** + * Gets the time spent in this period in milliseconds. + */ + public function getDuration(): int|float + { + return $this->end - $this->start; + } + + /** + * Gets the memory usage in bytes. + */ + public function getMemory(): int + { + return $this->memory; + } + + public function __toString(): string + { + return sprintf('%.2F MiB - %d ms', $this->getMemory() / 1024 / 1024, $this->getDuration()); + } +} diff --git a/vendor/symfony/stopwatch/composer.json b/vendor/symfony/stopwatch/composer.json new file mode 100644 index 00000000..35568695 --- /dev/null +++ b/vendor/symfony/stopwatch/composer.json @@ -0,0 +1,29 @@ +{ + "name": "symfony/stopwatch", + "type": "library", + "description": "Provides a way to profile code", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "symfony/service-contracts": "^2.5|^3" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Stopwatch\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/string/AbstractString.php b/vendor/symfony/string/AbstractString.php new file mode 100644 index 00000000..253d2dcb --- /dev/null +++ b/vendor/symfony/string/AbstractString.php @@ -0,0 +1,702 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String; + +use Symfony\Component\String\Exception\ExceptionInterface; +use Symfony\Component\String\Exception\InvalidArgumentException; +use Symfony\Component\String\Exception\RuntimeException; + +/** + * Represents a string of abstract characters. + * + * Unicode defines 3 types of "characters" (bytes, code points and grapheme clusters). + * This class is the abstract type to use as a type-hint when the logic you want to + * implement doesn't care about the exact variant it deals with. + * + * @author Nicolas Grekas + * @author Hugo Hamon + * + * @throws ExceptionInterface + */ +abstract class AbstractString implements \Stringable, \JsonSerializable +{ + public const PREG_PATTERN_ORDER = \PREG_PATTERN_ORDER; + public const PREG_SET_ORDER = \PREG_SET_ORDER; + public const PREG_OFFSET_CAPTURE = \PREG_OFFSET_CAPTURE; + public const PREG_UNMATCHED_AS_NULL = \PREG_UNMATCHED_AS_NULL; + + public const PREG_SPLIT = 0; + public const PREG_SPLIT_NO_EMPTY = \PREG_SPLIT_NO_EMPTY; + public const PREG_SPLIT_DELIM_CAPTURE = \PREG_SPLIT_DELIM_CAPTURE; + public const PREG_SPLIT_OFFSET_CAPTURE = \PREG_SPLIT_OFFSET_CAPTURE; + + protected string $string = ''; + protected ?bool $ignoreCase = false; + + abstract public function __construct(string $string = ''); + + /** + * Unwraps instances of AbstractString back to strings. + * + * @return string[]|array + */ + public static function unwrap(array $values): array + { + foreach ($values as $k => $v) { + if ($v instanceof self) { + $values[$k] = $v->__toString(); + } elseif (\is_array($v) && $values[$k] !== $v = static::unwrap($v)) { + $values[$k] = $v; + } + } + + return $values; + } + + /** + * Wraps (and normalizes) strings in instances of AbstractString. + * + * @return static[]|array + */ + public static function wrap(array $values): array + { + $i = 0; + $keys = null; + + foreach ($values as $k => $v) { + if (\is_string($k) && '' !== $k && $k !== $j = (string) new static($k)) { + $keys ??= array_keys($values); + $keys[$i] = $j; + } + + if (\is_string($v)) { + $values[$k] = new static($v); + } elseif (\is_array($v) && $values[$k] !== $v = static::wrap($v)) { + $values[$k] = $v; + } + + ++$i; + } + + return null !== $keys ? array_combine($keys, $values) : $values; + } + + /** + * @param string|string[] $needle + */ + public function after(string|iterable $needle, bool $includeNeedle = false, int $offset = 0): static + { + $str = clone $this; + $i = \PHP_INT_MAX; + + if (\is_string($needle)) { + $needle = [$needle]; + } + + foreach ($needle as $n) { + $n = (string) $n; + $j = $this->indexOf($n, $offset); + + if (null !== $j && $j < $i) { + $i = $j; + $str->string = $n; + } + } + + if (\PHP_INT_MAX === $i) { + return $str; + } + + if (!$includeNeedle) { + $i += $str->length(); + } + + return $this->slice($i); + } + + /** + * @param string|string[] $needle + */ + public function afterLast(string|iterable $needle, bool $includeNeedle = false, int $offset = 0): static + { + $str = clone $this; + $i = null; + + if (\is_string($needle)) { + $needle = [$needle]; + } + + foreach ($needle as $n) { + $n = (string) $n; + $j = $this->indexOfLast($n, $offset); + + if (null !== $j && $j >= $i) { + $i = $offset = $j; + $str->string = $n; + } + } + + if (null === $i) { + return $str; + } + + if (!$includeNeedle) { + $i += $str->length(); + } + + return $this->slice($i); + } + + abstract public function append(string ...$suffix): static; + + /** + * @param string|string[] $needle + */ + public function before(string|iterable $needle, bool $includeNeedle = false, int $offset = 0): static + { + $str = clone $this; + $i = \PHP_INT_MAX; + + if (\is_string($needle)) { + $needle = [$needle]; + } + + foreach ($needle as $n) { + $n = (string) $n; + $j = $this->indexOf($n, $offset); + + if (null !== $j && $j < $i) { + $i = $j; + $str->string = $n; + } + } + + if (\PHP_INT_MAX === $i) { + return $str; + } + + if ($includeNeedle) { + $i += $str->length(); + } + + return $this->slice(0, $i); + } + + /** + * @param string|string[] $needle + */ + public function beforeLast(string|iterable $needle, bool $includeNeedle = false, int $offset = 0): static + { + $str = clone $this; + $i = null; + + if (\is_string($needle)) { + $needle = [$needle]; + } + + foreach ($needle as $n) { + $n = (string) $n; + $j = $this->indexOfLast($n, $offset); + + if (null !== $j && $j >= $i) { + $i = $offset = $j; + $str->string = $n; + } + } + + if (null === $i) { + return $str; + } + + if ($includeNeedle) { + $i += $str->length(); + } + + return $this->slice(0, $i); + } + + /** + * @return int[] + */ + public function bytesAt(int $offset): array + { + $str = $this->slice($offset, 1); + + return '' === $str->string ? [] : array_values(unpack('C*', $str->string)); + } + + abstract public function camel(): static; + + /** + * @return static[] + */ + abstract public function chunk(int $length = 1): array; + + public function collapseWhitespace(): static + { + $str = clone $this; + $str->string = trim(preg_replace("/(?:[ \n\r\t\x0C]{2,}+|[\n\r\t\x0C])/", ' ', $str->string), " \n\r\t\x0C"); + + return $str; + } + + /** + * @param string|string[] $needle + */ + public function containsAny(string|iterable $needle): bool + { + return null !== $this->indexOf($needle); + } + + /** + * @param string|string[] $suffix + */ + public function endsWith(string|iterable $suffix): bool + { + if (\is_string($suffix)) { + throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); + } + + foreach ($suffix as $s) { + if ($this->endsWith((string) $s)) { + return true; + } + } + + return false; + } + + public function ensureEnd(string $suffix): static + { + if (!$this->endsWith($suffix)) { + return $this->append($suffix); + } + + $suffix = preg_quote($suffix); + $regex = '{('.$suffix.')(?:'.$suffix.')++$}D'; + + return $this->replaceMatches($regex.($this->ignoreCase ? 'i' : ''), '$1'); + } + + public function ensureStart(string $prefix): static + { + $prefix = new static($prefix); + + if (!$this->startsWith($prefix)) { + return $this->prepend($prefix); + } + + $str = clone $this; + $i = $prefixLen = $prefix->length(); + + while ($this->indexOf($prefix, $i) === $i) { + $str = $str->slice($prefixLen); + $i += $prefixLen; + } + + return $str; + } + + /** + * @param string|string[] $string + */ + public function equalsTo(string|iterable $string): bool + { + if (\is_string($string)) { + throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); + } + + foreach ($string as $s) { + if ($this->equalsTo((string) $s)) { + return true; + } + } + + return false; + } + + abstract public function folded(): static; + + public function ignoreCase(): static + { + $str = clone $this; + $str->ignoreCase = true; + + return $str; + } + + /** + * @param string|string[] $needle + */ + public function indexOf(string|iterable $needle, int $offset = 0): ?int + { + if (\is_string($needle)) { + throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); + } + + $i = \PHP_INT_MAX; + + foreach ($needle as $n) { + $j = $this->indexOf((string) $n, $offset); + + if (null !== $j && $j < $i) { + $i = $j; + } + } + + return \PHP_INT_MAX === $i ? null : $i; + } + + /** + * @param string|string[] $needle + */ + public function indexOfLast(string|iterable $needle, int $offset = 0): ?int + { + if (\is_string($needle)) { + throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); + } + + $i = null; + + foreach ($needle as $n) { + $j = $this->indexOfLast((string) $n, $offset); + + if (null !== $j && $j >= $i) { + $i = $offset = $j; + } + } + + return $i; + } + + public function isEmpty(): bool + { + return '' === $this->string; + } + + abstract public function join(array $strings, ?string $lastGlue = null): static; + + public function jsonSerialize(): string + { + return $this->string; + } + + abstract public function length(): int; + + abstract public function lower(): static; + + /** + * Matches the string using a regular expression. + * + * Pass PREG_PATTERN_ORDER or PREG_SET_ORDER as $flags to get all occurrences matching the regular expression. + * + * @return array All matches in a multi-dimensional array ordered according to flags + */ + abstract public function match(string $regexp, int $flags = 0, int $offset = 0): array; + + abstract public function padBoth(int $length, string $padStr = ' '): static; + + abstract public function padEnd(int $length, string $padStr = ' '): static; + + abstract public function padStart(int $length, string $padStr = ' '): static; + + abstract public function prepend(string ...$prefix): static; + + public function repeat(int $multiplier): static + { + if (0 > $multiplier) { + throw new InvalidArgumentException(sprintf('Multiplier must be positive, %d given.', $multiplier)); + } + + $str = clone $this; + $str->string = str_repeat($str->string, $multiplier); + + return $str; + } + + abstract public function replace(string $from, string $to): static; + + abstract public function replaceMatches(string $fromRegexp, string|callable $to): static; + + abstract public function reverse(): static; + + abstract public function slice(int $start = 0, ?int $length = null): static; + + abstract public function snake(): static; + + abstract public function splice(string $replacement, int $start = 0, ?int $length = null): static; + + /** + * @return static[] + */ + public function split(string $delimiter, ?int $limit = null, ?int $flags = null): array + { + if (null === $flags) { + throw new \TypeError('Split behavior when $flags is null must be implemented by child classes.'); + } + + if ($this->ignoreCase) { + $delimiter .= 'i'; + } + + set_error_handler(static fn ($t, $m) => throw new InvalidArgumentException($m)); + + try { + if (false === $chunks = preg_split($delimiter, $this->string, $limit, $flags)) { + throw new RuntimeException('Splitting failed with error: '.preg_last_error_msg()); + } + } finally { + restore_error_handler(); + } + + $str = clone $this; + + if (self::PREG_SPLIT_OFFSET_CAPTURE & $flags) { + foreach ($chunks as &$chunk) { + $str->string = $chunk[0]; + $chunk[0] = clone $str; + } + } else { + foreach ($chunks as &$chunk) { + $str->string = $chunk; + $chunk = clone $str; + } + } + + return $chunks; + } + + /** + * @param string|string[] $prefix + */ + public function startsWith(string|iterable $prefix): bool + { + if (\is_string($prefix)) { + throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); + } + + foreach ($prefix as $prefix) { + if ($this->startsWith((string) $prefix)) { + return true; + } + } + + return false; + } + + abstract public function title(bool $allWords = false): static; + + public function toByteString(?string $toEncoding = null): ByteString + { + $b = new ByteString(); + + $toEncoding = \in_array($toEncoding, ['utf8', 'utf-8', 'UTF8'], true) ? 'UTF-8' : $toEncoding; + + if (null === $toEncoding || $toEncoding === $fromEncoding = $this instanceof AbstractUnicodeString || preg_match('//u', $b->string) ? 'UTF-8' : 'Windows-1252') { + $b->string = $this->string; + + return $b; + } + + try { + $b->string = mb_convert_encoding($this->string, $toEncoding, 'UTF-8'); + } catch (\ValueError $e) { + if (!\function_exists('iconv')) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + + $b->string = iconv('UTF-8', $toEncoding, $this->string); + } + + return $b; + } + + public function toCodePointString(): CodePointString + { + return new CodePointString($this->string); + } + + public function toString(): string + { + return $this->string; + } + + public function toUnicodeString(): UnicodeString + { + return new UnicodeString($this->string); + } + + abstract public function trim(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): static; + + abstract public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): static; + + /** + * @param string|string[] $prefix + */ + public function trimPrefix($prefix): static + { + if (\is_array($prefix) || $prefix instanceof \Traversable) { // don't use is_iterable(), it's slow + foreach ($prefix as $s) { + $t = $this->trimPrefix($s); + + if ($t->string !== $this->string) { + return $t; + } + } + + return clone $this; + } + + $str = clone $this; + + if ($prefix instanceof self) { + $prefix = $prefix->string; + } else { + $prefix = (string) $prefix; + } + + if ('' !== $prefix && \strlen($this->string) >= \strlen($prefix) && 0 === substr_compare($this->string, $prefix, 0, \strlen($prefix), $this->ignoreCase)) { + $str->string = substr($this->string, \strlen($prefix)); + } + + return $str; + } + + abstract public function trimStart(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): static; + + /** + * @param string|string[] $suffix + */ + public function trimSuffix($suffix): static + { + if (\is_array($suffix) || $suffix instanceof \Traversable) { // don't use is_iterable(), it's slow + foreach ($suffix as $s) { + $t = $this->trimSuffix($s); + + if ($t->string !== $this->string) { + return $t; + } + } + + return clone $this; + } + + $str = clone $this; + + if ($suffix instanceof self) { + $suffix = $suffix->string; + } else { + $suffix = (string) $suffix; + } + + if ('' !== $suffix && \strlen($this->string) >= \strlen($suffix) && 0 === substr_compare($this->string, $suffix, -\strlen($suffix), null, $this->ignoreCase)) { + $str->string = substr($this->string, 0, -\strlen($suffix)); + } + + return $str; + } + + public function truncate(int $length, string $ellipsis = '', bool $cut = true): static + { + $stringLength = $this->length(); + + if ($stringLength <= $length) { + return clone $this; + } + + $ellipsisLength = '' !== $ellipsis ? (new static($ellipsis))->length() : 0; + + if ($length < $ellipsisLength) { + $ellipsisLength = 0; + } + + if (!$cut) { + if (null === $length = $this->indexOf([' ', "\r", "\n", "\t"], ($length ?: 1) - 1)) { + return clone $this; + } + + $length += $ellipsisLength; + } + + $str = $this->slice(0, $length - $ellipsisLength); + + return $ellipsisLength ? $str->trimEnd()->append($ellipsis) : $str; + } + + abstract public function upper(): static; + + /** + * Returns the printable length on a terminal. + */ + abstract public function width(bool $ignoreAnsiDecoration = true): int; + + public function wordwrap(int $width = 75, string $break = "\n", bool $cut = false): static + { + $lines = '' !== $break ? $this->split($break) : [clone $this]; + $chars = []; + $mask = ''; + + if (1 === \count($lines) && '' === $lines[0]->string) { + return $lines[0]; + } + + foreach ($lines as $i => $line) { + if ($i) { + $chars[] = $break; + $mask .= '#'; + } + + foreach ($line->chunk() as $char) { + $chars[] = $char->string; + $mask .= ' ' === $char->string ? ' ' : '?'; + } + } + + $string = ''; + $j = 0; + $b = $i = -1; + $mask = wordwrap($mask, $width, '#', $cut); + + while (false !== $b = strpos($mask, '#', $b + 1)) { + for (++$i; $i < $b; ++$i) { + $string .= $chars[$j]; + unset($chars[$j++]); + } + + if ($break === $chars[$j] || ' ' === $chars[$j]) { + unset($chars[$j++]); + } + + $string .= $break; + } + + $str = clone $this; + $str->string = $string.implode('', $chars); + + return $str; + } + + public function __sleep(): array + { + return ['string']; + } + + public function __clone() + { + $this->ignoreCase = false; + } + + public function __toString(): string + { + return $this->string; + } +} diff --git a/vendor/symfony/string/AbstractUnicodeString.php b/vendor/symfony/string/AbstractUnicodeString.php new file mode 100644 index 00000000..673ad8ce --- /dev/null +++ b/vendor/symfony/string/AbstractUnicodeString.php @@ -0,0 +1,590 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String; + +use Symfony\Component\String\Exception\ExceptionInterface; +use Symfony\Component\String\Exception\InvalidArgumentException; +use Symfony\Component\String\Exception\RuntimeException; + +/** + * Represents a string of abstract Unicode characters. + * + * Unicode defines 3 types of "characters" (bytes, code points and grapheme clusters). + * This class is the abstract type to use as a type-hint when the logic you want to + * implement is Unicode-aware but doesn't care about code points vs grapheme clusters. + * + * @author Nicolas Grekas + * + * @throws ExceptionInterface + */ +abstract class AbstractUnicodeString extends AbstractString +{ + public const NFC = \Normalizer::NFC; + public const NFD = \Normalizer::NFD; + public const NFKC = \Normalizer::NFKC; + public const NFKD = \Normalizer::NFKD; + + // all ASCII letters sorted by typical frequency of occurrence + private const ASCII = "\x20\x65\x69\x61\x73\x6E\x74\x72\x6F\x6C\x75\x64\x5D\x5B\x63\x6D\x70\x27\x0A\x67\x7C\x68\x76\x2E\x66\x62\x2C\x3A\x3D\x2D\x71\x31\x30\x43\x32\x2A\x79\x78\x29\x28\x4C\x39\x41\x53\x2F\x50\x22\x45\x6A\x4D\x49\x6B\x33\x3E\x35\x54\x3C\x44\x34\x7D\x42\x7B\x38\x46\x77\x52\x36\x37\x55\x47\x4E\x3B\x4A\x7A\x56\x23\x48\x4F\x57\x5F\x26\x21\x4B\x3F\x58\x51\x25\x59\x5C\x09\x5A\x2B\x7E\x5E\x24\x40\x60\x7F\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"; + + // the subset of folded case mappings that is not in lower case mappings + private const FOLD_FROM = ['İ', 'µ', 'ſ', "\xCD\x85", 'ς', 'ϐ', 'ϑ', 'ϕ', 'ϖ', 'ϰ', 'ϱ', 'ϵ', 'ẛ', "\xE1\xBE\xBE", 'ß', 'ʼn', 'ǰ', 'ΐ', 'ΰ', 'և', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'ẚ', 'ẞ', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ᾀ', 'ᾁ', 'ᾂ', 'ᾃ', 'ᾄ', 'ᾅ', 'ᾆ', 'ᾇ', 'ᾈ', 'ᾉ', 'ᾊ', 'ᾋ', 'ᾌ', 'ᾍ', 'ᾎ', 'ᾏ', 'ᾐ', 'ᾑ', 'ᾒ', 'ᾓ', 'ᾔ', 'ᾕ', 'ᾖ', 'ᾗ', 'ᾘ', 'ᾙ', 'ᾚ', 'ᾛ', 'ᾜ', 'ᾝ', 'ᾞ', 'ᾟ', 'ᾠ', 'ᾡ', 'ᾢ', 'ᾣ', 'ᾤ', 'ᾥ', 'ᾦ', 'ᾧ', 'ᾨ', 'ᾩ', 'ᾪ', 'ᾫ', 'ᾬ', 'ᾭ', 'ᾮ', 'ᾯ', 'ᾲ', 'ᾳ', 'ᾴ', 'ᾶ', 'ᾷ', 'ᾼ', 'ῂ', 'ῃ', 'ῄ', 'ῆ', 'ῇ', 'ῌ', 'ῒ', 'ῖ', 'ῗ', 'ῢ', 'ῤ', 'ῦ', 'ῧ', 'ῲ', 'ῳ', 'ῴ', 'ῶ', 'ῷ', 'ῼ', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'ſt', 'st', 'ﬓ', 'ﬔ', 'ﬕ', 'ﬖ', 'ﬗ']; + private const FOLD_TO = ['i̇', 'μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', 'ṡ', 'ι', 'ss', 'ʼn', 'ǰ', 'ΐ', 'ΰ', 'եւ', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'aʾ', 'ss', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ἀι', 'ἁι', 'ἂι', 'ἃι', 'ἄι', 'ἅι', 'ἆι', 'ἇι', 'ἀι', 'ἁι', 'ἂι', 'ἃι', 'ἄι', 'ἅι', 'ἆι', 'ἇι', 'ἠι', 'ἡι', 'ἢι', 'ἣι', 'ἤι', 'ἥι', 'ἦι', 'ἧι', 'ἠι', 'ἡι', 'ἢι', 'ἣι', 'ἤι', 'ἥι', 'ἦι', 'ἧι', 'ὠι', 'ὡι', 'ὢι', 'ὣι', 'ὤι', 'ὥι', 'ὦι', 'ὧι', 'ὠι', 'ὡι', 'ὢι', 'ὣι', 'ὤι', 'ὥι', 'ὦι', 'ὧι', 'ὰι', 'αι', 'άι', 'ᾶ', 'ᾶι', 'αι', 'ὴι', 'ηι', 'ήι', 'ῆ', 'ῆι', 'ηι', 'ῒ', 'ῖ', 'ῗ', 'ῢ', 'ῤ', 'ῦ', 'ῧ', 'ὼι', 'ωι', 'ώι', 'ῶ', 'ῶι', 'ωι', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'st', 'st', 'մն', 'մե', 'մի', 'վն', 'մխ']; + + // the subset of https://github.com/unicode-org/cldr/blob/master/common/transforms/Latin-ASCII.xml that is not in NFKD + private const TRANSLIT_FROM = ['Æ', 'Ð', 'Ø', 'Þ', 'ß', 'æ', 'ð', 'ø', 'þ', 'Đ', 'đ', 'Ħ', 'ħ', 'ı', 'ĸ', 'Ŀ', 'ŀ', 'Ł', 'ł', 'ʼn', 'Ŋ', 'ŋ', 'Œ', 'œ', 'Ŧ', 'ŧ', 'ƀ', 'Ɓ', 'Ƃ', 'ƃ', 'Ƈ', 'ƈ', 'Ɖ', 'Ɗ', 'Ƌ', 'ƌ', 'Ɛ', 'Ƒ', 'ƒ', 'Ɠ', 'ƕ', 'Ɩ', 'Ɨ', 'Ƙ', 'ƙ', 'ƚ', 'Ɲ', 'ƞ', 'Ƣ', 'ƣ', 'Ƥ', 'ƥ', 'ƫ', 'Ƭ', 'ƭ', 'Ʈ', 'Ʋ', 'Ƴ', 'ƴ', 'Ƶ', 'ƶ', 'DŽ', 'Dž', 'dž', 'Ǥ', 'ǥ', 'ȡ', 'Ȥ', 'ȥ', 'ȴ', 'ȵ', 'ȶ', 'ȷ', 'ȸ', 'ȹ', 'Ⱥ', 'Ȼ', 'ȼ', 'Ƚ', 'Ⱦ', 'ȿ', 'ɀ', 'Ƀ', 'Ʉ', 'Ɇ', 'ɇ', 'Ɉ', 'ɉ', 'Ɍ', 'ɍ', 'Ɏ', 'ɏ', 'ɓ', 'ɕ', 'ɖ', 'ɗ', 'ɛ', 'ɟ', 'ɠ', 'ɡ', 'ɢ', 'ɦ', 'ɧ', 'ɨ', 'ɪ', 'ɫ', 'ɬ', 'ɭ', 'ɱ', 'ɲ', 'ɳ', 'ɴ', 'ɶ', 'ɼ', 'ɽ', 'ɾ', 'ʀ', 'ʂ', 'ʈ', 'ʉ', 'ʋ', 'ʏ', 'ʐ', 'ʑ', 'ʙ', 'ʛ', 'ʜ', 'ʝ', 'ʟ', 'ʠ', 'ʣ', 'ʥ', 'ʦ', 'ʪ', 'ʫ', 'ᴀ', 'ᴁ', 'ᴃ', 'ᴄ', 'ᴅ', 'ᴆ', 'ᴇ', 'ᴊ', 'ᴋ', 'ᴌ', 'ᴍ', 'ᴏ', 'ᴘ', 'ᴛ', 'ᴜ', 'ᴠ', 'ᴡ', 'ᴢ', 'ᵫ', 'ᵬ', 'ᵭ', 'ᵮ', 'ᵯ', 'ᵰ', 'ᵱ', 'ᵲ', 'ᵳ', 'ᵴ', 'ᵵ', 'ᵶ', 'ᵺ', 'ᵻ', 'ᵽ', 'ᵾ', 'ᶀ', 'ᶁ', 'ᶂ', 'ᶃ', 'ᶄ', 'ᶅ', 'ᶆ', 'ᶇ', 'ᶈ', 'ᶉ', 'ᶊ', 'ᶌ', 'ᶍ', 'ᶎ', 'ᶏ', 'ᶑ', 'ᶒ', 'ᶓ', 'ᶖ', 'ᶙ', 'ẚ', 'ẜ', 'ẝ', 'ẞ', 'Ỻ', 'ỻ', 'Ỽ', 'ỽ', 'Ỿ', 'ỿ', '©', '®', '₠', '₢', '₣', '₤', '₧', '₺', '₹', 'ℌ', '℞', '㎧', '㎮', '㏆', '㏗', '㏞', '㏟', '¼', '½', '¾', '⅓', '⅔', '⅕', '⅖', '⅗', '⅘', '⅙', '⅚', '⅛', '⅜', '⅝', '⅞', '⅟', '〇', '‘', '’', '‚', '‛', '“', '”', '„', '‟', '′', '″', '〝', '〞', '«', '»', '‹', '›', '‐', '‑', '‒', '–', '—', '―', '︱', '︲', '﹘', '‖', '⁄', '⁅', '⁆', '⁎', '、', '。', '〈', '〉', '《', '》', '〔', '〕', '〘', '〙', '〚', '〛', '︑', '︒', '︹', '︺', '︽', '︾', '︿', '﹀', '﹑', '﹝', '﹞', '⦅', '⦆', '。', '、', '×', '÷', '−', '∕', '∖', '∣', '∥', '≪', '≫', '⦅', '⦆']; + private const TRANSLIT_TO = ['AE', 'D', 'O', 'TH', 'ss', 'ae', 'd', 'o', 'th', 'D', 'd', 'H', 'h', 'i', 'q', 'L', 'l', 'L', 'l', '\'n', 'N', 'n', 'OE', 'oe', 'T', 't', 'b', 'B', 'B', 'b', 'C', 'c', 'D', 'D', 'D', 'd', 'E', 'F', 'f', 'G', 'hv', 'I', 'I', 'K', 'k', 'l', 'N', 'n', 'OI', 'oi', 'P', 'p', 't', 'T', 't', 'T', 'V', 'Y', 'y', 'Z', 'z', 'DZ', 'Dz', 'dz', 'G', 'g', 'd', 'Z', 'z', 'l', 'n', 't', 'j', 'db', 'qp', 'A', 'C', 'c', 'L', 'T', 's', 'z', 'B', 'U', 'E', 'e', 'J', 'j', 'R', 'r', 'Y', 'y', 'b', 'c', 'd', 'd', 'e', 'j', 'g', 'g', 'G', 'h', 'h', 'i', 'I', 'l', 'l', 'l', 'm', 'n', 'n', 'N', 'OE', 'r', 'r', 'r', 'R', 's', 't', 'u', 'v', 'Y', 'z', 'z', 'B', 'G', 'H', 'j', 'L', 'q', 'dz', 'dz', 'ts', 'ls', 'lz', 'A', 'AE', 'B', 'C', 'D', 'D', 'E', 'J', 'K', 'L', 'M', 'O', 'P', 'T', 'U', 'V', 'W', 'Z', 'ue', 'b', 'd', 'f', 'm', 'n', 'p', 'r', 'r', 's', 't', 'z', 'th', 'I', 'p', 'U', 'b', 'd', 'f', 'g', 'k', 'l', 'm', 'n', 'p', 'r', 's', 'v', 'x', 'z', 'a', 'd', 'e', 'e', 'i', 'u', 'a', 's', 's', 'SS', 'LL', 'll', 'V', 'v', 'Y', 'y', '(C)', '(R)', 'CE', 'Cr', 'Fr.', 'L.', 'Pts', 'TL', 'Rs', 'x', 'Rx', 'm/s', 'rad/s', 'C/kg', 'pH', 'V/m', 'A/m', ' 1/4', ' 1/2', ' 3/4', ' 1/3', ' 2/3', ' 1/5', ' 2/5', ' 3/5', ' 4/5', ' 1/6', ' 5/6', ' 1/8', ' 3/8', ' 5/8', ' 7/8', ' 1/', '0', '\'', '\'', ',', '\'', '"', '"', ',,', '"', '\'', '"', '"', '"', '<<', '>>', '<', '>', '-', '-', '-', '-', '-', '-', '-', '-', '-', '||', '/', '[', ']', '*', ',', '.', '<', '>', '<<', '>>', '[', ']', '[', ']', '[', ']', ',', '.', '[', ']', '<<', '>>', '<', '>', ',', '[', ']', '((', '))', '.', ',', '*', '/', '-', '/', '\\', '|', '||', '<<', '>>', '((', '))']; + + private static array $transliterators = []; + private static array $tableZero; + private static array $tableWide; + + public static function fromCodePoints(int ...$codes): static + { + $string = ''; + + foreach ($codes as $code) { + if (0x80 > $code %= 0x200000) { + $string .= \chr($code); + } elseif (0x800 > $code) { + $string .= \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F); + } elseif (0x10000 > $code) { + $string .= \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } else { + $string .= \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } + } + + return new static($string); + } + + /** + * Generic UTF-8 to ASCII transliteration. + * + * Install the intl extension for best results. + * + * @param string[]|\Transliterator[]|\Closure[] $rules See "*-Latin" rules from Transliterator::listIDs() + */ + public function ascii(array $rules = []): self + { + $str = clone $this; + $s = $str->string; + $str->string = ''; + + array_unshift($rules, 'nfd'); + $rules[] = 'latin-ascii'; + + if (\function_exists('transliterator_transliterate')) { + $rules[] = 'any-latin/bgn'; + } + + $rules[] = 'nfkd'; + $rules[] = '[:nonspacing mark:] remove'; + + while (\strlen($s) - 1 > $i = strspn($s, self::ASCII)) { + if (0 < --$i) { + $str->string .= substr($s, 0, $i); + $s = substr($s, $i); + } + + if (!$rule = array_shift($rules)) { + $rules = []; // An empty rule interrupts the next ones + } + + if ($rule instanceof \Transliterator) { + $s = $rule->transliterate($s); + } elseif ($rule instanceof \Closure) { + $s = $rule($s); + } elseif ($rule) { + if ('nfd' === $rule = strtolower($rule)) { + normalizer_is_normalized($s, self::NFD) ?: $s = normalizer_normalize($s, self::NFD); + } elseif ('nfkd' === $rule) { + normalizer_is_normalized($s, self::NFKD) ?: $s = normalizer_normalize($s, self::NFKD); + } elseif ('[:nonspacing mark:] remove' === $rule) { + $s = preg_replace('/\p{Mn}++/u', '', $s); + } elseif ('latin-ascii' === $rule) { + $s = str_replace(self::TRANSLIT_FROM, self::TRANSLIT_TO, $s); + } elseif ('de-ascii' === $rule) { + $s = preg_replace("/([AUO])\u{0308}(?=\p{Ll})/u", '$1e', $s); + $s = str_replace(["a\u{0308}", "o\u{0308}", "u\u{0308}", "A\u{0308}", "O\u{0308}", "U\u{0308}"], ['ae', 'oe', 'ue', 'AE', 'OE', 'UE'], $s); + } elseif (\function_exists('transliterator_transliterate')) { + if (null === $transliterator = self::$transliterators[$rule] ??= \Transliterator::create($rule)) { + if ('any-latin/bgn' === $rule) { + $rule = 'any-latin'; + $transliterator = self::$transliterators[$rule] ??= \Transliterator::create($rule); + } + + if (null === $transliterator) { + throw new InvalidArgumentException(sprintf('Unknown transliteration rule "%s".', $rule)); + } + + self::$transliterators['any-latin/bgn'] = $transliterator; + } + + $s = $transliterator->transliterate($s); + } + } elseif (!\function_exists('iconv')) { + $s = preg_replace('/[^\x00-\x7F]/u', '?', $s); + } else { + $s = @preg_replace_callback('/[^\x00-\x7F]/u', static function ($c) { + $c = (string) iconv('UTF-8', 'ASCII//TRANSLIT', $c[0]); + + if ('' === $c && '' === iconv('UTF-8', 'ASCII//TRANSLIT', '²')) { + throw new \LogicException(sprintf('"%s" requires a translit-able iconv implementation, try installing "gnu-libiconv" if you\'re using Alpine Linux.', static::class)); + } + + return 1 < \strlen($c) ? ltrim($c, '\'`"^~') : ('' !== $c ? $c : '?'); + }, $s); + } + } + + $str->string .= $s; + + return $str; + } + + public function camel(): static + { + $str = clone $this; + $str->string = str_replace(' ', '', preg_replace_callback('/\b.(?![A-Z]{2,})/u', static function ($m) { + static $i = 0; + + return 1 === ++$i ? ('İ' === $m[0] ? 'i̇' : mb_strtolower($m[0], 'UTF-8')) : mb_convert_case($m[0], \MB_CASE_TITLE, 'UTF-8'); + }, preg_replace('/[^\pL0-9]++/u', ' ', $this->string))); + + return $str; + } + + /** + * @return int[] + */ + public function codePointsAt(int $offset): array + { + $str = $this->slice($offset, 1); + + if ('' === $str->string) { + return []; + } + + $codePoints = []; + + foreach (preg_split('//u', $str->string, -1, \PREG_SPLIT_NO_EMPTY) as $c) { + $codePoints[] = mb_ord($c, 'UTF-8'); + } + + return $codePoints; + } + + public function folded(bool $compat = true): static + { + $str = clone $this; + + if (!$compat || !\defined('Normalizer::NFKC_CF')) { + $str->string = normalizer_normalize($str->string, $compat ? \Normalizer::NFKC : \Normalizer::NFC); + $str->string = mb_strtolower(str_replace(self::FOLD_FROM, self::FOLD_TO, $this->string), 'UTF-8'); + } else { + $str->string = normalizer_normalize($str->string, \Normalizer::NFKC_CF); + } + + return $str; + } + + public function join(array $strings, ?string $lastGlue = null): static + { + $str = clone $this; + + $tail = null !== $lastGlue && 1 < \count($strings) ? $lastGlue.array_pop($strings) : ''; + $str->string = implode($this->string, $strings).$tail; + + if (!preg_match('//u', $str->string)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + return $str; + } + + public function lower(): static + { + $str = clone $this; + $str->string = mb_strtolower(str_replace('İ', 'i̇', $str->string), 'UTF-8'); + + return $str; + } + + public function match(string $regexp, int $flags = 0, int $offset = 0): array + { + $match = ((\PREG_PATTERN_ORDER | \PREG_SET_ORDER) & $flags) ? 'preg_match_all' : 'preg_match'; + + if ($this->ignoreCase) { + $regexp .= 'i'; + } + + set_error_handler(static fn ($t, $m) => throw new InvalidArgumentException($m)); + + try { + if (false === $match($regexp.'u', $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) { + throw new RuntimeException('Matching failed with error: '.preg_last_error_msg()); + } + } finally { + restore_error_handler(); + } + + return $matches; + } + + public function normalize(int $form = self::NFC): static + { + if (!\in_array($form, [self::NFC, self::NFD, self::NFKC, self::NFKD])) { + throw new InvalidArgumentException('Unsupported normalization form.'); + } + + $str = clone $this; + normalizer_is_normalized($str->string, $form) ?: $str->string = normalizer_normalize($str->string, $form); + + return $str; + } + + public function padBoth(int $length, string $padStr = ' '): static + { + if ('' === $padStr || !preg_match('//u', $padStr)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + $pad = clone $this; + $pad->string = $padStr; + + return $this->pad($length, $pad, \STR_PAD_BOTH); + } + + public function padEnd(int $length, string $padStr = ' '): static + { + if ('' === $padStr || !preg_match('//u', $padStr)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + $pad = clone $this; + $pad->string = $padStr; + + return $this->pad($length, $pad, \STR_PAD_RIGHT); + } + + public function padStart(int $length, string $padStr = ' '): static + { + if ('' === $padStr || !preg_match('//u', $padStr)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + $pad = clone $this; + $pad->string = $padStr; + + return $this->pad($length, $pad, \STR_PAD_LEFT); + } + + public function replaceMatches(string $fromRegexp, string|callable $to): static + { + if ($this->ignoreCase) { + $fromRegexp .= 'i'; + } + + if (\is_array($to) || $to instanceof \Closure) { + $replace = 'preg_replace_callback'; + $to = static function (array $m) use ($to): string { + $to = $to($m); + + if ('' !== $to && (!\is_string($to) || !preg_match('//u', $to))) { + throw new InvalidArgumentException('Replace callback must return a valid UTF-8 string.'); + } + + return $to; + }; + } elseif ('' !== $to && !preg_match('//u', $to)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } else { + $replace = 'preg_replace'; + } + + set_error_handler(static fn ($t, $m) => throw new InvalidArgumentException($m)); + + try { + if (null === $string = $replace($fromRegexp.'u', $to, $this->string)) { + $lastError = preg_last_error(); + + foreach (get_defined_constants(true)['pcre'] as $k => $v) { + if ($lastError === $v && str_ends_with($k, '_ERROR')) { + throw new RuntimeException('Matching failed with '.$k.'.'); + } + } + + throw new RuntimeException('Matching failed with unknown error code.'); + } + } finally { + restore_error_handler(); + } + + $str = clone $this; + $str->string = $string; + + return $str; + } + + public function reverse(): static + { + $str = clone $this; + $str->string = implode('', array_reverse(preg_split('/(\X)/u', $str->string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY))); + + return $str; + } + + public function snake(): static + { + $str = $this->camel(); + $str->string = mb_strtolower(preg_replace(['/(\p{Lu}+)(\p{Lu}\p{Ll})/u', '/([\p{Ll}0-9])(\p{Lu})/u'], '\1_\2', $str->string), 'UTF-8'); + + return $str; + } + + public function title(bool $allWords = false): static + { + $str = clone $this; + + $limit = $allWords ? -1 : 1; + + $str->string = preg_replace_callback('/\b./u', static fn (array $m): string => mb_convert_case($m[0], \MB_CASE_TITLE, 'UTF-8'), $str->string, $limit); + + return $str; + } + + public function trim(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): static + { + if (" \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}" !== $chars && !preg_match('//u', $chars)) { + throw new InvalidArgumentException('Invalid UTF-8 chars.'); + } + $chars = preg_quote($chars); + + $str = clone $this; + $str->string = preg_replace("{^[$chars]++|[$chars]++$}uD", '', $str->string); + + return $str; + } + + public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): static + { + if (" \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}" !== $chars && !preg_match('//u', $chars)) { + throw new InvalidArgumentException('Invalid UTF-8 chars.'); + } + $chars = preg_quote($chars); + + $str = clone $this; + $str->string = preg_replace("{[$chars]++$}uD", '', $str->string); + + return $str; + } + + public function trimPrefix($prefix): static + { + if (!$this->ignoreCase) { + return parent::trimPrefix($prefix); + } + + $str = clone $this; + + if ($prefix instanceof \Traversable) { + $prefix = iterator_to_array($prefix, false); + } elseif ($prefix instanceof parent) { + $prefix = $prefix->string; + } + + $prefix = implode('|', array_map('preg_quote', (array) $prefix)); + $str->string = preg_replace("{^(?:$prefix)}iuD", '', $this->string); + + return $str; + } + + public function trimStart(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): static + { + if (" \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}" !== $chars && !preg_match('//u', $chars)) { + throw new InvalidArgumentException('Invalid UTF-8 chars.'); + } + $chars = preg_quote($chars); + + $str = clone $this; + $str->string = preg_replace("{^[$chars]++}uD", '', $str->string); + + return $str; + } + + public function trimSuffix($suffix): static + { + if (!$this->ignoreCase) { + return parent::trimSuffix($suffix); + } + + $str = clone $this; + + if ($suffix instanceof \Traversable) { + $suffix = iterator_to_array($suffix, false); + } elseif ($suffix instanceof parent) { + $suffix = $suffix->string; + } + + $suffix = implode('|', array_map('preg_quote', (array) $suffix)); + $str->string = preg_replace("{(?:$suffix)$}iuD", '', $this->string); + + return $str; + } + + public function upper(): static + { + $str = clone $this; + $str->string = mb_strtoupper($str->string, 'UTF-8'); + + return $str; + } + + public function width(bool $ignoreAnsiDecoration = true): int + { + $width = 0; + $s = str_replace(["\x00", "\x05", "\x07"], '', $this->string); + + if (str_contains($s, "\r")) { + $s = str_replace(["\r\n", "\r"], "\n", $s); + } + + if (!$ignoreAnsiDecoration) { + $s = preg_replace('/[\p{Cc}\x7F]++/u', '', $s); + } + + foreach (explode("\n", $s) as $s) { + if ($ignoreAnsiDecoration) { + $s = preg_replace('/(?:\x1B(?: + \[ [\x30-\x3F]*+ [\x20-\x2F]*+ [\x40-\x7E] + | [P\]X^_] .*? \x1B\\\\ + | [\x41-\x7E] + )|[\p{Cc}\x7F]++)/xu', '', $s); + } + + $lineWidth = $this->wcswidth($s); + + if ($lineWidth > $width) { + $width = $lineWidth; + } + } + + return $width; + } + + private function pad(int $len, self $pad, int $type): static + { + $sLen = $this->length(); + + if ($len <= $sLen) { + return clone $this; + } + + $padLen = $pad->length(); + $freeLen = $len - $sLen; + $len = $freeLen % $padLen; + + switch ($type) { + case \STR_PAD_RIGHT: + return $this->append(str_repeat($pad->string, intdiv($freeLen, $padLen)).($len ? $pad->slice(0, $len) : '')); + + case \STR_PAD_LEFT: + return $this->prepend(str_repeat($pad->string, intdiv($freeLen, $padLen)).($len ? $pad->slice(0, $len) : '')); + + case \STR_PAD_BOTH: + $freeLen /= 2; + + $rightLen = ceil($freeLen); + $len = $rightLen % $padLen; + $str = $this->append(str_repeat($pad->string, intdiv($rightLen, $padLen)).($len ? $pad->slice(0, $len) : '')); + + $leftLen = floor($freeLen); + $len = $leftLen % $padLen; + + return $str->prepend(str_repeat($pad->string, intdiv($leftLen, $padLen)).($len ? $pad->slice(0, $len) : '')); + + default: + throw new InvalidArgumentException('Invalid padding type.'); + } + } + + /** + * Based on https://github.com/jquast/wcwidth, a Python implementation of https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c. + */ + private function wcswidth(string $string): int + { + $width = 0; + + foreach (preg_split('//u', $string, -1, \PREG_SPLIT_NO_EMPTY) as $c) { + $codePoint = mb_ord($c, 'UTF-8'); + + if (0 === $codePoint // NULL + || 0x034F === $codePoint // COMBINING GRAPHEME JOINER + || (0x200B <= $codePoint && 0x200F >= $codePoint) // ZERO WIDTH SPACE to RIGHT-TO-LEFT MARK + || 0x2028 === $codePoint // LINE SEPARATOR + || 0x2029 === $codePoint // PARAGRAPH SEPARATOR + || (0x202A <= $codePoint && 0x202E >= $codePoint) // LEFT-TO-RIGHT EMBEDDING to RIGHT-TO-LEFT OVERRIDE + || (0x2060 <= $codePoint && 0x2063 >= $codePoint) // WORD JOINER to INVISIBLE SEPARATOR + ) { + continue; + } + + // Non printable characters + if (32 > $codePoint // C0 control characters + || (0x07F <= $codePoint && 0x0A0 > $codePoint) // C1 control characters and DEL + ) { + return -1; + } + + self::$tableZero ??= require __DIR__.'/Resources/data/wcswidth_table_zero.php'; + + if ($codePoint >= self::$tableZero[0][0] && $codePoint <= self::$tableZero[$ubound = \count(self::$tableZero) - 1][1]) { + $lbound = 0; + while ($ubound >= $lbound) { + $mid = floor(($lbound + $ubound) / 2); + + if ($codePoint > self::$tableZero[$mid][1]) { + $lbound = $mid + 1; + } elseif ($codePoint < self::$tableZero[$mid][0]) { + $ubound = $mid - 1; + } else { + continue 2; + } + } + } + + self::$tableWide ??= require __DIR__.'/Resources/data/wcswidth_table_wide.php'; + + if ($codePoint >= self::$tableWide[0][0] && $codePoint <= self::$tableWide[$ubound = \count(self::$tableWide) - 1][1]) { + $lbound = 0; + while ($ubound >= $lbound) { + $mid = floor(($lbound + $ubound) / 2); + + if ($codePoint > self::$tableWide[$mid][1]) { + $lbound = $mid + 1; + } elseif ($codePoint < self::$tableWide[$mid][0]) { + $ubound = $mid - 1; + } else { + $width += 2; + + continue 2; + } + } + } + + ++$width; + } + + return $width; + } +} diff --git a/vendor/symfony/string/ByteString.php b/vendor/symfony/string/ByteString.php new file mode 100644 index 00000000..3ebe43c1 --- /dev/null +++ b/vendor/symfony/string/ByteString.php @@ -0,0 +1,485 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String; + +use Symfony\Component\String\Exception\ExceptionInterface; +use Symfony\Component\String\Exception\InvalidArgumentException; +use Symfony\Component\String\Exception\RuntimeException; + +/** + * Represents a binary-safe string of bytes. + * + * @author Nicolas Grekas + * @author Hugo Hamon + * + * @throws ExceptionInterface + */ +class ByteString extends AbstractString +{ + private const ALPHABET_ALPHANUMERIC = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; + + public function __construct(string $string = '') + { + $this->string = $string; + } + + /* + * The following method was derived from code of the Hack Standard Library (v4.40 - 2020-05-03) + * + * https://github.com/hhvm/hsl/blob/80a42c02f036f72a42f0415e80d6b847f4bf62d5/src/random/private.php#L16 + * + * Code subject to the MIT license (https://github.com/hhvm/hsl/blob/master/LICENSE). + * + * Copyright (c) 2004-2020, Facebook, Inc. (https://www.facebook.com/) + */ + + public static function fromRandom(int $length = 16, ?string $alphabet = null): self + { + if ($length <= 0) { + throw new InvalidArgumentException(sprintf('A strictly positive length is expected, "%d" given.', $length)); + } + + $alphabet ??= self::ALPHABET_ALPHANUMERIC; + $alphabetSize = \strlen($alphabet); + $bits = (int) ceil(log($alphabetSize, 2.0)); + if ($bits <= 0 || $bits > 56) { + throw new InvalidArgumentException('The length of the alphabet must in the [2^1, 2^56] range.'); + } + + $ret = ''; + while ($length > 0) { + $urandomLength = (int) ceil(2 * $length * $bits / 8.0); + $data = random_bytes($urandomLength); + $unpackedData = 0; + $unpackedBits = 0; + for ($i = 0; $i < $urandomLength && $length > 0; ++$i) { + // Unpack 8 bits + $unpackedData = ($unpackedData << 8) | \ord($data[$i]); + $unpackedBits += 8; + + // While we have enough bits to select a character from the alphabet, keep + // consuming the random data + for (; $unpackedBits >= $bits && $length > 0; $unpackedBits -= $bits) { + $index = ($unpackedData & ((1 << $bits) - 1)); + $unpackedData >>= $bits; + // Unfortunately, the alphabet size is not necessarily a power of two. + // Worst case, it is 2^k + 1, which means we need (k+1) bits and we + // have around a 50% chance of missing as k gets larger + if ($index < $alphabetSize) { + $ret .= $alphabet[$index]; + --$length; + } + } + } + } + + return new static($ret); + } + + public function bytesAt(int $offset): array + { + $str = $this->string[$offset] ?? ''; + + return '' === $str ? [] : [\ord($str)]; + } + + public function append(string ...$suffix): static + { + $str = clone $this; + $str->string .= 1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix); + + return $str; + } + + public function camel(): static + { + $str = clone $this; + + $parts = explode(' ', trim(ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $this->string)))); + $parts[0] = 1 !== \strlen($parts[0]) && ctype_upper($parts[0]) ? $parts[0] : lcfirst($parts[0]); + $str->string = implode('', $parts); + + return $str; + } + + public function chunk(int $length = 1): array + { + if (1 > $length) { + throw new InvalidArgumentException('The chunk length must be greater than zero.'); + } + + if ('' === $this->string) { + return []; + } + + $str = clone $this; + $chunks = []; + + foreach (str_split($this->string, $length) as $chunk) { + $str->string = $chunk; + $chunks[] = clone $str; + } + + return $chunks; + } + + public function endsWith(string|iterable|AbstractString $suffix): bool + { + if ($suffix instanceof AbstractString) { + $suffix = $suffix->string; + } elseif (!\is_string($suffix)) { + return parent::endsWith($suffix); + } + + return '' !== $suffix && \strlen($this->string) >= \strlen($suffix) && 0 === substr_compare($this->string, $suffix, -\strlen($suffix), null, $this->ignoreCase); + } + + public function equalsTo(string|iterable|AbstractString $string): bool + { + if ($string instanceof AbstractString) { + $string = $string->string; + } elseif (!\is_string($string)) { + return parent::equalsTo($string); + } + + if ('' !== $string && $this->ignoreCase) { + return 0 === strcasecmp($string, $this->string); + } + + return $string === $this->string; + } + + public function folded(): static + { + $str = clone $this; + $str->string = strtolower($str->string); + + return $str; + } + + public function indexOf(string|iterable|AbstractString $needle, int $offset = 0): ?int + { + if ($needle instanceof AbstractString) { + $needle = $needle->string; + } elseif (!\is_string($needle)) { + return parent::indexOf($needle, $offset); + } + + if ('' === $needle) { + return null; + } + + $i = $this->ignoreCase ? stripos($this->string, $needle, $offset) : strpos($this->string, $needle, $offset); + + return false === $i ? null : $i; + } + + public function indexOfLast(string|iterable|AbstractString $needle, int $offset = 0): ?int + { + if ($needle instanceof AbstractString) { + $needle = $needle->string; + } elseif (!\is_string($needle)) { + return parent::indexOfLast($needle, $offset); + } + + if ('' === $needle) { + return null; + } + + $i = $this->ignoreCase ? strripos($this->string, $needle, $offset) : strrpos($this->string, $needle, $offset); + + return false === $i ? null : $i; + } + + public function isUtf8(): bool + { + return '' === $this->string || preg_match('//u', $this->string); + } + + public function join(array $strings, ?string $lastGlue = null): static + { + $str = clone $this; + + $tail = null !== $lastGlue && 1 < \count($strings) ? $lastGlue.array_pop($strings) : ''; + $str->string = implode($this->string, $strings).$tail; + + return $str; + } + + public function length(): int + { + return \strlen($this->string); + } + + public function lower(): static + { + $str = clone $this; + $str->string = strtolower($str->string); + + return $str; + } + + public function match(string $regexp, int $flags = 0, int $offset = 0): array + { + $match = ((\PREG_PATTERN_ORDER | \PREG_SET_ORDER) & $flags) ? 'preg_match_all' : 'preg_match'; + + if ($this->ignoreCase) { + $regexp .= 'i'; + } + + set_error_handler(static fn ($t, $m) => throw new InvalidArgumentException($m)); + + try { + if (false === $match($regexp, $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) { + throw new RuntimeException('Matching failed with error: '.preg_last_error_msg()); + } + } finally { + restore_error_handler(); + } + + return $matches; + } + + public function padBoth(int $length, string $padStr = ' '): static + { + $str = clone $this; + $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_BOTH); + + return $str; + } + + public function padEnd(int $length, string $padStr = ' '): static + { + $str = clone $this; + $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_RIGHT); + + return $str; + } + + public function padStart(int $length, string $padStr = ' '): static + { + $str = clone $this; + $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_LEFT); + + return $str; + } + + public function prepend(string ...$prefix): static + { + $str = clone $this; + $str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$str->string; + + return $str; + } + + public function replace(string $from, string $to): static + { + $str = clone $this; + + if ('' !== $from) { + $str->string = $this->ignoreCase ? str_ireplace($from, $to, $this->string) : str_replace($from, $to, $this->string); + } + + return $str; + } + + public function replaceMatches(string $fromRegexp, string|callable $to): static + { + if ($this->ignoreCase) { + $fromRegexp .= 'i'; + } + + $replace = \is_array($to) || $to instanceof \Closure ? 'preg_replace_callback' : 'preg_replace'; + + set_error_handler(static fn ($t, $m) => throw new InvalidArgumentException($m)); + + try { + if (null === $string = $replace($fromRegexp, $to, $this->string)) { + $lastError = preg_last_error(); + + foreach (get_defined_constants(true)['pcre'] as $k => $v) { + if ($lastError === $v && str_ends_with($k, '_ERROR')) { + throw new RuntimeException('Matching failed with '.$k.'.'); + } + } + + throw new RuntimeException('Matching failed with unknown error code.'); + } + } finally { + restore_error_handler(); + } + + $str = clone $this; + $str->string = $string; + + return $str; + } + + public function reverse(): static + { + $str = clone $this; + $str->string = strrev($str->string); + + return $str; + } + + public function slice(int $start = 0, ?int $length = null): static + { + $str = clone $this; + $str->string = (string) substr($this->string, $start, $length ?? \PHP_INT_MAX); + + return $str; + } + + public function snake(): static + { + $str = $this->camel(); + $str->string = strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], '\1_\2', $str->string)); + + return $str; + } + + public function splice(string $replacement, int $start = 0, ?int $length = null): static + { + $str = clone $this; + $str->string = substr_replace($this->string, $replacement, $start, $length ?? \PHP_INT_MAX); + + return $str; + } + + public function split(string $delimiter, ?int $limit = null, ?int $flags = null): array + { + if (1 > $limit ??= \PHP_INT_MAX) { + throw new InvalidArgumentException('Split limit must be a positive integer.'); + } + + if ('' === $delimiter) { + throw new InvalidArgumentException('Split delimiter is empty.'); + } + + if (null !== $flags) { + return parent::split($delimiter, $limit, $flags); + } + + $str = clone $this; + $chunks = $this->ignoreCase + ? preg_split('{'.preg_quote($delimiter).'}iD', $this->string, $limit) + : explode($delimiter, $this->string, $limit); + + foreach ($chunks as &$chunk) { + $str->string = $chunk; + $chunk = clone $str; + } + + return $chunks; + } + + public function startsWith(string|iterable|AbstractString $prefix): bool + { + if ($prefix instanceof AbstractString) { + $prefix = $prefix->string; + } elseif (!\is_string($prefix)) { + return parent::startsWith($prefix); + } + + return '' !== $prefix && 0 === ($this->ignoreCase ? strncasecmp($this->string, $prefix, \strlen($prefix)) : strncmp($this->string, $prefix, \strlen($prefix))); + } + + public function title(bool $allWords = false): static + { + $str = clone $this; + $str->string = $allWords ? ucwords($str->string) : ucfirst($str->string); + + return $str; + } + + public function toUnicodeString(?string $fromEncoding = null): UnicodeString + { + return new UnicodeString($this->toCodePointString($fromEncoding)->string); + } + + public function toCodePointString(?string $fromEncoding = null): CodePointString + { + $u = new CodePointString(); + + if (\in_array($fromEncoding, [null, 'utf8', 'utf-8', 'UTF8', 'UTF-8'], true) && preg_match('//u', $this->string)) { + $u->string = $this->string; + + return $u; + } + + set_error_handler(static fn ($t, $m) => throw new InvalidArgumentException($m)); + + try { + try { + $validEncoding = false !== mb_detect_encoding($this->string, $fromEncoding ?? 'Windows-1252', true); + } catch (InvalidArgumentException $e) { + if (!\function_exists('iconv')) { + throw $e; + } + + $u->string = iconv($fromEncoding ?? 'Windows-1252', 'UTF-8', $this->string); + + return $u; + } + } finally { + restore_error_handler(); + } + + if (!$validEncoding) { + throw new InvalidArgumentException(sprintf('Invalid "%s" string.', $fromEncoding ?? 'Windows-1252')); + } + + $u->string = mb_convert_encoding($this->string, 'UTF-8', $fromEncoding ?? 'Windows-1252'); + + return $u; + } + + public function trim(string $chars = " \t\n\r\0\x0B\x0C"): static + { + $str = clone $this; + $str->string = trim($str->string, $chars); + + return $str; + } + + public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C"): static + { + $str = clone $this; + $str->string = rtrim($str->string, $chars); + + return $str; + } + + public function trimStart(string $chars = " \t\n\r\0\x0B\x0C"): static + { + $str = clone $this; + $str->string = ltrim($str->string, $chars); + + return $str; + } + + public function upper(): static + { + $str = clone $this; + $str->string = strtoupper($str->string); + + return $str; + } + + public function width(bool $ignoreAnsiDecoration = true): int + { + $string = preg_match('//u', $this->string) ? $this->string : preg_replace('/[\x80-\xFF]/', '?', $this->string); + + return (new CodePointString($string))->width($ignoreAnsiDecoration); + } +} diff --git a/vendor/symfony/string/CHANGELOG.md b/vendor/symfony/string/CHANGELOG.md new file mode 100644 index 00000000..31a3b54d --- /dev/null +++ b/vendor/symfony/string/CHANGELOG.md @@ -0,0 +1,40 @@ +CHANGELOG +========= + +6.2 +--- + + * Add support for emoji in `AsciiSlugger` + +5.4 +--- + + * Add `trimSuffix()` and `trimPrefix()` methods + +5.3 +--- + + * Made `AsciiSlugger` fallback to parent locale's symbolsMap + +5.2.0 +----- + + * added a `FrenchInflector` class + +5.1.0 +----- + + * added the `AbstractString::reverse()` method + * made `AbstractString::width()` follow POSIX.1-2001 + * added `LazyString` which provides memoizing stringable objects + * The component is not marked as `@experimental` anymore + * added the `s()` helper method to get either an `UnicodeString` or `ByteString` instance, + depending of the input string UTF-8 compliancy + * added `$cut` parameter to `Symfony\Component\String\AbstractString::truncate()` + * added `AbstractString::containsAny()` + * allow passing a string of custom characters to `ByteString::fromRandom()` + +5.0.0 +----- + + * added the component as experimental diff --git a/vendor/symfony/string/CodePointString.php b/vendor/symfony/string/CodePointString.php new file mode 100644 index 00000000..337bfc12 --- /dev/null +++ b/vendor/symfony/string/CodePointString.php @@ -0,0 +1,260 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String; + +use Symfony\Component\String\Exception\ExceptionInterface; +use Symfony\Component\String\Exception\InvalidArgumentException; + +/** + * Represents a string of Unicode code points encoded as UTF-8. + * + * @author Nicolas Grekas + * @author Hugo Hamon + * + * @throws ExceptionInterface + */ +class CodePointString extends AbstractUnicodeString +{ + public function __construct(string $string = '') + { + if ('' !== $string && !preg_match('//u', $string)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + $this->string = $string; + } + + public function append(string ...$suffix): static + { + $str = clone $this; + $str->string .= 1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix); + + if (!preg_match('//u', $str->string)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + return $str; + } + + public function chunk(int $length = 1): array + { + if (1 > $length) { + throw new InvalidArgumentException('The chunk length must be greater than zero.'); + } + + if ('' === $this->string) { + return []; + } + + $rx = '/('; + while (65535 < $length) { + $rx .= '.{65535}'; + $length -= 65535; + } + $rx .= '.{'.$length.'})/us'; + + $str = clone $this; + $chunks = []; + + foreach (preg_split($rx, $this->string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY) as $chunk) { + $str->string = $chunk; + $chunks[] = clone $str; + } + + return $chunks; + } + + public function codePointsAt(int $offset): array + { + $str = $offset ? $this->slice($offset, 1) : $this; + + return '' === $str->string ? [] : [mb_ord($str->string, 'UTF-8')]; + } + + public function endsWith(string|iterable|AbstractString $suffix): bool + { + if ($suffix instanceof AbstractString) { + $suffix = $suffix->string; + } elseif (!\is_string($suffix)) { + return parent::endsWith($suffix); + } + + if ('' === $suffix || !preg_match('//u', $suffix)) { + return false; + } + + if ($this->ignoreCase) { + return preg_match('{'.preg_quote($suffix).'$}iuD', $this->string); + } + + return \strlen($this->string) >= \strlen($suffix) && 0 === substr_compare($this->string, $suffix, -\strlen($suffix)); + } + + public function equalsTo(string|iterable|AbstractString $string): bool + { + if ($string instanceof AbstractString) { + $string = $string->string; + } elseif (!\is_string($string)) { + return parent::equalsTo($string); + } + + if ('' !== $string && $this->ignoreCase) { + return \strlen($string) === \strlen($this->string) && 0 === mb_stripos($this->string, $string, 0, 'UTF-8'); + } + + return $string === $this->string; + } + + public function indexOf(string|iterable|AbstractString $needle, int $offset = 0): ?int + { + if ($needle instanceof AbstractString) { + $needle = $needle->string; + } elseif (!\is_string($needle)) { + return parent::indexOf($needle, $offset); + } + + if ('' === $needle) { + return null; + } + + $i = $this->ignoreCase ? mb_stripos($this->string, $needle, $offset, 'UTF-8') : mb_strpos($this->string, $needle, $offset, 'UTF-8'); + + return false === $i ? null : $i; + } + + public function indexOfLast(string|iterable|AbstractString $needle, int $offset = 0): ?int + { + if ($needle instanceof AbstractString) { + $needle = $needle->string; + } elseif (!\is_string($needle)) { + return parent::indexOfLast($needle, $offset); + } + + if ('' === $needle) { + return null; + } + + $i = $this->ignoreCase ? mb_strripos($this->string, $needle, $offset, 'UTF-8') : mb_strrpos($this->string, $needle, $offset, 'UTF-8'); + + return false === $i ? null : $i; + } + + public function length(): int + { + return mb_strlen($this->string, 'UTF-8'); + } + + public function prepend(string ...$prefix): static + { + $str = clone $this; + $str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$this->string; + + if (!preg_match('//u', $str->string)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + return $str; + } + + public function replace(string $from, string $to): static + { + $str = clone $this; + + if ('' === $from || !preg_match('//u', $from)) { + return $str; + } + + if ('' !== $to && !preg_match('//u', $to)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + if ($this->ignoreCase) { + $str->string = implode($to, preg_split('{'.preg_quote($from).'}iuD', $this->string)); + } else { + $str->string = str_replace($from, $to, $this->string); + } + + return $str; + } + + public function slice(int $start = 0, ?int $length = null): static + { + $str = clone $this; + $str->string = mb_substr($this->string, $start, $length, 'UTF-8'); + + return $str; + } + + public function splice(string $replacement, int $start = 0, ?int $length = null): static + { + if (!preg_match('//u', $replacement)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + $str = clone $this; + $start = $start ? \strlen(mb_substr($this->string, 0, $start, 'UTF-8')) : 0; + $length = $length ? \strlen(mb_substr($this->string, $start, $length, 'UTF-8')) : $length; + $str->string = substr_replace($this->string, $replacement, $start, $length ?? \PHP_INT_MAX); + + return $str; + } + + public function split(string $delimiter, ?int $limit = null, ?int $flags = null): array + { + if (1 > $limit ??= \PHP_INT_MAX) { + throw new InvalidArgumentException('Split limit must be a positive integer.'); + } + + if ('' === $delimiter) { + throw new InvalidArgumentException('Split delimiter is empty.'); + } + + if (null !== $flags) { + return parent::split($delimiter.'u', $limit, $flags); + } + + if (!preg_match('//u', $delimiter)) { + throw new InvalidArgumentException('Split delimiter is not a valid UTF-8 string.'); + } + + $str = clone $this; + $chunks = $this->ignoreCase + ? preg_split('{'.preg_quote($delimiter).'}iuD', $this->string, $limit) + : explode($delimiter, $this->string, $limit); + + foreach ($chunks as &$chunk) { + $str->string = $chunk; + $chunk = clone $str; + } + + return $chunks; + } + + public function startsWith(string|iterable|AbstractString $prefix): bool + { + if ($prefix instanceof AbstractString) { + $prefix = $prefix->string; + } elseif (!\is_string($prefix)) { + return parent::startsWith($prefix); + } + + if ('' === $prefix || !preg_match('//u', $prefix)) { + return false; + } + + if ($this->ignoreCase) { + return 0 === mb_stripos($this->string, $prefix, 0, 'UTF-8'); + } + + return 0 === strncmp($this->string, $prefix, \strlen($prefix)); + } +} diff --git a/vendor/symfony/string/Exception/ExceptionInterface.php b/vendor/symfony/string/Exception/ExceptionInterface.php new file mode 100644 index 00000000..36197865 --- /dev/null +++ b/vendor/symfony/string/Exception/ExceptionInterface.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String\Exception; + +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/string/Exception/InvalidArgumentException.php b/vendor/symfony/string/Exception/InvalidArgumentException.php new file mode 100644 index 00000000..6aa586bc --- /dev/null +++ b/vendor/symfony/string/Exception/InvalidArgumentException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String\Exception; + +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/string/Exception/RuntimeException.php b/vendor/symfony/string/Exception/RuntimeException.php new file mode 100644 index 00000000..77cb091f --- /dev/null +++ b/vendor/symfony/string/Exception/RuntimeException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String\Exception; + +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/string/Inflector/EnglishInflector.php b/vendor/symfony/string/Inflector/EnglishInflector.php new file mode 100644 index 00000000..dc7b08e7 --- /dev/null +++ b/vendor/symfony/string/Inflector/EnglishInflector.php @@ -0,0 +1,553 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String\Inflector; + +final class EnglishInflector implements InflectorInterface +{ + /** + * Map English plural to singular suffixes. + * + * @see http://english-zone.com/spelling/plurals.html + */ + private const PLURAL_MAP = [ + // First entry: plural suffix, reversed + // Second entry: length of plural suffix + // Third entry: Whether the suffix may succeed a vowel + // Fourth entry: Whether the suffix may succeed a consonant + // Fifth entry: singular suffix, normal + + // bacteria (bacterium), criteria (criterion), phenomena (phenomenon) + ['a', 1, true, true, ['on', 'um']], + + // nebulae (nebula) + ['ea', 2, true, true, 'a'], + + // services (service) + ['secivres', 8, true, true, 'service'], + + // mice (mouse), lice (louse) + ['eci', 3, false, true, 'ouse'], + + // geese (goose) + ['esee', 4, false, true, 'oose'], + + // fungi (fungus), alumni (alumnus), syllabi (syllabus), radii (radius) + ['i', 1, true, true, 'us'], + + // men (man), women (woman) + ['nem', 3, true, true, 'man'], + + // children (child) + ['nerdlihc', 8, true, true, 'child'], + + // oxen (ox) + ['nexo', 4, false, false, 'ox'], + + // indices (index), appendices (appendix), prices (price) + ['seci', 4, false, true, ['ex', 'ix', 'ice']], + + // codes (code) + ['sedoc', 5, false, true, 'code'], + + // selfies (selfie) + ['seifles', 7, true, true, 'selfie'], + + // zombies (zombie) + ['seibmoz', 7, true, true, 'zombie'], + + // movies (movie) + ['seivom', 6, true, true, 'movie'], + + // names (name) + ['seman', 5, true, false, 'name'], + + // conspectuses (conspectus), prospectuses (prospectus) + ['sesutcep', 8, true, true, 'pectus'], + + // feet (foot) + ['teef', 4, true, true, 'foot'], + + // geese (goose) + ['eseeg', 5, true, true, 'goose'], + + // teeth (tooth) + ['hteet', 5, true, true, 'tooth'], + + // news (news) + ['swen', 4, true, true, 'news'], + + // series (series) + ['seires', 6, true, true, 'series'], + + // babies (baby) + ['sei', 3, false, true, 'y'], + + // accesses (access), addresses (address), kisses (kiss) + ['sess', 4, true, false, 'ss'], + + // statuses (status) + ['sesutats', 8, true, true, 'status'], + + // analyses (analysis), ellipses (ellipsis), fungi (fungus), + // neuroses (neurosis), theses (thesis), emphases (emphasis), + // oases (oasis), crises (crisis), houses (house), bases (base), + // atlases (atlas) + ['ses', 3, true, true, ['s', 'se', 'sis']], + + // objectives (objective), alternative (alternatives) + ['sevit', 5, true, true, 'tive'], + + // drives (drive) + ['sevird', 6, false, true, 'drive'], + + // lives (life), wives (wife) + ['sevi', 4, false, true, 'ife'], + + // moves (move) + ['sevom', 5, true, true, 'move'], + + // hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf), caves (cave), staves (staff) + ['sev', 3, true, true, ['f', 've', 'ff']], + + // axes (axis), axes (ax), axes (axe) + ['sexa', 4, false, false, ['ax', 'axe', 'axis']], + + // indexes (index), matrixes (matrix) + ['sex', 3, true, false, 'x'], + + // quizzes (quiz) + ['sezz', 4, true, false, 'z'], + + // bureaus (bureau) + ['suae', 4, false, true, 'eau'], + + // fees (fee), trees (tree), employees (employee) + ['see', 3, true, true, 'ee'], + + // edges (edge) + ['segd', 4, true, true, 'dge'], + + // roses (rose), garages (garage), cassettes (cassette), + // waltzes (waltz), heroes (hero), bushes (bush), arches (arch), + // shoes (shoe) + ['se', 2, true, true, ['', 'e']], + + // status (status) + ['sutats', 6, true, true, 'status'], + + // tags (tag) + ['s', 1, true, true, ''], + + // chateaux (chateau) + ['xuae', 4, false, true, 'eau'], + + // people (person) + ['elpoep', 6, true, true, 'person'], + ]; + + /** + * Map English singular to plural suffixes. + * + * @see http://english-zone.com/spelling/plurals.html + */ + private const SINGULAR_MAP = [ + // First entry: singular suffix, reversed + // Second entry: length of singular suffix + // Third entry: Whether the suffix may succeed a vowel + // Fourth entry: Whether the suffix may succeed a consonant + // Fifth entry: plural suffix, normal + + // axes (axis) + ['sixa', 4, false, false, 'axes'], + + // criterion (criteria) + ['airetirc', 8, false, false, 'criterion'], + + // nebulae (nebula) + ['aluben', 6, false, false, 'nebulae'], + + // children (child) + ['dlihc', 5, true, true, 'children'], + + // prices (price) + ['eci', 3, false, true, 'ices'], + + // services (service) + ['ecivres', 7, true, true, 'services'], + + // lives (life), wives (wife) + ['efi', 3, false, true, 'ives'], + + // selfies (selfie) + ['eifles', 6, true, true, 'selfies'], + + // movies (movie) + ['eivom', 5, true, true, 'movies'], + + // lice (louse) + ['esuol', 5, false, true, 'lice'], + + // mice (mouse) + ['esuom', 5, false, true, 'mice'], + + // geese (goose) + ['esoo', 4, false, true, 'eese'], + + // houses (house), bases (base) + ['es', 2, true, true, 'ses'], + + // geese (goose) + ['esoog', 5, true, true, 'geese'], + + // caves (cave) + ['ev', 2, true, true, 'ves'], + + // drives (drive) + ['evird', 5, false, true, 'drives'], + + // objectives (objective), alternative (alternatives) + ['evit', 4, true, true, 'tives'], + + // moves (move) + ['evom', 4, true, true, 'moves'], + + // staves (staff) + ['ffats', 5, true, true, 'staves'], + + // hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf) + ['ff', 2, true, true, 'ffs'], + + // hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf) + ['f', 1, true, true, ['fs', 'ves']], + + // arches (arch) + ['hc', 2, true, true, 'ches'], + + // bushes (bush) + ['hs', 2, true, true, 'shes'], + + // teeth (tooth) + ['htoot', 5, true, true, 'teeth'], + + // bacteria (bacterium), criteria (criterion), phenomena (phenomenon) + ['mu', 2, true, true, 'a'], + + // men (man), women (woman) + ['nam', 3, true, true, 'men'], + + // people (person) + ['nosrep', 6, true, true, ['persons', 'people']], + + // bacteria (bacterium), criteria (criterion), phenomena (phenomenon) + ['noi', 3, true, true, 'ions'], + + // coupon (coupons) + ['nop', 3, true, true, 'pons'], + + // seasons (season), treasons (treason), poisons (poison), lessons (lesson) + ['nos', 3, true, true, 'sons'], + + // icons (icon) + ['noc', 3, true, true, 'cons'], + + // bacteria (bacterium), criteria (criterion), phenomena (phenomenon) + ['no', 2, true, true, 'a'], + + // echoes (echo) + ['ohce', 4, true, true, 'echoes'], + + // heroes (hero) + ['oreh', 4, true, true, 'heroes'], + + // atlases (atlas) + ['salta', 5, true, true, 'atlases'], + + // irises (iris) + ['siri', 4, true, true, 'irises'], + + // analyses (analysis), ellipses (ellipsis), neuroses (neurosis) + // theses (thesis), emphases (emphasis), oases (oasis), + // crises (crisis) + ['sis', 3, true, true, 'ses'], + + // accesses (access), addresses (address), kisses (kiss) + ['ss', 2, true, false, 'sses'], + + // syllabi (syllabus) + ['suballys', 8, true, true, 'syllabi'], + + // buses (bus) + ['sub', 3, true, true, 'buses'], + + // circuses (circus) + ['suc', 3, true, true, 'cuses'], + + // hippocampi (hippocampus) + ['supmacoppih', 11, false, false, 'hippocampi'], + + // campuses (campus) + ['sup', 3, true, true, 'puses'], + + // status (status) + ['sutats', 6, true, true, ['status', 'statuses']], + + // conspectuses (conspectus), prospectuses (prospectus) + ['sutcep', 6, true, true, 'pectuses'], + + // fungi (fungus), alumni (alumnus), syllabi (syllabus), radii (radius) + ['su', 2, true, true, 'i'], + + // news (news) + ['swen', 4, true, true, 'news'], + + // feet (foot) + ['toof', 4, true, true, 'feet'], + + // chateaux (chateau), bureaus (bureau) + ['uae', 3, false, true, ['eaus', 'eaux']], + + // oxen (ox) + ['xo', 2, false, false, 'oxen'], + + // hoaxes (hoax) + ['xaoh', 4, true, false, 'hoaxes'], + + // indices (index) + ['xedni', 5, false, true, ['indicies', 'indexes']], + + // boxes (box) + ['xo', 2, false, true, 'oxes'], + + // indexes (index), matrixes (matrix) + ['x', 1, true, false, ['cies', 'xes']], + + // appendices (appendix) + ['xi', 2, false, true, 'ices'], + + // babies (baby) + ['y', 1, false, true, 'ies'], + + // quizzes (quiz) + ['ziuq', 4, true, false, 'quizzes'], + + // waltzes (waltz) + ['z', 1, true, true, 'zes'], + ]; + + /** + * A list of words which should not be inflected, reversed. + */ + private const UNINFLECTED = [ + '', + + // data + 'atad', + + // deer + 'reed', + + // equipment + 'tnempiuqe', + + // feedback + 'kcabdeef', + + // fish + 'hsif', + + // health + 'htlaeh', + + // history + 'yrotsih', + + // info + 'ofni', + + // information + 'noitamrofni', + + // money + 'yenom', + + // moose + 'esoom', + + // series + 'seires', + + // sheep + 'peehs', + + // species + 'seiceps', + + // traffic + 'ciffart', + + // aircraft + 'tfarcria', + ]; + + public function singularize(string $plural): array + { + $pluralRev = strrev($plural); + $lowerPluralRev = strtolower($pluralRev); + $pluralLength = \strlen($lowerPluralRev); + + // Check if the word is one which is not inflected, return early if so + if (\in_array($lowerPluralRev, self::UNINFLECTED, true)) { + return [$plural]; + } + + // The outer loop iterates over the entries of the plural table + // The inner loop $j iterates over the characters of the plural suffix + // in the plural table to compare them with the characters of the actual + // given plural suffix + foreach (self::PLURAL_MAP as $map) { + $suffix = $map[0]; + $suffixLength = $map[1]; + $j = 0; + + // Compare characters in the plural table and of the suffix of the + // given plural one by one + while ($suffix[$j] === $lowerPluralRev[$j]) { + // Let $j point to the next character + ++$j; + + // Successfully compared the last character + // Add an entry with the singular suffix to the singular array + if ($j === $suffixLength) { + // Is there any character preceding the suffix in the plural string? + if ($j < $pluralLength) { + $nextIsVowel = str_contains('aeiou', $lowerPluralRev[$j]); + + if (!$map[2] && $nextIsVowel) { + // suffix may not succeed a vowel but next char is one + break; + } + + if (!$map[3] && !$nextIsVowel) { + // suffix may not succeed a consonant but next char is one + break; + } + } + + $newBase = substr($plural, 0, $pluralLength - $suffixLength); + $newSuffix = $map[4]; + + // Check whether the first character in the plural suffix + // is uppercased. If yes, uppercase the first character in + // the singular suffix too + $firstUpper = ctype_upper($pluralRev[$j - 1]); + + if (\is_array($newSuffix)) { + $singulars = []; + + foreach ($newSuffix as $newSuffixEntry) { + $singulars[] = $newBase.($firstUpper ? ucfirst($newSuffixEntry) : $newSuffixEntry); + } + + return $singulars; + } + + return [$newBase.($firstUpper ? ucfirst($newSuffix) : $newSuffix)]; + } + + // Suffix is longer than word + if ($j === $pluralLength) { + break; + } + } + } + + // Assume that plural and singular is identical + return [$plural]; + } + + public function pluralize(string $singular): array + { + $singularRev = strrev($singular); + $lowerSingularRev = strtolower($singularRev); + $singularLength = \strlen($lowerSingularRev); + + // Check if the word is one which is not inflected, return early if so + if (\in_array($lowerSingularRev, self::UNINFLECTED, true)) { + return [$singular]; + } + + // The outer loop iterates over the entries of the singular table + // The inner loop $j iterates over the characters of the singular suffix + // in the singular table to compare them with the characters of the actual + // given singular suffix + foreach (self::SINGULAR_MAP as $map) { + $suffix = $map[0]; + $suffixLength = $map[1]; + $j = 0; + + // Compare characters in the singular table and of the suffix of the + // given plural one by one + + while ($suffix[$j] === $lowerSingularRev[$j]) { + // Let $j point to the next character + ++$j; + + // Successfully compared the last character + // Add an entry with the plural suffix to the plural array + if ($j === $suffixLength) { + // Is there any character preceding the suffix in the plural string? + if ($j < $singularLength) { + $nextIsVowel = str_contains('aeiou', $lowerSingularRev[$j]); + + if (!$map[2] && $nextIsVowel) { + // suffix may not succeed a vowel but next char is one + break; + } + + if (!$map[3] && !$nextIsVowel) { + // suffix may not succeed a consonant but next char is one + break; + } + } + + $newBase = substr($singular, 0, $singularLength - $suffixLength); + $newSuffix = $map[4]; + + // Check whether the first character in the singular suffix + // is uppercased. If yes, uppercase the first character in + // the singular suffix too + $firstUpper = ctype_upper($singularRev[$j - 1]); + + if (\is_array($newSuffix)) { + $plurals = []; + + foreach ($newSuffix as $newSuffixEntry) { + $plurals[] = $newBase.($firstUpper ? ucfirst($newSuffixEntry) : $newSuffixEntry); + } + + return $plurals; + } + + return [$newBase.($firstUpper ? ucfirst($newSuffix) : $newSuffix)]; + } + + // Suffix is longer than word + if ($j === $singularLength) { + break; + } + } + } + + // Assume that plural is singular with a trailing `s` + return [$singular.'s']; + } +} diff --git a/vendor/symfony/string/Inflector/FrenchInflector.php b/vendor/symfony/string/Inflector/FrenchInflector.php new file mode 100644 index 00000000..955abbf4 --- /dev/null +++ b/vendor/symfony/string/Inflector/FrenchInflector.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String\Inflector; + +/** + * French inflector. + * + * This class does only inflect nouns; not adjectives nor composed words like "soixante-dix". + */ +final class FrenchInflector implements InflectorInterface +{ + /** + * A list of all rules for pluralise. + * + * @see https://la-conjugaison.nouvelobs.com/regles/grammaire/le-pluriel-des-noms-121.php + */ + private const PLURALIZE_REGEXP = [ + // First entry: regexp + // Second entry: replacement + + // Words finishing with "s", "x" or "z" are invariables + // Les mots finissant par "s", "x" ou "z" sont invariables + ['/(s|x|z)$/i', '\1'], + + // Words finishing with "eau" are pluralized with a "x" + // Les mots finissant par "eau" prennent tous un "x" au pluriel + ['/(eau)$/i', '\1x'], + + // Words finishing with "au" are pluralized with a "x" excepted "landau" + // Les mots finissant par "au" prennent un "x" au pluriel sauf "landau" + ['/^(landau)$/i', '\1s'], + ['/(au)$/i', '\1x'], + + // Words finishing with "eu" are pluralized with a "x" excepted "pneu", "bleu", "émeu" + // Les mots finissant en "eu" prennent un "x" au pluriel sauf "pneu", "bleu", "émeu" + ['/^(pneu|bleu|émeu)$/i', '\1s'], + ['/(eu)$/i', '\1x'], + + // Words finishing with "al" are pluralized with a "aux" excepted + // Les mots finissant en "al" se terminent en "aux" sauf + ['/^(bal|carnaval|caracal|chacal|choral|corral|étal|festival|récital|val)$/i', '\1s'], + ['/al$/i', '\1aux'], + + // Aspirail, bail, corail, émail, fermail, soupirail, travail, vantail et vitrail font leur pluriel en -aux + ['/^(aspir|b|cor|ém|ferm|soupir|trav|vant|vitr)ail$/i', '\1aux'], + + // Bijou, caillou, chou, genou, hibou, joujou et pou qui prennent un x au pluriel + ['/^(bij|caill|ch|gen|hib|jouj|p)ou$/i', '\1oux'], + + // Invariable words + ['/^(cinquante|soixante|mille)$/i', '\1'], + + // French titles + ['/^(mon|ma)(sieur|dame|demoiselle|seigneur)$/', 'mes\2s'], + ['/^(Mon|Ma)(sieur|dame|demoiselle|seigneur)$/', 'Mes\2s'], + ]; + + /** + * A list of all rules for singularize. + */ + private const SINGULARIZE_REGEXP = [ + // First entry: regexp + // Second entry: replacement + + // Aspirail, bail, corail, émail, fermail, soupirail, travail, vantail et vitrail font leur pluriel en -aux + ['/((aspir|b|cor|ém|ferm|soupir|trav|vant|vitr))aux$/i', '\1ail'], + + // Words finishing with "eau" are pluralized with a "x" + // Les mots finissant par "eau" prennent tous un "x" au pluriel + ['/(eau)x$/i', '\1'], + + // Words finishing with "al" are pluralized with a "aux" expected + // Les mots finissant en "al" se terminent en "aux" sauf + ['/(amir|anim|arsen|boc|can|capit|capor|chev|crist|génér|hopit|hôpit|idé|journ|littor|loc|m|mét|minér|princip|radic|termin)aux$/i', '\1al'], + + // Words finishing with "au" are pluralized with a "x" excepted "landau" + // Les mots finissant par "au" prennent un "x" au pluriel sauf "landau" + ['/(au)x$/i', '\1'], + + // Words finishing with "eu" are pluralized with a "x" excepted "pneu", "bleu", "émeu" + // Les mots finissant en "eu" prennent un "x" au pluriel sauf "pneu", "bleu", "émeu" + ['/(eu)x$/i', '\1'], + + // Words finishing with "ou" are pluralized with a "s" excepted bijou, caillou, chou, genou, hibou, joujou, pou + // Les mots finissant par "ou" prennent un "s" sauf bijou, caillou, chou, genou, hibou, joujou, pou + ['/(bij|caill|ch|gen|hib|jouj|p)oux$/i', '\1ou'], + + // French titles + ['/^mes(dame|demoiselle)s$/', 'ma\1'], + ['/^Mes(dame|demoiselle)s$/', 'Ma\1'], + ['/^mes(sieur|seigneur)s$/', 'mon\1'], + ['/^Mes(sieur|seigneur)s$/', 'Mon\1'], + + // Default rule + ['/s$/i', ''], + ]; + + /** + * A list of words which should not be inflected. + * This list is only used by singularize. + */ + private const UNINFLECTED = '/^(abcès|accès|abus|albatros|anchois|anglais|autobus|bois|brebis|carquois|cas|chas|colis|concours|corps|cours|cyprès|décès|devis|discours|dos|embarras|engrais|entrelacs|excès|fils|fois|gâchis|gars|glas|héros|intrus|jars|jus|kermès|lacis|legs|lilas|marais|mars|matelas|mépris|mets|mois|mors|obus|os|palais|paradis|parcours|pardessus|pays|plusieurs|poids|pois|pouls|printemps|processus|progrès|puits|pus|rabais|radis|recors|recours|refus|relais|remords|remous|rictus|rhinocéros|repas|rubis|sans|sas|secours|sens|souris|succès|talus|tapis|tas|taudis|temps|tiers|univers|velours|verglas|vernis|virus)$/i'; + + public function singularize(string $plural): array + { + if ($this->isInflectedWord($plural)) { + return [$plural]; + } + + foreach (self::SINGULARIZE_REGEXP as $rule) { + [$regexp, $replace] = $rule; + + if (1 === preg_match($regexp, $plural)) { + return [preg_replace($regexp, $replace, $plural)]; + } + } + + return [$plural]; + } + + public function pluralize(string $singular): array + { + if ($this->isInflectedWord($singular)) { + return [$singular]; + } + + foreach (self::PLURALIZE_REGEXP as $rule) { + [$regexp, $replace] = $rule; + + if (1 === preg_match($regexp, $singular)) { + return [preg_replace($regexp, $replace, $singular)]; + } + } + + return [$singular.'s']; + } + + private function isInflectedWord(string $word): bool + { + return 1 === preg_match(self::UNINFLECTED, $word); + } +} diff --git a/vendor/symfony/string/Inflector/InflectorInterface.php b/vendor/symfony/string/Inflector/InflectorInterface.php new file mode 100644 index 00000000..67f28340 --- /dev/null +++ b/vendor/symfony/string/Inflector/InflectorInterface.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String\Inflector; + +interface InflectorInterface +{ + /** + * Returns the singular forms of a string. + * + * If the method can't determine the form with certainty, several possible singulars are returned. + * + * @return string[] + */ + public function singularize(string $plural): array; + + /** + * Returns the plural forms of a string. + * + * If the method can't determine the form with certainty, several possible plurals are returned. + * + * @return string[] + */ + public function pluralize(string $singular): array; +} diff --git a/vendor/symfony/string/LICENSE b/vendor/symfony/string/LICENSE new file mode 100644 index 00000000..f37c76b5 --- /dev/null +++ b/vendor/symfony/string/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2019-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/string/LazyString.php b/vendor/symfony/string/LazyString.php new file mode 100644 index 00000000..519b54ab --- /dev/null +++ b/vendor/symfony/string/LazyString.php @@ -0,0 +1,145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String; + +/** + * A string whose value is computed lazily by a callback. + * + * @author Nicolas Grekas + */ +class LazyString implements \Stringable, \JsonSerializable +{ + private \Closure|string $value; + + /** + * @param callable|array $callback A callable or a [Closure, method] lazy-callable + */ + public static function fromCallable(callable|array $callback, mixed ...$arguments): static + { + if (\is_array($callback) && !\is_callable($callback) && !(($callback[0] ?? null) instanceof \Closure || 2 < \count($callback))) { + throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be a callable or a [Closure, method] lazy-callable, "%s" given.', __METHOD__, '['.implode(', ', array_map('get_debug_type', $callback)).']')); + } + + $lazyString = new static(); + $lazyString->value = static function () use (&$callback, &$arguments): string { + static $value; + + if (null !== $arguments) { + if (!\is_callable($callback)) { + $callback[0] = $callback[0](); + $callback[1] ??= '__invoke'; + } + $value = $callback(...$arguments); + $callback = !\is_scalar($value) && !$value instanceof \Stringable ? self::getPrettyName($callback) : 'callable'; + $arguments = null; + } + + return $value ?? ''; + }; + + return $lazyString; + } + + public static function fromStringable(string|int|float|bool|\Stringable $value): static + { + if (\is_object($value)) { + return static::fromCallable($value->__toString(...)); + } + + $lazyString = new static(); + $lazyString->value = (string) $value; + + return $lazyString; + } + + /** + * Tells whether the provided value can be cast to string. + */ + final public static function isStringable(mixed $value): bool + { + return \is_string($value) || $value instanceof \Stringable || \is_scalar($value); + } + + /** + * Casts scalars and stringable objects to strings. + * + * @throws \TypeError When the provided value is not stringable + */ + final public static function resolve(\Stringable|string|int|float|bool $value): string + { + return $value; + } + + public function __toString(): string + { + if (\is_string($this->value)) { + return $this->value; + } + + try { + return $this->value = ($this->value)(); + } catch (\Throwable $e) { + if (\TypeError::class === $e::class && __FILE__ === $e->getFile()) { + $type = explode(', ', $e->getMessage()); + $type = substr(array_pop($type), 0, -\strlen(' returned')); + $r = new \ReflectionFunction($this->value); + $callback = $r->getStaticVariables()['callback']; + + $e = new \TypeError(sprintf('Return value of %s() passed to %s::fromCallable() must be of the type string, %s returned.', $callback, static::class, $type)); + } + + throw $e; + } + } + + public function __sleep(): array + { + $this->__toString(); + + return ['value']; + } + + public function jsonSerialize(): string + { + return $this->__toString(); + } + + private function __construct() + { + } + + private static function getPrettyName(callable $callback): string + { + if (\is_string($callback)) { + return $callback; + } + + if (\is_array($callback)) { + $class = \is_object($callback[0]) ? get_debug_type($callback[0]) : $callback[0]; + $method = $callback[1]; + } elseif ($callback instanceof \Closure) { + $r = new \ReflectionFunction($callback); + + if (str_contains($r->name, '{closure}') || !$class = $r->getClosureCalledClass()) { + return $r->name; + } + + $class = $class->name; + $method = $r->name; + } else { + $class = get_debug_type($callback); + $method = '__invoke'; + } + + return $class.'::'.$method; + } +} diff --git a/vendor/symfony/string/README.md b/vendor/symfony/string/README.md new file mode 100644 index 00000000..9c7e1e19 --- /dev/null +++ b/vendor/symfony/string/README.md @@ -0,0 +1,14 @@ +String Component +================ + +The String component provides an object-oriented API to strings and deals +with bytes, UTF-8 code points and grapheme clusters in a unified way. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/string.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/string/Resources/data/wcswidth_table_wide.php b/vendor/symfony/string/Resources/data/wcswidth_table_wide.php new file mode 100644 index 00000000..8314c8fd --- /dev/null +++ b/vendor/symfony/string/Resources/data/wcswidth_table_wide.php @@ -0,0 +1,1155 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String; + +if (!\function_exists(u::class)) { + function u(?string $string = ''): UnicodeString + { + return new UnicodeString($string ?? ''); + } +} + +if (!\function_exists(b::class)) { + function b(?string $string = ''): ByteString + { + return new ByteString($string ?? ''); + } +} + +if (!\function_exists(s::class)) { + /** + * @return UnicodeString|ByteString + */ + function s(?string $string = ''): AbstractString + { + $string ??= ''; + + return preg_match('//u', $string) ? new UnicodeString($string) : new ByteString($string); + } +} diff --git a/vendor/symfony/string/Slugger/AsciiSlugger.php b/vendor/symfony/string/Slugger/AsciiSlugger.php new file mode 100644 index 00000000..fd47770c --- /dev/null +++ b/vendor/symfony/string/Slugger/AsciiSlugger.php @@ -0,0 +1,207 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String\Slugger; + +use Symfony\Component\Intl\Transliterator\EmojiTransliterator; +use Symfony\Component\String\AbstractUnicodeString; +use Symfony\Component\String\UnicodeString; +use Symfony\Contracts\Translation\LocaleAwareInterface; + +if (!interface_exists(LocaleAwareInterface::class)) { + throw new \LogicException('You cannot use the "Symfony\Component\String\Slugger\AsciiSlugger" as the "symfony/translation-contracts" package is not installed. Try running "composer require symfony/translation-contracts".'); +} + +/** + * @author Titouan Galopin + */ +class AsciiSlugger implements SluggerInterface, LocaleAwareInterface +{ + private const LOCALE_TO_TRANSLITERATOR_ID = [ + 'am' => 'Amharic-Latin', + 'ar' => 'Arabic-Latin', + 'az' => 'Azerbaijani-Latin', + 'be' => 'Belarusian-Latin', + 'bg' => 'Bulgarian-Latin', + 'bn' => 'Bengali-Latin', + 'de' => 'de-ASCII', + 'el' => 'Greek-Latin', + 'fa' => 'Persian-Latin', + 'he' => 'Hebrew-Latin', + 'hy' => 'Armenian-Latin', + 'ka' => 'Georgian-Latin', + 'kk' => 'Kazakh-Latin', + 'ky' => 'Kirghiz-Latin', + 'ko' => 'Korean-Latin', + 'mk' => 'Macedonian-Latin', + 'mn' => 'Mongolian-Latin', + 'or' => 'Oriya-Latin', + 'ps' => 'Pashto-Latin', + 'ru' => 'Russian-Latin', + 'sr' => 'Serbian-Latin', + 'sr_Cyrl' => 'Serbian-Latin', + 'th' => 'Thai-Latin', + 'tk' => 'Turkmen-Latin', + 'uk' => 'Ukrainian-Latin', + 'uz' => 'Uzbek-Latin', + 'zh' => 'Han-Latin', + ]; + + private ?string $defaultLocale; + private \Closure|array $symbolsMap = [ + 'en' => ['@' => 'at', '&' => 'and'], + ]; + private bool|string $emoji = false; + + /** + * Cache of transliterators per locale. + * + * @var \Transliterator[] + */ + private array $transliterators = []; + + public function __construct(?string $defaultLocale = null, array|\Closure|null $symbolsMap = null) + { + $this->defaultLocale = $defaultLocale; + $this->symbolsMap = $symbolsMap ?? $this->symbolsMap; + } + + public function setLocale(string $locale): void + { + $this->defaultLocale = $locale; + } + + public function getLocale(): string + { + return $this->defaultLocale; + } + + /** + * @param bool|string $emoji true will use the same locale, + * false will disable emoji, + * and a string to use a specific locale + */ + public function withEmoji(bool|string $emoji = true): static + { + if (false !== $emoji && !class_exists(EmojiTransliterator::class)) { + throw new \LogicException(sprintf('You cannot use the "%s()" method as the "symfony/intl" package is not installed. Try running "composer require symfony/intl".', __METHOD__)); + } + + $new = clone $this; + $new->emoji = $emoji; + + return $new; + } + + public function slug(string $string, string $separator = '-', ?string $locale = null): AbstractUnicodeString + { + $locale ??= $this->defaultLocale; + + $transliterator = []; + if ($locale && ('de' === $locale || str_starts_with($locale, 'de_'))) { + // Use the shortcut for German in UnicodeString::ascii() if possible (faster and no requirement on intl) + $transliterator = ['de-ASCII']; + } elseif (\function_exists('transliterator_transliterate') && $locale) { + $transliterator = (array) $this->createTransliterator($locale); + } + + if ($emojiTransliterator = $this->createEmojiTransliterator($locale)) { + $transliterator[] = $emojiTransliterator; + } + + if ($this->symbolsMap instanceof \Closure) { + // If the symbols map is passed as a closure, there is no need to fallback to the parent locale + // as the closure can just provide substitutions for all locales of interest. + $symbolsMap = $this->symbolsMap; + array_unshift($transliterator, static fn ($s) => $symbolsMap($s, $locale)); + } + + $unicodeString = (new UnicodeString($string))->ascii($transliterator); + + if (\is_array($this->symbolsMap)) { + $map = null; + if (isset($this->symbolsMap[$locale])) { + $map = $this->symbolsMap[$locale]; + } else { + $parent = self::getParentLocale($locale); + if ($parent && isset($this->symbolsMap[$parent])) { + $map = $this->symbolsMap[$parent]; + } + } + if ($map) { + foreach ($map as $char => $replace) { + $unicodeString = $unicodeString->replace($char, ' '.$replace.' '); + } + } + } + + return $unicodeString + ->replaceMatches('/[^A-Za-z0-9]++/', $separator) + ->trim($separator) + ; + } + + private function createTransliterator(string $locale): ?\Transliterator + { + if (\array_key_exists($locale, $this->transliterators)) { + return $this->transliterators[$locale]; + } + + // Exact locale supported, cache and return + if ($id = self::LOCALE_TO_TRANSLITERATOR_ID[$locale] ?? null) { + return $this->transliterators[$locale] = \Transliterator::create($id.'/BGN') ?? \Transliterator::create($id); + } + + // Locale not supported and no parent, fallback to any-latin + if (!$parent = self::getParentLocale($locale)) { + return $this->transliterators[$locale] = null; + } + + // Try to use the parent locale (ie. try "de" for "de_AT") and cache both locales + if ($id = self::LOCALE_TO_TRANSLITERATOR_ID[$parent] ?? null) { + $transliterator = \Transliterator::create($id.'/BGN') ?? \Transliterator::create($id); + } + + return $this->transliterators[$locale] = $this->transliterators[$parent] = $transliterator ?? null; + } + + private function createEmojiTransliterator(?string $locale): ?EmojiTransliterator + { + if (\is_string($this->emoji)) { + $locale = $this->emoji; + } elseif (!$this->emoji) { + return null; + } + + while (null !== $locale) { + try { + return EmojiTransliterator::create("emoji-$locale"); + } catch (\IntlException) { + $locale = self::getParentLocale($locale); + } + } + + return null; + } + + private static function getParentLocale(?string $locale): ?string + { + if (!$locale) { + return null; + } + if (false === $str = strrchr($locale, '_')) { + // no parent locale + return null; + } + + return substr($locale, 0, -\strlen($str)); + } +} diff --git a/vendor/symfony/string/Slugger/SluggerInterface.php b/vendor/symfony/string/Slugger/SluggerInterface.php new file mode 100644 index 00000000..dd0d5810 --- /dev/null +++ b/vendor/symfony/string/Slugger/SluggerInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String\Slugger; + +use Symfony\Component\String\AbstractUnicodeString; + +/** + * Creates a URL-friendly slug from a given string. + * + * @author Titouan Galopin + */ +interface SluggerInterface +{ + /** + * Creates a slug for the given string and locale, using appropriate transliteration when needed. + */ + public function slug(string $string, string $separator = '-', ?string $locale = null): AbstractUnicodeString; +} diff --git a/vendor/symfony/string/UnicodeString.php b/vendor/symfony/string/UnicodeString.php new file mode 100644 index 00000000..4b16caf9 --- /dev/null +++ b/vendor/symfony/string/UnicodeString.php @@ -0,0 +1,382 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String; + +use Symfony\Component\String\Exception\ExceptionInterface; +use Symfony\Component\String\Exception\InvalidArgumentException; + +/** + * Represents a string of Unicode grapheme clusters encoded as UTF-8. + * + * A letter followed by combining characters (accents typically) form what Unicode defines + * as a grapheme cluster: a character as humans mean it in written texts. This class knows + * about the concept and won't split a letter apart from its combining accents. It also + * ensures all string comparisons happen on their canonically-composed representation, + * ignoring e.g. the order in which accents are listed when a letter has many of them. + * + * @see https://unicode.org/reports/tr15/ + * + * @author Nicolas Grekas + * @author Hugo Hamon + * + * @throws ExceptionInterface + */ +class UnicodeString extends AbstractUnicodeString +{ + public function __construct(string $string = '') + { + if ('' === $string || normalizer_is_normalized($this->string = $string)) { + return; + } + + if (false === $string = normalizer_normalize($string)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + $this->string = $string; + } + + public function append(string ...$suffix): static + { + $str = clone $this; + $str->string = $this->string.(1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix)); + + if (normalizer_is_normalized($str->string)) { + return $str; + } + + if (false === $string = normalizer_normalize($str->string)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + $str->string = $string; + + return $str; + } + + public function chunk(int $length = 1): array + { + if (1 > $length) { + throw new InvalidArgumentException('The chunk length must be greater than zero.'); + } + + if ('' === $this->string) { + return []; + } + + $rx = '/('; + while (65535 < $length) { + $rx .= '\X{65535}'; + $length -= 65535; + } + $rx .= '\X{'.$length.'})/u'; + + $str = clone $this; + $chunks = []; + + foreach (preg_split($rx, $this->string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY) as $chunk) { + $str->string = $chunk; + $chunks[] = clone $str; + } + + return $chunks; + } + + public function endsWith(string|iterable|AbstractString $suffix): bool + { + if ($suffix instanceof AbstractString) { + $suffix = $suffix->string; + } elseif (!\is_string($suffix)) { + return parent::endsWith($suffix); + } + + $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC; + normalizer_is_normalized($suffix, $form) ?: $suffix = normalizer_normalize($suffix, $form); + + if ('' === $suffix || false === $suffix) { + return false; + } + + if ($this->ignoreCase) { + return 0 === mb_stripos(grapheme_extract($this->string, \strlen($suffix), \GRAPHEME_EXTR_MAXBYTES, \strlen($this->string) - \strlen($suffix)), $suffix, 0, 'UTF-8'); + } + + return $suffix === grapheme_extract($this->string, \strlen($suffix), \GRAPHEME_EXTR_MAXBYTES, \strlen($this->string) - \strlen($suffix)); + } + + public function equalsTo(string|iterable|AbstractString $string): bool + { + if ($string instanceof AbstractString) { + $string = $string->string; + } elseif (!\is_string($string)) { + return parent::equalsTo($string); + } + + $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC; + normalizer_is_normalized($string, $form) ?: $string = normalizer_normalize($string, $form); + + if ('' !== $string && false !== $string && $this->ignoreCase) { + return \strlen($string) === \strlen($this->string) && 0 === mb_stripos($this->string, $string, 0, 'UTF-8'); + } + + return $string === $this->string; + } + + public function indexOf(string|iterable|AbstractString $needle, int $offset = 0): ?int + { + if ($needle instanceof AbstractString) { + $needle = $needle->string; + } elseif (!\is_string($needle)) { + return parent::indexOf($needle, $offset); + } + + $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC; + normalizer_is_normalized($needle, $form) ?: $needle = normalizer_normalize($needle, $form); + + if ('' === $needle || false === $needle) { + return null; + } + + try { + $i = $this->ignoreCase ? grapheme_stripos($this->string, $needle, $offset) : grapheme_strpos($this->string, $needle, $offset); + } catch (\ValueError) { + return null; + } + + return false === $i ? null : $i; + } + + public function indexOfLast(string|iterable|AbstractString $needle, int $offset = 0): ?int + { + if ($needle instanceof AbstractString) { + $needle = $needle->string; + } elseif (!\is_string($needle)) { + return parent::indexOfLast($needle, $offset); + } + + $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC; + normalizer_is_normalized($needle, $form) ?: $needle = normalizer_normalize($needle, $form); + + if ('' === $needle || false === $needle) { + return null; + } + + $string = $this->string; + + if (0 > $offset) { + // workaround https://bugs.php.net/74264 + if (0 > $offset += grapheme_strlen($needle)) { + $string = grapheme_substr($string, 0, $offset); + } + $offset = 0; + } + + $i = $this->ignoreCase ? grapheme_strripos($string, $needle, $offset) : grapheme_strrpos($string, $needle, $offset); + + return false === $i ? null : $i; + } + + public function join(array $strings, ?string $lastGlue = null): static + { + $str = parent::join($strings, $lastGlue); + normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string); + + return $str; + } + + public function length(): int + { + return grapheme_strlen($this->string); + } + + public function normalize(int $form = self::NFC): static + { + $str = clone $this; + + if (\in_array($form, [self::NFC, self::NFKC], true)) { + normalizer_is_normalized($str->string, $form) ?: $str->string = normalizer_normalize($str->string, $form); + } elseif (!\in_array($form, [self::NFD, self::NFKD], true)) { + throw new InvalidArgumentException('Unsupported normalization form.'); + } elseif (!normalizer_is_normalized($str->string, $form)) { + $str->string = normalizer_normalize($str->string, $form); + $str->ignoreCase = null; + } + + return $str; + } + + public function prepend(string ...$prefix): static + { + $str = clone $this; + $str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$this->string; + + if (normalizer_is_normalized($str->string)) { + return $str; + } + + if (false === $string = normalizer_normalize($str->string)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + $str->string = $string; + + return $str; + } + + public function replace(string $from, string $to): static + { + $str = clone $this; + normalizer_is_normalized($from) ?: $from = normalizer_normalize($from); + + if ('' !== $from && false !== $from) { + $tail = $str->string; + $result = ''; + $indexOf = $this->ignoreCase ? 'grapheme_stripos' : 'grapheme_strpos'; + + while ('' !== $tail && false !== $i = $indexOf($tail, $from)) { + $slice = grapheme_substr($tail, 0, $i); + $result .= $slice.$to; + $tail = substr($tail, \strlen($slice) + \strlen($from)); + } + + $str->string = $result.$tail; + + if (normalizer_is_normalized($str->string)) { + return $str; + } + + if (false === $string = normalizer_normalize($str->string)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + $str->string = $string; + } + + return $str; + } + + public function replaceMatches(string $fromRegexp, string|callable $to): static + { + $str = parent::replaceMatches($fromRegexp, $to); + normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string); + + return $str; + } + + public function slice(int $start = 0, ?int $length = null): static + { + $str = clone $this; + + $str->string = (string) grapheme_substr($this->string, $start, $length ?? 2147483647); + + return $str; + } + + public function splice(string $replacement, int $start = 0, ?int $length = null): static + { + $str = clone $this; + + $start = $start ? \strlen(grapheme_substr($this->string, 0, $start)) : 0; + $length = $length ? \strlen(grapheme_substr($this->string, $start, $length ?? 2147483647)) : $length; + $str->string = substr_replace($this->string, $replacement, $start, $length ?? 2147483647); + + if (normalizer_is_normalized($str->string)) { + return $str; + } + + if (false === $string = normalizer_normalize($str->string)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + $str->string = $string; + + return $str; + } + + public function split(string $delimiter, ?int $limit = null, ?int $flags = null): array + { + if (1 > $limit ??= 2147483647) { + throw new InvalidArgumentException('Split limit must be a positive integer.'); + } + + if ('' === $delimiter) { + throw new InvalidArgumentException('Split delimiter is empty.'); + } + + if (null !== $flags) { + return parent::split($delimiter.'u', $limit, $flags); + } + + normalizer_is_normalized($delimiter) ?: $delimiter = normalizer_normalize($delimiter); + + if (false === $delimiter) { + throw new InvalidArgumentException('Split delimiter is not a valid UTF-8 string.'); + } + + $str = clone $this; + $tail = $this->string; + $chunks = []; + $indexOf = $this->ignoreCase ? 'grapheme_stripos' : 'grapheme_strpos'; + + while (1 < $limit && false !== $i = $indexOf($tail, $delimiter)) { + $str->string = grapheme_substr($tail, 0, $i); + $chunks[] = clone $str; + $tail = substr($tail, \strlen($str->string) + \strlen($delimiter)); + --$limit; + } + + $str->string = $tail; + $chunks[] = clone $str; + + return $chunks; + } + + public function startsWith(string|iterable|AbstractString $prefix): bool + { + if ($prefix instanceof AbstractString) { + $prefix = $prefix->string; + } elseif (!\is_string($prefix)) { + return parent::startsWith($prefix); + } + + $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC; + normalizer_is_normalized($prefix, $form) ?: $prefix = normalizer_normalize($prefix, $form); + + if ('' === $prefix || false === $prefix) { + return false; + } + + if ($this->ignoreCase) { + return 0 === mb_stripos(grapheme_extract($this->string, \strlen($prefix), \GRAPHEME_EXTR_MAXBYTES), $prefix, 0, 'UTF-8'); + } + + return $prefix === grapheme_extract($this->string, \strlen($prefix), \GRAPHEME_EXTR_MAXBYTES); + } + + public function __wakeup(): void + { + if (!\is_string($this->string)) { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + normalizer_is_normalized($this->string) ?: $this->string = normalizer_normalize($this->string); + } + + public function __clone() + { + if (null === $this->ignoreCase) { + normalizer_is_normalized($this->string) ?: $this->string = normalizer_normalize($this->string); + } + + $this->ignoreCase = false; + } +} diff --git a/vendor/symfony/string/composer.json b/vendor/symfony/string/composer.json new file mode 100644 index 00000000..26ce26da --- /dev/null +++ b/vendor/symfony/string/composer.json @@ -0,0 +1,43 @@ +{ + "name": "symfony/string", + "type": "library", + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "keywords": ["string", "utf8", "utf-8", "grapheme", "i18n", "unicode"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "symfony/error-handler": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\String\\": "" }, + "files": [ "Resources/functions.php" ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/theseer/tokenizer/CHANGELOG.md b/vendor/theseer/tokenizer/CHANGELOG.md new file mode 100644 index 00000000..d867649f --- /dev/null +++ b/vendor/theseer/tokenizer/CHANGELOG.md @@ -0,0 +1,87 @@ +# Changelog + +All notable changes to Tokenizer are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. + +## [1.2.3] - 2024-03-03 + +### Changed + +* Do not use implicitly nullable parameters + +## [1.2.2] - 2023-11-20 + +### Fixed + +* [#18](https://github.com/theseer/tokenizer/issues/18): Tokenizer fails on protobuf metadata files + + +## [1.2.1] - 2021-07-28 + +### Fixed + +* [#13](https://github.com/theseer/tokenizer/issues/13): Fatal error when tokenizing files that contain only a single empty line + + +## [1.2.0] - 2020-07-13 + +This release is now PHP 8.0 compliant. + +### Fixed + +* Whitespace handling in general (only noticable in the intermediate `TokenCollection`) is now consitent + +### Changed + +* Updated `Tokenizer` to deal with changed whitespace handling in PHP 8.0 + The XMLSerializer was unaffected. + + +## [1.1.3] - 2019-06-14 + +### Changed + +* Ensure XMLSerializer can deal with empty token collections + +### Fixed + +* [#2](https://github.com/theseer/tokenizer/issues/2): Fatal error in infection / phpunit + + +## [1.1.2] - 2019-04-04 + +### Changed + +* Reverted PHPUnit 8 test update to stay PHP 7.0 compliant + + +## [1.1.1] - 2019-04-03 + +### Fixed + +* [#1](https://github.com/theseer/tokenizer/issues/1): Empty file causes invalid array read + +### Changed + +* Tests should now be PHPUnit 8 compliant + + +## [1.1.0] - 2017-04-07 + +### Added + +* Allow use of custom namespace for XML serialization + + +## [1.0.0] - 2017-04-05 + +Initial Release + +[1.2.3]: https://github.com/theseer/tokenizer/compare/1.2.2...1.2.3 +[1.2.2]: https://github.com/theseer/tokenizer/compare/1.2.1...1.2.2 +[1.2.1]: https://github.com/theseer/tokenizer/compare/1.2.0...1.2.1 +[1.2.0]: https://github.com/theseer/tokenizer/compare/1.1.3...1.2.0 +[1.1.3]: https://github.com/theseer/tokenizer/compare/1.1.2...1.1.3 +[1.1.2]: https://github.com/theseer/tokenizer/compare/1.1.1...1.1.2 +[1.1.1]: https://github.com/theseer/tokenizer/compare/1.1.0...1.1.1 +[1.1.0]: https://github.com/theseer/tokenizer/compare/1.0.0...1.1.0 +[1.0.0]: https://github.com/theseer/tokenizer/compare/b2493e57de80c1b7414219b28503fa5c6b4d0a98...1.0.0 diff --git a/vendor/theseer/tokenizer/LICENSE b/vendor/theseer/tokenizer/LICENSE new file mode 100644 index 00000000..e9694ad6 --- /dev/null +++ b/vendor/theseer/tokenizer/LICENSE @@ -0,0 +1,30 @@ +Tokenizer + +Copyright (c) 2017 Arne Blankerts and contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of Arne Blankerts nor the names of contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, +OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/theseer/tokenizer/README.md b/vendor/theseer/tokenizer/README.md new file mode 100644 index 00000000..a5f891b4 --- /dev/null +++ b/vendor/theseer/tokenizer/README.md @@ -0,0 +1,47 @@ +# Tokenizer + +A small library for converting tokenized PHP source code into XML. + +[![Test](https://github.com/theseer/tokenizer/actions/workflows/ci.yml/badge.svg)](https://github.com/theseer/tokenizer/actions/workflows/ci.yml) + +## Installation + +You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): + + composer require theseer/tokenizer + +If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency: + + composer require --dev theseer/tokenizer + +## Usage examples + +```php +$tokenizer = new TheSeer\Tokenizer\Tokenizer(); +$tokens = $tokenizer->parse(file_get_contents(__DIR__ . '/src/XMLSerializer.php')); + +$serializer = new TheSeer\Tokenizer\XMLSerializer(); +$xml = $serializer->toXML($tokens); + +echo $xml; +``` + +The generated XML structure looks something like this: + +```xml + + + + <?php + declare + ( + strict_types + + = + + 1 + ) + ; + + +``` diff --git a/vendor/theseer/tokenizer/composer.json b/vendor/theseer/tokenizer/composer.json new file mode 100644 index 00000000..3f452a9f --- /dev/null +++ b/vendor/theseer/tokenizer/composer.json @@ -0,0 +1,27 @@ +{ + "name": "theseer/tokenizer", + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "support": { + "issues": "https://github.com/theseer/tokenizer/issues" + }, + "require": { + "php": "^7.2 || ^8.0", + "ext-xmlwriter": "*", + "ext-dom": "*", + "ext-tokenizer": "*" + }, + "autoload": { + "classmap": [ + "src/" + ] + } +} + diff --git a/vendor/theseer/tokenizer/composer.lock b/vendor/theseer/tokenizer/composer.lock new file mode 100644 index 00000000..07fba9b5 --- /dev/null +++ b/vendor/theseer/tokenizer/composer.lock @@ -0,0 +1,22 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "b010f1b3d9d47d431ee1cb54ac1de755", + "packages": [], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^7.2 || ^8.0", + "ext-xmlwriter": "*", + "ext-dom": "*", + "ext-tokenizer": "*" + }, + "platform-dev": [] +} diff --git a/vendor/theseer/tokenizer/src/Exception.php b/vendor/theseer/tokenizer/src/Exception.php new file mode 100644 index 00000000..71fc117a --- /dev/null +++ b/vendor/theseer/tokenizer/src/Exception.php @@ -0,0 +1,5 @@ +ensureValidUri($value); + $this->value = $value; + } + + public function asString(): string { + return $this->value; + } + + private function ensureValidUri($value): void { + if (\strpos($value, ':') === false) { + throw new NamespaceUriException( + \sprintf("Namespace URI '%s' must contain at least one colon", $value) + ); + } + } +} diff --git a/vendor/theseer/tokenizer/src/NamespaceUriException.php b/vendor/theseer/tokenizer/src/NamespaceUriException.php new file mode 100644 index 00000000..ab1c48d2 --- /dev/null +++ b/vendor/theseer/tokenizer/src/NamespaceUriException.php @@ -0,0 +1,5 @@ +line = $line; + $this->name = $name; + $this->value = $value; + } + + public function getLine(): int { + return $this->line; + } + + public function getName(): string { + return $this->name; + } + + public function getValue(): string { + return $this->value; + } +} diff --git a/vendor/theseer/tokenizer/src/TokenCollection.php b/vendor/theseer/tokenizer/src/TokenCollection.php new file mode 100644 index 00000000..e5e6e401 --- /dev/null +++ b/vendor/theseer/tokenizer/src/TokenCollection.php @@ -0,0 +1,93 @@ +tokens[] = $token; + } + + public function current(): Token { + return \current($this->tokens); + } + + public function key(): int { + return \key($this->tokens); + } + + public function next(): void { + \next($this->tokens); + $this->pos++; + } + + public function valid(): bool { + return $this->count() > $this->pos; + } + + public function rewind(): void { + \reset($this->tokens); + $this->pos = 0; + } + + public function count(): int { + return \count($this->tokens); + } + + public function offsetExists($offset): bool { + return isset($this->tokens[$offset]); + } + + /** + * @throws TokenCollectionException + */ + public function offsetGet($offset): Token { + if (!$this->offsetExists($offset)) { + throw new TokenCollectionException( + \sprintf('No Token at offest %s', $offset) + ); + } + + return $this->tokens[$offset]; + } + + /** + * @param Token $value + * + * @throws TokenCollectionException + */ + public function offsetSet($offset, $value): void { + if (!\is_int($offset)) { + $type = \gettype($offset); + + throw new TokenCollectionException( + \sprintf( + 'Offset must be of type integer, %s given', + $type === 'object' ? \get_class($value) : $type + ) + ); + } + + if (!$value instanceof Token) { + $type = \gettype($value); + + throw new TokenCollectionException( + \sprintf( + 'Value must be of type %s, %s given', + Token::class, + $type === 'object' ? \get_class($value) : $type + ) + ); + } + $this->tokens[$offset] = $value; + } + + public function offsetUnset($offset): void { + unset($this->tokens[$offset]); + } +} diff --git a/vendor/theseer/tokenizer/src/TokenCollectionException.php b/vendor/theseer/tokenizer/src/TokenCollectionException.php new file mode 100644 index 00000000..4291ce0c --- /dev/null +++ b/vendor/theseer/tokenizer/src/TokenCollectionException.php @@ -0,0 +1,5 @@ + 'T_OPEN_BRACKET', + ')' => 'T_CLOSE_BRACKET', + '[' => 'T_OPEN_SQUARE', + ']' => 'T_CLOSE_SQUARE', + '{' => 'T_OPEN_CURLY', + '}' => 'T_CLOSE_CURLY', + ';' => 'T_SEMICOLON', + '.' => 'T_DOT', + ',' => 'T_COMMA', + '=' => 'T_EQUAL', + '<' => 'T_LT', + '>' => 'T_GT', + '+' => 'T_PLUS', + '-' => 'T_MINUS', + '*' => 'T_MULT', + '/' => 'T_DIV', + '?' => 'T_QUESTION_MARK', + '!' => 'T_EXCLAMATION_MARK', + ':' => 'T_COLON', + '"' => 'T_DOUBLE_QUOTES', + '@' => 'T_AT', + '&' => 'T_AMPERSAND', + '%' => 'T_PERCENT', + '|' => 'T_PIPE', + '$' => 'T_DOLLAR', + '^' => 'T_CARET', + '~' => 'T_TILDE', + '`' => 'T_BACKTICK' + ]; + + public function parse(string $source): TokenCollection { + $result = new TokenCollection(); + + if ($source === '') { + return $result; + } + + $tokens = \token_get_all($source); + + $lastToken = new Token( + $tokens[0][2], + 'Placeholder', + '' + ); + + foreach ($tokens as $pos => $tok) { + if (\is_string($tok)) { + $token = new Token( + $lastToken->getLine(), + $this->map[$tok], + $tok + ); + $result->addToken($token); + $lastToken = $token; + + continue; + } + + $line = $tok[2]; + $values = \preg_split('/\R+/Uu', $tok[1]); + + if (!$values) { + $result->addToken( + new Token( + $line, + \token_name($tok[0]), + '{binary data}' + ) + ); + + continue; + } + + foreach ($values as $v) { + $token = new Token( + $line, + \token_name($tok[0]), + $v + ); + $lastToken = $token; + $line++; + + if ($v === '') { + continue; + } + + $result->addToken($token); + } + } + + return $this->fillBlanks($result, $lastToken->getLine()); + } + + private function fillBlanks(TokenCollection $tokens, int $maxLine): TokenCollection { + $prev = new Token( + 0, + 'Placeholder', + '' + ); + + $final = new TokenCollection(); + + foreach ($tokens as $token) { + $gap = $token->getLine() - $prev->getLine(); + + while ($gap > 1) { + $linebreak = new Token( + $prev->getLine() + 1, + 'T_WHITESPACE', + '' + ); + $final->addToken($linebreak); + $prev = $linebreak; + $gap--; + } + + $final->addToken($token); + $prev = $token; + } + + $gap = $maxLine - $prev->getLine(); + + while ($gap > 0) { + $linebreak = new Token( + $prev->getLine() + 1, + 'T_WHITESPACE', + '' + ); + $final->addToken($linebreak); + $prev = $linebreak; + $gap--; + } + + return $final; + } +} diff --git a/vendor/theseer/tokenizer/src/XMLSerializer.php b/vendor/theseer/tokenizer/src/XMLSerializer.php new file mode 100644 index 00000000..518bfb06 --- /dev/null +++ b/vendor/theseer/tokenizer/src/XMLSerializer.php @@ -0,0 +1,79 @@ +xmlns = $xmlns; + } + + public function toDom(TokenCollection $tokens): DOMDocument { + $dom = new DOMDocument(); + $dom->preserveWhiteSpace = false; + $dom->loadXML($this->toXML($tokens)); + + return $dom; + } + + public function toXML(TokenCollection $tokens): string { + $this->writer = new \XMLWriter(); + $this->writer->openMemory(); + $this->writer->setIndent(true); + $this->writer->startDocument(); + $this->writer->startElement('source'); + $this->writer->writeAttribute('xmlns', $this->xmlns->asString()); + + if (\count($tokens) > 0) { + $this->writer->startElement('line'); + $this->writer->writeAttribute('no', '1'); + + $this->previousToken = $tokens[0]; + + foreach ($tokens as $token) { + $this->addToken($token); + } + } + + $this->writer->endElement(); + $this->writer->endElement(); + $this->writer->endDocument(); + + return $this->writer->outputMemory(); + } + + private function addToken(Token $token): void { + if ($this->previousToken->getLine() < $token->getLine()) { + $this->writer->endElement(); + + $this->writer->startElement('line'); + $this->writer->writeAttribute('no', (string)$token->getLine()); + $this->previousToken = $token; + } + + if ($token->getValue() !== '') { + $this->writer->startElement('token'); + $this->writer->writeAttribute('name', $token->getName()); + $this->writer->writeRaw(\htmlspecialchars($token->getValue(), \ENT_NOQUOTES | \ENT_DISALLOWED | \ENT_XML1)); + $this->writer->endElement(); + } + } +}